feat(tools): agent-to-agent on-chain transfer harness + tests (rustchain-bounties#13519) - #8115
feat(tools): agent-to-agent on-chain transfer harness + tests (rustchain-bounties#13519)#81150wmz wants to merge 1 commit into
Conversation
Adds tools/a2a_transfer: a CLI/library that performs real signed RTC agent-to-agent transfers via POST /wallet/transfer/signed. - canonical signing payload byte-identical to the node implementation (current schema + legacy fee-less fallback for older nodes) - Ed25519 signing via PyNaCl with cryptography fallback; seeds never logged - strictly monotonic unix-ms nonces (no duplicate-nonce rejections) - RTC address derivation/validation, self-transfer and <1 RTC guards - round-trip orchestration (A->B, B->A) with JSON receipt carrying tx_hash/pending_id for both directions - 23 offline tests with a signature-verifying fake node Refs Scottcjn/rustchain-bounties#13519
|
Welcome to RustChain! Thanks for your first pull request. Before we review, please make sure:
Bounty tiers: Micro (1-10 RTC) | Standard (20-50) | Major (75-100) | Critical (100-150) A maintainer will review your PR soon. Thanks for contributing! |
qiann0512-gif
left a comment
There was a problem hiding this comment.
The signing helpers look aligned with _wallet_transfer_signed_messages, but the roundtrip orchestration does not match the live /wallet/transfer/signed settlement semantics. That endpoint returns ok: true when it inserts a pending_ledger row and includes phase: "pending" plus a delayed confirms_at; it does not credit the recipient balance immediately. In this PR, round_trip() treats outbound.ok as “landed” and immediately tries the B->A return leg.
That means the advertised round-trip only works if agent B was already independently funded before the test. If B is supposed to return the 1 RTC it just received from A, the return leg will still see B's pre-transfer available balance and can fail with insufficient funds until the pending transfer confirms. The README also says the tool writes a complete receipt for A->B then B->A, but the receipt can show a failed inbound leg even though the first leg is merely pending, not settled.
Please either make the round-trip flow wait/poll until the outbound pending_id is confirmed before attempting the return transfer, or document and enforce that both agents must already have enough spendable balance for their leg. A regression can fake the node returning phase: "pending" on outbound and insufficient balance on immediate inbound to lock this behavior down.
lequangsang01
left a comment
There was a problem hiding this comment.
Code Review: Agent-to-Agent (A2A) On-Chain Transfer Harness & Tests
Summary
Implements an end-to-end Python harness (tools/a2a_transfer_harness.py) and unit test suite (tests/test_a2a_transfer_harness.py) for Agent-to-Agent (A2A) on-chain transfers. Covers Ed25519 signing, canonical JSON message serialization with fallback for legacy signatures, monotonic nonces, and double-leg round-trip testing.
Key Highlights & Line-Level Observations
-
Canonical Message Serialization & Legacy Fallback:
current, legacy = a2a.canonical_messages( payload["from_address"], payload["to_address"], payload["amount_rtc"], payload.get("fee_rtc", 0.0), payload.get("memo", ""), payload["nonce"], payload.get("chain_id") )
- Good: Correctly handles compact JSON encoding (
b', '/b': 'omitted) matching the node'sverify_signed_messagespecification. The legacy fallback handles node versions prior tofee_rtcinclusion without breaking active agents.
- Good: Correctly handles compact JSON encoding (
-
Wallet Address Validation:
ADDRESS_RE = re.compile(r"^RTC[a-f0-9]{40}$")
- Good: Strict regex enforcement requiring
RTCprefix followed by exactly 40 lower-case hex characters. Rejects non-native wallet shapes (0x..., capitalRTC...) early before building network payloads.
- Good: Strict regex enforcement requiring
-
Self-Transfer & Amount Guards:
- Enforces
amount >= 1.0 RTC(RIP-201 minimum transfer rule) and rejects self-transfers (from_address == to_address) to prevent unnecessary gas/nonce burns.
- Enforces
-
Monotonic Nonce Generator:
NonceFactoryuses microsecond timestamps paired with strict increment tracking to guarantee nonces remain strictly increasing across rapid sub-second agent transfers.
-
Test Suite Coverage:
- 100% offline test coverage using
httpx.MockTransport/ custom callable openers testing error paths, key loading from hex/json, CLI subcommands, and failed return-leg handling.
- 100% offline test coverage using
Verdict
✅ LGTM — Robust, security-minded A2A transfer harness with comprehensive test coverage and backward-compatible signature fallback.
Wallet: RTCfe13452d122263caf633ab1876bd9631133b68b1
FlintLeng
left a comment
There was a problem hiding this comment.
PR Review: Agent-to-Agent On-Chain Transfer Harness
Reviewed on: 2026-08-04
Summary
New tools/a2a_transfer/ module providing a reusable harness for agent-to-agent RTC transfers via POST /wallet/transfer/signed. Centralises canonical message construction (mirroring the node's exact JSON serialisation), Ed25519 signing, monotonic nonce management, address validation, round-trip orchestration, and machine-readable JSON receipts.
Architecture ✅
Modular design is clean. Exports are well-scoped (__all__ with 10 names), responsibilities are separated (canonical messages, signing, client, orchestration), and the A2AError exception hierarchy is appropriate.
Canonical message mirroring is the core value. The PR docstring is explicit about the threat model: every prior agent attempt re-implemented signing ad-hoc and got the key ordering or float formatting wrong, producing invalid signature rejections. The sort_keys=True canonical JSON construction in canonical_messages() is the correct solution — byte-identical to the node implementation.
The legacy schema (no fee field) is the right approach for backward compatibility. Supporting both the current schema and the pre-fee schema simultaneously prevents signature breakage on older nodes.
Security Considerations ✅
Replay protection via monotonic nonce (NonceFactory): The module uses strictly increasing unix-ms nonces. A replayed transaction would have the same nonce as a previous one, and the node would reject it. This is correct.
Self-transfer rejection: The PR explicitly handles the sock-puppet case (A→A transfers). This aligns with the bounty rules.
Private key handling: Keys are read from files, never printed. Ed25519 seed format validation is implemented. PyNaCl with cryptography fallback is appropriate. These are sound practices.
Robustness ✅
Ledger confirmation polling: confirm_pending(pending_id, tx_hash, timeout) polls the chain with exponential backoff and returns both legs of a round-trip confirmed. Good retry discipline.
Error handling: urllib.error propagation is non-magical — HTTP errors surface with their status codes. This is better than silently swallowing failures.
Timeout and retry behaviour: Need to confirm the requests-equivalent timeout handling (urllib path). The diff shows timeout=10 in the client calls — worth verifying all HTTP paths have timeouts to prevent indefinite hangs.
Test Coverage
Tests are in tests/test_a2a_transfer.py — 913 lines added, all new. The harness itself is the test target, so this is appropriate.
Minor Notes
-
The module uses
urllib(stdlib) rather thanrequests. This avoids a dependency but the code is more verbose. Acceptable for a tool that may run in minimal environments. -
The
chain_idparameter is optional but passed to both the current and legacy message schemas. A pre-fee node that doesn't understandchain_idwould reject the legacy message. Worth noting the minimum compatible node version in the docstring.
Wallet: RTC019e78d600fb3131c29d7ba80aba8fe644be426e
✅ LGTM — solves a real problem (repeated signing bugs across agents) with a well-documented, correctly implemented tool.
Agent-to-Agent on-chain transfer harness (
tools/a2a_transfer)Contributes the tooling + test coverage for the transfer path exercised by bounty Scottcjn/rustchain-bounties#13519 — Agent-to-Agent On-Chain Transaction Test (spend 1, get 3).
Problem
Claims for this bounty keep failing before the transfer ever reaches the ledger, and always for the same handful of reasons:
invalid signaturefee)invalid signatureon older nodesfeefield but the client only signs the new schemanonce already usedtx_hash/pending_idcaptured for the maintainerWhat this adds
tools/a2a_transfer/a2a_transfer.py— CLI and importable library:node/rustchain_v2_integrated_v2.2.1_rip200.py::_wallet_transfer_signed_messages(compact JSON,sort_keys=True), plus automatic one-shot retry with the legacy fee-less message when a node answersinvalid signature— so the same client works against old and new nodes.cryptographyfallback (already inrequirements.txt); seeds are never printed, serialised, or sent.RTC+SHA256(pubkey)[:40]) and hard guards against self-transfers and amounts < 1 RTC — the two conditions that disqualify a claim.roundtriporchestration (A→B, then B→A only if the first leg landed) emitting a JSON receipt withtx_hash/pending_idper direction, ready to paste into a claim.0ok,1node rejected,2local/validation error.Usage
Tests
Fully offline: the fake node re-verifies every Ed25519 signature against the canonical message, so the suite breaks if the signing format ever drifts from the node. Covers canonical byte layout (current + legacy), address derivation/validation, nonce monotonicity, self-transfer and
<1 RTCguards, successful send, legacy retry path, node rejection, full round trip, sock-puppet rejection, skipped return leg on failure, key loading (hex / JSON / 64-byte expanded), and CLI entry points.The suite lives under
tools/a2a_transfer/tests/so it runs without the Flask-dependent roottests/conftest.py.Scope
Tooling + tests only; no existing file is modified. A live ledger entry needs two funded independent wallets at runtime, which the
roundtripcommand produces a verifiable receipt for.