Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: integrated name service contract #77

Merged
merged 30 commits into from
Jun 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1636442
feat: integrated name service contract
Alejandro-Morales Mar 24, 2023
796ba7d
feat: managed repeated name reg
Alejandro-Morales Mar 24, 2023
8a09105
run black
Alejandro-Morales Mar 24, 2023
c651578
feat: only register when needed and bug fix
Alejandro-Morales Mar 28, 2023
1e05c39
fix: pylint
Alejandro-Morales Mar 28, 2023
e84f53d
feat: name ownership check
Alejandro-Morales Mar 29, 2023
3b70f13
Merge branch 'main' into service-contract
Alejandro-Morales Apr 4, 2023
b0e2c52
feat: created contract classes
Alejandro-Morales Apr 5, 2023
a8ecbb6
fix: test_agent_registration
Alejandro-Morales Apr 5, 2023
4bdeba7
feat: added registration tests
Alejandro-Morales Apr 6, 2023
828c52f
added min registration time variable
Alejandro-Morales Apr 6, 2023
1d7bad4
feat: reregister if endpoint chenages
Alejandro-Morales Apr 12, 2023
d738ebc
delete unused S import
Alejandro-Morales Apr 12, 2023
8e96807
Merge branch 'main' into service-contract
Alejandro-Morales Jun 2, 2023
71cc66c
feat: enable communication by agents name
Alejandro-Morales Jun 4, 2023
573abfe
format changes
Alejandro-Morales Jun 4, 2023
90782a4
fix: error
Alejandro-Morales Jun 5, 2023
380131d
lint fix
Alejandro-Morales Jun 5, 2023
d301d00
lint fix
Alejandro-Morales Jun 5, 2023
ba68e61
move name reg to helper function
Alejandro-Morales Jun 6, 2023
3b66f1b
minor change to exampple
Alejandro-Morales Jun 6, 2023
0c4dd45
test changes
Alejandro-Morales Jun 7, 2023
ef37606
Merge branch 'main' into service-contract
Alejandro-Morales Jun 7, 2023
4b8313d
fix: lint
Alejandro-Morales Jun 7, 2023
913a76d
Merge branch 'main' into service-contract
Alejandro-Morales Jun 22, 2023
2238038
feat: split GlobalResolver
Alejandro-Morales Jun 22, 2023
33df39d
black
Alejandro-Morales Jun 22, 2023
bc85950
run pylint
Alejandro-Morales Jun 22, 2023
3fc006d
fix: tests
Alejandro-Morales Jun 22, 2023
3f8ddea
minor correction
Alejandro-Morales Jun 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions examples/08-local-network-interaction/agent1.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from uagents.setup import fund_agent_if_low
from uagents.resolver import get_agent_address
from uagents import Agent, Context, Model


Expand All @@ -10,15 +11,21 @@ class Message(Model):


bob = Agent(
name="bob",
name="agent bob",
port=8001,
seed="bob secret phrase",
seed="agent bob secret phrase",
endpoint=["http://127.0.0.1:8001/submit"],
)

fund_agent_if_low(bob.wallet.address())


@bob.on_event("startup")
async def register_name(ctx: Context):
await bob.register_name()
print("agent bob registered address: ", get_agent_address(ctx.name))


@bob.on_message(model=Message)
async def message_handler(ctx: Context, sender: str, msg: Message):
ctx.logger.info(f"Received message from {sender}: {msg.message}")
Expand Down
4 changes: 1 addition & 3 deletions examples/08-local-network-interaction/agent2.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ class Message(Model):
message: str


RECIPIENT_ADDRESS = "agent1q2kxet3vh0scsf0sm7y2erzz33cve6tv5uk63x64upw5g68kr0chkv7hw50"

alice = Agent(
name="alice",
port=8000,
Expand All @@ -20,7 +18,7 @@ class Message(Model):

@alice.on_interval(period=2.0)
async def send_message(ctx: Context):
await ctx.send(RECIPIENT_ADDRESS, Message(message="Hello there bob."))
await ctx.send("agent bob", Message(message="Hello there bob."))


@alice.on_message(model=Message)
Expand Down
154 changes: 94 additions & 60 deletions src/uagents/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@

from cosmpy.aerial.wallet import LocalWallet, PrivateKey
from cosmpy.crypto.address import Address
from cosmpy.aerial.contract.cosmwasm import create_cosmwasm_execute_msg
from cosmpy.aerial.client import prepare_and_broadcast_basic_transaction
from cosmpy.aerial.tx import Transaction


from uagents.asgi import ASGIServer
from uagents.context import (
Expand All @@ -18,15 +22,21 @@
from uagents.dispatch import Sink, dispatcher, JsonStr
from uagents.models import Model, ErrorMessage
from uagents.protocol import Protocol
from uagents.resolver import Resolver, AlmanacResolver
from uagents.resolver import Resolver, GlobalResolver
from uagents.storage import KeyValueStore, get_or_create_private_keys
from uagents.network import get_ledger, get_reg_contract, wait_for_tx_to_complete
from uagents.network import (
get_ledger,
get_almanac_contract,
get_service_contract,
wait_for_tx_to_complete,
)
from uagents.mailbox import MailboxClient
from uagents.config import (
CONTRACT_ALMANAC,
REGISTRATION_FEE,
REGISTRATION_DENOM,
MIN_REGISTRATION_TIME,
LEDGER_PREFIX,
BLOCK_INTERVAL,
parse_endpoint_config,
parse_mailbox_config,
get_logger,
Expand Down Expand Up @@ -63,26 +73,11 @@ def __init__(
self._name = name
self._port = port if port is not None else 8000
self._background_tasks: Set[asyncio.Task] = set()
self._resolver = resolve if resolve is not None else AlmanacResolver()
self._resolver = resolve if resolve is not None else GlobalResolver()
self._loop = asyncio.get_event_loop_policy().get_event_loop()

# initialize wallet and identity
if seed is None:
if name is None:
self._wallet = LocalWallet.generate()
self._identity = Identity.generate()
else:
identity_key, wallet_key = get_or_create_private_keys(name)
self._wallet = LocalWallet(PrivateKey(wallet_key))
self._identity = Identity.from_string(identity_key)
else:
self._identity = Identity.from_seed(seed, 0)
self._wallet = LocalWallet(
PrivateKey(derive_key_from_seed(seed, LEDGER_PREFIX, 0)),
prefix=LEDGER_PREFIX,
)
if name is None:
self._name = self.address[0:16]
self._initialize_wallet_and_identity(seed, name)
self._logger = get_logger(self.name)

# configure endpoints and mailbox
Expand All @@ -103,7 +98,8 @@ def __init__(
self._mailbox_client = None

self._ledger = get_ledger()
self._reg_contract = get_reg_contract()
self._almanac_contract = get_almanac_contract()
self._service_contract = get_service_contract()
self._storage = KeyValueStore(self.address[0:16])
self._interval_handlers: List[Tuple[IntervalCallback, float]] = []
self._interval_messages: Set[str] = set()
Expand Down Expand Up @@ -147,6 +143,24 @@ def __init__(
self._port, self._loop, self._queries, logger=self._logger
)

def _initialize_wallet_and_identity(self, seed, name):
if seed is None:
if name is None:
self._wallet = LocalWallet.generate()
self._identity = Identity.generate()
else:
identity_key, wallet_key = get_or_create_private_keys(name)
self._wallet = LocalWallet(PrivateKey(wallet_key))
self._identity = Identity.from_string(identity_key)
else:
self._identity = Identity.from_seed(seed, 0)
self._wallet = LocalWallet(
PrivateKey(derive_key_from_seed(seed, LEDGER_PREFIX, 0)),
prefix=LEDGER_PREFIX,
)
if name is None:
self._name = self.address[0:16]

@property
def name(self) -> str:
return self._name
Expand Down Expand Up @@ -182,9 +196,10 @@ def sign_digest(self, digest: bytes) -> str:
return self._identity.sign_digest(digest)

def sign_registration(self) -> str:
assert self._reg_contract.address is not None
assert self._almanac_contract.address is not None
return self._identity.sign_registration(
str(self._reg_contract.address), self.get_registration_sequence()
str(self._almanac_contract.address),
self._almanac_contract.get_sequence(self.address),
)

def update_endpoints(self, endpoints: List[Dict[str, Any]]):
Expand All @@ -208,50 +223,56 @@ async def _register(self, ctx: Context):

signature = self.sign_registration()

msg = {
"register": {
"record": {
"service": {
"protocols": list(
map(lambda x: x.digest, self.protocols.values())
),
"endpoints": self._endpoints,
}
},
"signature": signature,
"sequence": self.get_registration_sequence(),
"agent_address": self.address,
}
}

self._logger.info("Registering on Almanac contract...")
transaction = self._reg_contract.execute(
msg,
ctx.wallet,
funds=f"{REGISTRATION_FEE}{REGISTRATION_DENOM}",
self._logger.info("Registering on almanac contract...")

transaction = Transaction()

almanac_msg = self._almanac_contract.get_registration_msg(
self.protocols, self._endpoints, signature, self.address
)

transaction.add_message(
create_cosmwasm_execute_msg(
ctx.wallet.address(),
CONTRACT_ALMANAC,
almanac_msg,
funds=f"{REGISTRATION_FEE}{REGISTRATION_DENOM}",
)
)

transaction = prepare_and_broadcast_basic_transaction(
ctx.ledger, transaction, ctx.wallet
)
await wait_for_tx_to_complete(transaction.tx_hash)
self._logger.info("Registering on Almanac contract...complete")
self._logger.info("Registering on almanac contract...complete")

def _schedule_registration(self):
query_msg = {"query_records": {"agent_address": self.address}}
response = self._reg_contract.query(query_msg)
return self._almanac_contract.get_expiry(self.address)

if not response["record"]:
contract_state = self._reg_contract.query({"query_contract_state": {}})
expiry = contract_state.get("state").get("expiry_height")
return expiry * BLOCK_INTERVAL
async def register_name(self):
self._logger.info("Registering name...")

expiry = response.get("record")[0].get("expiry")
height = response.get("height")
if not self._almanac_contract.is_registered(self.address):
self._logger.warning(
f"Agent {self.name} needs to be registered in almanac contract to register its name"
)
return

return (expiry - height) * BLOCK_INTERVAL
transaction = self._service_contract.get_registration_tx(
self.name, str(self.wallet.address()), self.address
)

def get_registration_sequence(self) -> int:
query_msg = {"query_sequence": {"agent_address": self.address}}
sequence = self._reg_contract.query(query_msg)["sequence"]
if transaction is None:
self._logger.error(
f"Please select another name, {self.name} is owned by another address"
)

return sequence
return
transaction = prepare_and_broadcast_basic_transaction(
self._ledger, transaction, self.wallet
)
await wait_for_tx_to_complete(transaction.tx_hash)
self._logger.info("Registering name...complete")

def on_interval(
self,
Expand Down Expand Up @@ -364,8 +385,21 @@ def start_background_tasks(self):

# start the contract registration update loop
if self._endpoints is not None:
self._loop.create_task(
_run_interval(self._register, self._ctx, self._schedule_registration())
if (
not self._almanac_contract.is_registered(self.address)
or self._schedule_registration() < MIN_REGISTRATION_TIME
or self._endpoints != self._almanac_contract.get_endpoints(self.address)
):
self._loop.create_task(
_run_interval(
self._register, self._ctx, self._schedule_registration()
)
)
else:
self._logger.info("Registration up to date!")
else:
self._logger.warning(
"I have no endpoint and won't be able to receive external messages"
)

def run(self):
Expand Down
6 changes: 5 additions & 1 deletion src/uagents/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@ class AgentNetwork(Enum):
AGENT_PREFIX = "agent"
LEDGER_PREFIX = "fetch"
USER_PREFIX = "user"
CONTRACT_ALMANAC = "fetch1tjagw8g8nn4cwuw00cf0m5tl4l6wfw9c0ue507fhx9e3yrsck8zs0l3q4w"
CONTRACT_ALMANAC = "fetch1h5rhtj5m6dqjmufj5m3t4mq6l7cnd8dvaxclwmrk6tfdm0gy3lmszksf0s"
CONTRACT_NAME_SERVICE = (
"fetch1yrf4xpglq02fzj50m9wn44qdq89a5vr0ufa42qa506uhwal4n79s99sp87"
)
REGISTRATION_FEE = 500000000000000000
REGISTRATION_DENOM = "atestfet"
MIN_REGISTRATION_TIME = 3600
BLOCK_INTERVAL = 5
AGENT_NETWORK = AgentNetwork.FETCHAI_TESTNET

Expand Down
6 changes: 3 additions & 3 deletions src/uagents/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ async def send_raw(
return

# resolve the endpoint
endpoint = await self._resolver.resolve(destination)
destination_address, endpoint = await self._resolver.resolve(destination)
if endpoint is None:
self._logger.exception(
f"Unable to resolve destination endpoint for address {destination}"
Expand All @@ -172,7 +172,7 @@ async def send_raw(
env = Envelope(
version=1,
sender=self.address,
target=destination,
target=destination_address,
session=self._session,
schema_digest=schema_digest,
protocol_digest=self.get_message_protocol(schema_digest),
Expand All @@ -189,5 +189,5 @@ async def send_raw(

if not success:
self._logger.exception(
f"Unable to send envelope to {destination} @ {endpoint}"
f"Unable to send envelope to {destination_address} @ {endpoint}"
)
Loading