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: add wallet messaging #53

Merged
merged 77 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from 75 commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
5708188
feat: add wallet messaging
jrriehl Feb 10, 2023
8715db6
chore: rename run to poll_server
jrriehl Feb 13, 2023
96feb0a
chore: remove checking wallet messages log
jrriehl Feb 24, 2023
cf601af
chore: merge main branch
jrriehl Feb 27, 2023
9fb9205
fix: error handling for wallet messaging server connection
jrriehl Feb 28, 2023
4c0a7d8
patch: fix security issue in the memorandum service (#60)
UrAvgDeveloper Feb 28, 2023
f5b113a
fix: memorandum connection error handling
jrriehl Feb 28, 2023
d1740e3
feat: add prepare tx function
jrriehl Feb 28, 2023
cf0107b
Merge branch 'babble' of github.com:fetchai/uAgents into babble
jrriehl Feb 28, 2023
db92535
feat: updates for wallet automations
jrriehl Mar 6, 2023
3271328
feat: integrate babble chain id update (#70)
jrriehl Mar 14, 2023
ecf8afd
chore: merge branches
jrriehl Mar 15, 2023
28aa0a5
chore: update babble to pypi version
jrriehl Mar 15, 2023
5bf6200
chore: poetry lock
jrriehl Mar 15, 2023
124dad9
chore: merge main branch
jrriehl Mar 24, 2023
7a334f7
feat: add wallet chain_id config
jrriehl Mar 24, 2023
9c30956
fix: wallet address and protocol contents
jrriehl Mar 24, 2023
e418e08
Merge branch 'fix/address-protocol' into babble
jrriehl Mar 24, 2023
df079fb
feat: add config for chain_id and key deriv index
jrriehl Mar 31, 2023
371da6e
chore: better exception logging
jrriehl Apr 3, 2023
ae4f580
chore: catch base exception in server poll
jrriehl Apr 12, 2023
a5e7db3
fix: add max number of consecutive failures to retrieve wallet messages
jrriehl May 2, 2023
2c643d4
chore: merge main and resolve dep conflicts
jrriehl May 2, 2023
81e5af0
deps: update cosmpy to 0.7.0
jrriehl May 4, 2023
d20d22e
feat: support python 3.11
jrriehl May 6, 2023
fe68e19
feat: support python 3.11
jrriehl May 6, 2023
a592ac3
chore: merge update-cosmpy branch
jrriehl May 12, 2023
e6f4811
chore: merge py311 branch
jrriehl May 12, 2023
6befdb3
chore: revert to correct version of Identity
jrriehl May 15, 2023
a3e601f
deps: update babble to 0.2.0
jrriehl May 15, 2023
e1f3067
chore: update cosmpy to 0.8.0
jrriehl May 16, 2023
1c4e4aa
chore: fix linting errors
jrriehl May 16, 2023
289f2b4
test: add Python 3.11 to CI tests
jrriehl May 16, 2023
0ffc183
chore: merge py311 branch
jrriehl May 17, 2023
465363e
chore: merge main and resolve poetry lock conflict
jrriehl May 18, 2023
6c96a94
chore: merge main and resolve conflicts
jrriehl Jul 12, 2023
456b520
chore: update example number and delete repeated logging
jrriehl Jul 12, 2023
aa7c1bd
chore: merge main and resolve conflicts
jrriehl Jul 27, 2023
940c8c6
chore: merge main and resolve conflicts
jrriehl Jul 27, 2023
82b6db0
chore: failure workaround no longer needed for babble 0.3.0
jrriehl Jul 28, 2023
692bc4e
chore: remove unused import
jrriehl Jul 28, 2023
aa4eba2
test: name service contract update
jrriehl Aug 9, 2023
3cf381d
chore: merge main and resolve poetry lock conflict
jrriehl Aug 14, 2023
1f4cc07
chore: update python version constraint
jrriehl Aug 14, 2023
9750941
chore: revert back to production name service contract
jrriehl Aug 17, 2023
d1a07c2
chore: merge main and resolve conflicts
jrriehl Aug 30, 2023
e7bee95
fix: add comma in .pylintrc
jrriehl Aug 30, 2023
59dd378
feat: update permissions query for name service contract
jrriehl Sep 5, 2023
c1074b3
chore: merge updates and resolve conflicts
jrriehl Sep 5, 2023
4eb3f60
Merge remote-tracking branch 'origin/main' into babble
jrriehl Sep 8, 2023
b14e372
Merge remote-tracking branch 'origin/main' into babble
jrriehl Sep 14, 2023
3813956
chore: merge main branch and resolve conflicts
jrriehl Oct 12, 2023
9591be1
chore: merge main and resolve conflict
jrriehl Nov 9, 2023
dea398c
remove unused transaction functions
jrriehl Nov 22, 2023
864cc6e
test: fix extras and update doc string
jrriehl Nov 23, 2023
a07d469
test: babble extra
jrriehl Nov 23, 2023
a1575ca
test: poetry add extras
jrriehl Nov 23, 2023
41180ff
test: babble extra
jrriehl Nov 23, 2023
f3a88ff
feat: correct extras setup
jrriehl Nov 23, 2023
1de362c
Merge branch 'main' of ssh://github.com/fetchai/uAgents into babble
jrriehl Nov 23, 2023
e4e2ce3
chore: move example to correct place and give import error warning
jrriehl Nov 23, 2023
a661aee
fix: module not found error
jrriehl Nov 23, 2023
534c62a
fix: log exception
jrriehl Nov 23, 2023
1bf6d6b
fix: module not found error
jrriehl Nov 23, 2023
4069829
fix: do not raise exception
jrriehl Nov 23, 2023
0a47adc
chore: revert changes to example
jrriehl Nov 23, 2023
a25e9aa
fix: set wallet msging client to none on import failure
jrriehl Nov 23, 2023
f5833d3
fix: return value on discarded handler
jrriehl Nov 23, 2023
27416db
chore: install poetry extras for linting
jrriehl Nov 23, 2023
9ec96c5
fix: -E all
jrriehl Nov 23, 2023
8b10f46
fix: pylint error
jrriehl Nov 23, 2023
b26a1a6
chore: some small pylint updates
jrriehl Nov 23, 2023
61008ae
docs: remove extra line
jrriehl Nov 23, 2023
38d5ca0
Merge branch 'main' into babble
jrriehl Nov 24, 2023
15a6e88
chore: update babble to 0.4.0
jrriehl Nov 24, 2023
4ab0c53
chore: updates based on PR review
jrriehl Nov 28, 2023
0b64112
chore: revert python constrain to exclude 3.12
jrriehl Nov 29, 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
4 changes: 2 additions & 2 deletions .github/workflows/ci-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ jobs:
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Install dependencies
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: poetry install --no-interaction --no-root
run: poetry install -E all --no-interaction --no-root

- run: poetry install --no-interaction
- run: poetry install -E all --no-interaction
- run: poetry run black --check .
- run: poetry run pylint $(git ls-files '*.py')
2 changes: 1 addition & 1 deletion python/.pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ disable=missing-module-docstring,
too-many-locals,
too-many-return-statements,
logging-fstring-interpolation,
import-outside-toplevel,
too-many-lines,
broad-exception-caught

32 changes: 32 additions & 0 deletions python/examples/15-wallet-messaging/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from uagents import Agent, Bureau, Context
from uagents.wallet_messaging import WalletMessage


ALICE_SEED = "alice dorado recovery phrase"
BOB_SEED = "bob dorado recovery phrase"

alice = Agent(name="alice", seed=ALICE_SEED, enable_wallet_messaging=True)
bob = Agent(name="bob", seed=BOB_SEED, enable_wallet_messaging=True)


@alice.on_wallet_message()
async def reply(ctx: Context, msg: WalletMessage):
ctx.logger.info(f"Got wallet message: {msg.text}")
await ctx.send_wallet_message(msg.sender, "hey, thanks for the message")


@bob.on_interval(period=5)
async def send_message(ctx: Context):
ctx.logger.info("Sending message...")
await ctx.send_wallet_message(alice.address, "hello")


@bob.on_wallet_message()
async def wallet_reply(ctx: Context, msg: WalletMessage):
ctx.logger.info(f"Got wallet message: {msg.text}")


bureau = Bureau()
bureau.add(alice)
bureau.add(bob)
bureau.run()
494 changes: 406 additions & 88 deletions python/poetry.lock

Large diffs are not rendered by default.

21 changes: 13 additions & 8 deletions python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ license = "Apache 2.0"
readme = "README.md"

[tool.poetry.dependencies]
python = ">=3.8,<3.12"
python = "^3.8"
pydantic = "^1.10.2"
msgpack = "^1.0.4"
bech32 = "^1.2.0"
Expand All @@ -18,6 +18,12 @@ aiohttp = "^3.8.3"
cosmpy = "^0.9.1"
websockets = "^10.4"

# extras
fetchai-babble = {version = "^0.4.0", optional = true}
tortoise-orm = {version = "^0.19.2", optional = true}
geopy = {version = "^2.3.0", optional = true}
pyngrok = {version = "^5.2.3", optional = true}

[tool.poetry.dev-dependencies]
aioresponses = "^0.7.4"
black = "^23.1.0"
Expand All @@ -26,14 +32,13 @@ pylint = "^2.15.3"
mkdocs = "^1.4.2"
mkdocs-material = "^9.1.13"

[tool.poetry.group.orm.dependencies]
tortoise-orm = "^0.19.2"

[tool.poetry.group.geo.dependencies]
geopy = "^2.3.0"

[tool.poetry.group.remote-agents.dependencies]
pyngrok = "^5.2.3"
[tool.poetry.extras]
all = ["tortoise-orm", "geopy", "fetchai-babble", "pyngrok"]
wallet = ["fetchai-babble"]
orm = ["tortoise-orm"]
geo = ["geopy"]
remote-agents = ["pyngrok"]

[build-system]
requires = ["poetry-core>=1.0.0"]
Expand Down
77 changes: 69 additions & 8 deletions python/src/uagents/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ def __init__(
agentverse: Optional[Union[str, Dict[str, str]]] = None,
mailbox: Optional[Union[str, Dict[str, str]]] = None,
resolve: Optional[Resolver] = None,
enable_wallet_messaging: Optional[Union[bool, Dict[str, str]]] = False,
jrriehl marked this conversation as resolved.
Show resolved Hide resolved
wallet_key_derivation_index: Optional[int] = 0,
max_resolver_endpoints: Optional[int] = None,
version: Optional[str] = None,
test: Optional[bool] = True,
Expand Down Expand Up @@ -189,8 +191,8 @@ def __init__(
else:
self._loop = asyncio.get_event_loop_policy().get_event_loop()

self._initialize_wallet_and_identity(seed, name)

# initialize wallet and identity
self._initialize_wallet_and_identity(seed, name, wallet_key_derivation_index)
self._logger = get_logger(self.name)

# configure endpoints and mailbox
Expand Down Expand Up @@ -237,6 +239,8 @@ def __init__(
self._test = test
self._version = version or "0.1.0"

self.initialize_wallet_messaging(enable_wallet_messaging)

# initialize the internal agent protocol
self._protocol = Protocol(name=self._name, version=self._version)

Expand All @@ -255,6 +259,7 @@ def __init__(
self._queries,
replies=self._replies,
interval_messages=self._interval_messages,
wallet_messaging_client=self._wallet_messaging_client,
protocols=self.protocols,
logger=self._logger,
)
Expand All @@ -272,7 +277,7 @@ def __init__(
async def _handle_error_message(ctx: Context, sender: str, msg: ErrorMessage):
ctx.logger.exception(f"Received error message from {sender}: {msg.error}")

def _initialize_wallet_and_identity(self, seed, name):
def _initialize_wallet_and_identity(self, seed, name, wallet_key_derivation_index):
"""
Initialize the wallet and identity for the agent.

Expand All @@ -282,6 +287,7 @@ def _initialize_wallet_and_identity(self, seed, name):
Args:
seed (str or None): The seed for generating keys.
name (str or None): The name of the agent.
wallet_key_derivation_index (int): The index for deriving the wallet key.
"""
if seed is None:
if name is None:
Expand All @@ -294,12 +300,51 @@ def _initialize_wallet_and_identity(self, seed, name):
else:
self._identity = Identity.from_seed(seed, 0)
self._wallet = LocalWallet(
PrivateKey(derive_key_from_seed(seed, LEDGER_PREFIX, 0)),
PrivateKey(
derive_key_from_seed(
seed, LEDGER_PREFIX, wallet_key_derivation_index
)
),
prefix=LEDGER_PREFIX,
)
if name is None:
self._name = self.address[0:16]

def initialize_wallet_messaging(
self, enable_wallet_messaging: Union[bool, Dict[str, str]]
jrriehl marked this conversation as resolved.
Show resolved Hide resolved
):
"""
Initialize wallet messaging for the agent.

Args:
enable_wallet_messaging (Union[bool, Dict[str, str]]): Wallet messaging configuration.
"""
if enable_wallet_messaging:
wallet_chain_id = self._ledger.network_config.chain_id
if (
isinstance(enable_wallet_messaging, dict)
and "chain_id" in enable_wallet_messaging
):
wallet_chain_id = enable_wallet_messaging["chain_id"]

try:
from uagents.wallet_messaging import WalletMessagingClient

self._wallet_messaging_client = WalletMessagingClient(
self._identity,
self._wallet,
wallet_chain_id,
self._logger,
)
except ModuleNotFoundError:
self._logger.exception(
"Unable to include wallet messaging. "
"Please install the 'wallet' extra to enable wallet messaging."
)
self._wallet_messaging_client = None
else:
self._wallet_messaging_client = None

@property
def name(self) -> str:
"""
Expand Down Expand Up @@ -679,6 +724,16 @@ def _add_event_handler(
elif event_type == "shutdown":
self._on_shutdown.append(func)

def on_wallet_message(
self,
):
if self._wallet_messaging_client is None:
self._logger.warning(
"Discarding 'on_wallet_message' handler because wallet messaging is disabled"
)
return lambda func: func
return self._wallet_messaging_client.on_message()

def include(self, protocol: Protocol, publish_manifest: Optional[bool] = False):
"""
Include a protocol into the agent's capabilities.
Expand Down Expand Up @@ -802,10 +857,6 @@ def setup(self):
# register the internal agent protocol
self.include(self._protocol)
self._loop.run_until_complete(self._startup())
if self._endpoints is None:
self._logger.warning(
"I have no endpoint and won't be able to receive external messages"
)
self.start_background_tasks()

def start_background_tasks(self):
Expand All @@ -824,6 +875,16 @@ def start_background_tasks(self):
self._background_tasks.add(task)
task.add_done_callback(self._background_tasks.discard)

# start the wallet messaging client if enabled
if self._wallet_messaging_client is not None:
for task in [
self._wallet_messaging_client.poll_server(),
self._wallet_messaging_client.process_message_queue(self._ctx),
]:
task = self._loop.create_task(task)
self._background_tasks.add(task)
task.add_done_callback(self._background_tasks.discard)

def run(self):
"""
Run the agent.
Expand Down
2 changes: 2 additions & 0 deletions python/src/uagents/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
ALMANAC_API_URL = AGENTVERSE_URL + "/v1/almanac/"
MAILBOX_POLL_INTERVAL_SECONDS = 1.0

WALLET_MESSAGING_POLL_INTERVAL_SECONDS = 2.0

RESPONSE_TIME_HINT_SECONDS = 5
DEFAULT_ENVELOPE_TIMEOUT_SECONDS = 30
DEFAULT_MAX_ENDPOINTS = 10
Expand Down
12 changes: 12 additions & 0 deletions python/src/uagents/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
IntervalCallback = Callable[["Context"], Awaitable[None]]
MessageCallback = Callable[["Context", str, Any], Awaitable[None]]
EventCallback = Callable[["Context"], Awaitable[None]]
WalletMessageCallback = Callable[["Context", Any], Awaitable[None]]


class DeliveryStatus(str, Enum):
Expand Down Expand Up @@ -147,6 +148,7 @@ def __init__(
replies: Optional[Dict[str, Dict[str, Type[Model]]]] = None,
interval_messages: Optional[Set[str]] = None,
message_received: Optional[MsgDigest] = None,
wallet_messaging_client: Optional[Any] = None,
protocols: Optional[Dict[str, Protocol]] = None,
logger: Optional[logging.Logger] = None,
):
Expand All @@ -168,6 +170,7 @@ def __init__(
for each type of incoming message.
interval_messages (Optional[Set[str]]): The optional set of interval messages.
message_received (Optional[MsgDigest]): The optional message digest received.
wallet_messaging_client (Optional[Any]): The optional wallet messaging client.
protocols (Optional[Dict[str, Protocol]]): The optional dictionary of protocols.
logger (Optional[logging.Logger]): The optional logger instance.
"""
Expand All @@ -184,6 +187,7 @@ def __init__(
self._replies = replies
self._interval_messages = interval_messages
self._message_received = message_received
self._wallet_messaging_client = wallet_messaging_client
self._protocols = protocols or {}
self._logger = logger

Expand Down Expand Up @@ -554,3 +558,11 @@ async def send_raw_exchange_envelope(
destination=destination,
endpoint="",
)

async def send_wallet_message(
self,
destination: str,
text: str,
msg_type: int = 1,
Archento marked this conversation as resolved.
Show resolved Hide resolved
):
await self._wallet_messaging_client.send(destination, text, msg_type)
jrriehl marked this conversation as resolved.
Show resolved Hide resolved
43 changes: 43 additions & 0 deletions python/src/uagents/crypto/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
import struct
from secrets import token_bytes
from typing import Tuple, Union
import json
import base64

import bech32
import ecdsa
from ecdsa.util import sigencode_string_canonize

from uagents.config import USER_PREFIX

Expand Down Expand Up @@ -78,6 +82,7 @@ def __init__(self, signing_key: ecdsa.SigningKey):
# build the address
pub_key_bytes = self._sk.get_verifying_key().to_string(encoding="compressed")
self._address = _encode_bech32("agent", pub_key_bytes)
self._pub_key = pub_key_bytes.hex()

@staticmethod
def from_seed(seed: str, index: int) -> "Identity":
Expand Down Expand Up @@ -126,12 +131,20 @@ def address(self) -> str:
"""
return self._address

@property
def pub_key(self) -> str:
return self._pub_key

def sign(self, data: bytes) -> str:
"""
Sign the provided data.
"""
return _encode_bech32("sig", self._sk.sign(data))

def sign_b64(self, data: bytes) -> str:
raw_signature = bytes(self._sk.sign(data, sigencode=sigencode_string_canonize))
return base64.b64encode(raw_signature).decode()

def sign_digest(self, digest: bytes) -> str:
"""
Sign the provided digest.
Expand All @@ -148,6 +161,36 @@ def sign_registration(self, contract_address: str, sequence: int) -> str:
hasher.update(encode_length_prefixed(sequence))
return self.sign_digest(hasher.digest())

def sign_arbitrary(self, data: bytes) -> Tuple[str, str]:
# create the sign doc
sign_doc = {
"chain_id": "",
"account_number": "0",
"sequence": "0",
"fee": {
"gas": "0",
"amount": [],
},
"msgs": [
{
"type": "sign/MsgSignData",
"value": {
"signer": self.address,
"data": base64.b64encode(data).decode(),
},
},
],
"memo": "",
}

raw_sign_doc = json.dumps(
sign_doc, sort_keys=True, separators=(",", ":")
).encode()
signature = self.sign_b64(raw_sign_doc)
enc_sign_doc = base64.b64encode(raw_sign_doc).decode()

return enc_sign_doc, signature

@staticmethod
def verify_digest(address: str, digest: bytes, signature: str) -> bool:
"""
Expand Down
2 changes: 1 addition & 1 deletion python/src/uagents/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
from cosmpy.aerial.exceptions import NotFoundError, QueryTimeoutError
from cosmpy.aerial.contract.cosmwasm import create_cosmwasm_execute_msg
from cosmpy.aerial.faucet import FaucetApi
from cosmpy.aerial.tx_helpers import TxResponse
from cosmpy.aerial.tx import Transaction
from cosmpy.aerial.tx_helpers import TxResponse
from cosmpy.aerial.wallet import LocalWallet

from uagents.config import (
Expand Down
Loading
Loading