diff --git a/crates/rpc/src/handlers/mining.rs b/crates/rpc/src/handlers/mining.rs index ec70fa2a..6bc27b73 100644 --- a/crates/rpc/src/handlers/mining.rs +++ b/crates/rpc/src/handlers/mining.rs @@ -759,7 +759,10 @@ mod tests { }; use parking_lot::Mutex; - use crate::handlers::util::{GENERATEBLOCK_INVALID_OUTPUT, descriptor_checksum}; + use crate::handlers::util::{ + GENERATEBLOCK_INVALID_OUTPUT, GENERATEBLOCK_MULTIPATH, GENERATEBLOCK_NEEDS_PRIVATE_KEYS, + GENERATEBLOCK_RANGED, descriptor_checksum, + }; struct FakeMiningControl { template: Mutex>, @@ -2244,4 +2247,43 @@ mod tests { assert_eq!(error.code(), RpcError::CORE_NOT_FOUND); assert_eq!(error.to_string(), GENERATEBLOCK_INVALID_OUTPUT); } + + // CONTRACT: docs/contracts/external-api.md#API-28 + #[test] + fn generateblock_rejects_multipath_before_ranged_like_core() { + let control = FakeMiningControl::with_template(sample_template()); + let ctx = ctx_with_control(control); + let tpub = "tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B"; + let multipath = generateblock(&ctx, &json!([format!("wpkh({tpub}/<0;1>/0)"), []])) + .err() + .unwrap_or_else(|| panic!("multipath descriptor must fail")); + assert_eq!(multipath.code(), RpcError::CORE_INVALID_PARAMETER); + assert_eq!(multipath.to_string(), GENERATEBLOCK_MULTIPATH); + let both = generateblock(&ctx, &json!([format!("wpkh({tpub}/<0;1>/*)"), []])) + .err() + .unwrap_or_else(|| panic!("multipath+ranged descriptor must fail as multipath")); + assert_eq!(both.code(), RpcError::CORE_INVALID_PARAMETER); + assert_eq!(both.to_string(), GENERATEBLOCK_MULTIPATH); + let ranged = generateblock(&ctx, &json!([format!("wpkh({tpub}/0/*)"), []])) + .err() + .unwrap_or_else(|| panic!("ranged descriptor must fail")); + assert_eq!(ranged.code(), RpcError::CORE_INVALID_PARAMETER); + assert_eq!(ranged.to_string(), GENERATEBLOCK_RANGED); + } + + // CONTRACT: docs/contracts/external-api.md#API-28 + #[test] + fn generateblock_rejects_hardened_xpub_like_core() { + let control = FakeMiningControl::with_template(sample_template()); + let ctx = ctx_with_control(control); + let tpub = "tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B"; + let error = generateblock(&ctx, &json!([format!("wpkh({tpub}/0h/0)"), []])) + .err() + .unwrap_or_else(|| panic!("hardened xpub must fail Expand")); + assert_eq!(error.code(), RpcError::CORE_NOT_FOUND); + assert_eq!(error.to_string(), GENERATEBLOCK_NEEDS_PRIVATE_KEYS); + let tprv = "tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS"; + generateblock(&ctx, &json!([format!("wpkh({tprv}/0h/0)"), []])) + .unwrap_or_else(|err| panic!("hardened tprv must Expand: {err}")); + } } diff --git a/crates/rpc/src/handlers/util.rs b/crates/rpc/src/handlers/util.rs index 34c4e666..09745a2d 100644 --- a/crates/rpc/src/handlers/util.rs +++ b/crates/rpc/src/handlers/util.rs @@ -333,6 +333,9 @@ pub(crate) fn deriveaddresses(ctx: &Arc, params: &Value) -> Result RpcError { match error { DescriptorError::Range(message) => RpcError::InvalidParameter(message.to_owned()), + DescriptorError::PrivateKeys => { + RpcError::InvalidAddressOrKey(GENERATEBLOCK_NEEDS_PRIVATE_KEYS.to_owned()) + } DescriptorError::Parse(message) => RpcError::InvalidAddressOrKey(message), } } @@ -532,6 +535,8 @@ enum DescriptorError { Parse(String), /// The derivation range does not match the descriptor. Range(&'static str), + /// `Expand` needs a private key (hardened path from an xpub, …). + PrivateKeys, } impl core::fmt::Display for DescriptorError { @@ -539,6 +544,7 @@ impl core::fmt::Display for DescriptorError { match self { Self::Parse(message) => write!(f, "{message}"), Self::Range(message) => write!(f, "{message}"), + Self::PrivateKeys => write!(f, "{GENERATEBLOCK_NEEDS_PRIVATE_KEYS}"), } } } @@ -972,17 +978,25 @@ fn strip_checksum(text: &str) -> &str { } pub(crate) const GENERATEBLOCK_INVALID_OUTPUT: &str = "Error: Invalid address or descriptor"; +pub(crate) const GENERATEBLOCK_MULTIPATH: &str = "Multipath descriptor not accepted"; +pub(crate) const GENERATEBLOCK_RANGED: &str = + "Ranged descriptor not accepted. Maybe pass through deriveaddresses first?"; +pub(crate) const GENERATEBLOCK_NEEDS_PRIVATE_KEYS: &str = + "Cannot derive script without private keys"; /// Coinbase script for `generateblock`'s `output` argument (`API-05`). /// /// CONTRACT: docs/contracts/external-api.md#API-26 +/// CONTRACT: docs/contracts/external-api.md#API-28 pub(crate) fn generateblock_payout_script( text: &str, network: bitcoin::Network, ) -> Result, RpcError> { match script_from_descriptor(text, network) { Ok(script) => Ok(script), - Err(error @ DescriptorError::Range(_)) => Err(descriptor_error(error)), + Err(error @ (DescriptorError::Range(_) | DescriptorError::PrivateKeys)) => { + Err(descriptor_error(error)) + } Err(_) => payout_script_from_address(text, network, GENERATEBLOCK_INVALID_OUTPUT), } } @@ -1028,38 +1042,62 @@ fn script_from_descriptor( let (descriptor, keys) = MiniscriptDescriptor::::parse_descriptor(&secp, &checksummed) .map_err(|error| DescriptorError::Parse(error.to_string()))?; - if descriptor.has_wildcard() || descriptor.is_multipath() { + if descriptor.is_multipath() { + return Err(multipath_descriptor_rejected()); + } + if descriptor.has_wildcard() { return Err(ranged_descriptor_rejected()); } ensure_keys_match_network(&descriptor, network)?; ensure_secret_keys_match_network(keys, network)?; + reject_hardened_xpub(&descriptor)?; let derived = descriptor .at_derivation_index(0) - .map_err(|error| DescriptorError::Parse(error.to_string()))?; + .map_err(|_| DescriptorError::PrivateKeys)?; Ok(derived.script_pubkey().as_bytes().to_vec()) } fn combo_payout_script(key: &str, network: bitcoin::Network) -> Result, DescriptorError> { let combo = parse_combo_info(key, network)?; - if combo.is_range || combo.paths.len() != 1 { + if combo.paths.len() != 1 { + return Err(multipath_descriptor_rejected()); + } + if combo.is_range { return Err(ranged_descriptor_rejected()); } let path = combo .paths .first() .ok_or_else(|| DescriptorError::Parse("Invalid combo descriptor".into()))?; + reject_hardened_xpub(path)?; let derived = path .at_derivation_index(0) - .map_err(|error| DescriptorError::Parse(error.to_string()))?; + .map_err(|_| DescriptorError::PrivateKeys)?; // Core's combo Expand emits P2PK first and generateblock uses scripts[0]. let pk = MiniscriptDescriptor::new_pk(combo_key(&derived)?); Ok(pk.script_pubkey().as_bytes().to_vec()) } +fn multipath_descriptor_rejected() -> DescriptorError { + DescriptorError::Range(GENERATEBLOCK_MULTIPATH) +} + fn ranged_descriptor_rejected() -> DescriptorError { - DescriptorError::Range( - "Ranged descriptor not accepted. Maybe pass through deriveaddresses first?", - ) + DescriptorError::Range(GENERATEBLOCK_RANGED) +} + +/// rust-miniscript panics in `at_derivation_index` on an xpub hardened step. +/// Core `Expand` returns false, which `getScriptFromDescriptor` maps to +/// `Cannot derive script without private keys`. An xprv is converted to an +/// xpub with those steps already applied during parse, so it never hits this. +fn reject_hardened_xpub( + descriptor: &MiniscriptDescriptor, +) -> Result<(), DescriptorError> { + if descriptor.for_any_key(DescriptorPublicKey::has_hardened_step) { + Err(DescriptorError::PrivateKeys) + } else { + Ok(()) + } } fn descriptor_text_with_optional_checksum(text: &str) -> Result { diff --git a/crates/rpc/src/registry.rs b/crates/rpc/src/registry.rs index 715bfc08..bb2f606d 100644 --- a/crates/rpc/src/registry.rs +++ b/crates/rpc/src/registry.rs @@ -141,7 +141,7 @@ declare_rows! { "submitheader", SurfaceKind::Rpc, Status::Implemented, "", CORE_VERSION, "Header-only admission through the same tree path as inbound P2P headers. Decode failures are -22; missing previous or invalid headers are -25.", "0.4.0", Some(mining::submitheader); "prioritisetransaction", SurfaceKind::Rpc, Status::Implemented, "", CORE_VERSION, "Dummy (params[1]) must be 0 or null; fee_delta is params[2]. Non-zero dummy is Core -8. Pooled dust outputs are -8 except on regtest.", "0.4.0", Some(mining::prioritisetransaction); "generatetoaddress", SurfaceKind::Rpc, Status::Implemented, "", CORE_VERSION, "Assembles, solves, and submits n blocks paying the given address through the mining coordinator. A non-address is Core -5 Error: Invalid address.", "0.4.0", Some(mining::generatetoaddress); - "generateblock", SurfaceKind::Rpc, Status::Implemented, "", CORE_VERSION, "Assembles and solves one block paying an address or descriptor from the listed mempool txids or raw txs in that order; third param is Core's submit flag. Unknown 64-hex txids are -5; raw-tx decode failures are -22. Invalid output is -5 Error: Invalid address or descriptor. Consensus failure before solve is -25 TestBlockValidity failed: {reason}.", "0.4.0", Some(mining::generateblock); + "generateblock", SurfaceKind::Rpc, Status::Implemented, "", CORE_VERSION, "Assembles and solves one block paying an address or descriptor from the listed mempool txids or raw txs in that order; third param is Core's submit flag. Unknown 64-hex txids are -5; raw-tx decode failures are -22. Invalid output is -5 Error: Invalid address or descriptor. Consensus failure before solve is -25 TestBlockValidity failed: {reason}. Multipath is -8 Multipath descriptor not accepted; ranged is -8 Ranged descriptor not accepted…; Expand without private keys is -5 Cannot derive script without private keys.", "0.4.0", Some(mining::generateblock); "getnetworkhashps", SurfaceKind::Rpc, Status::Implemented, "", CORE_VERSION, "Estimated hashes/s over a caller-chosen lookback ending at a caller-chosen height; default lookback 120, height the applied tip.", "0.4.0", Some(mining::getnetworkhashps); "getprioritisedtransactions", SurfaceKind::Rpc, Status::Implemented, "", CORE_VERSION, "Projects the mempool's signed fee-delta overlay, including txids not currently pooled. modified_fee is satoshis like Core mining RPCs, not BTC.", "0.4.0", Some(mining::getprioritisedtransactions); diff --git a/docs/contracts/README.md b/docs/contracts/README.md index 9f21962b..bf52f06d 100644 --- a/docs/contracts/README.md +++ b/docs/contracts/README.md @@ -46,7 +46,7 @@ match a regression. | [chain-events.md](chain-events.md) | `EVT-01`–`EVT-05` | `ChainSnapshot`, `ChainEventHint`, `ChainEventPublisher`, `ConsumerCursor`, `UndoStore`/`DisconnectMarker`, `ChainChangeProof`: the seam between the apply path and reconciliation consumers | `crates/node/src/txindex_worker.rs` (first consumer); any index mirroring the applied chain | `crates/node/src/state.rs` tests `record_publishes_snapshot_and_hints_in_commit_order`, `record_drops_hints_when_channel_full`, `active_chain_snapshot_anchors_at_restored_tip_after_restart`; `crates/node/src/txindex_worker_recovery_tests.rs` tests `shallow_reorg_rewinds_to_common_ancestor_then_replays`, `tip_change_during_rebuild_converges_on_new_tip`; `crates/node/src/apply.rs` tests `a_clean_disconnect_leaves_no_in_flight_marker`, `chain_change_proof_finish_restores_even_generation` | | [mempool-mutations.md](mempool-mutations.md) | `MPL-01`–`MPL-04` | Gateway ordering invariant, `MutationEnvelope`/`MutationResult` semantics, ZMQ `A`/`R` payload bytes, generation-validated admission and chain-change fencing | apply path (`crates/node/src/apply.rs`), `sendrawtransaction` (`crates/rpc/src/handlers/tx.rs`), ZMQ `sequence` subscribers (enforcer `--enable-mempool`) | `crates/mempool/src/gateway.rs` test `accepted_and_block_inclusion_events_arrive_in_commit_order`; `crates/node/src/zmq_publisher.rs` tests `block_inclusion_suppresses_r_frames` and `mempool_event_payloads_carry_reversed_txid_label_and_le_sequence`; `crates/node/src/apply.rs` test `stable_generation_is_even_before_and_after_connect` | | [mempool-policy.md](mempool-policy.md) | `POL-01` | Pointer: relay policy contract pinned to Core 31.1 | `sendrawtransaction`/`testmempoolaccept` (`crates/rpc/src/handlers/tx.rs`), P2P relay admission | `crates/mempool/tests/policy_contract.rs` and `crates/rpc/tests/policy_contract.rs` (`cargo test -p bitcoin-rs-mempool --test policy_contract` / `-p bitcoin-rs-rpc --test policy_contract`) | -| [external-api.md](external-api.md) | `API-01`–`API-27` | Pointer: JSON-RPC/REST/ZMQ manifest, generated reference, error code mappings, query budgeting, solo-mining generate, `getnetworkhashps` snapshot behavior, BIP22/BIP23 template extras, mainnet GBT operational gates, `submitheader`, GBT client-rule negotiation, `submitblock` decode, GBT proposal request parsing, `submitblock` uncommitted witness fill, Core v31 submit/proposal duplicate vocabulary, BIP22 reject-reason mapping, GBT `vbrequired` always 0, Core `CheckWitnessMalleation` reject reasons, GBT `coinbaseaux.flags` empty hex, `prioritisetransaction` dummy/`fee_delta` arity, pooled-dust refusal, reorged scripts-valid duplicate, getmininginfo omitted optional fields, and estimatesmartfee Core conf_target/estimate_mode gates, and generateblock Core txid/raw-tx parse errors, and getprioritisedtransactions modified_fee in satoshis, and generate invalid-output Core -5 text, and generateblock TestBlockValidity before solve | RPC/REST/ZMQ clients; `tools/bip300301-enforcer` | `crates/rpc/tests/manifest_coverage.rs` tests `rpc_rows_and_the_live_registry_agree_both_ways`, `generated_reference_matches_checked_in`; `crates/rpc/src/handlers/mining.rs` generate, GBT extra, GBT gate, `submitheader`, GBT rule-negotiation, `submitblock` decode, GBT proposal-parse, `coinbaseaux`, `prioritisetransaction` dummy/dust, and `getmininginfo` omit-null tests; `crates/node/tests/mining.rs` generate, GBT extra, `submitheader`, uncommitted-witness submit, submit/proposal duplicate, BIP22 reject-reason, `vbrequired`, witness-malleation proposal, and reorged scripts-valid duplicate tests; `crates/mining/src/coinbase.rs` uncommitted-witness tests; `crates/consensus/src/verify_block.rs` CheckWitnessMalleation tests; `crates/node/src/mining.rs` test `hash_ps_at_rejects_a_height_the_tip_cannot_resolve`; `crates/node/tests/mining.rs` test `network_hash_ps_rejects_core_invalid_windows` | +| [external-api.md](external-api.md) | `API-01`–`API-28` | Pointer: JSON-RPC/REST/ZMQ manifest, generated reference, error code mappings, query budgeting, solo-mining generate, `getnetworkhashps` snapshot behavior, BIP22/BIP23 template extras, mainnet GBT operational gates, `submitheader`, GBT client-rule negotiation, `submitblock` decode, GBT proposal request parsing, `submitblock` uncommitted witness fill, Core v31 submit/proposal duplicate vocabulary, BIP22 reject-reason mapping, GBT `vbrequired` always 0, Core `CheckWitnessMalleation` reject reasons, GBT `coinbaseaux.flags` empty hex, `prioritisetransaction` dummy/`fee_delta` arity, pooled-dust refusal, reorged scripts-valid duplicate, getmininginfo omitted optional fields, and estimatesmartfee Core conf_target/estimate_mode gates, and generateblock Core txid/raw-tx parse errors, and getprioritisedtransactions modified_fee in satoshis, and generate invalid-output Core -5 text, and generateblock TestBlockValidity before solve, and generateblock Core multipath/ranged/Expand private-key errors | RPC/REST/ZMQ clients; `tools/bip300301-enforcer` | `crates/rpc/tests/manifest_coverage.rs` tests `rpc_rows_and_the_live_registry_agree_both_ways`, `generated_reference_matches_checked_in`; `crates/rpc/src/handlers/mining.rs` generate, GBT extra, GBT gate, `submitheader`, GBT rule-negotiation, `submitblock` decode, GBT proposal-parse, `coinbaseaux`, `prioritisetransaction` dummy/dust, and `getmininginfo` omit-null tests; `crates/node/tests/mining.rs` generate, GBT extra, `submitheader`, uncommitted-witness submit, submit/proposal duplicate, BIP22 reject-reason, `vbrequired`, witness-malleation proposal, and reorged scripts-valid duplicate tests; `crates/mining/src/coinbase.rs` uncommitted-witness tests; `crates/consensus/src/verify_block.rs` CheckWitnessMalleation tests; `crates/node/src/mining.rs` test `hash_ps_at_rejects_a_height_the_tip_cannot_resolve`; `crates/node/tests/mining.rs` test `network_hash_ps_rejects_core_invalid_windows` | | [wallet-facing.md](wallet-facing.md) | `WF-01`–`WF-03` | Public Esplora/JSON-RPC surface an external wallet may use; no `NodeState` / `UtxoSet` / index types | [bitcoin-wallet](https://github.com/gosuda/bitcoin-wallet) (`btcw`); any Esplora HTTP client | `bin/bitcoin-rs/tests/wallet_facing.rs` tests `external_wallet_can_scan_estimate_and_broadcast`, `source_does_not_import_node_internals`; `crates/rpc/src/esplora.rs` tests `esplora_lives_only_under_the_api_prefix`, `api_is_the_public_electrs_directory`, `esplora_is_the_mempool_backend_superset` | | [p2p-wire.md](p2p-wire.md) | `P2P-01`–`P2P-02` | Pointer: command inventory in `crates/p2p/src/compat.rs`, handshake/reject/deviations pinned to Core 31.1 | `crates/p2p` peers; `crates/p2p/src/chain_query.rs` active-chain serving | `crates/p2p/tests/core_compat.rs` (`cargo test -p bitcoin-rs-p2p --test core_compat`); live lane `scripts/run-p2p-core-interop.sh` | | [qa-corpus.md](qa-corpus.md) | `QAC-01` | Pointer: fuzz seed provenance and refresh rules | `fuzz/fuzz_targets/{p2p_message,block_decode,tx_decode,script_eval}.rs`; CI fuzz lanes | `fuzz/CORPUS_PROVENANCE.md` mapping table; targets run under `cargo fuzz run -- -runs=10000` | diff --git a/docs/contracts/external-api.md b/docs/contracts/external-api.md index 132d9d4f..5664a307 100644 --- a/docs/contracts/external-api.md +++ b/docs/contracts/external-api.md @@ -19,7 +19,8 @@ reject reasons. `API-18` is GBT `coinbaseaux.flags`. `API-19` is is `generateblock` Core txid/raw-tx parse errors. `API-25` is `getprioritisedtransactions` `modified_fee` in satoshis. `API-26` is Core `generatetoaddress` / `generateblock` invalid-output text. `API-27` -is `generateblock` `TestBlockValidity` before solve. +is `generateblock` `TestBlockValidity` before solve. `API-28` is +`generateblock` multipath, ranged, and Expand private-key errors. ## Clauses @@ -82,8 +83,9 @@ is `generateblock` `TestBlockValidity` before solve. - `generatetoaddress` accepts only a network-valid address, uses mempool package selection, collects fees, and always submits. - `generateblock` accepts an address or descriptor (`require_checksum = false`; - a supplied checksum is verified). Ranged/multipath descriptors are refused. - The transactions array is required (an explicit `[]` is coinbase-only). + a supplied checksum is verified). Ranged and multipath descriptors are + refused (`API-28`). The transactions array is required (an explicit `[]` is + coinbase-only). Listed order is kept, those fees are not added to the coinbase, 64-character hex is a mempool txid, and decoded raw transactions are included without mempool admission. Extra positional arguments are rejected. Transaction @@ -334,7 +336,7 @@ is `generateblock` `TestBlockValidity` before solve. - `generatetoaddress` refuses a non-address with `-5` `Error: Invalid address`. - `generateblock` tries a descriptor first (`require_checksum = false`). - Ranged/multipath descriptors stay `-8`. If Parse fails, the text is + Ranged/multipath descriptors are `-8` (`API-28`). If Parse fails, the text is tried as an address; a miss is `-5` `Error: Invalid address or descriptor`, matching Core `src/rpc/mining.cpp`. A supplied checksum that fails Parse is refused @@ -358,6 +360,20 @@ is `generateblock` `TestBlockValidity` before solve. vocabulary). Shutdown and journal backpressure stay `Unavailable`, not TestBlockValidity. +### `API-28`: `generateblock` multipath, ranged, and Expand errors + +- **Owner**: `generateblock_payout_script` in + `crates/rpc/src/handlers/util.rs`. +- Core `getScriptFromDescriptor` throws before the address fallback: + - more than one parsed descriptor → `-8` + `Multipath descriptor not accepted` + - `IsRange()` → `-8` + `Ranged descriptor not accepted. Maybe pass through deriveaddresses first?` + - `Expand(0)` failure → `-5` + `Cannot derive script without private keys` +- Multipath is checked first, matching Core `descs.size() > 1` before + `IsRange()`. A descriptor that is both is the multipath error. + The wallet-facing subset of this surface — tip, fees, address/script queries, and broadcast over Esplora, plus the key-free node RPCs — is owned by [wallet-facing.md](wallet-facing.md). @@ -509,3 +525,7 @@ owned by [wallet-facing.md](wallet-facing.md). `generateblock_raw_tx_does_not_require_mempool_admission` - `crates/rpc/src/handlers/mining.rs` test `generateblock_maps_test_block_validity_to_verify_error` +- `API-28`: + - `crates/rpc/src/handlers/mining.rs` tests + `generateblock_rejects_multipath_before_ranged_like_core`, + `generateblock_rejects_hardened_xpub_like_core` diff --git a/docs/rpc-reference.md b/docs/rpc-reference.md index 8d8327d1..687f8f1b 100644 --- a/docs/rpc-reference.md +++ b/docs/rpc-reference.md @@ -75,7 +75,7 @@ Unimplemented-set derivation: audited against the Bitcoin Core v31.0 source comm | `submitheader` | 0.4.0 | Header-only admission through the same tree path as inbound P2P headers. Decode failures are -22; missing previous or invalid headers are -25. | | `prioritisetransaction` | 0.4.0 | Dummy (params[1]) must be 0 or null; fee_delta is params[2]. Non-zero dummy is Core -8. Pooled dust outputs are -8 except on regtest. | | `generatetoaddress` | 0.4.0 | Assembles, solves, and submits n blocks paying the given address through the mining coordinator. A non-address is Core -5 Error: Invalid address. | -| `generateblock` | 0.4.0 | Assembles and solves one block paying an address or descriptor from the listed mempool txids or raw txs in that order; third param is Core's submit flag. Unknown 64-hex txids are -5; raw-tx decode failures are -22. Invalid output is -5 Error: Invalid address or descriptor. Consensus failure before solve is -25 TestBlockValidity failed: {reason}. | +| `generateblock` | 0.4.0 | Assembles and solves one block paying an address or descriptor from the listed mempool txids or raw txs in that order; third param is Core's submit flag. Unknown 64-hex txids are -5; raw-tx decode failures are -22. Invalid output is -5 Error: Invalid address or descriptor. Consensus failure before solve is -25 TestBlockValidity failed: {reason}. Multipath is -8 Multipath descriptor not accepted; ranged is -8 Ranged descriptor not accepted…; Expand without private keys is -5 Cannot derive script without private keys. | | `getnetworkhashps` | 0.4.0 | Estimated hashes/s over a caller-chosen lookback ending at a caller-chosen height; default lookback 120, height the applied tip. | | `getprioritisedtransactions` | 0.4.0 | Projects the mempool's signed fee-delta overlay, including txids not currently pooled. modified_fee is satoshis like Core mining RPCs, not BTC. |