kaspa_consensus/pipeline/body_processor/
body_validation_in_context.rs1use super::BlockBodyProcessor;
2use crate::{
3 errors::{BlockProcessResult, RuleError},
4 model::stores::{ghostdag::GhostdagStoreReader, statuses::StatusesStoreReader},
5 processes::window::WindowManager,
6};
7use kaspa_consensus_core::block::Block;
8use kaspa_database::prelude::StoreResultExtensions;
9use kaspa_hashes::Hash;
10use kaspa_utils::option::OptionExtensions;
11use std::sync::Arc;
12
13impl BlockBodyProcessor {
14 pub fn validate_body_in_context(self: &Arc<Self>, block: &Block) -> BlockProcessResult<()> {
15 self.check_parent_bodies_exist(block)?;
16 self.check_coinbase_blue_score_and_subsidy(block)?;
17 self.check_block_transactions_in_context(block)
18 }
19
20 fn check_block_transactions_in_context(self: &Arc<Self>, block: &Block) -> BlockProcessResult<()> {
21 let (pmt, _) = self.window_manager.calc_past_median_time(&self.ghostdag_store.get_data(block.hash()).unwrap())?;
22 for tx in block.transactions.iter() {
23 if let Err(e) = self.transaction_validator.utxo_free_tx_validation(tx, block.header.daa_score, pmt) {
24 return Err(RuleError::TxInContextFailed(tx.id(), e));
25 }
26 }
27
28 Ok(())
29 }
30
31 fn check_parent_bodies_exist(self: &Arc<Self>, block: &Block) -> BlockProcessResult<()> {
32 let statuses_read_guard = self.statuses_store.read();
33 let missing: Vec<Hash> = block
34 .header
35 .direct_parents()
36 .iter()
37 .copied()
38 .filter(|parent| {
39 let status_option = statuses_read_guard.get(*parent).unwrap_option();
40 status_option.is_none_or_ex(|s| !s.has_block_body())
41 })
42 .collect();
43 if !missing.is_empty() {
44 return Err(RuleError::MissingParents(missing));
45 }
46
47 Ok(())
48 }
49
50 fn check_coinbase_blue_score_and_subsidy(self: &Arc<Self>, block: &Block) -> BlockProcessResult<()> {
51 match self.coinbase_manager.deserialize_coinbase_payload(&block.transactions[0].payload) {
52 Ok(data) => {
53 if data.blue_score != block.header.blue_score {
54 return Err(RuleError::BadCoinbasePayloadBlueScore(data.blue_score, block.header.blue_score));
55 }
56
57 let expected_subsidy = self.coinbase_manager.calc_block_subsidy(block.header.daa_score);
58
59 if data.subsidy != expected_subsidy {
60 return Err(RuleError::WrongSubsidy(expected_subsidy, data.subsidy));
61 }
62
63 Ok(())
64 }
65 Err(e) => Err(RuleError::BadCoinbasePayload(e)),
66 }
67 }
68}
69
70#[cfg(test)]
71mod tests {
72
73 use crate::{
74 config::ConfigBuilder,
75 consensus::test_consensus::TestConsensus,
76 constants::TX_VERSION,
77 errors::RuleError,
78 model::stores::ghostdag::GhostdagStoreReader,
79 params::DEVNET_PARAMS,
80 processes::{transaction_validator::errors::TxRuleError, window::WindowManager},
81 };
82 use kaspa_consensus_core::{
83 api::ConsensusApi,
84 merkle::calc_hash_merkle_root as calc_hash_merkle_root_with_options,
85 subnets::SUBNETWORK_ID_NATIVE,
86 tx::{Transaction, TransactionInput, TransactionOutpoint},
87 };
88 use kaspa_core::assert_match;
89 use kaspa_hashes::Hash;
90
91 fn calc_hash_merkle_root<'a>(txs: impl ExactSizeIterator<Item = &'a Transaction>) -> Hash {
92 calc_hash_merkle_root_with_options(txs, false)
93 }
94
95 #[tokio::test]
96 async fn validate_body_in_context_test() {
97 let config = ConfigBuilder::new(DEVNET_PARAMS)
98 .skip_proof_of_work()
99 .edit_consensus_params(|p| p.deflationary_phase_daa_score = 2)
100 .build();
101 let consensus = TestConsensus::new(&config);
102 let wait_handles = consensus.init();
103 let body_processor = consensus.block_body_processor();
104
105 consensus.add_block_with_parents(1.into(), vec![config.genesis.hash]).await.unwrap();
106
107 {
108 let block = consensus.build_block_with_parents_and_transactions(2.into(), vec![1.into()], vec![]);
109 assert_match!(body_processor.validate_body_in_context(&block.to_immutable()), Err(RuleError::MissingParents(_)));
111 }
112
113 let valid_block = consensus.build_block_with_parents_and_transactions(3.into(), vec![config.genesis.hash], vec![]);
114 consensus.validate_and_insert_block(valid_block.to_immutable()).virtual_state_task.await.unwrap();
115 {
116 let mut block = consensus.build_block_with_parents_and_transactions(2.into(), vec![3.into()], vec![]);
117 block.transactions[0].payload[8..16].copy_from_slice(&(5_u64).to_le_bytes());
118 block.header.hash_merkle_root = calc_hash_merkle_root(block.transactions.iter());
119
120 assert_match!(
121 consensus.validate_and_insert_block(block.clone().to_immutable()).virtual_state_task.await, Err(RuleError::WrongSubsidy(expected,_)) if expected == 50000000000);
122
123 assert_match!(
125 consensus.validate_and_insert_block(block.to_immutable()).virtual_state_task.await,
126 Err(RuleError::KnownInvalid)
127 );
128 }
129
130 {
131 let mut block = consensus.build_block_with_parents_and_transactions(4.into(), vec![3.into()], vec![]);
132 block.transactions[0].payload[0..8].copy_from_slice(&(100_u64).to_le_bytes());
133 block.header.hash_merkle_root = calc_hash_merkle_root(block.transactions.iter());
134
135 assert_match!(
136 consensus.validate_and_insert_block(block.to_immutable()).virtual_state_task.await,
137 Err(RuleError::BadCoinbasePayloadBlueScore(_, _))
138 );
139 }
140
141 {
142 let mut block = consensus.build_block_with_parents_and_transactions(5.into(), vec![3.into()], vec![]);
143 block.transactions[0].payload = vec![];
144 block.header.hash_merkle_root = calc_hash_merkle_root(block.transactions.iter());
145
146 assert_match!(
147 consensus.validate_and_insert_block(block.to_immutable()).virtual_state_task.await,
148 Err(RuleError::BadCoinbasePayload(_))
149 );
150 }
151
152 let valid_block_child = consensus.build_block_with_parents_and_transactions(6.into(), vec![3.into()], vec![]);
153 consensus.validate_and_insert_block(valid_block_child.clone().to_immutable()).virtual_state_task.await.unwrap();
154 {
155 let mut block = consensus.build_block_with_parents_and_transactions(7.into(), vec![6.into()], vec![]);
157 block.transactions[0].payload[8..16].copy_from_slice(&(5_u64).to_le_bytes());
158 block.header.hash_merkle_root = calc_hash_merkle_root(block.transactions.iter());
159 assert_match!(consensus.validate_and_insert_block(block.to_immutable()).virtual_state_task.await, Err(RuleError::WrongSubsidy(expected,_)) if expected == 44000000000);
160 }
161
162 {
163 let tip_daa_score = valid_block_child.header.daa_score + 1;
165 check_for_lock_time_and_sequence(&consensus, valid_block_child.header.hash, 8.into(), tip_daa_score + 1, 0, false).await;
166 check_for_lock_time_and_sequence(&consensus, valid_block_child.header.hash, 9.into(), tip_daa_score, 0, false).await;
167 check_for_lock_time_and_sequence(&consensus, valid_block_child.header.hash, 10.into(), tip_daa_score - 1, 0, true).await;
168
169 let valid_block_child_gd = consensus.ghostdag_store().get_data(valid_block_child.header.hash).unwrap();
170 let (valid_block_child_gd_pmt, _) = consensus.window_manager().calc_past_median_time(&valid_block_child_gd).unwrap();
171 let past_median_time = valid_block_child_gd_pmt + 1;
172
173 let tip_daa_score = valid_block_child.header.daa_score + 1;
175 check_for_lock_time_and_sequence(&consensus, valid_block_child.header.hash, 11.into(), past_median_time + 1, 0, false)
176 .await;
177 check_for_lock_time_and_sequence(&consensus, valid_block_child.header.hash, 12.into(), past_median_time, 0, false).await;
178 check_for_lock_time_and_sequence(&consensus, valid_block_child.header.hash, 13.into(), past_median_time - 1, 0, true)
179 .await;
180
181 check_for_lock_time_and_sequence(
183 &consensus,
184 valid_block_child.header.hash,
185 14.into(),
186 past_median_time + 1,
187 u64::MAX,
188 true,
189 )
190 .await;
191
192 check_for_lock_time_and_sequence(&consensus, valid_block_child.header.hash, 15.into(), tip_daa_score + 1, u64::MAX, true)
193 .await;
194 }
195
196 consensus.shutdown(wait_handles);
197 }
198
199 async fn check_for_lock_time_and_sequence(
200 consensus: &TestConsensus,
201 parent: Hash,
202 block_hash: Hash,
203 lock_time: u64,
204 sequence: u64,
205 should_pass: bool,
206 ) {
207 let block = consensus.build_block_with_parents_and_transactions(
209 block_hash,
210 vec![parent],
211 vec![Transaction::new(
212 TX_VERSION,
213 vec![TransactionInput::new(TransactionOutpoint::new(1.into(), 0), vec![], sequence, 0)],
214 vec![],
215 lock_time,
216 SUBNETWORK_ID_NATIVE,
217 0,
218 vec![],
219 )],
220 );
221
222 if should_pass {
223 consensus.validate_and_insert_block(block.to_immutable()).virtual_state_task.await.unwrap();
224 } else {
225 assert_match!(
226 consensus.validate_and_insert_block(block.to_immutable()).virtual_state_task.await,
227 Err(RuleError::TxInContextFailed(_, e)) if matches!(e, TxRuleError::NotFinalized(_)));
228 }
229 }
230}