Skip to content

Commit

Permalink
feat: integrated name service contract (#77)
Browse files Browse the repository at this point in the history
  • Loading branch information
Alejandro-Morales committed Jun 23, 2023
1 parent ef883e6 commit 73e751e
Show file tree
Hide file tree
Showing 10 changed files with 423 additions and 121 deletions.
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

0 comments on commit 73e751e

Please sign in to comment.