fix(rpc): refuse to pay Platform addresses before the v24 hard fork - #7577
fix(rpc): refuse to pay Platform addresses before the v24 hard fork#7577PastaPastaPasta wants to merge 1 commit into
Conversation
knst
left a comment
There was a problem hiding this comment.
concept NACK, because it should not be limited by wallet.
Maybe it could be soft guard in RPC code but definitely not CreateTransactionInternal.
Anyway, if there's a node is out of sync and think that 'v24' is not activated, user should still be able to create asset v2 transaction and spend funds. I think it is not typical but not rare situation. I believe, user should be warned about it but not strictly limited to create v2 asset lock prior v24 activation.
|
Thanks — I went looking for what the codebase already does here, and you're right about the layer. Reworking the PR accordingly. Details, since two of the three points cut differently: Layer: agreed, this belongs in RPC. The wallet has never gated on deployment state — there is no One caveat worth flagging: Hard error vs. warning: I'd keep it an error. On the out-of-sync node: I don't think that user can spend either way. A question for your PR rather than mine. There's a third pattern in the tree that would serve the out-of-sync user properly: I left that out of this PR because it changes your feature's semantics rather than just its guard rails, and there's one thing I can't answer from the code: does Platform interpret the credit output differently based on the payload version, or only on the output script? If it's only the script, then a v1 lock to a DIP-18 P2PKH address is indistinguishable on-chain and users could send to Platform addresses before v24 and on default-policy nodes. Happy to implement it if you think that holds. 🤖 Posted autonomously by Claude on behalf of pasta. |
sendtoaddress and sendmany built a version 2 asset lock for Platform recipients unconditionally, without checking whether the v24 hard fork had activated. On a node running with -acceptnonstdtxn=1 (the only configuration where Platform sends currently relay at all), paying a Platform address before v24 activation built, signed and committed a consensus-invalid transaction (bad-assetlocktx-version-2). CommitTransaction returns void and only logs the broadcast failure, so the caller still got a txid back and the user was left with spent inputs, a permanently pending wallet entry and abandontransaction as the only way out. Gate this the way ProTx RPCs already gate on DIP0003 in SignAndSendSpecialTx(): check activation in the RPC layer rather than in the wallet, which has never consulted deployment state. The check goes in ParseRecipients next to the other Platform-address validation, and throws RPC_INVALID_PARAMETER with wording matching the existing "requires <version>" refusals in rpc/evo_util.cpp. The accessor still has to live on interfaces::Chain because wallet RPCs have no direct ChainstateManager access.
68db044 to
c9fa309
Compare
|
Reworked in c9fa309 — the check moved out of Verified: full One consequence worth naming: The version-derivation question from my previous comment still stands if you'd rather solve it at the root. 🤖 Posted autonomously by Claude on behalf of pasta. |
|
✅ Final review complete — no blockers (commit c9fa309) |
WalkthroughThe public chain interface now exposes v24 activation status. Estimated code review effort: 2 (Simple) | ~10 minutes Mergeability Score: ⚪ Minimal · up to The change cleanly rejects Platform payments before v24 and preserves the post-v24 path. No actionable merge-blocking risk remains; adding explicit post-v24 sendmany coverage is a minor follow-up for test completeness. Sequence Diagram(s)sequenceDiagram
participant RPCClient
participant WalletRPC
participant ParseRecipients
participant Chain
RPCClient->>WalletRPC: sendtoaddress or sendmany with Platform address
WalletRPC->>ParseRecipients: pass wallet chain interface
ParseRecipients->>Chain: isV24Active()
Chain-->>ParseRecipients: activation status
ParseRecipients-->>WalletRPC: invalid-parameter error when inactive
WalletRPC-->>RPCClient: reject payment
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/functional/feature_asset_locks.py`:
- Around line 777-782: Add post-v24 coverage alongside the existing
sendtoaddress assertions in the asset-lock functional test: invoke
node_wallet.sendmany with the Platform address after v24 activation, verify the
resulting asset-lock transaction is version 2, and update the expected
credit-pool accounting to include this additional transaction.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 57dc7200-1fc3-4d73-8c2a-936a614072c3
📒 Files selected for processing (4)
src/interfaces/chain.hsrc/node/interfaces.cppsrc/wallet/rpc/spend.cpptest/functional/feature_asset_locks.py
thepastaclaw
left a comment
There was a problem hiding this comment.
Final validation — Codex/Sol only (Phase 2 disabled)
At exact head c9fa309, the v24 gate uses next-block activation semantics and both affected RPCs exercise the rejection path; the existing post-v24 sendtoaddress test exercises the active branch of their shared ParseRecipients implementation. The requested post-v24 sendmany case would duplicate that shared branch rather than cover distinct changed behavior, so no actionable issue remains. Source: reviewer backend model gpt-5.6-sol; final verifier backend model gpt-5.6-sol. openclaw-agent/cliproxy/gpt-5.6-sol is orchestration-only and not reviewer evidence.
Validated zero-blocker Codex/Sol precheck evidence was promoted to final because Phase 2 (Sonnet/Opus) is temporarily disabled. This is Codex/Sol-only final validation, not Codex + Sonnet/Opus coverage.
Review provenance
- Codex reviewers:
gpt-5.6-sol— general (completed) - Verifier:
gpt-5.6-sol— verifier - Sonnet/Opus: not run (Phase 2 disabled — temporary Codex/Sol-only final)
- Secondary pass: disabled (
temporary_phase2_sonnet_disable)
Issue being fixed or feature implemented
Follow-up to #7294. Built on top of that PR's branch; only the last commit is new here — please review just
fix(rpc): refuse to pay Platform addresses before the v24 hard fork.sendtoaddressandsendmanybuild a version 2 asset lock for Platform recipients unconditionally, without checking whether the v24 hard fork has activated. On a node running with-acceptnonstdtxn=1— currently the only configuration where Platform sends relay at all, since v2 asset locks are deliberately non-standard — paying a Platform address before v24 activation builds, signs and commits a consensus-invalid transaction (bad-assetlocktx-version-2).That is not a recoverable error today.
CWallet::CommitTransactionreturnsvoidand only logs the broadcast failure, so the RPC still returns a txid and the user is left with inputs marked spent, a wallet entry pending forever, a failed rebroadcast every 1-3 hours, andabandontransactionas the only way out. On default-policy nodes the standardness pre-flight happens to block it, but with an error suggesting-acceptnonstdtxn=1, which pre-fork would only make things worse.What was done?
Gated it the same way ProTx RPCs already gate on DIP0003 in
SignAndSendSpecialTx()(src/rpc/evo.cpp:434) — in the RPC layer, not in the wallet, which has never consulted deployment state.ParseRecipients, next to the other Platform-address validation, and throwsRPC_INVALID_PARAMETERwith wording matching the existing"... requires <version>"refusals insrc/rpc/evo_util.cpp:97andsrc/rpc/evo.cpp:847.Chain::isV24Active()is added becausesrc/wallet/rpc/has no directChainstateManageraccess — wallet RPCs reach the node only throughinterfaces::Chain.ParseRecipientsis the only place in the tree that setsCRecipient::fPlatformTransfer; every otherCRecipientconstruction uses 3-element aggregate initialisation, and the GUI cannot reach the path at all (WalletModel::validateAddressusesIsValidDestinationString, which rejects DIP-18 addresses). So the RPC-layer check covers every reachable path today. Worth noting for the Platform GUI work: a futurewalletmodel.cpppath that constructs Platform recipients would reachCreateTransactionwithout passing throughParseRecipientsand would need the same guard.How Has This Been Tested?
Extended
feature_asset_locks.py: in the pre-v24 section, bothsendtoaddressandsendmanyto a Platform address on the permissive (-acceptnonstdtxn=1) node must now fail with-8instead of committing an unconfirmable transaction. The full file was run end-to-end against a debug build with-DDEBUG_LOCKORDERand passes, with no lock-order warnings from the newcs_wallet->cs_maincall (the same order already used bychain().havePruned()atsrc/wallet/rpc/backup.cpp:143). Existing post-fork wallet tests in the same file cover that the gate does not block sends after activation.Breaking Changes
None beyond #7294 itself. Paying a Platform address pre-v24 now fails cleanly with
-8instead of producing a stuck transaction.Checklist: