Skip to content

Commit 77ba83d

Browse files
committed
feat(consensus): Prevalidate blocks in reducers not enabling conditions
Add more checks to prevalidation. Also use error type for validation failure instead of boolean.
1 parent 384d24c commit 77ba83d

File tree

6 files changed

+105
-22
lines changed

6 files changed

+105
-22
lines changed

node/src/action_kind.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ pub enum ActionKind {
162162
CheckTimeouts,
163163
ConsensusBestTipUpdate,
164164
ConsensusBlockChainProofUpdate,
165+
ConsensusBlockPrevalidateError,
166+
ConsensusBlockPrevalidateSuccess,
165167
ConsensusBlockReceived,
166168
ConsensusBlockSnarkVerifyError,
167169
ConsensusBlockSnarkVerifyPending,
@@ -716,7 +718,7 @@ pub enum ActionKind {
716718
}
717719

718720
impl ActionKind {
719-
pub const COUNT: u16 = 599;
721+
pub const COUNT: u16 = 601;
720722
}
721723

722724
impl std::fmt::Display for ActionKind {
@@ -857,6 +859,8 @@ impl ActionKindGet for ConsensusAction {
857859
fn kind(&self) -> ActionKind {
858860
match self {
859861
Self::BlockReceived { .. } => ActionKind::ConsensusBlockReceived,
862+
Self::BlockPrevalidateSuccess { .. } => ActionKind::ConsensusBlockPrevalidateSuccess,
863+
Self::BlockPrevalidateError { .. } => ActionKind::ConsensusBlockPrevalidateError,
860864
Self::BlockChainProofUpdate { .. } => ActionKind::ConsensusBlockChainProofUpdate,
861865
Self::BlockSnarkVerifyPending { .. } => ActionKind::ConsensusBlockSnarkVerifyPending,
862866
Self::BlockSnarkVerifySuccess { .. } => ActionKind::ConsensusBlockSnarkVerifySuccess,

node/src/consensus/consensus_actions.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use snark::block_verify::SnarkBlockVerifyError;
99

1010
use crate::consensus::ConsensusBlockStatus;
1111
use crate::snark::block_verify::SnarkBlockVerifyId;
12+
use crate::state::BlockPrevalidationError;
1213

1314
pub type ConsensusActionWithMeta = redux::ActionWithMeta<ConsensusAction>;
1415
pub type ConsensusActionWithMetaRef<'a> = redux::ActionWithMeta<&'a ConsensusAction>;
@@ -24,6 +25,13 @@ pub enum ConsensusAction {
2425
block: Arc<MinaBlockBlockStableV2>,
2526
chain_proof: Option<(Vec<StateHash>, ArcBlockWithHash)>,
2627
},
28+
BlockPrevalidateSuccess {
29+
hash: StateHash,
30+
},
31+
BlockPrevalidateError {
32+
hash: StateHash,
33+
error: BlockPrevalidationError,
34+
},
2735
BlockChainProofUpdate {
2836
hash: StateHash,
2937
chain_proof: (Vec<StateHash>, ArcBlockWithHash),
@@ -69,10 +77,14 @@ impl redux::EnablingCondition<crate::State> for ConsensusAction {
6977
hash: hash.clone(),
7078
block: block.clone()
7179
};
72-
!block.is_genesis()
73-
&& !state.consensus.blocks.contains_key(hash)
74-
&& state.prevalidate_block(&block)
80+
!block.is_genesis() && !state.consensus.blocks.contains_key(hash)
7581
},
82+
ConsensusAction::BlockPrevalidateSuccess { hash }
83+
| ConsensusAction::BlockPrevalidateError { hash, .. } => state
84+
.consensus
85+
.blocks
86+
.get(hash)
87+
.map_or(false, |block| block.status.is_received()),
7688
ConsensusAction::BlockChainProofUpdate { hash, .. } => {
7789
(state.consensus.best_tip.as_ref() == Some(hash)
7890
&& state.consensus.best_tip_chain_proof.is_none())
@@ -87,7 +99,7 @@ impl redux::EnablingCondition<crate::State> for ConsensusAction {
8799
.consensus
88100
.blocks
89101
.get(hash)
90-
.map_or(false, |block| block.status.is_received())
102+
.map_or(false, |block| block.status.is_prevalidated())
91103
&& state.snark.block_verify.jobs.contains(*req_id)
92104
},
93105
ConsensusAction::BlockSnarkVerifySuccess { hash } => {

node/src/consensus/consensus_reducer.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use openmina_core::{
2-
block::BlockHash,
2+
block::{ArcBlockWithHash, BlockHash},
33
bug_condition,
44
consensus::{is_short_range_fork, long_range_fork_take, short_range_fork_take},
55
};
@@ -48,9 +48,33 @@ impl ConsensusState {
4848
);
4949

5050
// Dispatch
51+
let (dispatcher, state) = state_context.into_dispatcher_and_state();
52+
53+
let hash = hash.clone();
54+
let block = ArcBlockWithHash {
55+
hash: hash.clone(),
56+
block: block.clone(),
57+
};
58+
match state.prevalidate_block(&block) {
59+
Ok(()) => {
60+
dispatcher.push(ConsensusAction::BlockPrevalidateSuccess { hash });
61+
}
62+
Err(error) => {
63+
dispatcher.push(ConsensusAction::BlockPrevalidateError { hash, error });
64+
}
65+
}
66+
}
67+
ConsensusAction::BlockPrevalidateSuccess { hash } => {
68+
let Some(block) = state.blocks.get_mut(hash) else {
69+
return;
70+
};
71+
block.status = ConsensusBlockStatus::Prevalidated;
72+
73+
// Dispatch
74+
let block = (hash.clone(), block.block.clone()).into();
5175
let dispatcher = state_context.into_dispatcher();
5276
dispatcher.push(SnarkBlockVerifyAction::Init {
53-
block: (hash.clone(), block.clone()).into(),
77+
block,
5478
on_init: redux::callback!(
5579
on_received_block_snark_verify_init((hash: BlockHash, req_id: SnarkBlockVerifyId)) -> crate::Action {
5680
ConsensusAction::BlockSnarkVerifyPending { hash, req_id }
@@ -65,6 +89,9 @@ impl ConsensusState {
6589
}),
6690
});
6791
}
92+
ConsensusAction::BlockPrevalidateError { hash, .. } => {
93+
state.blocks.remove(hash);
94+
}
6895
ConsensusAction::BlockChainProofUpdate { hash, chain_proof } => {
6996
if state.best_tip.as_ref() == Some(hash) {
7097
state.best_tip_chain_proof = Some(chain_proof.clone());

node/src/consensus/consensus_state.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ pub enum ConsensusBlockStatus {
4444
Received {
4545
time: redux::Timestamp,
4646
},
47+
Prevalidated,
4748
SnarkVerifyPending {
4849
time: redux::Timestamp,
4950
req_id: SnarkBlockVerifyId,
@@ -73,6 +74,10 @@ impl ConsensusBlockStatus {
7374
matches!(self, Self::Received { .. })
7475
}
7576

77+
pub fn is_prevalidated(&self) -> bool {
78+
matches!(self, Self::Prevalidated)
79+
}
80+
7681
pub fn is_snark_verify_pending(&self) -> bool {
7782
matches!(self, Self::SnarkVerifyPending { .. })
7883
}
@@ -167,6 +172,7 @@ impl ConsensusState {
167172
};
168173
match &candidate.status {
169174
ConsensusBlockStatus::Received { .. } => false,
175+
ConsensusBlockStatus::Prevalidated => false,
170176
ConsensusBlockStatus::SnarkVerifyPending { .. } => false,
171177
ConsensusBlockStatus::SnarkVerifySuccess { .. } => false,
172178
ConsensusBlockStatus::ForkRangeDetected { .. } => false,

node/src/p2p/peer/p2p_peer_actions.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,6 @@ use super::*;
22

33
impl redux::EnablingCondition<crate::State> for P2pPeerAction {
44
fn is_enabled(&self, state: &crate::State, time: redux::Timestamp) -> bool {
5-
if let P2pPeerAction::BestTipUpdate { best_tip, .. } = self {
6-
if !state.prevalidate_block(best_tip) {
7-
return false;
8-
}
9-
}
105
state.p2p.is_enabled(self, time)
116
}
127
}

node/src/state.rs

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::sync::Arc;
22
use std::time::Duration;
33

44
use mina_p2p_messages::v2::{MinaBaseUserCommandStableV2, MinaBlockBlockStableV2};
5+
use openmina_core::constants::PROTOCOL_VERSION;
56
use rand::prelude::*;
67

78
use openmina_core::block::BlockWithHash;
@@ -73,6 +74,17 @@ pub struct State {
7374
applied_actions_count: u64,
7475
}
7576

77+
#[derive(Serialize, Deserialize, Debug, Clone)]
78+
pub enum BlockPrevalidationError {
79+
GenesisNotReady,
80+
ReceivedTooEarly,
81+
ReceivedTooLate,
82+
InvalidGenesisProtocolState,
83+
InvalidProtocolVersion,
84+
MismatchedProtocolVersion,
85+
InvalidDeltaBlockChainProof,
86+
}
87+
7688
// Substate accessors that will be used in reducers
7789
use openmina_core::{impl_substate_access, SubstateAccess};
7890

@@ -356,15 +368,18 @@ impl State {
356368
})
357369
}
358370

359-
pub fn prevalidate_block(&self, block: &ArcBlockWithHash) -> bool {
371+
pub fn prevalidate_block(
372+
&self,
373+
block: &ArcBlockWithHash,
374+
) -> Result<(), BlockPrevalidationError> {
360375
let Some((genesis, cur_global_slot)) =
361376
None.or_else(|| Some((self.genesis_block()?, self.cur_global_slot()?)))
362377
else {
363378
// we don't have genesis block. This should be impossible
364379
// because we don't even know chain_id before we have genesis
365380
// block, so we can't be connected to any peers from which
366381
// we would receive a block.
367-
return false;
382+
return Err(BlockPrevalidationError::GenesisNotReady);
368383
};
369384

370385
// received_at_valid_time
@@ -375,23 +390,47 @@ impl State {
375390
let delta = genesis.constants().delta.as_u32();
376391
if cur_global_slot < block_global_slot {
377392
// Too_early
378-
return false;
393+
return Err(BlockPrevalidationError::ReceivedTooEarly);
379394
} else if cur_global_slot.saturating_sub(block_global_slot) > delta {
380395
// Too_late
381-
return false;
396+
return Err(BlockPrevalidationError::ReceivedTooLate);
382397
}
383398
}
384399

385-
if block.constants() != genesis.constants() {
386-
return false;
400+
if block.header().genesis_state_hash() != genesis.hash() {
401+
return Err(BlockPrevalidationError::InvalidGenesisProtocolState);
387402
}
388403

389-
if block.header().genesis_state_hash() != genesis.hash() {
390-
return false;
404+
let (protocol_versions_are_valid, protocol_version_matches_daemon) = {
405+
let min_transaction_version = 1.into();
406+
let v = &block.header().current_protocol_version;
407+
let nv = block
408+
.header()
409+
.proposed_protocol_version_opt
410+
.as_ref()
411+
.unwrap_or(v);
412+
413+
// Our version values are unsigned, so there is no need to check that the
414+
// other parts are not negative.
415+
let valid = v.transaction >= min_transaction_version
416+
&& nv.transaction >= min_transaction_version;
417+
let compatible = v.transaction == PROTOCOL_VERSION.transaction
418+
&& v.network == PROTOCOL_VERSION.network;
419+
420+
(valid, compatible)
421+
};
422+
423+
if !protocol_versions_are_valid {
424+
return Err(BlockPrevalidationError::InvalidProtocolVersion);
425+
} else if !protocol_version_matches_daemon {
426+
return Err(BlockPrevalidationError::MismatchedProtocolVersion);
391427
}
392428

393-
// TODO(binier): more checks.
394-
true
429+
// TODO(tizoc): check for InvalidDeltaBlockChainProof
430+
// https://github.com/MinaProtocol/mina/blob/d800da86a764d8d37ffb8964dd8d54d9f522b358/src/lib/mina_block/validation.ml#L369
431+
// https://github.com/MinaProtocol/mina/blob/d800da86a764d8d37ffb8964dd8d54d9f522b358/src/lib/transition_chain_verifier/transition_chain_verifier.ml
432+
433+
Ok(())
395434
}
396435

397436
pub fn should_log_node_id(&self) -> bool {

0 commit comments

Comments
 (0)