From 5016a73207ec38087fc9a335392e93a38d3a41da Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 6 Nov 2024 12:08:44 +1100 Subject: [PATCH 1/4] Manually implement consensus traits for Block In preparation for hacking the `Block` type implement the consensus traits manually instead of using the macro. Refactor only, no logic changes. --- bitcoin/src/blockdata/block.rs | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/bitcoin/src/blockdata/block.rs b/bitcoin/src/blockdata/block.rs index f6a039042..96dbb1b7a 100644 --- a/bitcoin/src/blockdata/block.rs +++ b/bitcoin/src/blockdata/block.rs @@ -110,8 +110,6 @@ pub struct Block { pub txdata: Vec, } -impl_consensus_encoding!(Block, header, txdata); - impl Block { /// Returns the block hash. pub fn block_hash(&self) -> BlockHash { self.header.block_hash() } @@ -272,6 +270,37 @@ impl From<&Block> for BlockHash { fn from(block: &Block) -> BlockHash { block.block_hash() } } +impl Encodable for Block { + #[inline] + fn consensus_encode(&self, r: &mut R) -> Result { + let mut len = 0; + len += self.header.consensus_encode(r)?; + len += self.txdata.consensus_encode(r)?; + Ok(len) + } +} + +impl Decodable for Block { + #[inline] + fn consensus_decode_from_finite_reader( + r: &mut R, + ) -> Result { + Ok(Block { + header: Decodable::consensus_decode_from_finite_reader(r)?, + txdata: Decodable::consensus_decode_from_finite_reader(r)?, + }) + } + + #[inline] + fn consensus_decode(r: &mut R) -> Result { + let mut r = r.take(internals::ToU64::to_u64(encode::MAX_VEC_SIZE)); + Ok(Block { + header: Decodable::consensus_decode(&mut r)?, + txdata: Decodable::consensus_decode(&mut r)?, + }) + } +} + /// An error when looking up a BIP34 block height. #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] From d5b148d400d1745935dacb4f01be0bb7d2fe8e1b Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 31 Oct 2024 16:08:04 +1100 Subject: [PATCH 2/4] Make block_size free standing and rename In preparation for adding a block extension trait move the only private method off the `Block` type and make it free standing. While we are at it just take a slice of transactions since we don't need the block itself - rename appropriately. Internal change only. --- bitcoin/src/blockdata/block.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/bitcoin/src/blockdata/block.rs b/bitcoin/src/blockdata/block.rs index 96dbb1b7a..2c19fd163 100644 --- a/bitcoin/src/blockdata/block.rs +++ b/bitcoin/src/blockdata/block.rs @@ -197,23 +197,10 @@ impl Block { /// > Block weight is defined as Base size * 3 + Total size. pub fn weight(&self) -> Weight { // This is the exact definition of a weight unit, as defined by BIP-141 (quote above). - let wu = self.base_size() * 3 + self.total_size(); + let wu = block_base_size(&self.txdata) * 3 + self.total_size(); Weight::from_wu_usize(wu) } - /// Returns the base block size. - /// - /// > Base size is the block size in bytes with the original transaction serialization without - /// > any witness-related data, as seen by a non-upgraded node. - fn base_size(&self) -> usize { - let mut size = Header::SIZE; - - size += compact_size::encoded_size(self.txdata.len()); - size += self.txdata.iter().map(|tx| tx.base_size()).sum::(); - - size - } - /// Returns the total block size. /// /// > Total size is the block size in bytes with transactions serialized as described in BIP144, @@ -262,6 +249,19 @@ impl Block { } } +/// Returns the base block size. +/// +/// > Base size is the block size in bytes with the original transaction serialization without +/// > any witness-related data, as seen by a non-upgraded node. +fn block_base_size(transactions: &[Transaction]) -> usize { + let mut size = Header::SIZE; + + size += compact_size::encoded_size(transactions.len()); + size += transactions.iter().map(|tx| tx.base_size()).sum::(); + + size +} + impl From for BlockHash { fn from(block: Block) -> BlockHash { block.block_hash() } } @@ -476,7 +476,7 @@ mod tests { assert_eq!(real_decode.header.difficulty_float(¶ms), 1.0); assert_eq!(real_decode.total_size(), some_block.len()); - assert_eq!(real_decode.base_size(), some_block.len()); + assert_eq!(block_base_size(&real_decode.txdata), some_block.len()); assert_eq!( real_decode.weight(), Weight::from_non_witness_data_size(some_block.len().to_u64()) @@ -518,7 +518,7 @@ mod tests { assert_eq!(real_decode.header.difficulty_float(¶ms), 2456598.4399242126); assert_eq!(real_decode.total_size(), segwit_block.len()); - assert_eq!(real_decode.base_size(), 4283); + assert_eq!(block_base_size(&real_decode.txdata), 4283); assert_eq!(real_decode.weight(), Weight::from_wu(17168)); assert!(real_decode.check_witness_commitment()); From 55200924e48477201c05af0a2629c7e3bfbc201d Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 6 Nov 2024 13:12:27 +1100 Subject: [PATCH 3/4] Move Sealed to bottom of file In preparation for adding `Block` to the private `sealed` module move the module down to the bottom of the file. --- bitcoin/src/blockdata/block.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bitcoin/src/blockdata/block.rs b/bitcoin/src/blockdata/block.rs index 2c19fd163..641f36475 100644 --- a/bitcoin/src/blockdata/block.rs +++ b/bitcoin/src/blockdata/block.rs @@ -73,11 +73,6 @@ crate::internal_macros::define_extension_trait! { } } -mod sealed { - pub trait Sealed {} - impl Sealed for super::Header {} -} - impl Encodable for Version { fn consensus_encode(&self, w: &mut W) -> Result { self.to_consensus().consensus_encode(w) @@ -301,6 +296,11 @@ impl Decodable for Block { } } +mod sealed { + pub trait Sealed {} + impl Sealed for super::Header {} +} + /// An error when looking up a BIP34 block height. #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] From 7819e50055c1be1d09b7d08734955f43229e6ed5 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 12 Nov 2024 12:56:17 +1100 Subject: [PATCH 4/4] Move Block to primitives On the way re-design the API by doing: - Introduce `Checked` and `Unchecked` tags - Rename the `txdata` field to `transactions` - Make the `Block` fields private - Add getters for `header` and `transactions` fields - Move the various `compute_` methods to be free standing functions - Make the `check_` functions private - Introduce extension traits --- bitcoin/src/bip152.rs | 48 +- bitcoin/src/bip158.rs | 18 +- bitcoin/src/blockdata/block.rs | 471 +++++++++++------- bitcoin/src/blockdata/constants.rs | 96 ++-- bitcoin/src/blockdata/transaction.rs | 2 +- bitcoin/src/lib.rs | 6 +- bitcoin/src/merkle_tree/block.rs | 29 +- bitcoin/src/merkle_tree/mod.rs | 21 - bitcoin/src/pow.rs | 2 +- .../fuzz_targets/bitcoin/deserialize_block.rs | 19 +- primitives/src/block.rs | 143 ++++++ primitives/src/lib.rs | 17 +- 12 files changed, 577 insertions(+), 295 deletions(-) diff --git a/bitcoin/src/bip152.rs b/bitcoin/src/bip152.rs index 98ecce7de..7fcfe38fc 100644 --- a/bitcoin/src/bip152.rs +++ b/bitcoin/src/bip152.rs @@ -18,7 +18,7 @@ use crate::internal_macros::{ }; use crate::prelude::Vec; use crate::transaction::TxIdentifier; -use crate::{block, consensus, Block, BlockHash, Transaction}; +use crate::{block, consensus, Block, BlockChecked, BlockHash, Transaction}; /// A BIP-152 error #[derive(Debug, Clone, PartialEq, Eq)] @@ -203,7 +203,7 @@ impl HeaderAndShortIds { /// /// > Nodes SHOULD NOT use the same nonce across multiple different blocks. pub fn from_block( - block: &Block, + block: &Block, nonce: u64, version: u32, mut prefill: &[usize], @@ -212,12 +212,12 @@ impl HeaderAndShortIds { return Err(Error::UnknownVersion); } - let siphash_keys = ShortId::calculate_siphash_keys(&block.header, nonce); + let siphash_keys = ShortId::calculate_siphash_keys(block.header(), nonce); let mut prefilled = Vec::with_capacity(prefill.len() + 1); // +1 for coinbase tx - let mut short_ids = Vec::with_capacity(block.txdata.len() - prefill.len()); + let mut short_ids = Vec::with_capacity(block.transactions().len() - prefill.len()); let mut last_prefill = 0; - for (idx, tx) in block.txdata.iter().enumerate() { + for (idx, tx) in block.transactions().iter().enumerate() { // Check if we should prefill this tx. let prefill_tx = if prefill.first() == Some(&idx) { prefill = &prefill[1..]; @@ -263,7 +263,7 @@ impl HeaderAndShortIds { } Ok(HeaderAndShortIds { - header: block.header, + header: *block.header(), nonce, // Provide coinbase prefilled. prefilled_txs: prefilled, @@ -382,17 +382,17 @@ impl BlockTransactions { /// the corresponding full [`Block`] by providing all requested transactions. pub fn from_request( request: &BlockTransactionsRequest, - block: &Block, + block: &Block, ) -> Result { Ok(BlockTransactions { block_hash: request.block_hash, transactions: { let mut txs = Vec::with_capacity(request.indexes.len()); for idx in &request.indexes { - if *idx >= block.txdata.len().to_u64() { + if *idx >= block.transactions().len().to_u64() { return Err(TxIndexOutOfRangeError(*idx)); } - txs.push(block.txdata[*idx as usize].clone()); + txs.push(block.transactions()[*idx as usize].clone()); } txs }, @@ -410,8 +410,8 @@ mod test { use crate::merkle_tree::TxMerkleNode; use crate::transaction::OutPointExt; use crate::{ - transaction, Amount, CompactTarget, OutPoint, ScriptBuf, Sequence, TxIn, TxOut, Txid, - Witness, + transaction, Amount, BlockChecked, CompactTarget, OutPoint, ScriptBuf, Sequence, TxIn, + TxOut, Txid, Witness, }; fn dummy_tx(nonce: &[u8]) -> Transaction { @@ -429,18 +429,17 @@ mod test { } } - fn dummy_block() -> Block { - Block { - header: block::Header { - version: block::Version::ONE, - prev_blockhash: BlockHash::from_byte_array([0x99; 32]), - merkle_root: TxMerkleNode::from_byte_array([0x77; 32]), - time: 2, - bits: CompactTarget::from_consensus(3), - nonce: 4, - }, - txdata: vec![dummy_tx(&[2]), dummy_tx(&[3]), dummy_tx(&[4])], - } + fn dummy_block() -> Block { + let header = block::Header { + version: block::Version::ONE, + prev_blockhash: BlockHash::from_byte_array([0x99; 32]), + merkle_root: TxMerkleNode::from_byte_array([0x77; 32]), + time: 2, + bits: CompactTarget::from_consensus(3), + nonce: 4, + }; + let transactions = vec![dummy_tx(&[2]), dummy_tx(&[3]), dummy_tx(&[4])]; + Block::new_unchecked(header, transactions).assume_checked(None) } #[test] @@ -452,7 +451,7 @@ mod test { assert_eq!(compact.short_ids.len(), 2); assert_eq!(compact.prefilled_txs.len(), 1); assert_eq!(compact.prefilled_txs[0].idx, 0); - assert_eq!(&compact.prefilled_txs[0].tx, &block.txdata[0]); + assert_eq!(&compact.prefilled_txs[0].tx, &block.transactions()[0]); let compact = HeaderAndShortIds::from_block(&block, 42, 2, &[0, 1, 2]).unwrap(); let idxs = compact.prefilled_txs.iter().map(|t| t.idx).collect::>(); @@ -470,6 +469,7 @@ mod test { let raw_compact = Vec::::from_hex("000000206c750a364035aefd5f81508a08769975116d9195312ee4520dceac39e1fdc62c4dc67473b8e354358c1e610afeaff7410858bd45df43e2940f8a62bd3d5e3ac943c2975cffff7f2000000000a4df3c3744da89fa010a6979e971450100020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04016b0101ffffffff020006062a0100000001510000000000000000266a24aa21a9ed4a3d9f3343dafcc0d6f6d4310f2ee5ce273ed34edca6c75db3a73e7f368734200120000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(); let block: Block = deserialize(&raw_block).unwrap(); + let block = block.assume_checked(None); let nonce = 18053200567810711460; let compact = HeaderAndShortIds::from_block(&block, nonce, 2, &[]).unwrap(); let compact_expected = deserialize(&raw_compact).unwrap(); diff --git a/bitcoin/src/bip158.rs b/bitcoin/src/bip158.rs index 8c5499ad5..5261ddfe8 100644 --- a/bitcoin/src/bip158.rs +++ b/bitcoin/src/bip158.rs @@ -44,7 +44,7 @@ use hashes::{sha256d, siphash24, HashEngine as _}; use internals::{write_err, ToU64 as _}; use io::{BufRead, Write}; -use crate::block::{Block, BlockHash}; +use crate::block::{Block, BlockHash, Checked}; use crate::consensus::{ReadExt, WriteExt}; use crate::internal_macros::impl_hashencode; use crate::prelude::{BTreeSet, Borrow, Vec}; @@ -126,7 +126,10 @@ impl BlockFilter { pub fn new(content: &[u8]) -> BlockFilter { BlockFilter { content: content.to_vec() } } /// Computes a SCRIPT_FILTER that contains spent and output scripts. - pub fn new_script_filter(block: &Block, script_for_coin: M) -> Result + pub fn new_script_filter( + block: &Block, + script_for_coin: M, + ) -> Result where M: Fn(&OutPoint) -> Result, S: Borrow