1use crate::observation::Chain;
2use crate::validation::{BlockValidationError, validate_block_integrity};
3use bitcoin::{
4 Block, BlockHash, Transaction,
5 consensus::{deserialize, deserialize_partial},
6};
7use litecoin::block::MwebBlock;
8use litecoin::consensus::{Decodable, Encodable, serialize};
9
10#[derive(Clone, Copy)]
11enum Encoding {
12 Core,
13 Esplora,
14}
15
16#[derive(Debug)]
23pub struct DecodedBlock {
24 block: Block,
25 raw_transactions: Vec<Vec<u8>>,
26}
27
28impl DecodedBlock {
29 pub fn block(&self) -> &Block {
30 &self.block
31 }
32
33 pub fn raw_transactions(&self) -> &[Vec<u8>] {
34 &self.raw_transactions
35 }
36
37 pub fn into_parts(self) -> (Block, Vec<Vec<u8>>) {
38 (self.block, self.raw_transactions)
39 }
40}
41
42#[derive(Debug)]
43pub enum DecodeError {
44 LimitExceeded,
45 Bitcoin(bitcoin::consensus::encode::Error),
46 Litecoin(litecoin::consensus::encode::Error),
47 Integrity(BlockValidationError),
48 NonCanonical,
49 UnsupportedMwebTransaction,
50 InvalidHogEx,
51}
52
53impl std::fmt::Display for DecodeError {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 match self {
56 Self::LimitExceeded => {
57 f.write_str("raw chain data exceeds 8,000,000 byte client limit")
58 }
59 Self::Bitcoin(cause) => write!(f, "decode Bitcoin data: {cause}"),
60 Self::Litecoin(cause) => write!(f, "decode Litecoin data: {cause}"),
61 Self::Integrity(cause) => std::fmt::Display::fmt(cause, f),
62 Self::NonCanonical => f.write_str("noncanonical or trailing chain data"),
63 Self::UnsupportedMwebTransaction => {
64 f.write_str("embedded confidential MWEB transactions are unsupported")
65 }
66 Self::InvalidHogEx => f.write_str("invalid HogEx placement"),
67 }
68 }
69}
70
71impl std::error::Error for DecodeError {
72 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
73 match self {
74 Self::Bitcoin(cause) => Some(cause),
75 Self::Litecoin(cause) => Some(cause),
76 Self::Integrity(cause) => Some(cause),
77 _ => None,
78 }
79 }
80}
81
82pub fn block(raw: &[u8], chain: Chain, expected_hash: BlockHash) -> Result<Block, DecodeError> {
83 block_with_transactions(raw, chain, expected_hash).map(|decoded| decoded.block)
84}
85
86pub fn esplora_block(
87 raw: &[u8],
88 chain: Chain,
89 expected_hash: BlockHash,
90) -> Result<Block, DecodeError> {
91 esplora_block_with_transactions(raw, chain, expected_hash).map(|decoded| decoded.block)
92}
93
94pub fn block_with_transactions(
96 raw: &[u8],
97 chain: Chain,
98 expected_hash: BlockHash,
99) -> Result<DecodedBlock, DecodeError> {
100 decode_block(raw, chain, expected_hash, Encoding::Core)
101}
102
103pub fn esplora_block_with_transactions(
105 raw: &[u8],
106 chain: Chain,
107 expected_hash: BlockHash,
108) -> Result<DecodedBlock, DecodeError> {
109 decode_block(raw, chain, expected_hash, Encoding::Esplora)
110}
111
112fn decode_block(
113 raw: &[u8],
114 chain: Chain,
115 expected_hash: BlockHash,
116 encoding: Encoding,
117) -> Result<DecodedBlock, DecodeError> {
118 bounded(raw)?;
119 let block = match chain {
120 Chain::BitcoinMainnet | Chain::BitcoinRegtest | Chain::BitcoinTestnet4 => {
121 bitcoin_decoded_block(raw)?
122 }
123 Chain::LitecoinMainnet | Chain::LitecoinTestnet => litecoin_block(raw, encoding)?,
124 };
125 validate_block_integrity(block.block(), expected_hash).map_err(DecodeError::Integrity)?;
126 Ok(block)
127}
128
129fn bitcoin_decoded_block(raw: &[u8]) -> Result<DecodedBlock, DecodeError> {
130 let block: Block = deserialize(raw).map_err(DecodeError::Bitcoin)?;
133 let mut remaining = &raw[bitcoin::block::Header::SIZE..];
134 let (_, prefix_bytes) = deserialize_partial::<bitcoin::consensus::encode::VarInt>(remaining)
135 .map_err(DecodeError::Bitcoin)?;
136 remaining = &remaining[prefix_bytes..];
137 let mut raw_transactions = Vec::new();
138 for _tx in &block.txdata {
139 let (_, consumed) =
140 deserialize_partial::<Transaction>(remaining).map_err(DecodeError::Bitcoin)?;
141 raw_transactions.push(remaining[..consumed].to_vec());
142 remaining = &remaining[consumed..];
143 }
144 Ok(DecodedBlock {
145 block,
146 raw_transactions,
147 })
148}
149
150pub fn bitcoin_block(raw: &[u8], expected_hash: BlockHash) -> Result<Block, DecodeError> {
151 block(raw, Chain::BitcoinRegtest, expected_hash)
152}
153
154pub fn transaction(raw: &[u8], chain: Chain) -> Result<Transaction, DecodeError> {
155 bounded(raw)?;
156 match chain {
157 Chain::BitcoinMainnet | Chain::BitcoinRegtest | Chain::BitcoinTestnet4 => {
158 deserialize(raw).map_err(DecodeError::Bitcoin)
159 }
160 Chain::LitecoinMainnet | Chain::LitecoinTestnet => {
161 let mut remaining = raw;
162 let tx = canonical::<litecoin::Transaction>(&mut remaining)?;
163 finished(remaining)?;
164 transparent(tx)
165 }
166 }
167}
168
169fn bounded(raw: &[u8]) -> Result<(), DecodeError> {
170 if raw.len() > 8_000_000 {
171 return Err(DecodeError::LimitExceeded);
172 }
173 Ok(())
174}
175
176fn canonical<T: Decodable + Encodable>(remaining: &mut &[u8]) -> Result<T, DecodeError> {
177 let before = *remaining;
178 let value = T::consensus_decode_from_finite_reader(remaining).map_err(DecodeError::Litecoin)?;
179 if serialize(&value) != before[..before.len() - remaining.len()] {
180 return Err(DecodeError::NonCanonical);
181 }
182 Ok(value)
183}
184
185fn finished(remaining: &[u8]) -> Result<(), DecodeError> {
186 if !remaining.is_empty() {
187 return Err(DecodeError::NonCanonical);
188 }
189 Ok(())
190}
191
192fn transparent(mut tx: litecoin::Transaction) -> Result<Transaction, DecodeError> {
193 if tx.mw_tx.iter().count() != 0 || tx.input.is_empty() {
194 return Err(DecodeError::UnsupportedMwebTransaction);
195 }
196 tx.is_hog_ex = false;
197 deserialize(&serialize(&tx)).map_err(DecodeError::Bitcoin)
198}
199
200fn litecoin_block(raw: &[u8], encoding: Encoding) -> Result<DecodedBlock, DecodeError> {
201 let mut remaining = raw;
202 let header = canonical::<litecoin::block::Header>(&mut remaining)?;
203 let transactions_start = remaining;
204 let transactions = canonical::<Vec<litecoin::Transaction>>(&mut remaining)?;
205 let mut transaction_bytes = &transactions_start[..transactions_start.len() - remaining.len()];
206 canonical::<litecoin::consensus::encode::VarInt>(&mut transaction_bytes)?;
207 let count = transactions.len();
208 let mut txdata = Vec::new();
209 let mut raw_transactions = Vec::new();
210 for (index, tx) in transactions.into_iter().enumerate() {
211 let length = serialize(&tx).len();
215 raw_transactions.push(transaction_bytes[..length].to_vec());
216 transaction_bytes = &transaction_bytes[length..];
217 if tx.is_hog_ex {
218 if index == 0 || index + 1 != count {
219 return Err(DecodeError::InvalidHogEx);
220 }
221 if !remaining.is_empty() || matches!(encoding, Encoding::Core) {
222 match canonical::<u8>(&mut remaining)? {
223 0 => (),
224 1 => {
225 canonical::<MwebBlock>(&mut remaining)?;
226 }
227 _ => return Err(DecodeError::NonCanonical),
228 }
229 }
230 }
231 txdata.push(transparent(tx)?);
232 }
233 finished(remaining)?;
234 Ok(DecodedBlock {
235 block: Block {
236 header: deserialize(&serialize(&header)).map_err(DecodeError::Bitcoin)?,
237 txdata,
238 },
239 raw_transactions,
240 })
241}