From a9a39c4b08f3064d9773a5e9e32c8fbd762de9b9 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Sun, 14 Nov 2021 19:03:02 -0600 Subject: [PATCH 1/5] blockdata: Derive PartialOrd, Ord and Hash for BlockHeader --- src/blockdata/block.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blockdata/block.rs b/src/blockdata/block.rs index db43b0f5..bbec5438 100644 --- a/src/blockdata/block.rs +++ b/src/blockdata/block.rs @@ -37,7 +37,7 @@ use crate::internal_macros::impl_consensus_encoding; /// ### Bitcoin Core References /// /// * [CBlockHeader definition](https://github.com/bitcoin/bitcoin/blob/345457b542b6a980ccfbc868af0970a6f91d1b82/src/primitives/block.h#L20) -#[derive(Copy, PartialEq, Eq, Clone, Debug)] +#[derive(Copy, PartialEq, Eq, Clone, Debug, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] pub struct BlockHeader { From f2fcdc86e69c878a71e0e27f23dc731913768892 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Sun, 24 Mar 2019 15:20:46 +0000 Subject: [PATCH 2/5] BIP152: Add basic Compact Block structures --- src/consensus/encode.rs | 5 + src/util/bip152.rs | 417 ++++++++++++++++++++++++++++++++++++++++ src/util/mod.rs | 1 + 3 files changed, 423 insertions(+) create mode 100644 src/util/bip152.rs diff --git a/src/consensus/encode.rs b/src/consensus/encode.rs index 97c3604b..e1f2b41b 100644 --- a/src/consensus/encode.rs +++ b/src/consensus/encode.rs @@ -27,6 +27,7 @@ use crate::io::{self, Cursor, Read}; use crate::util::endian; use crate::util::psbt; +use crate::util::bip152::{ShortId, PrefilledTransaction}; use crate::util::taproot::TapLeafHash; use crate::hashes::hex::ToHex; @@ -558,6 +559,7 @@ macro_rules! impl_array { impl_array!(2); impl_array!(4); +impl_array!(6); impl_array!(8); impl_array!(10); impl_array!(12); @@ -629,6 +631,9 @@ impl_vec!(TxIn); impl_vec!(Vec); impl_vec!(u64); impl_vec!(TapLeafHash); +impl_vec!(VarInt); +impl_vec!(ShortId); +impl_vec!(PrefilledTransaction); #[cfg(feature = "std")] impl_vec!(Inventory); #[cfg(feature = "std")] impl_vec!((u32, Address)); diff --git a/src/util/bip152.rs b/src/util/bip152.rs new file mode 100644 index 00000000..5b92c0c7 --- /dev/null +++ b/src/util/bip152.rs @@ -0,0 +1,417 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! BIP152 Compact Blocks +//! +//! Implementation of compact blocks data structure and algorithms. +//! + +use crate::prelude::*; + +use crate::io; +use core::{convert, convert::TryFrom, fmt, mem}; +#[cfg(feature = "std")] +use std::error; + +use crate::consensus::encode::{self, Decodable, Encodable, VarInt}; +use crate::hashes::{sha256, siphash24, Hash}; +use crate::internal_macros::{impl_array_newtype, impl_bytes_newtype, impl_consensus_encoding}; +use crate::util::endian; +use crate::{Block, BlockHash, BlockHeader, Transaction}; + +/// A BIP-152 error +#[derive(Clone, PartialEq, Eq, Debug, Copy, PartialOrd, Ord, Hash)] +pub enum Error { + /// An unknown version number was used. + UnknownVersion, + /// The prefill slice provided was invalid. + InvalidPrefill, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Error::UnknownVersion => write!(f, "an unknown version number was used"), + Error::InvalidPrefill => write!(f, "the prefill slice provided was invalid"), + } + } +} + +#[cfg(feature = "std")] +impl error::Error for Error {} + +/// A [PrefilledTransaction] structure is used in [HeaderAndShortIds] to +/// provide a list of a few transactions explicitly. +#[derive(PartialEq, Eq, Clone, Debug, PartialOrd, Ord, Hash)] +pub struct PrefilledTransaction { + /// The index of the transaction in the block. + /// + /// This field is differentially encoded relative to the previous + /// prefilled transaction as described as follows: + /// + /// > Several uses of CompactSize below are "differentially encoded". For + /// > these, instead of using raw indexes, the number encoded is the + /// > difference between the current index and the previous index, minus one. + /// > For example, a first index of 0 implies a real index of 0, a second + /// > index of 0 thereafter refers to a real index of 1, etc. + pub idx: u16, + /// The actual transaction. + pub tx: Transaction, +} + +impl convert::AsRef for PrefilledTransaction { + fn as_ref(&self) -> &Transaction { + &self.tx + } +} + +impl Encodable for PrefilledTransaction { + #[inline] + fn consensus_encode(&self, mut s: &mut S) -> Result { + Ok(VarInt(self.idx as u64).consensus_encode(&mut s)? + self.tx.consensus_encode(&mut s)?) + } +} + +impl Decodable for PrefilledTransaction { + #[inline] + fn consensus_decode( + mut d: &mut D, + ) -> Result { + let idx = VarInt::consensus_decode(&mut d)?.0; + let idx = u16::try_from(idx) + .map_err(|_| encode::Error::ParseFailed("BIP152 prefilled tx index out of bounds"))?; + let tx = Transaction::consensus_decode(&mut d)?; + Ok(PrefilledTransaction { idx, tx }) + } +} + +/// Short transaction IDs are used to represent a transaction without sending a full 256-bit hash. +#[derive(PartialEq, Eq, Clone, Copy, Hash, Default, PartialOrd, Ord)] +pub struct ShortId([u8; 6]); +impl_array_newtype!(ShortId, u8, 6); +impl_bytes_newtype!(ShortId, 6); + +impl ShortId { + /// Calculate the SipHash24 keys used to calculate short IDs. + pub fn calculate_siphash_keys(header: &BlockHeader, nonce: u64) -> (u64, u64) { + // 1. single-SHA256 hashing the block header with the nonce appended (in little-endian) + let h = { + let mut engine = sha256::Hash::engine(); + header.consensus_encode(&mut engine).expect("engines don't error"); + nonce.consensus_encode(&mut engine).expect("engines don't error"); + sha256::Hash::from_engine(engine) + }; + + // 2. Running SipHash-2-4 with the input being the transaction ID and the keys (k0/k1) + // set to the first two little-endian 64-bit integers from the above hash, respectively. + (endian::slice_to_u64_le(&h[0..8]), endian::slice_to_u64_le(&h[8..16])) + } + + /// Calculate the short ID with the given (w)txid and using the provided SipHash keys. + pub fn with_siphash_keys>(txid: &T, siphash_keys: (u64, u64)) -> ShortId { + // 2. Running SipHash-2-4 with the input being the transaction ID and the keys (k0/k1) + // set to the first two little-endian 64-bit integers from the above hash, respectively. + let hash = siphash24::Hash::hash_with_keys(siphash_keys.0, siphash_keys.1, txid.as_ref()); + + // 3. Dropping the 2 most significant bytes from the SipHash output to make it 6 bytes. + let mut id = ShortId([0; 6]); + id.0.copy_from_slice(&hash[0..6]); + id + } +} + +impl Encodable for ShortId { + #[inline] + fn consensus_encode(&self, s: &mut S) -> Result { + self.0.consensus_encode(s) + } +} + +impl Decodable for ShortId { + #[inline] + fn consensus_decode(d: &mut D) -> Result { + Ok(ShortId(Decodable::consensus_decode(d)?)) + } +} + +/// A [HeaderAndShortIds] structure is used to relay a block header, the short +/// transactions IDs used for matching already-available transactions, and a +/// select few transactions which we expect a peer may be missing. +#[derive(PartialEq, Eq, Clone, Debug, PartialOrd, Ord, Hash)] +pub struct HeaderAndShortIds { + /// The header of the block being provided. + pub header: BlockHeader, + /// A nonce for use in short transaction ID calculations. + pub nonce: u64, + /// The short transaction IDs calculated from the transactions + /// which were not provided explicitly in prefilled_txs. + pub short_ids: Vec, + /// Used to provide the coinbase transaction and a select few + /// which we expect a peer may be missing. + pub prefilled_txs: Vec, +} +impl_consensus_encoding!(HeaderAndShortIds, header, nonce, short_ids, prefilled_txs); + +impl HeaderAndShortIds { + /// Create a new [HeaderAndShortIds] from a full block. + /// + /// The version number must be either 1 or 2. + /// + /// The `prefill` slice indicates which transactions should be prefilled in + /// the block. It should contain the indexes in the block of the txs to + /// prefill. It must be ordered. 0 should not be included as the + /// coinbase tx is always prefilled. + /// + /// > Nodes SHOULD NOT use the same nonce across multiple different blocks. + pub fn from_block( + block: &Block, + nonce: u64, + version: u32, + mut prefill: &[usize], + ) -> Result { + if version != 1 && version != 2 { + return Err(Error::UnknownVersion); + } + + 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 last_prefill = 0; + for (idx, tx) in block.txdata.iter().enumerate() { + // Check if we should prefill this tx. + let prefill_tx = if prefill.get(0) == Some(&idx) { + prefill = &prefill[1..]; + true + } else { + idx == 0 // Always prefill coinbase. + }; + + if prefill_tx { + let diff_idx = idx - last_prefill; + last_prefill = idx + 1; + prefilled.push(PrefilledTransaction { + idx: diff_idx as u16, + tx: match version { + // > As encoded in "tx" messages sent in response to getdata MSG_TX + 1 => { + // strip witness for version 1 + let mut no_witness = tx.clone(); + no_witness.input.iter_mut().for_each(|i| i.witness.clear()); + no_witness + } + // > Transactions inside cmpctblock messages (both those used as direct + // > announcement and those in response to getdata) and in blocktxn should + // > include witness data, using the same format as responses to getdata + // > MSG_WITNESS_TX, specified in BIP144. + 2 => tx.clone(), + _ => unreachable!(), + }, + }); + } else { + short_ids.push(ShortId::with_siphash_keys( + &match version { + 1 => tx.txid().as_hash(), + 2 => tx.wtxid().as_hash(), + _ => unreachable!(), + }, + siphash_keys, + )); + } + } + + if !prefill.is_empty() { + return Err(Error::InvalidPrefill); + } + + Ok(HeaderAndShortIds { + header: block.header, + nonce, + // Provide coinbase prefilled. + prefilled_txs: prefilled, + short_ids, + }) + } +} + +/// A [BlockTransactionsRequest] structure is used to list transaction indexes +/// in a block being requested. +#[derive(PartialEq, Eq, Clone, Debug, PartialOrd, Ord, Hash)] +pub struct BlockTransactionsRequest { + /// The blockhash of the block which the transactions being requested are in. + pub block_hash: BlockHash, + /// The indexes of the transactions being requested in the block. + /// + /// Warning: Encoding panics with [`u64::MAX`] values. See [`BlockTransactionsRequest::consensus_encode()`] + pub indexes: Vec, +} + +impl Encodable for BlockTransactionsRequest { + /// # Panics + /// + /// Panics if the index overflows [`u64::MAX`]. This happens when [`BlockTransactionsRequest::indexes`] + /// contains an entry with the value [`u64::MAX`] as `u64` overflows during differential encoding. + fn consensus_encode(&self, mut s: &mut S) -> Result { + let mut len = self.block_hash.consensus_encode(&mut s)?; + // Manually encode indexes because they are differentially encoded VarInts. + len += VarInt(self.indexes.len() as u64).consensus_encode(&mut s)?; + let mut last_idx = 0; + for idx in &self.indexes { + len += VarInt(*idx - last_idx).consensus_encode(&mut s)?; + last_idx = *idx + 1; // can panic here + } + Ok(len) + } +} + +impl Decodable for BlockTransactionsRequest { + fn consensus_decode( + mut d: &mut D, + ) -> Result { + Ok(BlockTransactionsRequest { + block_hash: BlockHash::consensus_decode(&mut d)?, + indexes: { + // Manually decode indexes because they are differentially encoded VarInts. + let nb_indexes = VarInt::consensus_decode(&mut d)?.0 as usize; + + // Since the number of indices ultimately represent transactions, + // we can limit the number of indices to the maximum number of + // transactions that would be allowed in a vector. + let byte_size = (nb_indexes as usize) + .checked_mul(mem::size_of::()) + .ok_or(encode::Error::ParseFailed("Invalid length"))?; + if byte_size > encode::MAX_VEC_SIZE { + return Err(encode::Error::OversizedVectorAllocation { + requested: byte_size, + max: encode::MAX_VEC_SIZE, + }); + } + + let mut indexes = Vec::with_capacity(nb_indexes); + let mut last_index: u64 = 0; + for _ in 0..nb_indexes { + let differential: VarInt = Decodable::consensus_decode(&mut d)?; + last_index = match last_index.checked_add(differential.0) { + Some(r) => r, + None => return Err(encode::Error::ParseFailed("block index overflow")), + }; + indexes.push(last_index); + last_index = match last_index.checked_add(1) { + Some(r) => r, + None => return Err(encode::Error::ParseFailed("block index overflow")), + }; + } + indexes + }, + }) + } +} + +/// A transaction index is requested that is out of range from the +/// corresponding block. +#[derive(Clone, PartialEq, Eq, Debug, Copy, PartialOrd, Ord, Hash)] +pub struct TxIndexOutOfRangeError(u64); + +impl fmt::Display for TxIndexOutOfRangeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "a transaction index is requested that is \ + out of range from the corresponding block: {}", + self.0, + ) + } +} + +#[cfg(feature = "std")] +impl error::Error for TxIndexOutOfRangeError {} + +/// A [BlockTransactions] structure is used to provide some of the transactions +/// in a block, as requested. +#[derive(PartialEq, Eq, Clone, Debug, PartialOrd, Ord, Hash)] +pub struct BlockTransactions { + /// The blockhash of the block which the transactions being provided are in. + pub block_hash: BlockHash, + /// The transactions provided. + pub transactions: Vec, +} +impl_consensus_encoding!(BlockTransactions, block_hash, transactions); + +impl BlockTransactions { + /// Construct a [BlockTransactions] from a [BlockTransactionsRequest] and + /// the corresponsing full [Block] by providing all requested transactions. + pub fn from_request( + request: &BlockTransactionsRequest, + 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() as u64 { + return Err(TxIndexOutOfRangeError(*idx)); + } + txs.push(block.txdata[*idx as usize].clone()); + } + txs + }, + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{ + Block, BlockHash, BlockHeader, OutPoint, Script, Sequence, Transaction, TxIn, TxMerkleNode, + TxOut, Txid, Witness, + }; + + fn dummy_tx(nonce: &[u8]) -> Transaction { + Transaction { + version: 1, + lock_time: 2, + input: vec![TxIn { + previous_output: OutPoint::new(Txid::hash(nonce), 0), + script_sig: Script::new(), + sequence: Sequence(1), + witness: Witness::new(), + }], + output: vec![TxOut { value: 1, script_pubkey: Script::new() }], + } + } + + fn dummy_block() -> Block { + Block { + header: BlockHeader { + version: 1, + prev_blockhash: BlockHash::hash(&[0]), + merkle_root: TxMerkleNode::hash(&[1]), + time: 2, + bits: 3, + nonce: 4, + }, + txdata: vec![dummy_tx(&[2]), dummy_tx(&[3]), dummy_tx(&[4])], + } + } + + #[test] + fn test_header_and_short_ids_from_block() { + let block = dummy_block(); + + let compact = HeaderAndShortIds::from_block(&block, 42, 2, &[]).unwrap(); + assert_eq!(compact.nonce, 42); + 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]); + + let compact = HeaderAndShortIds::from_block(&block, 42, 2, &[0, 1, 2]).unwrap(); + let idxs = compact.prefilled_txs.iter().map(|t| t.idx).collect::>(); + assert_eq!(idxs, vec![0, 0, 0]); + + let compact = HeaderAndShortIds::from_block(&block, 42, 2, &[2]).unwrap(); + let idxs = compact.prefilled_txs.iter().map(|t| t.idx).collect::>(); + assert_eq!(idxs, vec![0, 1]); + } +} diff --git a/src/util/mod.rs b/src/util/mod.rs index e3013ede..d3754794 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -14,6 +14,7 @@ pub mod amount; pub mod base58; pub mod bip32; pub mod bip143; +pub mod bip152; pub mod hash; pub mod merkleblock; pub mod misc; From d4d92a838e6318d7e789c73ee31477d4c115af36 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Sun, 24 Mar 2019 15:21:44 +0000 Subject: [PATCH 3/5] BIP152: Add Compact Blocks network messages --- src/network/message.rs | 21 +++++++++++++ src/network/message_blockdata.rs | 4 +++ src/network/message_compact_blocks.rs | 45 +++++++++++++++++++++++++++ src/network/mod.rs | 3 ++ 4 files changed, 73 insertions(+) create mode 100644 src/network/message_compact_blocks.rs diff --git a/src/network/message.rs b/src/network/message.rs index ac276560..5522d8e2 100644 --- a/src/network/message.rs +++ b/src/network/message.rs @@ -19,6 +19,7 @@ use crate::network::address::{Address, AddrV2Message}; use crate::network::{message_network, message_bloom}; use crate::network::message_blockdata; use crate::network::message_filter; +use crate::network::message_compact_blocks; use crate::consensus::encode::{CheckedData, Decodable, Encodable, VarInt}; use crate::consensus::{encode, serialize}; use crate::util::merkleblock::MerkleBlock; @@ -181,6 +182,14 @@ pub enum NetworkMessage { GetCFCheckpt(message_filter::GetCFCheckpt), /// BIP157 cfcheckpt CFCheckpt(message_filter::CFCheckpt), + /// BIP152 sendcmpct + SendCmpct(message_compact_blocks::SendCmpct), + /// BIP152 cmpctblock + CmpctBlock(message_compact_blocks::CmpctBlock), + /// BIP152 getblocktxn + GetBlockTxn(message_compact_blocks::GetBlockTxn), + /// BIP152 blocktxn + BlockTxn(message_compact_blocks::BlockTxn), /// `alert` Alert(Vec), /// `reject` @@ -237,6 +246,10 @@ impl NetworkMessage { NetworkMessage::CFHeaders(_) => "cfheaders", NetworkMessage::GetCFCheckpt(_) => "getcfcheckpt", NetworkMessage::CFCheckpt(_) => "cfcheckpt", + NetworkMessage::SendCmpct(_) => "sendcmpct", + NetworkMessage::CmpctBlock(_) => "cmpctblock", + NetworkMessage::GetBlockTxn(_) => "getblocktxn", + NetworkMessage::BlockTxn(_) => "blocktxn", NetworkMessage::Alert(_) => "alert", NetworkMessage::Reject(_) => "reject", NetworkMessage::FeeFilter(_) => "feefilter", @@ -314,6 +327,10 @@ impl Encodable for RawNetworkMessage { NetworkMessage::CFHeaders(ref dat) => serialize(dat), NetworkMessage::GetCFCheckpt(ref dat) => serialize(dat), NetworkMessage::CFCheckpt(ref dat) => serialize(dat), + NetworkMessage::SendCmpct(ref dat) => serialize(dat), + NetworkMessage::CmpctBlock(ref dat) => serialize(dat), + NetworkMessage::GetBlockTxn(ref dat) => serialize(dat), + NetworkMessage::BlockTxn(ref dat) => serialize(dat), NetworkMessage::Alert(ref dat) => serialize(dat), NetworkMessage::Reject(ref dat) => serialize(dat), NetworkMessage::FeeFilter(ref data) => serialize(data), @@ -394,6 +411,10 @@ impl Decodable for RawNetworkMessage { "reject" => NetworkMessage::Reject(Decodable::consensus_decode_from_finite_reader(&mut mem_d)?), "alert" => NetworkMessage::Alert(Decodable::consensus_decode_from_finite_reader(&mut mem_d)?), "feefilter" => NetworkMessage::FeeFilter(Decodable::consensus_decode_from_finite_reader(&mut mem_d)?), + "sendcmpct" => NetworkMessage::SendCmpct(Decodable::consensus_decode_from_finite_reader(&mut mem_d)?), + "cmpctblock" => NetworkMessage::CmpctBlock(Decodable::consensus_decode_from_finite_reader(&mut mem_d)?), + "getblocktxn" => NetworkMessage::GetBlockTxn(Decodable::consensus_decode_from_finite_reader(&mut mem_d)?), + "blocktxn" => NetworkMessage::BlockTxn(Decodable::consensus_decode_from_finite_reader(&mut mem_d)?), "wtxidrelay" => NetworkMessage::WtxidRelay, "addrv2" => NetworkMessage::AddrV2(Decodable::consensus_decode_from_finite_reader(&mut mem_d)?), "sendaddrv2" => NetworkMessage::SendAddrV2, diff --git a/src/network/message_blockdata.rs b/src/network/message_blockdata.rs index 09ca2492..98e5bbc8 100644 --- a/src/network/message_blockdata.rs +++ b/src/network/message_blockdata.rs @@ -27,6 +27,8 @@ pub enum Inventory { Transaction(Txid), /// Block Block(BlockHash), + /// Compact Block + CompactBlock(BlockHash), /// Witness Transaction by Wtxid WTx(Wtxid), /// Witness Transaction @@ -54,6 +56,7 @@ impl Encodable for Inventory { Inventory::Error => encode_inv!(0, sha256d::Hash::all_zeros()), Inventory::Transaction(ref t) => encode_inv!(1, t), Inventory::Block(ref b) => encode_inv!(2, b), + Inventory::CompactBlock(ref b) => encode_inv!(4, b), Inventory::WTx(w) => encode_inv!(5, w), Inventory::WitnessTransaction(ref t) => encode_inv!(0x40000001, t), Inventory::WitnessBlock(ref b) => encode_inv!(0x40000002, b), @@ -70,6 +73,7 @@ impl Decodable for Inventory { 0 => Inventory::Error, 1 => Inventory::Transaction(Decodable::consensus_decode(r)?), 2 => Inventory::Block(Decodable::consensus_decode(r)?), + 4 => Inventory::CompactBlock(Decodable::consensus_decode(r)?), 5 => Inventory::WTx(Decodable::consensus_decode(r)?), 0x40000001 => Inventory::WitnessTransaction(Decodable::consensus_decode(r)?), 0x40000002 => Inventory::WitnessBlock(Decodable::consensus_decode(r)?), diff --git a/src/network/message_compact_blocks.rs b/src/network/message_compact_blocks.rs new file mode 100644 index 00000000..19fe42d7 --- /dev/null +++ b/src/network/message_compact_blocks.rs @@ -0,0 +1,45 @@ +//! +//! BIP152 Compact Blocks network messages +//! + +use crate::internal_macros::impl_consensus_encoding; +use crate::util::bip152; + +/// sendcmpct message +#[derive(PartialEq, Eq, Clone, Debug, Copy, PartialOrd, Ord, Hash)] +pub struct SendCmpct { + /// Request to be send compact blocks. + pub send_compact: bool, + /// Compact Blocks protocol version number. + pub version: u64, +} +impl_consensus_encoding!(SendCmpct, send_compact, version); + +/// cmpctblock message +/// +/// Note that the rules for validation before relaying compact blocks is +/// different from headers and regular block messages. Thus, you shouldn't use +/// compact blocks when relying on an upstream full node to have validated data +/// being forwarded to you. +#[derive(PartialEq, Eq, Clone, Debug, PartialOrd, Ord, Hash)] +pub struct CmpctBlock { + /// The Compact Block. + pub compact_block: bip152::HeaderAndShortIds, +} +impl_consensus_encoding!(CmpctBlock, compact_block); + +/// getblocktxn message +#[derive(PartialEq, Eq, Clone, Debug, PartialOrd, Ord, Hash)] +pub struct GetBlockTxn { + /// The block transactions request. + pub txs_request: bip152::BlockTransactionsRequest, +} +impl_consensus_encoding!(GetBlockTxn, txs_request); + +/// blocktxn message +#[derive(PartialEq, Eq, Clone, Debug, PartialOrd, Ord, Hash)] +pub struct BlockTxn { + /// The requested block transactions. + pub transactions: bip152::BlockTransactions, +} +impl_consensus_encoding!(BlockTxn, transactions); diff --git a/src/network/mod.rs b/src/network/mod.rs index 24735636..96b724e6 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -26,6 +26,9 @@ pub mod message_blockdata; pub mod message_bloom; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod message_compact_blocks; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod message_network; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] From cd1aaaf3443481effe0c8f944507725f5467ce24 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Thu, 17 Oct 2019 14:41:18 +0100 Subject: [PATCH 4/5] BIP152: Add Compact Block unit test based on Elements --- src/util/bip152.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/util/bip152.rs b/src/util/bip152.rs index 5b92c0c7..92a4a3a3 100644 --- a/src/util/bip152.rs +++ b/src/util/bip152.rs @@ -362,6 +362,8 @@ impl BlockTransactions { #[cfg(test)] mod test { use super::*; + use crate::hashes::hex::FromHex; + use crate::consensus::encode::deserialize; use crate::{ Block, BlockHash, BlockHeader, OutPoint, Script, Sequence, Transaction, TxIn, TxMerkleNode, TxOut, Txid, Witness, @@ -414,4 +416,18 @@ mod test { let idxs = compact.prefilled_txs.iter().map(|t| t.idx).collect::>(); assert_eq!(idxs, vec![0, 1]); } + + #[test] + fn test_compact_block_vector() { + // Tested with Elements implementation of compact blocks. + let raw_block = Vec::::from_hex("000000206c750a364035aefd5f81508a08769975116d9195312ee4520dceac39e1fdc62c4dc67473b8e354358c1e610afeaff7410858bd45df43e2940f8a62bd3d5e3ac943c2975cffff7f200000000002020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04016b0101ffffffff020006062a0100000001510000000000000000266a24aa21a9ed4a3d9f3343dafcc0d6f6d4310f2ee5ce273ed34edca6c75db3a73e7f368734200120000000000000000000000000000000000000000000000000000000000000000000000000020000000001021fc20ba2bd745507b8e00679e3b362558f9457db374ca28ffa5243f4c23a4d5f00000000171600147c9dea14ffbcaec4b575e03f05ceb7a81cd3fcbffdffffff915d689be87b43337f42e26033df59807b768223368f189a023d0242d837768900000000171600147c9dea14ffbcaec4b575e03f05ceb7a81cd3fcbffdffffff0200cdf5050000000017a9146803c72d9154a6a20f404bed6d3dcee07986235a8700e1f5050000000017a9144e6a4c7cb5b5562904843bdf816342f4db9f5797870247304402205e9bf6e70eb0e4b495bf483fd8e6e02da64900f290ef8aaa64bb32600d973c450220670896f5d0e5f33473e5f399ab680cc1d25c2d2afd15abd722f04978f28be887012103e4e4d9312b2261af508b367d8ba9be4f01b61d6d6e78bec499845b4f410bcf2702473044022045ac80596a6ac9c8c572f94708709adaf106677221122e08daf8b9741a04f66a022003ccd52a3b78f8fd08058fc04fc0cffa5f4c196c84eae9e37e2a85babe731b57012103e4e4d9312b2261af508b367d8ba9be4f01b61d6d6e78bec499845b4f410bcf276a000000").unwrap(); + let raw_compact = Vec::::from_hex("000000206c750a364035aefd5f81508a08769975116d9195312ee4520dceac39e1fdc62c4dc67473b8e354358c1e610afeaff7410858bd45df43e2940f8a62bd3d5e3ac943c2975cffff7f2000000000a4df3c3744da89fa010a6979e971450100020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04016b0101ffffffff020006062a0100000001510000000000000000266a24aa21a9ed4a3d9f3343dafcc0d6f6d4310f2ee5ce273ed34edca6c75db3a73e7f368734200120000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(); + + let block: Block = deserialize(&raw_block).unwrap(); + let nonce = 18053200567810711460; + let compact = HeaderAndShortIds::from_block(&block, nonce, 2, &[]).unwrap(); + let compact_expected = deserialize(&raw_compact).unwrap(); + + assert_eq!(compact, compact_expected); + } } From ee29910911e786e40b1cb25c72ed90eaad48c512 Mon Sep 17 00:00:00 2001 From: 0xb10c <0xb10c@gmail.com> Date: Wed, 6 Jul 2022 17:05:45 +0200 Subject: [PATCH 5/5] BIP152: Test net msg ser/der and diff encoding This adds tests for serialization of BIP152 network messages and tests specifically for the differential encoding of the transaction indicies of 'getblocktx'. Previously, this code contained an off-by-one error. --- src/network/message.rs | 9 +++++++ src/util/bip152.rs | 54 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/network/message.rs b/src/network/message.rs index 5522d8e2..32608045 100644 --- a/src/network/message.rs +++ b/src/network/message.rs @@ -453,6 +453,8 @@ mod test { use crate::blockdata::script::Script; use crate::network::message_bloom::{FilterAdd, FilterLoad, BloomFlags}; use crate::MerkleBlock; + use crate::network::message_compact_blocks::{GetBlockTxn, SendCmpct}; + use crate::util::bip152::BlockTransactionsRequest; fn hash(slice: [u8;32]) -> Hash { Hash::from_slice(&slice).unwrap() @@ -467,6 +469,9 @@ mod test { let header: BlockHeader = deserialize(&Vec::from_hex("010000004ddccd549d28f385ab457e98d1b11ce80bfea2c5ab93015ade4973e400000000bf4473e53794beae34e64fccc471dace6ae544180816f89591894e0f417a914cd74d6e49ffff001d323b3a7b").unwrap()).unwrap(); let script: Script = deserialize(&Vec::from_hex("1976a91431a420903c05a0a7de2de40c9f02ebedbacdc17288ac").unwrap()).unwrap(); let merkle_block: MerkleBlock = deserialize(&Vec::from_hex("0100000079cda856b143d9db2c1caff01d1aecc8630d30625d10e8b4b8b0000000000000b50cc069d6a3e33e3ff84a5c41d9d3febe7c770fdcc96b2c3ff60abe184f196367291b4d4c86041b8fa45d630100000001b50cc069d6a3e33e3ff84a5c41d9d3febe7c770fdcc96b2c3ff60abe184f19630101").unwrap()).unwrap(); + let cmptblock = deserialize(&Vec::from_hex("00000030d923ad36ff2d955abab07f8a0a6e813bc6e066b973e780c5e36674cad5d1cd1f6e265f2a17a0d35cbe701fe9d06e2c6324cfe135f6233e8b767bfa3fb4479b71115dc562ffff7f2006000000000000000000000000010002000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0302ee00ffffffff0100f9029500000000015100000000").unwrap()).unwrap(); + let blocktxn = deserialize(&Vec::from_hex("2e93c0cff39ff605020072d96bc3a8d20b8447e294d08092351c8583e08d9b5a01020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0402dc0000ffffffff0200f90295000000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000").unwrap()).unwrap(); + let msgs = vec![ NetworkMessage::Version(version_msg), @@ -502,6 +507,10 @@ mod test { NetworkMessage::WtxidRelay, NetworkMessage::AddrV2(vec![AddrV2Message{ addr: AddrV2::Ipv4(Ipv4Addr::new(127, 0, 0, 1)), port: 0, services: ServiceFlags::NONE, time: 0 }]), NetworkMessage::SendAddrV2, + NetworkMessage::CmpctBlock(cmptblock), + NetworkMessage::GetBlockTxn(GetBlockTxn { txs_request: BlockTransactionsRequest { block_hash: hash([11u8; 32]).into(), indexes: vec![0, 1, 2, 3, 10, 3002] } }), + NetworkMessage::BlockTxn(blocktxn), + NetworkMessage::SendCmpct(SendCmpct{send_compact: true, version: 8333}), ]; for msg in msgs { diff --git a/src/util/bip152.rs b/src/util/bip152.rs index 92a4a3a3..09c5b675 100644 --- a/src/util/bip152.rs +++ b/src/util/bip152.rs @@ -362,8 +362,8 @@ impl BlockTransactions { #[cfg(test)] mod test { use super::*; + use crate::consensus::encode::{deserialize, serialize}; use crate::hashes::hex::FromHex; - use crate::consensus::encode::deserialize; use crate::{ Block, BlockHash, BlockHeader, OutPoint, Script, Sequence, Transaction, TxIn, TxMerkleNode, TxOut, Txid, Witness, @@ -430,4 +430,56 @@ mod test { assert_eq!(compact, compact_expected); } + + #[test] + fn test_getblocktx_differential_encoding_de_and_serialization() { + let testcases = vec![ + // differentially encoded VarInts, indicies + (vec![4, 0, 5, 1, 10], vec![0, 6, 8, 19]), + (vec![1, 0], vec![0]), + (vec![5, 0, 0, 0, 0, 0], vec![0, 1, 2, 3, 4]), + (vec![3, 1, 1, 1], vec![1, 3, 5]), + (vec![3, 0, 0, 253, 0, 1], vec![0, 1, 258]), // .., 253, 0, 1] == VarInt(256) + ]; + let deser_errorcases = vec![ + vec![2, 255, 254, 255, 255, 255, 255, 255, 255, 255, 0], // .., 255, 254, .., 255] == VarInt(u64::MAX-1) + vec![1, 255, 255, 255, 255, 255, 255, 255, 255, 255], // .., 255, 255, .., 255] == VarInt(u64::MAX) + ]; + for testcase in testcases { + { + // test deserialization + let mut raw: Vec = [0u8; 32].to_vec(); + raw.extend(testcase.0.clone()); + let btr: BlockTransactionsRequest = deserialize(&raw.to_vec()).unwrap(); + assert_eq!(testcase.1, btr.indexes); + } + { + // test serialization + let raw: Vec = serialize(&BlockTransactionsRequest { + block_hash: Hash::all_zeros(), + indexes: testcase.1, + }); + let mut expected_raw: Vec = [0u8; 32].to_vec(); + expected_raw.extend(testcase.0); + assert_eq!(expected_raw, raw); + } + } + for errorcase in deser_errorcases { + { + // test that we return Err() if deserialization fails (and don't panic) + let mut raw: Vec = [0u8; 32].to_vec(); + raw.extend(errorcase); + assert!(deserialize::(&raw.to_vec()).is_err()); + } + } + } + + #[test] + #[should_panic] // 'attempt to add with overflow' in consensus_encode() + fn test_getblocktx_panic_when_encoding_u64_max() { + serialize(&BlockTransactionsRequest { + block_hash: Hash::all_zeros(), + indexes: vec![core::u64::MAX], + }); + } }