rpc: return -22 TX decode failed for invalid rawtx (Q-41) - #626
Hero-Gamer wants to merge 1 commit into
Conversation
|
This is what I am being told by my boss on what to say.. 👇 "I think I understand the situation better now: Q-59 was already considered closed on master, so I don't want to present #626 as reopening/closing Q-59. What #626 does is fix the prerequisites that were preventing I'm leaving that second issue out of this PR and can open it separately. So #626 is really a follow-up that makes the existing compatibility test actually exercise the Q-59 path, while also revealing a previously masked parity issue." |
e3473b7 to
b699791
Compare
There was a problem hiding this comment.
Q-59 is already closed. This PR is not a Q-59 bugfix — it reverses two closed honesty decisions and retargets operator RPC so a still-skipped Core test can walk a few more lines.
Current contract (docs/rpc.md, CHANGELOG RPC honesty):
- Node
maxfeerateis sat/vB (default 10000;>= 100000is-8). Core BTC/kvB is the functional-harness proxy only. getmempoolinfo.permitbaremultisigis alwaystrue. Libre has no CoreIsStandardbare-multisig gate;--permitbaremultisigis not a node flag.
mempool_accept.py stays skip / rpc-dialect in inventory.toml. The analog already names the rawtxs type needle and the bare-multisig shim. Do not change the operator surface to chase a test you are not flipping to run.
If the real ask is “put Core BTC/kvB on the node so wallets speak Core units,” that is a new dialect change: update docs/rpc.md / CHANGELOG / proxy module docs, keep one parser, and do not floor sat_kvb / 1000 to 0 (unlimited). That is not this mix of permitbaremultisig lie + identity proxy + leftover comments.
decode_tx_hex → -22 TX decode failed matches decoderawtransaction and is the one change here that looks like a real sendraw/testmempoolaccept needle. Keep it only with an docs/rpc.md line for those methods — and not bundled with the dialect flip.
Agree with the PR comment: do not present this as reopening/closing Q-59. The leftover txn-already-known vs txn-already-in-mempool belongs in its own issue/PR if you want it.
| // Core reports false by default; rbitcoin does not enforce bare-multisig IsStandard — reported for compat only | ||
| "permitbaremultisig": false, |
There was a problem hiding this comment.
Q-59 closed this the other way: permitbaremultisig is always true because Libre has no Core IsStandard bare-multisig gate (docs/rpc.md, CHANGELOG Q-59 slice). Reporting false “for compat only” while still admitting bare multisig is a lie.
mempool_accept.py is skip/rpc-dialect in part because -permitbaremultisig is shim-ignored. Do not change the JSON field to uns-kip a test you are not running.
| fn parse_btc_kvb_to_sat_kvb_for_maxfeerate(s: &str) -> Result<u64, Value> { | ||
| let s = s.trim(); | ||
| if s.is_empty() { | ||
| return Err(rpc_error( | ||
| ERR_TYPE_ERROR, | ||
| "Amount is not a number or string", | ||
| )); | ||
| } | ||
| if s.starts_with('-') { | ||
| return Err(rpc_error(ERR_TYPE_ERROR, "Amount out of range")); | ||
| } | ||
| // Reuse BTC parsing but map its errors to -3 for this RPC | ||
| parse_rpc_btc_to_sat(s).map_err(|e| { | ||
| // If underlying was Amount out of range -> keep -3, else also -3 for invalid amount? Core uses -3 for out of range only. | ||
| // For simplicity, preserve message but force code -3 if message is Amount out of range, else keep original for invalid amount which Core also reports as -3 in this path. | ||
| let msg = e | ||
| .get("message") | ||
| .and_then(|m| m.as_str()) | ||
| .unwrap_or("Invalid amount"); | ||
| if msg == "Amount out of range" { | ||
| rpc_error(ERR_TYPE_ERROR, "Amount out of range") | ||
| } else { | ||
| // Core still uses -3 for amount parsing in this path per test history | ||
| // Map Invalid amount -> -3 as well to match Core's RPC_TYPE_ERROR for maxfeerate | ||
| if msg == "Invalid amount" { | ||
| rpc_error(ERR_TYPE_ERROR, "Invalid amount") | ||
| } else { | ||
| e | ||
| } | ||
| } | ||
| }) | ||
| } |
There was a problem hiding this comment.
These comments restate the function. Drop them (CONTRIBUTING 7).
The map_err tree is a no-op on codes: parse_rpc_btc_to_sat already returns Amount out of range / Invalid amount; you only force -3 and then branch on the same two strings. If the node stays sat/vB, this helper goes away. If you really switch the node to BTC/kvB, parse once and map errors in one match — do not add a second BTC parser beside amount_sat_from_json.
| fn parse_maxfeerate_btc_kvb_to_sat_vb(v: &Value) -> Result<u64, Value> { | ||
| // RPC is BTC/kvB; internal is sat/vB. Core: 0 = unlimited, >=1 BTC/kvB = -8 | ||
| let sat_kvb = match v { | ||
| Value::Number(n) => parse_btc_kvb_to_sat_kvb_for_maxfeerate(&n.to_string())?, | ||
| Value::String(st) => parse_btc_kvb_to_sat_kvb_for_maxfeerate(st)?, | ||
| _ => { | ||
| return Err(rpc_error( | ||
| ERR_TYPE_ERROR, | ||
| "Amount is not a number or string", | ||
| )) | ||
| } | ||
| Value::String(s) => { | ||
| let t = s.trim(); | ||
| if t.starts_with('-') { | ||
| return Err(rpc_error(ERR_INVALID_PARAMETER, "Amount out of range")); | ||
| } | ||
| t.parse::<u64>().map_err(|_| { | ||
| rpc_error( | ||
| ERR_INVALID_PARAMETER, | ||
| "maxfeerate must be an integer sat/vB", | ||
| ) | ||
| }) | ||
| } | ||
| _ => Err(rpc_error( | ||
| ERR_TYPE_ERROR, | ||
| "maxfeerate is not a number or string", | ||
| )), | ||
| }; | ||
| if sat_kvb == 0 { | ||
| return Ok(0); | ||
| } | ||
| let sat_vb = sat_kvb / 1000; | ||
| if sat_vb >= MAX_ALLOWED_FEERATE_SAT_VB { | ||
| return Err(rpc_error( | ||
| ERR_INVALID_PARAMETER, | ||
| "Fee rates larger than or equal to 1BTC/kvB are not accepted", | ||
| )); | ||
| } | ||
| Ok(sat_vb) | ||
| } |
There was a problem hiding this comment.
sat_kvb / 1000 floors. 0.000001 BTC/kvB is 100 sat/kvB = 0.1 sat/vB (Libre min-relay). This becomes 0, which fee_exceeds_max treats as unlimited.
The cap is also checked twice (here and again in opt_maxfeerate_sat_vb). One site.
Owner docs still say the node argument is sat/vB and the proxy translates Core BTC/kvB. Flipping the node without docs/rpc.md + CHANGELOG is a silent dialect change. Cross-surface submitpackage(..., 0.00001) is the same unit rewrite of a test that used to pass 1 sat/vB — that only makes sense after the owner docs move.
| let raw = params.get(0, "rawtxs"); | ||
| let arr = match raw { | ||
| Some(v) if v.is_array() => v.as_array().unwrap(), | ||
| Some(v) => { | ||
| let got = match v { | ||
| Value::Null => "null", | ||
| Value::Bool(_) => "boolean", | ||
| Value::Number(_) => "number", | ||
| Value::String(_) => "string", | ||
| Value::Array(_) => "array", | ||
| Value::Object(_) => "object", | ||
| }; | ||
| return Err(rpc_error( | ||
| ERR_TYPE_ERROR, | ||
| format!("JSON value of type {got} for field rawtxs is not of expected type array"), | ||
| )); | ||
| } | ||
| None => { | ||
| return Err(rpc_error( | ||
| ERR_TYPE_ERROR, | ||
| "JSON value of type null for field rawtxs is not of expected type array", | ||
| )); | ||
| } | ||
| }; | ||
| if arr.is_empty() || arr.len() > 25 { | ||
| return Err(rpc_error( | ||
| ERR_INVALID_PARAMETER, | ||
| "Array must contain between 1 and 25 transactions.", | ||
| )); |
There was a problem hiding this comment.
inventory.toml analog for mempool_accept.py is exactly this needle: rbitcoin returns rawtxs array required, Core returns -3 JSON value of type string is not of expected type array. You are changing the operator dialect for a test that remains skip.
If Core type-check text is now the COMPAT contract, update docs/rpc.md and the analog, and say whether submitpackage (package array required) stays the old shape on purpose.
| let mem = dispatch(&ctx, "getmempoolinfo", vec![]).unwrap(); | ||
| assert_eq!( | ||
| mem["permitbaremultisig"], true, | ||
| mem["permitbaremultisig"], false, |
There was a problem hiding this comment.
getmempoolinfo_permitbaremultisig_is_always_true now asserts false. The message still says Libre has no IsStandard gate — that is the reason the field is true, not false. Revert with the JSON field.
| if btc >= 1: | ||
| raise RpcError(-8, _CORE_MAXFEERATE_MSG) | ||
| return int(round(btc * 100_000)) | ||
| return btc # node now expects BTC/kvB like Core (Q-59), not sat/vB |
There was a problem hiding this comment.
Module docstring and _MAXFEERATE_METHODS comment still say the node is sat/vB and this function rewrites BTC/kvB → sat/vB. The body now returns btc (and is annotated -> int while returning a float).
Worse: invalid amounts still die in the proxy as -8 Invalid amount, while the node tests now expect -3. Negatives are -3 here and on the node. Core functional therefore still does not exercise the new node parser for those paths — two error tables.
If the node stays sat/vB, restore int(round(btc * 100_000)) and keep -8 for the Core needles the proxy owns. If the node moves to BTC/kvB, stop rewriting and let the node return the errors (or delete this helper).
|
rebase this PR on the latest master - made some docs updates relevant to agents - should make working on the repo more token efficient and maybe inform updating this PR |
Core mempool_accept expects -22 at line 98 (ff00baar). Previous returned -8 with tx decode: prefix. This is the only needle from reardencode#626 that matches existing contract. permitbaremultisig stays true per D-03, maxfeerate stays sat/vB with proxy BTC/kvB translation. txn-already-known vs in-mempool is separate PR.
b699791 to
0d46ea9
Compare
Ready for re-review. @reardencode |
Context: Q-41 mempool_accept dialect.
Original #626 tried to fix several dialects at once (maxfeerate BTC/kvB -> sat/vB, permitbaremultisig default, etc).
Review:
This PR now scoped to single needle reviewer approved:
testmempoolaccept with invalid hex "ff00baar" (mempool_accept.py:98) expects Core behavior -22 "TX decode failed".
Previously returned -8 "tx decode: {e}".
Change: crates/rbitcoin-rpc/src/methods/mine.rs decode_tx_hex() -> ERR_DESERIALIZATION
Tests: 105 passed; 0 failed (TMPDIR=/tmp cargo test -p rbitcoin-rpc --lib)
Next PR after this merges: fix txn-already-known ordering.