Merge rust-bitcoin/rust-bitcoin#3582: Re-design and move `Block` to `primitives`

7819e50055 Move Block to primitives (Tobin C. Harding)
55200924e4 Move Sealed to bottom of file (Tobin C. Harding)
d5b148d400 Make block_size free standing and rename (Tobin C. Harding)
5016a73207 Manually implement consensus traits for Block (Tobin C. Harding)

Pull request description:

  Re-design the `Block` API and move the type to `primitives`.

ACKs for top commit:
  apoelstra:
    ACK 7819e50055c1be1d09b7d08734955f43229e6ed5; successfully ran local tests; let's go

Tree-SHA512: 13dc27508348966392aab4a80a3009bd53dd604d98ef392b9655bb97d669e01c4399901043ca3558b9b339cec5a1521ed0abfe7d666a66c6cc84ee4a97772de1
This commit is contained in:
merge-script 2024-11-14 23:41:31 +00:00
commit 9f68802887
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
12 changed files with 605 additions and 294 deletions

View File

@ -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<BlockChecked>,
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<BlockChecked>,
) -> Result<BlockTransactions, TxIndexOutOfRangeError> {
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 {
fn dummy_block() -> Block<BlockChecked> {
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,
},
txdata: vec![dummy_tx(&[2]), dummy_tx(&[3]), dummy_tx(&[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::<Vec<_>>();
@ -470,6 +469,7 @@ mod test {
let raw_compact = Vec::<u8>::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();

View File

@ -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<M, S>(block: &Block, script_for_coin: M) -> Result<BlockFilter, Error>
pub fn new_script_filter<M, S>(
block: &Block<Checked>,
script_for_coin: M,
) -> Result<BlockFilter, Error>
where
M: Fn(&OutPoint) -> Result<S, Error>,
S: Borrow<Script>,
@ -177,13 +180,13 @@ impl BlockFilter {
/// Compiles and writes a block filter.
pub struct BlockFilterWriter<'a, W> {
block: &'a Block,
block: &'a Block<Checked>,
writer: GcsFilterWriter<'a, W>,
}
impl<'a, W: Write> BlockFilterWriter<'a, W> {
/// Constructs a new [`BlockFilterWriter`] from `block`.
pub fn new(writer: &'a mut W, block: &'a Block) -> BlockFilterWriter<'a, W> {
pub fn new(writer: &'a mut W, block: &'a Block<Checked>) -> BlockFilterWriter<'a, W> {
let block_hash_as_int = block.block_hash().to_byte_array();
let k0 = u64::from_le_bytes(block_hash_as_int[0..8].try_into().expect("8 byte slice"));
let k1 = u64::from_le_bytes(block_hash_as_int[8..16].try_into().expect("8 byte slice"));
@ -193,7 +196,7 @@ impl<'a, W: Write> BlockFilterWriter<'a, W> {
/// Adds output scripts of the block to filter (excluding OP_RETURN scripts).
pub fn add_output_scripts(&mut self) {
for transaction in &self.block.txdata {
for transaction in self.block.transactions() {
for output in &transaction.output {
if !output.script_pubkey.is_op_return() {
self.add_element(output.script_pubkey.as_bytes());
@ -210,7 +213,7 @@ impl<'a, W: Write> BlockFilterWriter<'a, W> {
{
for script in self
.block
.txdata
.transactions()
.iter()
.skip(1) // skip coinbase
.flat_map(|t| t.input.iter().map(|i| &i.previous_output))
@ -583,6 +586,7 @@ mod test {
for t in testdata.iter().skip(1) {
let block_hash = t.get(1).unwrap().as_str().unwrap().parse::<BlockHash>().unwrap();
let block: Block = deserialize(&hex!(t.get(2).unwrap().as_str().unwrap())).unwrap();
let block = block.assume_checked(None);
assert_eq!(block.block_hash(), block_hash);
let scripts = t.get(3).unwrap().as_array().unwrap();
let previous_filter_header =
@ -593,7 +597,7 @@ mod test {
let mut txmap = HashMap::new();
let mut si = scripts.iter();
for tx in block.txdata.iter().skip(1) {
for tx in block.transactions().iter().skip(1) {
for input in tx.input.iter() {
txmap.insert(
input.previous_output,

View File

@ -9,13 +9,12 @@
use core::fmt;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
use hashes::{sha256d, HashEngine};
use internals::compact_size;
use io::{BufRead, Write};
use super::Weight;
use crate::consensus::encode::WriteExt as _;
use crate::consensus::{encode, Decodable, Encodable};
use crate::internal_macros::{impl_consensus_encoding, impl_hashencode};
use crate::merkle_tree::{MerkleNode as _, TxMerkleNode, WitnessMerkleNode};
@ -27,7 +26,7 @@ use crate::transaction::{Transaction, TransactionExt as _, Wtxid};
#[rustfmt::skip] // Keep public re-exports separate.
#[doc(inline)]
pub use primitives::block::{Version, BlockHash, Header, WitnessCommitment};
pub use primitives::block::{Block, Checked, Unchecked, Validation, Version, BlockHash, Header, WitnessCommitment};
#[doc(inline)]
pub use units::block::{BlockHeight, BlockInterval, TooBigForRelativeBlockHeightError};
@ -73,11 +72,6 @@ crate::internal_macros::define_extension_trait! {
}
}
mod sealed {
pub trait Sealed {}
impl Sealed for super::Header {}
}
impl Encodable for Version {
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
self.to_consensus().consensus_encode(w)
@ -90,100 +84,59 @@ impl Decodable for Version {
}
}
/// Bitcoin block.
/// Extension functionality for the [`Block<Unchecked>`] type.
pub trait BlockUncheckedExt: sealed::Sealed {
/// Validates (or checks) a block.
///
/// A collection of transactions with an attached proof of work.
/// We define valid as:
///
/// See [Bitcoin Wiki: Block][wiki-block] for more information.
///
/// [wiki-block]: https://en.bitcoin.it/wiki/Block
///
/// ### Bitcoin Core References
///
/// * [CBlock definition](https://github.com/bitcoin/bitcoin/blob/345457b542b6a980ccfbc868af0970a6f91d1b82/src/primitives/block.h#L62)
#[derive(PartialEq, Eq, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Block {
/// The block header
pub header: Header,
/// List of transactions contained in the block
pub txdata: Vec<Transaction>,
/// * The Merkle root of the header matches Merkle root of the transaction list.
/// * The witness commitment in coinbase matches the transaction list.
fn validate(self) -> Result<Block<Checked>, InvalidBlockError>;
}
impl_consensus_encoding!(Block, header, txdata);
impl BlockUncheckedExt for Block<Unchecked> {
fn validate(self) -> Result<Block<Checked>, InvalidBlockError> {
let (header, transactions) = self.into_parts();
impl Block {
/// Returns the block hash.
pub fn block_hash(&self) -> BlockHash { self.header.block_hash() }
/// Checks if Merkle root of header matches Merkle root of the transaction list.
pub fn check_merkle_root(&self) -> bool {
match self.compute_merkle_root() {
Some(merkle_root) => self.header.merkle_root == merkle_root,
None => false,
}
if !check_merkle_root(&header, &transactions) {
return Err(InvalidBlockError::InvalidMerkleRoot);
}
/// Checks if witness commitment in coinbase matches the transaction list.
pub fn check_witness_commitment(&self) -> bool {
// Consists of OP_RETURN, OP_PUSHBYTES_36, and four "witness header" bytes.
const MAGIC: [u8; 6] = [0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed];
// Witness commitment is optional if there are no transactions using SegWit in the block.
if self.txdata.iter().all(|t| t.input.iter().all(|i| i.witness.is_empty())) {
return true;
match check_witness_commitment(&transactions) {
(false, _) => Err(InvalidBlockError::InvalidWitnessCommitment),
(true, witness_root) => {
let block = Block::new_unchecked(header, transactions);
Ok(block.assume_checked(witness_root))
}
if self.txdata.is_empty() {
return false;
}
let coinbase = &self.txdata[0];
if !coinbase.is_coinbase() {
return false;
}
// Commitment is in the last output that starts with magic bytes.
if let Some(pos) = coinbase
.output
.iter()
.rposition(|o| o.script_pubkey.len() >= 38 && o.script_pubkey.as_bytes()[0..6] == MAGIC)
{
let bytes = <[u8; 32]>::try_from(&coinbase.output[pos].script_pubkey.as_bytes()[6..38])
.unwrap();
let commitment = WitnessCommitment::from_byte_array(bytes);
// Witness reserved value is in coinbase input witness.
let witness_vec: Vec<_> = coinbase.input[0].witness.iter().collect();
if witness_vec.len() == 1 && witness_vec[0].len() == 32 {
if let Some(witness_root) = self.witness_root() {
return commitment
== Self::compute_witness_commitment(witness_root, witness_vec[0]);
}
}
}
false
}
/// Computes the transaction Merkle root.
pub fn compute_merkle_root(&self) -> Option<TxMerkleNode> {
let hashes = self.txdata.iter().map(|obj| obj.compute_txid());
/// Computes the Merkle root for a list of transactions.
pub fn compute_merkle_root(transactions: &[Transaction]) -> Option<TxMerkleNode> {
let hashes = transactions.iter().map(|obj| obj.compute_txid());
TxMerkleNode::calculate_root(hashes)
}
/// Computes the witness commitment for the block's transaction list.
/// Computes the witness commitment for a list of transactions.
pub fn compute_witness_commitment(
witness_root: WitnessMerkleNode,
transactions: &[Transaction],
witness_reserved_value: &[u8],
) -> WitnessCommitment {
) -> Option<(WitnessMerkleNode, WitnessCommitment)> {
compute_witness_root(transactions).map(|witness_root| {
let mut encoder = sha256d::Hash::engine();
witness_root.consensus_encode(&mut encoder).expect("engines don't error");
encoder.input(witness_reserved_value);
WitnessCommitment::from_byte_array(sha256d::Hash::from_engine(encoder).to_byte_array())
let witness_commitment =
WitnessCommitment::from_byte_array(sha256d::Hash::from_engine(encoder).to_byte_array());
(witness_root, witness_commitment)
})
}
/// Computes the Merkle root of transactions hashed for witness.
pub fn witness_root(&self) -> Option<WitnessMerkleNode> {
let hashes = self.txdata.iter().enumerate().map(|(i, t)| {
pub fn compute_witness_root(transactions: &[Transaction]) -> Option<WitnessMerkleNode> {
let hashes = transactions.iter().enumerate().map(|(i, t)| {
if i == 0 {
// Replace the first hash with zeroes.
Wtxid::COINBASE
@ -194,46 +147,139 @@ impl Block {
WitnessMerkleNode::calculate_root(hashes)
}
/// Checks if Merkle root of header matches Merkle root of the transaction list.
fn check_merkle_root(header: &Header, transactions: &[Transaction]) -> bool {
match compute_merkle_root(transactions) {
Some(merkle_root) => header.merkle_root == merkle_root,
None => false,
}
}
/// Checks if witness commitment in coinbase matches the transaction list.
// Returns the Merkle root if it was computed (so it can be cached in `assume_checked`).
fn check_witness_commitment(transactions: &[Transaction]) -> (bool, Option<WitnessMerkleNode>) {
// Witness commitment is optional if there are no transactions using SegWit in the block.
if transactions.iter().all(|t| t.input.iter().all(|i| i.witness.is_empty())) {
return (true, None);
}
if transactions.is_empty() {
return (false, None);
}
if transactions[0].is_coinbase() {
let coinbase = transactions[0].clone();
if let Some(commitment) = witness_commitment_from_coinbase(&coinbase) {
// Witness reserved value is in coinbase input witness.
let witness_vec: Vec<_> = coinbase.input[0].witness.iter().collect();
if witness_vec.len() == 1 && witness_vec[0].len() == 32 {
if let Some((witness_root, witness_commitment)) =
compute_witness_commitment(transactions, witness_vec[0])
{
if commitment == witness_commitment {
return (true, Some(witness_root));
}
}
}
}
}
(false, None)
}
fn witness_commitment_from_coinbase(coinbase: &Transaction) -> Option<WitnessCommitment> {
// Consists of OP_RETURN, OP_PUSHBYTES_36, and four "witness header" bytes.
const MAGIC: [u8; 6] = [0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed];
if !coinbase.is_coinbase() {
return None;
}
// Commitment is in the last output that starts with magic bytes.
if let Some(pos) = coinbase
.output
.iter()
.rposition(|o| o.script_pubkey.len() >= 38 && o.script_pubkey.as_bytes()[0..6] == MAGIC)
{
let bytes =
<[u8; 32]>::try_from(&coinbase.output[pos].script_pubkey.as_bytes()[6..38]).unwrap();
Some(WitnessCommitment::from_byte_array(bytes))
} else {
None
}
}
/// Extension functionality for the [`Block<Checked>`] type.
pub trait BlockCheckedExt: sealed::Sealed {
/// Constructs a new [`Block`].
///
/// # Returns
///
/// Return the block if it is valid, `None` if not. See [`Block::validate`].
fn new_checked(
header: Header,
transactions: Vec<Transaction>,
) -> Result<Block<Checked>, InvalidBlockError>;
/// Returns the transaction Merkle root.
fn merkle_root(&self) -> TxMerkleNode;
/// Returns the Merkle root of transactions hashed for witness.
///
/// This value was computed during block validation and was cached at that time.
fn witness_root(&mut self) -> Option<WitnessMerkleNode>;
/// Returns the weight of the 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();
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::<usize>();
size
}
fn weight(&self) -> Weight;
/// Returns the total block size.
///
/// > Total size is the block size in bytes with transactions serialized as described in BIP144,
/// > including base data and witness data.
pub fn total_size(&self) -> usize {
fn total_size(&self) -> usize;
/// Returns the coinbase transaction, if one is present.
fn coinbase(&self) -> Option<&Transaction>;
/// Returns the block height, as encoded in the coinbase transaction according to BIP34.
fn bip34_block_height(&self) -> Result<u64, Bip34Error>;
}
impl BlockCheckedExt for Block<Checked> {
fn new_checked(
header: Header,
transactions: Vec<Transaction>,
) -> Result<Block<Checked>, InvalidBlockError> {
let block = Block::new_unchecked(header, transactions);
block.validate()
}
fn merkle_root(&self) -> TxMerkleNode { self.header().merkle_root }
fn witness_root(&mut self) -> Option<WitnessMerkleNode> { self.cached_witness_root() }
fn weight(&self) -> Weight {
// This is the exact definition of a weight unit, as defined by BIP-141 (quote above).
let wu = block_base_size(self.transactions()) * 3 + self.total_size();
Weight::from_wu_usize(wu)
}
fn total_size(&self) -> usize {
let mut size = Header::SIZE;
size += compact_size::encoded_size(self.txdata.len());
size += self.txdata.iter().map(|tx| tx.total_size()).sum::<usize>();
size += compact_size::encoded_size(self.transactions().len());
size += self.transactions().iter().map(|tx| tx.total_size()).sum::<usize>();
size
}
/// Returns the coinbase transaction, if one is present.
pub fn coinbase(&self) -> Option<&Transaction> { self.txdata.first() }
fn coinbase(&self) -> Option<&Transaction> { self.transactions().first() }
/// Returns the block height, as encoded in the coinbase transaction according to BIP34.
pub fn bip34_block_height(&self) -> Result<u64, Bip34Error> {
fn bip34_block_height(&self) -> Result<u64, Bip34Error> {
// Citing the spec:
// Add height as the first item in the coinbase transaction's scriptSig,
// and increase block version to 2. The format of the height is
@ -243,7 +289,7 @@ impl Block {
// number (including a sign bit). Height is the height of the mined
// block in the block chain, where the genesis block is height zero (0).
if self.header.version < Version::TWO {
if self.header().version < Version::TWO {
return Err(Bip34Error::Unsupported);
}
@ -264,13 +310,94 @@ impl Block {
}
}
impl From<Block> for BlockHash {
fn from(block: Block) -> BlockHash { block.block_hash() }
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::<usize>();
size
}
impl From<&Block> for BlockHash {
fn from(block: &Block) -> BlockHash { block.block_hash() }
impl Encodable for Block<Unchecked> {
#[inline]
fn consensus_encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
// TODO: Should we be able to encode without cloning?
// This is ok, we decode as unchecked anyway.
let block = self.clone().assume_checked(None);
block.consensus_encode(w)
}
}
impl Encodable for Block<Checked> {
#[inline]
fn consensus_encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
let mut len = 0;
len += self.header().consensus_encode(w)?;
let transactions = self.transactions();
len += w.emit_compact_size(transactions.len())?;
for c in transactions.iter() {
len += c.consensus_encode(w)?;
}
Ok(len)
}
}
impl Decodable for Block<Unchecked> {
#[inline]
fn consensus_decode_from_finite_reader<R: io::BufRead + ?Sized>(
r: &mut R,
) -> Result<Block, encode::Error> {
let header = Decodable::consensus_decode_from_finite_reader(r)?;
let transactions = Decodable::consensus_decode_from_finite_reader(r)?;
Ok(Block::new_unchecked(header, transactions))
}
#[inline]
fn consensus_decode<R: io::BufRead + ?Sized>(r: &mut R) -> Result<Block, encode::Error> {
let mut r = r.take(internals::ToU64::to_u64(encode::MAX_VEC_SIZE));
let header = Decodable::consensus_decode(&mut r)?;
let transactions = Decodable::consensus_decode(&mut r)?;
Ok(Block::new_unchecked(header, transactions))
}
}
mod sealed {
/// Seals the extension traits.
pub trait Sealed {}
impl Sealed for super::Header {}
impl<V: super::Validation> Sealed for super::Block<V> {}
}
/// Invalid block error.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum InvalidBlockError {
/// Header Merkle root does not match the calculated Merkle root.
InvalidMerkleRoot,
/// The witness commitment in coinbase transaction does not match the calculated witness_root.
InvalidWitnessCommitment,
}
internals::impl_from_infallible!(InvalidBlockError);
impl fmt::Display for InvalidBlockError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use InvalidBlockError::*;
match *self {
InvalidMerkleRoot => write!(f, "header Merkle root does not match the calculated Merkle root"),
InvalidWitnessCommitment => write!(f, "the witness commitment in coinbase transaction does not match the calculated witness_root"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for InvalidBlockError {}
/// An error when looking up a BIP34 block height.
#[derive(Debug, Clone, PartialEq, Eq)]
@ -354,13 +481,6 @@ impl std::error::Error for ValidationError {
}
}
#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for Block {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(Block { header: Header::arbitrary(u)?, txdata: Vec::<Transaction>::arbitrary(u)? })
}
}
#[cfg(test)]
mod tests {
use hex::test_hex_unwrap as hex;
@ -368,14 +488,31 @@ mod tests {
use super::*;
use crate::consensus::encode::{deserialize, serialize};
use crate::{CompactTarget, Network, TestnetVersion};
use crate::pow::test_utils::{u128_to_work, u64_to_work};
use crate::{block, CompactTarget, Network, TestnetVersion};
#[test]
fn static_vector() {
// testnet block 000000000000045e0b1660b6445b5e5c5ab63c9a4f956be7e1e69be04fa4497b
let segwit_block = include_bytes!("../../tests/data/testnet_block_000000000000045e0b1660b6445b5e5c5ab63c9a4f956be7e1e69be04fa4497b.raw");
let block: Block = deserialize(&segwit_block[..]).expect("failed to deserialize block");
let (header, transactions) = block.into_parts();
assert!(block::check_merkle_root(&header, &transactions));
let block = Block::new_unchecked(header, transactions).assume_checked(None);
// Same as `block.check_merkle_root` but do it explicitly.
let hashes_iter = block.transactions().iter().map(|obj| obj.compute_txid());
let from_iter = TxMerkleNode::calculate_root(hashes_iter.clone());
assert_eq!(from_iter, Some(block.header().merkle_root));
}
#[test]
fn test_coinbase_and_bip34() {
// testnet block 100,000
const BLOCK_HEX: &str = "0200000035ab154183570282ce9afc0b494c9fc6a3cfea05aa8c1add2ecc56490000000038ba3d78e4500a5a7570dbe61960398add4410d278b21cd9708e6d9743f374d544fc055227f1001c29c1ea3b0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff3703a08601000427f1001c046a510100522cfabe6d6d0000000000000000000068692066726f6d20706f6f6c7365727665726aac1eeeed88ffffffff0100f2052a010000001976a914912e2b234f941f30b18afbb4fa46171214bf66c888ac00000000";
let block: Block = deserialize(&hex!(BLOCK_HEX)).unwrap();
let block = block.assume_checked(None);
let cb_txid = "d574f343976d8e70d91cb278d21044dd8a396019e6db70755a0a50e4783dba38";
assert_eq!(block.coinbase().unwrap().compute_txid().to_string(), cb_txid);
@ -385,24 +522,28 @@ mod tests {
// block with 3-byte bip34 push for height 0x03010000 (non-minimal 1)
const BAD_HEX: &str = "0200000035ab154183570282ce9afc0b494c9fc6a3cfea05aa8c1add2ecc56490000000038ba3d78e4500a5a7570dbe61960398add4410d278b21cd9708e6d9743f374d544fc055227f1001c29c1ea3b0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff3703010000000427f1001c046a510100522cfabe6d6d0000000000000000000068692066726f6d20706f6f6c7365727665726aac1eeeed88ffffffff0100f2052a010000001976a914912e2b234f941f30b18afbb4fa46171214bf66c888ac00000000";
let bad: Block = deserialize(&hex!(BAD_HEX)).unwrap();
let bad = bad.assume_checked(None);
assert_eq!(bad.bip34_block_height(), Err(super::Bip34Error::NonMinimalPush));
// Block 15 on Testnet4 has height of 0x5f (15 PUSHNUM)
const BLOCK_HEX_SMALL_HEIGHT_15: &str = "000000200fd8c4c1e88f313b561b2724542ff9be1bc54a7dab8db8ef6359d48a00000000705bf9145e6d3c413702cc61f32e4e7bfe3117b1eb928071a59adcf75694a3fb07d83866ffff001dcf4c5e8401010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff095f00062f4077697a2fffffffff0200f2052a010000001976a9140a59837ccd4df25adc31cdad39be6a8d97557ed688ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000";
let block: Block = deserialize(&hex!(BLOCK_HEX_SMALL_HEIGHT_15)).unwrap();
let block = block.assume_checked(None);
assert_eq!(block.bip34_block_height(), Ok(15));
// Block 42 on Testnet4 has height of 0x012a (42)
const BLOCK_HEX_SMALL_HEIGHT_42: &str = "000000202803addb5a3f42f3e8d6c8536598b2d872b04f3b4f0698c26afdb17300000000463dd9a37a5d3d5c05f9c80a1485b41f1f513dee00338bbc33f5a6e836fce0345dda3866ffff001d872b9def01010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff09012a062f4077697a2fffffffff0200f2052a010000001976a9140a59837ccd4df25adc31cdad39be6a8d97557ed688ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000";
let block: Block = deserialize(&hex!(BLOCK_HEX_SMALL_HEIGHT_42)).unwrap();
let block = block.assume_checked(None);
assert_eq!(block.bip34_block_height(), Ok(42));
// Block 42 on Testnet4 using OP_PUSHDATA1 0x4c012a (42) instead of 0x012a (42)
const BLOCK_HEX_SMALL_HEIGHT_42_WRONG: &str = "000000202803addb5a3f42f3e8d6c8536598b2d872b04f3b4f0698c26afdb17300000000463dd9a37a5d3d5c05f9c80a1485b41f1f513dee00338bbc33f5a6e836fce0345dda3866ffff001d872b9def01010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0a4c012a062f4077697a2fffffffff0200f2052a010000001976a9140a59837ccd4df25adc31cdad39be6a8d97557ed688ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000";
let block: Block = deserialize(&hex!(BLOCK_HEX_SMALL_HEIGHT_42_WRONG)).unwrap();
let block = block.assume_checked(None);
assert_eq!(block.bip34_block_height(), Err(super::Bip34Error::NonMinimalPush));
@ -410,6 +551,7 @@ mod tests {
// this is an overflow for ScriptNum (i32) parsing
const BLOCK_HEX_5_BYTE_HEIGHT: &str = "000000202803addb5a3f42f3e8d6c8536598b2d872b04f3b4f0698c26afdb17300000000463dd9a37a5d3d5c05f9c80a1485b41f1f513dee00338bbc33f5a6e836fce0345dda3866ffff001d872b9def01010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0d052a2a2a2a2a062f4077697a2fffffffff0200f2052a010000001976a9140a59837ccd4df25adc31cdad39be6a8d97557ed688ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000";
let block: Block = deserialize(&hex!(BLOCK_HEX_5_BYTE_HEIGHT)).unwrap();
let block = block.assume_checked(None);
assert_eq!(block.bip34_block_height(), Err(super::Bip34Error::NotPresent));
}
@ -430,32 +572,42 @@ mod tests {
assert!(decode.is_ok());
assert!(bad_decode.is_err());
let real_decode = decode.unwrap();
assert_eq!(real_decode.header.version, Version::from_consensus(1));
assert_eq!(serialize(&real_decode.header.prev_blockhash), prevhash);
assert_eq!(real_decode.header.merkle_root, real_decode.compute_merkle_root().unwrap());
assert_eq!(serialize(&real_decode.header.merkle_root), merkle);
assert_eq!(real_decode.header.time, 1231965655);
assert_eq!(real_decode.header.bits, CompactTarget::from_consensus(486604799));
assert_eq!(real_decode.header.nonce, 2067413810);
assert_eq!(real_decode.header.work(), work);
let (header, transactions) = decode.unwrap().into_parts();
// should be also ok for a non-witness block as commitment is optional in that case
let (witness_commitment_matches, witness_root) =
block::check_witness_commitment(&transactions);
assert!(witness_commitment_matches);
let real_decode =
Block::new_unchecked(header, transactions.clone()).assume_checked(witness_root);
assert_eq!(real_decode.header().version, Version::from_consensus(1));
assert_eq!(serialize(&real_decode.header().prev_blockhash), prevhash);
assert_eq!(
real_decode.header.validate_pow(real_decode.header.target()).unwrap(),
real_decode.header().merkle_root,
block::compute_merkle_root(&transactions).unwrap()
);
assert_eq!(serialize(&real_decode.header().merkle_root), merkle);
assert_eq!(real_decode.header().time, 1231965655);
assert_eq!(real_decode.header().bits, CompactTarget::from_consensus(486604799));
assert_eq!(real_decode.header().nonce, 2067413810);
assert_eq!(real_decode.header().work(), work);
assert_eq!(real_decode.header().difficulty(&params), 1);
assert_eq!(real_decode.header().difficulty_float(&params), 1.0);
assert_eq!(
real_decode.header().validate_pow(real_decode.header().target()).unwrap(),
real_decode.block_hash()
);
assert_eq!(real_decode.header.difficulty(&params), 1);
assert_eq!(real_decode.header.difficulty_float(&params), 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.transactions()), some_block.len());
assert_eq!(
real_decode.weight(),
Weight::from_non_witness_data_size(some_block.len().to_u64())
);
// should be also ok for a non-witness block as commitment is optional in that case
assert!(real_decode.check_witness_commitment());
assert_eq!(serialize(&real_decode), some_block);
}
@ -472,28 +624,37 @@ mod tests {
let work = u64_to_work(0x257c3becdacc64_u64);
assert!(decode.is_ok());
let real_decode = decode.unwrap();
assert_eq!(real_decode.header.version, Version::from_consensus(0x2000_0000)); // VERSIONBITS but no bits set
assert_eq!(serialize(&real_decode.header.prev_blockhash), prevhash);
assert_eq!(serialize(&real_decode.header.merkle_root), merkle);
assert_eq!(real_decode.header.merkle_root, real_decode.compute_merkle_root().unwrap());
assert_eq!(real_decode.header.time, 1472004949);
assert_eq!(real_decode.header.bits, CompactTarget::from_consensus(0x1a06d450));
assert_eq!(real_decode.header.nonce, 1879759182);
assert_eq!(real_decode.header.work(), work);
let (header, transactions) = decode.unwrap().into_parts();
let (witness_commitment_matches, witness_root) =
block::check_witness_commitment(&transactions);
assert!(witness_commitment_matches);
let real_decode =
Block::new_unchecked(header, transactions.clone()).assume_checked(witness_root);
assert_eq!(real_decode.header().version, Version::from_consensus(0x2000_0000)); // VERSIONBITS but no bits set
assert_eq!(serialize(&real_decode.header().prev_blockhash), prevhash);
assert_eq!(serialize(&real_decode.header().merkle_root), merkle);
assert_eq!(
real_decode.header.validate_pow(real_decode.header.target()).unwrap(),
real_decode.header().merkle_root,
block::compute_merkle_root(&transactions).unwrap()
);
assert_eq!(real_decode.header().time, 1472004949);
assert_eq!(real_decode.header().bits, CompactTarget::from_consensus(0x1a06d450));
assert_eq!(real_decode.header().nonce, 1879759182);
assert_eq!(real_decode.header().work(), work);
assert_eq!(real_decode.header().difficulty(&params), 2456598);
assert_eq!(real_decode.header().difficulty_float(&params), 2456598.4399242126);
assert_eq!(
real_decode.header().validate_pow(real_decode.header().target()).unwrap(),
real_decode.block_hash()
);
assert_eq!(real_decode.header.difficulty(&params), 2456598);
assert_eq!(real_decode.header.difficulty_float(&params), 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.transactions()), 4283);
assert_eq!(real_decode.weight(), Weight::from_wu(17168));
assert!(real_decode.check_witness_commitment());
assert_eq!(serialize(&real_decode), segwit_block);
}
@ -502,14 +663,15 @@ mod tests {
let block = hex!("ffffff7f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
let decode: Result<Block, _> = deserialize(&block);
assert!(decode.is_ok());
let real_decode = decode.unwrap();
assert_eq!(real_decode.header.version, Version::from_consensus(2147483647));
let real_decode = decode.unwrap().assume_checked(None);
assert_eq!(real_decode.header().version, Version::from_consensus(2147483647));
let block2 = hex!("000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
let decode2: Result<Block, _> = deserialize(&block2);
assert!(decode2.is_ok());
let real_decode2 = decode2.unwrap();
assert_eq!(real_decode2.header.version, Version::from_consensus(-2147483648));
let real_decode2 = decode2.unwrap().assume_checked(None);
assert_eq!(real_decode2.header().version, Version::from_consensus(-2147483648));
}
#[test]

View File

@ -8,7 +8,7 @@
use hashes::sha256d;
use crate::block::{self, Block};
use crate::block::{self, Block, Checked};
use crate::internal_macros::{impl_array_newtype, impl_array_newtype_stringify};
use crate::locktime::absolute;
use crate::network::{Network, Params};
@ -119,15 +119,16 @@ fn bitcoin_genesis_tx(params: &Params) -> Transaction {
}
/// Constructs and returns the genesis block.
pub fn genesis_block(params: impl AsRef<Params>) -> Block {
pub fn genesis_block(params: impl AsRef<Params>) -> Block<Checked> {
let params = params.as_ref();
let txdata = vec![bitcoin_genesis_tx(params)];
let hash: sha256d::Hash = txdata[0].compute_txid().into();
let transactions = vec![bitcoin_genesis_tx(params)];
let hash: sha256d::Hash = transactions[0].compute_txid().into();
let merkle_root: crate::TxMerkleNode = hash.into();
let witness_root = block::compute_witness_root(&transactions);
match params.network {
Network::Bitcoin => Block {
header: block::Header {
Network::Bitcoin => Block::new_unchecked(
block::Header {
version: block::Version::ONE,
prev_blockhash: BlockHash::GENESIS_PREVIOUS_BLOCK_HASH,
merkle_root,
@ -135,10 +136,11 @@ pub fn genesis_block(params: impl AsRef<Params>) -> Block {
bits: CompactTarget::from_consensus(0x1d00ffff),
nonce: 2083236893,
},
txdata,
},
Network::Testnet(TestnetVersion::V3) => Block {
header: block::Header {
transactions,
)
.assume_checked(witness_root),
Network::Testnet(TestnetVersion::V3) => Block::new_unchecked(
block::Header {
version: block::Version::ONE,
prev_blockhash: BlockHash::GENESIS_PREVIOUS_BLOCK_HASH,
merkle_root,
@ -146,10 +148,11 @@ pub fn genesis_block(params: impl AsRef<Params>) -> Block {
bits: CompactTarget::from_consensus(0x1d00ffff),
nonce: 414098458,
},
txdata,
},
Network::Testnet(TestnetVersion::V4) => Block {
header: block::Header {
transactions,
)
.assume_checked(witness_root),
Network::Testnet(TestnetVersion::V4) => Block::new_unchecked(
block::Header {
version: block::Version::ONE,
prev_blockhash: BlockHash::GENESIS_PREVIOUS_BLOCK_HASH,
merkle_root,
@ -157,10 +160,11 @@ pub fn genesis_block(params: impl AsRef<Params>) -> Block {
bits: CompactTarget::from_consensus(0x1d00ffff),
nonce: 393743547,
},
txdata,
},
Network::Signet => Block {
header: block::Header {
transactions,
)
.assume_checked(witness_root),
Network::Signet => Block::new_unchecked(
block::Header {
version: block::Version::ONE,
prev_blockhash: BlockHash::GENESIS_PREVIOUS_BLOCK_HASH,
merkle_root,
@ -168,10 +172,11 @@ pub fn genesis_block(params: impl AsRef<Params>) -> Block {
bits: CompactTarget::from_consensus(0x1e0377ae),
nonce: 52613770,
},
txdata,
},
Network::Regtest => Block {
header: block::Header {
transactions,
)
.assume_checked(witness_root),
Network::Regtest => Block::new_unchecked(
block::Header {
version: block::Version::ONE,
prev_blockhash: BlockHash::GENESIS_PREVIOUS_BLOCK_HASH,
merkle_root,
@ -179,8 +184,9 @@ pub fn genesis_block(params: impl AsRef<Params>) -> Block {
bits: CompactTarget::from_consensus(0x207fffff),
nonce: 2,
},
txdata,
},
transactions,
)
.assume_checked(witness_root),
}
}
@ -307,18 +313,18 @@ mod test {
fn bitcoin_genesis_full_block() {
let gen = genesis_block(&params::MAINNET);
assert_eq!(gen.header.version, block::Version::ONE);
assert_eq!(gen.header.prev_blockhash, BlockHash::GENESIS_PREVIOUS_BLOCK_HASH);
assert_eq!(gen.header().version, block::Version::ONE);
assert_eq!(gen.header().prev_blockhash, BlockHash::GENESIS_PREVIOUS_BLOCK_HASH);
assert_eq!(
gen.header.merkle_root.to_string(),
gen.header().merkle_root.to_string(),
"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
);
assert_eq!(gen.header.time, 1231006505);
assert_eq!(gen.header.bits, CompactTarget::from_consensus(0x1d00ffff));
assert_eq!(gen.header.nonce, 2083236893);
assert_eq!(gen.header().time, 1231006505);
assert_eq!(gen.header().bits, CompactTarget::from_consensus(0x1d00ffff));
assert_eq!(gen.header().nonce, 2083236893);
assert_eq!(
gen.header.block_hash().to_string(),
gen.header().block_hash().to_string(),
"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
);
}
@ -326,17 +332,17 @@ mod test {
#[test]
fn testnet_genesis_full_block() {
let gen = genesis_block(&params::TESTNET3);
assert_eq!(gen.header.version, block::Version::ONE);
assert_eq!(gen.header.prev_blockhash, BlockHash::GENESIS_PREVIOUS_BLOCK_HASH);
assert_eq!(gen.header().version, block::Version::ONE);
assert_eq!(gen.header().prev_blockhash, BlockHash::GENESIS_PREVIOUS_BLOCK_HASH);
assert_eq!(
gen.header.merkle_root.to_string(),
gen.header().merkle_root.to_string(),
"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
);
assert_eq!(gen.header.time, 1296688602);
assert_eq!(gen.header.bits, CompactTarget::from_consensus(0x1d00ffff));
assert_eq!(gen.header.nonce, 414098458);
assert_eq!(gen.header().time, 1296688602);
assert_eq!(gen.header().bits, CompactTarget::from_consensus(0x1d00ffff));
assert_eq!(gen.header().nonce, 414098458);
assert_eq!(
gen.header.block_hash().to_string(),
gen.header().block_hash().to_string(),
"000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
);
}
@ -344,17 +350,17 @@ mod test {
#[test]
fn signet_genesis_full_block() {
let gen = genesis_block(&params::SIGNET);
assert_eq!(gen.header.version, block::Version::ONE);
assert_eq!(gen.header.prev_blockhash, BlockHash::GENESIS_PREVIOUS_BLOCK_HASH);
assert_eq!(gen.header().version, block::Version::ONE);
assert_eq!(gen.header().prev_blockhash, BlockHash::GENESIS_PREVIOUS_BLOCK_HASH);
assert_eq!(
gen.header.merkle_root.to_string(),
gen.header().merkle_root.to_string(),
"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
);
assert_eq!(gen.header.time, 1598918400);
assert_eq!(gen.header.bits, CompactTarget::from_consensus(0x1e0377ae));
assert_eq!(gen.header.nonce, 52613770);
assert_eq!(gen.header().time, 1598918400);
assert_eq!(gen.header().bits, CompactTarget::from_consensus(0x1e0377ae));
assert_eq!(gen.header().nonce, 52613770);
assert_eq!(
gen.header.block_hash().to_string(),
gen.header().block_hash().to_string(),
"00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6"
);
}

View File

@ -1263,7 +1263,7 @@ mod tests {
use crate::network::Network;
let genesis = constants::genesis_block(Network::Bitcoin);
assert!(genesis.txdata[0].is_coinbase());
assert!(genesis.transactions()[0].is_coinbase());
let tx_bytes = hex!("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000");
let tx: Transaction = deserialize(&tx_bytes).unwrap();
assert!(!tx.is_coinbase());

View File

@ -118,7 +118,10 @@ pub mod taproot;
// that it is available in the event that we add some functionality there.
#[doc(inline)]
pub use primitives::{
block::{BlockHash, Header as BlockHeader, WitnessCommitment},
block::{
Block, BlockHash, Checked as BlockChecked, Header as BlockHeader,
Unchecked as BockUnchecked, Validation as BlockValidation, WitnessCommitment,
},
merkle_tree::{TxMerkleNode, WitnessMerkleNode},
opcodes::Opcode,
pow::CompactTarget, // No `pow` module outside of `primitives`.
@ -157,7 +160,6 @@ pub use crate::{
#[doc(inline)]
pub use crate::{
// Also, re-export types and modules from `blockdata` that don't come from `primitives`.
blockdata::block::Block, // TODO: Move this after `Block` is in primitives.
blockdata::locktime::{absolute, relative},
blockdata::script::witness_program::{self, WitnessProgram},
blockdata::script::witness_version::{self, WitnessVersion},

View File

@ -15,7 +15,7 @@ use internals::ToU64 as _;
use io::{BufRead, Write};
use self::MerkleBlockError::*;
use crate::block::{self, Block};
use crate::block::{self, Block, Checked};
use crate::consensus::encode::{self, Decodable, Encodable, ReadExt, WriteExt, MAX_VEC_SIZE};
use crate::merkle_tree::{MerkleNode as _, TxMerkleNode};
use crate::prelude::Vec;
@ -43,9 +43,9 @@ impl MerkleBlock {
/// # Examples
///
/// ```rust
/// use bitcoin::hash_types::Txid;
/// use bitcoin::block::BlockUncheckedExt as _;
/// use bitcoin::hex::FromHex;
/// use bitcoin::{Block, MerkleBlock};
/// use bitcoin::{Block, MerkleBlock, Txid};
///
/// // Block 80000
/// let block_bytes = Vec::from_hex("01000000ba8b9cda965dd8e536670f9ddec10e53aab14b20bacad2\
@ -59,6 +59,7 @@ impl MerkleBlock {
/// 5d6cc8d25c6b241501ffffffff0100f2052a010000001976a914404371705fa9bd789a2fcd52d2c580b6\
/// 5d35549d88ac00000000").unwrap();
/// let block: Block = bitcoin::consensus::deserialize(&block_bytes).unwrap();
/// let block = block.validate().expect("valid block");
///
/// // Constructs a new Merkle block containing a single transaction
/// let txid = "5a4ebf66822b0b2d56bd9dc64ece0bc38ee7844a23ff1d7320a88c5fdb2ad3e2".parse::<Txid>().unwrap();
@ -71,12 +72,13 @@ impl MerkleBlock {
/// assert!(mb.extract_matches(&mut matches, &mut index).is_ok());
/// assert_eq!(txid, matches[0]);
/// ```
pub fn from_block_with_predicate<F>(block: &Block, match_txids: F) -> Self
pub fn from_block_with_predicate<F>(block: &Block<Checked>, match_txids: F) -> Self
where
F: Fn(&Txid) -> bool,
{
let block_txids: Vec<_> = block.txdata.iter().map(Transaction::compute_txid).collect();
Self::from_header_txids_with_predicate(&block.header, &block_txids, match_txids)
let block_txids: Vec<_> =
block.transactions().iter().map(Transaction::compute_txid).collect();
Self::from_header_txids_with_predicate(block.header(), &block_txids, match_txids)
}
/// Constructs a new MerkleBlock from the block's header and txids, that contain proofs for specific txids.
@ -511,6 +513,7 @@ mod tests {
use {core::cmp, secp256k1::rand::prelude::*};
use super::*;
use crate::block::{BlockUncheckedExt as _, Unchecked};
use crate::consensus::encode;
use crate::hash_types::Txid;
use crate::hex::{test_hex_unwrap as hex, DisplayHex, FromHex};
@ -673,14 +676,14 @@ mod tests {
let merkle_block = MerkleBlock::from_block_with_predicate(&block, |t| txids.contains(t));
assert_eq!(merkle_block.header.block_hash(), block.block_hash());
assert_eq!(merkle_block.header.block_hash(), block.clone().block_hash());
let mut matches: Vec<Txid> = vec![];
let mut index: Vec<u32> = vec![];
assert_eq!(
merkle_block.txn.extract_matches(&mut matches, &mut index).unwrap(),
block.header.merkle_root
block.header().merkle_root
);
assert_eq!(matches.len(), 2);
@ -703,14 +706,14 @@ mod tests {
let merkle_block = MerkleBlock::from_block_with_predicate(&block, |t| txids.contains(t));
assert_eq!(merkle_block.header.block_hash(), block.block_hash());
assert_eq!(merkle_block.header.block_hash(), block.clone().block_hash());
let mut matches: Vec<Txid> = vec![];
let mut index: Vec<u32> = vec![];
assert_eq!(
merkle_block.txn.extract_matches(&mut matches, &mut index).unwrap(),
block.header.merkle_root
block.header().merkle_root
);
assert_eq!(matches.len(), 0);
assert_eq!(index.len(), 0);
@ -731,9 +734,11 @@ mod tests {
/// Returns a real block (0000000000013b8ab2cd513b0261a14096412195a72a0c4827d229dcc7e0f7af)
/// with 9 txs.
fn get_block_13b8a() -> Block {
fn get_block_13b8a() -> Block<Checked> {
let block_hex = include_str!("../../tests/data/block_13b8a.hex");
encode::deserialize(&Vec::from_hex(block_hex).unwrap()).unwrap()
let block: Block<Unchecked> =
encode::deserialize(&Vec::from_hex(block_hex).unwrap()).unwrap();
block.validate().expect("block should be valid")
}
macro_rules! check_calc_tree_width {

View File

@ -113,24 +113,3 @@ impl MerkleNode for WitnessMerkleNode {
Self::from_byte_array(sha256d::Hash::from_engine(encoder).to_byte_array())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::block::Block;
use crate::consensus::encode::deserialize;
#[test]
fn static_vector() {
// testnet block 000000000000045e0b1660b6445b5e5c5ab63c9a4f956be7e1e69be04fa4497b
let segwit_block = include_bytes!("../../tests/data/testnet_block_000000000000045e0b1660b6445b5e5c5ab63c9a4f956be7e1e69be04fa4497b.raw");
let block: Block = deserialize(&segwit_block[..]).expect("failed to deserialize block");
assert!(block.check_merkle_root());
// Same as `block.check_merkle_root` but do it explicitly.
let hashes_iter = block.txdata.iter().map(|obj| obj.compute_txid());
let from_iter = TxMerkleNode::calculate_root(hashes_iter.clone());
assert_eq!(from_iter, Some(block.header.merkle_root));
}
}

View File

@ -1784,7 +1784,7 @@ mod tests {
use crate::constants::genesis_block;
use crate::TxMerkleNode;
let params = Params::new(crate::Network::Signet);
let epoch_start = genesis_block(&params).header;
let epoch_start = *genesis_block(&params).header();
// Block 2015, the only information used are `bits` and `time`
let current = Header {

View File

@ -1,3 +1,4 @@
use bitcoin::block::{self, Block, BlockCheckedExt as _};
use honggfuzz::fuzz;
fn do_test(data: &[u8]) {
@ -9,12 +10,18 @@ fn do_test(data: &[u8]) {
Ok(block) => {
let ser = bitcoin::consensus::encode::serialize(&block);
assert_eq!(&ser[..], data);
// Manually call all compute functions with unchecked block data.
let (header, transactions) = block.into_parts();
block::compute_merkle_root(&transactions);
block::compute_witness_commitment(&transactions, &[]); // TODO: Is empty slice ok?
block::compute_witness_root(&transactions);
if let Ok(block) = Block::new_checked(header, transactions) {
let _ = block.bip34_block_height();
block.block_hash();
block.check_merkle_root();
block.check_witness_commitment();
block.weight();
block.witness_root();
}
}
}
}

View File

@ -8,13 +8,146 @@
//! these blocks and the blockchain.
use core::fmt;
#[cfg(feature = "alloc")]
use core::marker::PhantomData;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
use hashes::{sha256d, HashEngine as _};
use crate::merkle_tree::TxMerkleNode;
#[cfg(feature = "alloc")]
use crate::merkle_tree::WitnessMerkleNode;
use crate::pow::CompactTarget;
#[cfg(feature = "alloc")]
use crate::prelude::Vec;
#[cfg(feature = "alloc")]
use crate::transaction::Transaction;
/// Marker for whether or not a block has been validated.
///
/// We define valid as:
///
/// * The Merkle root of the header matches Merkle root of the transaction list.
/// * The witness commitment in coinbase matches the transaction list.
///
/// See `bitcoin::block::BlockUncheckedExt::validate()`.
#[cfg(feature = "alloc")]
pub trait Validation: sealed::Validation + Sync + Send + Sized + Unpin {
/// Indicates whether this `Validation` is `Checked` or not.
const IS_CHECKED: bool;
}
/// Bitcoin block.
///
/// A collection of transactions with an attached proof of work.
///
/// See [Bitcoin Wiki: Block][wiki-block] for more information.
///
/// [wiki-block]: https://en.bitcoin.it/wiki/Block
///
/// ### Bitcoin Core References
///
/// * [CBlock definition](https://github.com/bitcoin/bitcoin/blob/345457b542b6a980ccfbc868af0970a6f91d1b82/src/primitives/block.h#L62)
#[cfg(feature = "alloc")]
#[derive(PartialEq, Eq, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Block<V = Unchecked>
where
V: Validation,
{
/// The block header
header: Header,
/// List of transactions contained in the block
transactions: Vec<Transaction>,
/// Cached witness root if its been computed.
#[cfg_attr(feature = "serde", serde(skip_serializing))]
witness_root: Option<WitnessMerkleNode>,
/// Validation marker.
marker: PhantomData<V>,
}
#[cfg(feature = "alloc")]
impl Block<Unchecked> {
/// Constructs a new `Block` without doing any validation.
pub fn new_unchecked(header: Header, transactions: Vec<Transaction>) -> Block<Unchecked> {
Block { header, transactions, witness_root: None, marker: PhantomData::<Unchecked> }
}
/// Ignores block validation logic and just assumes you know what you are doing.
///
/// You should only use this function if you trust the block i.e., it comes from a trusted node.
pub fn assume_checked(self, witness_root: Option<WitnessMerkleNode>) -> Block<Checked> {
Block {
header: self.header,
transactions: self.transactions,
witness_root,
marker: PhantomData::<Checked>,
}
}
/// Decomposes block into its constituent parts.
pub fn into_parts(self) -> (Header, Vec<Transaction>) { (self.header, self.transactions) }
}
#[cfg(feature = "alloc")]
impl Block<Checked> {
/// Gets a reference to the block header.
pub fn header(&self) -> &Header { &self.header }
/// Gets a reference to the block's list of transactions.
pub fn transactions(&self) -> &[Transaction] { &self.transactions }
/// Returns the cached witness root if one is present.
///
/// It is assumed that a block will have the witness root calculated and cached as part of the
/// validation process.
pub fn cached_witness_root(&self) -> Option<WitnessMerkleNode> { self.witness_root }
}
#[cfg(feature = "alloc")]
impl<V: Validation> Block<V> {
/// Returns the block hash.
pub fn block_hash(&self) -> BlockHash { self.header.block_hash() }
}
#[cfg(feature = "alloc")]
impl From<Block> for BlockHash {
fn from(block: Block) -> BlockHash { block.block_hash() }
}
#[cfg(feature = "alloc")]
impl From<&Block> for BlockHash {
fn from(block: &Block) -> BlockHash { block.block_hash() }
}
/// Marker that the block's merkle root has been successfully validated.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg(feature = "alloc")]
pub enum Checked {}
#[cfg(feature = "alloc")]
impl Validation for Checked {
const IS_CHECKED: bool = true;
}
/// Marker that the block's merkle root has not been validated.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg(feature = "alloc")]
pub enum Unchecked {}
#[cfg(feature = "alloc")]
impl Validation for Unchecked {
const IS_CHECKED: bool = false;
}
#[cfg(feature = "alloc")]
mod sealed {
/// Seals the block validation marker traits.
pub trait Validation {}
impl Validation for super::Checked {}
impl Validation for super::Unchecked {}
}
/// Bitcoin block header.
///
@ -167,6 +300,16 @@ impl BlockHash {
pub const GENESIS_PREVIOUS_BLOCK_HASH: Self = Self::from_byte_array([0; 32]);
}
#[cfg(feature = "arbitrary")]
#[cfg(feature = "alloc")]
impl<'a> Arbitrary<'a> for Block {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let header = Header::arbitrary(u)?;
let transactions = Vec::<Transaction>::arbitrary(u)?;
Ok(Block::new_unchecked(header, transactions))
}
}
#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for Header {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {

View File

@ -53,6 +53,16 @@ pub use units::{
weight::{self, Weight},
};
#[doc(inline)]
#[cfg(feature = "alloc")]
pub use self::{
block::{
Block, Checked as BlockChecked, Unchecked as BlockUnchecked, Validation as BlockValidation,
},
locktime::{absolute, relative},
transaction::{Transaction, TxIn, TxOut},
witness::Witness,
};
#[doc(inline)]
pub use self::{
block::{BlockHash, Header as BlockHeader, WitnessCommitment},
@ -62,13 +72,6 @@ pub use self::{
taproot::{TapBranchTag, TapLeafHash, TapLeafTag, TapNodeHash, TapTweakHash, TapTweakTag},
transaction::{Txid, Wtxid},
};
#[doc(inline)]
#[cfg(feature = "alloc")]
pub use self::{
locktime::{absolute, relative},
transaction::{Transaction, TxIn, TxOut},
witness::Witness,
};
#[rustfmt::skip]
#[allow(unused_imports)]