Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions mina-p2p-messages/src/v2/manual.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1532,6 +1532,12 @@ impl From<OffsetDateTime> for BlockTimeTimeStableV1 {
}
}

impl MinaBlockHeaderStableV2 {
pub fn genesis_state_hash(&self) -> &StateHash {
&self.protocol_state.body.genesis_state_hash
}
}

impl StagedLedgerDiffBodyStableV1 {
pub fn diff(&self) -> &StagedLedgerDiffDiffDiffStableV2 {
&self.staged_ledger_diff.diff
Expand Down
6 changes: 5 additions & 1 deletion node/src/action_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ pub enum ActionKind {
CheckTimeouts,
ConsensusBestTipUpdate,
ConsensusBlockChainProofUpdate,
ConsensusBlockPrevalidateError,
ConsensusBlockPrevalidateSuccess,
ConsensusBlockReceived,
ConsensusBlockSnarkVerifyError,
ConsensusBlockSnarkVerifyPending,
Expand Down Expand Up @@ -716,7 +718,7 @@ pub enum ActionKind {
}

impl ActionKind {
pub const COUNT: u16 = 599;
pub const COUNT: u16 = 601;
}

impl std::fmt::Display for ActionKind {
Expand Down Expand Up @@ -857,6 +859,8 @@ impl ActionKindGet for ConsensusAction {
fn kind(&self) -> ActionKind {
match self {
Self::BlockReceived { .. } => ActionKind::ConsensusBlockReceived,
Self::BlockPrevalidateSuccess { .. } => ActionKind::ConsensusBlockPrevalidateSuccess,
Self::BlockPrevalidateError { .. } => ActionKind::ConsensusBlockPrevalidateError,
Self::BlockChainProofUpdate { .. } => ActionKind::ConsensusBlockChainProofUpdate,
Self::BlockSnarkVerifyPending { .. } => ActionKind::ConsensusBlockSnarkVerifyPending,
Self::BlockSnarkVerifySuccess { .. } => ActionKind::ConsensusBlockSnarkVerifySuccess,
Expand Down
16 changes: 15 additions & 1 deletion node/src/consensus/consensus_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use snark::block_verify::SnarkBlockVerifyError;

use crate::consensus::ConsensusBlockStatus;
use crate::snark::block_verify::SnarkBlockVerifyId;
use crate::state::BlockPrevalidationError;

pub type ConsensusActionWithMeta = redux::ActionWithMeta<ConsensusAction>;
pub type ConsensusActionWithMetaRef<'a> = redux::ActionWithMeta<&'a ConsensusAction>;
Expand All @@ -24,6 +25,13 @@ pub enum ConsensusAction {
block: Arc<MinaBlockBlockStableV2>,
chain_proof: Option<(Vec<StateHash>, ArcBlockWithHash)>,
},
BlockPrevalidateSuccess {
hash: StateHash,
},
BlockPrevalidateError {
hash: StateHash,
error: BlockPrevalidationError,
},
BlockChainProofUpdate {
hash: StateHash,
chain_proof: (Vec<StateHash>, ArcBlockWithHash),
Expand Down Expand Up @@ -71,6 +79,12 @@ impl redux::EnablingCondition<crate::State> for ConsensusAction {
};
!block.is_genesis() && !state.consensus.blocks.contains_key(hash)
},
ConsensusAction::BlockPrevalidateSuccess { hash }
| ConsensusAction::BlockPrevalidateError { hash, .. } => state
.consensus
.blocks
.get(hash)
.map_or(false, |block| block.status.is_received()),
ConsensusAction::BlockChainProofUpdate { hash, .. } => {
(state.consensus.best_tip.as_ref() == Some(hash)
&& state.consensus.best_tip_chain_proof.is_none())
Expand All @@ -85,7 +99,7 @@ impl redux::EnablingCondition<crate::State> for ConsensusAction {
.consensus
.blocks
.get(hash)
.map_or(false, |block| block.status.is_received())
.map_or(false, |block| block.status.is_prevalidated())
&& state.snark.block_verify.jobs.contains(*req_id)
},
ConsensusAction::BlockSnarkVerifySuccess { hash } => {
Expand Down
31 changes: 29 additions & 2 deletions node/src/consensus/consensus_reducer.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use openmina_core::{
block::BlockHash,
block::{ArcBlockWithHash, BlockHash},
bug_condition,
consensus::{is_short_range_fork, long_range_fork_take, short_range_fork_take},
};
Expand Down Expand Up @@ -48,9 +48,33 @@ impl ConsensusState {
);

// Dispatch
let (dispatcher, state) = state_context.into_dispatcher_and_state();

let hash = hash.clone();
let block = ArcBlockWithHash {
hash: hash.clone(),
block: block.clone(),
};
match state.prevalidate_block(&block) {
Ok(()) => {
dispatcher.push(ConsensusAction::BlockPrevalidateSuccess { hash });
}
Err(error) => {
dispatcher.push(ConsensusAction::BlockPrevalidateError { hash, error });
}
}
}
ConsensusAction::BlockPrevalidateSuccess { hash } => {
let Some(block) = state.blocks.get_mut(hash) else {
return;
};
block.status = ConsensusBlockStatus::Prevalidated;

// Dispatch
let block = (hash.clone(), block.block.clone()).into();
let dispatcher = state_context.into_dispatcher();
dispatcher.push(SnarkBlockVerifyAction::Init {
block: (hash.clone(), block.clone()).into(),
block,
on_init: redux::callback!(
on_received_block_snark_verify_init((hash: BlockHash, req_id: SnarkBlockVerifyId)) -> crate::Action {
ConsensusAction::BlockSnarkVerifyPending { hash, req_id }
Expand All @@ -65,6 +89,9 @@ impl ConsensusState {
}),
});
}
ConsensusAction::BlockPrevalidateError { hash, .. } => {
state.blocks.remove(hash);
}
ConsensusAction::BlockChainProofUpdate { hash, chain_proof } => {
if state.best_tip.as_ref() == Some(hash) {
state.best_tip_chain_proof = Some(chain_proof.clone());
Expand Down
6 changes: 6 additions & 0 deletions node/src/consensus/consensus_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub enum ConsensusBlockStatus {
Received {
time: redux::Timestamp,
},
Prevalidated,
SnarkVerifyPending {
time: redux::Timestamp,
req_id: SnarkBlockVerifyId,
Expand Down Expand Up @@ -73,6 +74,10 @@ impl ConsensusBlockStatus {
matches!(self, Self::Received { .. })
}

pub fn is_prevalidated(&self) -> bool {
matches!(self, Self::Prevalidated)
}

pub fn is_snark_verify_pending(&self) -> bool {
matches!(self, Self::SnarkVerifyPending { .. })
}
Expand Down Expand Up @@ -167,6 +172,7 @@ impl ConsensusState {
};
match &candidate.status {
ConsensusBlockStatus::Received { .. } => false,
ConsensusBlockStatus::Prevalidated => false,
ConsensusBlockStatus::SnarkVerifyPending { .. } => false,
ConsensusBlockStatus::SnarkVerifySuccess { .. } => false,
ConsensusBlockStatus::ForkRangeDetected { .. } => false,
Expand Down
101 changes: 100 additions & 1 deletion node/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::sync::Arc;
use std::time::Duration;

use mina_p2p_messages::v2::{MinaBaseUserCommandStableV2, MinaBlockBlockStableV2};
use openmina_core::constants::PROTOCOL_VERSION;
use rand::prelude::*;

use openmina_core::block::BlockWithHash;
Expand Down Expand Up @@ -73,8 +74,27 @@ pub struct State {
applied_actions_count: u64,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum BlockPrevalidationError {
GenesisNotReady,
ReceivedTooEarly {
current_global_slot: u32,
block_global_slot: u32,
},
ReceivedTooLate {
current_global_slot: u32,
block_global_slot: u32,
delta: u32,
},
InvalidGenesisProtocolState,
InvalidProtocolVersion,
MismatchedProtocolVersion,
ConsantsMismatch,
InvalidDeltaBlockChainProof,
}

// Substate accessors that will be used in reducers
use openmina_core::{impl_substate_access, SubstateAccess};
use openmina_core::{bug_condition, impl_substate_access, SubstateAccess};

impl_substate_access!(State, SnarkState, snark);
impl_substate_access!(State, SnarkBlockVerifyState, snark.block_verify);
Expand Down Expand Up @@ -356,6 +376,85 @@ impl State {
})
}

pub fn prevalidate_block(
&self,
block: &ArcBlockWithHash,
) -> Result<(), BlockPrevalidationError> {
let Some((genesis, cur_global_slot)) =
None.or_else(|| Some((self.genesis_block()?, self.cur_global_slot()?)))
else {
// we don't have genesis block. This should be impossible
// because we don't even know chain_id before we have genesis
// block, so we can't be connected to any peers from which
// we would receive a block.
bug_condition!("Tried to prevalidate a block before the genesis block was ready");
return Err(BlockPrevalidationError::GenesisNotReady);
};

// received_at_valid_time
// https://github.com/minaprotocol/mina/blob/6af211ad58e9356f00ea4a636cea70aa8267c072/src/lib/consensus/proof_of_stake.ml#L2746
{
let block_global_slot = block.global_slot();

let delta = genesis.constants().delta.as_u32();
if cur_global_slot < block_global_slot {
// Too_early
return Err(BlockPrevalidationError::ReceivedTooEarly {
current_global_slot: cur_global_slot,
block_global_slot,
});
} else if cur_global_slot.saturating_sub(block_global_slot) > delta {
// Too_late
return Err(BlockPrevalidationError::ReceivedTooLate {
current_global_slot: cur_global_slot,
block_global_slot,
delta,
});
}
}

if block.header().genesis_state_hash() != genesis.hash() {
return Err(BlockPrevalidationError::InvalidGenesisProtocolState);
}

let (protocol_versions_are_valid, protocol_version_matches_daemon) = {
let min_transaction_version = 1.into();
let v = &block.header().current_protocol_version;
let nv = block
.header()
.proposed_protocol_version_opt
.as_ref()
.unwrap_or(v);

// Our version values are unsigned, so there is no need to check that the
// other parts are not negative.
let valid = v.transaction >= min_transaction_version
&& nv.transaction >= min_transaction_version;
let compatible = v.transaction == PROTOCOL_VERSION.transaction
&& v.network == PROTOCOL_VERSION.network;

(valid, compatible)
};

if !protocol_versions_are_valid {
return Err(BlockPrevalidationError::InvalidProtocolVersion);
} else if !protocol_version_matches_daemon {
return Err(BlockPrevalidationError::MismatchedProtocolVersion);
}

// NOTE: currently these cannot change between blocks, but that
// may not always be true?
if block.constants() != genesis.constants() {
return Err(BlockPrevalidationError::ConsantsMismatch);
}

// TODO(tizoc): check for InvalidDeltaBlockChainProof
// https://github.com/MinaProtocol/mina/blob/d800da86a764d8d37ffb8964dd8d54d9f522b358/src/lib/mina_block/validation.ml#L369
// https://github.com/MinaProtocol/mina/blob/d800da86a764d8d37ffb8964dd8d54d9f522b358/src/lib/transition_chain_verifier/transition_chain_verifier.ml

Ok(())
}

pub fn should_log_node_id(&self) -> bool {
self.config.testing_run
}
Expand Down
29 changes: 25 additions & 4 deletions node/testing/src/scenarios/solo_node/bootstrap.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::time::Duration;

use node::transition_frontier::sync::TransitionFrontierSyncState;
use openmina_core::constants::constraint_constants;
use redux::Instant;

use crate::{
Expand All @@ -19,6 +20,24 @@ use crate::{
#[derive(documented::Documented, Default, Clone, Copy)]
pub struct SoloNodeBootstrap;

// TODO(tizoc): this is ugly, do a cleaner conversion or figure out a better way.
// This test will fail if we don't start with this as the initial time because
// the time validation for the first block will reject it.
fn first_block_slot_timestamp_nanos(config: &RustNodeTestingConfig) -> u64 {
let first_block_global_slot = 46891; // Update if replay changes
let protocol_constants = config.genesis.protocol_constants().unwrap();
let genesis_timestamp_ms = protocol_constants.genesis_state_timestamp.0.as_u64();
let milliseconds_per_slot = constraint_constants().block_window_duration_ms;
let first_block_global_slot_delta_ms = first_block_global_slot * milliseconds_per_slot;

// Convert to nanos
genesis_timestamp_ms
.checked_add(first_block_global_slot_delta_ms)
.unwrap()
.checked_mul(1_000_000)
.unwrap()
}

impl SoloNodeBootstrap {
pub async fn run(self, mut runner: ClusterRunner<'_>) {
use self::TransitionFrontierSyncState::*;
Expand All @@ -27,10 +46,12 @@ impl SoloNodeBootstrap {

let replayer = hosts::replayer();

let node_id = runner.add_rust_node(
RustNodeTestingConfig::devnet_default()
.initial_peers(vec![ListenerNode::Custom(replayer)]),
);
let mut config = RustNodeTestingConfig::devnet_default();

config.initial_time = redux::Timestamp::new(first_block_slot_timestamp_nanos(&config));

let node_id =
runner.add_rust_node(config.initial_peers(vec![ListenerNode::Custom(replayer)]));
eprintln!("launch Openmina node with default configuration, id: {node_id}");

let mut timeout = TIMEOUT;
Expand Down
Loading