Repository navigation
rpc: scriptPubKey type is nulldata only for push-only data - #1187
Conversation
20ab353 to
163dd55
Compare
There was a problem hiding this comment.
Verdict
APPROVE. I would merge this as-is.
script_core_type is the type string on scriptPubKey JSON. The patch returns nulldata only when the script starts with OP_RETURN and the bytes after that are push-only. That is Bitcoin Core 31.1 Solver: size >= 1 && script[0] == OP_RETURN && IsPushOnly(begin+1) (src/script/solver.cpp, src/script/script.cpp). rust-bitcoin 0.32.102 Script::is_push_only walks instructions() with minimal-push checks off and accepts any opcode <= 0x60, which is Core's opcode > OP_16 rule, including OP_RESERVED (0x50) and OP_1NEGATE (0x4f). A truncated push makes the iterator fail, so the type is nonstandard, same as GetOp failing.
The change is RPC output only. Consensus and relay still treat a leading OP_RETURN as unspendable through is_unspendable (leading OP_RETURN, or a script longer than 10,000 bytes). gettxout returns null for those confirmed outputs before it builds scriptPubKey. The new type is visible on decodescript, decoderawtransaction, getrawtransaction, getblock verbosity 2, and a mempool coin from gettxout / REST getutxos. scriptSig objects have no type. A leading OP_RETURN cannot fall through into pubkey or multisig.
An empty script makes is_op_return false, so the [1..] slice is not evaluated. A PUSHDATA4 length of 0xffffffff stays a short script and comes back nonstandard. No new dependency, unsafe block, or second classification path. The changelog fragment matches changelog.d/ format.
Findings
No bugs.
Nit: the committed rows stop at direct pushes and PUSHDATA1. They omit PUSHDATA2 / PUSHDATA4, a push over 520 bytes, and a length that does not fit. A throwaway probe of those cases on this commit matched Core. I would not hold the PR for more rows.
What was built and tested
Worktree at 20ab353da61e7671fcb8d99ad726422982a20a5b, then restored. Nothing committed or pushed. No GitHub comment.
cargo fmt --all -- --check: exit 0.cargo test -p rbitcoin-rpc --test suite decodescript_nulldata_type_matches_core -- --quiet: exit 0.- Same test with the push-only conjunct removed, leaving
is_op_return()alone: exit 101. First failure is6a61, leftnulldata, rightnonstandard. Production file restored after. - Throwaway probe, not left in the tree:
OP_RESERVED,OP_1NEGATE,OP_16,OP_VER, two pushes, 75- and 76-byte pushes, a 521-bytePUSHDATA2, truncatedPUSHDATA2, empty and non-minimalPUSHDATA4,PUSHDATA4length0xffffffff, and a 10,000-byte push. Exit 0. cargo test -p rbitcoin-rpc --test suite -- --quiet: exit 0 (134 tests).cargo clippy -p rbitcoin-rpc --all-targets -- -D warnings: exit 0.
Core functional was not run here. CI skipped that job on this SHA. decodescript stays on the node, and the in-tree RPC suite is the pin for this path. GitHub test, qc, macos, windows, and pread were already green.
|
Two independent reviews, nothing blocking a merge. |
163dd55 to
823dbea
Compare
|
@reardencode this is rebased onto current master (4036f76) and ready. The only conflict was in the test file, where #1176 added its desc test in the same spot, and both tests are kept. rearden-grok's nit is addressed in 823dbea. The nulldata test now also covers PUSHDATA2 and PUSHDATA4 pushes, a 521-byte push, and PUSHDATA2/PUSHDATA4 lengths that run past the end of the script, all pinned to Core 31.1's types. Required checks are green. |
823dbea to
62f14ee
Compare
|
@reardencode rebased again, onto current master (a9b3e49) now that #1183 has merged. The only conflict was in the test file, where #1183 added its multisig test in the same spot. Both tests are kept, and the |
62f14ee to
1bcbb3a
Compare
|
@reardencode rebased onto current master (e5079de) now that #1186 has merged. #1186's witness arm sits right next to the nulldata arm in |
`type` said `nulldata` for any script whose first byte is OP_RETURN. Core's Solver also needs the rest of the script to pass IsPushOnly: each op is a complete push or an opcode up to OP_16. A script that fails that falls through to `nonstandard`. So `6aac`, `6a6a`, and the truncated push `6a4c` were `nulldata` here and `nonstandard` in Core. `type` now asks rust-bitcoin's `is_push_only` about the bytes after OP_RETURN. It rejects a truncated push where Core's GetOp does, and it counts OP_RESERVED and OP_1NEGATE as pushes, as Core does. A bare OP_RETURN and a push of any size stay `nulldata`. Expected types are Bitcoin Core 31.1 `decodescript` on regtest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-up. The nulldata type test stopped at direct pushes and PUSHDATA1. It now also covers PUSHDATA2 and PUSHDATA4 pushes, a 521-byte push (Core's IsPushOnly has no size limit), and PUSHDATA2 or PUSHDATA4 lengths that run past the end of the script. Expected types are Bitcoin Core 31.1 decodescript on regtest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1bcbb3a to
b2c3556
Compare
|
@reardencode rebased onto current master (4f400f4) now that #1188 has merged. The only conflict was in the test file, where both tests are kept. |
Summary
Core's
SolverreturnsNULL_DATAonly when the bytes after the leadingOP_RETURNpassIsPushOnly. Otherwise the script falls through toNONSTANDARD. rbitcoin looked only at the first byte, so6aacand6a4cwerenulldata. rust-bitcoin'sis_push_onlyfails in the same places as Core'sGetOp, and it countsOP_0,OP_1NEGATE,OP_RESERVED, andOP_1–OP_16as pushes, as Core does.Evidence
decodescript_nulldata_type_matches_corefailed on6a61:left: String("nulldata"),right: String("nonstandard"). After:test methods::decodescript_nulldata_type_matches_core ... okdecodescripton regtest. In theOP_RETURNbranch, mismatches went from 887 to 0, and all 887 were Corenonstandardvs rbitcoinnulldata. The one mismatch outside the branch,5101ab51ae(Corenonstandard, rbitcoinmultisig), is the multisig branch that rpc: scriptPubKey type finds bare multisig as Core does #1183 fixed. That fix is now on master.6a6a0051504f606a4c01cd(non-minimal PUSHDATA1)6a4c64+ 100 bytes6a4d0100ab(PUSHDATA2)6a4e01000000ab(PUSHDATA4)6a4d0902+ 521 bytes6a616aac6a6a6a01abac6a01(truncated push)6a4c(truncated PUSHDATA1)6a4d0500ab(truncated PUSHDATA2)6a4e05000000ab(truncated PUSHDATA4)OP_RETURNbranch.cargo fmt --all -- --check,./scripts/ast-grep.sh,cargo clippy --workspace --all-targets -- -D warnings,cargo test --workspace --quiet --no-fail-fast, and./scripts/ci-os-smoke.shall pass.cargo denyis not installed here and did not run.Merge Danger
Door: two-way
Blast Radius: RPC
scriptPubKey.typechanges fromnulldatatononstandardfor any script that starts withOP_RETURNand then has a non-push opcode or a truncated push. That affectsdecodescript,decoderawtransaction,getrawtransaction,getblockverbosity 2,gettxout, and RESTgetutxos. Push-onlyOP_RETURNoutputs, including the witness commitment and pushes of any size, staynulldata. Consensus, policy, andgettxout's unspendable rule (IsUnspendable, leadingOP_RETURN) do not change.🤖 Generated with Claude Code