use core::convert::Infallible;
use core::fmt;
#[cfg(feature = "alloc")]
use core::marker::PhantomData;
#[cfg(feature = "alloc")]
use core::mem;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
use encoding::{ArrayDecoder, Decoder6, Encodable as _, Encoder as _};
#[cfg(feature = "alloc")]
use encoding::{CompactSizeEncoder, Decoder2, Encoder2, SliceEncoder, VecDecoder};
use hashes::{sha256d, HashEngine as _};
use internals::write_err;
#[cfg(feature = "alloc")]
use internals::ToU64 as _;
#[cfg(feature = "hex")]
use crate::hex_codec::{HexPrimitive, ParsePrimitiveError};
#[cfg(feature = "alloc")]
use crate::merkle_tree::WitnessMerkleNode;
use crate::merkle_tree::{TxMerkleNode, TxMerkleNodeDecoder, TxMerkleNodeDecoderError};
use crate::pow::{CompactTargetDecoder, CompactTargetDecoderError};
#[cfg(feature = "alloc")]
use crate::prelude::{Box, Vec};
#[cfg(feature = "alloc")]
use crate::script::{ScriptPubKeyBuf, ScriptSigBuf};
use crate::time::{BlockTimeDecoder, BlockTimeDecoderError};
#[cfg(feature = "alloc")]
use crate::transaction::{check_transaction_sanity, TransactionSanityError};
#[cfg(feature = "alloc")]
use crate::{Amount, Transaction, TxIn, TxOut, Weight, Wtxid};
use crate::{BlockTime, CompactTarget};
#[rustfmt::skip] #[doc(inline)]
pub use units::block::{error, BlockHeight, BlockHeightDecoder, BlockHeightEncoder, BlockHeightInterval, BlockMtp, BlockMtpInterval};
#[doc(no_inline)]
pub use units::block::{BlockHeightDecoderError, TooBigForRelativeHeightError};
#[doc(inline)]
pub use crate::hash_types::{
BlockHash, BlockHashDecoder, BlockHashDecoderError, BlockHashEncoder, WitnessCommitment,
};
#[cfg(feature = "alloc")]
pub const MAX_BLOCK_SIGOPS_COST: usize = 80_000;
#[cfg(feature = "alloc")]
pub trait Validation: sealed::Validation + Sync + Send + Sized + Unpin {
const IS_CHECKED: bool;
}
#[cfg(feature = "alloc")]
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Block<V = Unchecked>
where
V: Validation,
{
header: Header,
transactions: Vec<Transaction>,
witness_root: Option<WitnessMerkleNode>,
_marker: PhantomData<V>,
}
#[cfg(feature = "alloc")]
impl Block<Unchecked> {
#[inline]
pub fn new_unchecked(header: Header, transactions: Vec<Transaction>) -> Self {
Self { header, transactions, witness_root: None, _marker: PhantomData::<Unchecked> }
}
#[must_use]
#[inline]
pub fn assume_checked(self, witness_root: Option<WitnessMerkleNode>) -> Block<Checked> {
Block {
header: self.header,
transactions: self.transactions,
witness_root,
_marker: PhantomData::<Checked>,
}
}
#[inline]
pub fn into_parts(self) -> (Header, Vec<Transaction>) {
(self.header, self.transactions)
}
#[inline]
pub fn as_parts(&self) -> (&Header, &[Transaction]) {
(&self.header, &self.transactions)
}
pub fn validate(self) -> Result<Block<Checked>, InvalidBlockError> {
check_block_sanity_inner(&self).map_err(InvalidBlockError::from)?;
let witness_root =
check_block_witness_and_weight(&self).map_err(InvalidBlockError::from)?;
let block = Self::new_unchecked(self.header, self.transactions);
Ok(block.assume_checked(witness_root))
}
pub fn check_merkle_root(&self) -> bool {
match compute_merkle_root(&self.transactions) {
Some(merkle_root) => self.header.merkle_root == merkle_root,
None => false,
}
}
pub fn compute_witness_commitment(
&self,
witness_reserved_value: &[u8],
) -> Option<(WitnessMerkleNode, WitnessCommitment)> {
compute_witness_root(&self.transactions).map(|witness_root| {
let mut encoder = sha256d::Hash::engine();
encoder = hashes::encode_to_engine(&witness_root, encoder);
encoder.input(witness_reserved_value);
let witness_commitment = WitnessCommitment::from_byte_array(
sha256d::Hash::from_engine(encoder).to_byte_array(),
);
(witness_root, witness_commitment)
})
}
pub fn check_witness_commitment(&self) -> (bool, Option<WitnessMerkleNode>) {
if self.transactions.is_empty() {
return (false, None);
}
if self.transactions.iter().all(|t| t.inputs.iter().all(|i| i.witness.is_empty())) {
return (true, None);
}
if self.transactions[0].is_coinbase() {
let coinbase = self.transactions[0].clone();
if let Some(commitment) = witness_commitment_from_coinbase(&coinbase) {
let witness_vec: Vec<_> = coinbase.inputs[0].witness.iter().collect();
if witness_vec.len() == 1 && witness_vec[0].len() == 32 {
if let Some((witness_root, witness_commitment)) =
self.compute_witness_commitment(witness_vec[0])
{
if commitment == witness_commitment {
return (true, Some(witness_root));
}
}
}
}
}
(false, None)
}
}
#[cfg(feature = "alloc")]
pub fn check_block_sanity(block: &Block<Unchecked>) -> Result<(), BlockSanityError> {
check_block_sanity_inner(block)
}
#[cfg(feature = "alloc")]
fn check_block_sanity_inner(block: &Block<Unchecked>) -> Result<(), BlockSanityError> {
if block.transactions.is_empty() {
return Err(BlockSanityError::NoTransactions);
}
if block.transactions.len().to_u64().saturating_mul(Weight::WITNESS_SCALE_FACTOR)
> Weight::MAX_BLOCK.to_wu()
{
return Err(BlockSanityError::SizeLimits);
}
if block.base_size().to_u64().saturating_mul(Weight::WITNESS_SCALE_FACTOR)
> Weight::MAX_BLOCK.to_wu()
{
return Err(BlockSanityError::SizeLimits);
}
if !block.transactions[0].is_coinbase() {
return Err(BlockSanityError::MissingCoinbase);
}
for (index, tx) in block.transactions.iter().enumerate().skip(1) {
if tx.is_coinbase() {
return Err(BlockSanityError::MultipleCoinbase { index });
}
}
match compute_merkle_root(&block.transactions) {
Some(merkle_root) if block.header.merkle_root == merkle_root => {}
Some(_) => return Err(BlockSanityError::InvalidMerkleRoot),
None => return Err(BlockSanityError::MutatedMerkleRoot),
}
for (index, tx) in block.transactions.iter().enumerate() {
check_transaction_sanity(tx)
.map_err(|err| BlockSanityError::Transaction { index, source: err })?;
}
let legacy_sigop_cost = block
.transactions
.iter()
.map(transaction_legacy_sigop_count)
.sum::<usize>()
.saturating_mul(Weight::WITNESS_SCALE_FACTOR as usize);
if legacy_sigop_cost > MAX_BLOCK_SIGOPS_COST {
return Err(BlockSanityError::TooManyLegacySigops { cost: legacy_sigop_cost });
}
Ok(())
}
#[cfg(feature = "alloc")]
pub fn check_block_witness_and_weight(
block: &Block<Unchecked>,
) -> Result<Option<WitnessMerkleNode>, BlockSanityError> {
let (witness_valid, witness_root) = block.check_witness_commitment();
if !witness_valid {
return Err(BlockSanityError::InvalidWitnessCommitment);
}
if block.weight().to_wu() > Weight::MAX_BLOCK.to_wu() {
return Err(BlockSanityError::WeightLimit);
}
Ok(witness_root)
}
#[cfg(feature = "alloc")]
fn transaction_legacy_sigop_count(tx: &Transaction) -> usize {
tx.inputs
.iter()
.map(|input| input.script_sig.count_sigops_legacy())
.sum::<usize>()
.saturating_add(
tx.outputs
.iter()
.map(|output| output.script_pubkey.count_sigops_legacy())
.sum::<usize>(),
)
}
#[cfg(feature = "alloc")]
impl Block<Checked> {
#[inline]
pub fn header(&self) -> &Header {
&self.header
}
#[inline]
pub fn transactions(&self) -> &[Transaction] {
&self.transactions
}
#[inline]
pub fn cached_witness_root(&self) -> Option<WitnessMerkleNode> {
self.witness_root
}
}
#[cfg(feature = "alloc")]
impl<V: Validation> Block<V> {
#[inline]
pub fn block_hash(&self) -> BlockHash {
self.header.block_hash()
}
#[inline]
pub fn weight(&self) -> Weight {
Weight::from_wu((self.base_size() * 3 + self.total_size()).to_u64())
}
#[inline]
pub fn base_size(&self) -> usize {
self.header.serialized_len()
+ CompactSizeEncoder::encoded_size(self.transactions.len())
+ self.transactions.iter().map(Transaction::base_size).sum::<usize>()
}
#[inline]
pub fn total_size(&self) -> usize {
self.header.serialized_len()
+ CompactSizeEncoder::encoded_size(self.transactions.len())
+ self.transactions.iter().map(Transaction::total_size).sum::<usize>()
}
}
#[cfg(feature = "alloc")]
impl From<Block> for BlockHash {
#[inline]
fn from(block: Block) -> Self {
block.block_hash()
}
}
#[cfg(feature = "alloc")]
impl From<&Block> for BlockHash {
#[inline]
fn from(block: &Block) -> Self {
block.block_hash()
}
}
#[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;
}
#[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 {
pub trait Validation {}
impl Validation for super::Checked {}
impl Validation for super::Unchecked {}
}
#[cfg(all(feature = "hex", feature = "alloc"))]
impl core::str::FromStr for Block<Unchecked>
where
Self: encoding::Decodable,
{
type Err = ParseBlockError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
HexPrimitive::from_str(s).map_err(ParseBlockError)
}
}
#[cfg(all(feature = "hex", feature = "alloc"))]
impl<V: Validation> fmt::Display for Block<V>
where
Self: encoding::Encodable,
{
#[allow(clippy::use_self)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&HexPrimitive(self), f)
}
}
#[cfg(all(feature = "hex", feature = "alloc"))]
impl<V: Validation> fmt::LowerHex for Block<V> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::LowerHex::fmt(&HexPrimitive(self), f)
}
}
#[cfg(all(feature = "hex", feature = "alloc"))]
impl<V: Validation> fmt::UpperHex for Block<V> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::UpperHex::fmt(&HexPrimitive(self), f)
}
}
#[cfg(all(feature = "hex", feature = "alloc"))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseBlockError(ParsePrimitiveError<Block>);
#[cfg(all(feature = "hex", feature = "alloc"))]
impl From<Infallible> for ParseBlockError {
fn from(never: Infallible) -> Self {
match never {}
}
}
#[cfg(all(feature = "hex", feature = "alloc"))]
impl fmt::Display for ParseBlockError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write_err!(f, "parse block error"; self.0)
}
}
#[cfg(all(feature = "hex", feature = "alloc", feature = "std"))]
impl std::error::Error for ParseBlockError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.0)
}
}
#[cfg(feature = "alloc")]
encoding::encoder_newtype! {
pub struct BlockEncoder<'e>(
Encoder2<HeaderEncoder<'e>, Encoder2<CompactSizeEncoder, SliceEncoder<'e, Transaction>>>
);
}
#[cfg(feature = "alloc")]
impl<V> encoding::Encodable for Block<V>
where
V: Validation,
{
type Encoder<'e>
= Encoder2<HeaderEncoder<'e>, Encoder2<CompactSizeEncoder, SliceEncoder<'e, Transaction>>>
where
Self: 'e;
fn encoder(&self) -> Self::Encoder<'_> {
Encoder2::new(
self.header.encoder(),
Encoder2::new(
CompactSizeEncoder::new(self.transactions.len()),
SliceEncoder::without_length_prefix(&self.transactions),
),
)
}
}
#[cfg(feature = "alloc")]
type BlockInnerDecoder = Decoder2<HeaderDecoder, VecDecoder<Transaction>>;
#[cfg(feature = "alloc")]
pub struct BlockDecoder(BlockInnerDecoder);
#[cfg(feature = "alloc")]
impl BlockDecoder {
pub const fn new() -> Self {
Self(Decoder2::new(HeaderDecoder::new(), VecDecoder::new()))
}
}
#[cfg(feature = "alloc")]
impl Default for BlockDecoder {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "alloc")]
impl encoding::Decoder for BlockDecoder {
type Output = Block;
type Error = BlockDecoderError;
#[inline]
fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
self.0.push_bytes(bytes).map_err(BlockDecoderError)
}
#[inline]
fn end(self) -> Result<Self::Output, Self::Error> {
let (header, transactions) = self.0.end().map_err(BlockDecoderError)?;
Ok(Self::Output::new_unchecked(header, transactions))
}
#[inline]
fn read_limit(&self) -> usize {
self.0.read_limit()
}
}
#[cfg(feature = "alloc")]
impl encoding::Decodable for Block<Unchecked> {
type Decoder = BlockDecoder;
fn decoder() -> Self::Decoder {
BlockDecoder(Decoder2::new(Header::decoder(), VecDecoder::<Transaction>::new()))
}
}
#[cfg(feature = "alloc")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlockDecoderError(<BlockInnerDecoder as encoding::Decoder>::Error);
#[cfg(feature = "alloc")]
impl From<Infallible> for BlockDecoderError {
fn from(never: Infallible) -> Self {
match never {}
}
}
#[cfg(feature = "alloc")]
impl fmt::Display for BlockDecoderError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write_err!(f, "block decoder error"; self.0)
}
}
#[cfg(feature = "alloc")]
#[cfg(feature = "std")]
impl std::error::Error for BlockDecoderError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.0)
}
}
#[cfg(feature = "alloc")]
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum InvalidBlockError {
InvalidMerkleRoot,
InvalidWitnessCommitment,
NoTransactions,
InvalidCoinbase,
Sanity(BlockSanityError),
}
#[cfg(feature = "alloc")]
impl From<Infallible> for InvalidBlockError {
fn from(never: Infallible) -> Self {
match never {}
}
}
#[cfg(feature = "alloc")]
impl From<BlockSanityError> for InvalidBlockError {
fn from(err: BlockSanityError) -> Self {
match err {
BlockSanityError::InvalidMerkleRoot => Self::InvalidMerkleRoot,
BlockSanityError::InvalidWitnessCommitment => Self::InvalidWitnessCommitment,
BlockSanityError::NoTransactions => Self::NoTransactions,
BlockSanityError::MissingCoinbase => Self::InvalidCoinbase,
other => Self::Sanity(other),
}
}
}
#[cfg(feature = "alloc")]
impl fmt::Display for InvalidBlockError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::InvalidMerkleRoot => {
write!(f, "header Merkle root does not match the calculated Merkle root")
}
Self::InvalidWitnessCommitment => write!(
f,
"the witness commitment in coinbase transaction does not match the calculated witness_root"
),
Self::NoTransactions => write!(f, "block has no transactions (missing coinbase)"),
Self::InvalidCoinbase => {
write!(f, "the first transaction is not a valid coinbase transaction")
}
Self::Sanity(err) => write_err!(f, "block sanity error"; err),
}
}
}
#[cfg(feature = "alloc")]
#[cfg(feature = "std")]
impl std::error::Error for InvalidBlockError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Sanity(err) => Some(err),
_ => None,
}
}
}
#[cfg(feature = "alloc")]
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum BlockSanityError {
InvalidMerkleRoot,
MutatedMerkleRoot,
InvalidWitnessCommitment,
NoTransactions,
MissingCoinbase,
MultipleCoinbase {
index: usize,
},
Transaction {
index: usize,
source: TransactionSanityError,
},
SizeLimits,
WeightLimit,
TooManyLegacySigops {
cost: usize,
},
}
#[cfg(feature = "alloc")]
impl fmt::Display for BlockSanityError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidMerkleRoot => {
write!(f, "header Merkle root does not match the calculated Merkle root")
}
Self::MutatedMerkleRoot => write!(f, "transaction merkle tree is mutated"),
Self::InvalidWitnessCommitment => write!(
f,
"the witness commitment in coinbase transaction does not match the calculated witness_root"
),
Self::NoTransactions => write!(f, "block has no transactions"),
Self::MissingCoinbase => {
write!(f, "the first transaction is not a valid coinbase transaction")
}
Self::MultipleCoinbase { index } => {
write!(f, "non-first transaction {} is coinbase", index)
}
Self::Transaction { index, source } => {
write_err!(f, "transaction sanity failed at index {}", index; source)
}
Self::SizeLimits => write!(f, "block context-free size limits failed"),
Self::WeightLimit => write!(f, "block weight limit failed"),
Self::TooManyLegacySigops { cost } => {
write!(f, "block legacy sigop cost {} exceeds {}", cost, MAX_BLOCK_SIGOPS_COST)
}
}
}
}
#[cfg(all(feature = "alloc", feature = "std"))]
impl std::error::Error for BlockSanityError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Transaction { source, .. } => Some(source),
_ => None,
}
}
}
#[cfg(feature = "alloc")]
pub fn compute_merkle_root(transactions: &[Transaction]) -> Option<TxMerkleNode> {
let hashes = transactions.iter().map(Transaction::compute_txid);
TxMerkleNode::calculate_root(hashes)
}
#[cfg(feature = "alloc")]
pub fn compute_witness_root(transactions: &[Transaction]) -> Option<WitnessMerkleNode> {
let hashes = transactions.iter().enumerate().map(|(i, t)| {
if i == 0 {
Wtxid::COINBASE
} else {
t.compute_wtxid()
}
});
WitnessMerkleNode::calculate_root(hashes)
}
#[cfg(feature = "alloc")]
fn witness_commitment_from_coinbase(coinbase: &Transaction) -> Option<WitnessCommitment> {
const MAGIC: [u8; 6] = [0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed];
if !coinbase.is_coinbase() {
return None;
}
if let Some(pos) = coinbase
.outputs
.iter()
.rposition(|o| o.script_pubkey.len() >= 38 && o.script_pubkey.as_bytes()[0..6] == MAGIC)
{
let bytes =
<[u8; 32]>::try_from(&coinbase.outputs[pos].script_pubkey.as_bytes()[6..38]).unwrap();
Some(WitnessCommitment::from_byte_array(bytes))
} else {
None
}
}
#[cfg(feature = "alloc")]
#[derive(PartialEq, Eq, Clone, Debug, PartialOrd, Ord, Hash)]
pub struct AuxPow {
pub coinbase_tx: Transaction,
pub merkle_branch: Vec<BlockHash>,
pub chain_merkle_branch: Vec<BlockHash>,
pub chain_index: i32,
pub parent_block: Header,
}
#[cfg(feature = "alloc")]
impl AuxPow {
fn consensus_len(&self) -> usize {
let encoder = self.encoder();
encoding::ExactSizeEncoder::len(&encoder)
}
pub fn minimal_for_header(header: &Header) -> Self {
debug_assert!(header.version.is_auxpow());
let mut input_data = header.block_hash().to_byte_array().to_vec();
input_data.reverse();
input_data.push(1);
input_data.extend_from_slice(&[0; 7]);
let mut script_sig = Vec::with_capacity(1 + input_data.len());
script_sig.push(u8::try_from(input_data.len()).expect("merged-mining commitment is small"));
script_sig.extend_from_slice(&input_data);
let coinbase_tx = Transaction {
version: crate::transaction::Version::ONE,
lock_time: crate::absolute::LockTime::ZERO,
inputs: Vec::from([TxIn {
script_sig: ScriptSigBuf::from_bytes(script_sig),
..TxIn::EMPTY_COINBASE
}]),
outputs: Vec::from([TxOut {
amount: Amount::ZERO,
script_pubkey: ScriptPubKeyBuf::new(),
}]),
};
let parent_block = Header {
version: Version::ONE,
prev_blockhash: BlockHash::from_byte_array([0; 32]),
merkle_root: compute_merkle_root(core::slice::from_ref(&coinbase_tx))
.expect("single coinbase transaction has a merkle root"),
time: BlockTime::from_u32(0),
bits: CompactTarget::from_consensus(0),
nonce: 0,
auxpow: None,
};
Self {
coinbase_tx,
merkle_branch: Vec::new(),
chain_merkle_branch: Vec::new(),
chain_index: 0,
parent_block,
}
}
}
fn sha256d_hash_encoder(mut encoder: impl encoding::Encoder) -> sha256d::Hash {
let mut enc = sha256d::Hash::engine();
loop {
enc.input(encoder.current_chunk());
if !encoder.advance() {
break;
}
}
enc.finalize()
}
#[derive(PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
pub struct Header {
pub version: Version,
pub prev_blockhash: BlockHash,
pub merkle_root: TxMerkleNode,
pub time: BlockTime,
pub bits: CompactTarget,
pub nonce: u32,
#[cfg(feature = "alloc")]
pub auxpow: Option<Box<AuxPow>>,
}
impl Header {
pub const SIZE: usize = 4 + 32 + 32 + 4 + 4 + 4;
#[cfg(feature = "alloc")]
pub fn serialized_len(&self) -> usize {
Self::SIZE + self.auxpow.as_ref().map_or(0, |auxpow| auxpow.consensus_len())
}
pub fn block_hash(&self) -> BlockHash {
let bare_hash = sha256d_hash_encoder(self.pure_encoder());
BlockHash::from_byte_array(bare_hash.to_byte_array())
}
pub fn pure_header_bytes(&self) -> [u8; Self::SIZE] {
let mut out = [0u8; Self::SIZE];
let mut offset = 0;
let mut encoder = self.pure_encoder();
loop {
let chunk = encoder.current_chunk();
out[offset..offset + chunk.len()].copy_from_slice(chunk);
offset += chunk.len();
if !encoder.advance() {
break;
}
}
debug_assert_eq!(offset, Self::SIZE);
out
}
fn pure_encoder(&self) -> PureHeaderEncoder<'_> {
PureHeaderEncoder::new(encoding::Encoder6::new(
self.version.encoder(),
self.prev_blockhash.encoder(),
self.merkle_root.encoder(),
self.time.encoder(),
self.bits.encoder(),
encoding::ArrayEncoder::without_length_prefix(self.nonce.to_le_bytes()),
))
}
}
#[cfg(feature = "hex")]
impl core::str::FromStr for Header {
type Err = ParseHeaderError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
HexPrimitive::from_str(s).map_err(ParseHeaderError)
}
}
#[cfg(feature = "hex")]
impl fmt::Display for Header {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&HexPrimitive(self), f)
}
}
#[cfg(feature = "hex")]
impl fmt::LowerHex for Header {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::LowerHex::fmt(&HexPrimitive(self), f)
}
}
#[cfg(feature = "hex")]
impl fmt::UpperHex for Header {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::UpperHex::fmt(&HexPrimitive(self), f)
}
}
impl fmt::Debug for Header {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut dbg = f.debug_struct("Header");
dbg.field("block_hash", &self.block_hash())
.field("version", &self.version)
.field("prev_blockhash", &self.prev_blockhash)
.field("merkle_root", &self.merkle_root)
.field("time", &self.time)
.field("bits", &self.bits)
.field("nonce", &self.nonce);
#[cfg(feature = "alloc")]
dbg.field("auxpow", &self.auxpow);
dbg.finish()
}
}
#[cfg(feature = "hex")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseHeaderError(ParsePrimitiveError<Header>);
#[cfg(feature = "hex")]
impl From<Infallible> for ParseHeaderError {
fn from(never: Infallible) -> Self {
match never {}
}
}
#[cfg(feature = "hex")]
impl fmt::Display for ParseHeaderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write_err!(f, "parse header error"; self.0)
}
}
#[cfg(all(feature = "hex", feature = "std"))]
impl std::error::Error for ParseHeaderError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.0)
}
}
encoding::encoder_newtype_exact! {
pub struct PureHeaderEncoder<'e>(
encoding::Encoder6<
VersionEncoder<'e>,
BlockHashEncoder<'e>,
crate::merkle_tree::TxMerkleNodeEncoder<'e>,
crate::time::BlockTimeEncoder<'e>,
crate::pow::CompactTargetEncoder<'e>,
encoding::ArrayEncoder<4>,
>
);
}
#[cfg(feature = "alloc")]
type MerkleBranchEncoder<'e> = Encoder2<CompactSizeEncoder, SliceEncoder<'e, BlockHash>>;
#[cfg(feature = "alloc")]
type AuxPowEncoderInner<'e> = encoding::Encoder6<
crate::transaction::TransactionEncoder<'e>,
encoding::ArrayEncoder<32>,
MerkleBranchEncoder<'e>,
encoding::ArrayEncoder<4>,
MerkleBranchEncoder<'e>,
Encoder2<encoding::ArrayEncoder<4>, PureHeaderEncoder<'e>>,
>;
#[cfg(feature = "alloc")]
encoding::encoder_newtype_exact! {
struct AuxPowLayoutEncoder<'e>(AuxPowEncoderInner<'e>);
}
#[cfg(feature = "alloc")]
fn auxpow_layout_encoder(auxpow: &AuxPow) -> AuxPowLayoutEncoder<'_> {
let merkle_branch = Encoder2::new(
CompactSizeEncoder::new(auxpow.merkle_branch.len()),
SliceEncoder::without_length_prefix(&auxpow.merkle_branch),
);
let chain_merkle_branch = Encoder2::new(
CompactSizeEncoder::new(auxpow.chain_merkle_branch.len()),
SliceEncoder::without_length_prefix(&auxpow.chain_merkle_branch),
);
AuxPowLayoutEncoder::new(encoding::Encoder6::new(
auxpow.coinbase_tx.encoder(),
encoding::ArrayEncoder::without_length_prefix([0; 32]),
merkle_branch,
encoding::ArrayEncoder::without_length_prefix(0_i32.to_le_bytes()),
chain_merkle_branch,
Encoder2::new(
encoding::ArrayEncoder::without_length_prefix(auxpow.chain_index.to_le_bytes()),
auxpow.parent_block.pure_encoder(),
),
))
}
#[cfg(feature = "alloc")]
encoding::encoder_newtype_exact! {
pub struct AuxPowEncoder<'e>(AuxPowLayoutEncoder<'e>);
}
#[cfg(feature = "alloc")]
impl encoding::Encodable for AuxPow {
type Encoder<'e>
= AuxPowEncoder<'e>
where
Self: 'e;
fn encoder(&self) -> Self::Encoder<'_> {
AuxPowEncoder::new(auxpow_layout_encoder(self))
}
}
#[cfg(feature = "alloc")]
pub struct HeaderEncoder<'e> {
pure: PureHeaderEncoder<'e>,
auxpow: Option<AuxPowEncoder<'e>>,
pure_done: bool,
}
#[cfg(not(feature = "alloc"))]
pub use PureHeaderEncoder as HeaderEncoder;
#[cfg(feature = "alloc")]
impl<'e> HeaderEncoder<'e> {
fn new(header: &'e Header) -> Self {
Self {
pure: header.pure_encoder(),
auxpow: header.auxpow.as_deref().map(encoding::Encodable::encoder),
pure_done: false,
}
}
}
#[cfg(feature = "alloc")]
impl encoding::Encoder for HeaderEncoder<'_> {
fn current_chunk(&self) -> &[u8] {
if self.pure_done {
self.auxpow.as_ref().map_or(&[], encoding::Encoder::current_chunk)
} else {
self.pure.current_chunk()
}
}
fn advance(&mut self) -> bool {
if !self.pure_done {
if self.pure.advance() {
return true;
}
self.pure_done = true;
return self.auxpow.is_some();
}
if let Some(auxpow) = self.auxpow.as_mut() {
if auxpow.advance() {
return true;
}
self.auxpow = None;
}
false
}
}
#[cfg(feature = "alloc")]
impl encoding::ExactSizeEncoder for HeaderEncoder<'_> {
fn len(&self) -> usize {
self.pure.len() + self.auxpow.as_ref().map_or(0, encoding::ExactSizeEncoder::len)
}
}
#[cfg(feature = "alloc")]
impl encoding::Encodable for Header {
type Encoder<'e> = HeaderEncoder<'e>;
fn encoder(&self) -> Self::Encoder<'_> {
HeaderEncoder::new(self)
}
}
#[cfg(not(feature = "alloc"))]
impl encoding::Encodable for Header {
type Encoder<'e> = HeaderEncoder<'e>;
fn encoder(&self) -> Self::Encoder<'_> {
self.pure_encoder()
}
}
type HeaderInnerDecoder = Decoder6<
VersionDecoder,
BlockHashDecoder,
TxMerkleNodeDecoder,
BlockTimeDecoder,
CompactTargetDecoder,
encoding::ArrayDecoder<4>, >;
pub struct PureHeaderDecoder(HeaderInnerDecoder);
impl PureHeaderDecoder {
pub const fn new() -> Self {
Self(Decoder6::new(
VersionDecoder::new(),
BlockHashDecoder::new(),
TxMerkleNodeDecoder::new(),
BlockTimeDecoder::new(),
CompactTargetDecoder::new(),
ArrayDecoder::new(),
))
}
fn from_inner(e: <HeaderInnerDecoder as encoding::Decoder>::Error) -> HeaderDecoderError {
match e {
encoding::Decoder6Error::First(e) => HeaderDecoderError::Version(e),
encoding::Decoder6Error::Second(e) => HeaderDecoderError::PrevBlockhash(e),
encoding::Decoder6Error::Third(e) => HeaderDecoderError::MerkleRoot(e),
encoding::Decoder6Error::Fourth(e) => HeaderDecoderError::Time(e),
encoding::Decoder6Error::Fifth(e) => HeaderDecoderError::Bits(e),
encoding::Decoder6Error::Sixth(e) => HeaderDecoderError::Nonce(e),
}
}
}
impl Default for PureHeaderDecoder {
fn default() -> Self {
Self::new()
}
}
impl encoding::Decoder for PureHeaderDecoder {
type Output = Header;
type Error = HeaderDecoderError;
#[inline]
fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
self.0.push_bytes(bytes).map_err(Self::from_inner)
}
#[inline]
fn end(self) -> Result<Self::Output, Self::Error> {
let (version, prev_blockhash, merkle_root, time, bits, nonce) =
self.0.end().map_err(Self::from_inner)?;
let nonce = u32::from_le_bytes(nonce);
Ok(Header {
version,
prev_blockhash,
merkle_root,
time,
bits,
nonce,
#[cfg(feature = "alloc")]
auxpow: None,
})
}
#[inline]
fn read_limit(&self) -> usize {
self.0.read_limit()
}
}
#[cfg(not(feature = "alloc"))]
pub use PureHeaderDecoder as HeaderDecoder;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum HeaderDecoderError {
Version(VersionDecoderError),
PrevBlockhash(BlockHashDecoderError),
MerkleRoot(TxMerkleNodeDecoderError),
Time(BlockTimeDecoderError),
Bits(CompactTargetDecoderError),
Nonce(encoding::UnexpectedEofError),
#[cfg(feature = "alloc")]
AuxPow(Box<AuxPowDecoderError>),
}
impl From<Infallible> for HeaderDecoderError {
fn from(never: Infallible) -> Self {
match never {}
}
}
impl fmt::Display for HeaderDecoderError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::Version(ref e) => write_err!(f, "header decoder error"; e),
Self::PrevBlockhash(ref e) => write_err!(f, "header decoder error"; e),
Self::MerkleRoot(ref e) => write_err!(f, "header decoder error"; e),
Self::Time(ref e) => write_err!(f, "header decoder error"; e),
Self::Bits(ref e) => write_err!(f, "header decoder error"; e),
Self::Nonce(ref e) => write_err!(f, "header decoder error"; e),
#[cfg(feature = "alloc")]
Self::AuxPow(ref e) => write_err!(f, "header decoder error"; e),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for HeaderDecoderError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match *self {
Self::Version(ref e) => Some(e),
Self::PrevBlockhash(ref e) => Some(e),
Self::MerkleRoot(ref e) => Some(e),
Self::Time(ref e) => Some(e),
Self::Bits(ref e) => Some(e),
Self::Nonce(ref e) => Some(e),
#[cfg(feature = "alloc")]
Self::AuxPow(ref e) => Some(e),
}
}
}
#[cfg(feature = "alloc")]
enum AuxPowDecoderState {
CoinbaseTx(crate::transaction::TransactionDecoder),
HashBlock(encoding::ArrayDecoder<32>, Transaction),
MerkleBranch(VecDecoder<BlockHash>, Transaction),
CoinbaseIndex(encoding::ArrayDecoder<4>, Transaction, Vec<BlockHash>),
ChainMerkleBranch(VecDecoder<BlockHash>, Transaction, Vec<BlockHash>),
ChainIndex(encoding::ArrayDecoder<4>, Transaction, Vec<BlockHash>, Vec<BlockHash>),
ParentBlock(PureHeaderDecoder, Transaction, Vec<BlockHash>, Vec<BlockHash>, i32),
Done(AuxPow),
Errored,
}
#[cfg(feature = "alloc")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AuxPowDecoderError {
CoinbaseTx(crate::transaction::TransactionDecoderError),
HashBlock(encoding::UnexpectedEofError),
MerkleBranch(<VecDecoder<BlockHash> as encoding::Decoder>::Error),
CoinbaseIndex(encoding::UnexpectedEofError),
ChainMerkleBranch(<VecDecoder<BlockHash> as encoding::Decoder>::Error),
ChainIndex(encoding::UnexpectedEofError),
ParentBlock(Box<HeaderDecoderError>),
InvalidCoinbaseIndex(i32),
EarlyEnd(&'static str),
}
#[cfg(feature = "alloc")]
impl fmt::Display for AuxPowDecoderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CoinbaseTx(e) => write_err!(f, "auxpow decoder error"; e),
Self::HashBlock(e) => write_err!(f, "auxpow decoder error"; e),
Self::MerkleBranch(e) => write_err!(f, "auxpow decoder error"; e),
Self::CoinbaseIndex(e) => write_err!(f, "auxpow decoder error"; e),
Self::ChainMerkleBranch(e) => write_err!(f, "auxpow decoder error"; e),
Self::ChainIndex(e) => write_err!(f, "auxpow decoder error"; e),
Self::ParentBlock(e) => write_err!(f, "auxpow decoder error"; e),
Self::InvalidCoinbaseIndex(index) => {
write!(f, "auxpow decoder error: coinbase index must be zero, got {}", index)
}
Self::EarlyEnd(field) => {
write!(f, "auxpow decoder error: early end while decoding {}", field)
}
}
}
}
#[cfg(all(feature = "alloc", feature = "std"))]
impl std::error::Error for AuxPowDecoderError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::CoinbaseTx(e) => Some(e),
Self::HashBlock(e) => Some(e),
Self::MerkleBranch(e) => Some(e),
Self::CoinbaseIndex(e) => Some(e),
Self::ChainMerkleBranch(e) => Some(e),
Self::ChainIndex(e) => Some(e),
Self::ParentBlock(e) => Some(e),
Self::InvalidCoinbaseIndex(_) | Self::EarlyEnd(_) => None,
}
}
}
#[cfg(feature = "alloc")]
struct AuxPowDecoder {
state: AuxPowDecoderState,
}
#[cfg(feature = "alloc")]
impl AuxPowDecoder {
pub const fn new() -> Self {
Self {
state: AuxPowDecoderState::CoinbaseTx(crate::transaction::TransactionDecoder::new()),
}
}
}
#[cfg(feature = "alloc")]
impl Default for AuxPowDecoder {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "alloc")]
impl encoding::Decoder for AuxPowDecoder {
type Output = AuxPow;
type Error = AuxPowDecoderError;
#[allow(clippy::too_many_lines)]
fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
use AuxPowDecoderError as E;
use AuxPowDecoderState as S;
loop {
match &mut self.state {
S::CoinbaseTx(decoder) => {
if decoder.push_bytes(bytes).map_err(E::CoinbaseTx)? {
return Ok(true);
}
}
S::HashBlock(decoder, _) => {
if decoder.push_bytes(bytes).map_err(E::HashBlock)? {
return Ok(true);
}
}
S::MerkleBranch(decoder, _) => {
if decoder.push_bytes(bytes).map_err(E::MerkleBranch)? {
return Ok(true);
}
}
S::CoinbaseIndex(decoder, ..) => {
if decoder.push_bytes(bytes).map_err(E::CoinbaseIndex)? {
return Ok(true);
}
}
S::ChainMerkleBranch(decoder, ..) => {
if decoder.push_bytes(bytes).map_err(E::ChainMerkleBranch)? {
return Ok(true);
}
}
S::ChainIndex(decoder, ..) => {
if decoder.push_bytes(bytes).map_err(E::ChainIndex)? {
return Ok(true);
}
}
S::ParentBlock(decoder, ..) => {
if decoder.push_bytes(bytes).map_err(|e| E::ParentBlock(Box::new(e)))? {
return Ok(true);
}
}
S::Done(..) => return Ok(false),
S::Errored => panic!("call to push_bytes() after auxpow decoder errored"),
}
match mem::replace(&mut self.state, S::Errored) {
S::CoinbaseTx(decoder) => {
let coinbase_tx = decoder.end().map_err(E::CoinbaseTx)?;
self.state = S::HashBlock(encoding::ArrayDecoder::new(), coinbase_tx);
}
S::HashBlock(decoder, coinbase_tx) => {
let _ = decoder.end().map_err(E::HashBlock)?;
self.state = S::MerkleBranch(VecDecoder::<BlockHash>::new(), coinbase_tx);
}
S::MerkleBranch(decoder, coinbase_tx) => {
let merkle_branch = decoder.end().map_err(E::MerkleBranch)?;
self.state =
S::CoinbaseIndex(encoding::ArrayDecoder::new(), coinbase_tx, merkle_branch);
}
S::CoinbaseIndex(decoder, coinbase_tx, merkle_branch) => {
let index = i32::from_le_bytes(decoder.end().map_err(E::CoinbaseIndex)?);
if index != 0 {
return Err(E::InvalidCoinbaseIndex(index));
}
self.state = S::ChainMerkleBranch(
VecDecoder::<BlockHash>::new(),
coinbase_tx,
merkle_branch,
);
}
S::ChainMerkleBranch(decoder, coinbase_tx, merkle_branch) => {
let chain_merkle_branch = decoder.end().map_err(E::ChainMerkleBranch)?;
self.state = S::ChainIndex(
encoding::ArrayDecoder::new(),
coinbase_tx,
merkle_branch,
chain_merkle_branch,
);
}
S::ChainIndex(decoder, coinbase_tx, merkle_branch, chain_merkle_branch) => {
let chain_index = i32::from_le_bytes(decoder.end().map_err(E::ChainIndex)?);
self.state = S::ParentBlock(
PureHeaderDecoder::new(),
coinbase_tx,
merkle_branch,
chain_merkle_branch,
chain_index,
);
}
S::ParentBlock(
decoder,
coinbase_tx,
merkle_branch,
chain_merkle_branch,
chain_index,
) => {
let parent_block = decoder.end().map_err(|e| E::ParentBlock(Box::new(e)))?;
self.state = S::Done(AuxPow {
coinbase_tx,
merkle_branch,
chain_merkle_branch,
chain_index,
parent_block,
});
return Ok(false);
}
S::Done(auxpow) => {
self.state = S::Done(auxpow);
return Ok(false);
}
S::Errored => unreachable!("checked above"),
}
}
}
fn end(self) -> Result<Self::Output, Self::Error> {
use AuxPowDecoderError as E;
use AuxPowDecoderState as S;
match self.state {
S::CoinbaseTx(_) => Err(E::EarlyEnd("coinbase transaction")),
S::HashBlock(..) => Err(E::EarlyEnd("reserved auxpow hashBlock field")),
S::MerkleBranch(..) => Err(E::EarlyEnd("coinbase merkle branch")),
S::CoinbaseIndex(..) => Err(E::EarlyEnd("coinbase merkle index")),
S::ChainMerkleBranch(..) => Err(E::EarlyEnd("chain merkle branch")),
S::ChainIndex(..) => Err(E::EarlyEnd("chain index")),
S::ParentBlock(
header_decoder,
coinbase_tx,
merkle_branch,
chain_merkle_branch,
chain_index,
) => {
let parent_block = header_decoder.end().map_err(|e| E::ParentBlock(Box::new(e)))?;
Ok(AuxPow {
coinbase_tx,
merkle_branch,
chain_merkle_branch,
chain_index,
parent_block,
})
}
S::Done(auxpow) => Ok(auxpow),
S::Errored => panic!("call to end() after auxpow decoder errored"),
}
}
fn read_limit(&self) -> usize {
use AuxPowDecoderState as S;
match &self.state {
S::CoinbaseTx(decoder) => decoder.read_limit(),
S::HashBlock(decoder, _) => decoder.read_limit(),
S::MerkleBranch(decoder, _) => decoder.read_limit(),
S::CoinbaseIndex(decoder, ..) => decoder.read_limit(),
S::ChainMerkleBranch(decoder, ..) => decoder.read_limit(),
S::ChainIndex(decoder, ..) => decoder.read_limit(),
S::ParentBlock(decoder, ..) => decoder.read_limit(),
S::Done(..) => 0,
S::Errored => 0,
}
}
}
#[cfg(feature = "alloc")]
enum HeaderDecoderState {
Pure(PureHeaderDecoder),
AuxPow(Box<(Header, AuxPowDecoder)>),
Done(Header),
Errored,
}
#[cfg(feature = "alloc")]
pub struct HeaderDecoder {
state: HeaderDecoderState,
}
#[cfg(feature = "alloc")]
impl HeaderDecoder {
pub const fn new() -> Self {
Self { state: HeaderDecoderState::Pure(PureHeaderDecoder::new()) }
}
}
#[cfg(feature = "alloc")]
impl Default for HeaderDecoder {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "alloc")]
impl encoding::Decoder for HeaderDecoder {
type Output = Header;
type Error = HeaderDecoderError;
fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
use HeaderDecoderState as S;
loop {
match &mut self.state {
S::Pure(decoder) => {
if decoder.push_bytes(bytes)? {
return Ok(true);
}
}
S::AuxPow(state) => {
if state
.1
.push_bytes(bytes)
.map_err(|e| HeaderDecoderError::AuxPow(Box::new(e)))?
{
return Ok(true);
}
}
S::Done(..) => return Ok(false),
S::Errored => panic!("call to push_bytes() after header decoder errored"),
}
match mem::replace(&mut self.state, S::Errored) {
S::Pure(decoder) => {
let header = decoder.end()?;
if header.version.is_auxpow() {
self.state = S::AuxPow(Box::new((header, AuxPowDecoder::new())));
} else {
self.state = S::Done(header);
return Ok(false);
}
}
S::AuxPow(state) => {
let (mut header, decoder) = *state;
header.auxpow = Some(Box::new(
decoder.end().map_err(|e| HeaderDecoderError::AuxPow(Box::new(e)))?,
));
self.state = S::Done(header);
return Ok(false);
}
S::Done(header) => {
self.state = S::Done(header);
return Ok(false);
}
S::Errored => unreachable!("checked above"),
}
}
}
fn end(self) -> Result<Self::Output, Self::Error> {
use HeaderDecoderState as S;
match self.state {
S::Pure(decoder) => decoder.end(),
S::AuxPow(state) => {
let (mut header, decoder) = *state;
header.auxpow = Some(Box::new(
decoder.end().map_err(|e| HeaderDecoderError::AuxPow(Box::new(e)))?,
));
Ok(header)
}
S::Done(header) => Ok(header),
S::Errored => panic!("call to end() after header decoder errored"),
}
}
fn read_limit(&self) -> usize {
use HeaderDecoderState as S;
match &self.state {
S::Pure(decoder) => decoder.read_limit(),
S::AuxPow(state) => state.1.read_limit(),
S::Done(..) => 0,
S::Errored => 0,
}
}
}
#[cfg(feature = "alloc")]
impl encoding::Decodable for Header {
type Decoder = HeaderDecoder;
fn decoder() -> Self::Decoder {
HeaderDecoder::new()
}
}
#[cfg(not(feature = "alloc"))]
impl encoding::Decodable for Header {
type Decoder = HeaderDecoder;
fn decoder() -> Self::Decoder {
HeaderDecoder::new()
}
}
impl From<Header> for BlockHash {
#[inline]
fn from(header: Header) -> Self {
header.block_hash()
}
}
impl From<&Header> for BlockHash {
#[inline]
fn from(header: &Header) -> Self {
header.block_hash()
}
}
#[derive(Copy, PartialEq, Eq, Clone, Debug, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Version(i32);
impl Version {
pub const ONE: Self = Self(1);
pub const TWO: Self = Self(2);
pub const NO_SOFT_FORK_SIGNALLING: Self = Self(Self::USE_VERSION_BITS as i32);
pub const VERSION_AUXPOW: i32 = 1 << 8;
pub const VERSION_START_BIT: u8 = 16;
pub const VERSION_CHAIN_START: i32 = 1 << Self::VERSION_START_BIT;
pub const VERSION_AUXPOW_TOP_MASK: i32 = (1 << 28) | (1 << 29) | (1 << 30);
pub const MASK_AUXPOW_CHAINID_SHIFTED: i32 = 0x001f << Self::VERSION_START_BIT;
const VERSION_BITS_MASK: u32 = 0x1FFF_FFFF;
const USE_VERSION_BITS: u32 = 0x2000_0000;
#[inline]
pub const fn from_consensus(v: i32) -> Self {
Self(v)
}
#[inline]
pub const fn to_consensus(self) -> i32 {
self.0
}
pub const fn is_auxpow(self) -> bool {
(self.0 & Self::VERSION_AUXPOW) != 0
}
#[must_use]
pub const fn with_auxpow(self, enabled: bool) -> Self {
if enabled {
Self(self.0 | Self::VERSION_AUXPOW)
} else {
Self(self.0 & !Self::VERSION_AUXPOW)
}
}
pub const fn base_version(self) -> i32 {
(self.0 & !Self::VERSION_AUXPOW) & !Self::MASK_AUXPOW_CHAINID_SHIFTED
}
pub const fn chain_id(self) -> i32 {
if self.is_auxpow() {
(self.0 & Self::MASK_AUXPOW_CHAINID_SHIFTED) >> Self::VERSION_START_BIT
} else {
0
}
}
pub const fn with_base_version(base_version: i32, chain_id: i32) -> Self {
Self(base_version | (chain_id << Self::VERSION_START_BIT))
}
pub const fn is_valid_base_version(base_version: i32) -> bool {
(base_version & !Self::VERSION_AUXPOW_TOP_MASK) < Self::VERSION_CHAIN_START
}
pub const fn is_legacy(self) -> bool {
self.0 == 1
}
pub fn is_signalling_soft_fork(self, bit: u8) -> bool {
if bit > 28 {
return false;
}
if (self.0 as u32) & !Self::VERSION_BITS_MASK != Self::USE_VERSION_BITS {
return false;
}
(self.0 as u32 & Self::VERSION_BITS_MASK) & (1 << bit) > 0
}
}
impl fmt::Display for Version {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl fmt::LowerHex for Version {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::LowerHex::fmt(&self.0, f)
}
}
impl fmt::UpperHex for Version {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::UpperHex::fmt(&self.0, f)
}
}
impl fmt::Octal for Version {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Octal::fmt(&self.0, f)
}
}
impl fmt::Binary for Version {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Binary::fmt(&self.0, f)
}
}
impl Default for Version {
#[inline]
fn default() -> Self {
Self::NO_SOFT_FORK_SIGNALLING
}
}
encoding::encoder_newtype_exact! {
pub struct VersionEncoder<'e>(encoding::ArrayEncoder<4>);
}
impl encoding::Encodable for Version {
type Encoder<'e> = VersionEncoder<'e>;
fn encoder(&self) -> Self::Encoder<'_> {
VersionEncoder::new(encoding::ArrayEncoder::without_length_prefix(
self.to_consensus().to_le_bytes(),
))
}
}
pub struct VersionDecoder(encoding::ArrayDecoder<4>);
impl VersionDecoder {
pub const fn new() -> Self {
Self(encoding::ArrayDecoder::new())
}
}
impl Default for VersionDecoder {
fn default() -> Self {
Self::new()
}
}
impl encoding::Decoder for VersionDecoder {
type Output = Version;
type Error = VersionDecoderError;
#[inline]
fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
self.0.push_bytes(bytes).map_err(VersionDecoderError)
}
#[inline]
fn end(self) -> Result<Self::Output, Self::Error> {
let n = i32::from_le_bytes(self.0.end().map_err(VersionDecoderError)?);
Ok(Version::from_consensus(n))
}
#[inline]
fn read_limit(&self) -> usize {
self.0.read_limit()
}
}
impl encoding::Decodable for Version {
type Decoder = VersionDecoder;
fn decoder() -> Self::Decoder {
VersionDecoder(encoding::ArrayDecoder::<4>::new())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VersionDecoderError(encoding::UnexpectedEofError);
impl From<Infallible> for VersionDecoderError {
fn from(never: Infallible) -> Self {
match never {}
}
}
impl fmt::Display for VersionDecoderError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write_err!(f, "version decoder error"; self.0)
}
}
#[cfg(feature = "std")]
impl std::error::Error for VersionDecoderError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.0)
}
}
#[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(Self::new_unchecked(header, transactions))
}
}
#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for Header {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(Self {
version: Version::arbitrary(u)?,
prev_blockhash: BlockHash::from_byte_array(u.arbitrary()?),
merkle_root: TxMerkleNode::from_byte_array(u.arbitrary()?),
time: u.arbitrary()?,
bits: CompactTarget::from_consensus(u.arbitrary()?),
nonce: u.arbitrary()?,
#[cfg(feature = "alloc")]
auxpow: None,
})
}
}
#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for Version {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let choice = u.int_in_range(0..=3)?;
match choice {
0 => Ok(Self::ONE),
1 => Ok(Self::TWO),
2 => Ok(Self::NO_SOFT_FORK_SIGNALLING),
_ => Ok(Self::from_consensus(u.arbitrary()?)),
}
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "alloc")]
use alloc::string::ToString;
#[cfg(feature = "alloc")]
use alloc::{format, vec};
#[cfg(all(feature = "alloc", feature = "hex"))]
use core::str::FromStr as _;
#[cfg(feature = "alloc")]
use encoding::Decodable as _;
use encoding::{Decoder as _, Encodable as _, Encoder as _};
#[cfg(all(feature = "serde", feature = "hex", feature = "alloc"))]
use serde::{Deserialize, Serialize};
use super::*;
fn dummy_header() -> Header {
Header {
version: Version::ONE,
prev_blockhash: BlockHash::from_byte_array([0x99; 32]),
merkle_root: TxMerkleNode::from_byte_array([0x77; 32]),
time: BlockTime::from(2),
bits: CompactTarget::from_consensus(3),
nonce: 4,
#[cfg(feature = "alloc")]
auxpow: None,
}
}
#[test]
fn version_is_not_signalling_with_invalid_bit() {
let arbitrary_version = Version::from_consensus(1_234_567_890);
assert!(!Version::is_signalling_soft_fork(arbitrary_version, 29));
}
#[test]
fn version_is_not_signalling_when_use_version_bit_not_set() {
let version = Version::from_consensus(0b0100_0000_0000_0000_0000_0000_0000_0000);
assert!(!Version::is_signalling_soft_fork(version, 1));
}
#[test]
fn version_is_signalling() {
let version = Version::from_consensus(0b0010_0000_0000_0000_0000_0000_0000_0010);
assert!(Version::is_signalling_soft_fork(version, 1));
let version = Version::from_consensus(0b0011_0000_0000_0000_0000_0000_0000_0000);
assert!(Version::is_signalling_soft_fork(version, 28));
}
#[test]
fn version_is_not_signalling() {
let version = Version::from_consensus(0b0010_0000_0000_0000_0000_0000_0000_0010);
assert!(!Version::is_signalling_soft_fork(version, 0));
}
#[test]
fn version_to_consensus() {
let version = Version::from_consensus(1_234_567_890);
assert_eq!(version.to_consensus(), 1_234_567_890);
}
#[test]
fn version_default() {
let version = Version::default();
assert_eq!(version.to_consensus(), Version::NO_SOFT_FORK_SIGNALLING.to_consensus());
}
#[test]
#[cfg(feature = "alloc")]
fn version_display() {
let version = Version(75);
assert_eq!(format!("{}", version), "75");
assert_eq!(format!("{:x}", version), "4b");
assert_eq!(format!("{:#x}", version), "0x4b");
assert_eq!(format!("{:X}", version), "4B");
assert_eq!(format!("{:#X}", version), "0x4B");
assert_eq!(format!("{:o}", version), "113");
assert_eq!(format!("{:#o}", version), "0o113");
assert_eq!(format!("{:b}", version), "1001011");
assert_eq!(format!("{:#b}", version), "0b1001011");
}
#[test]
fn header_size() {
let header = dummy_header();
let header_size = header.version.to_consensus().to_le_bytes().len()
+ header.prev_blockhash.as_byte_array().len()
+ header.merkle_root.as_byte_array().len()
+ header.time.to_u32().to_le_bytes().len()
+ header.bits.to_consensus().to_le_bytes().len()
+ header.nonce.to_le_bytes().len();
assert_eq!(header_size, Header::SIZE);
}
#[test]
#[cfg(feature = "alloc")]
fn block_new_unchecked() {
let header = dummy_header();
let transactions = vec![];
let block = Block::new_unchecked(header.clone(), transactions.clone());
assert_eq!(block.header, header);
assert_eq!(block.transactions, transactions);
}
#[test]
#[cfg(feature = "alloc")]
fn block_assume_checked() {
let header = dummy_header();
let transactions = vec![];
let block = Block::new_unchecked(header.clone(), transactions.clone());
let witness_root = Some(WitnessMerkleNode::from_byte_array([0x88; 32]));
let checked_block = block.assume_checked(witness_root);
assert_eq!(checked_block.header(), &header);
assert_eq!(checked_block.transactions(), &transactions);
assert_eq!(checked_block.cached_witness_root(), witness_root);
}
#[test]
#[cfg(feature = "alloc")]
fn block_into_parts() {
let header = dummy_header();
let transactions = vec![];
let block = Block::new_unchecked(header.clone(), transactions.clone());
let (block_header, block_transactions) = block.into_parts();
assert_eq!(block_header, header);
assert_eq!(block_transactions, transactions);
}
#[test]
#[cfg(feature = "alloc")]
fn block_cached_witness_root() {
let header = dummy_header();
let transactions = vec![];
let block = Block::new_unchecked(header, transactions);
let witness_root = Some(WitnessMerkleNode::from_byte_array([0x88; 32]));
let checked_block = block.assume_checked(witness_root);
assert_eq!(checked_block.cached_witness_root(), witness_root);
}
#[test]
#[cfg(feature = "alloc")]
fn block_validation_no_transactions() {
let header = dummy_header();
let transactions = Vec::new();
let block = Block::new_unchecked(header, transactions);
matches!(block.validate(), Err(InvalidBlockError::NoTransactions));
}
#[test]
#[cfg(feature = "alloc")]
fn block_validation_invalid_coinbase() {
let header = dummy_header();
let non_coinbase_tx = Transaction {
version: crate::transaction::Version::TWO,
lock_time: crate::absolute::LockTime::ZERO,
inputs: vec![crate::TxIn {
previous_output: crate::OutPoint {
txid: crate::Txid::from_byte_array([1; 32]), vout: 0,
},
script_sig: crate::ScriptSigBuf::new(),
sequence: units::Sequence::ENABLE_LOCKTIME_AND_RBF,
witness: crate::Witness::new(),
}],
outputs: vec![crate::TxOut {
amount: units::Amount::ONE_TDC,
script_pubkey: crate::ScriptPubKeyBuf::new(),
}],
};
let transactions = vec![non_coinbase_tx];
let block = Block::new_unchecked(header, transactions);
matches!(block.validate(), Err(InvalidBlockError::InvalidCoinbase));
}
#[test]
#[cfg(feature = "alloc")]
fn block_decoder_read_limit() {
let mut coinbase_in = crate::TxIn::EMPTY_COINBASE;
coinbase_in.script_sig = crate::ScriptSigBuf::from_bytes(vec![0u8; 2]);
let block = Block::new_unchecked(
dummy_header(),
vec![Transaction {
version: crate::transaction::Version::ONE,
lock_time: crate::absolute::LockTime::ZERO,
inputs: vec![coinbase_in],
outputs: vec![crate::TxOut {
amount: units::Amount::MIN,
script_pubkey: crate::ScriptPubKeyBuf::new(),
}],
}],
);
let bytes = encoding::encode_to_vec(&block);
let mut view = bytes.as_slice();
let mut decoder = Block::decoder();
assert!(decoder.read_limit() > 0);
let needs_more = decoder.push_bytes(&mut view).unwrap();
assert!(!needs_more);
assert_eq!(decoder.read_limit(), 0);
assert_eq!(decoder.end().unwrap(), block);
}
#[test]
#[cfg(feature = "alloc")]
fn header_decoder_read_limit() {
let header = dummy_header();
let bytes = encoding::encode_to_vec(&header);
let mut view = bytes.as_slice();
let mut decoder = Header::decoder();
assert!(decoder.read_limit() > 0);
let needs_more = decoder.push_bytes(&mut view).unwrap();
assert!(!needs_more);
assert_eq!(decoder.read_limit(), 0);
assert_eq!(decoder.end().unwrap(), header);
}
#[test]
#[cfg(feature = "alloc")]
fn block_check_witness_commitment_optional() {
let mut header = dummy_header();
header.merkle_root = TxMerkleNode::from_byte_array([0u8; 32]);
let coinbase = Transaction {
version: crate::transaction::Version::ONE,
lock_time: crate::absolute::LockTime::ZERO,
inputs: vec![crate::TxIn::EMPTY_COINBASE],
outputs: vec![],
};
let transactions = vec![coinbase];
let block = Block::new_unchecked(header, transactions);
let result = block.check_witness_commitment();
assert_eq!(result, (true, None));
}
#[test]
#[cfg(feature = "alloc")]
fn block_block_hash() {
let header = dummy_header();
let transactions = vec![];
let block = Block::new_unchecked(header.clone(), transactions);
assert_eq!(block.block_hash(), header.block_hash());
}
#[test]
fn block_hash_from_header() {
let header = dummy_header();
let block_hash = header.block_hash();
assert_eq!(block_hash, BlockHash::from(header));
}
#[test]
fn block_hash_from_header_ref() {
let header = dummy_header();
let block_hash: BlockHash = BlockHash::from(&header);
assert_eq!(block_hash, header.block_hash());
}
#[test]
#[cfg(feature = "alloc")]
fn block_hash_from_block() {
let header = dummy_header();
let transactions = vec![];
let block = Block::new_unchecked(header.clone(), transactions);
let block_hash: BlockHash = BlockHash::from(block);
assert_eq!(block_hash, header.block_hash());
}
#[test]
#[cfg(feature = "alloc")]
fn block_hash_from_block_ref() {
let header = dummy_header();
let transactions = vec![];
let block = Block::new_unchecked(header.clone(), transactions);
let block_hash: BlockHash = BlockHash::from(&block);
assert_eq!(block_hash, header.block_hash());
}
#[test]
#[cfg(feature = "alloc")]
fn header_debug() {
let header = dummy_header();
let expected = format!(
"Header {{ block_hash: {:?}, version: {:?}, prev_blockhash: {:?}, merkle_root: {:?}, time: {:?}, bits: {:?}, nonce: {:?}, auxpow: None }}",
header.block_hash(),
header.version,
header.prev_blockhash,
header.merkle_root,
header.time,
header.bits,
header.nonce
);
assert_eq!(format!("{:?}", header), expected);
}
#[test]
#[cfg(feature = "alloc")]
fn version_auxpow_helpers_roundtrip() {
let version = Version::with_base_version(1, 8).with_auxpow(true);
assert!(version.is_auxpow());
assert_eq!(version.base_version(), 1);
assert_eq!(version.chain_id(), 8);
assert!(!version.is_legacy());
assert!(Version::from_consensus(version.base_version()).is_legacy());
assert!(Version::is_valid_base_version(1));
}
#[test]
#[cfg(feature = "alloc")]
fn auxpow_payload_does_not_change_header_hash() {
let mut header = dummy_header();
header.version = Version::with_base_version(1, 8).with_auxpow(true);
let expected = header.block_hash();
header.auxpow = Some(Box::new(AuxPow::minimal_for_header(&header)));
assert_eq!(header.block_hash(), expected);
}
#[test]
#[cfg(feature = "alloc")]
fn pure_header_bytes_exclude_auxpow_payload() {
let mut header = dummy_header();
header.version = Version::with_base_version(1, 8).with_auxpow(true);
let expected = header.pure_header_bytes();
header.auxpow = Some(Box::new(AuxPow::minimal_for_header(&header)));
assert_eq!(header.pure_header_bytes(), expected);
assert_eq!(header.pure_header_bytes().len(), Header::SIZE);
}
#[test]
#[cfg(feature = "hex")]
#[cfg(feature = "alloc")]
fn header_display() {
let seconds: u32 = 1_653_195_600;
let header = Header {
version: Version::TWO,
prev_blockhash: BlockHash::from_byte_array([0xab; 32]),
merkle_root: TxMerkleNode::from_byte_array([0xcd; 32]),
time: BlockTime::from(seconds),
bits: CompactTarget::from_consensus(0xbeef),
nonce: 0xcafe,
auxpow: None,
};
let want = concat!(
"02000000", "abababababababababababababababababababababababababababababababab", "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", "50c38962", "efbe0000", "feca0000", );
assert_eq!(want.len(), 160);
assert_eq!(format!("{}", header), want);
let want = format!("{:.20}", want);
let got = format!("{:.20}", header);
assert_eq!(got, want);
let want = format!("{:.0}", want);
let got = format!("{:.0}", header);
assert_eq!(got, want);
}
#[test]
#[cfg(feature = "hex")]
#[cfg(feature = "alloc")]
fn header_hex() {
let header = dummy_header();
let lower_hex = concat!(
"01000000", "9999999999999999999999999999999999999999999999999999999999999999", "7777777777777777777777777777777777777777777777777777777777777777", "02000000", "03000000", "04000000", );
assert_eq!(lower_hex, format!("{:x}", header));
assert_eq!(lower_hex, format!("{}", header));
let upper_hex = lower_hex.to_ascii_uppercase();
assert_eq!(upper_hex, format!("{:X}", header));
assert_eq!(format!("{:>164}", lower_hex), format!("{:>164x}", header));
assert_eq!(format!("{:<164}", lower_hex), format!("{:<164x}", header));
assert_eq!(format!("{:^164}", lower_hex), format!("{:^164x}", header));
assert_eq!(format!("{:_>164}", lower_hex), format!("{:_>164x}", header));
let lower_hex_alt = format!("0x{}", lower_hex);
assert_eq!(lower_hex_alt, format!("{:#x}", header));
assert_eq!(format!("0X{}", upper_hex), format!("{:#X}", header));
assert_eq!(format!("{:>166}", lower_hex_alt), format!("{:>#166x}", header));
assert_eq!(format!("{:<166}", lower_hex_alt), format!("{:<#166x}", header));
assert_eq!(format!("{:^166}", lower_hex_alt), format!("{:^#166x}", header));
assert_eq!(format!("{:>.20}", lower_hex_alt), format!("{:>#.20x}", header));
assert_eq!(format!("{:<.20}", lower_hex_alt), format!("{:<#.20x}", header));
assert_eq!(format!("{:^.20}", lower_hex_alt), format!("{:^#.20x}", header));
}
#[test]
#[cfg(feature = "hex")]
#[cfg(feature = "alloc")]
fn header_from_hex_str_round_trip() {
let header = dummy_header();
let lower_hex_header = format!("{:x}", header);
let upper_hex_header = format!("{:X}", header);
let parsed_lower = Header::from_str(&lower_hex_header).unwrap();
let parsed_upper = Header::from_str(&upper_hex_header).unwrap();
assert_eq!(header, parsed_lower);
assert_eq!(header, parsed_upper);
}
#[cfg(feature = "alloc")]
fn dummy_block() -> Block {
let header = Header {
version: Version::ONE,
#[rustfmt::skip]
prev_blockhash: BlockHash::from_byte_array([
0xDC, 0xBA, 0xDC, 0xBA, 0xDC, 0xBA, 0xDC, 0xBA,
0xDC, 0xBA, 0xDC, 0xBA, 0xDC, 0xBA, 0xDC, 0xBA,
0xDC, 0xBA, 0xDC, 0xBA, 0xDC, 0xBA, 0xDC, 0xBA,
0xDC, 0xBA, 0xDC, 0xBA, 0xDC, 0xBA, 0xDC, 0xBA,
]),
#[rustfmt::skip]
merkle_root: TxMerkleNode::from_byte_array([
0xAB, 0xCD, 0xAB, 0xCD, 0xAB, 0xCD, 0xAB, 0xCD,
0xAB, 0xCD, 0xAB, 0xCD, 0xAB, 0xCD, 0xAB, 0xCD,
0xAB, 0xCD, 0xAB, 0xCD, 0xAB, 0xCD, 0xAB, 0xCD,
0xAB, 0xCD, 0xAB, 0xCD, 0xAB, 0xCD, 0xAB, 0xCD,
]),
time: BlockTime::from(1_742_979_600), bits: CompactTarget::from_consensus(12_345_678),
nonce: 1024,
auxpow: None,
};
let block: u32 = 741_521;
let transactions = vec![Transaction {
version: crate::transaction::Version::ONE,
lock_time: units::absolute::LockTime::from_height(block).unwrap(),
inputs: vec![crate::transaction::TxIn {
previous_output: crate::transaction::OutPoint::COINBASE_PREVOUT,
script_sig: crate::script::ScriptSigBuf::from_bytes(vec![0x51, 0x51]),
sequence: crate::sequence::Sequence::MAX,
witness: crate::witness::Witness::new(),
}],
outputs: vec![crate::transaction::TxOut {
amount: units::Amount::ONE_SAT,
script_pubkey: crate::script::ScriptPubKeyBuf::new(),
}],
}];
Block::new_unchecked(header, transactions)
}
#[test]
#[cfg(feature = "hex")]
#[cfg(feature = "alloc")]
fn block_hex() {
let header = dummy_header();
let transactions = vec![Transaction {
version: crate::transaction::Version::ONE,
lock_time: crate::locktime::absolute::LockTime::ZERO,
inputs: vec![],
outputs: vec![],
}];
let block = Block::new_unchecked(header, transactions);
let want = "010000009999999999999999999999999999999999999999999999999999999999999999777777777777777777777777777777777777777777777777777777777777777702000000030000000400000001010000000001000000000000";
assert_eq!(format!("{}", block), want);
assert_eq!(format!("{:x}", block), want);
let want =
want.chars().map(|chr| chr.to_ascii_uppercase()).collect::<alloc::string::String>();
assert_eq!(want, format!("{:X}", block));
}
#[test]
#[cfg(feature = "hex")]
#[cfg(feature = "alloc")]
fn block_from_hex_str_round_trip() {
let block = dummy_block();
let lower_hex_block = format!("{:x}", block);
let upper_hex_block = format!("{:X}", block);
let parsed_lower = Block::from_str(&lower_hex_block).unwrap();
let parsed_upper = Block::from_str(&upper_hex_block).unwrap();
assert_eq!(parsed_lower, block);
assert_eq!(parsed_upper, block);
}
#[test]
#[cfg(feature = "alloc")]
fn block_decode() {
let original = dummy_block();
let encoded = encoding::encode_to_vec(&original);
let decoded: Block = encoding::decode_from_slice(encoded.as_slice()).unwrap();
assert_eq!(decoded, original);
}
#[test]
#[cfg(feature = "alloc")]
fn merkle_tree_hash_collision() {
fn coinbase_tx() -> Transaction {
let mut coinbase_in = crate::TxIn::EMPTY_COINBASE;
coinbase_in.script_sig = crate::ScriptSigBuf::from_bytes(vec![0x51, 0x51]);
Transaction {
version: crate::transaction::Version::ONE,
lock_time: crate::absolute::LockTime::ZERO,
inputs: vec![coinbase_in],
outputs: vec![crate::TxOut {
amount: units::Amount::ONE_SAT,
script_pubkey: crate::ScriptPubKeyBuf::from_bytes(vec![0x51]),
}],
}
}
fn spend_tx(tag: u8) -> Transaction {
Transaction {
version: crate::transaction::Version::TWO,
lock_time: crate::absolute::LockTime::ZERO,
inputs: vec![crate::TxIn {
previous_output: crate::OutPoint {
txid: crate::Txid::from_byte_array([tag; 32]),
vout: tag.into(),
},
script_sig: crate::ScriptSigBuf::from_bytes(vec![tag]),
sequence: crate::sequence::Sequence::MAX,
witness: crate::Witness::new(),
}],
outputs: vec![crate::TxOut {
amount: units::Amount::ONE_SAT,
script_pubkey: crate::ScriptPubKeyBuf::from_bytes(vec![tag.wrapping_add(1)]),
}],
}
}
let transactions = vec![coinbase_tx(), spend_tx(0x11), spend_tx(0x12)];
let mut header = dummy_header();
header.merkle_root = compute_merkle_root(&transactions).unwrap();
let valid_block = Block::new_unchecked(header.clone(), transactions.clone());
let mut forged_transactions = transactions;
forged_transactions.push(forged_transactions[2].clone());
let forged_block = Block::new_unchecked(header, forged_transactions);
assert!(valid_block.validate().is_ok());
assert!(forged_block.validate().is_err());
}
#[test]
#[cfg(feature = "alloc")]
fn witness_commitment_from_coinbase_simple() {
let magic = [0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed];
let mut pubkey_bytes = [0; 38];
pubkey_bytes[0..6].copy_from_slice(&magic);
let witness_commitment =
WitnessCommitment::from_byte_array(pubkey_bytes[6..38].try_into().unwrap());
let commitment_script = crate::script::ScriptBuf::from_bytes(pubkey_bytes.to_vec());
let tx = Transaction {
version: crate::transaction::Version::ONE,
lock_time: crate::absolute::LockTime::ZERO,
inputs: vec![crate::TxIn::EMPTY_COINBASE],
outputs: vec![crate::TxOut {
amount: units::Amount::MIN,
script_pubkey: commitment_script,
}],
};
let extracted = witness_commitment_from_coinbase(&tx);
assert_eq!(extracted, Some(witness_commitment));
}
#[test]
#[cfg(feature = "alloc")]
fn witness_commitment_from_non_coinbase_returns_none() {
let tx = Transaction {
version: crate::transaction::Version::ONE,
lock_time: crate::absolute::LockTime::ZERO,
inputs: vec![crate::TxIn {
previous_output: crate::OutPoint {
txid: crate::Txid::from_byte_array([1; 32]),
vout: 0,
},
script_sig: crate::ScriptSigBuf::new(),
sequence: units::Sequence::ENABLE_LOCKTIME_AND_RBF,
witness: crate::Witness::new(),
}],
outputs: vec![crate::TxOut {
amount: units::Amount::MIN,
script_pubkey: crate::ScriptPubKeyBuf::new(),
}],
};
assert!(witness_commitment_from_coinbase(&tx).is_none());
}
#[test]
#[cfg(feature = "alloc")]
fn block_check_witness_commitment_empty_script_pubkey() {
let mut txin = crate::TxIn::EMPTY_COINBASE;
let push = [11_u8];
txin.witness.push(push);
let tx = Transaction {
version: crate::transaction::Version::ONE,
lock_time: crate::absolute::LockTime::ZERO,
inputs: vec![txin],
outputs: vec![crate::TxOut {
amount: units::Amount::MIN,
script_pubkey: crate::script::ScriptBuf::new(),
}],
};
let block = Block::new_unchecked(dummy_header(), vec![tx]);
let result = block.check_witness_commitment();
assert_eq!(result, (false, None)); }
#[test]
#[cfg(feature = "alloc")]
fn block_check_witness_commitment_no_transactions() {
let empty_block = Block::new_unchecked(dummy_header(), vec![]);
let result = empty_block.check_witness_commitment();
assert_eq!(result, (false, None));
}
#[test]
#[cfg(all(feature = "alloc", feature = "hex"))]
fn block_check_witness_commitment_with_witness() {
let mut txin = crate::TxIn::EMPTY_COINBASE;
let witness_bytes: [u8; 32] = [11u8; 32];
txin.witness.push(witness_bytes);
let script_pubkey_bytes = hex::decode_to_array::<38>(
"6a24aa21a9ed3cde9e0b9f4ad8f9d0fd66d6b9326cd68597c04fa22ab64b8e455f08d2e31ceb",
)
.unwrap();
let tx1 = Transaction {
version: crate::transaction::Version::ONE,
lock_time: crate::absolute::LockTime::ZERO,
inputs: vec![txin],
outputs: vec![crate::TxOut {
amount: units::Amount::MIN,
script_pubkey: crate::script::ScriptBuf::from_bytes(script_pubkey_bytes.to_vec()),
}],
};
let tx2 = Transaction {
version: crate::transaction::Version::ONE,
lock_time: crate::absolute::LockTime::ZERO,
inputs: vec![crate::TxIn::EMPTY_COINBASE],
outputs: vec![crate::TxOut {
amount: units::Amount::MIN,
script_pubkey: crate::script::ScriptBuf::new(),
}],
};
let block = Block::new_unchecked(dummy_header(), vec![tx1, tx2]);
let result = block.check_witness_commitment();
let exp_bytes = hex::decode_to_array::<32>(
"fb848679079938b249a12f14b72d56aeb116df79254e17cdf72b46523bcb49db",
)
.unwrap();
let expected = WitnessMerkleNode::from_byte_array(exp_bytes);
assert_eq!(result, (true, Some(expected)));
}
#[test]
#[cfg(all(feature = "alloc", feature = "hex"))]
fn block_check_witness_commitment_invalid_witness() {
let mut txin = crate::TxIn::EMPTY_COINBASE;
txin.script_sig = crate::ScriptSigBuf::from_bytes(vec![0u8; 2]);
let witness_bytes: [u8; 32] = [11u8; 32];
txin.witness.push(witness_bytes);
txin.witness.push([12u8]);
let script_pubkey_bytes = hex::decode_to_array::<38>(
"6a24aa21a9ed3cde9e0b9f4ad8f9d0fd66d6b9326cd68597c04fa22ab64b8e455f08d2e31ceb",
)
.unwrap();
let tx1 = Transaction {
version: crate::transaction::Version::ONE,
lock_time: crate::absolute::LockTime::ZERO,
inputs: vec![txin],
outputs: vec![crate::TxOut {
amount: units::Amount::MIN,
script_pubkey: crate::script::ScriptBuf::from_bytes(script_pubkey_bytes.to_vec()),
}],
};
let tx2 = Transaction {
version: crate::transaction::Version::ONE,
lock_time: crate::absolute::LockTime::ZERO,
inputs: vec![crate::TxIn {
previous_output: crate::OutPoint {
txid: crate::Txid::from_byte_array([1; 32]),
vout: 0,
},
script_sig: crate::ScriptSigBuf::new(),
sequence: crate::Sequence::MAX,
witness: crate::Witness::default(),
}],
outputs: vec![crate::TxOut {
amount: units::Amount::MIN,
script_pubkey: crate::script::ScriptBuf::new(),
}],
};
let mut header = dummy_header();
let transactions = vec![tx1, tx2];
header.merkle_root = compute_merkle_root(&transactions).unwrap();
let block = Block::new_unchecked(header, transactions);
assert_eq!(block.check_witness_commitment(), (false, None));
assert!(matches!(block.validate(), Err(InvalidBlockError::InvalidWitnessCommitment)));
}
#[test]
fn version_encoder_emits_consensus_bytes() {
let version = Version::from_consensus(123_456_789);
let mut encoder = version.encoder();
assert_eq!(encoder.current_chunk(), &version.to_consensus().to_le_bytes());
assert!(!encoder.advance());
}
#[test]
fn version_decoder_end_and_read_limit() {
let mut decoder = VersionDecoder::new();
let bytes_arr = Version::TWO.to_consensus().to_le_bytes();
let mut bytes = bytes_arr.as_slice();
assert!(decoder.read_limit() > 0);
let needs_more = decoder.push_bytes(&mut bytes).unwrap();
assert!(!needs_more);
assert!(bytes.is_empty());
assert_eq!(decoder.read_limit(), 0);
let decoded = decoder.end().unwrap();
assert_eq!(decoded, Version::TWO);
}
#[test]
fn version_decoder_default_roundtrip() {
let version = Version::from_consensus(123_456_789);
let mut decoder = VersionDecoder::default();
let consensus = version.to_consensus().to_le_bytes();
let mut bytes = consensus.as_slice();
decoder.push_bytes(&mut bytes).unwrap();
assert_eq!(decoder.end().unwrap(), version);
}
#[test]
#[cfg(feature = "alloc")]
fn block_decoder_error() {
let err_first = Block::decoder().end().unwrap_err();
assert!(matches!(err_first.0, encoding::Decoder2Error::First(_)));
assert!(!err_first.to_string().is_empty());
#[cfg(feature = "std")]
assert!(std::error::Error::source(&err_first).is_some());
let mut bytes = encoding::encode_to_vec(&dummy_header());
bytes.push(1u8);
let mut view = bytes.as_slice();
let mut decoder = Block::decoder();
assert!(decoder.push_bytes(&mut view).unwrap());
assert!(view.is_empty());
let err_second = decoder.end().unwrap_err();
assert!(matches!(err_second.0, encoding::Decoder2Error::Second(_)));
assert!(!err_second.to_string().is_empty());
#[cfg(feature = "std")]
assert!(std::error::Error::source(&err_second).is_some());
}
#[test]
#[cfg(feature = "alloc")]
fn header_decoder_error() {
let header_bytes = encoding::encode_to_vec(&dummy_header());
let lengths = [0usize, 4, 36, 68, 72, 76];
for &len in &lengths {
let mut decoder = Header::decoder();
let mut slice = header_bytes[..len].as_ref();
decoder.push_bytes(&mut slice).unwrap();
let err = decoder.end().unwrap_err();
match len {
0 => assert!(matches!(err, HeaderDecoderError::Version(_))),
4 => assert!(matches!(err, HeaderDecoderError::PrevBlockhash(_))),
36 => assert!(matches!(err, HeaderDecoderError::MerkleRoot(_))),
68 => assert!(matches!(err, HeaderDecoderError::Time(_))),
72 => assert!(matches!(err, HeaderDecoderError::Bits(_))),
76 => assert!(matches!(err, HeaderDecoderError::Nonce(_))),
_ => unreachable!(),
}
assert!(!err.to_string().is_empty());
#[cfg(feature = "std")]
assert!(std::error::Error::source(&err).is_some());
}
}
#[test]
#[cfg(feature = "alloc")]
fn invalid_block_error() {
#[cfg(feature = "std")]
use std::error::Error as _;
let variants = [
InvalidBlockError::InvalidMerkleRoot,
InvalidBlockError::InvalidWitnessCommitment,
InvalidBlockError::NoTransactions,
InvalidBlockError::InvalidCoinbase,
];
for variant in variants {
assert!(!variant.to_string().is_empty());
#[cfg(feature = "std")]
assert!(variant.source().is_none());
}
}
#[test]
#[cfg(feature = "alloc")]
fn version_decoder_error() {
let err = encoding::decode_from_slice::<Version>(&[0x01]).unwrap_err();
assert!(!err.to_string().is_empty());
#[cfg(feature = "std")]
assert!(std::error::Error::source(&err).is_some());
}
#[cfg(all(feature = "serde", feature = "hex", feature = "alloc"))]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct Adt {
#[serde(with = "crate::serde_as_consensus")]
header: Header,
#[serde(with = "crate::serde_as_consensus")]
block: Block,
}
#[test]
#[cfg(all(feature = "serde", feature = "hex", feature = "alloc"))]
fn can_serde_as_consensus_json() {
let orig = Adt { header: dummy_header(), block: dummy_block() };
let json = serde_json::to_string(&orig).expect("failed to serialize");
let want = "{\"header\":\"0100000099999999999999999999999999999999999999999999999999999999999999997777777777777777777777777777777777777777777777777777777777777777020000000300000004000000\",\"block\":\"01000000dcbadcbadcbadcbadcbadcbadcbadcbadcbadcbadcbadcbadcbadcbadcbadcbaabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd10c2e3674e61bc00000400000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff025151ffffffff0101000000000000000091500b00\"}";
assert_eq!(json, want);
let roundtrip: Adt = serde_json::from_str(&json).expect("failed to deserialize");
assert_eq!(roundtrip, orig);
}
#[test]
#[cfg(all(feature = "serde", feature = "hex", feature = "alloc"))]
fn can_serde_as_consensus_bincode() {
let orig = Adt { header: dummy_header(), block: dummy_block() };
let bytes = bincode::serialize(&orig).expect("failed to serialize");
let roundtrip: Adt = bincode::deserialize(&bytes).expect("failed to deserialize");
assert_eq!(roundtrip, orig);
}
}