Merge rust-bitcoin/rust-bitcoin#1388: Refactor `merkle_tree::block`

861fdd6ab1 Put the `MerkleBlock` struct at the top of the file (Tobin C. Harding)
f0d968197a Put error at the bottom of the file (Tobin C. Harding)
19e094788f Use self for Error variant imports (Tobin C. Harding)
83c2a552db Put helper function below where its called (Tobin C. Harding)
5076579fb9 Fix indentation in pmt_tests macro (Tobin C. Harding)
a7edbfb52e Move hex data to tests/data (Tobin C. Harding)

Pull request description:

  PR 2 in the `merkle_tree::block` series, used to be on top of the now merged #1374.

  Do a bunch of refactorings in preparation for more invasive changes. This is a separate PR because, other than the first patch which moves hex strings to `tests/data/` the other patches are refactoring only patches, no logic changes. However the last patch is big and will be annoying to review - sorry about that. If you really oppose this basically stylistic patch putting important things first, the opposite of C code, please say and I'll try to stop doing it.

ACKs for top commit:
  Kixunil:
    ACK 861fdd6ab1
  apoelstra:
    ACK 861fdd6ab1

Tree-SHA512: 3da0a600898f490b602ab05a396061587d86ffef55697877885a8c611eff96e7382a2d816fe9594c100d378dc56fe7fdc88009a0343bc602b7f4c180836adbd3
This commit is contained in:
Andrew Poelstra 2023-02-10 14:21:43 +00:00
commit 6340c8e1bb
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
3 changed files with 147 additions and 220 deletions

View File

@ -48,45 +48,115 @@ use crate::consensus::encode::{self, Decodable, Encodable};
use crate::hash_types::{TxMerkleNode, Txid}; use crate::hash_types::{TxMerkleNode, Txid};
use crate::hashes::Hash; use crate::hashes::Hash;
use crate::io; use crate::io;
use crate::merkle_tree::MerkleBlockError::*;
use crate::prelude::*; use crate::prelude::*;
use self::MerkleBlockError::*;
/// An error when verifying the merkle block. /// Data structure that represents a block header paired to a partial merkle tree.
#[derive(Clone, PartialEq, Eq, Debug)] ///
#[non_exhaustive] /// NOTE: This assumes that the given Block has *at least* 1 transaction. If the Block has 0 txs,
pub enum MerkleBlockError { /// it will hit an assertion.
/// Merkle root in the header doesn't match to the root calculated from partial merkle tree. #[derive(PartialEq, Eq, Clone, Debug)]
MerkleRootMismatch, pub struct MerkleBlock {
/// Partial merkle tree contains no transactions. /// The block header
NoTransactions, pub header: block::Header,
/// There are too many transactions. /// Transactions making up a partial merkle tree
TooManyTransactions, pub txn: PartialMerkleTree,
/// General format error.
BadFormat(String),
} }
impl fmt::Display for MerkleBlockError { impl MerkleBlock {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { /// Create a MerkleBlock from a block, that contains proofs for specific txids.
use self::MerkleBlockError::*; ///
/// The `block` is a full block containing the header and transactions and `match_txids` is a
/// function that returns true for the ids that should be included in the partial merkle tree.
///
/// # Examples
///
/// ```rust
/// use bitcoin::hash_types::Txid;
/// use bitcoin::hashes::hex::FromHex;
/// use bitcoin::{Block, MerkleBlock};
///
/// // Block 80000
/// let block_bytes = Vec::from_hex("01000000ba8b9cda965dd8e536670f9ddec10e53aab14b20bacad2\
/// 7b9137190000000000190760b278fe7b8565fda3b968b918d5fd997f993b23674c0af3b6fde300b38f33\
/// a5914ce6ed5b1b01e32f5702010000000100000000000000000000000000000000000000000000000000\
/// 00000000000000ffffffff0704e6ed5b1b014effffffff0100f2052a01000000434104b68a50eaa0287e\
/// ff855189f949c1c6e5f58b37c88231373d8a59809cbae83059cc6469d65c665ccfd1cfeb75c6e8e19413\
/// bba7fbff9bc762419a76d87b16086eac000000000100000001a6b97044d03da79c005b20ea9c0e1a6d9d\
/// c12d9f7b91a5911c9030a439eed8f5000000004948304502206e21798a42fae0e854281abd38bacd1aee\
/// d3ee3738d9e1446618c4571d1090db022100e2ac980643b0b82c0e88ffdfec6b64e3e6ba35e7ba5fdd7d\
/// 5d6cc8d25c6b241501ffffffff0100f2052a010000001976a914404371705fa9bd789a2fcd52d2c580b6\
/// 5d35549d88ac00000000").unwrap();
/// let block: Block = bitcoin::consensus::deserialize(&block_bytes).unwrap();
///
/// // Create a merkle block containing a single transaction
/// let txid = "5a4ebf66822b0b2d56bd9dc64ece0bc38ee7844a23ff1d7320a88c5fdb2ad3e2".parse::<Txid>().unwrap();
/// let match_txids: Vec<Txid> = vec![txid].into_iter().collect();
/// let mb = MerkleBlock::from_block_with_predicate(&block, |t| match_txids.contains(t));
///
/// // Authenticate and extract matched transaction ids
/// let mut matches: Vec<Txid> = vec![];
/// let mut index: Vec<u32> = vec![];
/// 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
where
F: Fn(&Txid) -> bool,
{
let block_txids: Vec<_> = block.txdata.iter().map(Transaction::txid).collect();
Self::from_header_txids_with_predicate(&block.header, &block_txids, match_txids)
}
match *self { /// Create a MerkleBlock from the block's header and txids, that contain proofs for specific txids.
MerkleRootMismatch => write!(f, "merkle header root doesn't match to the root calculated from the partial merkle tree"), ///
NoTransactions => write!(f, "partial merkle tree contains no transactions"), /// The `header` is the block header, `block_txids` is the full list of txids included in the block and
TooManyTransactions => write!(f, "too many transactions"), /// `match_txids` is a function that returns true for the ids that should be included in the partial merkle tree.
BadFormat(ref s) => write!(f, "general format error: {}", s), pub fn from_header_txids_with_predicate<F>(
header: &block::Header,
block_txids: &[Txid],
match_txids: F,
) -> Self
where
F: Fn(&Txid) -> bool,
{
let matches: Vec<bool> = block_txids.iter().map(match_txids).collect();
let pmt = PartialMerkleTree::from_txids(block_txids, &matches);
MerkleBlock { header: *header, txn: pmt }
}
/// Extract the matching txid's represented by this partial merkle tree
/// and their respective indices within the partial tree.
/// returns Ok(()) on success, or error in case of failure
pub fn extract_matches(
&self,
matches: &mut Vec<Txid>,
indexes: &mut Vec<u32>,
) -> Result<(), MerkleBlockError> {
let merkle_root = self.txn.extract_matches(matches, indexes)?;
if merkle_root.eq(&self.header.merkle_root) {
Ok(())
} else {
Err(MerkleRootMismatch)
} }
} }
} }
#[cfg(feature = "std")] impl Encodable for MerkleBlock {
#[cfg_attr(docsrs, doc(cfg(feature = "std")))] fn consensus_encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
impl std::error::Error for MerkleBlockError { let len = self.header.consensus_encode(w)? + self.txn.consensus_encode(w)?;
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Ok(len)
use self::MerkleBlockError::*;
match *self {
MerkleRootMismatch | NoTransactions | TooManyTransactions | BadFormat(_) => None,
} }
}
impl Decodable for MerkleBlock {
fn consensus_decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
Ok(MerkleBlock {
header: Decodable::consensus_decode(r)?,
txn: Decodable::consensus_decode(r)?,
})
} }
} }
@ -386,112 +456,42 @@ impl Decodable for PartialMerkleTree {
} }
} }
/// Data structure that represents a block header paired to a partial merkle tree. /// An error when verifying the merkle block.
/// #[derive(Clone, PartialEq, Eq, Debug)]
/// NOTE: This assumes that the given Block has *at least* 1 transaction. If the Block has 0 txs, #[non_exhaustive]
/// it will hit an assertion. pub enum MerkleBlockError {
#[derive(PartialEq, Eq, Clone, Debug)] /// Merkle root in the header doesn't match to the root calculated from partial merkle tree.
pub struct MerkleBlock { MerkleRootMismatch,
/// The block header /// Partial merkle tree contains no transactions.
pub header: block::Header, NoTransactions,
/// Transactions making up a partial merkle tree /// There are too many transactions.
pub txn: PartialMerkleTree, TooManyTransactions,
/// General format error.
BadFormat(String),
} }
impl MerkleBlock { impl fmt::Display for MerkleBlockError {
/// Create a MerkleBlock from a block, that contains proofs for specific txids. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
/// use self::MerkleBlockError::*;
/// The `block` is a full block containing the header and transactions and `match_txids` is a
/// function that returns true for the ids that should be included in the partial merkle tree.
///
/// # Examples
///
/// ```rust
/// use bitcoin::hash_types::Txid;
/// use bitcoin::hashes::hex::FromHex;
/// use bitcoin::{Block, MerkleBlock};
///
/// // Block 80000
/// let block_bytes = Vec::from_hex("01000000ba8b9cda965dd8e536670f9ddec10e53aab14b20bacad2\
/// 7b9137190000000000190760b278fe7b8565fda3b968b918d5fd997f993b23674c0af3b6fde300b38f33\
/// a5914ce6ed5b1b01e32f5702010000000100000000000000000000000000000000000000000000000000\
/// 00000000000000ffffffff0704e6ed5b1b014effffffff0100f2052a01000000434104b68a50eaa0287e\
/// ff855189f949c1c6e5f58b37c88231373d8a59809cbae83059cc6469d65c665ccfd1cfeb75c6e8e19413\
/// bba7fbff9bc762419a76d87b16086eac000000000100000001a6b97044d03da79c005b20ea9c0e1a6d9d\
/// c12d9f7b91a5911c9030a439eed8f5000000004948304502206e21798a42fae0e854281abd38bacd1aee\
/// d3ee3738d9e1446618c4571d1090db022100e2ac980643b0b82c0e88ffdfec6b64e3e6ba35e7ba5fdd7d\
/// 5d6cc8d25c6b241501ffffffff0100f2052a010000001976a914404371705fa9bd789a2fcd52d2c580b6\
/// 5d35549d88ac00000000").unwrap();
/// let block: Block = bitcoin::consensus::deserialize(&block_bytes).unwrap();
///
/// // Create a merkle block containing a single transaction
/// let txid = "5a4ebf66822b0b2d56bd9dc64ece0bc38ee7844a23ff1d7320a88c5fdb2ad3e2".parse::<Txid>().unwrap();
/// let match_txids: Vec<Txid> = vec![txid].into_iter().collect();
/// let mb = MerkleBlock::from_block_with_predicate(&block, |t| match_txids.contains(t));
///
/// // Authenticate and extract matched transaction ids
/// let mut matches: Vec<Txid> = vec![];
/// let mut index: Vec<u32> = vec![];
/// 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
where
F: Fn(&Txid) -> bool,
{
let block_txids: Vec<_> = block.txdata.iter().map(Transaction::txid).collect();
Self::from_header_txids_with_predicate(&block.header, &block_txids, match_txids)
}
/// Create a MerkleBlock from the block's header and txids, that contain proofs for specific txids. match *self {
/// MerkleRootMismatch => write!(f, "merkle header root doesn't match to the root calculated from the partial merkle tree"),
/// The `header` is the block header, `block_txids` is the full list of txids included in the block and NoTransactions => write!(f, "partial merkle tree contains no transactions"),
/// `match_txids` is a function that returns true for the ids that should be included in the partial merkle tree. TooManyTransactions => write!(f, "too many transactions"),
pub fn from_header_txids_with_predicate<F>( BadFormat(ref s) => write!(f, "general format error: {}", s),
header: &block::Header,
block_txids: &[Txid],
match_txids: F,
) -> Self
where
F: Fn(&Txid) -> bool,
{
let matches: Vec<bool> = block_txids.iter().map(match_txids).collect();
let pmt = PartialMerkleTree::from_txids(block_txids, &matches);
MerkleBlock { header: *header, txn: pmt }
}
/// Extract the matching txid's represented by this partial merkle tree
/// and their respective indices within the partial tree.
/// returns Ok(()) on success, or error in case of failure
pub fn extract_matches(
&self,
matches: &mut Vec<Txid>,
indexes: &mut Vec<u32>,
) -> Result<(), MerkleBlockError> {
let merkle_root = self.txn.extract_matches(matches, indexes)?;
if merkle_root.eq(&self.header.merkle_root) {
Ok(())
} else {
Err(MerkleRootMismatch)
} }
} }
} }
impl Encodable for MerkleBlock { #[cfg(feature = "std")]
fn consensus_encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> { #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
let len = self.header.consensus_encode(w)? + self.txn.consensus_encode(w)?; impl std::error::Error for MerkleBlockError {
Ok(len) fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
} use self::MerkleBlockError::*;
}
impl Decodable for MerkleBlock { match *self {
fn consensus_decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, encode::Error> { MerkleRootMismatch | NoTransactions | TooManyTransactions | BadFormat(_) => None,
Ok(MerkleBlock { }
header: Decodable::consensus_decode(r)?,
txn: Decodable::consensus_decode(r)?,
})
} }
} }
@ -509,10 +509,6 @@ mod tests {
use crate::internal_macros::hex; use crate::internal_macros::hex;
use crate::{Block, Txid}; use crate::{Block, Txid};
/// accepts `pmt_test_$num`
#[cfg(feature = "rand-std")]
fn pmt_test_from_name(name: &str) { pmt_test(name[9..].parse().unwrap()) }
#[cfg(feature = "rand-std")] #[cfg(feature = "rand-std")]
macro_rules! pmt_tests { macro_rules! pmt_tests {
($($name:ident),* $(,)?) => { ($($name:ident),* $(,)?) => {
@ -523,7 +519,8 @@ mod tests {
} }
)* )*
} }
} }
#[cfg(feature = "rand-std")] #[cfg(feature = "rand-std")]
pmt_tests!( pmt_tests!(
pmt_test_1, pmt_test_1,
@ -540,6 +537,10 @@ mod tests {
pmt_test_4095 pmt_test_4095
); );
/// Parses the transaction count out of `name` with form: `pmt_test_$num`.
#[cfg(feature = "rand-std")]
fn pmt_test_from_name(name: &str) { pmt_test(name[9..].parse().unwrap()) }
#[cfg(feature = "rand-std")] #[cfg(feature = "rand-std")]
fn pmt_test(tx_count: usize) { fn pmt_test(tx_count: usize) {
use core::cmp::min; use core::cmp::min;
@ -638,22 +639,16 @@ mod tests {
fn merkleblock_serialization() { fn merkleblock_serialization() {
// Got it by running the rpc call // Got it by running the rpc call
// `gettxoutproof '["220ebc64e21abece964927322cba69180ed853bb187fbc6923bac7d010b9d87a"]'` // `gettxoutproof '["220ebc64e21abece964927322cba69180ed853bb187fbc6923bac7d010b9d87a"]'`
const MB_HEX: &str = let mb_hex = include_str!("../../tests/data/merkle_block.hex");
"0100000090f0a9f110702f808219ebea1173056042a714bad51b916cb6800000000000005275289558f51c\
9966699404ae2294730c3c9f9bda53523ce50e9b95e558da2fdb261b4d4c86041b1ab1bf930900000005fac\
7708a6e81b2a986dea60db2663840ed141130848162eb1bd1dee54f309a1b2ee1e12587e497ada70d9bd10d\
31e83f0a924825b96cb8d04e8936d793fb60db7ad8b910d0c7ba2369bc7f18bb53d80e1869ba2c32274996c\
ebe1ae264bc0e2289189ff0316cdc10511da71da757e553cada9f3b5b1434f3923673adb57d83caac392c38\
af156d6fc30b55fad4112df2b95531e68114e9ad10011e72f7b7cfdb025700";
let mb: MerkleBlock = deserialize(&hex!(MB_HEX)).unwrap(); let mb: MerkleBlock = deserialize(&hex!(mb_hex)).unwrap();
assert_eq!(get_block_13b8a().block_hash(), mb.header.block_hash()); assert_eq!(get_block_13b8a().block_hash(), mb.header.block_hash());
assert_eq!( assert_eq!(
mb.header.merkle_root, mb.header.merkle_root,
mb.txn.extract_matches(&mut vec![], &mut vec![]).unwrap() mb.txn.extract_matches(&mut vec![], &mut vec![]).unwrap()
); );
// Serialize again and check that it matches the original bytes // Serialize again and check that it matches the original bytes
assert_eq!(MB_HEX, serialize(&mb).to_lower_hex_string().as_str()); assert_eq!(mb_hex, serialize(&mb).to_lower_hex_string().as_str());
} }
/// Create a CMerkleBlock using a list of txids which will be found in the /// Create a CMerkleBlock using a list of txids which will be found in the
@ -735,78 +730,8 @@ mod tests {
/// Returns a real block (0000000000013b8ab2cd513b0261a14096412195a72a0c4827d229dcc7e0f7af) /// Returns a real block (0000000000013b8ab2cd513b0261a14096412195a72a0c4827d229dcc7e0f7af)
/// with 9 txs. /// with 9 txs.
fn get_block_13b8a() -> Block { fn get_block_13b8a() -> Block {
const BLOCK_HEX: &str = use crate::hashes::hex::FromHex;
"0100000090f0a9f110702f808219ebea1173056042a714bad51b916cb6800000000000005275289558f51c\ let block_hex = include_str!("../../tests/data/block_13b8a.hex");
9966699404ae2294730c3c9f9bda53523ce50e9b95e558da2fdb261b4d4c86041b1ab1bf930901000000010\ deserialize(&Vec::from_hex(block_hex).unwrap()).unwrap()
000000000000000000000000000000000000000000000000000000000000000ffffffff07044c86041b0146\
ffffffff0100f2052a01000000434104e18f7afbe4721580e81e8414fc8c24d7cfacf254bb5c7b949450c3e\
997c2dc1242487a8169507b631eb3771f2b425483fb13102c4eb5d858eef260fe70fbfae0ac000000000100\
00000196608ccbafa16abada902780da4dc35dafd7af05fa0da08cf833575f8cf9e836000000004a4930460\
22100dab24889213caf43ae6adc41cf1c9396c08240c199f5225acf45416330fd7dbd022100fe37900e0644\
bf574493a07fc5edba06dbc07c311b947520c2d514bc5725dcb401ffffffff0100f2052a010000001976a91\
4f15d1921f52e4007b146dfa60f369ed2fc393ce288ac000000000100000001fb766c1288458c2bafcfec81\
e48b24d98ec706de6b8af7c4e3c29419bfacb56d000000008c493046022100f268ba165ce0ad2e6d93f089c\
fcd3785de5c963bb5ea6b8c1b23f1ce3e517b9f022100da7c0f21adc6c401887f2bfd1922f11d76159cbc59\
7fbd756a23dcbb00f4d7290141042b4e8625a96127826915a5b109852636ad0da753c9e1d5606a50480cd0c\
40f1f8b8d898235e571fe9357d9ec842bc4bba1827daaf4de06d71844d0057707966affffffff0280969800\
000000001976a9146963907531db72d0ed1a0cfb471ccb63923446f388ac80d6e34c000000001976a914f06\
88ba1c0d1ce182c7af6741e02658c7d4dfcd388ac000000000100000002c40297f730dd7b5a99567eb8d27b\
78758f607507c52292d02d4031895b52f2ff010000008b483045022100f7edfd4b0aac404e5bab4fd3889e0\
c6c41aa8d0e6fa122316f68eddd0a65013902205b09cc8b2d56e1cd1f7f2fafd60a129ed94504c4ac7bdc67\
b56fe67512658b3e014104732012cb962afa90d31b25d8fb0e32c94e513ab7a17805c14ca4c3423e18b4fb5\
d0e676841733cb83abaf975845c9f6f2a8097b7d04f4908b18368d6fc2d68ecffffffffca5065ff9617cbcb\
a45eb23726df6498a9b9cafed4f54cbab9d227b0035ddefb000000008a473044022068010362a13c7f9919f\
a832b2dee4e788f61f6f5d344a7c2a0da6ae740605658022006d1af525b9a14a35c003b78b72bd59738cd67\
6f845d1ff3fc25049e01003614014104732012cb962afa90d31b25d8fb0e32c94e513ab7a17805c14ca4c34\
23e18b4fb5d0e676841733cb83abaf975845c9f6f2a8097b7d04f4908b18368d6fc2d68ecffffffff01001e\
c4110200000043410469ab4181eceb28985b9b4e895c13fa5e68d85761b7eee311db5addef76fa862186513\
4a221bd01f28ec9999ee3e021e60766e9d1f3458c115fb28650605f11c9ac000000000100000001cdaf2f75\
8e91c514655e2dc50633d1e4c84989f8aa90a0dbc883f0d23ed5c2fa010000008b48304502207ab51be6f12\
a1962ba0aaaf24a20e0b69b27a94fac5adf45aa7d2d18ffd9236102210086ae728b370e5329eead9accd880\
d0cb070aea0c96255fae6c4f1ddcce1fd56e014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf3\
3d789904f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffff\
ffff02404b4c00000000001976a9142b6ba7c9d796b75eef7942fc9288edd37c32f5c388ac002d310100000\
0001976a9141befba0cdc1ad56529371864d9f6cb042faa06b588ac000000000100000001b4a47603e71b61\
bc3326efd90111bf02d2f549b067f4c4a8fa183b57a0f800cb010000008a4730440220177c37f9a505c3f1a\
1f0ce2da777c339bd8339ffa02c7cb41f0a5804f473c9230220585b25a2ee80eb59292e52b987dad92acb0c\
64eced92ed9ee105ad153cdb12d001410443bd44f683467e549dae7d20d1d79cbdb6df985c6e9c029c8d0c6\
cb46cc1a4d3cf7923c5021b27f7a0b562ada113bc85d5fda5a1b41e87fe6e8802817cf69996ffffffff0280\
651406000000001976a9145505614859643ab7b547cd7f1f5e7e2a12322d3788ac00aa0271000000001976a\
914ea4720a7a52fc166c55ff2298e07baf70ae67e1b88ac00000000010000000586c62cd602d219bb60edb1\
4a3e204de0705176f9022fe49a538054fb14abb49e010000008c493046022100f2bc2aba2534becbdf062eb\
993853a42bbbc282083d0daf9b4b585bd401aa8c9022100b1d7fd7ee0b95600db8535bbf331b19eed8d961f\
7a8e54159c53675d5f69df8c014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf33d789904f07d\
0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffffffff03ad0e58\
ccdac3df9dc28a218bcf6f1997b0a93306faaa4b3a28ae83447b2179010000008b483045022100be12b2937\
179da88599e27bb31c3525097a07cdb52422d165b3ca2f2020ffcf702200971b51f853a53d644ebae9ec8f3\
512e442b1bcb6c315a5b491d119d10624c83014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf3\
3d789904f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffff\
ffff2acfcab629bbc8685792603762c921580030ba144af553d271716a95089e107b010000008b483045022\
100fa579a840ac258871365dd48cd7552f96c8eea69bd00d84f05b283a0dab311e102207e3c0ee9234814cf\
bb1b659b83671618f45abc1326b9edcc77d552a4f2a805c0014104462e76fd4067b3a0aa42070082dcb0bf2\
f388b6495cf33d789904f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312ef1c0e8eb\
bb12dcd4ffffffffdcdc6023bbc9944a658ddc588e61eacb737ddf0a3cd24f113b5a8634c517fcd20000000\
08b4830450221008d6df731df5d32267954bd7d2dda2302b74c6c2a6aa5c0ca64ecbabc1af03c75022010e5\
5c571d65da7701ae2da1956c442df81bbf076cdbac25133f99d98a9ed34c014104462e76fd4067b3a0aa420\
70082dcb0bf2f388b6495cf33d789904f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15\
312ef1c0e8ebbb12dcd4ffffffffe15557cd5ce258f479dfd6dc6514edf6d7ed5b21fcfa4a038fd69f06b83\
ac76e010000008b483045022023b3e0ab071eb11de2eb1cc3a67261b866f86bf6867d4558165f7c8c8aca2d\
86022100dc6e1f53a91de3efe8f63512850811f26284b62f850c70ca73ed5de8771fb451014104462e76fd4\
067b3a0aa42070082dcb0bf2f388b6495cf33d789904f07d0f55c40fbd4b82963c69b3dc31895d0c772c812\
b1d5fbcade15312ef1c0e8ebbb12dcd4ffffffff01404b4c00000000001976a9142b6ba7c9d796b75eef794\
2fc9288edd37c32f5c388ac00000000010000000166d7577163c932b4f9690ca6a80b6e4eb001f0a2fa9023\
df5595602aae96ed8d000000008a4730440220262b42546302dfb654a229cefc86432b89628ff259dc87edd\
1154535b16a67e102207b4634c020a97c3e7bbd0d4d19da6aa2269ad9dded4026e896b213d73ca4b63f0141\
04979b82d02226b3a4597523845754d44f13639e3bf2df5e82c6aab2bdc79687368b01b1ab8b19875ae3c90\
d661a3d0a33161dab29934edeb36aa01976be3baf8affffffff02404b4c00000000001976a9144854e695a0\
2af0aeacb823ccbc272134561e0a1688ac40420f00000000001976a914abee93376d6b37b5c2940655a6fca\
f1c8e74237988ac0000000001000000014e3f8ef2e91349a9059cb4f01e54ab2597c1387161d3da89919f7e\
a6acdbb371010000008c49304602210081f3183471a5ca22307c0800226f3ef9c353069e0773ac76bb58065\
4d56aa523022100d4c56465bdc069060846f4fbf2f6b20520b2a80b08b168b31e66ddb9c694e24001410497\
6c79848e18251612f8940875b2b08d06e6dc73b9840e8860c066b7e87432c477e9a59a453e71e6d76d5fe34\
058b800a098fc1740ce3012e8fc8a00c96af966ffffffff02c0e1e400000000001976a9144134e75a6fcb60\
42034aab5e18570cf1f844f54788ac404b4c00000000001976a9142b6ba7c9d796b75eef7942fc9288edd37\
c32f5c388ac00000000";
deserialize(&hex!(BLOCK_HEX)).unwrap()
} }
} }

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
0100000090f0a9f110702f808219ebea1173056042a714bad51b916cb6800000000000005275289558f51c9966699404ae2294730c3c9f9bda53523ce50e9b95e558da2fdb261b4d4c86041b1ab1bf930900000005fac7708a6e81b2a986dea60db2663840ed141130848162eb1bd1dee54f309a1b2ee1e12587e497ada70d9bd10d31e83f0a924825b96cb8d04e8936d793fb60db7ad8b910d0c7ba2369bc7f18bb53d80e1869ba2c32274996cebe1ae264bc0e2289189ff0316cdc10511da71da757e553cada9f3b5b1434f3923673adb57d83caac392c38af156d6fc30b55fad4112df2b95531e68114e9ad10011e72f7b7cfdb025700