Skip to content

feat(tools): agent-to-agent on-chain transfer harness + tests (rustchain-bounties#13519) - #8115

Open
0wmz wants to merge 1 commit into
Scottcjn:mainfrom
0wmz:feat/a2a-transfer-13519
Open

feat(tools): agent-to-agent on-chain transfer harness + tests (rustchain-bounties#13519)#8115
0wmz wants to merge 1 commit into
Scottcjn:mainfrom
0wmz:feat/a2a-transfer-13519

Conversation

@0wmz

@0wmz 0wmz commented Aug 1, 2026

Copy link
Copy Markdown

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#13519Agent-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:

Failure Cause
invalid signature canonical message rebuilt by hand (wrong key order, spaces, missing fee)
invalid signature on older nodes node predates the fee field but the client only signs the new schema
nonce already used both legs of a round trip fired in the same millisecond
accepted but unpayable self-transfer / second wallet of the same agent (explicitly excluded)
unverifiable claim no tx_hash / pending_id captured for the maintainer

What this adds

tools/a2a_transfer/a2a_transfer.py — CLI and importable library:

  • Canonical signing payload byte-identical to 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 answers invalid signature — so the same client works against old and new nodes.
  • Ed25519 signing via PyNaCl with a cryptography fallback (already in requirements.txt); seeds are never printed, serialised, or sent.
  • Strictly monotonic unix-ms nonces, so round-trip legs can never collide.
  • Native address derivation/validation (RTC + SHA256(pubkey)[:40]) and hard guards against self-transfers and amounts < 1 RTC — the two conditions that disqualify a claim.
  • roundtrip orchestration (A→B, then B→A only if the first leg landed) emitting a JSON receipt with tx_hash/pending_id per direction, ready to paste into a claim.
  • Exit codes: 0 ok, 1 node rejected, 2 local/validation error.

Usage

python tools/a2a_transfer/a2a_transfer.py address  --key-file agent.key
python tools/a2a_transfer/a2a_transfer.py send     --key-file agent.key --to RTC<partner> --amount 1 --receipt claim.json
python tools/a2a_transfer/a2a_transfer.py roundtrip --key-file a.key --peer-key-file b.key --amount 1

Tests

python -m pytest tools/a2a_transfer/tests -q
23 passed

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 RTC guards, 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 root tests/conftest.py.

Scope

Tooling + tests only; no existing file is modified. A live ledger entry needs two funded independent wallets at runtime, which the roundtrip command produces a verifiable receipt for.

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
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Welcome to RustChain! Thanks for your first pull request.

Before we review, please make sure:

  • Non-doc PRs have a BCOS-L1 or BCOS-L2 label
  • Doc-only PRs are exempt from BCOS tier labels when they only touch docs/**, *.md, or common image/PDF files
  • New code files include an SPDX license header
  • You've tested your changes against the live node

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!

@github-actions github-actions Bot added documentation Improvements or additions to documentation BCOS-L1 Beacon Certified Open Source tier BCOS-L1 (required for non-doc PRs) size/XL PR: 500+ lines labels Aug 1, 2026

@qiann0512-gif qiann0512-gif left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 lequangsang01 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  1. 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's verify_signed_message specification. The legacy fallback handles node versions prior to fee_rtc inclusion without breaking active agents.
  2. Wallet Address Validation:

    ADDRESS_RE = re.compile(r"^RTC[a-f0-9]{40}$")
    • Good: Strict regex enforcement requiring RTC prefix followed by exactly 40 lower-case hex characters. Rejects non-native wallet shapes (0x..., capital RTC...) early before building network payloads.
  3. 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.
  4. Monotonic Nonce Generator:

    • NonceFactory uses microsecond timestamps paired with strict increment tracking to guarantee nonces remain strictly increasing across rapid sub-second agent transfers.
  5. 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.

Verdict

LGTM — Robust, security-minded A2A transfer harness with comprehensive test coverage and backward-compatible signature fallback.

Wallet: RTCfe13452d122263caf633ab1876bd9631133b68b1

@FlintLeng FlintLeng left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  1. The module uses urllib (stdlib) rather than requests. This avoids a dependency but the code is more verbose. Acceptable for a tool that may run in minimal environments.

  2. The chain_id parameter is optional but passed to both the current and legacy message schemas. A pre-fee node that doesn't understand chain_id would 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

BCOS-L1 Beacon Certified Open Source tier BCOS-L1 (required for non-doc PRs) documentation Improvements or additions to documentation size/XL PR: 500+ lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants