Skip to content

Commit 61d1294

Browse files
committed
Merge rust-bitcoin#485: Add getblock verbosity 2/3 support for v29 onward
9c325ef feat: implement getblock verbosity levels 2-3 return types (renato) 515955e fix(types): remove comment for weight conversion in GetBlockVerbose (renato) 3532f06 feat(types): add coinbase tx support in RawTransactionInput type (renato) Pull request description: This PR: - Adds full support for `getblock` RPC with verbosity levels 2 and 3 for v29 and later. - Add coinbase transaction support in `RawTransactionInput` type, to be fully compliant with the RPC specification. - Introduces versioned RPC types and model conversions, including transaction fees and prevout data, extends raw transaction input handling to correctly support coinbase inputs, and updates the client API with verbosity levels. - Adds integration tests to validate new models conversions. - Removes FIXME comment regarding weight units on getblock verbosity 0 and 1, the value really is weight units (WU). This partially fixes rust-bitcoin#474. Notably missing are the changes in implementation from v17 to v28 inclusive. ACKs for top commit: tcharding: ACK 9c325ef Tree-SHA512: 6f9a63fce50fae03218e338c59e4a723b001adb1fad2e38f242b7757098885ca358666fe458f49a1bf74e648b572f22bfc72a4a6f3ea5be3698b0c372a4e38ab
2 parents b184243 + 9c325ef commit 61d1294

14 files changed

Lines changed: 817 additions & 31 deletions

File tree

client/src/client_sync/v29/blockchain.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,37 @@ macro_rules! impl_client_v29__get_descriptor_activity {
3636
}
3737
};
3838
}
39+
40+
/// Implements Bitcoin Core JSON-RPC API method `getblock`.
41+
#[macro_export]
42+
macro_rules! impl_client_v29__get_block {
43+
() => {
44+
impl Client {
45+
/// Gets a block by blockhash. Kept for compatibility; uses verbose set to 0.
46+
pub fn get_block(&self, hash: BlockHash) -> Result<Block> {
47+
let json = self.get_block_verbose_zero(hash)?;
48+
Ok(json.block()?)
49+
}
50+
51+
/// Gets a block by blockhash with verbose set to 0.
52+
pub fn get_block_verbose_zero(&self, hash: BlockHash) -> Result<GetBlockVerboseZero> {
53+
self.call("getblock", &[into_json(hash)?, 0.into()])
54+
}
55+
56+
/// Gets a block by blockhash with verbose set to 1.
57+
pub fn get_block_verbose_one(&self, hash: BlockHash) -> Result<GetBlockVerboseOne> {
58+
self.call("getblock", &[into_json(hash)?, 1.into()])
59+
}
60+
61+
/// Gets a block by blockhash with verbose set to 2.
62+
pub fn get_block_verbose_two(&self, hash: BlockHash) -> Result<GetBlockVerboseTwo> {
63+
self.call("getblock", &[into_json(hash)?, 2.into()])
64+
}
65+
66+
/// Gets a block by blockhash with verbose set to 3.
67+
pub fn get_block_verbose_three(&self, hash: BlockHash) -> Result<GetBlockVerboseThree> {
68+
self.call("getblock", &[into_json(hash)?, 3.into()])
69+
}
70+
}
71+
};
72+
}

client/src/client_sync/v29/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ crate::impl_client_check_expected_server_version!({ [290000] });
3232
// == Blockchain ==
3333
crate::impl_client_v29__dump_tx_out_set!();
3434
crate::impl_client_v17__get_best_block_hash!();
35-
crate::impl_client_v17__get_block!();
35+
crate::impl_client_v29__get_block!();
3636
crate::impl_client_v17__get_blockchain_info!();
3737
crate::impl_client_v17__get_block_count!();
3838
crate::impl_client_v19__get_block_filter!();

client/src/client_sync/v30/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ crate::impl_client_check_expected_server_version!({ [300000, 300100, 300200] });
2828
// == Blockchain ==
2929
crate::impl_client_v29__dump_tx_out_set!();
3030
crate::impl_client_v17__get_best_block_hash!();
31-
crate::impl_client_v17__get_block!();
31+
crate::impl_client_v29__get_block!();
3232
crate::impl_client_v17__get_blockchain_info!();
3333
crate::impl_client_v17__get_block_count!();
3434
crate::impl_client_v19__get_block_filter!();

integration_test/tests/blockchain.rs

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,54 @@ fn blockchain__get_block__modelled() {
5858
let model: Result<mtype::GetBlockVerboseOne, GetBlockVerboseOneError> = json.into_model();
5959
model.unwrap();
6060

61-
// TODO: Test getblock 2
62-
// let json = node.client.get_block_with_verbosity(block_hash, 2).expect("getblock verbosity 2");
63-
// assert!(json.into_model().is_ok());
61+
#[cfg(not(feature = "v28_and_below"))]
62+
{
63+
let node = Node::with_wallet(Wallet::Default, &[]);
64+
node.fund_wallet();
65+
let (_address, mined_tx) = node.create_mined_transaction();
66+
let block_hash = node.client.best_block_hash().expect("best_block_hash failed");
67+
68+
let json: GetBlockVerboseTwo =
69+
node.client.get_block_verbose_two(block_hash).expect("getblock verbose=2");
70+
let model: Result<mtype::GetBlockVerboseTwo, GetBlockVerboseTwoError> = json.into_model();
71+
let block_v2 = model.unwrap();
72+
73+
assert_eq!(block_v2.tx.len(), block_v2.n_tx as usize);
74+
75+
let mined_txid = mined_tx.compute_txid();
76+
let mined_entry = block_v2
77+
.tx
78+
.iter()
79+
.find(|entry| entry.transaction.transaction.compute_txid() == mined_txid)
80+
.expect("mined transaction should be present in verbosity=2 results");
81+
assert!(mined_entry.fee.is_some());
82+
assert!(!mined_entry.transaction.transaction.input.is_empty());
83+
assert!(!mined_entry.transaction.transaction.output.is_empty());
84+
85+
let json: GetBlockVerboseThree =
86+
node.client.get_block_verbose_three(block_hash).expect("getblock verbose=3");
87+
let model: Result<mtype::GetBlockVerboseThree, GetBlockVerboseThreeError> =
88+
json.into_model();
89+
let block_v3 = model.unwrap();
90+
91+
assert_eq!(block_v3.tx.len(), block_v3.n_tx as usize);
92+
93+
let mined_entry = block_v3
94+
.tx
95+
.iter()
96+
.find(|entry| entry.transaction.transaction.compute_txid() == mined_txid)
97+
.expect("mined transaction should be present in verbosity=3 results");
98+
assert!(
99+
mined_entry.prevouts.iter().any(|prevout| prevout.is_some()),
100+
"expected at least one prevout for the mined transaction"
101+
);
102+
103+
for prevout in mined_entry.prevouts.iter().flatten() {
104+
assert!(prevout.value.to_sat() > 0);
105+
assert!(prevout.height <= block_v3.height);
106+
assert!(!prevout.script_pubkey.script_pubkey.is_empty());
107+
}
108+
}
64109
}
65110

66111
#[test]

types/src/model/blockchain.rs

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use bitcoin::{
1515
};
1616
use serde::{Deserialize, Serialize};
1717

18-
use super::ScriptPubkey;
18+
use super::{GetRawTransactionVerbose, ScriptPubkey};
1919

2020
/// Models the result of JSON-RPC method `dumptxoutset`.
2121
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
@@ -69,7 +69,7 @@ pub struct GetBlockVerboseOne {
6969
pub median_time: Option<u32>,
7070
/// The nonce.
7171
pub nonce: u32,
72-
/// The bits.
72+
/// nBits: compact representation of the block difficulty target.
7373
pub bits: CompactTarget,
7474
/// The difficulty target.
7575
pub target: Option<Target>, // Only from v29 onwards
@@ -85,6 +85,126 @@ pub struct GetBlockVerboseOne {
8585
pub next_block_hash: Option<BlockHash>,
8686
}
8787

88+
/// Models the result of JSON-RPC method `getblock` with verbosity set to 2.
89+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
90+
pub struct GetBlockVerboseTwo {
91+
/// The block hash (same as provided).
92+
pub hash: BlockHash,
93+
/// The number of confirmations, or -1 if the block is not on the main chain.
94+
pub confirmations: i64,
95+
/// The block size.
96+
pub size: u32,
97+
/// The block size excluding witness data.
98+
pub stripped_size: Option<u32>,
99+
/// The block weight as defined in BIP-141.
100+
pub weight: Weight,
101+
/// The block height or index.
102+
pub height: u32,
103+
/// The block version.
104+
pub version: block::Version,
105+
/// The merkle root.
106+
pub merkle_root: String,
107+
/// The transactions.
108+
pub tx: Vec<GetBlockVerboseTwoTransaction>,
109+
/// The block time expressed in UNIX epoch time.
110+
pub time: u32,
111+
/// The median block time expressed in UNIX epoch time.
112+
pub median_time: Option<u32>,
113+
/// The nonce.
114+
pub nonce: u32,
115+
/// nBits: compact representation of the block difficulty target.
116+
pub bits: CompactTarget,
117+
/// The difficulty target.
118+
pub target: Option<Target>,
119+
/// The difficulty.
120+
pub difficulty: f64,
121+
/// Expected number of hashes required to produce the chain up to this block (in hex).
122+
pub chain_work: Work,
123+
/// The number of transactions in the block.
124+
pub n_tx: u32,
125+
/// The hash of the previous block (if available).
126+
pub previous_block_hash: Option<BlockHash>,
127+
/// The hash of the next block (if available).
128+
pub next_block_hash: Option<BlockHash>,
129+
}
130+
131+
/// A transaction entry for `getblock` verbosity 2.
132+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
133+
pub struct GetBlockVerboseTwoTransaction {
134+
/// The transaction data (same as `getrawtransaction` verbose output).
135+
pub transaction: GetRawTransactionVerbose,
136+
/// The transaction fee in BTC (omitted if block undo data is not available).
137+
pub fee: Option<Amount>,
138+
}
139+
140+
/// Models the result of JSON-RPC method `getblock` with verbosity set to 3.
141+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
142+
pub struct GetBlockVerboseThree {
143+
/// The block hash (same as provided) in RPC call.
144+
pub hash: BlockHash,
145+
/// The number of confirmations, or -1 if the block is not on the main chain.
146+
pub confirmations: i64,
147+
/// The block size.
148+
pub size: u32,
149+
/// The block size excluding witness data.
150+
pub stripped_size: Option<u32>,
151+
/// The block weight as defined in BIP-141.
152+
pub weight: Weight,
153+
/// The block height or index.
154+
pub height: u32,
155+
/// The block version.
156+
pub version: block::Version,
157+
/// The merkle root.
158+
pub merkle_root: String,
159+
/// The transactions.
160+
pub tx: Vec<GetBlockVerboseThreeTransaction>,
161+
/// The block time expressed in UNIX epoch time.
162+
pub time: u32,
163+
/// The median block time expressed in UNIX epoch time.
164+
pub median_time: Option<u32>,
165+
/// The nonce.
166+
pub nonce: u32,
167+
/// nBits: compact representation of the block difficulty target.
168+
pub bits: CompactTarget,
169+
/// The difficulty target.
170+
pub target: Option<Target>,
171+
/// The difficulty.
172+
pub difficulty: f64,
173+
/// Expected number of hashes required to produce the chain up to this block (in hex).
174+
pub chain_work: Work,
175+
/// The number of transactions in the block.
176+
pub n_tx: u32,
177+
/// The hash of the previous block (if available).
178+
pub previous_block_hash: Option<BlockHash>,
179+
/// The hash of the next block (if available).
180+
pub next_block_hash: Option<BlockHash>,
181+
}
182+
183+
/// A transaction entry for `getblock` verbosity 3.
184+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
185+
pub struct GetBlockVerboseThreeTransaction {
186+
/// The transaction data (same as `getrawtransaction` verbose output).
187+
pub transaction: GetRawTransactionVerbose,
188+
/// The prevout data aligned with the transaction input order.
189+
pub prevouts: Vec<Option<GetBlockVerboseThreePrevout>>,
190+
/// The transaction fee in BTC, omitted if block undo data is not available.
191+
pub fee: Option<Amount>,
192+
}
193+
194+
/// The prevout information for a transaction input (verbosity 3 only).
195+
/// TODO: This type adding prevouts is exactly the same as the getrawtransaction's type with verbosity set to 2, which is not implemented yet. Consider reusing that structure when implemented.
196+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
197+
pub struct GetBlockVerboseThreePrevout {
198+
/// Coinbase or not.
199+
pub generated: bool,
200+
/// The height of the prevout.
201+
pub height: u32,
202+
/// The value in BTC.
203+
pub value: Amount,
204+
/// The script pubkey.
205+
pub script_pubkey: ScriptPubkey,
206+
}
207+
88208
/// Models the result of JSON-RPC method `getblockchaininfo`.
89209
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
90210
pub struct GetBlockchainInfo {

types/src/model/mod.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@ pub use self::{
2929
ActivityEntry, Bip9Info, Bip9SoftforkInfo, Bip9SoftforkStatistics, Bip9SoftforkStatus,
3030
Bip9Statistics, ChainState, ChainTips, ChainTipsStatus, DeploymentInfo, DumpTxOutSet,
3131
GetBestBlockHash, GetBlockCount, GetBlockFilter, GetBlockHash, GetBlockHeader,
32-
GetBlockHeaderVerbose, GetBlockStats, GetBlockVerboseOne, GetBlockVerboseZero,
33-
GetBlockchainInfo, GetChainStates, GetChainTips, GetChainTxStats, GetDeploymentInfo,
34-
GetDescriptorActivity, GetDifficulty, GetMempoolAncestors, GetMempoolAncestorsVerbose,
35-
GetMempoolDescendants, GetMempoolDescendantsVerbose, GetMempoolEntry, GetMempoolInfo,
36-
GetRawMempool, GetRawMempoolSequence, GetRawMempoolVerbose, GetTxOut, GetTxOutSetInfo,
32+
GetBlockHeaderVerbose, GetBlockStats, GetBlockVerboseOne, GetBlockVerboseThree,
33+
GetBlockVerboseThreePrevout, GetBlockVerboseThreeTransaction, GetBlockVerboseTwo,
34+
GetBlockVerboseTwoTransaction, GetBlockVerboseZero, GetBlockchainInfo, GetChainStates,
35+
GetChainTips, GetChainTxStats, GetDeploymentInfo, GetDescriptorActivity, GetDifficulty,
36+
GetMempoolAncestors, GetMempoolAncestorsVerbose, GetMempoolDescendants,
37+
GetMempoolDescendantsVerbose, GetMempoolEntry, GetMempoolInfo, GetRawMempool,
38+
GetRawMempoolSequence, GetRawMempoolVerbose, GetTxOut, GetTxOutSetInfo,
3739
GetTxOutSetInfoBlockInfo, GetTxOutSetInfoUnspendables, GetTxSpendingPrevout,
3840
GetTxSpendingPrevoutItem, LoadTxOutSet, MempoolEntry, MempoolEntryFees, ReceiveActivity,
3941
ScanBlocksStart, ScanTxOutSetStart, ScanTxOutSetUnspent, Softfork, SoftforkType,

types/src/psbt/error.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ impl std::error::Error for RawTransactionError {
4242
pub enum RawTransactionInputError {
4343
/// Conversion of the input `txid` field failed.
4444
Txid(hex::HexToArrayError),
45+
/// Input lacked both `txid` and `coinbase` data.
46+
MissingTxid,
47+
/// Input lacked the `vout` field for a non-coinbase input.
48+
MissingVout,
49+
/// Input lacked both `scriptSig` and `coinbase` data.
50+
MissingScriptSig,
4551
/// Conversion of the input `script_sig` field failed.
4652
ScriptSig(hex::HexToBytesError),
4753
/// Conversion of one of the `witness` hex strings failed.
@@ -52,6 +58,12 @@ impl fmt::Display for RawTransactionInputError {
5258
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
5359
match *self {
5460
Self::Txid(ref e) => write_err!(f, "conversion of the input `txid` field failed"; e),
61+
Self::MissingTxid =>
62+
write!(f, "missing both `txid` and `coinbase` fields for the transaction input"),
63+
Self::MissingVout =>
64+
write!(f, "missing `vout` field for non-coinbase transaction input"),
65+
Self::MissingScriptSig =>
66+
write!(f, "missing both `scriptSig` and `coinbase` data for the transaction input"),
5567
Self::ScriptSig(ref e) =>
5668
write_err!(f, "conversion of the input `script_sig` field failed"; e),
5769
Self::Witness(ref e) =>
@@ -65,6 +77,9 @@ impl std::error::Error for RawTransactionInputError {
6577
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
6678
match *self {
6779
Self::Txid(ref e) => Some(e),
80+
Self::MissingTxid => None,
81+
Self::MissingVout => None,
82+
Self::MissingScriptSig => None,
6883
Self::ScriptSig(ref e) => Some(e),
6984
Self::Witness(ref e) => Some(e),
7085
}

types/src/psbt/mod.rs

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,19 @@ impl RawTransaction {
7676
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
7777
#[cfg_attr(feature = "serde-deny-unknown-fields", serde(deny_unknown_fields))]
7878
pub struct RawTransactionInput {
79-
/// The transaction id.
80-
pub txid: String,
81-
/// The output number.
82-
pub vout: u32,
83-
/// The script.
79+
/// Coinbase data (present on coinbase transactions only).
80+
#[serde(default)]
81+
pub coinbase: Option<String>,
82+
/// The transaction id (absent for coinbase inputs).
83+
#[serde(default)]
84+
pub txid: Option<String>,
85+
/// The output number (absent for coinbase inputs).
86+
#[serde(default)]
87+
pub vout: Option<u32>,
88+
/// The script (absent on coinbase inputs, the raw coinbase hex is available in `coinbase`).
8489
#[serde(rename = "scriptSig")]
85-
pub script_sig: ScriptSig,
90+
#[serde(default)]
91+
pub script_sig: Option<ScriptSig>,
8692
/// Hex-encoded witness data (if any).
8793
#[serde(rename = "txinwitness")]
8894
pub txin_witness: Option<Vec<String>>,
@@ -95,16 +101,33 @@ impl RawTransactionInput {
95101
pub fn to_input(&self) -> Result<TxIn, RawTransactionInputError> {
96102
use RawTransactionInputError as E;
97103

98-
let txid = self.txid.parse::<Txid>().map_err(E::Txid)?;
99-
let script_sig = self.script_sig.script_buf().map_err(E::ScriptSig)?;
104+
// if txid is present, parse it
105+
// if coinbase transaction, leave it empty
106+
let previous_output = match (&self.txid, &self.coinbase) {
107+
(Some(txid), _) => {
108+
let parsed_txid = txid.parse::<Txid>().map_err(E::Txid)?;
109+
let vout = self.vout.ok_or(RawTransactionInputError::MissingVout)?;
110+
OutPoint { txid: parsed_txid, vout }
111+
}
112+
(None, Some(_)) => OutPoint::null(),
113+
(None, None) => return Err(E::MissingTxid),
114+
};
115+
116+
// if scriptSig is present, parse it
117+
// if coinbase transaction, parse coinbase as scriptSig
118+
let script_sig = match (&self.script_sig, &self.coinbase) {
119+
(Some(script_sig), _) => script_sig.script_buf().map_err(E::ScriptSig)?,
120+
(None, Some(coinbase)) => ScriptBuf::from_hex(coinbase).map_err(E::ScriptSig)?,
121+
(None, None) => return Err(E::MissingScriptSig),
122+
};
100123

101124
let witness = match &self.txin_witness {
102125
Some(v) => crate::witness_from_hex_slice(v).map_err(E::Witness)?,
103126
None => Witness::new(),
104127
};
105128

106129
Ok(TxIn {
107-
previous_output: OutPoint { txid, vout: self.vout },
130+
previous_output,
108131
script_sig,
109132
sequence: Sequence::from_consensus(self.sequence),
110133
witness,

types/src/v17/blockchain/into.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ impl GetBlockVerboseOne {
3636
let hash = self.hash.parse::<BlockHash>().map_err(E::Hash)?;
3737
let stripped_size =
3838
self.stripped_size.map(|size| crate::to_u32(size, "stripped_size")).transpose()?;
39-
let weight = Weight::from_wu(self.weight); // FIXME: Confirm this uses weight units.
39+
let weight = Weight::from_wu(self.weight);
4040
let version = block::Version::from_consensus(self.version);
4141
let tx = self
4242
.tx

0 commit comments

Comments
 (0)