1use super::len::scan_len;
2use crate::bsl::{BlockHeader, Transaction};
3use crate::{ParseResult, SResult, Visit, Visitor};
4
5#[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 #[cfg(feature = "bitcoin_hashes")]
39 pub fn block_hash(&self) -> crate::bitcoin_hashes::sha256d::Hash {
40 self.header.block_hash()
41 }
42
43 #[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 pub fn total_transactions(&self) -> usize {
55 self.total_txs
56 }
57
58 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 pub struct FindTransaction {
79 to_find: bitcoin::Txid,
80 tx_found: Option<bitcoin::Transaction>,
81 }
82 impl FindTransaction {
83 pub fn new(to_find: bitcoin::Txid) -> Self {
85 Self {
86 to_find,
87 tx_found: None,
88 }
89 }
90 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 }
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}