kaspa_consensus/pipeline/body_processor/
body_validation_in_isolation.rs

1use std::{collections::HashSet, sync::Arc};
2
3use super::BlockBodyProcessor;
4use crate::errors::{BlockProcessResult, RuleError};
5use kaspa_consensus_core::{block::Block, merkle::calc_hash_merkle_root, tx::TransactionOutpoint};
6
7impl BlockBodyProcessor {
8    pub fn validate_body_in_isolation(self: &Arc<Self>, block: &Block) -> BlockProcessResult<u64> {
9        let storage_mass_activated = block.header.daa_score > self.storage_mass_activation_daa_score;
10
11        Self::check_has_transactions(block)?;
12        Self::check_hash_merkle_root(block, storage_mass_activated)?;
13        Self::check_only_one_coinbase(block)?;
14        self.check_transactions_in_isolation(block)?;
15        let mass = self.check_block_mass(block, storage_mass_activated)?;
16        self.check_duplicate_transactions(block)?;
17        self.check_block_double_spends(block)?;
18        self.check_no_chained_transactions(block)?;
19        Ok(mass)
20    }
21
22    fn check_has_transactions(block: &Block) -> BlockProcessResult<()> {
23        // We expect the outer flow to not queue blocks with no transactions for body validation,
24        // but we still check it in case the outer flow changes.
25        if block.transactions.is_empty() {
26            return Err(RuleError::NoTransactions);
27        }
28        Ok(())
29    }
30
31    fn check_hash_merkle_root(block: &Block, storage_mass_activated: bool) -> BlockProcessResult<()> {
32        let calculated = calc_hash_merkle_root(block.transactions.iter(), storage_mass_activated);
33        if calculated != block.header.hash_merkle_root {
34            return Err(RuleError::BadMerkleRoot(block.header.hash_merkle_root, calculated));
35        }
36        Ok(())
37    }
38
39    fn check_only_one_coinbase(block: &Block) -> BlockProcessResult<()> {
40        if !block.transactions[0].is_coinbase() {
41            return Err(RuleError::FirstTxNotCoinbase);
42        }
43
44        if let Some(i) = block.transactions[1..].iter().position(|tx| tx.is_coinbase()) {
45            return Err(RuleError::MultipleCoinbases(i));
46        }
47
48        Ok(())
49    }
50
51    fn check_transactions_in_isolation(self: &Arc<Self>, block: &Block) -> BlockProcessResult<()> {
52        for tx in block.transactions.iter() {
53            if let Err(e) = self.transaction_validator.validate_tx_in_isolation(tx) {
54                return Err(RuleError::TxInIsolationValidationFailed(tx.id(), e));
55            }
56        }
57        Ok(())
58    }
59
60    fn check_block_mass(self: &Arc<Self>, block: &Block, storage_mass_activated: bool) -> BlockProcessResult<u64> {
61        let mut total_mass: u64 = 0;
62        if storage_mass_activated {
63            for tx in block.transactions.iter() {
64                // This is only the compute part of the mass, the storage part cannot be computed here
65                let calculated_tx_compute_mass = self.mass_calculator.calc_tx_compute_mass(tx);
66                let committed_contextual_mass = tx.mass();
67                // We only check the lower-bound here, a precise check of the mass commitment
68                // is done when validating the tx in context
69                if committed_contextual_mass < calculated_tx_compute_mass {
70                    return Err(RuleError::MassFieldTooLow(tx.id(), committed_contextual_mass, calculated_tx_compute_mass));
71                }
72                // Sum over the committed masses
73                total_mass = total_mass.saturating_add(committed_contextual_mass);
74                if total_mass > self.max_block_mass {
75                    return Err(RuleError::ExceedsMassLimit(self.max_block_mass));
76                }
77            }
78        } else {
79            for tx in block.transactions.iter() {
80                let calculated_tx_mass = self.mass_calculator.calc_tx_compute_mass(tx);
81                total_mass = total_mass.saturating_add(calculated_tx_mass);
82                if total_mass > self.max_block_mass {
83                    return Err(RuleError::ExceedsMassLimit(self.max_block_mass));
84                }
85            }
86        }
87        Ok(total_mass)
88    }
89
90    fn check_block_double_spends(self: &Arc<Self>, block: &Block) -> BlockProcessResult<()> {
91        let mut existing = HashSet::new();
92        for input in block.transactions.iter().flat_map(|tx| &tx.inputs) {
93            if !existing.insert(input.previous_outpoint) {
94                return Err(RuleError::DoubleSpendInSameBlock(input.previous_outpoint));
95            }
96        }
97        Ok(())
98    }
99
100    fn check_no_chained_transactions(self: &Arc<Self>, block: &Block) -> BlockProcessResult<()> {
101        let mut block_created_outpoints = HashSet::new();
102        for tx in block.transactions.iter() {
103            for index in 0..tx.outputs.len() {
104                block_created_outpoints.insert(TransactionOutpoint { transaction_id: tx.id(), index: index as u32 });
105            }
106        }
107
108        for input in block.transactions.iter().flat_map(|tx| &tx.inputs) {
109            if block_created_outpoints.contains(&input.previous_outpoint) {
110                return Err(RuleError::ChainedTransaction(input.previous_outpoint));
111            }
112        }
113        Ok(())
114    }
115
116    fn check_duplicate_transactions(self: &Arc<Self>, block: &Block) -> BlockProcessResult<()> {
117        let mut ids = HashSet::new();
118        for tx in block.transactions.iter() {
119            if !ids.insert(tx.id()) {
120                return Err(RuleError::DuplicateTransactions(tx.id()));
121            }
122        }
123
124        Ok(())
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use crate::{
131        config::{Config, ConfigBuilder},
132        consensus::test_consensus::TestConsensus,
133        errors::RuleError,
134        params::MAINNET_PARAMS,
135    };
136    use kaspa_consensus_core::{
137        api::{BlockValidationFutures, ConsensusApi},
138        block::MutableBlock,
139        header::Header,
140        merkle::calc_hash_merkle_root as calc_hash_merkle_root_with_options,
141        subnets::{SUBNETWORK_ID_COINBASE, SUBNETWORK_ID_NATIVE},
142        tx::{scriptvec, ScriptPublicKey, Transaction, TransactionId, TransactionInput, TransactionOutpoint, TransactionOutput},
143    };
144    use kaspa_core::assert_match;
145    use kaspa_hashes::Hash;
146
147    fn calc_hash_merkle_root<'a>(txs: impl ExactSizeIterator<Item = &'a Transaction>) -> Hash {
148        calc_hash_merkle_root_with_options(txs, false)
149    }
150
151    #[test]
152    fn validate_body_in_isolation_test() {
153        let consensus = TestConsensus::new(&Config::new(MAINNET_PARAMS));
154        let wait_handles = consensus.init();
155
156        let body_processor = consensus.block_body_processor();
157        let example_block = MutableBlock::new(
158            Header::new_finalized(
159                0,
160                vec![vec![
161                    Hash::from_slice(&[
162                        0x16, 0x5e, 0x38, 0xe8, 0xb3, 0x91, 0x45, 0x95, 0xd9, 0xc6, 0x41, 0xf3, 0xb8, 0xee, 0xc2, 0xf3, 0x46, 0x11,
163                        0x89, 0x6b, 0x82, 0x1a, 0x68, 0x3b, 0x7a, 0x4e, 0xde, 0xfe, 0x2c, 0x00, 0x00, 0x00,
164                    ]),
165                    Hash::from_slice(&[
166                        0x4b, 0xb0, 0x75, 0x35, 0xdf, 0xd5, 0x8e, 0x0b, 0x3c, 0xd6, 0x4f, 0xd7, 0x15, 0x52, 0x80, 0x87, 0x2a, 0x04,
167                        0x71, 0xbc, 0xf8, 0x30, 0x95, 0x52, 0x6a, 0xce, 0x0e, 0x38, 0xc6, 0x00, 0x00, 0x00,
168                    ]),
169                ]],
170                Hash::from_slice(&[
171                    0x46, 0xec, 0xf4, 0x5b, 0xe3, 0xba, 0xca, 0x34, 0x9d, 0xfe, 0x8a, 0x78, 0xde, 0xaf, 0x05, 0x3b, 0x0a, 0xa6, 0xd5,
172                    0x38, 0x97, 0x4d, 0xa5, 0x0f, 0xd6, 0xef, 0xb4, 0xd2, 0x66, 0xbc, 0x8d, 0x21,
173                ]),
174                Default::default(),
175                Default::default(),
176                0x17305aa654a,
177                0x207fffff,
178                1,
179                0,
180                0.into(),
181                9,
182                Default::default(),
183            ),
184            vec![
185                Transaction::new(
186                    0,
187                    vec![],
188                    vec![TransactionOutput {
189                        value: 0x12a05f200,
190                        script_public_key: ScriptPublicKey::new(
191                            0,
192                            scriptvec!(
193                                0xa9, 0x14, 0xda, 0x17, 0x45, 0xe9, 0xb5, 0x49, 0xbd, 0x0b, 0xfa, 0x1a, 0x56, 0x99, 0x71, 0xc7, 0x7e,
194                                0xba, 0x30, 0xcd, 0x5a, 0x4b, 0x87
195                            ),
196                        ),
197                    }],
198                    0,
199                    SUBNETWORK_ID_COINBASE,
200                    0,
201                    vec![9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
202                ),
203                Transaction::new(
204                    0,
205                    vec![
206                        TransactionInput {
207                            previous_outpoint: TransactionOutpoint {
208                                transaction_id: TransactionId::from_slice(&[
209                                    0x16, 0x5e, 0x38, 0xe8, 0xb3, 0x91, 0x45, 0x95, 0xd9, 0xc6, 0x41, 0xf3, 0xb8, 0xee, 0xc2, 0xf3,
210                                    0x46, 0x11, 0x89, 0x6b, 0x82, 0x1a, 0x68, 0x3b, 0x7a, 0x4e, 0xde, 0xfe, 0x2c, 0x00, 0x00, 0x00,
211                                ]),
212                                index: 0xffffffff,
213                            },
214                            signature_script: vec![],
215                            sequence: u64::MAX,
216                            sig_op_count: 0,
217                        },
218                        TransactionInput {
219                            previous_outpoint: TransactionOutpoint {
220                                transaction_id: TransactionId::from_slice(&[
221                                    0x4b, 0xb0, 0x75, 0x35, 0xdf, 0xd5, 0x8e, 0x0b, 0x3c, 0xd6, 0x4f, 0xd7, 0x15, 0x52, 0x80, 0x87,
222                                    0x2a, 0x04, 0x71, 0xbc, 0xf8, 0x30, 0x95, 0x52, 0x6a, 0xce, 0x0e, 0x38, 0xc6, 0x00, 0x00, 0x00,
223                                ]),
224                                index: 0xffffffff,
225                            },
226                            signature_script: vec![],
227                            sequence: u64::MAX,
228                            sig_op_count: 0,
229                        },
230                    ],
231                    vec![],
232                    0,
233                    SUBNETWORK_ID_NATIVE,
234                    0,
235                    vec![],
236                ),
237                Transaction::new(
238                    0,
239                    vec![TransactionInput {
240                        previous_outpoint: TransactionOutpoint {
241                            transaction_id: TransactionId::from_slice(&[
242                                0x03, 0x2e, 0x38, 0xe9, 0xc0, 0xa8, 0x4c, 0x60, 0x46, 0xd6, 0x87, 0xd1, 0x05, 0x56, 0xdc, 0xac, 0xc4,
243                                0x1d, 0x27, 0x5e, 0xc5, 0x5f, 0xc0, 0x07, 0x79, 0xac, 0x88, 0xfd, 0xf3, 0x57, 0xa1, 0x87,
244                            ]),
245                            index: 0,
246                        },
247                        signature_script: vec![
248                            0x49, // OP_DATA_73
249                            0x30, 0x46, 0x02, 0x21, 0x00, 0xc3, 0x52, 0xd3, 0xdd, 0x99, 0x3a, 0x98, 0x1b, 0xeb, 0xa4, 0xa6, 0x3a,
250                            0xd1, 0x5c, 0x20, 0x92, 0x75, 0xca, 0x94, 0x70, 0xab, 0xfc, 0xd5, 0x7d, 0xa9, 0x3b, 0x58, 0xe4, 0xeb,
251                            0x5d, 0xce, 0x82, 0x02, 0x21, 0x00, 0x84, 0x07, 0x92, 0xbc, 0x1f, 0x45, 0x60, 0x62, 0x81, 0x9f, 0x15,
252                            0xd3, 0x3e, 0xe7, 0x05, 0x5c, 0xf7, 0xb5, 0xee, 0x1a, 0xf1, 0xeb, 0xcc, 0x60, 0x28, 0xd9, 0xcd, 0xb1,
253                            0xc3, 0xaf, 0x77, 0x48, 0x01, // 73-byte signature
254                            0x41, // OP_DATA_65
255                            0x04, 0xf4, 0x6d, 0xb5, 0xe9, 0xd6, 0x1a, 0x9d, 0xc2, 0x7b, 0x8d, 0x64, 0xad, 0x23, 0xe7, 0x38, 0x3a,
256                            0x4e, 0x6c, 0xa1, 0x64, 0x59, 0x3c, 0x25, 0x27, 0xc0, 0x38, 0xc0, 0x85, 0x7e, 0xb6, 0x7e, 0xe8, 0xe8,
257                            0x25, 0xdc, 0xa6, 0x50, 0x46, 0xb8, 0x2c, 0x93, 0x31, 0x58, 0x6c, 0x82, 0xe0, 0xfd, 0x1f, 0x63, 0x3f,
258                            0x25, 0xf8, 0x7c, 0x16, 0x1b, 0xc6, 0xf8, 0xa6, 0x30, 0x12, 0x1d, 0xf2, 0xb3, 0xd3, // 65-byte pubkey
259                        ],
260                        sequence: u64::MAX,
261                        sig_op_count: 0,
262                    }],
263                    vec![
264                        TransactionOutput {
265                            value: 0x2123e300,
266                            script_public_key: ScriptPublicKey::new(
267                                0,
268                                scriptvec!(
269                                    0x76, // OP_DUP
270                                    0xa9, // OP_HASH160
271                                    0x14, // OP_DATA_20
272                                    0xc3, 0x98, 0xef, 0xa9, 0xc3, 0x92, 0xba, 0x60, 0x13, 0xc5, 0xe0, 0x4e, 0xe7, 0x29, 0x75, 0x5e,
273                                    0xf7, 0xf5, 0x8b, 0x32, 0x88, // OP_EQUALVERIFY
274                                    0xac  // OP_CHECKSIG
275                                ),
276                            ),
277                        },
278                        TransactionOutput {
279                            value: 0x108e20f00,
280                            script_public_key: ScriptPublicKey::new(
281                                0,
282                                scriptvec!(
283                                    0x76, // OP_DUP
284                                    0xa9, // OP_HASH160
285                                    0x14, // OP_DATA_20
286                                    0x94, 0x8c, 0x76, 0x5a, 0x69, 0x14, 0xd4, 0x3f, 0x2a, 0x7a, 0xc1, 0x77, 0xda, 0x2c, 0x2f, 0x6b,
287                                    0x52, 0xde, 0x3d, 0x7c, 0x88, // OP_EQUALVERIFY
288                                    0xac  // OP_CHECKSIG
289                                ),
290                            ),
291                        },
292                    ],
293                    0,
294                    SUBNETWORK_ID_NATIVE,
295                    0,
296                    vec![],
297                ),
298                Transaction::new(
299                    0,
300                    vec![TransactionInput {
301                        previous_outpoint: TransactionOutpoint {
302                            transaction_id: TransactionId::from_slice(&[
303                                0xc3, 0x3e, 0xbf, 0xf2, 0xa7, 0x09, 0xf1, 0x3d, 0x9f, 0x9a, 0x75, 0x69, 0xab, 0x16, 0xa3, 0x27, 0x86,
304                                0xaf, 0x7d, 0x7e, 0x2d, 0xe0, 0x92, 0x65, 0xe4, 0x1c, 0x61, 0xd0, 0x78, 0x29, 0x4e, 0xcf,
305                            ]),
306                            index: 1,
307                        },
308                        signature_script: vec![
309                            0x47, // OP_DATA_71
310                            0x30, 0x44, 0x02, 0x20, 0x03, 0x2d, 0x30, 0xdf, 0x5e, 0xe6, 0xf5, 0x7f, 0xa4, 0x6c, 0xdd, 0xb5, 0xeb,
311                            0x8d, 0x0d, 0x9f, 0xe8, 0xde, 0x6b, 0x34, 0x2d, 0x27, 0x94, 0x2a, 0xe9, 0x0a, 0x32, 0x31, 0xe0, 0xba,
312                            0x33, 0x3e, 0x02, 0x20, 0x3d, 0xee, 0xe8, 0x06, 0x0f, 0xdc, 0x70, 0x23, 0x0a, 0x7f, 0x5b, 0x4a, 0xd7,
313                            0xd7, 0xbc, 0x3e, 0x62, 0x8c, 0xbe, 0x21, 0x9a, 0x88, 0x6b, 0x84, 0x26, 0x9e, 0xae, 0xb8, 0x1e, 0x26,
314                            0xb4, 0xfe, 0x01, 0x41, // OP_DATA_65
315                            0x04, 0xae, 0x31, 0xc3, 0x1b, 0xf9, 0x12, 0x78, 0xd9, 0x9b, 0x83, 0x77, 0xa3, 0x5b, 0xbc, 0xe5, 0xb2,
316                            0x7d, 0x9f, 0xff, 0x15, 0x45, 0x68, 0x39, 0xe9, 0x19, 0x45, 0x3f, 0xc7, 0xb3, 0xf7, 0x21, 0xf0, 0xba,
317                            0x40, 0x3f, 0xf9, 0x6c, 0x9d, 0xee, 0xb6, 0x80, 0xe5, 0xfd, 0x34, 0x1c, 0x0f, 0xc3, 0xa7, 0xb9, 0x0d,
318                            0xa4, 0x63, 0x1e, 0xe3, 0x95, 0x60, 0x63, 0x9d, 0xb4, 0x62, 0xe9, 0xcb, 0x85, 0x0f, // 65-byte pubkey
319                        ],
320                        sequence: u64::MAX,
321                        sig_op_count: 0,
322                    }],
323                    vec![
324                        TransactionOutput {
325                            value: 0xf4240,
326                            script_public_key: ScriptPublicKey::new(
327                                0,
328                                scriptvec!(
329                                    0x76, // OP_DUP
330                                    0xa9, // OP_HASH160
331                                    0x14, // OP_DATA_20
332                                    0xb0, 0xdc, 0xbf, 0x97, 0xea, 0xbf, 0x44, 0x04, 0xe3, 0x1d, 0x95, 0x24, 0x77, 0xce, 0x82, 0x2d,
333                                    0xad, 0xbe, 0x7e, 0x10, 0x88, // OP_EQUALVERIFY
334                                    0xac  // OP_CHECKSIG
335                                ),
336                            ),
337                        },
338                        TransactionOutput {
339                            value: 0x11d260c0,
340                            script_public_key: ScriptPublicKey::new(
341                                0,
342                                scriptvec!(
343                                    0x76, // OP_DUP
344                                    0xa9, // OP_HASH160
345                                    0x14, // OP_DATA_20
346                                    0x6b, 0x12, 0x81, 0xee, 0xc2, 0x5a, 0xb4, 0xe1, 0xe0, 0x79, 0x3f, 0xf4, 0xe0, 0x8a, 0xb1, 0xab,
347                                    0xb3, 0x40, 0x9c, 0xd9, 0x88, // OP_EQUALVERIFY
348                                    0xac  // OP_CHECKSIG
349                                ),
350                            ),
351                        },
352                    ],
353                    0,
354                    SUBNETWORK_ID_NATIVE,
355                    0,
356                    vec![],
357                ),
358                Transaction::new(
359                    0,
360                    vec![TransactionInput {
361                        previous_outpoint: TransactionOutpoint {
362                            transaction_id: TransactionId::from_slice(&[
363                                0x0b, 0x60, 0x72, 0xb3, 0x86, 0xd4, 0xa7, 0x73, 0x23, 0x52, 0x37, 0xf6, 0x4c, 0x11, 0x26, 0xac, 0x3b,
364                                0x24, 0x0c, 0x84, 0xb9, 0x17, 0xa3, 0x90, 0x9b, 0xa1, 0xc4, 0x3d, 0xed, 0x5f, 0x51, 0xf4,
365                            ]),
366                            index: 0,
367                        },
368                        signature_script: vec![
369                            0x49, // OP_DATA_73
370                            0x30, 0x46, 0x02, 0x21, 0x00, 0xbb, 0x1a, 0xd2, 0x6d, 0xf9, 0x30, 0xa5, 0x1c, 0xce, 0x11, 0x0c, 0xf4,
371                            0x4f, 0x7a, 0x48, 0xc3, 0xc5, 0x61, 0xfd, 0x97, 0x75, 0x00, 0xb1, 0xae, 0x5d, 0x6b, 0x6f, 0xd1, 0x3d,
372                            0x0b, 0x3f, 0x4a, 0x02, 0x21, 0x00, 0xc5, 0xb4, 0x29, 0x51, 0xac, 0xed, 0xff, 0x14, 0xab, 0xba, 0x27,
373                            0x36, 0xfd, 0x57, 0x4b, 0xdb, 0x46, 0x5f, 0x3e, 0x6f, 0x8d, 0xa1, 0x2e, 0x2c, 0x53, 0x03, 0x95, 0x4a,
374                            0xca, 0x7f, 0x78, 0xf3, 0x01, // 73-byte signature
375                            0x41, // OP_DATA_65
376                            0x04, 0xa7, 0x13, 0x5b, 0xfe, 0x82, 0x4c, 0x97, 0xec, 0xc0, 0x1e, 0xc7, 0xd7, 0xe3, 0x36, 0x18, 0x5c,
377                            0x81, 0xe2, 0xaa, 0x2c, 0x41, 0xab, 0x17, 0x54, 0x07, 0xc0, 0x94, 0x84, 0xce, 0x96, 0x94, 0xb4, 0x49,
378                            0x53, 0xfc, 0xb7, 0x51, 0x20, 0x65, 0x64, 0xa9, 0xc2, 0x4d, 0xd0, 0x94, 0xd4, 0x2f, 0xdb, 0xfd, 0xd5,
379                            0xaa, 0xd3, 0xe0, 0x63, 0xce, 0x6a, 0xf4, 0xcf, 0xaa, 0xea, 0x4e, 0xa1, 0x4f, 0xbb, // 65-byte pubkey
380                        ],
381                        sequence: u64::MAX,
382                        sig_op_count: 0,
383                    }],
384                    vec![TransactionOutput {
385                        value: 0xf4240,
386                        script_public_key: ScriptPublicKey::new(
387                            0,
388                            scriptvec!(
389                                0x76, // OP_DUP
390                                0xa9, // OP_HASH160
391                                0x14, // OP_DATA_20
392                                0x39, 0xaa, 0x3d, 0x56, 0x9e, 0x06, 0xa1, 0xd7, 0x92, 0x6d, 0xc4, 0xbe, 0x11, 0x93, 0xc9, 0x9b, 0xf2,
393                                0xeb, 0x9e, 0xe0, 0x88, // OP_EQUALVERIFY
394                                0xac  // OP_CHECKSIG
395                            ),
396                        ),
397                    }],
398                    0,
399                    SUBNETWORK_ID_NATIVE,
400                    0,
401                    vec![],
402                ),
403            ],
404        );
405
406        body_processor.validate_body_in_isolation(&example_block.clone().to_immutable()).unwrap();
407
408        let mut block = example_block.clone();
409        let txs = &mut block.transactions;
410        txs[1].version += 1;
411        assert_match!(body_processor.validate_body_in_isolation(&block.to_immutable()), Err(RuleError::BadMerkleRoot(_, _)));
412
413        let mut block = example_block.clone();
414        let txs = &mut block.transactions;
415        txs[1].inputs[0].sig_op_count = 255;
416        txs[1].inputs[1].sig_op_count = 255;
417        block.header.hash_merkle_root = calc_hash_merkle_root(txs.iter());
418        assert_match!(body_processor.validate_body_in_isolation(&block.to_immutable()), Err(RuleError::ExceedsMassLimit(_)));
419
420        let mut block = example_block.clone();
421        let txs = &mut block.transactions;
422        txs.push(txs[1].clone());
423        block.header.hash_merkle_root = calc_hash_merkle_root(txs.iter());
424        assert_match!(body_processor.validate_body_in_isolation(&block.to_immutable()), Err(RuleError::DuplicateTransactions(_)));
425
426        let mut block = example_block.clone();
427        let txs = &mut block.transactions;
428        txs[1].subnetwork_id = SUBNETWORK_ID_COINBASE;
429        block.header.hash_merkle_root = calc_hash_merkle_root(txs.iter());
430        assert_match!(body_processor.validate_body_in_isolation(&block.to_immutable()), Err(RuleError::MultipleCoinbases(_)));
431
432        let mut block = example_block.clone();
433        let txs = &mut block.transactions;
434        txs[2].inputs[0].previous_outpoint = txs[1].inputs[0].previous_outpoint;
435        block.header.hash_merkle_root = calc_hash_merkle_root(txs.iter());
436        assert_match!(body_processor.validate_body_in_isolation(&block.to_immutable()), Err(RuleError::DoubleSpendInSameBlock(_)));
437
438        let mut block = example_block.clone();
439        let txs = &mut block.transactions;
440        txs[0].subnetwork_id = SUBNETWORK_ID_NATIVE;
441        block.header.hash_merkle_root = calc_hash_merkle_root(txs.iter());
442        assert_match!(body_processor.validate_body_in_isolation(&block.to_immutable()), Err(RuleError::FirstTxNotCoinbase));
443
444        let mut block = example_block.clone();
445        let txs = &mut block.transactions;
446        txs[1].inputs = vec![];
447        block.header.hash_merkle_root = calc_hash_merkle_root(txs.iter());
448        assert_match!(
449            body_processor.validate_body_in_isolation(&block.to_immutable()),
450            Err(RuleError::TxInIsolationValidationFailed(_, _))
451        );
452
453        let mut block = example_block;
454        let txs = &mut block.transactions;
455        txs[3].inputs[0].previous_outpoint = TransactionOutpoint { transaction_id: txs[2].id(), index: 0 };
456        block.header.hash_merkle_root = calc_hash_merkle_root(txs.iter());
457        assert_match!(body_processor.validate_body_in_isolation(&block.to_immutable()), Err(RuleError::ChainedTransaction(_)));
458
459        consensus.shutdown(wait_handles);
460    }
461
462    #[tokio::test]
463    async fn merkle_root_missing_parents_known_invalid_test() {
464        let config = ConfigBuilder::new(MAINNET_PARAMS).skip_proof_of_work().build();
465        let consensus = TestConsensus::new(&config);
466        let wait_handles = consensus.init();
467
468        let mut block = consensus.build_block_with_parents_and_transactions(1.into(), vec![config.genesis.hash], vec![]);
469        block.transactions[0].version += 1;
470
471        let BlockValidationFutures { block_task, virtual_state_task } =
472            consensus.validate_and_insert_block(block.clone().to_immutable());
473
474        assert_match!(block_task.await, Err(RuleError::BadMerkleRoot(_, _)));
475        // Assert that both tasks return the same error
476        assert_match!(virtual_state_task.await, Err(RuleError::BadMerkleRoot(_, _)));
477
478        // BadMerkleRoot shouldn't mark the block as known invalid
479        assert_match!(
480            consensus.validate_and_insert_block(block.to_immutable()).virtual_state_task.await,
481            Err(RuleError::BadMerkleRoot(_, _))
482        );
483
484        let mut block = consensus.build_block_with_parents_and_transactions(1.into(), vec![config.genesis.hash], vec![]);
485        block.header.parents_by_level[0][0] = 0.into();
486
487        assert_match!(
488            consensus.validate_and_insert_block(block.clone().to_immutable()).virtual_state_task.await,
489            Err(RuleError::MissingParents(_))
490        );
491
492        // MissingParents shouldn't mark the block as known invalid
493        assert_match!(
494            consensus.validate_and_insert_block(block.to_immutable()).virtual_state_task.await,
495            Err(RuleError::MissingParents(_))
496        );
497
498        consensus.shutdown(wait_handles);
499    }
500}