bitcoin_slices/bsl/
block.rs

1use super::len::scan_len;
2use crate::bsl::{BlockHeader, Transaction};
3use crate::{ParseResult, SResult, Visit, Visitor};
4
5/// A Bitcoin block.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct Block<'a> {
8    slice: &'a [u8],
9    header: BlockHeader<'a>,
10    total_txs: usize,
11}
12
13impl<'a> Visit<'a> for Block<'a> {
14    fn visit<'b, V: Visitor>(slice: &'a [u8], visit: &'b mut V) -> SResult<'a, Self> {
15        let header = BlockHeader::visit(slice, visit)?;
16        let mut consumed = 0;
17        let total_txs = scan_len(header.remaining(), &mut consumed)? as usize;
18        consumed += 80;
19
20        visit.visit_block_begin(total_txs);
21        for _ in 0..total_txs {
22            let tx = Transaction::visit(&slice[consumed..], visit)?;
23            consumed += tx.consumed();
24        }
25
26        let (slice, remaining) = slice.split_at(consumed);
27        let parsed = Block {
28            slice,
29            header: header.parsed_owned(),
30            total_txs,
31        };
32        Ok(ParseResult::new(remaining, parsed))
33    }
34}
35
36impl<'a> Block<'a> {
37    /// Returns the hash of this block
38    #[cfg(feature = "bitcoin_hashes")]
39    pub fn block_hash(&self) -> crate::bitcoin_hashes::sha256d::Hash {
40        self.header.block_hash()
41    }
42
43    /// Calculate the block hash using the sha2 crate.
44    /// NOTE: the result type is not displayed backwards when converted to string.
45    #[cfg(feature = "sha2")]
46    pub fn block_hash_sha2(
47        &self,
48    ) -> crate::sha2::digest::generic_array::GenericArray<u8, crate::sha2::digest::typenum::U32>
49    {
50        self.header.block_hash_sha2()
51    }
52
53    /// Returns the total transactions in this block
54    pub fn total_transactions(&self) -> usize {
55        self.total_txs
56    }
57
58    /// Returns the header in this block
59    pub fn header(&self) -> &BlockHeader {
60        &self.header
61    }
62}
63
64impl<'a> AsRef<[u8]> for Block<'a> {
65    fn as_ref(&self) -> &[u8] {
66        self.slice
67    }
68}
69
70#[cfg(all(feature = "bitcoin", feature = "sha2"))]
71pub mod visitor {
72    use core::ops::ControlFlow;
73
74    use bitcoin::consensus::Decodable;
75    use bitcoin::hashes::Hash;
76
77    /// Implement a visitor to find a Transaction in a Block given its txid
78    pub struct FindTransaction {
79        to_find: bitcoin::Txid,
80        tx_found: Option<bitcoin::Transaction>,
81    }
82    impl FindTransaction {
83        /// Creates [`FindTransaction`] for txid `to_find`
84        pub fn new(to_find: bitcoin::Txid) -> Self {
85            Self {
86                to_find,
87                tx_found: None,
88            }
89        }
90        /// Returns the transaction found if any
91        pub fn tx_found(self) -> Option<bitcoin::Transaction> {
92            self.tx_found
93        }
94    }
95    impl crate::Visitor for FindTransaction {
96        fn visit_transaction(&mut self, tx: &crate::bsl::Transaction) -> ControlFlow<()> {
97            let current = bitcoin::Txid::from_slice(tx.txid_sha2().as_slice()).expect("32");
98            if self.to_find == current {
99                let tx_found = bitcoin::Transaction::consensus_decode(&mut tx.as_ref())
100                    .expect("slice validated");
101                self.tx_found = Some(tx_found);
102                ControlFlow::Break(())
103            } else {
104                ControlFlow::Continue(())
105            }
106        }
107    }
108}
109
110#[cfg(test)]
111mod test {
112    use bitcoin_test_data::blocks::mainnet_702861;
113
114    use crate::{
115        bsl::{Block, BlockHeader},
116        test_common::GENESIS_BLOCK,
117        Parse,
118    };
119
120    const FUZZ_DATA: [u8; 132] = [
121        255, 255, 255, 255, 1, 0, 0, 0, 255, 255, 255, 255, 255, 2, 0, 0, 0, 0, 65, 0, 0, 0, 255,
122        255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 182, 182, 182,
123        255, 255, 182, 182, 182, 182, 182, 182, 182, 182, 255, 255, 255, 255, 255, 255, 255, 255,
124        255, 0, 0, 0, 0, 0, 0, 0, 0, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251,
125        251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 0, 0, 0, 255, 255, 255, 255, 255, 255,
126        255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 253, 255,
127        255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 10,
128    ];
129
130    #[test]
131    fn parse_block() {
132        let block_header = BlockHeader::parse(&GENESIS_BLOCK).unwrap();
133        let block = Block::parse(&GENESIS_BLOCK).unwrap();
134
135        assert_eq!(block.remaining(), &[][..]);
136        assert_eq!(
137            block.parsed(),
138            &Block {
139                slice: &GENESIS_BLOCK,
140                header: block_header.parsed_owned(),
141                total_txs: 1
142            }
143        );
144        assert_eq!(block.consumed(), 285);
145
146        let block = Block::parse(mainnet_702861()).unwrap();
147        assert_eq!(block.remaining(), &[][..]);
148        assert_eq!(
149            block.parsed(),
150            &Block {
151                slice: mainnet_702861(),
152                header: BlockHeader::parse(mainnet_702861()).unwrap().parsed_owned(),
153                total_txs: 2500,
154            }
155        );
156        assert_eq!(block.consumed(), 1381836);
157
158        let block = Block::parse(&FUZZ_DATA).unwrap_err();
159        assert_eq!(block, crate::Error::MoreBytesNeeded);
160        // let mut iter = block.parsed.transactions();
161        // let genesis_tx = iter.next().unwrap();
162        // assert_eq!(genesis_tx.as_ref(), GENESIS_TX);
163        // assert!(iter.next().is_none())
164    }
165
166    #[cfg(all(feature = "bitcoin", feature = "sha2"))]
167    #[test]
168    fn find_tx() {
169        use crate::Visit;
170        use bitcoin_test_data::blocks::mainnet_702861;
171        use core::str::FromStr;
172
173        let txid = bitcoin::Txid::from_str(
174            "416a5f96cb63e7649f6f272e7f82a43a97bcf6cfc46184c733344de96ff1e433",
175        )
176        .unwrap();
177        let mut visitor = crate::bsl::FindTransaction::new(txid.clone());
178        let _ = Block::visit(&mainnet_702861(), &mut visitor);
179        let tx = visitor.tx_found().unwrap();
180        assert_eq!(tx.compute_txid(), txid);
181    }
182
183    #[cfg(target_pointer_width = "64")]
184    #[test]
185    fn size_of() {
186        use core::ops::ControlFlow;
187
188        assert_eq!(std::mem::size_of::<Block>(), 56);
189
190        assert_eq!(std::mem::size_of::<ControlFlow<()>>(), 1);
191    }
192}