Skip to content
This repository was archived by the owner on Nov 26, 2024. It is now read-only.

Commit bd6863c

Browse files
committed
Add submitpackage RPC support
1 parent 383ec94 commit bd6863c

File tree

8 files changed

+253
-1
lines changed

8 files changed

+253
-1
lines changed

client/src/client_sync/v28.rs renamed to client/src/client_sync/v28/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
//!
55
//! We ignore option arguments unless they effect the shape of the returned JSON data.
66
7+
mod raw_transactions;
8+
79
use bitcoin::address::{Address, NetworkChecked};
810
use bitcoin::{Amount, Block, BlockHash, Txid};
911

@@ -30,6 +32,7 @@ crate::impl_client_check_expected_server_version!({ [280000] });
3032

3133
// == Rawtransactions ==
3234
crate::impl_client_v17__sendrawtransaction!();
35+
crate::impl_client_v28__submitpackage!();
3336

3437
// == Wallet ==
3538
crate::impl_client_v17__createwallet!();
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// SPDX-License-Identifier: CC0-1.0
2+
3+
//! Macros for implementing JSON-RPC methods on a client.
4+
//!
5+
//! Specifically this is methods found under the `== Rawtransactions ==` section of the
6+
//! API docs of `bitcoind v28.0`.
7+
//!
8+
//! All macros require `Client` to be in scope.
9+
//!
10+
//! See or use the `define_jsonrpc_minreq_client!` macro to define a `Client`.
11+
12+
/// Implements bitcoind JSON-RPC API method `submitpackage`
13+
#[macro_export]
14+
macro_rules! impl_client_v28__submitpackage {
15+
() => {
16+
impl Client {
17+
/// Submit a package of transactions to local node.
18+
///
19+
/// The package will be validated according to consensus and mempool policy rules. If any transaction passes, it will be accepted to mempool.
20+
///
21+
/// ## Arguments:
22+
/// 1. `package`: An array of raw transactions.
23+
/// The package must solely consist of a child and its parents. None of the parents may depend on each other.
24+
/// The package must be topologically sorted, with the child being the last element in the array.
25+
/// 2. `maxfeerate`: Reject transactions whose fee rate is higher than the specified value.
26+
/// Fee rates larger than 1BTC/kvB are rejected.
27+
/// Set to 0 to accept any fee rate.
28+
/// If unset, will default to 0.10 BTC/kvb.
29+
/// 3. `maxburnamount` If set, reject transactions with provably unspendable outputs (e.g. 'datacarrier' outputs that use the OP_RETURN opcode) greater than the specified value.
30+
/// If burning funds through unspendable outputs is desired, increase this value.
31+
/// This check is based on heuristics and does not guarantee spendability of outputs.
32+
pub fn submit_package(
33+
&self,
34+
package: &[bitcoin::Transaction],
35+
max_fee_rate: Option<bitcoin::FeeRate>,
36+
max_burn_amount: Option<bitcoin::Amount>,
37+
) -> Result<SubmitPackage> {
38+
let package_txs = package
39+
.into_iter()
40+
.map(|tx| bitcoin::consensus::encode::serialize_hex(tx))
41+
.collect::<Vec<_>>();
42+
let max_fee_rate_btc_kvb =
43+
max_fee_rate.map(|r| r.to_sat_per_vb_floor() as f64 / 100_000.0);
44+
let max_burn_amount_btc = max_burn_amount.map(|a| a.to_btc());
45+
self.call(
46+
"submitpackage",
47+
&[package_txs.into(), max_fee_rate_btc_kvb.into(), max_burn_amount_btc.into()],
48+
)
49+
}
50+
}
51+
};
52+
}

integration_test/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
pub mod v17;
44
pub mod v19;
55
pub mod v22;
6+
pub mod v28;
67

78
/// Requires `RPC_PORT` to be in scope.
89
use bitcoind::BitcoinD;

integration_test/src/v28/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// SPDX-License-Identifier: CC0-1.0
2+
3+
//! Macros for implementing test methods on a JSON-RPC client for `bitcoind v28.0`.
4+
5+
pub mod raw_transactions;
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// SPDX-License-Identifier: CC0-1.0
2+
3+
//! Macros for implementing test methods on a JSON-RPC client.
4+
//!
5+
//! Specifically this is methods found under the `== Rawtransactions ==` section of the
6+
//! API docs of `bitcoind v28.0`.
7+
8+
/// Requires `Client` to be in scope
9+
#[macro_export]
10+
macro_rules! impl_test_v28__submitpackage {
11+
() => {
12+
#[test]
13+
fn submitpackage() {
14+
//let bitcoind = $crate::bitcoind_no_wallet();
15+
16+
let bitcoind = $crate::bitcoind_with_default_wallet();
17+
18+
// Submitting the empty package should simply fail.
19+
assert!(bitcoind.client.submit_package(&[], None, None).is_err());
20+
21+
// Premine to get some funds
22+
let address = bitcoind.client.new_address().expect("failed to get new address");
23+
let json =
24+
bitcoind.client.generate_to_address(101, &address).expect("generatetoaddress");
25+
json.into_model().unwrap();
26+
27+
// Send to ourselves, mine, send again to generate two transactions.
28+
let (tx_0, tx_1) = {
29+
let new_address = bitcoind.client.new_address().expect("failed to get new address");
30+
let txid = bitcoind
31+
.client
32+
.send_to_address(&new_address, bitcoin::Amount::from_sat(1000000))
33+
.unwrap()
34+
.into_model()
35+
.unwrap()
36+
.txid;
37+
38+
let _ =
39+
bitcoind.client.generate_to_address(1, &address).expect("generatetoaddress");
40+
41+
let best_block_hash = bitcoind.client.best_block_hash().unwrap();
42+
let best_block = bitcoind.client.get_block(best_block_hash).unwrap();
43+
let tx_0 = best_block.txdata[1].clone();
44+
45+
let new_address = bitcoind.client.new_address().expect("failed to get new address");
46+
let txid = bitcoind
47+
.client
48+
.send_to_address(&new_address, bitcoin::Amount::from_sat(1000000))
49+
.unwrap()
50+
.into_model()
51+
.unwrap()
52+
.txid;
53+
54+
let _ =
55+
bitcoind.client.generate_to_address(1, &address).expect("generatetoaddress");
56+
57+
let best_block_hash = bitcoind.client.best_block_hash().unwrap();
58+
let best_block = bitcoind.client.get_block(best_block_hash).unwrap();
59+
let tx_1 = best_block.txdata[1].clone();
60+
(tx_0, tx_1)
61+
};
62+
63+
// The call for submitting this package should succeed, but yield an 'already known'
64+
// error for all transactions.
65+
let res = bitcoind
66+
.client
67+
.submit_package(&[tx_0, tx_1], None, None)
68+
.expect("failed to submit package");
69+
for (_, tx_result) in &res.tx_results {
70+
assert!(tx_result.error.is_some());
71+
}
72+
assert!(res.replaced_transactions.is_empty());
73+
}
74+
};
75+
}

integration_test/tests/v28_api.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ mod raw_transactions {
4040
use super::*;
4141

4242
impl_test_v17__sendrawtransaction!();
43+
impl_test_v28__submitpackage!();
4344
}
4445

4546
// == Wallet ==

json/src/v28/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
//! - [ ] `joinpsbts ["psbt",...]`
9191
//! - [ ] `sendrawtransaction "hexstring" ( maxfeerate maxburnamount )`
9292
//! - [ ] `signrawtransactionwithkey "hexstring" ["privatekey",...] ( [{"txid":"hex","vout":n,"scriptPubKey":"hex","redeemScript":"hex","witnessScript":"hex","amount":amount},...] "sighashtype" )`
93-
//! - [ ] `submitpackage ["rawtx",...] ( maxfeerate maxburnamount )`
93+
//! - [x] `submitpackage ["rawtx",...] ( maxfeerate maxburnamount )`
9494
//! - [ ] `testmempoolaccept ["rawtx",...] ( maxfeerate )`
9595
//! - [ ] `utxoupdatepsbt "psbt" ( ["",{"desc":"str","range":n or [n,n]},...] )`
9696
//!
@@ -182,12 +182,15 @@
182182
183183
mod blockchain;
184184
mod network;
185+
mod raw_transactions;
185186

186187
#[doc(inline)]
187188
pub use self::blockchain::GetBlockchainInfo;
188189
#[doc(inline)]
189190
pub use self::network::GetNetworkInfo;
190191
#[doc(inline)]
192+
pub use self::raw_transactions::{SubmitPackage, SubmitPackageTxResult, SubmitPackageTxResultFees};
193+
#[doc(inline)]
191194
pub use crate::{
192195
v17::{
193196
GenerateToAddress, GetBalance, GetBestBlockHash, GetBlockVerbosityOne,

json/src/v28/raw_transactions.rs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// SPDX-License-Identifier: CC0-1.0
2+
3+
//! The JSON-RPC API for Bitcoin Core v28.0 - raw transactions.
4+
//!
5+
//! Types for methods found under the `== Rawtransactions ==` section of the API docs.
6+
7+
use std::collections::HashMap;
8+
9+
use bitcoin::{Amount, FeeRate, Txid, Wtxid};
10+
use serde::{Deserialize, Serialize};
11+
12+
/// Models the result of JSON-RPC method `submitpackage`.
13+
//submitpackage ["rawtx",...] ( maxfeerate maxburnamount )
14+
//
15+
//Submit a package of raw transactions (serialized, hex-encoded) to local node.
16+
//The package will be validated according to consensus and mempool policy rules. If any transaction passes, it will be accepted to mempool.
17+
//This RPC is experimental and the interface may be unstable. Refer to doc/policy/packages.md for documentation on package policies.
18+
//Warning: successful submission does not mean the transactions will propagate throughout the network.
19+
//
20+
//Arguments:
21+
//1. package (json array, required) An array of raw transactions.
22+
// The package must solely consist of a child and its parents. None of the parents may depend on each other.
23+
// The package must be topologically sorted, with the child being the last element in the array.
24+
// [
25+
// "rawtx", (string)
26+
// ...
27+
// ]
28+
//2. maxfeerate (numeric or string, optional, default="0.10") Reject transactions whose fee rate is higher than the specified value, expressed in BTC/kvB.
29+
// Fee rates larger than 1BTC/kvB are rejected.
30+
// Set to 0 to accept any fee rate.
31+
//3. maxburnamount (numeric or string, optional, default="0.00") Reject transactions with provably unspendable outputs (e.g. 'datacarrier' outputs that use the OP_RETURN opcode) greater than the specified value, expressed in BTC.
32+
// If burning funds through unspendable outputs is desired, increase this value.
33+
// This check is based on heuristics and does not guarantee spendability of outputs.
34+
//
35+
//
36+
//Result:
37+
//{ (json object)
38+
// "package_msg" : "str", (string) The transaction package result message. "success" indicates all transactions were accepted into or are already in the mempool.
39+
// "tx-results" : { (json object) transaction results keyed by wtxid
40+
// "wtxid" : { (json object) transaction wtxid
41+
// "txid" : "hex", (string) The transaction hash in hex
42+
// "other-wtxid" : "hex", (string, optional) The wtxid of a different transaction with the same txid but different witness found in the mempool. This means the submitted transaction was ignored.
43+
// "vsize" : n, (numeric, optional) Sigops-adjusted virtual transaction size.
44+
// "fees" : { (json object, optional) Transaction fees
45+
// "base" : n, (numeric) transaction fee in BTC
46+
// "effective-feerate" : n, (numeric, optional) if the transaction was not already in the mempool, the effective feerate in BTC per KvB. For example, the package feerate and/or feerate with modified fees from prioritisetransaction.
47+
// "effective-includes" : [ (json array, optional) if effective-feerate is provided, the wtxids of the transactions whose fees and vsizes are included in effective-feerate.
48+
// "hex", (string) transaction wtxid in hex
49+
// ...
50+
// ]
51+
// },
52+
// "error" : "str" (string, optional) The transaction error string, if it was rejected by the mempool
53+
// },
54+
// ...
55+
// },
56+
// "replaced-transactions" : [ (json array, optional) List of txids of replaced transactions
57+
// "hex", (string) The transaction id
58+
// ...
59+
// ]
60+
//}
61+
//
62+
//Examples:
63+
//> curl --user myusername --data-binary '{"jsonrpc": "2.0", "id": "curltest", "method": "submitpackage", "params": [["rawtx1", "rawtx2"]]}' -H 'content-type: application/json' http://127.0.0.1:8332/
64+
//> bitcoin-cli submitpackage '["rawtx1", "rawtx2"]'
65+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
66+
pub struct SubmitPackage {
67+
/// The transaction package result message. "success" indicates all transactions were accepted into or are already in the mempool.
68+
pub package_msg: String,
69+
/// Transaction results keyed by [`Wtxid`].
70+
#[serde(rename = "tx-results")]
71+
pub tx_results: HashMap<Wtxid, SubmitPackageTxResult>,
72+
/// List of txids of replaced transactions.
73+
#[serde(rename = "replaced-transactions")]
74+
pub replaced_transactions: Vec<Txid>,
75+
}
76+
77+
/// Models the per-transaction result included in the JSON-RPC method `submitpackage`.
78+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
79+
pub struct SubmitPackageTxResult {
80+
/// The transaction id.
81+
pub txid: Txid,
82+
/// The [`Wtxid`] of a different transaction with the same [`Txid`] but different witness found in the mempool.
83+
///
84+
/// If set, this means the submitted transaction was ignored.
85+
#[serde(rename = "other-wtxid")]
86+
pub other_wtxid: Option<Wtxid>,
87+
/// Sigops-adjusted virtual transaction size.
88+
pub vsize: Option<usize>,
89+
/// Transaction fees.
90+
pub fees: Option<SubmitPackageTxResultFees>,
91+
/// The transaction error string, if it was rejected by the mempool
92+
pub error: Option<String>,
93+
}
94+
95+
/// Models the fees included in the per-transaction result of the JSON-RPC method `submitpackage`.
96+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
97+
pub struct SubmitPackageTxResultFees {
98+
/// Transaction fee.
99+
#[serde(rename = "base")]
100+
pub base_fee: Amount,
101+
/// The effective feerate.
102+
///
103+
/// Will be `None` if the transaction was already in the mempool.
104+
///
105+
/// For example, the package feerate and/or feerate with modified fees from the `prioritisetransaction` JSON-RPC method.
106+
#[serde(rename = "effective-feerate")]
107+
pub effective_feerate: Option<FeeRate>,
108+
/// If [`Self::effective_feerate`] is provided, this holds the [`Wtxid`]s of the transactions
109+
/// whose fees and vsizes are included in effective-feerate.
110+
#[serde(rename = "effective-includes")]
111+
pub effective_includes: Vec<Wtxid>,
112+
}

0 commit comments

Comments
 (0)