Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions crates/rpc/src/handlers/mining.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ use compact_str::CompactString;
use sonic_rs::{JsonContainerTrait, JsonValueMutTrait, JsonValueTrait, Value, json};

use crate::compat::convert::{
self, compact_target_hex, i64_saturated, sat_to_btc, signed_sat_to_btc,
typed_to_sonic_omitting_nulls,
self, compact_target_hex, i64_saturated, sat_to_btc, typed_to_sonic_omitting_nulls,
};
use crate::context::Context;
use crate::error::RpcError;
Expand Down Expand Up @@ -287,7 +286,15 @@ pub(crate) fn getprioritisedtransactions(
let _ = row.insert("fee_delta", json!(entry.fee_delta));
let _ = row.insert("in_mempool", json!(entry.in_mempool));
if let Some(modified_fee) = entry.modified_fee {
let _ = row.insert("modified_fee", json!(signed_sat_to_btc(modified_fee)));
// CONTRACT: docs/contracts/external-api.md#API-25
let sats = i64::try_from(modified_fee).unwrap_or_else(|_| {
if modified_fee.is_negative() {
i64::MIN
} else {
Comment on lines +290 to +293

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.

Remediation recommended

2. Signed saturation logic duplicated 📘 Rule violation ⌂ Architecture

The new i128-to-i64 saturation formula duplicates the conversion already embedded in
signed_sat_to_btc instead of using one canonical conversion helper. Future changes to RPC
numeric-boundary behavior could therefore diverge between the two paths.
Agent Prompt
## Issue description
The new `modified_fee` projection repeats signed `i128`-to-`i64` saturation logic already present in `signed_sat_to_btc`.

## Issue Context
Introduce a canonical signed saturation helper in the RPC conversion module, use it from both `signed_sat_to_btc` and `getprioritisedtransactions`, and retain the existing boundary behavior.

## Fix Focus Areas
- crates/rpc/src/compat/convert.rs[68-77]
- crates/rpc/src/handlers/mining.rs[290-296]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

i64::MAX
}
});
let _ = row.insert("modified_fee", json!(sats));
}
let _ = object.insert(&txid, Value::from(row));
}
Expand Down Expand Up @@ -1874,6 +1881,7 @@ mod tests {
));
}

// CONTRACT: docs/contracts/external-api.md#API-25
#[test]
fn getprioritisedtransactions_projects_the_overlay() {
use bitcoin_rs_mempool::MempoolEntry;
Expand Down Expand Up @@ -1916,13 +1924,13 @@ mod tests {
.and_then(JsonValueTrait::as_bool),
Some(true)
);
let modified = pooled_row
.get("modified_fee")
.and_then(JsonValueTrait::as_f64)
.unwrap_or_else(|| panic!("pooled overlay must carry modified_fee in BTC"));
assert!(
(modified - signed_sat_to_btc(1_500)).abs() < f64::EPSILON,
"modified_fee must be actual fee plus delta in BTC, got {modified}"
// CONTRACT: docs/contracts/external-api.md#API-25
assert_eq!(
pooled_row
.get("modified_fee")
.and_then(JsonValueTrait::as_i64),
Some(1_500),
"modified_fee is actual fee plus delta in satoshis"
);
let absent_row = object
.get(&absent.to_string())
Expand Down
2 changes: 1 addition & 1 deletion crates/rpc/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ declare_rows! {
"generatetoaddress", SurfaceKind::Rpc, Status::Implemented, "", CORE_VERSION, "Assembles, solves, and submits n blocks paying the given address through the mining coordinator.", "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.", "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.", "0.4.0", Some(mining::getprioritisedtransactions);
"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);

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.

Remediation recommended

1. modified_fee rule duplicated 📘 Rule violation ⚙ Maintainability

The registry metadata independently restates the modified_fee unit rule already defined by
API-25, without referencing that authoritative contract. This creates parallel documentation that
can drift when the API contract changes.
Agent Prompt
## Issue description
The `getprioritisedtransactions` registry note duplicates the `modified_fee` unit rule defined in the authoritative `API-25` contract.

## Issue Context
Keep the registry description concise and refer to `docs/contracts/external-api.md#API-25` rather than independently restating the satoshi-versus-BTC rule. Regenerate `docs/rpc-reference.md` afterward because it is generated from the registry.

## Fix Focus Areas
- crates/rpc/src/registry.rs[146-146]
- docs/rpc-reference.md[80-80]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


// -- JSON-RPC: bitcoin-rs extension ------------------------------
"getcapabilities", SurfaceKind::Rpc, Status::Extension, "", CORE_VERSION, "bitcoin-rs reporting of compiled/enabled concrete service capabilities and index lifecycle state (crates/rpc/src/handlers/chain.rs, crates/rpc/src/capabilities.rs).", "0.4.0", Some(chain::getcapabilities);
Expand Down
2 changes: 1 addition & 1 deletion docs/contracts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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-24` | 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 | 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-25` | 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 | 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 <target> -- -runs=10000` |
Expand Down
15 changes: 14 additions & 1 deletion docs/contracts/external-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ reject reasons. `API-18` is GBT `coinbaseaux.flags`. `API-19` is
`submitblock` duplicate for reorged scripts-valid bodies. `API-22` is
`getmininginfo` omitting unset optional fields. `API-23` is
`estimatesmartfee` Core `conf_target` and `estimate_mode` gates. `API-24`
is `generateblock` Core txid/raw-tx parse errors.
is `generateblock` Core txid/raw-tx parse errors. `API-25` is
`getprioritisedtransactions` `modified_fee` in satoshis.

## Clauses

Expand Down Expand Up @@ -312,6 +313,15 @@ is `generateblock` Core txid/raw-tx parse errors.
`Transaction decode failed for {str}. Make sure the tx has at least one
input.`

### `API-25`: `getprioritisedtransactions` `modified_fee` is satoshis

- **Owner**: `getprioritisedtransactions` in
`crates/rpc/src/handlers/mining.rs`.
- Core mining RPCs use satoshi amounts, not BTC. `fee_delta` is already
an integer satoshi overlay. `modified_fee` (actual fee plus delta,
present only when `in_mempool`) is the same unit: a JSON number in
satoshis, matching Core `CAmount`.

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).
Expand Down Expand Up @@ -446,3 +456,6 @@ owned by [wallet-facing.md](wallet-facing.md).
`generateblock_rejects_unknown_mempool_txid_like_core`,
`generateblock_rejects_undecodable_raw_tx_like_core`,
`generateblock_keeps_raw_transactions`
- `API-25`:
- `crates/rpc/src/handlers/mining.rs` test
`getprioritisedtransactions_projects_the_overlay`
2 changes: 1 addition & 1 deletion docs/rpc-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ Unimplemented-set derivation: audited against the Bitcoin Core v31.0 source comm
| `generatetoaddress` | 0.4.0 | Assembles, solves, and submits n blocks paying the given address through the mining coordinator. |
| `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. |
| `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. |
| `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. |

### Deviation

Expand Down
Loading