Skip to content

Commit

Permalink
feat(core): support almanac contract v2.0.0 (#560)
Browse files Browse the repository at this point in the history
Co-authored-by: James Riehl <james.riehl@fetch.ai>
Co-authored-by: James Riehl <33920192+jrriehl@users.noreply.github.com>
  • Loading branch information
3 people authored Oct 22, 2024
1 parent 4a5f960 commit e5bdc9a
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 30 deletions.
16 changes: 3 additions & 13 deletions python/src/uagents/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
from uagents.asgi import ASGIServer
from uagents.communication import Dispenser
from uagents.config import (
ALMANAC_CONTRACT_VERSION,
AVERAGE_BLOCK_INTERVAL,
LEDGER_PREFIX,
MAINNET_PREFIX,
Expand Down Expand Up @@ -700,7 +699,7 @@ def sign_digest(self, digest: bytes) -> str:
"""
return self._identity.sign_digest(digest)

def sign_registration(self) -> str:
def sign_registration(self, current_time: int) -> str:
"""
Sign the registration data for Almanac contract.
Returns:
Expand All @@ -711,7 +710,8 @@ def sign_registration(self) -> str:
assert self._almanac_contract.address is not None
return self._identity.sign_registration(
str(self._almanac_contract.address),
self._almanac_contract.get_sequence(self.address),
current_time,
str(self.wallet.address()),
)

def update_endpoints(self, endpoints: List[AgentEndpoint]):
Expand Down Expand Up @@ -766,16 +766,6 @@ async def register(self):
if necessary.
"""
# Check if the deployed contract version matches the supported version
deployed_version = self._almanac_contract.get_contract_version()
if deployed_version != ALMANAC_CONTRACT_VERSION:
self._logger.warning(
"Mismatch in almanac contract versions: supported (%s), deployed (%s). "
"Update uAgents to the latest version for compatibility.",
ALMANAC_CONTRACT_VERSION,
deployed_version,
)

await self._registration_policy.register(
self.address, list(self.protocols.keys()), self._endpoints, self._metadata
)
Expand Down
3 changes: 2 additions & 1 deletion python/src/uagents/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@
REGISTRATION_UPDATE_INTERVAL_SECONDS = 3600
REGISTRATION_RETRY_INTERVAL_SECONDS = 60
AVERAGE_BLOCK_INTERVAL = 6
ALMANAC_CONTRACT_VERSION = "1.0.0"
ALMANAC_CONTRACT_VERSION = "2.0.0"

AGENTVERSE_URL = "https://agentverse.ai"
ALMANAC_API_URL = AGENTVERSE_URL + "/v1/almanac"
ALMANAC_API_TIMEOUT_SECONDS = 1.0
ALMANAC_API_MAX_RETRIES = 10
ALMANAC_REGISTRATION_WAIT = 100
MAILBOX_POLL_INTERVAL_SECONDS = 1.0

WALLET_MESSAGING_POLL_INTERVAL_SECONDS = 2.0
Expand Down
8 changes: 7 additions & 1 deletion python/src/uagents/crypto/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,18 @@ def sign_digest(self, digest: bytes) -> str:
"""Sign the provided digest."""
return _encode_bech32("sig", self._sk.sign_digest(digest))

def sign_registration(self, contract_address: str, sequence: int) -> str:
def sign_registration(
self,
contract_address: str,
sequence: int,
wallet_address: str,
) -> str:
"""Sign the registration data for the Almanac contract."""
hasher = hashlib.sha256()
hasher.update(encode_length_prefixed(contract_address))
hasher.update(encode_length_prefixed(self.address))
hasher.update(encode_length_prefixed(sequence))
hasher.update(encode_length_prefixed(wallet_address))
return self.sign_digest(hasher.digest())

def sign_arbitrary(self, data: bytes) -> Tuple[str, str]:
Expand Down
41 changes: 35 additions & 6 deletions python/src/uagents/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from cosmpy.crypto.address import Address

from uagents.config import (
ALMANAC_CONTRACT_VERSION,
AVERAGE_BLOCK_INTERVAL,
MAINNET_CONTRACT_ALMANAC,
MAINNET_CONTRACT_NAME_SERVICE,
Expand Down Expand Up @@ -150,6 +151,30 @@ class AlmanacContract(LedgerContract):
registration, and getting the endpoints associated with an agent's registration.
"""

def check_version(self) -> bool:
"""
Check if the contract version supported by this version of uAgents matches the
deployed version.
Returns:
bool: True if the contract version is supported, False otherwise.
"""
try:
deployed_version = self.get_contract_version()
if deployed_version != ALMANAC_CONTRACT_VERSION:
logger.warning(
f"The deployed version of the Almanac Contract is {deployed_version} "
f"and you are using version {ALMANAC_CONTRACT_VERSION}. "
"Update uAgents to the latest version to enable contract interactions.",
)
return False
except Exception:
logger.error(
"Failed to query contract version. Contract interactions will be disabled."
)
return False
return True

def query_contract(self, query_msg: Dict[str, Any]) -> Any:
"""
Execute a query with additional checks and error handling.
Expand Down Expand Up @@ -292,6 +317,7 @@ async def register(
protocols: List[str],
endpoints: List[AgentEndpoint],
signature: str,
current_time: int,
):
"""
Register an agent with the Almanac contract.
Expand All @@ -309,12 +335,11 @@ async def register(

transaction = Transaction()

sequence = self.get_sequence(agent_address)
almanac_msg = self.get_registration_msg(
protocols=protocols,
endpoints=endpoints,
signature=signature,
sequence=sequence,
sequence=current_time,
address=agent_address,
)

Expand Down Expand Up @@ -356,19 +381,23 @@ def get_sequence(self, address: str) -> int:
)


def get_almanac_contract(test: bool = True) -> AlmanacContract:
def get_almanac_contract(test: bool = True) -> Optional[AlmanacContract]:
"""
Get the AlmanacContract instance.
Args:
test (bool): Whether to use the testnet or mainnet. Defaults to True.
Returns:
AlmanacContract: The AlmanacContract instance.
AlmanacContract: The AlmanacContract instance if version is supported.
"""
if test:
return _testnet_almanac_contract
return _mainnet_almanac_contract
if _testnet_almanac_contract.check_version():
return _testnet_almanac_contract
return None
if _mainnet_almanac_contract.check_version():
return _mainnet_almanac_contract
return None


class NameServiceContract(LedgerContract):
Expand Down
23 changes: 17 additions & 6 deletions python/src/uagents/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
ALMANAC_API_MAX_RETRIES,
ALMANAC_API_TIMEOUT_SECONDS,
ALMANAC_API_URL,
ALMANAC_REGISTRATION_WAIT,
REGISTRATION_FEE,
REGISTRATION_UPDATE_INTERVAL_SECONDS,
)
Expand Down Expand Up @@ -219,14 +220,17 @@ async def register(

self._logger.info("Registering on almanac contract...")

signature = self._sign_registration(agent_address)
current_time = int(time.time()) - ALMANAC_REGISTRATION_WAIT

signature = self._sign_registration(current_time)
await self._almanac_contract.register(
self._ledger,
self._wallet,
agent_address,
protocols,
endpoints,
signature,
current_time,
)
self._logger.info("Registering on almanac contract...complete")
else:
Expand All @@ -235,7 +239,7 @@ async def register(
def _get_balance(self) -> int:
return self._ledger.query_bank_balance(Address(self._wallet.address()))

def _sign_registration(self, agent_address: str) -> str:
def _sign_registration(self, current_time: int) -> str:
"""
Sign the registration data for Almanac contract.
Expand All @@ -249,7 +253,8 @@ def _sign_registration(self, agent_address: str) -> str:
assert self._almanac_contract.address is not None
return self._identity.sign_registration(
str(self._almanac_contract.address),
self._almanac_contract.get_sequence(agent_address),
current_time,
str(self._wallet.address()),
)


Expand All @@ -269,9 +274,12 @@ def __init__(
self._api_policy = AlmanacApiRegistrationPolicy(
identity, almanac_api=almanac_api, logger=logger
)
self._ledger_policy = LedgerBasedRegistrationPolicy(
identity, ledger, wallet, almanac_contract, testnet, logger=logger
)
if almanac_contract is None:
self._ledger_policy = None
else:
self._ledger_policy = LedgerBasedRegistrationPolicy(
identity, ledger, wallet, almanac_contract, testnet, logger=logger
)

async def register(
self,
Expand All @@ -290,6 +298,9 @@ async def register(
f"Failed to register on Almanac API: {e.__class__.__name__}"
)

if self._ledger_policy is None:
return

# schedule the ledger registration
try:
await self._ledger_policy.register(
Expand Down
11 changes: 8 additions & 3 deletions python/tests/test_agent_registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@
EXPECTED_FUNDS = Coin(amount="8640000000000000", denom="atestfet")


def generate_digest(agent_address: str, contract_address: str, sequence: int) -> bytes:
def generate_digest(
agent_address: str, contract_address: str, sequence: int, wallet_address: str
) -> bytes:
hasher = hashlib.sha256()
hasher.update(encode_length_prefixed(contract_address))
hasher.update(encode_length_prefixed(agent_address))
hasher.update(encode_length_prefixed(sequence))
hasher.update(encode_length_prefixed(wallet_address))
return hasher.digest()


Expand Down Expand Up @@ -112,15 +115,17 @@ async def test_mock_almanac_registration(self):
endpoint=["http://localhost:8000/submit"], seed="almanact_reg_agent"
)

signature = agent.sign_registration()
signature = agent.sign_registration(0)

almanac_msg = agent._almanac_contract.get_registration_msg(
list(agent.protocols.keys()), agent._endpoints, signature, 0, agent.address
)

contract_address = str(agent._almanac_contract.address)

digest = generate_digest(agent.address, contract_address, 0)
digest = generate_digest(
agent.address, contract_address, 0, str(agent.wallet.address())
)

self.assertEqual(
mock_almanac_registration(almanac_msg, digest),
Expand Down

0 comments on commit e5bdc9a

Please sign in to comment.