From d3916651d8282068876b3c691be9213533cbd63b Mon Sep 17 00:00:00 2001 From: Mimir Date: Wed, 30 Oct 2024 15:34:33 +0100 Subject: [PATCH 001/175] Ported block producer and VRF evaluator --- .../service/block_producer/vrf_evaluator.rs | 4 +- node/src/action.rs | 3 + node/src/action_kind.rs | 40 +- .../block_producer/block_producer_actions.rs | 7 +- .../block_producer/block_producer_reducer.rs | 862 +++++++++++------- .../block_producer/block_producer_state.rs | 17 +- node/src/block_producer/mod.rs | 6 - .../block_producer_vrf_evaluator_effects.rs | 238 ----- .../block_producer_vrf_evaluator_reducer.rs | 323 +++++-- node/src/block_producer/vrf_evaluator/mod.rs | 5 - .../block_producer_effectful_actions.rs | 26 + .../block_producer_effectful_effects.rs} | 172 +--- .../block_producer_effectful_service.rs} | 0 node/src/block_producer_effectful/mod.rs | 10 + ...roducer_vrf_evaluator_effectful_actions.rs | 20 + ...roducer_vrf_evaluator_effectful_effects.rs | 15 + ...oducer_vrf_evaluator_effectful_service.rs} | 2 +- .../vrf_evaluator_effectful/mod.rs | 7 + node/src/consensus/consensus_reducer.rs | 1 + node/src/effects.rs | 6 +- node/src/ledger/ledger_service.rs | 2 +- node/src/ledger/write/mod.rs | 2 +- node/src/lib.rs | 1 + node/src/reducer.rs | 10 +- node/src/service.rs | 4 +- node/src/state.rs | 19 + .../sync/transition_frontier_sync_actions.rs | 2 + .../sync/transition_frontier_sync_effects.rs | 4 + .../sync/transition_frontier_sync_reducer.rs | 1 + 29 files changed, 976 insertions(+), 833 deletions(-) delete mode 100644 node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_effects.rs create mode 100644 node/src/block_producer_effectful/block_producer_effectful_actions.rs rename node/src/{block_producer/block_producer_effects.rs => block_producer_effectful/block_producer_effectful_effects.rs} (52%) rename node/src/{block_producer/block_producer_service.rs => block_producer_effectful/block_producer_effectful_service.rs} (100%) create mode 100644 node/src/block_producer_effectful/mod.rs create mode 100644 node/src/block_producer_effectful/vrf_evaluator_effectful/block_producer_vrf_evaluator_effectful_actions.rs create mode 100644 node/src/block_producer_effectful/vrf_evaluator_effectful/block_producer_vrf_evaluator_effectful_effects.rs rename node/src/{block_producer/vrf_evaluator/block_producer_vrf_evaluator_service.rs => block_producer_effectful/vrf_evaluator_effectful/block_producer_vrf_evaluator_effectful_service.rs} (65%) create mode 100644 node/src/block_producer_effectful/vrf_evaluator_effectful/mod.rs diff --git a/node/common/src/service/block_producer/vrf_evaluator.rs b/node/common/src/service/block_producer/vrf_evaluator.rs index 837a582a30..5f9768e538 100644 --- a/node/common/src/service/block_producer/vrf_evaluator.rs +++ b/node/common/src/service/block_producer/vrf_evaluator.rs @@ -51,7 +51,9 @@ pub fn vrf_evaluator( } } -impl node::block_producer::vrf_evaluator::BlockProducerVrfEvaluatorService for NodeService { +impl node::block_producer_effectful::vrf_evaluator_effectful::BlockProducerVrfEvaluatorService + for NodeService +{ fn evaluate(&mut self, data: VrfEvaluatorInput) { if let Some(bp) = self.block_producer.as_mut() { let _ = bp.vrf_evaluation_sender.send(data); diff --git a/node/src/action.rs b/node/src/action.rs index 867890ea0b..88ac985bf8 100644 --- a/node/src/action.rs +++ b/node/src/action.rs @@ -5,6 +5,7 @@ pub type ActionWithMeta = redux::ActionWithMeta; pub type ActionWithMetaRef<'a> = redux::ActionWithMeta<&'a Action>; pub use crate::block_producer::BlockProducerAction; +pub use crate::block_producer_effectful::BlockProducerEffectfulAction; pub use crate::consensus::ConsensusAction; pub use crate::event_source::EventSourceAction; pub use crate::external_snark_worker::ExternalSnarkWorkerAction; @@ -48,6 +49,7 @@ pub enum Action { TransactionPoolEffect(TransactionPoolEffectfulAction), ExternalSnarkWorker(ExternalSnarkWorkerAction), BlockProducer(BlockProducerAction), + BlockProducerEffectful(BlockProducerEffectfulAction), Rpc(RpcAction), RpcEffectful(RpcEffectfulAction), @@ -92,6 +94,7 @@ impl redux::EnablingCondition for Action { Action::SnarkPoolEffect(a) => a.is_enabled(state, time), Action::ExternalSnarkWorker(a) => a.is_enabled(state, time), Action::BlockProducer(a) => a.is_enabled(state, time), + Action::BlockProducerEffectful(a) => a.is_enabled(state, time), Action::Rpc(a) => a.is_enabled(state, time), Action::WatchedAccounts(a) => a.is_enabled(state, time), Action::TransactionPool(a) => a.is_enabled(state, time), diff --git a/node/src/action_kind.rs b/node/src/action_kind.rs index 6009bf5971..ca8823d8c8 100644 --- a/node/src/action_kind.rs +++ b/node/src/action_kind.rs @@ -17,6 +17,8 @@ use strum_macros::VariantArray; use crate::block_producer::vrf_evaluator::BlockProducerVrfEvaluatorAction; use crate::block_producer::BlockProducerAction; +use crate::block_producer_effectful::vrf_evaluator_effectful::BlockProducerVrfEvaluatorEffectfulAction; +use crate::block_producer_effectful::BlockProducerEffectfulAction; use crate::consensus::ConsensusAction; use crate::event_source::EventSourceAction; use crate::external_snark_worker::ExternalSnarkWorkerAction; @@ -129,6 +131,13 @@ pub enum ActionKind { BlockProducerWonSlotTransactionsGet, BlockProducerWonSlotTransactionsSuccess, BlockProducerWonSlotWait, + BlockProducerEffectfulBlockProveInit, + BlockProducerEffectfulBlockProveSuccess, + BlockProducerEffectfulBlockUnprovenBuild, + BlockProducerEffectfulStagedLedgerDiffCreateInit, + BlockProducerEffectfulStagedLedgerDiffCreateSuccess, + BlockProducerEffectfulWonSlot, + BlockProducerEffectfulWonSlotDiscard, BlockProducerVrfEvaluatorBeginDelegatorTableConstruction, BlockProducerVrfEvaluatorBeginEpochEvaluation, BlockProducerVrfEvaluatorCheckEpochBounds, @@ -145,6 +154,7 @@ pub enum ActionKind { BlockProducerVrfEvaluatorProcessSlotEvaluationSuccess, BlockProducerVrfEvaluatorSelectInitialSlot, BlockProducerVrfEvaluatorWaitForNextEvaluation, + BlockProducerVrfEvaluatorEffectfulEvaluateSlot, CheckTimeouts, ConsensusBestTipUpdate, ConsensusBlockChainProofUpdate, @@ -694,7 +704,7 @@ pub enum ActionKind { } impl ActionKind { - pub const COUNT: u16 = 581; + pub const COUNT: u16 = 589; } impl std::fmt::Display for ActionKind { @@ -721,6 +731,7 @@ impl ActionKindGet for Action { Self::TransactionPoolEffect(a) => a.kind(), Self::ExternalSnarkWorker(a) => a.kind(), Self::BlockProducer(a) => a.kind(), + Self::BlockProducerEffectful(a) => a.kind(), Self::Rpc(a) => a.kind(), Self::RpcEffectful(a) => a.kind(), Self::WatchedAccounts(a) => a.kind(), @@ -969,6 +980,25 @@ impl ActionKindGet for BlockProducerAction { } } +impl ActionKindGet for BlockProducerEffectfulAction { + fn kind(&self) -> ActionKind { + match self { + Self::VrfEvaluator(a) => a.kind(), + Self::WonSlot { .. } => ActionKind::BlockProducerEffectfulWonSlot, + Self::WonSlotDiscard { .. } => ActionKind::BlockProducerEffectfulWonSlotDiscard, + Self::StagedLedgerDiffCreateInit => { + ActionKind::BlockProducerEffectfulStagedLedgerDiffCreateInit + } + Self::StagedLedgerDiffCreateSuccess => { + ActionKind::BlockProducerEffectfulStagedLedgerDiffCreateSuccess + } + Self::BlockUnprovenBuild => ActionKind::BlockProducerEffectfulBlockUnprovenBuild, + Self::BlockProveInit => ActionKind::BlockProducerEffectfulBlockProveInit, + Self::BlockProveSuccess => ActionKind::BlockProducerEffectfulBlockProveSuccess, + } + } +} + impl ActionKindGet for RpcAction { fn kind(&self) -> ActionKind { match self { @@ -1482,6 +1512,14 @@ impl ActionKindGet for BlockProducerVrfEvaluatorAction { } } +impl ActionKindGet for BlockProducerVrfEvaluatorEffectfulAction { + fn kind(&self) -> ActionKind { + match self { + Self::EvaluateSlot { .. } => ActionKind::BlockProducerVrfEvaluatorEffectfulEvaluateSlot, + } + } +} + impl ActionKindGet for P2pConnectionOutgoingAction { fn kind(&self) -> ActionKind { match self { diff --git a/node/src/block_producer/block_producer_actions.rs b/node/src/block_producer/block_producer_actions.rs index b4baca2dfc..e234f33495 100644 --- a/node/src/block_producer/block_producer_actions.rs +++ b/node/src/block_producer/block_producer_actions.rs @@ -4,11 +4,10 @@ use openmina_core::block::ArcBlockWithHash; use openmina_core::ActionEvent; use serde::{Deserialize, Serialize}; +use crate::block_producer_effectful::StagedLedgerDiffCreateOutput; + use super::vrf_evaluator::BlockProducerVrfEvaluatorAction; -use super::{ - BlockProducerCurrentState, BlockProducerWonSlot, BlockProducerWonSlotDiscardReason, - StagedLedgerDiffCreateOutput, -}; +use super::{BlockProducerCurrentState, BlockProducerWonSlot, BlockProducerWonSlotDiscardReason}; pub type BlockProducerActionWithMeta = redux::ActionWithMeta; pub type BlockProducerActionWithMetaRef<'a> = redux::ActionWithMeta<&'a BlockProducerAction>; diff --git a/node/src/block_producer/block_producer_reducer.rs b/node/src/block_producer/block_producer_reducer.rs index d51596312f..2451bf6507 100644 --- a/node/src/block_producer/block_producer_reducer.rs +++ b/node/src/block_producer/block_producer_reducer.rs @@ -12,68 +12,104 @@ use mina_p2p_messages::{ StagedLedgerDiffBodyStableV1, StateBodyHash, StateHash, UnsignedExtendedUInt32StableV1, }, }; -use openmina_core::constants::constraint_constants; +use openmina_core::{block::ArcBlockWithHash, constants::constraint_constants}; use openmina_core::{ - block::AppliedBlock, + bug_condition, consensus::{ global_sub_window, grace_period_end, in_same_checkpoint_window, in_seed_update_range, relative_sub_window, }, }; +use redux::{callback, Dispatcher, Timestamp}; + +use crate::{ + transition_frontier::sync::TransitionFrontierSyncAction, Action, BlockProducerEffectfulAction, + State, Substate, TransactionPoolAction, +}; use super::{ - calc_epoch_seed, to_epoch_and_slot, BlockProducerAction, BlockProducerActionWithMetaRef, - BlockProducerCurrentState, BlockProducerEnabled, BlockProducerState, BlockWithoutProof, + calc_epoch_seed, next_epoch_first_slot, to_epoch_and_slot, + vrf_evaluator::{ + BlockProducerVrfEvaluatorAction, BlockProducerVrfEvaluatorState, InterruptReason, + }, + BlockProducerAction, BlockProducerActionWithMetaRef, BlockProducerCurrentState, + BlockProducerEnabled, BlockProducerState, BlockWithoutProof, }; impl BlockProducerState { - pub fn reducer( - &mut self, - action: BlockProducerActionWithMetaRef<'_>, - best_chain: &[AppliedBlock], - ) { - self.with_mut((), move |state| state.reducer(action, best_chain)) + pub fn reducer(state_context: Substate, action: BlockProducerActionWithMetaRef<'_>) { + BlockProducerEnabled::reducer(state_context, action); } } impl BlockProducerEnabled { - pub fn reducer( - &mut self, - action: BlockProducerActionWithMetaRef<'_>, - best_chain: &[AppliedBlock], - ) { + pub fn reducer(mut state_context: Substate, action: BlockProducerActionWithMetaRef<'_>) { let (action, meta) = action.split(); + let Ok(global_state) = state_context.get_substate_mut() else { + return; + }; + + let best_chain = &global_state.transition_frontier.best_chain; + let Some(state) = global_state.block_producer.as_mut() else { + return; + }; + match action { BlockProducerAction::VrfEvaluator(action) => { - self.vrf_evaluator.reducer(meta.with_action(action)) + BlockProducerVrfEvaluatorState::reducer( + Substate::from_compatible_substate(state_context), + meta.with_action(action), + ); } BlockProducerAction::BestTipUpdate { best_tip } => { - self.injected_blocks.remove(best_tip.hash()); + state.injected_blocks.remove(best_tip.hash()); // set the genesis timestamp on the first best tip update // TODO: move/remove once we can generate the genesis block - if self.vrf_evaluator.genesis_timestamp == redux::Timestamp::ZERO { - self.vrf_evaluator.genesis_timestamp = best_tip.genesis_timestamp(); + if state.vrf_evaluator.genesis_timestamp == redux::Timestamp::ZERO { + state.vrf_evaluator.genesis_timestamp = best_tip.genesis_timestamp(); + } + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + Self::dispatch_best_tip_update(dispatcher, state, best_tip); + } + BlockProducerAction::WonSlotSearch => { + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + if let Some(won_slot) = state.block_producer.with(None, |bp| { + let best_tip = state.transition_frontier.best_tip()?; + let cur_global_slot = state.cur_global_slot()?; + bp.vrf_evaluator.next_won_slot(cur_global_slot, best_tip) + }) { + dispatcher.push(BlockProducerAction::WonSlot { won_slot }); } } - BlockProducerAction::WonSlotSearch => {} BlockProducerAction::WonSlot { won_slot } => { - self.current = BlockProducerCurrentState::WonSlot { + state.current = BlockProducerCurrentState::WonSlot { time: meta.time(), won_slot: won_slot.clone(), }; + + state_context + .into_dispatcher() + .push(BlockProducerEffectfulAction::WonSlot { + won_slot: won_slot.clone(), + }); } BlockProducerAction::WonSlotDiscard { reason } => { - if let Some(won_slot) = self.current.won_slot() { - self.current = BlockProducerCurrentState::WonSlotDiscarded { + if let Some(won_slot) = state.current.won_slot() { + state.current = BlockProducerCurrentState::WonSlotDiscarded { time: meta.time(), won_slot: won_slot.clone(), - reason: reason.clone(), + reason: *reason, }; } + + state_context + .into_dispatcher() + .push(BlockProducerEffectfulAction::WonSlotDiscard { reason: *reason }); } BlockProducerAction::WonSlotWait => { - if let Some(won_slot) = self.current.won_slot() { - self.current = BlockProducerCurrentState::WonSlotWait { + if let Some(won_slot) = state.current.won_slot() { + state.current = BlockProducerCurrentState::WonSlotWait { time: meta.time(), won_slot: won_slot.clone(), }; @@ -82,37 +118,45 @@ impl BlockProducerEnabled { BlockProducerAction::WonSlotTransactionsGet => { let BlockProducerCurrentState::WonSlotProduceInit { won_slot, chain, .. - } = &mut self.current + } = &mut state.current else { return; }; - self.current = BlockProducerCurrentState::WonSlotTransactionsGet { + state.current = BlockProducerCurrentState::WonSlotTransactionsGet { time: meta.time(), won_slot: won_slot.clone(), chain: chain.clone(), - } + }; + + state_context + .into_dispatcher() + .push(TransactionPoolAction::CollectTransactionsByFee); } BlockProducerAction::WonSlotTransactionsSuccess { transactions_by_fee, } => { let BlockProducerCurrentState::WonSlotTransactionsGet { won_slot, chain, .. - } = &mut self.current + } = &mut state.current else { return; }; - self.current = BlockProducerCurrentState::WonSlotTransactionsSuccess { + state.current = BlockProducerCurrentState::WonSlotTransactionsSuccess { time: meta.time(), won_slot: won_slot.clone(), chain: chain.clone(), transactions_by_fee: transactions_by_fee.clone(), - } + }; + + state_context + .into_dispatcher() + .push(BlockProducerAction::StagedLedgerDiffCreateInit); } BlockProducerAction::WonSlotProduceInit => { - if let Some(won_slot) = self.current.won_slot() { - let Some(chain) = best_chain.last().map(|best_tip| { + if let Some(won_slot) = state.current.won_slot() { + if let Some(chain) = best_chain.last().map(|best_tip| { if best_tip.global_slot() == won_slot.global_slot() { // We are producing block which replaces current best tip // instead of extending it. @@ -120,29 +164,35 @@ impl BlockProducerEnabled { } else { best_chain.to_vec() } - }) else { - return; - }; - - self.current = BlockProducerCurrentState::WonSlotProduceInit { - time: meta.time(), - won_slot: won_slot.clone(), - chain, + }) { + state.current = BlockProducerCurrentState::WonSlotProduceInit { + time: meta.time(), + won_slot: won_slot.clone(), + chain, + }; }; } + + state_context + .into_dispatcher() + .push(BlockProducerAction::WonSlotTransactionsGet); + } + BlockProducerAction::StagedLedgerDiffCreateInit => { + state_context + .into_dispatcher() + .push(BlockProducerEffectfulAction::StagedLedgerDiffCreateInit); } - BlockProducerAction::StagedLedgerDiffCreateInit => {} BlockProducerAction::StagedLedgerDiffCreatePending => { let BlockProducerCurrentState::WonSlotTransactionsSuccess { won_slot, chain, transactions_by_fee, .. - } = &mut self.current + } = &mut state.current else { return; }; - self.current = BlockProducerCurrentState::StagedLedgerDiffCreatePending { + state.current = BlockProducerCurrentState::StagedLedgerDiffCreatePending { time: meta.time(), won_slot: won_slot.clone(), chain: std::mem::take(chain), @@ -154,11 +204,11 @@ impl BlockProducerEnabled { won_slot, chain, .. - } = &mut self.current + } = &mut state.current else { return; }; - self.current = BlockProducerCurrentState::StagedLedgerDiffCreateSuccess { + state.current = BlockProducerCurrentState::StagedLedgerDiffCreateSuccess { time: meta.time(), won_slot: won_slot.clone(), chain: std::mem::take(chain), @@ -170,302 +220,23 @@ impl BlockProducerEnabled { pending_coinbase_witness: output.pending_coinbase_witness.clone(), stake_proof_sparse_ledger: output.stake_proof_sparse_ledger.clone(), }; + + state_context + .into_dispatcher() + .push(BlockProducerEffectfulAction::StagedLedgerDiffCreateSuccess); } BlockProducerAction::BlockUnprovenBuild => { - let BlockProducerCurrentState::StagedLedgerDiffCreateSuccess { - won_slot, - chain, - diff, - diff_hash, - staged_ledger_hash, - emitted_ledger_proof, - pending_coinbase_update, - pending_coinbase_witness, - stake_proof_sparse_ledger, - .. - } = std::mem::take(&mut self.current) - else { - return; - }; - let Some(pred_block) = chain.last() else { - return; - }; - - let pred_consensus_state = &pred_block.header().protocol_state.body.consensus_state; - let pred_blockchain_state = - &pred_block.header().protocol_state.body.blockchain_state; - - let genesis_ledger_hash = &pred_blockchain_state.genesis_ledger_hash; - - let block_timestamp = won_slot.timestamp(); - let pred_global_slot = pred_consensus_state - .curr_global_slot_since_hard_fork - .clone(); - let curr_global_slot_since_hard_fork = won_slot.global_slot.clone(); - let global_slot_since_genesis = - won_slot.global_slot_since_genesis(pred_block.global_slot_diff()); - let (pred_epoch, _) = to_epoch_and_slot(&pred_global_slot); - let (next_epoch, next_slot) = to_epoch_and_slot(&curr_global_slot_since_hard_fork); - let has_ancestor_in_same_checkpoint_window = - in_same_checkpoint_window(&pred_global_slot, &curr_global_slot_since_hard_fork); - - let block_stake_winner = won_slot.delegator.0.clone(); - let vrf_truncated_output: ConsensusVrfOutputTruncatedStableV1 = - (*won_slot.vrf_output).clone().into(); - let vrf_hash = won_slot.vrf_output.hash(); - let block_creator = self.config.pub_key.clone(); - let coinbase_receiver = self.config.coinbase_receiver().clone(); - let proposed_protocol_version_opt = self.config.proposed_protocol_version.clone(); - - let ledger_proof_statement = ledger_proof_statement_from_emitted_proof( - emitted_ledger_proof.as_deref(), - &pred_blockchain_state.ledger_proof_statement, - ); - - let supply_increase = emitted_ledger_proof.as_ref().map_or(Signed::zero(), |v| { - Signed::from(&v.statement.supply_increase) - }); - - let total_currency = { - let (amount, overflowed) = - Amount::from(pred_consensus_state.total_currency.clone()) - .add_signed_flagged(supply_increase); - if overflowed { - todo!("total_currency overflowed"); - } - amount - }; - - let (staking_epoch_data, next_epoch_data, epoch_count) = { - let next_staking_ledger = - if pred_block.snarked_ledger_hash() == genesis_ledger_hash { - pred_consensus_state.next_epoch_data.ledger.clone() - } else { - MinaBaseEpochLedgerValueStableV1 { - hash: pred_block.snarked_ledger_hash().clone(), - total_currency: (&total_currency).into(), - } - }; - let (staking_data, next_data, epoch_count) = if next_epoch > pred_epoch { - let staking_data = - next_to_staking_epoch_data(&pred_consensus_state.next_epoch_data); - let next_data = - ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1 { - seed: pred_consensus_state.next_epoch_data.seed.clone(), - ledger: next_staking_ledger, - start_checkpoint: pred_block.hash().clone(), - // comment from Mina repo: (* TODO: We need to make sure issue #2328 is properly addressed. *) - lock_checkpoint: StateHash::zero(), - epoch_length: UnsignedExtendedUInt32StableV1(1.into()), - }; - let epoch_count = UnsignedExtendedUInt32StableV1( - (pred_consensus_state.epoch_count.as_u32() + 1).into(), - ); - (staking_data, next_data, epoch_count) - } else { - assert_eq!(pred_epoch, next_epoch); - let mut next_data = pred_consensus_state.next_epoch_data.clone(); - next_data.epoch_length = UnsignedExtendedUInt32StableV1( - (next_data.epoch_length.as_u32() + 1).into(), - ); - ( - pred_consensus_state.staking_epoch_data.clone(), - next_data, - pred_consensus_state.epoch_count, - ) - }; - - let next_data = if in_seed_update_range(next_slot, pred_block.constants()) { - ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1 { - seed: calc_epoch_seed(&next_data.seed, vrf_hash), - lock_checkpoint: pred_block.hash().clone(), - ..next_data - } - } else { - next_data - }; - - (staking_data, next_data, epoch_count) - }; - - let (min_window_density, sub_window_densities) = { - // TODO(binier): when should this be false? - let incr_window = true; - let pred_sub_window_densities = &pred_consensus_state.sub_window_densities; - - let pred_global_sub_window = - global_sub_window(&pred_global_slot, pred_block.constants()); - let next_global_sub_window = global_sub_window( - &curr_global_slot_since_hard_fork, - pred_block.constants(), - ); - - let pred_relative_sub_window = relative_sub_window(pred_global_sub_window); - let next_relative_sub_window = relative_sub_window(next_global_sub_window); - - let is_same_global_sub_window = - pred_global_sub_window == next_global_sub_window; - let are_windows_overlapping = pred_global_sub_window - + constraint_constants().sub_windows_per_window as u32 - >= next_global_sub_window; - - let current_sub_window_densities = pred_sub_window_densities - .iter() - .enumerate() - .map(|(i, density)| (i as u32, density.as_u32())) - .map(|(i, density)| { - let gt_pred_sub_window = i > pred_relative_sub_window; - let lt_next_sub_window = i < next_relative_sub_window; - let within_range = - if pred_relative_sub_window < next_relative_sub_window { - gt_pred_sub_window && lt_next_sub_window - } else { - gt_pred_sub_window || lt_next_sub_window - }; - if is_same_global_sub_window || are_windows_overlapping && !within_range - { - density - } else { - 0 - } - }) - .collect::>(); - - let grace_period_end = grace_period_end(pred_block.constants()); - let min_window_density = if is_same_global_sub_window - || curr_global_slot_since_hard_fork.slot_number.as_u32() < grace_period_end - { - pred_consensus_state.min_window_density - } else { - let cur_density = current_sub_window_densities.iter().sum(); - let min_density = pred_consensus_state - .min_window_density - .as_u32() - .min(cur_density); - UnsignedExtendedUInt32StableV1(min_density.into()) - }; - - let next_sub_window_densities = current_sub_window_densities - .into_iter() - .enumerate() - .map(|(i, density)| (i as u32, density)) - .map(|(i, density)| { - let is_next_sub_window = i == next_relative_sub_window; - if is_next_sub_window { - let density = if is_same_global_sub_window { - density - } else { - 0 - }; - if incr_window { - density + 1 - } else { - density - } - } else { - density - } - }) - .map(|v| UnsignedExtendedUInt32StableV1(v.into())) - .collect(); - - (min_window_density, next_sub_window_densities) - }; - - let consensus_state = ConsensusProofOfStakeDataConsensusStateValueStableV2 { - blockchain_length: UnsignedExtendedUInt32StableV1( - (pred_block.height() + 1).into(), - ), - epoch_count, - min_window_density, - sub_window_densities, - last_vrf_output: vrf_truncated_output, - total_currency: (&total_currency).into(), - curr_global_slot_since_hard_fork, - global_slot_since_genesis, - staking_epoch_data, - next_epoch_data, - has_ancestor_in_same_checkpoint_window, - block_stake_winner, - block_creator, - coinbase_receiver, - // TODO(binier): Staged_ledger.can_apply_supercharged_coinbase_exn - supercharge_coinbase: constraint_constants().supercharged_coinbase_factor != 0, - }; - - let protocol_state = MinaStateProtocolStateValueStableV2 { - previous_state_hash: pred_block.hash().clone(), - body: MinaStateProtocolStateBodyValueStableV2 { - genesis_state_hash: if pred_block.is_genesis() { - pred_block.hash().clone() - } else { - pred_block - .header() - .protocol_state - .body - .genesis_state_hash - .clone() - }, - constants: pred_block.header().protocol_state.body.constants.clone(), - blockchain_state: MinaStateBlockchainStateValueStableV2 { - staged_ledger_hash: staged_ledger_hash.clone(), - genesis_ledger_hash: genesis_ledger_hash.clone(), - ledger_proof_statement, - timestamp: block_timestamp, - body_reference: diff_hash.clone(), - }, - consensus_state, - }, - }; - - let chain_proof_len = pred_block.constants().delta.as_u32() as usize; - let delta_block_chain_proof = match chain_proof_len { - 0 => (pred_block.hash().clone(), List::new()), - chain_proof_len => { - // TODO(binier): test - let mut iter = chain.iter().rev().take(chain_proof_len + 1).rev(); - if let Some(first_block) = iter.next() { - let first_hash = first_block.hash().clone(); - let body_hashes = iter - .filter_map(|b| b.header().protocol_state.body.try_hash().ok()) // TODO: Handle error ? - .map(StateBodyHash::from) - .collect(); - (first_hash, body_hashes) - } else { - // TODO: test this as well - // If the chain is empty, return the same as when chain_proof_len is 0 - (pred_block.hash().clone(), List::new()) - } - } - }; - - let block = BlockWithoutProof { - protocol_state, - delta_block_chain_proof, - current_protocol_version: pred_block.header().current_protocol_version.clone(), - proposed_protocol_version_opt, - body: StagedLedgerDiffBodyStableV1 { - staged_ledger_diff: diff.clone(), - }, - }; - let Ok(block_hash) = block.protocol_state.try_hash() else { - openmina_core::log::inner::error!("Invalid protocol state"); - return; - }; + state.reduce_block_unproved_build(meta.time()); - self.current = BlockProducerCurrentState::BlockUnprovenBuilt { - time: meta.time(), - won_slot, - chain, - emitted_ledger_proof, - pending_coinbase_update, - pending_coinbase_witness, - stake_proof_sparse_ledger, - block, - block_hash, - } + state_context + .into_dispatcher() + .push(BlockProducerEffectfulAction::BlockUnprovenBuild); + } + BlockProducerAction::BlockProveInit => { + state_context + .into_dispatcher() + .push(BlockProducerEffectfulAction::BlockProveInit); } - BlockProducerAction::BlockProveInit => {} BlockProducerAction::BlockProvePending => { if let BlockProducerCurrentState::BlockUnprovenBuilt { won_slot, @@ -477,9 +248,9 @@ impl BlockProducerEnabled { block, block_hash, .. - } = std::mem::take(&mut self.current) + } = std::mem::take(&mut state.current) { - self.current = BlockProducerCurrentState::BlockProvePending { + state.current = BlockProducerCurrentState::BlockProvePending { time: meta.time(), won_slot, chain, @@ -499,9 +270,9 @@ impl BlockProducerEnabled { block, block_hash, .. - } = std::mem::take(&mut self.current) + } = std::mem::take(&mut state.current) { - self.current = BlockProducerCurrentState::BlockProveSuccess { + state.current = BlockProducerCurrentState::BlockProveSuccess { time: meta.time(), won_slot, chain, @@ -510,6 +281,10 @@ impl BlockProducerEnabled { proof: proof.clone(), }; } + + state_context + .into_dispatcher() + .push(BlockProducerEffectfulAction::BlockProveSuccess); } BlockProducerAction::BlockProduced => { if let BlockProducerCurrentState::BlockProveSuccess { @@ -519,35 +294,424 @@ impl BlockProducerEnabled { block_hash, proof, .. - } = std::mem::take(&mut self.current) + } = std::mem::take(&mut state.current) { - self.current = BlockProducerCurrentState::Produced { + state.current = BlockProducerCurrentState::Produced { time: meta.time(), won_slot, chain, block: block.with_hash_and_proof(block_hash, *proof), }; } + + state_context + .into_dispatcher() + .push(BlockProducerAction::BlockInject); + } + BlockProducerAction::BlockInject => { + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + + let Some((best_tip, root_block, blocks_inbetween)) = None.or_else(|| { + let (best_tip, chain) = state.block_producer.produced_block_with_chain()?; + let mut iter = chain.iter(); + let root_block = iter.next()?.block_with_hash(); + let blocks_inbetween = iter.map(|b| b.hash().clone()).collect(); + Some((best_tip.clone(), root_block.clone(), blocks_inbetween)) + }) else { + return; + }; + + let previous_root_snarked_ledger_hash = state + .transition_frontier + .root() + .map(|b| b.snarked_ledger_hash().clone()); + + dispatcher.push(TransitionFrontierSyncAction::BestTipUpdate { + previous_root_snarked_ledger_hash, + best_tip: best_tip.clone(), + root_block, + blocks_inbetween, + on_success: Some(callback!( + on_transition_frontier_sync_best_tip_update(_p: ()) -> crate::Action{ + BlockProducerAction::BlockInjected + } + )), + }); } - BlockProducerAction::BlockInject => {} BlockProducerAction::BlockInjected => { if let BlockProducerCurrentState::Produced { won_slot, chain, block, .. - } = &mut self.current + } = &mut state.current { - self.injected_blocks.insert(block.hash().clone()); - self.current = BlockProducerCurrentState::Injected { + state.injected_blocks.insert(block.hash().clone()); + state.current = BlockProducerCurrentState::Injected { time: meta.time(), won_slot: won_slot.clone(), chain: std::mem::take(chain), block: block.clone(), }; } + + state_context + .into_dispatcher() + .push(BlockProducerAction::WonSlotSearch); + } + } + } + + fn reduce_block_unproved_build(&mut self, time: Timestamp) { + let BlockProducerCurrentState::StagedLedgerDiffCreateSuccess { + won_slot, + chain, + diff, + diff_hash, + staged_ledger_hash, + emitted_ledger_proof, + pending_coinbase_update, + pending_coinbase_witness, + stake_proof_sparse_ledger, + .. + } = std::mem::take(&mut self.current) + else { + return; + }; + let Some(pred_block) = chain.last() else { + return; + }; + + let pred_consensus_state = &pred_block.header().protocol_state.body.consensus_state; + let pred_blockchain_state = &pred_block.header().protocol_state.body.blockchain_state; + + let genesis_ledger_hash = &pred_blockchain_state.genesis_ledger_hash; + + let block_timestamp = won_slot.timestamp(); + let pred_global_slot = pred_consensus_state + .curr_global_slot_since_hard_fork + .clone(); + let curr_global_slot_since_hard_fork = won_slot.global_slot.clone(); + let global_slot_since_genesis = + won_slot.global_slot_since_genesis(pred_block.global_slot_diff()); + let (pred_epoch, _) = to_epoch_and_slot(&pred_global_slot); + let (next_epoch, next_slot) = to_epoch_and_slot(&curr_global_slot_since_hard_fork); + let has_ancestor_in_same_checkpoint_window = + in_same_checkpoint_window(&pred_global_slot, &curr_global_slot_since_hard_fork); + + let block_stake_winner = won_slot.delegator.0.clone(); + let vrf_truncated_output: ConsensusVrfOutputTruncatedStableV1 = + (*won_slot.vrf_output).clone().into(); + let vrf_hash = won_slot.vrf_output.hash(); + let block_creator = self.config.pub_key.clone(); + let coinbase_receiver = self.config.coinbase_receiver().clone(); + let proposed_protocol_version_opt = self.config.proposed_protocol_version.clone(); + + let ledger_proof_statement = ledger_proof_statement_from_emitted_proof( + emitted_ledger_proof.as_deref(), + &pred_blockchain_state.ledger_proof_statement, + ); + + let supply_increase = emitted_ledger_proof.as_ref().map_or(Signed::zero(), |v| { + Signed::from(&v.statement.supply_increase) + }); + + let total_currency = { + let (amount, overflowed) = Amount::from(pred_consensus_state.total_currency.clone()) + .add_signed_flagged(supply_increase); + if overflowed { + todo!("total_currency overflowed"); + } + amount + }; + + let (staking_epoch_data, next_epoch_data, epoch_count) = { + let next_staking_ledger = if pred_block.snarked_ledger_hash() == genesis_ledger_hash { + pred_consensus_state.next_epoch_data.ledger.clone() + } else { + MinaBaseEpochLedgerValueStableV1 { + hash: pred_block.snarked_ledger_hash().clone(), + total_currency: (&total_currency).into(), + } + }; + let (staking_data, next_data, epoch_count) = if next_epoch > pred_epoch { + let staking_data = + next_to_staking_epoch_data(&pred_consensus_state.next_epoch_data); + let next_data = ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1 { + seed: pred_consensus_state.next_epoch_data.seed.clone(), + ledger: next_staking_ledger, + start_checkpoint: pred_block.hash().clone(), + // comment from Mina repo: (* TODO: We need to make sure issue #2328 is properly addressed. *) + lock_checkpoint: StateHash::zero(), + epoch_length: UnsignedExtendedUInt32StableV1(1.into()), + }; + let epoch_count = UnsignedExtendedUInt32StableV1( + (pred_consensus_state.epoch_count.as_u32() + 1).into(), + ); + (staking_data, next_data, epoch_count) + } else { + assert_eq!(pred_epoch, next_epoch); + let mut next_data = pred_consensus_state.next_epoch_data.clone(); + next_data.epoch_length = + UnsignedExtendedUInt32StableV1((next_data.epoch_length.as_u32() + 1).into()); + ( + pred_consensus_state.staking_epoch_data.clone(), + next_data, + pred_consensus_state.epoch_count, + ) + }; + + let next_data = if in_seed_update_range(next_slot, pred_block.constants()) { + ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1 { + seed: calc_epoch_seed(&next_data.seed, vrf_hash), + lock_checkpoint: pred_block.hash().clone(), + ..next_data + } + } else { + next_data + }; + + (staking_data, next_data, epoch_count) + }; + + let (min_window_density, sub_window_densities) = { + // TODO(binier): when should this be false? + let incr_window = true; + let pred_sub_window_densities = &pred_consensus_state.sub_window_densities; + + let pred_global_sub_window = + global_sub_window(&pred_global_slot, pred_block.constants()); + let next_global_sub_window = + global_sub_window(&curr_global_slot_since_hard_fork, pred_block.constants()); + + let pred_relative_sub_window = relative_sub_window(pred_global_sub_window); + let next_relative_sub_window = relative_sub_window(next_global_sub_window); + + let is_same_global_sub_window = pred_global_sub_window == next_global_sub_window; + let are_windows_overlapping = pred_global_sub_window + + constraint_constants().sub_windows_per_window as u32 + >= next_global_sub_window; + + let current_sub_window_densities = pred_sub_window_densities + .iter() + .enumerate() + .map(|(i, density)| (i as u32, density.as_u32())) + .map(|(i, density)| { + let gt_pred_sub_window = i > pred_relative_sub_window; + let lt_next_sub_window = i < next_relative_sub_window; + let within_range = if pred_relative_sub_window < next_relative_sub_window { + gt_pred_sub_window && lt_next_sub_window + } else { + gt_pred_sub_window || lt_next_sub_window + }; + if is_same_global_sub_window || are_windows_overlapping && !within_range { + density + } else { + 0 + } + }) + .collect::>(); + + let grace_period_end = grace_period_end(pred_block.constants()); + let min_window_density = if is_same_global_sub_window + || curr_global_slot_since_hard_fork.slot_number.as_u32() < grace_period_end + { + pred_consensus_state.min_window_density + } else { + let cur_density = current_sub_window_densities.iter().sum(); + let min_density = pred_consensus_state + .min_window_density + .as_u32() + .min(cur_density); + UnsignedExtendedUInt32StableV1(min_density.into()) + }; + + let next_sub_window_densities = current_sub_window_densities + .into_iter() + .enumerate() + .map(|(i, density)| (i as u32, density)) + .map(|(i, density)| { + let is_next_sub_window = i == next_relative_sub_window; + if is_next_sub_window { + let density = if is_same_global_sub_window { + density + } else { + 0 + }; + if incr_window { + density + 1 + } else { + density + } + } else { + density + } + }) + .map(|v| UnsignedExtendedUInt32StableV1(v.into())) + .collect(); + + (min_window_density, next_sub_window_densities) + }; + + let consensus_state = ConsensusProofOfStakeDataConsensusStateValueStableV2 { + blockchain_length: UnsignedExtendedUInt32StableV1((pred_block.height() + 1).into()), + epoch_count, + min_window_density, + sub_window_densities, + last_vrf_output: vrf_truncated_output, + total_currency: (&total_currency).into(), + curr_global_slot_since_hard_fork, + global_slot_since_genesis, + staking_epoch_data, + next_epoch_data, + has_ancestor_in_same_checkpoint_window, + block_stake_winner, + block_creator, + coinbase_receiver, + // TODO(binier): Staged_ledger.can_apply_supercharged_coinbase_exn + supercharge_coinbase: constraint_constants().supercharged_coinbase_factor != 0, + }; + + let protocol_state = MinaStateProtocolStateValueStableV2 { + previous_state_hash: pred_block.hash().clone(), + body: MinaStateProtocolStateBodyValueStableV2 { + genesis_state_hash: if pred_block.is_genesis() { + pred_block.hash().clone() + } else { + pred_block + .header() + .protocol_state + .body + .genesis_state_hash + .clone() + }, + constants: pred_block.header().protocol_state.body.constants.clone(), + blockchain_state: MinaStateBlockchainStateValueStableV2 { + staged_ledger_hash: staged_ledger_hash.clone(), + genesis_ledger_hash: genesis_ledger_hash.clone(), + ledger_proof_statement, + timestamp: block_timestamp, + body_reference: diff_hash.clone(), + }, + consensus_state, + }, + }; + + let chain_proof_len = pred_block.constants().delta.as_u32() as usize; + let delta_block_chain_proof = match chain_proof_len { + 0 => (pred_block.hash().clone(), List::new()), + chain_proof_len => { + // TODO(binier): test + let mut iter = chain.iter().rev().take(chain_proof_len + 1).rev(); + if let Some(first_block) = iter.next() { + let first_hash = first_block.hash().clone(); + let body_hashes = iter + .filter_map(|b| b.header().protocol_state.body.try_hash().ok()) // TODO: Handle error ? + .map(StateBodyHash::from) + .collect(); + (first_hash, body_hashes) + } else { + // TODO: test this as well + // If the chain is empty, return the same as when chain_proof_len is 0 + (pred_block.hash().clone(), List::new()) + } + } + }; + + let block = BlockWithoutProof { + protocol_state, + delta_block_chain_proof, + current_protocol_version: pred_block.header().current_protocol_version.clone(), + proposed_protocol_version_opt, + body: StagedLedgerDiffBodyStableV1 { + staged_ledger_diff: diff.clone(), + }, + }; + let Ok(block_hash) = block.protocol_state.try_hash() else { + openmina_core::log::inner::error!("Invalid protocol state"); + return; + }; + + self.current = BlockProducerCurrentState::BlockUnprovenBuilt { + time, + won_slot, + chain, + emitted_ledger_proof, + pending_coinbase_update, + pending_coinbase_witness, + stake_proof_sparse_ledger, + block, + block_hash, + }; + } + + fn dispatch_best_tip_update( + dispatcher: &mut Dispatcher, + state: &State, + best_tip: &ArcBlockWithHash, + ) { + let global_slot = best_tip + .consensus_state() + .curr_global_slot_since_hard_fork + .clone(); + + let (best_tip_epoch, best_tip_slot) = to_epoch_and_slot(&global_slot); + let root_block_epoch = if let Some(root_block) = state.transition_frontier.root() { + let root_block_global_slot = root_block.curr_global_slot_since_hard_fork(); + to_epoch_and_slot(root_block_global_slot).0 + } else { + bug_condition!("Expected to find a block at the root of the transition frontier but there was none"); + best_tip_epoch.saturating_sub(1) + }; + let next_epoch_first_slot = next_epoch_first_slot(&global_slot); + let current_epoch = state.current_epoch(); + let current_slot = state.current_slot(); + + dispatcher.push(BlockProducerVrfEvaluatorAction::InitializeEvaluator { + best_tip: best_tip.clone(), + }); + + // None if the evaluator is not evaluating + let currenty_evaluated_epoch = state + .block_producer + .vrf_evaluator() + .and_then(|vrf_evaluator| vrf_evaluator.currently_evaluated_epoch()); + + if let Some(currently_evaluated_epoch) = currenty_evaluated_epoch { + // if we receive a block with higher epoch than the current one, interrupt the evaluation + if currently_evaluated_epoch < best_tip_epoch { + dispatcher.push(BlockProducerVrfEvaluatorAction::InterruptEpochEvaluation { + reason: InterruptReason::BestTipWithHigherEpoch, + }); } } + + let is_next_epoch_seed_finalized = if let Some(current_slot) = current_slot { + !in_seed_update_range(current_slot, best_tip.constants()) + } else { + false + }; + + dispatcher.push(BlockProducerVrfEvaluatorAction::CheckEpochEvaluability { + current_epoch, + is_next_epoch_seed_finalized, + root_block_epoch, + best_tip_epoch, + best_tip_slot, + best_tip_global_slot: best_tip.global_slot(), + next_epoch_first_slot, + staking_epoch_data: Box::new(best_tip.consensus_state().staking_epoch_data.clone()), + next_epoch_data: Box::new(best_tip.consensus_state().next_epoch_data.clone()), + }); + + if let Some(reason) = state + .block_producer + .with(None, |bp| bp.current.won_slot_should_discard(best_tip)) + { + dispatcher.push(BlockProducerAction::WonSlotDiscard { reason }); + } else { + dispatcher.push(BlockProducerAction::WonSlotSearch); + } } } diff --git a/node/src/block_producer/block_producer_state.rs b/node/src/block_producer/block_producer_state.rs index 015b4aa2e0..69c7405a03 100644 --- a/node/src/block_producer/block_producer_state.rs +++ b/node/src/block_producer/block_producer_state.rs @@ -135,7 +135,7 @@ pub enum BlockProducerCurrentState { }, } -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Copy)] pub enum BlockProducerWonSlotDiscardReason { BestTipStakingLedgerDifferent, BestTipGlobalSlotHigher, @@ -152,20 +152,19 @@ impl BlockProducerState { })) } - #[inline(always)] - pub(super) fn with<'a, F, R: 'a>(&'a self, default: R, fun: F) -> R + pub fn with<'a, F, R: 'a>(&'a self, default: R, fun: F) -> R where F: FnOnce(&'a BlockProducerEnabled) -> R, { self.0.as_ref().map_or(default, fun) } - #[inline(always)] - pub(super) fn with_mut(&mut self, default: R, fun: F) -> R - where - F: FnOnce(&mut BlockProducerEnabled) -> R, - { - self.0.as_mut().map_or(default, fun) + pub fn as_mut(&mut self) -> Option<&mut BlockProducerEnabled> { + self.0.as_mut() + } + + pub fn as_ref(&self) -> Option<&BlockProducerEnabled> { + self.0.as_ref() } pub fn is_enabled(&self) -> bool { diff --git a/node/src/block_producer/mod.rs b/node/src/block_producer/mod.rs index ba0008c70a..e0fd494eb1 100644 --- a/node/src/block_producer/mod.rs +++ b/node/src/block_producer/mod.rs @@ -14,12 +14,6 @@ pub use block_producer_actions::*; mod block_producer_reducer; -mod block_producer_effects; -pub use block_producer_effects::*; - -mod block_producer_service; -pub use block_producer_service::*; - use ledger::AccountIndex; use mina_p2p_messages::{list::List, v2}; use openmina_core::block::ArcBlockWithHash; diff --git a/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_effects.rs b/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_effects.rs deleted file mode 100644 index 0127e3eb71..0000000000 --- a/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_effects.rs +++ /dev/null @@ -1,238 +0,0 @@ -use redux::ActionMeta; - -use crate::block_producer::to_epoch_and_slot; -use crate::ledger::read::LedgerReadAction; -use crate::ledger::read::LedgerReadInitCallback; -use crate::ledger::read::LedgerReadRequest; -use crate::Service; -use crate::Store; - -use super::BlockProducerVrfEvaluatorAction; -use super::BlockProducerVrfEvaluatorStatus; -use super::SlotPositionInEpoch; - -impl BlockProducerVrfEvaluatorAction { - pub fn effects(self, _: &ActionMeta, store: &mut Store) { - match self { - BlockProducerVrfEvaluatorAction::EvaluateSlot { vrf_input } => { - store.service.evaluate(vrf_input); - } - BlockProducerVrfEvaluatorAction::ProcessSlotEvaluationSuccess { - vrf_output, .. - } => { - if let Some(vrf_evaluator_state) = store.state().block_producer.vrf_evaluator() { - if let Some(pending_evaluation) = vrf_evaluator_state.current_evaluation() { - store.dispatch(BlockProducerVrfEvaluatorAction::CheckEpochBounds { - epoch_number: pending_evaluation.epoch_number, - latest_evaluated_global_slot: vrf_output.global_slot(), - }); - } - } - } - BlockProducerVrfEvaluatorAction::CheckEpochBounds { - latest_evaluated_global_slot, - epoch_number, - } => { - if let Some(epoch_bound) = store - .state() - .block_producer - .vrf_evaluator() - .and_then(|s| s.get_epoch_bound_from_check()) - { - match epoch_bound { - SlotPositionInEpoch::Beginning | SlotPositionInEpoch::Within => { - store.dispatch( - BlockProducerVrfEvaluatorAction::ContinueEpochEvaluation { - latest_evaluated_global_slot, - epoch_number, - }, - ); - } - SlotPositionInEpoch::End => { - store.dispatch( - BlockProducerVrfEvaluatorAction::FinishEpochEvaluation { - latest_evaluated_global_slot, - epoch_number, - }, - ); - } - } - } - } - BlockProducerVrfEvaluatorAction::InitializeEvaluator { best_tip } => { - // Note: pure function, but needs access to other parts of the state - if store.state().block_producer.vrf_evaluator().is_some() { - if best_tip.consensus_state().epoch_count.as_u32() == 0 { - store.dispatch( - BlockProducerVrfEvaluatorAction::FinalizeEvaluatorInitialization { - previous_epoch_and_height: None, - }, - ); - } else { - let k = best_tip.constants().k.as_u32(); - let (epoch, slot) = to_epoch_and_slot( - &best_tip.consensus_state().curr_global_slot_since_hard_fork, - ); - let previous_epoch = epoch.saturating_sub(1); - let last_height = if slot < k { - let found = store - .state() - .transition_frontier - .best_chain - .iter() - .rev() - .find(|b| { - b.consensus_state().epoch_count.as_u32() == previous_epoch - }); - - if let Some(block) = found { - block.height() - } else { - Default::default() - } - } else if let Some(root_block) = store.state().transition_frontier.root() { - root_block.height() - } else { - Default::default() - }; - store.dispatch( - BlockProducerVrfEvaluatorAction::FinalizeEvaluatorInitialization { - previous_epoch_and_height: Some((previous_epoch, last_height)), - }, - ); - } - } - } - BlockProducerVrfEvaluatorAction::FinalizeEvaluatorInitialization { .. } => {} - BlockProducerVrfEvaluatorAction::CheckEpochEvaluability { - root_block_epoch: _, - best_tip_global_slot, - best_tip_epoch, - best_tip_slot, - next_epoch_first_slot, - current_epoch: _, - is_next_epoch_seed_finalized: _, - staking_epoch_data: _, - next_epoch_data: _, - } => { - let vrf_evaluator_state = store.state().block_producer.vrf_evaluator_with_config(); - - if let Some((vrf_evaluator_state, config)) = vrf_evaluator_state { - if let Some(epoch_data) = vrf_evaluator_state.epoch_context().get_epoch_data() { - store.dispatch( - BlockProducerVrfEvaluatorAction::InitializeEpochEvaluation { - staking_epoch_data: epoch_data, - producer: config.pub_key.clone().into(), - best_tip_global_slot, - best_tip_epoch, - best_tip_slot, - next_epoch_first_slot, - }, - ); - } else { - // If None is returned, than we are waiting for evaluation - store.dispatch(BlockProducerVrfEvaluatorAction::WaitForNextEvaluation); - } - - store.dispatch(BlockProducerVrfEvaluatorAction::CleanupOldSlots { - best_tip_epoch, - }); - } - } - BlockProducerVrfEvaluatorAction::InitializeEpochEvaluation { .. } => { - store.dispatch(BlockProducerVrfEvaluatorAction::BeginDelegatorTableConstruction); - } - BlockProducerVrfEvaluatorAction::BeginDelegatorTableConstruction => { - let (staking_ledger_hash, producer) = - match store.state().block_producer.vrf_delegator_table_inputs() { - Some((v1, v2)) => (v1.clone(), v2.clone()), - None => return, - }; - if store.dispatch(LedgerReadAction::Init { - request: LedgerReadRequest::DelegatorTable(staking_ledger_hash, producer), - callback: LedgerReadInitCallback::None, - }) { - // TODO(binier): have pending action. - } else { - unreachable!() - } - } - BlockProducerVrfEvaluatorAction::FinalizeDelegatorTableConstruction { .. } => { - let Some(( - current_global_slot, - BlockProducerVrfEvaluatorStatus::EpochDelegatorTableSuccess { - best_tip_epoch, - best_tip_slot, - best_tip_global_slot, - next_epoch_first_slot, - staking_epoch_data, - .. - }, - )) = None.or_else(|| { - let cur_global_slot = store.state().cur_global_slot()?; - let status = &store.state().block_producer.vrf_evaluator()?.status; - - Some((cur_global_slot, status)) - }) - else { - // error here! - return; - }; - - store.dispatch(BlockProducerVrfEvaluatorAction::SelectInitialSlot { - current_global_slot, - best_tip_epoch: *best_tip_epoch, - best_tip_slot: *best_tip_slot, - best_tip_global_slot: *best_tip_global_slot, - next_epoch_first_slot: *next_epoch_first_slot, - staking_epoch_data: staking_epoch_data.clone(), - }); - } - BlockProducerVrfEvaluatorAction::BeginEpochEvaluation { - latest_evaluated_global_slot, - best_tip_epoch: current_epoch_number, - .. - } => { - if store.state().block_producer.vrf_evaluator().is_some() { - store.dispatch(BlockProducerVrfEvaluatorAction::ContinueEpochEvaluation { - latest_evaluated_global_slot, - epoch_number: current_epoch_number, - }); - } - } - BlockProducerVrfEvaluatorAction::ContinueEpochEvaluation { .. } => { - if let Some(vrf_evaluator_state) = store.state().block_producer.vrf_evaluator() { - if let Some(vrf_input) = vrf_evaluator_state.construct_vrf_input() { - store.dispatch(BlockProducerVrfEvaluatorAction::EvaluateSlot { vrf_input }); - } - } - } - BlockProducerVrfEvaluatorAction::FinishEpochEvaluation { .. } => {} - BlockProducerVrfEvaluatorAction::WaitForNextEvaluation { .. } => {} - BlockProducerVrfEvaluatorAction::SelectInitialSlot { - best_tip_epoch: current_epoch_number, - best_tip_global_slot: current_best_tip_global_slot, - best_tip_slot: current_best_tip_slot, - staking_epoch_data, - .. - } => { - if let Some(initial_slot) = store - .state() - .block_producer - .vrf_evaluator() - .and_then(|v| v.initial_slot()) - { - store.dispatch(BlockProducerVrfEvaluatorAction::BeginEpochEvaluation { - best_tip_epoch: current_epoch_number, - best_tip_global_slot: current_best_tip_global_slot, - best_tip_slot: current_best_tip_slot, - staking_epoch_data, - latest_evaluated_global_slot: initial_slot, - }); - } - } - BlockProducerVrfEvaluatorAction::CleanupOldSlots { .. } => {} - BlockProducerVrfEvaluatorAction::InterruptEpochEvaluation { .. } => {} - } - } -} diff --git a/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_reducer.rs b/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_reducer.rs index 37b53fb660..420c2c8925 100644 --- a/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_reducer.rs +++ b/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_reducer.rs @@ -1,18 +1,40 @@ +use vrf::VrfEvaluationOutput; + +use crate::{ + block_producer::to_epoch_and_slot, + block_producer_effectful::vrf_evaluator_effectful::BlockProducerVrfEvaluatorEffectfulAction, + ledger::read::{LedgerReadAction, LedgerReadInitCallback, LedgerReadRequest}, + BlockProducerAction, Substate, +}; + use super::{ BlockProducerVrfEvaluatorAction, BlockProducerVrfEvaluatorActionWithMetaRef, BlockProducerVrfEvaluatorState, BlockProducerVrfEvaluatorStatus, PendingEvaluation, - VrfWonSlotWithHash, + SlotPositionInEpoch, VrfWonSlotWithHash, }; impl BlockProducerVrfEvaluatorState { - pub fn reducer(&mut self, action: BlockProducerVrfEvaluatorActionWithMetaRef<'_>) { + pub fn reducer( + mut state_context: Substate, + action: BlockProducerVrfEvaluatorActionWithMetaRef<'_>, + ) { + let Ok(state) = state_context.get_substate_mut() else { + return; + }; + let (action, meta) = action.split(); match action { BlockProducerVrfEvaluatorAction::EvaluateSlot { vrf_input } => { - self.status = BlockProducerVrfEvaluatorStatus::SlotEvaluationPending { + state.status = BlockProducerVrfEvaluatorStatus::SlotEvaluationPending { time: meta.time(), global_slot: vrf_input.global_slot, }; + + state_context.into_dispatcher().push( + BlockProducerVrfEvaluatorEffectfulAction::EvaluateSlot { + vrf_input: vrf_input.clone(), + }, + ); } BlockProducerVrfEvaluatorAction::ProcessSlotEvaluationSuccess { vrf_output, @@ -20,7 +42,7 @@ impl BlockProducerVrfEvaluatorState { } => { let global_slot_evaluated = match &vrf_output { vrf::VrfEvaluationOutput::SlotWon(won_slot_data) => { - self.won_slots.insert( + state.won_slots.insert( won_slot_data.global_slot, VrfWonSlotWithHash::new( won_slot_data.clone(), @@ -31,36 +53,122 @@ impl BlockProducerVrfEvaluatorState { } vrf::VrfEvaluationOutput::SlotLost(global_slot) => *global_slot, }; - self.set_latest_evaluated_global_slot(&global_slot_evaluated); + state.set_latest_evaluated_global_slot(&global_slot_evaluated); - self.status = BlockProducerVrfEvaluatorStatus::SlotEvaluationReceived { + state.status = BlockProducerVrfEvaluatorStatus::SlotEvaluationReceived { time: meta.time(), global_slot: global_slot_evaluated, + }; + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + + if let Some(vrf_evaluator_state) = state.block_producer.vrf_evaluator() { + if let Some(pending_evaluation) = vrf_evaluator_state.current_evaluation() { + dispatcher.push(BlockProducerVrfEvaluatorAction::CheckEpochBounds { + epoch_number: pending_evaluation.epoch_number, + latest_evaluated_global_slot: vrf_output.global_slot(), + }); + } + } + + if matches!(vrf_output, VrfEvaluationOutput::SlotWon(_)) { + dispatcher.push(BlockProducerAction::WonSlotSearch); } } BlockProducerVrfEvaluatorAction::CheckEpochBounds { epoch_number, latest_evaluated_global_slot, } => { - let epoch_current_bound = Self::evaluate_epoch_bounds(latest_evaluated_global_slot); - self.status = BlockProducerVrfEvaluatorStatus::EpochBoundsCheck { + let latest_evaluated_global_slot = *latest_evaluated_global_slot; + let epoch_number = *epoch_number; + + let epoch_current_bound = + Self::evaluate_epoch_bounds(&latest_evaluated_global_slot); + state.status = BlockProducerVrfEvaluatorStatus::EpochBoundsCheck { time: meta.time(), - epoch_number: *epoch_number, - latest_evaluated_global_slot: *latest_evaluated_global_slot, + epoch_number, + latest_evaluated_global_slot, epoch_current_bound, }; + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + + if let Some(epoch_bound) = state + .block_producer + .vrf_evaluator() + .and_then(|s| s.get_epoch_bound_from_check()) + { + match epoch_bound { + SlotPositionInEpoch::Beginning | SlotPositionInEpoch::Within => { + dispatcher.push( + BlockProducerVrfEvaluatorAction::ContinueEpochEvaluation { + latest_evaluated_global_slot, + epoch_number, + }, + ); + } + SlotPositionInEpoch::End => { + dispatcher.push( + BlockProducerVrfEvaluatorAction::FinishEpochEvaluation { + latest_evaluated_global_slot, + epoch_number, + }, + ); + } + } + } } - BlockProducerVrfEvaluatorAction::InitializeEvaluator { .. } => { - self.status = - BlockProducerVrfEvaluatorStatus::InitialisationPending { time: meta.time() } + BlockProducerVrfEvaluatorAction::InitializeEvaluator { best_tip } => { + state.status = + BlockProducerVrfEvaluatorStatus::InitialisationPending { time: meta.time() }; + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + + // Note: pure function, but needs access to other parts of the state + if state.block_producer.vrf_evaluator().is_some() { + if best_tip.consensus_state().epoch_count.as_u32() == 0 { + dispatcher.push( + BlockProducerVrfEvaluatorAction::FinalizeEvaluatorInitialization { + previous_epoch_and_height: None, + }, + ); + } else { + let k = best_tip.constants().k.as_u32(); + let (epoch, slot) = to_epoch_and_slot( + &best_tip.consensus_state().curr_global_slot_since_hard_fork, + ); + let previous_epoch = epoch.saturating_sub(1); + let last_height = if slot < k { + let found = + state.transition_frontier.best_chain.iter().rev().find(|b| { + b.consensus_state().epoch_count.as_u32() == previous_epoch + }); + + if let Some(block) = found { + block.height() + } else { + Default::default() + } + } else if let Some(root_block) = state.transition_frontier.root() { + root_block.height() + } else { + Default::default() + }; + dispatcher.push( + BlockProducerVrfEvaluatorAction::FinalizeEvaluatorInitialization { + previous_epoch_and_height: Some((previous_epoch, last_height)), + }, + ); + } + } } BlockProducerVrfEvaluatorAction::FinalizeEvaluatorInitialization { previous_epoch_and_height, } => { if let Some((epoch, last_height)) = previous_epoch_and_height { - self.initialize_evaluator(*epoch, *last_height); + state.initialize_evaluator(*epoch, *last_height); } - self.status = + state.status = BlockProducerVrfEvaluatorStatus::InitialisationComplete { time: meta.time() } } BlockProducerVrfEvaluatorAction::CheckEpochEvaluability { @@ -70,24 +178,50 @@ impl BlockProducerVrfEvaluatorState { root_block_epoch, staking_epoch_data, next_epoch_data, - best_tip_slot: _, - best_tip_global_slot: _, - next_epoch_first_slot: _, + best_tip_slot, + best_tip_global_slot, + next_epoch_first_slot, } => { - self.status = BlockProducerVrfEvaluatorStatus::ReadinessCheck { + let best_tip_epoch = *best_tip_epoch; + + state.status = BlockProducerVrfEvaluatorStatus::ReadinessCheck { time: meta.time(), current_epoch: *current_epoch, is_next_epoch_seed_finalized: *is_next_epoch_seed_finalized, - best_tip_epoch: *best_tip_epoch, + best_tip_epoch, root_block_epoch: *root_block_epoch, - is_current_epoch_evaluated: self.is_epoch_evaluated(*best_tip_epoch), - is_next_epoch_evaluated: self.is_epoch_evaluated(best_tip_epoch + 1), - last_evaluated_epoch: self.last_evaluated_epoch(), + is_current_epoch_evaluated: state.is_epoch_evaluated(best_tip_epoch), + is_next_epoch_evaluated: state.is_epoch_evaluated(best_tip_epoch + 1), + last_evaluated_epoch: state.last_evaluated_epoch(), staking_epoch_data: staking_epoch_data.clone(), next_epoch_data: next_epoch_data.clone(), }; - self.set_epoch_context(); + state.set_epoch_context(); + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + let vrf_evaluator_state = state.block_producer.vrf_evaluator_with_config(); + + if let Some((vrf_evaluator_state, config)) = vrf_evaluator_state { + if let Some(epoch_data) = vrf_evaluator_state.epoch_context().get_epoch_data() { + dispatcher.push( + BlockProducerVrfEvaluatorAction::InitializeEpochEvaluation { + staking_epoch_data: epoch_data, + producer: config.pub_key.clone().into(), + best_tip_global_slot: *best_tip_global_slot, + best_tip_epoch, + best_tip_slot: *best_tip_slot, + next_epoch_first_slot: *next_epoch_first_slot, + }, + ); + } else { + // If None is returned, than we are waiting for evaluation + dispatcher.push(BlockProducerVrfEvaluatorAction::WaitForNextEvaluation); + } + + dispatcher + .push(BlockProducerVrfEvaluatorAction::CleanupOldSlots { best_tip_epoch }); + } } BlockProducerVrfEvaluatorAction::InitializeEpochEvaluation { best_tip_epoch, @@ -97,17 +231,21 @@ impl BlockProducerVrfEvaluatorState { staking_epoch_data, producer, } => { - self.status = BlockProducerVrfEvaluatorStatus::ReadyToEvaluate { + state.status = BlockProducerVrfEvaluatorStatus::ReadyToEvaluate { time: meta.time(), best_tip_epoch: *best_tip_epoch, - is_current_epoch_evaluated: self.is_epoch_evaluated(*best_tip_epoch), - is_next_epoch_evaluated: self.is_epoch_evaluated(best_tip_epoch + 1), + is_current_epoch_evaluated: state.is_epoch_evaluated(*best_tip_epoch), + is_next_epoch_evaluated: state.is_epoch_evaluated(best_tip_epoch + 1), best_tip_slot: *best_tip_slot, best_tip_global_slot: *best_tip_global_slot, next_epoch_first_slot: *next_epoch_first_slot, staking_epoch_data: staking_epoch_data.clone(), producer: producer.clone(), - } + }; + + state_context + .into_dispatcher() + .push(BlockProducerVrfEvaluatorAction::BeginDelegatorTableConstruction); } BlockProducerVrfEvaluatorAction::BeginDelegatorTableConstruction => { let BlockProducerVrfEvaluatorStatus::ReadyToEvaluate { @@ -120,11 +258,11 @@ impl BlockProducerVrfEvaluatorState { time: _, is_current_epoch_evaluated: _, is_next_epoch_evaluated: _, - } = &self.status + } = &state.status else { return; }; - self.status = BlockProducerVrfEvaluatorStatus::EpochDelegatorTablePending { + state.status = BlockProducerVrfEvaluatorStatus::EpochDelegatorTablePending { time: meta.time(), best_tip_epoch: *best_tip_epoch, staking_epoch_ledger_hash: staking_epoch_data.ledger.clone(), @@ -133,7 +271,19 @@ impl BlockProducerVrfEvaluatorState { next_epoch_first_slot: *next_epoch_first_slot, staking_epoch_data: staking_epoch_data.clone(), producer: producer.clone(), - } + }; + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + let (staking_ledger_hash, producer) = + match state.block_producer.vrf_delegator_table_inputs() { + Some((v1, v2)) => (v1.clone(), v2.clone()), + None => return, + }; + + dispatcher.push(LedgerReadAction::Init { + request: LedgerReadRequest::DelegatorTable(staking_ledger_hash, producer), + callback: LedgerReadInitCallback::None, + }) } BlockProducerVrfEvaluatorAction::FinalizeDelegatorTableConstruction { delegator_table, @@ -147,7 +297,7 @@ impl BlockProducerVrfEvaluatorState { producer, time: _, staking_epoch_ledger_hash: _, - } = &self.status + } = &state.status else { return; }; @@ -155,7 +305,7 @@ impl BlockProducerVrfEvaluatorState { let mut staking_epoch_data = staking_epoch_data.clone(); staking_epoch_data.delegator_table = delegator_table.clone(); - self.status = BlockProducerVrfEvaluatorStatus::EpochDelegatorTableSuccess { + state.status = BlockProducerVrfEvaluatorStatus::EpochDelegatorTableSuccess { time: meta.time(), best_tip_epoch: *best_tip_epoch, staking_epoch_ledger_hash: staking_epoch_data.ledger.clone(), @@ -164,7 +314,40 @@ impl BlockProducerVrfEvaluatorState { next_epoch_first_slot: *next_epoch_first_slot, staking_epoch_data: staking_epoch_data.clone(), producer: producer.clone(), - } + }; + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + let get_slot_and_status = || { + let cur_global_slot = state.cur_global_slot()?; + let status = &state.block_producer.vrf_evaluator()?.status; + + Some((cur_global_slot, status)) + }; + + let Some(( + current_global_slot, + BlockProducerVrfEvaluatorStatus::EpochDelegatorTableSuccess { + best_tip_epoch, + best_tip_slot, + best_tip_global_slot, + next_epoch_first_slot, + staking_epoch_data, + .. + }, + )) = get_slot_and_status() + else { + // error here! + return; + }; + + dispatcher.push(BlockProducerVrfEvaluatorAction::SelectInitialSlot { + current_global_slot, + best_tip_epoch: *best_tip_epoch, + best_tip_slot: *best_tip_slot, + best_tip_global_slot: *best_tip_global_slot, + next_epoch_first_slot: *next_epoch_first_slot, + staking_epoch_data: staking_epoch_data.clone(), + }); } BlockProducerVrfEvaluatorAction::BeginEpochEvaluation { best_tip_epoch, @@ -173,28 +356,47 @@ impl BlockProducerVrfEvaluatorState { best_tip_slot: _, best_tip_global_slot: _, } => { - self.set_pending_evaluation(PendingEvaluation { - epoch_number: *best_tip_epoch, + let latest_evaluated_global_slot = *latest_evaluated_global_slot; + let epoch_number = *best_tip_epoch; + + state.set_pending_evaluation(PendingEvaluation { + epoch_number, epoch_data: staking_epoch_data.clone(), - latest_evaluated_slot: *latest_evaluated_global_slot, + latest_evaluated_slot: latest_evaluated_global_slot, }); - self.status = BlockProducerVrfEvaluatorStatus::EpochEvaluationPending { + state.status = BlockProducerVrfEvaluatorStatus::EpochEvaluationPending { time: meta.time(), - epoch_number: *best_tip_epoch, + epoch_number, epoch_data: staking_epoch_data.clone(), - latest_evaluated_global_slot: *latest_evaluated_global_slot, + latest_evaluated_global_slot, + }; + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + if state.block_producer.vrf_evaluator().is_some() { + dispatcher.push(BlockProducerVrfEvaluatorAction::ContinueEpochEvaluation { + latest_evaluated_global_slot, + epoch_number, + }); } } BlockProducerVrfEvaluatorAction::ContinueEpochEvaluation { epoch_number, latest_evaluated_global_slot, } => { - if let Some(pending_evaluation) = self.current_evaluation() { - self.status = BlockProducerVrfEvaluatorStatus::EpochEvaluationPending { + if let Some(pending_evaluation) = state.current_evaluation() { + state.status = BlockProducerVrfEvaluatorStatus::EpochEvaluationPending { time: meta.time(), epoch_number: *epoch_number, epoch_data: pending_evaluation.epoch_data, latest_evaluated_global_slot: *latest_evaluated_global_slot, + }; + } + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + if let Some(vrf_evaluator_state) = state.block_producer.vrf_evaluator() { + if let Some(vrf_input) = vrf_evaluator_state.construct_vrf_input() { + dispatcher + .push(BlockProducerVrfEvaluatorAction::EvaluateSlot { vrf_input }); } } } @@ -202,41 +404,56 @@ impl BlockProducerVrfEvaluatorState { epoch_number, latest_evaluated_global_slot: _, } => { - self.unset_pending_evaluation(); - self.status = BlockProducerVrfEvaluatorStatus::EpochEvaluationSuccess { + state.unset_pending_evaluation(); + state.status = BlockProducerVrfEvaluatorStatus::EpochEvaluationSuccess { time: meta.time(), epoch_number: *epoch_number, }; - self.set_last_evaluated_epoch(); + state.set_last_evaluated_epoch(); } BlockProducerVrfEvaluatorAction::WaitForNextEvaluation => { - self.status = + state.status = BlockProducerVrfEvaluatorStatus::WaitingForNextEvaluation { time: meta.time() }; } BlockProducerVrfEvaluatorAction::SelectInitialSlot { best_tip_epoch, current_global_slot, next_epoch_first_slot, - best_tip_slot: _, - best_tip_global_slot: _, - staking_epoch_data: _, + best_tip_slot: current_best_tip_slot, + best_tip_global_slot: current_best_tip_global_slot, + staking_epoch_data, } => { - let (epoch_number, initial_slot) = match self.epoch_context() { + let (epoch_number, initial_slot) = match state.epoch_context() { super::EpochContext::Current(_) => (*best_tip_epoch, *current_global_slot), super::EpochContext::Next(_) => (best_tip_epoch + 1, next_epoch_first_slot - 1), super::EpochContext::Waiting => todo!(), }; - self.status = BlockProducerVrfEvaluatorStatus::InitialSlotSelection { + state.status = BlockProducerVrfEvaluatorStatus::InitialSlotSelection { time: meta.time(), epoch_number, initial_slot, + }; + + let (dispatcher, state) = state_context.into_dispatcher_and_state(); + if let Some(initial_slot) = state + .block_producer + .vrf_evaluator() + .and_then(|v| v.initial_slot()) + { + dispatcher.push(BlockProducerVrfEvaluatorAction::BeginEpochEvaluation { + best_tip_epoch: *best_tip_epoch, + best_tip_global_slot: *current_best_tip_global_slot, + best_tip_slot: *current_best_tip_slot, + staking_epoch_data: staking_epoch_data.clone(), + latest_evaluated_global_slot: initial_slot, + }); } } BlockProducerVrfEvaluatorAction::CleanupOldSlots { best_tip_epoch } => { - self.cleanup_old_won_slots(best_tip_epoch); + state.cleanup_old_won_slots(best_tip_epoch); } BlockProducerVrfEvaluatorAction::InterruptEpochEvaluation { reason } => { - self.status = BlockProducerVrfEvaluatorStatus::EpochEvaluationInterrupted { + state.status = BlockProducerVrfEvaluatorStatus::EpochEvaluationInterrupted { time: meta.time(), reason: reason.clone(), }; diff --git a/node/src/block_producer/vrf_evaluator/mod.rs b/node/src/block_producer/vrf_evaluator/mod.rs index 1c8f624618..0cfe2e0014 100644 --- a/node/src/block_producer/vrf_evaluator/mod.rs +++ b/node/src/block_producer/vrf_evaluator/mod.rs @@ -9,11 +9,6 @@ pub use block_producer_vrf_evaluator_actions::*; mod block_producer_vrf_evaluator_reducer; -mod block_producer_vrf_evaluator_effects; - -mod block_producer_vrf_evaluator_service; -pub use block_producer_vrf_evaluator_service::*; - use std::collections::BTreeMap; use std::sync::Arc; diff --git a/node/src/block_producer_effectful/block_producer_effectful_actions.rs b/node/src/block_producer_effectful/block_producer_effectful_actions.rs new file mode 100644 index 0000000000..a81b4702b9 --- /dev/null +++ b/node/src/block_producer_effectful/block_producer_effectful_actions.rs @@ -0,0 +1,26 @@ +use super::vrf_evaluator_effectful::BlockProducerVrfEvaluatorEffectfulAction; +use crate::block_producer::{BlockProducerWonSlot, BlockProducerWonSlotDiscardReason}; +use openmina_core::ActionEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone, ActionEvent)] +pub enum BlockProducerEffectfulAction { + VrfEvaluator(BlockProducerVrfEvaluatorEffectfulAction), + WonSlot { + won_slot: BlockProducerWonSlot, + }, + WonSlotDiscard { + reason: BlockProducerWonSlotDiscardReason, + }, + StagedLedgerDiffCreateInit, + StagedLedgerDiffCreateSuccess, + BlockUnprovenBuild, + BlockProveInit, + BlockProveSuccess, +} + +impl redux::EnablingCondition for BlockProducerEffectfulAction { + fn is_enabled(&self, _state: &crate::State, _time: redux::Timestamp) -> bool { + true + } +} diff --git a/node/src/block_producer/block_producer_effects.rs b/node/src/block_producer_effectful/block_producer_effectful_effects.rs similarity index 52% rename from node/src/block_producer/block_producer_effects.rs rename to node/src/block_producer_effectful/block_producer_effectful_effects.rs index 66bca390a4..798ddaf009 100644 --- a/node/src/block_producer/block_producer_effects.rs +++ b/node/src/block_producer_effectful/block_producer_effectful_effects.rs @@ -1,122 +1,28 @@ +use crate::{ + block_producer::BlockProducerCurrentState, + ledger::write::{LedgerWriteAction, LedgerWriteRequest}, + BlockProducerAction, Store, +}; use mina_p2p_messages::v2::{ BlockchainSnarkBlockchainStableV2, ConsensusStakeProofStableV2, MinaStateSnarkTransitionValueStableV2, ProverExtendBlockchainInputStableV2, }; -use openmina_core::bug_condition; -use openmina_core::consensus::in_seed_update_range; - -use crate::account::AccountSecretKey; -use crate::ledger::write::{LedgerWriteAction, LedgerWriteRequest}; -use crate::transition_frontier::sync::TransitionFrontierSyncAction; -use crate::{Store, TransactionPoolAction}; +use openmina_node_account::AccountSecretKey; +use redux::ActionWithMeta; -use super::vrf_evaluator::{BlockProducerVrfEvaluatorAction, InterruptReason}; -use super::{ - next_epoch_first_slot, to_epoch_and_slot, BlockProducerAction, BlockProducerActionWithMeta, - BlockProducerCurrentState, -}; +use super::BlockProducerEffectfulAction; pub fn block_producer_effects( store: &mut Store, - action: BlockProducerActionWithMeta, + action: ActionWithMeta, ) { let (action, meta) = action.split(); match action { - BlockProducerAction::VrfEvaluator(a) => { - // TODO: does the order matter? can this clone be avoided? - let has_won_slot = match &a { - BlockProducerVrfEvaluatorAction::ProcessSlotEvaluationSuccess { - vrf_output, - .. - } => { - matches!(vrf_output, vrf::VrfEvaluationOutput::SlotWon(_)) - } - _ => false, - }; + BlockProducerEffectfulAction::VrfEvaluator(a) => { a.effects(&meta, store); - if has_won_slot { - store.dispatch(BlockProducerAction::WonSlotSearch); - } } - BlockProducerAction::BestTipUpdate { best_tip } => { - let global_slot = best_tip - .consensus_state() - .curr_global_slot_since_hard_fork - .clone(); - - let (best_tip_epoch, best_tip_slot) = to_epoch_and_slot(&global_slot); - let root_block_epoch = if let Some(root_block) = - store.state().transition_frontier.root() - { - let root_block_global_slot = root_block.curr_global_slot_since_hard_fork(); - to_epoch_and_slot(root_block_global_slot).0 - } else { - bug_condition!("Expected to find a block at the root of the transition frontier but there was none"); - best_tip_epoch.saturating_sub(1) - }; - let next_epoch_first_slot = next_epoch_first_slot(&global_slot); - let current_epoch = store.state().current_epoch(); - let current_slot = store.state().current_slot(); - - store.dispatch(BlockProducerVrfEvaluatorAction::InitializeEvaluator { - best_tip: best_tip.clone(), - }); - - // None if the evaluator is not evaluating - let currenty_evaluated_epoch = store - .state() - .block_producer - .vrf_evaluator() - .and_then(|vrf_evaluator| vrf_evaluator.currently_evaluated_epoch()); - - if let Some(currently_evaluated_epoch) = currenty_evaluated_epoch { - // if we receive a block with higher epoch than the current one, interrupt the evaluation - if currently_evaluated_epoch < best_tip_epoch { - store.dispatch(BlockProducerVrfEvaluatorAction::InterruptEpochEvaluation { - reason: InterruptReason::BestTipWithHigherEpoch, - }); - } - } - - let is_next_epoch_seed_finalized = if let Some(current_slot) = current_slot { - !in_seed_update_range(current_slot, best_tip.constants()) - } else { - false - }; - - store.dispatch(BlockProducerVrfEvaluatorAction::CheckEpochEvaluability { - current_epoch, - is_next_epoch_seed_finalized, - root_block_epoch, - best_tip_epoch, - best_tip_slot, - best_tip_global_slot: best_tip.global_slot(), - next_epoch_first_slot, - staking_epoch_data: Box::new(best_tip.consensus_state().staking_epoch_data.clone()), - next_epoch_data: Box::new(best_tip.consensus_state().next_epoch_data.clone()), - }); - - if let Some(reason) = store - .state() - .block_producer - .with(None, |bp| bp.current.won_slot_should_discard(&best_tip)) - { - store.dispatch(BlockProducerAction::WonSlotDiscard { reason }); - } else { - store.dispatch(BlockProducerAction::WonSlotSearch); - } - } - BlockProducerAction::WonSlotSearch => { - if let Some(won_slot) = store.state().block_producer.with(None, |bp| { - let best_tip = store.state().transition_frontier.best_tip()?; - let cur_global_slot = store.state().cur_global_slot()?; - bp.vrf_evaluator.next_won_slot(cur_global_slot, best_tip) - }) { - store.dispatch(BlockProducerAction::WonSlot { won_slot }); - } - } - BlockProducerAction::WonSlot { won_slot } => { + BlockProducerEffectfulAction::WonSlot { won_slot } => { if let Some(stats) = store.service.stats() { stats.block_producer().scheduled(meta.time(), &won_slot); } @@ -124,17 +30,7 @@ pub fn block_producer_effects( store.dispatch(BlockProducerAction::WonSlotProduceInit); } } - BlockProducerAction::WonSlotWait => {} - BlockProducerAction::WonSlotProduceInit => { - store.dispatch(BlockProducerAction::WonSlotTransactionsGet); - } - BlockProducerAction::WonSlotTransactionsGet => { - store.dispatch(TransactionPoolAction::CollectTransactionsByFee); - } - BlockProducerAction::WonSlotTransactionsSuccess { .. } => { - store.dispatch(BlockProducerAction::StagedLedgerDiffCreateInit); - } - BlockProducerAction::StagedLedgerDiffCreateInit => { + BlockProducerEffectfulAction::StagedLedgerDiffCreateInit => { if let Some(stats) = store.service.stats() { stats .block_producer() @@ -195,8 +91,7 @@ pub fn block_producer_effects( ), }); } - BlockProducerAction::StagedLedgerDiffCreatePending => {} - BlockProducerAction::StagedLedgerDiffCreateSuccess { .. } => { + BlockProducerEffectfulAction::StagedLedgerDiffCreateSuccess => { if let Some(stats) = store.service.stats() { stats .block_producer() @@ -204,7 +99,7 @@ pub fn block_producer_effects( } store.dispatch(BlockProducerAction::BlockUnprovenBuild); } - BlockProducerAction::BlockUnprovenBuild => { + BlockProducerEffectfulAction::BlockUnprovenBuild => { if let Some(stats) = store.service.stats() { let bp = &store.state.get().block_producer; if let Some((block_hash, block)) = bp.with(None, |bp| match &bp.current { @@ -221,7 +116,7 @@ pub fn block_producer_effects( store.dispatch(BlockProducerAction::BlockProveInit); } - BlockProducerAction::BlockProveInit => { + BlockProducerEffectfulAction::BlockProveInit => { let service = &mut store.service; if let Some(stats) = service.stats() { @@ -293,46 +188,13 @@ pub fn block_producer_effects( service.prove(block_hash, input); store.dispatch(BlockProducerAction::BlockProvePending); } - BlockProducerAction::BlockProvePending => {} - BlockProducerAction::BlockProveSuccess { .. } => { + BlockProducerEffectfulAction::BlockProveSuccess => { if let Some(stats) = store.service.stats() { stats.block_producer().proof_create_end(meta.time()); } store.dispatch(BlockProducerAction::BlockProduced); } - BlockProducerAction::BlockProduced => { - store.dispatch(BlockProducerAction::BlockInject); - } - BlockProducerAction::BlockInject => { - let Some((best_tip, root_block, blocks_inbetween)) = None.or_else(|| { - let (best_tip, chain) = store.state().block_producer.produced_block_with_chain()?; - let mut iter = chain.iter(); - let root_block = iter.next()?.block_with_hash(); - let blocks_inbetween = iter.map(|b| b.hash().clone()).collect(); - Some((best_tip.clone(), root_block.clone(), blocks_inbetween)) - }) else { - return; - }; - - let previous_root_snarked_ledger_hash = store - .state() - .transition_frontier - .root() - .map(|b| b.snarked_ledger_hash().clone()); - - if store.dispatch(TransitionFrontierSyncAction::BestTipUpdate { - previous_root_snarked_ledger_hash, - best_tip: best_tip.clone(), - root_block, - blocks_inbetween, - }) { - store.dispatch(BlockProducerAction::BlockInjected); - } - } - BlockProducerAction::BlockInjected => { - store.dispatch(BlockProducerAction::WonSlotSearch); - } - BlockProducerAction::WonSlotDiscard { reason } => { + BlockProducerEffectfulAction::WonSlotDiscard { reason } => { if let Some(stats) = store.service.stats() { stats.block_producer().discarded(meta.time(), reason); } diff --git a/node/src/block_producer/block_producer_service.rs b/node/src/block_producer_effectful/block_producer_effectful_service.rs similarity index 100% rename from node/src/block_producer/block_producer_service.rs rename to node/src/block_producer_effectful/block_producer_effectful_service.rs diff --git a/node/src/block_producer_effectful/mod.rs b/node/src/block_producer_effectful/mod.rs new file mode 100644 index 0000000000..40ce48fda6 --- /dev/null +++ b/node/src/block_producer_effectful/mod.rs @@ -0,0 +1,10 @@ +pub mod vrf_evaluator_effectful; + +mod block_producer_effectful_actions; +pub use block_producer_effectful_actions::*; + +mod block_producer_effectful_effects; +pub use block_producer_effectful_effects::*; + +mod block_producer_effectful_service; +pub use block_producer_effectful_service::*; diff --git a/node/src/block_producer_effectful/vrf_evaluator_effectful/block_producer_vrf_evaluator_effectful_actions.rs b/node/src/block_producer_effectful/vrf_evaluator_effectful/block_producer_vrf_evaluator_effectful_actions.rs new file mode 100644 index 0000000000..01fcf6f521 --- /dev/null +++ b/node/src/block_producer_effectful/vrf_evaluator_effectful/block_producer_vrf_evaluator_effectful_actions.rs @@ -0,0 +1,20 @@ +use crate::block_producer::vrf_evaluator::VrfEvaluatorInput; +use openmina_core::ActionEvent; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone, ActionEvent)] +pub enum BlockProducerVrfEvaluatorEffectfulAction { + EvaluateSlot { vrf_input: VrfEvaluatorInput }, +} + +impl redux::EnablingCondition for BlockProducerVrfEvaluatorEffectfulAction { + fn is_enabled(&self, _state: &crate::State, _time: redux::Timestamp) -> bool { + true + } +} + +impl From for crate::Action { + fn from(value: BlockProducerVrfEvaluatorEffectfulAction) -> Self { + Self::BlockProducerEffectful(crate::BlockProducerEffectfulAction::VrfEvaluator(value)) + } +} diff --git a/node/src/block_producer_effectful/vrf_evaluator_effectful/block_producer_vrf_evaluator_effectful_effects.rs b/node/src/block_producer_effectful/vrf_evaluator_effectful/block_producer_vrf_evaluator_effectful_effects.rs new file mode 100644 index 0000000000..1a0d6bf41c --- /dev/null +++ b/node/src/block_producer_effectful/vrf_evaluator_effectful/block_producer_vrf_evaluator_effectful_effects.rs @@ -0,0 +1,15 @@ +use crate::Service; +use crate::Store; +use redux::ActionMeta; + +use super::BlockProducerVrfEvaluatorEffectfulAction; + +impl BlockProducerVrfEvaluatorEffectfulAction { + pub fn effects(self, _: &ActionMeta, store: &mut Store) { + match self { + BlockProducerVrfEvaluatorEffectfulAction::EvaluateSlot { vrf_input } => { + store.service.evaluate(vrf_input); + } + } + } +} diff --git a/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_service.rs b/node/src/block_producer_effectful/vrf_evaluator_effectful/block_producer_vrf_evaluator_effectful_service.rs similarity index 65% rename from node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_service.rs rename to node/src/block_producer_effectful/vrf_evaluator_effectful/block_producer_vrf_evaluator_effectful_service.rs index 84b3a0564d..f66525d360 100644 --- a/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_service.rs +++ b/node/src/block_producer_effectful/vrf_evaluator_effectful/block_producer_vrf_evaluator_effectful_service.rs @@ -1,4 +1,4 @@ -use super::VrfEvaluatorInput; +use crate::block_producer::vrf_evaluator::VrfEvaluatorInput; pub trait BlockProducerVrfEvaluatorService: redux::Service { fn evaluate(&mut self, data: VrfEvaluatorInput); diff --git a/node/src/block_producer_effectful/vrf_evaluator_effectful/mod.rs b/node/src/block_producer_effectful/vrf_evaluator_effectful/mod.rs new file mode 100644 index 0000000000..c55fbe7da8 --- /dev/null +++ b/node/src/block_producer_effectful/vrf_evaluator_effectful/mod.rs @@ -0,0 +1,7 @@ +mod block_producer_vrf_evaluator_effectful_actions; +pub use block_producer_vrf_evaluator_effectful_actions::*; + +mod block_producer_vrf_evaluator_effectful_effects; + +mod block_producer_vrf_evaluator_effectful_service; +pub use block_producer_vrf_evaluator_effectful_service::*; diff --git a/node/src/consensus/consensus_reducer.rs b/node/src/consensus/consensus_reducer.rs index 19fd383106..2db1931633 100644 --- a/node/src/consensus/consensus_reducer.rs +++ b/node/src/consensus/consensus_reducer.rs @@ -268,6 +268,7 @@ impl ConsensusState { best_tip, root_block, blocks_inbetween, + on_success: None, }); } ConsensusAction::P2pBestTipUpdate { best_tip } => { diff --git a/node/src/effects.rs b/node/src/effects.rs index b94fca3399..88c051216a 100644 --- a/node/src/effects.rs +++ b/node/src/effects.rs @@ -1,7 +1,8 @@ use openmina_core::log::system_time; use rand::prelude::*; -use crate::block_producer::{block_producer_effects, BlockProducerAction}; +use crate::block_producer::BlockProducerAction; +use crate::block_producer_effectful::block_producer_effects; use crate::event_source::event_source_effects; use crate::external_snark_worker::external_snark_worker_effects; use crate::ledger::ledger_effects; @@ -81,7 +82,8 @@ pub fn effects(store: &mut Store, action: ActionWithMeta) { Action::SnarkPoolEffect(action) => { snark_pool_effects(store, meta.with_action(action)); } - Action::BlockProducer(action) => { + Action::BlockProducer(_) => {} + Action::BlockProducerEffectful(action) => { block_producer_effects(store, meta.with_action(action)); } Action::ExternalSnarkWorker(action) => { diff --git a/node/src/ledger/ledger_service.rs b/node/src/ledger/ledger_service.rs index 301ec0788c..7e9846e4fb 100644 --- a/node/src/ledger/ledger_service.rs +++ b/node/src/ledger/ledger_service.rs @@ -49,7 +49,7 @@ use openmina_core::{block::AppliedBlock, constants::constraint_constants}; use mina_signer::CompressedPubKey; use openmina_core::block::ArcBlockWithHash; -use crate::block_producer::StagedLedgerDiffCreateOutput; +use crate::block_producer_effectful::StagedLedgerDiffCreateOutput; use crate::p2p::channels::rpc::StagedLedgerAuxAndPendingCoinbases; use crate::rpc::{ RpcScanStateSummaryBlockTransaction, RpcScanStateSummaryScanStateJob, diff --git a/node/src/ledger/write/mod.rs b/node/src/ledger/write/mod.rs index 88a8646a65..64f8c3ea81 100644 --- a/node/src/ledger/write/mod.rs +++ b/node/src/ledger/write/mod.rs @@ -16,7 +16,7 @@ use ledger::scan_state::scan_state::AvailableJobMessage; use mina_p2p_messages::v2; use serde::{Deserialize, Serialize}; -use crate::block_producer::StagedLedgerDiffCreateOutput; +use crate::block_producer_effectful::StagedLedgerDiffCreateOutput; use crate::core::block::ArcBlockWithHash; use crate::core::snark::{Snark, SnarkJobId}; use crate::transition_frontier::sync::ledger::staged::StagedLedgerAuxAndPendingCoinbasesValid; diff --git a/node/src/lib.rs b/node/src/lib.rs index 7e18daf5e8..76f23d7dbb 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -30,6 +30,7 @@ pub mod recorder; pub mod stats; pub mod block_producer; +pub mod block_producer_effectful; pub mod consensus; pub mod daemon_json; pub mod event_source; diff --git a/node/src/reducer.rs b/node/src/reducer.rs index 4809b93858..9b5a08befe 100644 --- a/node/src/reducer.rs +++ b/node/src/reducer.rs @@ -2,7 +2,8 @@ use openmina_core::{bug_condition, error, Substate}; use p2p::{P2pAction, P2pEffectfulAction, P2pInitializeAction, P2pState}; use crate::{ - rpc::RpcState, Action, ActionWithMeta, ConsensusAction, EventSourceAction, P2p, State, + rpc::RpcState, state::BlockProducerState, Action, ActionWithMeta, ConsensusAction, + EventSourceAction, P2p, State, }; pub fn reducer( @@ -81,11 +82,10 @@ pub fn reducer( ); } Action::TransactionPoolEffect(_) => {} - Action::BlockProducer(a) => { - state - .block_producer - .reducer(meta.with_action(a), &state.transition_frontier.best_chain); + Action::BlockProducer(action) => { + BlockProducerState::reducer(Substate::new(state, dispatcher), meta.with_action(action)); } + Action::BlockProducerEffectful(_) => {} Action::ExternalSnarkWorker(a) => { state.external_snark_worker.reducer(meta.with_action(a)); } diff --git a/node/src/service.rs b/node/src/service.rs index f29474cf1f..c656a6e579 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -1,5 +1,5 @@ -pub use crate::block_producer::vrf_evaluator::BlockProducerVrfEvaluatorService; -pub use crate::block_producer::BlockProducerService; +pub use crate::block_producer_effectful::vrf_evaluator_effectful::BlockProducerVrfEvaluatorService; +pub use crate::block_producer_effectful::BlockProducerService; pub use crate::event_source::EventSourceService; pub use crate::external_snark_worker::ExternalSnarkWorkerService; pub use crate::ledger::LedgerService; diff --git a/node/src/state.rs b/node/src/state.rs index 76de2ce522..f62f3abdd3 100644 --- a/node/src/state.rs +++ b/node/src/state.rs @@ -24,6 +24,7 @@ use snark::block_verify::SnarkBlockVerifyState; use snark::user_command_verify::SnarkUserCommandVerifyState; use snark::work_verify::SnarkWorkVerifyState; +use crate::block_producer::vrf_evaluator::BlockProducerVrfEvaluatorState; pub use crate::block_producer::BlockProducerState; pub use crate::consensus::ConsensusState; use crate::external_snark_worker::ExternalSnarkWorkers; @@ -128,6 +129,24 @@ impl openmina_core::SubstateAccess for State } } +impl SubstateAccess for State { + fn substate(&self) -> openmina_core::SubstateResult<&BlockProducerVrfEvaluatorState> { + self.block_producer + .as_ref() + .map(|state| &state.vrf_evaluator) + .ok_or_else(|| "Block producer VRF evaluator state unavailable".to_owned()) + } + + fn substate_mut( + &mut self, + ) -> openmina_core::SubstateResult<&mut BlockProducerVrfEvaluatorState> { + self.block_producer + .as_mut() + .map(|state| &mut state.vrf_evaluator) + .ok_or_else(|| "Block producer VRF evaluator state unavailable".to_owned()) + } +} + impl openmina_core::SubstateAccess for State { fn substate(&self) -> openmina_core::SubstateResult<&TransitionFrontierSyncLedgerSnarkedState> { self.transition_frontier diff --git a/node/src/transition_frontier/sync/transition_frontier_sync_actions.rs b/node/src/transition_frontier/sync/transition_frontier_sync_actions.rs index 7ac2b1416c..c2c37d4857 100644 --- a/node/src/transition_frontier/sync/transition_frontier_sync_actions.rs +++ b/node/src/transition_frontier/sync/transition_frontier_sync_actions.rs @@ -2,6 +2,7 @@ use mina_p2p_messages::v2::{LedgerHash, StateHash}; use openmina_core::block::ArcBlockWithHash; use openmina_core::consensus::consensus_take; use openmina_core::ActionEvent; +use redux::Callback; use serde::{Deserialize, Serialize}; use crate::ledger::write::CommitResult; @@ -45,6 +46,7 @@ pub enum TransitionFrontierSyncAction { best_tip: ArcBlockWithHash, root_block: ArcBlockWithHash, blocks_inbetween: Vec, + on_success: Option>, }, /// Staking Ledger sync is pending #[action_event(level = info)] diff --git a/node/src/transition_frontier/sync/transition_frontier_sync_effects.rs b/node/src/transition_frontier/sync/transition_frontier_sync_effects.rs index 496c71c936..e121f1ab44 100644 --- a/node/src/transition_frontier/sync/transition_frontier_sync_effects.rs +++ b/node/src/transition_frontier/sync/transition_frontier_sync_effects.rs @@ -49,6 +49,7 @@ impl TransitionFrontierSyncAction { TransitionFrontierSyncAction::BestTipUpdate { previous_root_snarked_ledger_hash, best_tip, + on_success, .. } => { // TODO(tizoc): this is currently required because how how complicated the BestTipUpdate reducer is, @@ -72,6 +73,9 @@ impl TransitionFrontierSyncAction { store.dispatch(TransitionFrontierSyncAction::BlocksNextApplyInit); // TODO(binier): cleanup ledgers + if let Some(callback) = on_success { + store.dispatch_callback(callback.clone(), ()); + } } // TODO(tizoc): this action is never called with the current implementation, // either remove it or figure out how to recover it as a reaction to diff --git a/node/src/transition_frontier/sync/transition_frontier_sync_reducer.rs b/node/src/transition_frontier/sync/transition_frontier_sync_reducer.rs index e1809a8c19..029a2b7667 100644 --- a/node/src/transition_frontier/sync/transition_frontier_sync_reducer.rs +++ b/node/src/transition_frontier/sync/transition_frontier_sync_reducer.rs @@ -44,6 +44,7 @@ impl TransitionFrontierSyncState { best_tip, root_block, blocks_inbetween, + .. } => match state { Self::StakingLedgerPending(substate) | Self::NextEpochLedgerPending(substate) From 22b7aaac278058d7b6bda72f8b3ad9b8ce272ce1 Mon Sep 17 00:00:00 2001 From: Mimir Date: Thu, 31 Oct 2024 12:39:25 +0100 Subject: [PATCH 002/175] Review fixes --- .../block_producer/block_producer_reducer.rs | 65 ++++++++----------- .../block_producer_vrf_evaluator_reducer.rs | 18 ++--- .../p2p_connection_incoming_reducer.rs | 3 +- .../p2p_connection_outgoing_reducer.rs | 8 ++- 4 files changed, 43 insertions(+), 51 deletions(-) diff --git a/node/src/block_producer/block_producer_reducer.rs b/node/src/block_producer/block_producer_reducer.rs index 2451bf6507..fe178cd5fb 100644 --- a/node/src/block_producer/block_producer_reducer.rs +++ b/node/src/block_producer/block_producer_reducer.rs @@ -43,6 +43,7 @@ impl BlockProducerState { } impl BlockProducerEnabled { + /// Substate is accesses from global state, because applied blocks from transition frontier are required pub fn reducer(mut state_context: Substate, action: BlockProducerActionWithMetaRef<'_>) { let (action, meta) = action.split(); let Ok(global_state) = state_context.get_substate_mut() else { @@ -88,11 +89,10 @@ impl BlockProducerEnabled { won_slot: won_slot.clone(), }; - state_context - .into_dispatcher() - .push(BlockProducerEffectfulAction::WonSlot { - won_slot: won_slot.clone(), - }); + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerEffectfulAction::WonSlot { + won_slot: won_slot.clone(), + }); } BlockProducerAction::WonSlotDiscard { reason } => { if let Some(won_slot) = state.current.won_slot() { @@ -103,9 +103,8 @@ impl BlockProducerEnabled { }; } - state_context - .into_dispatcher() - .push(BlockProducerEffectfulAction::WonSlotDiscard { reason: *reason }); + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerEffectfulAction::WonSlotDiscard { reason: *reason }); } BlockProducerAction::WonSlotWait => { if let Some(won_slot) = state.current.won_slot() { @@ -129,9 +128,8 @@ impl BlockProducerEnabled { chain: chain.clone(), }; - state_context - .into_dispatcher() - .push(TransactionPoolAction::CollectTransactionsByFee); + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(TransactionPoolAction::CollectTransactionsByFee); } BlockProducerAction::WonSlotTransactionsSuccess { transactions_by_fee, @@ -150,9 +148,8 @@ impl BlockProducerEnabled { transactions_by_fee: transactions_by_fee.clone(), }; - state_context - .into_dispatcher() - .push(BlockProducerAction::StagedLedgerDiffCreateInit); + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerAction::StagedLedgerDiffCreateInit); } BlockProducerAction::WonSlotProduceInit => { if let Some(won_slot) = state.current.won_slot() { @@ -173,14 +170,12 @@ impl BlockProducerEnabled { }; } - state_context - .into_dispatcher() - .push(BlockProducerAction::WonSlotTransactionsGet); + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerAction::WonSlotTransactionsGet); } BlockProducerAction::StagedLedgerDiffCreateInit => { - state_context - .into_dispatcher() - .push(BlockProducerEffectfulAction::StagedLedgerDiffCreateInit); + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerEffectfulAction::StagedLedgerDiffCreateInit); } BlockProducerAction::StagedLedgerDiffCreatePending => { let BlockProducerCurrentState::WonSlotTransactionsSuccess { @@ -221,21 +216,18 @@ impl BlockProducerEnabled { stake_proof_sparse_ledger: output.stake_proof_sparse_ledger.clone(), }; - state_context - .into_dispatcher() - .push(BlockProducerEffectfulAction::StagedLedgerDiffCreateSuccess); + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerEffectfulAction::StagedLedgerDiffCreateSuccess); } BlockProducerAction::BlockUnprovenBuild => { state.reduce_block_unproved_build(meta.time()); - state_context - .into_dispatcher() - .push(BlockProducerEffectfulAction::BlockUnprovenBuild); + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerEffectfulAction::BlockUnprovenBuild); } BlockProducerAction::BlockProveInit => { - state_context - .into_dispatcher() - .push(BlockProducerEffectfulAction::BlockProveInit); + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerEffectfulAction::BlockProveInit); } BlockProducerAction::BlockProvePending => { if let BlockProducerCurrentState::BlockUnprovenBuilt { @@ -282,9 +274,8 @@ impl BlockProducerEnabled { }; } - state_context - .into_dispatcher() - .push(BlockProducerEffectfulAction::BlockProveSuccess); + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerEffectfulAction::BlockProveSuccess); } BlockProducerAction::BlockProduced => { if let BlockProducerCurrentState::BlockProveSuccess { @@ -304,9 +295,8 @@ impl BlockProducerEnabled { }; } - state_context - .into_dispatcher() - .push(BlockProducerAction::BlockInject); + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerAction::BlockInject); } BlockProducerAction::BlockInject => { let (dispatcher, state) = state_context.into_dispatcher_and_state(); @@ -355,9 +345,8 @@ impl BlockProducerEnabled { }; } - state_context - .into_dispatcher() - .push(BlockProducerAction::WonSlotSearch); + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerAction::WonSlotSearch); } } } diff --git a/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_reducer.rs b/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_reducer.rs index 420c2c8925..93e789260a 100644 --- a/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_reducer.rs +++ b/node/src/block_producer/vrf_evaluator/block_producer_vrf_evaluator_reducer.rs @@ -1,3 +1,4 @@ +use openmina_core::bug_condition; use vrf::VrfEvaluationOutput; use crate::{ @@ -30,11 +31,10 @@ impl BlockProducerVrfEvaluatorState { global_slot: vrf_input.global_slot, }; - state_context.into_dispatcher().push( - BlockProducerVrfEvaluatorEffectfulAction::EvaluateSlot { - vrf_input: vrf_input.clone(), - }, - ); + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerVrfEvaluatorEffectfulAction::EvaluateSlot { + vrf_input: vrf_input.clone(), + }); } BlockProducerVrfEvaluatorAction::ProcessSlotEvaluationSuccess { vrf_output, @@ -243,9 +243,8 @@ impl BlockProducerVrfEvaluatorState { producer: producer.clone(), }; - state_context - .into_dispatcher() - .push(BlockProducerVrfEvaluatorAction::BeginDelegatorTableConstruction); + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(BlockProducerVrfEvaluatorAction::BeginDelegatorTableConstruction); } BlockProducerVrfEvaluatorAction::BeginDelegatorTableConstruction => { let BlockProducerVrfEvaluatorStatus::ReadyToEvaluate { @@ -299,6 +298,7 @@ impl BlockProducerVrfEvaluatorState { staking_epoch_ledger_hash: _, } = &state.status else { + bug_condition!("Invalid state for `BlockProducerVrfEvaluatorAction::FinalizeDelegatorTableConstruction` expected: `BlockProducerVrfEvaluatorStatus::EpochDelegatorTablePending`, found: {:?}", state.status); return; }; @@ -336,7 +336,7 @@ impl BlockProducerVrfEvaluatorState { }, )) = get_slot_and_status() else { - // error here! + bug_condition!("Invalid state for `BlockProducerVrfEvaluatorAction::FinalizeDelegatorTableConstruction`"); return; }; diff --git a/p2p/src/connection/incoming/p2p_connection_incoming_reducer.rs b/p2p/src/connection/incoming/p2p_connection_incoming_reducer.rs index a47c5c14a6..0b227ee79e 100644 --- a/p2p/src/connection/incoming/p2p_connection_incoming_reducer.rs +++ b/p2p/src/connection/incoming/p2p_connection_incoming_reducer.rs @@ -252,7 +252,8 @@ impl P2pConnectionIncomingState { rpc_id: rpc_id.take(), }; - state_context.into_dispatcher().push(P2pConnectionIncomingEffectfulAction::ConnectionAuthorizationEncryptAndSend { peer_id, other_pub_key, auth }); + let dispatcher = state_context.into_dispatcher(); + dispatcher.push(P2pConnectionIncomingEffectfulAction::ConnectionAuthorizationEncryptAndSend { peer_id, other_pub_key, auth }); } else { bug_condition!( "Invalid state for `P2pConnectionIncomingAction::FinalizePending`: {:?}", diff --git a/p2p/src/connection/outgoing/p2p_connection_outgoing_reducer.rs b/p2p/src/connection/outgoing/p2p_connection_outgoing_reducer.rs index 4994b9bd49..de0acbd268 100644 --- a/p2p/src/connection/outgoing/p2p_connection_outgoing_reducer.rs +++ b/p2p/src/connection/outgoing/p2p_connection_outgoing_reducer.rs @@ -315,8 +315,9 @@ impl P2pConnectionOutgoingState { state ); } - state_context - .into_dispatcher() + + let dispatcher = state_context.into_dispatcher(); + dispatcher .push(P2pConnectionOutgoingEffectfulAction::AnswerSet { peer_id, answer }); Ok(()) } @@ -362,7 +363,8 @@ impl P2pConnectionOutgoingState { } }; - state_context.into_dispatcher().push( + let dispatcher = state_context.into_dispatcher(); + dispatcher.push( P2pConnectionOutgoingEffectfulAction::ConnectionAuthorizationEncryptAndSend { peer_id, other_pub_key, From d2323ab71f64de9f2f4589cdf5428d6a427f8996 Mon Sep 17 00:00:00 2001 From: Zura Benashvili Date: Fri, 1 Nov 2024 12:20:47 +0400 Subject: [PATCH 003/175] fix(redux/time): handle system suspension --- Cargo.lock | 2 +- Cargo.toml | 2 +- node/testing/src/cluster/runner/mod.rs | 2 +- .../scenarios/multi_node/vrf_epoch_bounds_correct_ledgers.rs | 2 +- node/testing/src/scenarios/solo_node/bootstrap.rs | 2 +- p2p/testing/src/service.rs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7350cf9a1f..9bbde61f6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5686,7 +5686,7 @@ dependencies = [ [[package]] name = "redux" version = "0.1.0" -source = "git+https://github.com/openmina/redux-rs.git?rev=bf0726e5#bf0726e596a456f2204c5138528ac7a64695e3b4" +source = "git+https://github.com/openmina/redux-rs.git?rev=741a01d#741a01df74b84715391651438c1b1ff60d117901" dependencies = [ "enum_dispatch", "linkme", diff --git a/Cargo.toml b/Cargo.toml index 763bd8b627..17198539e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ poly-commitment = {git = "https://github.com/openmina/proof-systems", rev = "2fd libp2p = { git = "https://github.com/openmina/rust-libp2p", rev = "5c44c7d9", default-features = false } vrf = { path = "vrf" } openmina-node-account = { path = "node/account" } -redux = { git = "https://github.com/openmina/redux-rs.git", rev = "bf0726e5", features = ["serde"] } +redux = { git = "https://github.com/openmina/redux-rs.git", rev = "741a01d", features = ["serde"] } serde = "1.0.190" serde_json = "1.0.107" serde_with = { version = "3.7.0", features = ["hex"] } diff --git a/node/testing/src/cluster/runner/mod.rs b/node/testing/src/cluster/runner/mod.rs index afc240d32d..163fe717e1 100644 --- a/node/testing/src/cluster/runner/mod.rs +++ b/node/testing/src/cluster/runner/mod.rs @@ -319,7 +319,7 @@ impl<'a> ClusterRunner<'a> { where F: Fn(&State, u32, u32) -> bool, { - let now = tokio::time::Instant::now(); + let now = redux::Instant::now(); let mut last_slot: u32 = 0; let mut produced_blocks: u32 = 0; diff --git a/node/testing/src/scenarios/multi_node/vrf_epoch_bounds_correct_ledgers.rs b/node/testing/src/scenarios/multi_node/vrf_epoch_bounds_correct_ledgers.rs index e37b82be3c..d3d3a10be3 100644 --- a/node/testing/src/scenarios/multi_node/vrf_epoch_bounds_correct_ledgers.rs +++ b/node/testing/src/scenarios/multi_node/vrf_epoch_bounds_correct_ledgers.rs @@ -32,7 +32,7 @@ pub struct MultiNodeVrfEpochBoundsCorrectLedger; impl MultiNodeVrfEpochBoundsCorrectLedger { pub async fn run(self, mut runner: ClusterRunner<'_>) { - let start = tokio::time::Instant::now(); + let start = redux::Instant::now(); let initial_time = runner.get_initial_time().unwrap(); let (initial_node, _) = runner.nodes_iter().last().unwrap(); diff --git a/node/testing/src/scenarios/solo_node/bootstrap.rs b/node/testing/src/scenarios/solo_node/bootstrap.rs index 858decf4b3..5293592b93 100644 --- a/node/testing/src/scenarios/solo_node/bootstrap.rs +++ b/node/testing/src/scenarios/solo_node/bootstrap.rs @@ -1,7 +1,7 @@ use std::time::Duration; use node::transition_frontier::sync::TransitionFrontierSyncState; -use tokio::time::Instant; +use redux::Instant; use crate::{ hosts, diff --git a/p2p/testing/src/service.rs b/p2p/testing/src/service.rs index 58681a347e..1a25065c54 100644 --- a/p2p/testing/src/service.rs +++ b/p2p/testing/src/service.rs @@ -64,7 +64,7 @@ impl ClusterService { impl TimeService for ClusterService { fn monotonic_time(&mut self) -> redux::Instant { - self.time + self.time.into() } } From f63efa5caebabef81a7d714a2f5fdde47d796798 Mon Sep 17 00:00:00 2001 From: directcuteo <37619567+directcuteo@users.noreply.github.com> Date: Fri, 1 Nov 2024 15:46:09 +0200 Subject: [PATCH 004/175] Frontend - Web node loading page v1 (#858) Frontend - Web node loading page v1 (#858) --- frontend/src/app/app.component.html | 4 +- frontend/src/app/app.component.ts | 15 ++- frontend/src/app/app.module.ts | 2 +- frontend/src/app/app.routing.ts | 8 +- .../src/app/core/services/web-node.service.ts | 6 +- .../web-node-demo-dashboard.component.html | 55 +++++++++++ .../web-node-demo-dashboard.component.scss | 60 +++++++++++ .../web-node-demo-dashboard.component.ts | 99 +++++++++++++++++++ .../features/webnode/webnode.component.html | 1 + .../features/webnode/webnode.component.scss | 0 .../app/features/webnode/webnode.component.ts | 18 ++++ .../app/features/webnode/webnode.module.ts | 14 +++ .../app/features/webnode/webnode.routing.ts | 16 +++ .../web-node-landing-page.component.scss | 2 +- frontend/src/app/shared/enums/routes.enum.ts | 1 + frontend/src/environments/environment.ts | 32 +++--- 16 files changed, 310 insertions(+), 23 deletions(-) create mode 100644 frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.html create mode 100644 frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.scss create mode 100644 frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.ts create mode 100644 frontend/src/app/features/webnode/webnode.component.html create mode 100644 frontend/src/app/features/webnode/webnode.component.scss create mode 100644 frontend/src/app/features/webnode/webnode.component.ts create mode 100644 frontend/src/app/features/webnode/webnode.module.ts create mode 100644 frontend/src/app/features/webnode/webnode.routing.ts diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index fdd63676c1..06b79b0c3d 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -1,7 +1,9 @@ @if (showLandingPage$ | async) { -} @else { +} @else if (showLoadingWebNodePage$ | async) { + +} @else if (loaded) { diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index 2d36c7a388..8d44c2f904 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -5,9 +5,10 @@ import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { AppSelectors } from '@app/app.state'; import { AppActions } from '@app/app.actions'; import { filter, map, Observable, Subscription, take, timer } from 'rxjs'; -import { CONFIG, getFirstFeature } from '@shared/constants/config'; +import { CONFIG } from '@shared/constants/config'; import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; import { Router } from '@angular/router'; +import { Routes } from '@shared/enums/routes.enum'; @Component({ selector: 'app-root', @@ -20,8 +21,10 @@ export class AppComponent extends StoreDispatcher implements OnInit { protected readonly menu$: Observable = this.select$(AppSelectors.menu); protected readonly showLandingPage$: Observable = this.select$(getMergedRoute).pipe(filter(Boolean), map((route: MergedRoute) => route.url === '/')); + protected readonly showLoadingWebNodePage$: Observable = this.select$(getMergedRoute).pipe(filter(Boolean), map((route: MergedRoute) => route.url === `/${Routes.LOADING_WEB_NODE}`)); subMenusLength: number = 0; hideToolbar: boolean = CONFIG.hideToolbar; + loaded: boolean; private nodeUpdateSubscription: Subscription | null = null; @@ -42,10 +45,18 @@ export class AppComponent extends StoreDispatcher implements OnInit { take(1), filter((route: MergedRoute) => route.url !== '/'), ); + this.select( + getMergedRoute, + () => { + this.loaded = true; + this.detect(); + }, + filter(Boolean), + ); } goToWebNode(): void { - this.router.navigate([getFirstFeature()]); + this.router.navigate([Routes.LOADING_WEB_NODE]); this.initAppFunctionalities(); } diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index f2dbdf9b12..e63bed102f 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -1,4 +1,4 @@ -import { APP_INITIALIZER, ErrorHandler, Injectable, LOCALE_ID, NgModule, Provider } from '@angular/core'; +import { APP_INITIALIZER, ErrorHandler, Injectable, LOCALE_ID, NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; diff --git a/frontend/src/app/app.routing.ts b/frontend/src/app/app.routing.ts index ccf6903619..0a8cf9194a 100644 --- a/frontend/src/app/app.routing.ts +++ b/frontend/src/app/app.routing.ts @@ -14,6 +14,7 @@ export const SNARKS_TITLE: string = APP_TITLE + ' - Snarks'; export const BLOCK_PRODUCTION_TITLE: string = APP_TITLE + ' - Block Production'; export const MEMPOOL_TITLE: string = APP_TITLE + ' - Mempool'; export const BENCHMARKS_TITLE: string = APP_TITLE + ' - Benchmarks'; +export const WEBNODE_TITLE: string = APP_TITLE + ' - Web Node'; function generateRoutes(): Routes { @@ -64,6 +65,11 @@ function generateRoutes(): Routes { loadChildren: () => import('./features/benchmarks/benchmarks.module').then(m => m.BenchmarksModule), title: BENCHMARKS_TITLE, }, + { + path: 'loading-web-node', + loadChildren: () => import('./features/webnode/webnode.module').then(m => m.WebnodeModule), + title: WEBNODE_TITLE, + }, ]; if (CONFIG.showWebNodeLandingPage) { routes.push({ @@ -76,7 +82,7 @@ function generateRoutes(): Routes { ...routes, { path: '**', - redirectTo: getFirstFeature(), + redirectTo: CONFIG.showWebNodeLandingPage ? '' : getFirstFeature(), pathMatch: 'full', }, ]; diff --git a/frontend/src/app/core/services/web-node.service.ts b/frontend/src/app/core/services/web-node.service.ts index 612b29b6eb..1394721876 100644 --- a/frontend/src/app/core/services/web-node.service.ts +++ b/frontend/src/app/core/services/web-node.service.ts @@ -16,6 +16,8 @@ export class WebNodeService { private webNodeStartTime: number; private sentryEvents: any = {}; + readonly webnodeProgress$: BehaviorSubject = new BehaviorSubject(''); + constructor(private http: HttpClient) { const basex = base('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'); any(window)['bs58btc'] = { @@ -45,6 +47,7 @@ export class WebNodeService { switchMap((wasm: any) => from(wasm.default('assets/webnode/pkg/openmina_node_web_bg.wasm')).pipe(map(() => wasm))), switchMap((wasm) => { sendSentryEvent('WebNode Wasm loaded. Starting WebNode'); + this.webnodeProgress$.next('Loaded'); return from(wasm.run(this.webNodeKeyPair.privateKey)); }), tap((webnode: any) => { @@ -52,6 +55,7 @@ export class WebNodeService { this.webNodeStartTime = Date.now(); (window as any)['webnode'] = webnode; this.webnode$.next(webnode); + this.webnodeProgress$.next('Started'); }), catchError((error) => { sendSentryEvent('WebNode failed to start'); @@ -83,7 +87,6 @@ export class WebNodeService { switchMap(handle => from(any(handle).state().peers())), tap((peers) => { if (!this.sentryEvents.sentNoPeersEvent && Date.now() - this.webNodeStartTime >= 5000 && peers.length === 0) { - console.log('WebNode has no peers after 5 seconds from startup.'); sendSentryEvent('WebNode has no peers after 5 seconds from startup.'); this.sentryEvents.sentNoPeersEvent = true; } @@ -96,6 +99,7 @@ export class WebNodeService { const seconds = (Date.now() - this.webNodeStartTime) / 1000; sendSentryEvent(`WebNode connected to its first peer after ${seconds}s`); this.sentryEvents.firstPeerConnected = true; + this.webnodeProgress$.next('Connected'); } }), ); diff --git a/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.html b/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.html new file mode 100644 index 0000000000..ae60393a22 --- /dev/null +++ b/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.html @@ -0,0 +1,55 @@ +
+
+
+ With the Web Node, you can produce blocks directly through your browser +
+ +
+ @if (!loading[loading.length - 1].loaded) { + Setting up your in-browser Web Node... + } @else { + Web Node is ready + } +
+
+ +
+ @for (item of loading; track $index) { +
+
{{ item.name }}
+
+ + {{ item.loaded ? 'task_alt' : 'more_horiz' }} + +
+
+ } +
+ +
+ @if (ready) { + + } +
+ + @if (!loading[loading.length - 1].loaded && errors.length) { +
+
+
It appears there are some errors.
+
+
+ @for (error of errors; track $index) { +
{{ error }}
+ } +
+
+ } +
diff --git a/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.scss b/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.scss new file mode 100644 index 0000000000..e403a607d1 --- /dev/null +++ b/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.scss @@ -0,0 +1,60 @@ +@import 'openmina'; + +$green: #59bfb5; +$light-peach: #0d0d0d; +$peach: #acdea0; +$sand: #5bb3fb; +$white: #000000; + +.font-16 { + font-size: 16px; +} + +.data-wrapper { + max-width: 568px; + + .header { + margin-bottom: 56px; + + .loading-webnode { + font-size: 24px; + line-height: 32px; + } + } + + .mina-icon.aware-primary { + animation: fadeIn 1500ms ease-in-out infinite; + } +} + +.launch { + height: 56px !important; + font-size: 20px; + width: 341px; + background: linear-gradient(to right, $green 0%, $sand 50%, $peach 100%); + background-size: 500%; + margin-top: 64px; + transition: background-position 1s ease; + background-position: 0 50%; + left: 50%; + transform: translateX(-50%); + + &:hover { + background-position: 100% 50%; + } +} + +@keyframes fadeIn { + 10% { + opacity: 1; + } + 38% { + opacity: 0.1; + } + 42% { + opacity: 0.1; + } + 70% { + opacity: 1; + } +} diff --git a/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.ts b/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.ts new file mode 100644 index 0000000000..c181de2b15 --- /dev/null +++ b/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.ts @@ -0,0 +1,99 @@ +import { ChangeDetectionStrategy, Component, NgZone, OnInit } from '@angular/core'; +import { untilDestroyed } from '@ngneat/until-destroy'; +import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; +import { WebNodeService } from '@core/services/web-node.service'; +import { GlobalErrorHandlerService } from '@openmina/shared'; +import { NgClass, NgForOf, NgIf } from '@angular/common'; +import { Router } from '@angular/router'; +import { getFirstFeature } from '@shared/constants/config'; +import { trigger, state, style, transition, animate } from '@angular/animations'; +import { switchMap, timer } from 'rxjs'; + +export interface WebNodeDemoLoadingStep { + name: string; + loaded: boolean; + attempt?: number; + step: number; +} + + +@Component({ + selector: 'mina-web-node-demo-dashboard', + templateUrl: './web-node-demo-dashboard.component.html', + styleUrls: ['./web-node-demo-dashboard.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + host: { class: 'flex-column h-100 w-100 align-center' }, + standalone: true, + imports: [ + NgClass, + NgIf, + NgForOf, + ], + animations: [ + trigger('fadeIn', [ + state('void', style({ opacity: 0 })), + state('*', style({ opacity: 1 })), + transition('void => *', [ + animate('.6s ease-in'), + ]), + ]), + ], +}) +export class WebNodeDemoDashboardComponent extends StoreDispatcher implements OnInit { + + readonly loading: WebNodeDemoLoadingStep[] = [ + { name: 'Setting up browser for Web Node', loaded: false, step: 1 }, + { name: 'Getting ready to produce blocks', loaded: false, step: 2 }, + { name: 'Connecting directly to Mina network', loaded: false, step: 3 }, + ]; + ready: boolean = false; + errors: string[] = []; + + constructor(private errorHandler: GlobalErrorHandlerService, + private webNodeService: WebNodeService, + private zone: NgZone, + private router: Router) { super(); } + + ngOnInit(): void { + this.listenToErrorIssuing(); + this.checkWebNodeProgress(); + this.fetchPeersInformation(); + } + + private checkWebNodeProgress(): void { + this.webNodeService.webnodeProgress$.pipe(untilDestroyed(this)).subscribe((state: string) => { + if (state === 'Loaded') { + this.loading[0].loaded = true; + } else if (state === 'Started') { + this.loading[0].loaded = true; + this.loading[1].loaded = true; + } else if (state === 'Connected') { + this.loading.forEach((step: WebNodeDemoLoadingStep) => step.loaded = true); + } + this.ready = this.loading.every((step: WebNodeDemoLoadingStep) => step.loaded); + this.detect(); + }); + } + + private fetchPeersInformation(): void { + timer(0, 1000).pipe( + switchMap(() => this.webNodeService.peers$), + untilDestroyed(this), + ).subscribe(); + } + + private listenToErrorIssuing(): void { + this.errorHandler.errors$ + .pipe(untilDestroyed(this)) + .subscribe((error: string) => { + console.log(error); + }); + } + + goToDashboard(): void { + if (!this.ready) { + return; + } + this.router.navigate([getFirstFeature()]); + } +} diff --git a/frontend/src/app/features/webnode/webnode.component.html b/frontend/src/app/features/webnode/webnode.component.html new file mode 100644 index 0000000000..98ac206ace --- /dev/null +++ b/frontend/src/app/features/webnode/webnode.component.html @@ -0,0 +1 @@ + diff --git a/frontend/src/app/features/webnode/webnode.component.scss b/frontend/src/app/features/webnode/webnode.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/app/features/webnode/webnode.component.ts b/frontend/src/app/features/webnode/webnode.component.ts new file mode 100644 index 0000000000..676e16709d --- /dev/null +++ b/frontend/src/app/features/webnode/webnode.component.ts @@ -0,0 +1,18 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { + WebNodeDemoDashboardComponent, +} from '@app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component'; + +@Component({ + selector: 'mina-webnode', + standalone: true, + imports: [ + WebNodeDemoDashboardComponent, + ], + templateUrl: './webnode.component.html', + styleUrl: './webnode.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class WebnodeComponent { + +} diff --git a/frontend/src/app/features/webnode/webnode.module.ts b/frontend/src/app/features/webnode/webnode.module.ts new file mode 100644 index 0000000000..b9f10c30ef --- /dev/null +++ b/frontend/src/app/features/webnode/webnode.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { WebnodeRouting } from './webnode.routing'; + + +@NgModule({ + declarations: [], + imports: [ + CommonModule, + WebnodeRouting, + ], +}) +export class WebnodeModule {} diff --git a/frontend/src/app/features/webnode/webnode.routing.ts b/frontend/src/app/features/webnode/webnode.routing.ts new file mode 100644 index 0000000000..fc0e9b4e9d --- /dev/null +++ b/frontend/src/app/features/webnode/webnode.routing.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { WebnodeComponent } from '@app/features/webnode/webnode.component'; + +const routes: Routes = [ + { + path: '', + component: WebnodeComponent, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class WebnodeRouting {} diff --git a/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.scss b/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.scss index 47a212ed2b..1862257a3c 100644 --- a/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.scss +++ b/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.scss @@ -74,7 +74,7 @@ $white: #000000; background: linear-gradient(to right, $green 0%, $sand 50%, $peach 100%); background-size: 500%; margin-bottom: 96px; - transition: background-position 2s ease; + transition: background-position 1s ease; background-position: 0 50%; &:hover { diff --git a/frontend/src/app/shared/enums/routes.enum.ts b/frontend/src/app/shared/enums/routes.enum.ts index f4a62ababd..cefbede581 100644 --- a/frontend/src/app/shared/enums/routes.enum.ts +++ b/frontend/src/app/shared/enums/routes.enum.ts @@ -37,4 +37,5 @@ export enum Routes { BLOCK_PRODUCTION = 'block-production', WON_SLOTS = 'won-slots', MEMPOOL = 'mempool', + LOADING_WEB_NODE = 'loading-web-node', } diff --git a/frontend/src/environments/environment.ts b/frontend/src/environments/environment.ts index 92de0479fe..c7e94f9576 100644 --- a/frontend/src/environments/environment.ts +++ b/frontend/src/environments/environment.ts @@ -39,22 +39,22 @@ export const environment: Readonly = { // name: 'Producer-2', // url: 'https://staging-devnet-openmina-bp-2-dashboard.minaprotocol.network', // }, - { - name: 'staging-devnet-bp-0', - url: 'https://staging-devnet-openmina-bp-0.minaprotocol.network', - }, - { - name: 'staging-devnet-bp-1', - url: 'https://staging-devnet-openmina-bp-1.minaprotocol.network', - }, - { - name: 'staging-devnet-bp-2', - url: 'https://staging-devnet-openmina-bp-2.minaprotocol.network', - }, - { - name: 'staging-devnet-bp-3', - url: 'https://staging-devnet-openmina-bp-3.minaprotocol.network', - }, + // { + // name: 'staging-devnet-bp-0', + // url: 'https://staging-devnet-openmina-bp-0.minaprotocol.network', + // }, + // { + // name: 'staging-devnet-bp-1', + // url: 'https://staging-devnet-openmina-bp-1.minaprotocol.network', + // }, + // { + // name: 'staging-devnet-bp-2', + // url: 'https://staging-devnet-openmina-bp-2.minaprotocol.network', + // }, + // { + // name: 'staging-devnet-bp-3', + // url: 'https://staging-devnet-openmina-bp-3.minaprotocol.network', + // }, { name: 'Web Node 1', isWebNode: true, From 0d34847f07b8be6edf97a2bb9332fb2f38c6ef9a Mon Sep 17 00:00:00 2001 From: Sebastien Chapuis Date: Sat, 2 Nov 2024 10:33:33 +0100 Subject: [PATCH 005/175] Fix `grace_period_end` value when building inputs for block proof --- .../block_producer/block_producer_reducer.rs | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/node/src/block_producer/block_producer_reducer.rs b/node/src/block_producer/block_producer_reducer.rs index fe178cd5fb..9ca3c2e147 100644 --- a/node/src/block_producer/block_producer_reducer.rs +++ b/node/src/block_producer/block_producer_reducer.rs @@ -12,12 +12,13 @@ use mina_p2p_messages::{ StagedLedgerDiffBodyStableV1, StateBodyHash, StateHash, UnsignedExtendedUInt32StableV1, }, }; -use openmina_core::{block::ArcBlockWithHash, constants::constraint_constants}; +use openmina_core::{ + block::ArcBlockWithHash, consensus::ConsensusConstants, constants::constraint_constants, +}; use openmina_core::{ bug_condition, consensus::{ - global_sub_window, grace_period_end, in_same_checkpoint_window, in_seed_update_range, - relative_sub_window, + global_sub_window, in_same_checkpoint_window, in_seed_update_range, relative_sub_window, }, }; use redux::{callback, Dispatcher, Timestamp}; @@ -49,6 +50,7 @@ impl BlockProducerEnabled { let Ok(global_state) = state_context.get_substate_mut() else { return; }; + let consensus_constants = &global_state.config.consensus_constants; let best_chain = &global_state.transition_frontier.best_chain; let Some(state) = global_state.block_producer.as_mut() else { @@ -220,7 +222,7 @@ impl BlockProducerEnabled { dispatcher.push(BlockProducerEffectfulAction::StagedLedgerDiffCreateSuccess); } BlockProducerAction::BlockUnprovenBuild => { - state.reduce_block_unproved_build(meta.time()); + state.reduce_block_unproved_build(consensus_constants, meta.time()); let dispatcher = state_context.into_dispatcher(); dispatcher.push(BlockProducerEffectfulAction::BlockUnprovenBuild); @@ -351,7 +353,11 @@ impl BlockProducerEnabled { } } - fn reduce_block_unproved_build(&mut self, time: Timestamp) { + fn reduce_block_unproved_build( + &mut self, + consensus_constants: &ConsensusConstants, + time: Timestamp, + ) { let BlockProducerCurrentState::StagedLedgerDiffCreateSuccess { won_slot, chain, @@ -493,7 +499,7 @@ impl BlockProducerEnabled { } else { gt_pred_sub_window || lt_next_sub_window }; - if is_same_global_sub_window || are_windows_overlapping && !within_range { + if is_same_global_sub_window || (are_windows_overlapping && !within_range) { density } else { 0 @@ -501,7 +507,7 @@ impl BlockProducerEnabled { }) .collect::>(); - let grace_period_end = grace_period_end(pred_block.constants()); + let grace_period_end = consensus_constants.grace_period_end; let min_window_density = if is_same_global_sub_window || curr_global_slot_since_hard_fork.slot_number.as_u32() < grace_period_end { From cb199730f8deb21fa10da4b5536602b510e9b1f4 Mon Sep 17 00:00:00 2001 From: Serhii Pimenov Date: Sat, 2 Nov 2024 15:04:59 +0200 Subject: [PATCH 006/175] fix(consensus): Subtract with underflow in `relative_min_window_density()` (#860) * fix subtract with overflow in relative_min_window_density() consensus.rs Panic with message: `Subtract with overflow` at core/src/consensus.rs:78:27 When debug vars: max_slot: 98879 global_slot + 1: 98880 * fix lint issue format code * use saturating_sub isntead of wrapping_sub * fix subtract with overflow in relative_min_window_density() consensus.rs --- .idea/vcs.xml | 1 - core/src/consensus.rs | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.idea/vcs.xml b/.idea/vcs.xml index c4f6642370..35eb1ddfbb 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,6 +2,5 @@ - \ No newline at end of file diff --git a/core/src/consensus.rs b/core/src/consensus.rs index 5429dbf07d..7b63303c03 100644 --- a/core/src/consensus.rs +++ b/core/src/consensus.rs @@ -75,7 +75,9 @@ pub fn relative_min_window_density(b1: &MinaConsensusState, b2: &MinaConsensusSt let projected_window = { // Compute shift count - let shift_count = (max_slot - global_slot(b1) - 1).clamp(0, SUB_WINDOWS_PER_WINDOW); + let shift_count = max_slot + .saturating_sub(global_slot(b1) + 1) + .min(SUB_WINDOWS_PER_WINDOW); // Initialize projected window let mut projected_window = b1 From 07cbd2cbb46aaf03ca242e570d3d6aaf0ab7f759 Mon Sep 17 00:00:00 2001 From: Bruno Deferrari Date: Fri, 1 Nov 2024 17:01:13 -0300 Subject: [PATCH 007/175] tweak: Use `v2` prefix when referencing wire types --- .../block_producer/block_producer_reducer.rs | 77 ++++++++----------- 1 file changed, 34 insertions(+), 43 deletions(-) diff --git a/node/src/block_producer/block_producer_reducer.rs b/node/src/block_producer/block_producer_reducer.rs index 9ca3c2e147..5361f92abc 100644 --- a/node/src/block_producer/block_producer_reducer.rs +++ b/node/src/block_producer/block_producer_reducer.rs @@ -1,17 +1,5 @@ use ledger::scan_state::currency::{Amount, Signed}; -use mina_p2p_messages::{ - list::List, - v2::{ - ConsensusProofOfStakeDataConsensusStateValueStableV2, - ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1, - ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueStableV1, - ConsensusVrfOutputTruncatedStableV1, LedgerProofProdStableV2, - MinaBaseEpochLedgerValueStableV1, MinaStateBlockchainStateValueStableV2, - MinaStateBlockchainStateValueStableV2LedgerProofStatement, - MinaStateProtocolStateBodyValueStableV2, MinaStateProtocolStateValueStableV2, - StagedLedgerDiffBodyStableV1, StateBodyHash, StateHash, UnsignedExtendedUInt32StableV1, - }, -}; +use mina_p2p_messages::{list::List, v2}; use openmina_core::{ block::ArcBlockWithHash, consensus::ConsensusConstants, constants::constraint_constants, }; @@ -395,7 +383,7 @@ impl BlockProducerEnabled { in_same_checkpoint_window(&pred_global_slot, &curr_global_slot_since_hard_fork); let block_stake_winner = won_slot.delegator.0.clone(); - let vrf_truncated_output: ConsensusVrfOutputTruncatedStableV1 = + let vrf_truncated_output: v2::ConsensusVrfOutputTruncatedStableV1 = (*won_slot.vrf_output).clone().into(); let vrf_hash = won_slot.vrf_output.hash(); let block_creator = self.config.pub_key.clone(); @@ -424,7 +412,7 @@ impl BlockProducerEnabled { let next_staking_ledger = if pred_block.snarked_ledger_hash() == genesis_ledger_hash { pred_consensus_state.next_epoch_data.ledger.clone() } else { - MinaBaseEpochLedgerValueStableV1 { + v2::MinaBaseEpochLedgerValueStableV1 { hash: pred_block.snarked_ledger_hash().clone(), total_currency: (&total_currency).into(), } @@ -432,23 +420,25 @@ impl BlockProducerEnabled { let (staking_data, next_data, epoch_count) = if next_epoch > pred_epoch { let staking_data = next_to_staking_epoch_data(&pred_consensus_state.next_epoch_data); - let next_data = ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1 { - seed: pred_consensus_state.next_epoch_data.seed.clone(), - ledger: next_staking_ledger, - start_checkpoint: pred_block.hash().clone(), - // comment from Mina repo: (* TODO: We need to make sure issue #2328 is properly addressed. *) - lock_checkpoint: StateHash::zero(), - epoch_length: UnsignedExtendedUInt32StableV1(1.into()), - }; - let epoch_count = UnsignedExtendedUInt32StableV1( + let next_data = + v2::ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1 { + seed: pred_consensus_state.next_epoch_data.seed.clone(), + ledger: next_staking_ledger, + start_checkpoint: pred_block.hash().clone(), + // comment from Mina repo: (* TODO: We need to make sure issue #2328 is properly addressed. *) + lock_checkpoint: v2::StateHash::zero(), + epoch_length: v2::UnsignedExtendedUInt32StableV1(1.into()), + }; + let epoch_count = v2::UnsignedExtendedUInt32StableV1( (pred_consensus_state.epoch_count.as_u32() + 1).into(), ); (staking_data, next_data, epoch_count) } else { assert_eq!(pred_epoch, next_epoch); let mut next_data = pred_consensus_state.next_epoch_data.clone(); - next_data.epoch_length = - UnsignedExtendedUInt32StableV1((next_data.epoch_length.as_u32() + 1).into()); + next_data.epoch_length = v2::UnsignedExtendedUInt32StableV1( + (next_data.epoch_length.as_u32() + 1).into(), + ); ( pred_consensus_state.staking_epoch_data.clone(), next_data, @@ -457,7 +447,7 @@ impl BlockProducerEnabled { }; let next_data = if in_seed_update_range(next_slot, pred_block.constants()) { - ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1 { + v2::ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1 { seed: calc_epoch_seed(&next_data.seed, vrf_hash), lock_checkpoint: pred_block.hash().clone(), ..next_data @@ -471,6 +461,7 @@ impl BlockProducerEnabled { let (min_window_density, sub_window_densities) = { // TODO(binier): when should this be false? + // https://github.com/MinaProtocol/mina/blob/4aac38814556b9641ffbdfaef19b38ab7980011b/src/lib/consensus/proof_of_stake.ml#L2864 let incr_window = true; let pred_sub_window_densities = &pred_consensus_state.sub_window_densities; @@ -518,7 +509,7 @@ impl BlockProducerEnabled { .min_window_density .as_u32() .min(cur_density); - UnsignedExtendedUInt32StableV1(min_density.into()) + v2::UnsignedExtendedUInt32StableV1(min_density.into()) }; let next_sub_window_densities = current_sub_window_densities @@ -542,14 +533,14 @@ impl BlockProducerEnabled { density } }) - .map(|v| UnsignedExtendedUInt32StableV1(v.into())) + .map(|v| v2::UnsignedExtendedUInt32StableV1(v.into())) .collect(); (min_window_density, next_sub_window_densities) }; - let consensus_state = ConsensusProofOfStakeDataConsensusStateValueStableV2 { - blockchain_length: UnsignedExtendedUInt32StableV1((pred_block.height() + 1).into()), + let consensus_state = v2::ConsensusProofOfStakeDataConsensusStateValueStableV2 { + blockchain_length: v2::UnsignedExtendedUInt32StableV1((pred_block.height() + 1).into()), epoch_count, min_window_density, sub_window_densities, @@ -567,9 +558,9 @@ impl BlockProducerEnabled { supercharge_coinbase: constraint_constants().supercharged_coinbase_factor != 0, }; - let protocol_state = MinaStateProtocolStateValueStableV2 { + let protocol_state = v2::MinaStateProtocolStateValueStableV2 { previous_state_hash: pred_block.hash().clone(), - body: MinaStateProtocolStateBodyValueStableV2 { + body: v2::MinaStateProtocolStateBodyValueStableV2 { genesis_state_hash: if pred_block.is_genesis() { pred_block.hash().clone() } else { @@ -581,7 +572,7 @@ impl BlockProducerEnabled { .clone() }, constants: pred_block.header().protocol_state.body.constants.clone(), - blockchain_state: MinaStateBlockchainStateValueStableV2 { + blockchain_state: v2::MinaStateBlockchainStateValueStableV2 { staged_ledger_hash: staged_ledger_hash.clone(), genesis_ledger_hash: genesis_ledger_hash.clone(), ledger_proof_statement, @@ -602,7 +593,7 @@ impl BlockProducerEnabled { let first_hash = first_block.hash().clone(); let body_hashes = iter .filter_map(|b| b.header().protocol_state.body.try_hash().ok()) // TODO: Handle error ? - .map(StateBodyHash::from) + .map(v2::StateBodyHash::from) .collect(); (first_hash, body_hashes) } else { @@ -618,7 +609,7 @@ impl BlockProducerEnabled { delta_block_chain_proof, current_protocol_version: pred_block.header().current_protocol_version.clone(), proposed_protocol_version_opt, - body: StagedLedgerDiffBodyStableV1 { + body: v2::StagedLedgerDiffBodyStableV1 { staged_ledger_diff: diff.clone(), }, }; @@ -711,9 +702,9 @@ impl BlockProducerEnabled { } fn next_to_staking_epoch_data( - data: &ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1, -) -> ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueStableV1 { - ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueStableV1 { + data: &v2::ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1, +) -> v2::ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueStableV1 { + v2::ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueStableV1 { seed: data.seed.clone(), ledger: data.ledger.clone(), start_checkpoint: data.start_checkpoint.clone(), @@ -723,12 +714,12 @@ fn next_to_staking_epoch_data( } fn ledger_proof_statement_from_emitted_proof( - emitted_ledger_proof: Option<&LedgerProofProdStableV2>, - pred_proof_statement: &MinaStateBlockchainStateValueStableV2LedgerProofStatement, -) -> MinaStateBlockchainStateValueStableV2LedgerProofStatement { + emitted_ledger_proof: Option<&v2::LedgerProofProdStableV2>, + pred_proof_statement: &v2::MinaStateBlockchainStateValueStableV2LedgerProofStatement, +) -> v2::MinaStateBlockchainStateValueStableV2LedgerProofStatement { match emitted_ledger_proof.map(|proof| &proof.statement) { None => pred_proof_statement.clone(), - Some(stmt) => MinaStateBlockchainStateValueStableV2LedgerProofStatement { + Some(stmt) => v2::MinaStateBlockchainStateValueStableV2LedgerProofStatement { source: stmt.source.clone(), target: stmt.target.clone(), connecting_ledger_left: stmt.connecting_ledger_left.clone(), From 3a792cba34faab178efea7096e099b3924e30d8c Mon Sep 17 00:00:00 2001 From: Bruno Deferrari Date: Fri, 1 Nov 2024 17:02:57 -0300 Subject: [PATCH 008/175] fix(block-producer): Fix computation of `supercharge_coinbase` --- ledger/src/staged_ledger/staged_ledger.rs | 2 +- .../block_producer/block_producer_reducer.rs | 26 +++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/ledger/src/staged_ledger/staged_ledger.rs b/ledger/src/staged_ledger/staged_ledger.rs index 06af06cdff..a2527c0b4b 100644 --- a/ledger/src/staged_ledger/staged_ledger.rs +++ b/ledger/src/staged_ledger/staged_ledger.rs @@ -1692,7 +1692,7 @@ impl StagedLedger { } /// https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/staged_ledger/staged_ledger.ml#L1781 - fn can_apply_supercharged_coinbase_exn( + pub fn can_apply_supercharged_coinbase_exn( winner: CompressedPubKey, epoch_ledger: &SparseLedger, global_slot: Slot, diff --git a/node/src/block_producer/block_producer_reducer.rs b/node/src/block_producer/block_producer_reducer.rs index 5361f92abc..87ec1a0fa2 100644 --- a/node/src/block_producer/block_producer_reducer.rs +++ b/node/src/block_producer/block_producer_reducer.rs @@ -539,6 +539,11 @@ impl BlockProducerEnabled { (min_window_density, next_sub_window_densities) }; + let supercharge_coinbase = can_apply_supercharged_coinbase( + &block_stake_winner, + &stake_proof_sparse_ledger, + &global_slot_since_genesis, + ); let consensus_state = v2::ConsensusProofOfStakeDataConsensusStateValueStableV2 { blockchain_length: v2::UnsignedExtendedUInt32StableV1((pred_block.height() + 1).into()), epoch_count, @@ -554,8 +559,7 @@ impl BlockProducerEnabled { block_stake_winner, block_creator, coinbase_receiver, - // TODO(binier): Staged_ledger.can_apply_supercharged_coinbase_exn - supercharge_coinbase: constraint_constants().supercharged_coinbase_factor != 0, + supercharge_coinbase, }; let protocol_state = v2::MinaStateProtocolStateValueStableV2 { @@ -701,6 +705,24 @@ impl BlockProducerEnabled { } } +fn can_apply_supercharged_coinbase( + block_stake_winner: &v2::NonZeroCurvePoint, + stake_proof_sparse_ledger: &v2::MinaBaseSparseLedgerBaseStableV2, + global_slot_since_genesis: &v2::MinaNumbersGlobalSlotSinceGenesisMStableV1, +) -> bool { + use ledger::staged_ledger::staged_ledger::StagedLedger; + + let winner = (block_stake_winner) + .try_into() + .expect("Public key being used cannot be invalid here"); + let epoch_ledger = (stake_proof_sparse_ledger) + .try_into() + .expect("Sparse ledger being used cannot be invalid here"); + let global_slot = (global_slot_since_genesis).into(); + + StagedLedger::can_apply_supercharged_coinbase_exn(winner, &epoch_ledger, global_slot) +} + fn next_to_staking_epoch_data( data: &v2::ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1, ) -> v2::ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueStableV1 { From 5c542656f52f0c8c3babfc2021731bb8ee55fca3 Mon Sep 17 00:00:00 2001 From: Zura Benashvili Date: Sat, 2 Nov 2024 17:30:08 +0400 Subject: [PATCH 009/175] fix(transition_frontier/genesis): genesis account in ledger must be first, not last --- .../genesis/transition_frontier_genesis_config.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/node/src/transition_frontier/genesis/transition_frontier_genesis_config.rs b/node/src/transition_frontier/genesis/transition_frontier_genesis_config.rs index 9275e50e67..238b6425fb 100644 --- a/node/src/transition_frontier/genesis/transition_frontier_genesis_config.rs +++ b/node/src/transition_frontier/genesis/transition_frontier_genesis_config.rs @@ -429,7 +429,7 @@ impl GenesisConfig { account } - let mut accounts = Vec::new(); + let mut accounts = genesis_account_iter().map(Ok).collect::>(); // Process block producers and their delegators for (bp_balance, delegators) in block_producers { @@ -476,11 +476,6 @@ impl GenesisConfig { } } - // Add genesis accounts - for genesis_account in genesis_account_iter() { - accounts.push(Ok(genesis_account)); - } - Self::build_ledger_from_accounts(accounts) } From b17e3b0e70ec7b7761064eda30f3f1442b089f63 Mon Sep 17 00:00:00 2001 From: Bruno Deferrari Date: Sat, 2 Nov 2024 10:45:13 -0300 Subject: [PATCH 010/175] chore: Remove unused `grace_period_end` function (#862) --- core/src/consensus.rs | 2 +- core/src/constants.rs | 13 ------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/core/src/consensus.rs b/core/src/consensus.rs index 7b63303c03..5cd9bc2e76 100644 --- a/core/src/consensus.rs +++ b/core/src/consensus.rs @@ -7,7 +7,7 @@ use time::{macros::format_description, OffsetDateTime}; use crate::constants::constraint_constants; pub use crate::constants::{ - checkpoint_window_size_in_slots, grace_period_end, slots_per_window, CHECKPOINTS_PER_YEAR, + checkpoint_window_size_in_slots, slots_per_window, CHECKPOINTS_PER_YEAR, }; // TODO get constants from elsewhere diff --git a/core/src/constants.rs b/core/src/constants.rs index 7d99d64772..070adfbddc 100644 --- a/core/src/constants.rs +++ b/core/src/constants.rs @@ -107,19 +107,6 @@ pub fn checkpoint_window_size_in_slots() -> u32 { size_in_slots as u32 } -pub fn grace_period_end(constants: &v2::MinaBaseProtocolConstantsCheckedValueStableV1) -> u32 { - let slots = { - const NUM_DAYS: u64 = 3; - let n_days_ms = days_to_ms(NUM_DAYS); - let n_days = n_days_ms / constraint_constants().block_window_duration_ms; - (n_days as u32).min(constants.slots_per_epoch.as_u32()) - }; - match constraint_constants().fork.as_ref() { - None => slots, - Some(fork) => slots + fork.global_slot_since_genesis, - } -} - pub const DEFAULT_GENESIS_TIMESTAMP_MILLISECONDS: u64 = 1707157200000; pub const PROTOCOL_TRANSACTION_VERSION: u8 = 3; From dd675ea103e9f6476f53e9b473bde9cf7bd4e957 Mon Sep 17 00:00:00 2001 From: Zura Benashvili Date: Sat, 2 Nov 2024 20:20:33 +0400 Subject: [PATCH 011/175] perf(node): load prover indexes in parallel with bootstrap to reduce startup time closes: #751 --- cli/src/commands/node/mod.rs | 10 +- core/src/thread.rs | 18 ++ ledger/src/proofs/circuit_blobs.rs | 19 +- ledger/src/proofs/provers.rs | 199 ++---------------- ledger/src/proofs/verifiers.rs | 2 +- node/common/src/service/block_producer/mod.rs | 9 +- node/common/src/service/builder.rs | 4 +- node/native/src/node/builder.rs | 12 +- node/native/src/service/builder.rs | 4 +- node/testing/src/cluster/mod.rs | 2 +- node/web/src/lib.rs | 13 +- node/web/src/node/builder.rs | 8 +- 12 files changed, 92 insertions(+), 208 deletions(-) diff --git a/cli/src/commands/node/mod.rs b/cli/src/commands/node/mod.rs index fde5d76d9a..aa6b2897c1 100644 --- a/cli/src/commands/node/mod.rs +++ b/cli/src/commands/node/mod.rs @@ -226,10 +226,12 @@ impl Node { if let Some(producer_key_path) = self.producer_key { let password = &self.producer_key_password; - node::core::info!(node::core::log::system_time(); summary = "loading provers index"); - let provers = BlockProver::make(Some(block_verifier_index), Some(work_verifier_index)); - node::core::info!(node::core::log::system_time(); summary = "loaded provers index"); - node_builder.block_producer_from_file(provers, producer_key_path, password)?; + openmina_core::thread::spawn(|| { + node::core::info!(node::core::log::system_time(); summary = "loading provers index"); + BlockProver::make(Some(block_verifier_index), Some(work_verifier_index)); + node::core::info!(node::core::log::system_time(); summary = "loaded provers index"); + }); + node_builder.block_producer_from_file(producer_key_path, password, None)?; if let Some(pub_key) = self.coinbase_receiver { node_builder diff --git a/core/src/thread.rs b/core/src/thread.rs index daabd4fc67..7752288db6 100644 --- a/core/src/thread.rs +++ b/core/src/thread.rs @@ -73,6 +73,24 @@ mod main_thread { })); rx.await.ok() } + + pub fn run_async_fn_in_main_thread_blocking(f: F) -> Option + where + T: 'static + Send, + FU: Future, + F: 'static + Send + FnOnce() -> FU, + { + let sender = MAIN_THREAD_TASK_SENDER + .get() + .expect("main thread not initialized"); + let (tx, rx) = oneshot::channel(); + let _ = sender.send(Box::pin(async move { + wasm_bindgen_futures::spawn_local(async move { + let _ = tx.send(f().await); + }) + })); + rx.blocking_recv().ok() + } } #[cfg(target_family = "wasm")] pub use main_thread::*; diff --git a/ledger/src/proofs/circuit_blobs.rs b/ledger/src/proofs/circuit_blobs.rs index ed89c42852..af19c7dc2a 100644 --- a/ledger/src/proofs/circuit_blobs.rs +++ b/ledger/src/proofs/circuit_blobs.rs @@ -36,6 +36,15 @@ mod http { _get_bytes(url).await } } + + pub fn get_bytes_blocking(url: &str) -> std::io::Result> { + let url = url.to_owned(); + if thread::is_web_worker_thread() { + thread::run_async_fn_in_main_thread_blocking(move || _get_bytes(url)).expect("failed to run task in the main thread! Maybe main thread crashed or not initialized?") + } else { + panic!("can't do blocking requests from main browser thread"); + } + } } #[cfg(not(target_family = "wasm"))] @@ -53,7 +62,7 @@ fn git_release_url(filename: &impl AsRef) -> String { } #[cfg(not(target_family = "wasm"))] -pub fn fetch(filename: &impl AsRef) -> std::io::Result> { +pub fn fetch_blocking(filename: &impl AsRef) -> std::io::Result> { use std::path::PathBuf; fn try_base_dir>(base_dir: P, filename: &impl AsRef) -> Option { @@ -124,3 +133,11 @@ pub async fn fetch(filename: &impl AsRef) -> std::io::Result> { http::get_bytes(&url).await // http::get_bytes(&git_release_url(filename)).await } + +#[cfg(target_family = "wasm")] +pub fn fetch_blocking(filename: &impl AsRef) -> std::io::Result> { + let prefix = + option_env!("CIRCUIT_BLOBS_HTTP_PREFIX").unwrap_or("/assets/webnode/circuit-blobs"); + let url = format!("{prefix}/{}", filename.as_ref().to_str().unwrap()); + http::get_bytes_blocking(&url) +} diff --git a/ledger/src/proofs/provers.rs b/ledger/src/proofs/provers.rs index 1e1da34c12..3afd8aa71c 100644 --- a/ledger/src/proofs/provers.rs +++ b/ledger/src/proofs/provers.rs @@ -50,23 +50,13 @@ fn decode_gates_file( Ok(data.gates) } -#[cfg(not(target_family = "wasm"))] fn read_gates_file( filename: &impl AsRef, ) -> std::io::Result>> { - let bytes = circuit_blobs::fetch(filename)?; + let bytes = circuit_blobs::fetch_blocking(filename)?; decode_gates_file(bytes.as_slice()) } -#[cfg(target_family = "wasm")] -async fn read_gates_file( - filepath: &impl AsRef, -) -> std::io::Result>> { - let resp = circuit_blobs::fetch(filepath).await?; - decode_gates_file(&mut resp.as_slice()) -} - -#[cfg(not(target_family = "wasm"))] fn make_gates( filename: &str, ) -> ( @@ -88,30 +78,6 @@ fn make_gates( (internal_vars_path, rows_rev_path, gates) } -#[cfg(target_family = "wasm")] -async fn make_gates( - filename: &str, -) -> ( - HashMap, Option)>, - Vec>>, - Vec>, -) { - let circuits_config = openmina_core::NetworkConfig::global().circuits_config; - let base_dir = Path::new(circuits_config.directory_name); - - let internal_vars_path = base_dir.join(format!("{}_internal_vars.bin", filename)); - let rows_rev_path = base_dir.join(format!("{}_rows_rev.bin", filename)); - let gates_path = base_dir.join(format!("{}_gates.json", filename)); - - let gates: Vec> = read_gates_file(&gates_path).await.unwrap(); - let (internal_vars_path, rows_rev_path) = - read_constraints_data::(&internal_vars_path, &rows_rev_path) - .await - .unwrap(); - - (internal_vars_path, rows_rev_path, gates) -} - macro_rules! get_or_make { ($constant: ident, $type: ty, $filename: expr) => {{ get_or_make!($constant, $type, None, $filename) @@ -121,13 +87,7 @@ macro_rules! get_or_make { return prover.clone(); } - let (internal_vars, rows_rev, gates) = { - #[cfg(not(target_family = "wasm"))] - let res = make_gates($filename); - #[cfg(target_family = "wasm")] - let res = make_gates($filename).await; - res - }; + let (internal_vars, rows_rev, gates) = make_gates($filename); let index = make_prover_index::<$type, _>(gates, $verifier_index); let prover = Prover { @@ -153,7 +113,6 @@ fn default_circuits_config() -> &'static CircuitsConfig { openmina_core::NetworkConfig::global().circuits_config } -#[cfg(not(target_family = "wasm"))] mod prover_makers { use super::*; @@ -274,128 +233,6 @@ mod prover_makers { } } -#[cfg(target_family = "wasm")] -mod prover_makers { - use super::*; - - async fn get_or_make_tx_step_prover(config: &CircuitsConfig) -> Arc> { - get_or_make!( - TX_STEP_PROVER, - StepTransactionProof, - config.step_transaction_gates - ) - } - async fn get_or_make_tx_wrap_prover( - config: &CircuitsConfig, - verifier_index: Option, - ) -> Arc> { - get_or_make!( - TX_WRAP_PROVER, - WrapTransactionProof, - verifier_index.map(Into::into), - config.wrap_transaction_gates - ) - } - async fn get_or_make_merge_step_prover(config: &CircuitsConfig) -> Arc> { - get_or_make!(MERGE_STEP_PROVER, StepMergeProof, config.step_merge_gates) - } - async fn get_or_make_block_step_prover(config: &CircuitsConfig) -> Arc> { - get_or_make!( - BLOCK_STEP_PROVER, - StepBlockProof, - config.step_blockchain_gates - ) - } - async fn get_or_make_block_wrap_prover( - config: &CircuitsConfig, - verifier_index: Option, - ) -> Arc> { - get_or_make!( - BLOCK_WRAP_PROVER, - WrapBlockProof, - verifier_index.map(Into::into), - config.wrap_blockchain_gates - ) - } - async fn get_or_make_zkapp_step_opt_signed_opt_signed_prover( - config: &CircuitsConfig, - ) -> Arc> { - get_or_make!( - ZKAPP_STEP_OPT_SIGNED_OPT_SIGNED_PROVER, - StepZkappOptSignedOptSignedProof, - config.step_transaction_opt_signed_opt_signed_gates - ) - } - async fn get_or_make_zkapp_step_opt_signed_prover(config: &CircuitsConfig) -> Arc> { - get_or_make!( - ZKAPP_STEP_OPT_SIGNED_PROVER, - StepZkappOptSignedProof, - config.step_transaction_opt_signed_gates - ) - } - async fn get_or_make_zkapp_step_proof_prover(config: &CircuitsConfig) -> Arc> { - get_or_make!( - ZKAPP_STEP_PROOF_PROVER, - StepZkappProvedProof, - config.step_transaction_proved_gates - ) - } - - impl BlockProver { - pub async fn make( - block_verifier_index: Option, - tx_verifier_index: Option, - ) -> Self { - let config = default_circuits_config(); - let block_step_prover = get_or_make_block_step_prover(config).await; - let block_wrap_prover = - get_or_make_block_wrap_prover(config, block_verifier_index).await; - let tx_wrap_prover = get_or_make_tx_wrap_prover(config, tx_verifier_index).await; - - Self { - block_step_prover, - block_wrap_prover, - tx_wrap_prover, - } - } - } - - impl TransactionProver { - pub async fn make(tx_verifier_index: Option) -> Self { - let config = default_circuits_config(); - let tx_step_prover = get_or_make_tx_step_prover(config).await; - let tx_wrap_prover = get_or_make_tx_wrap_prover(config, tx_verifier_index).await; - let merge_step_prover = get_or_make_merge_step_prover(config).await; - - Self { - tx_step_prover, - tx_wrap_prover, - merge_step_prover, - } - } - } - - impl ZkappProver { - pub async fn make(tx_verifier_index: Option) -> Self { - let config = default_circuits_config(); - let tx_wrap_prover = get_or_make_tx_wrap_prover(config, tx_verifier_index).await; - let merge_step_prover = get_or_make_merge_step_prover(config).await; - let step_opt_signed_opt_signed_prover = - get_or_make_zkapp_step_opt_signed_opt_signed_prover(config).await; - let step_opt_signed_prover = get_or_make_zkapp_step_opt_signed_prover(config).await; - let step_proof_prover = get_or_make_zkapp_step_proof_prover(config).await; - - Self { - tx_wrap_prover, - merge_step_prover, - step_opt_signed_opt_signed_prover, - step_opt_signed_prover, - step_proof_prover, - } - } - } -} - #[derive(Clone)] pub struct BlockProver { pub block_step_prover: Arc>, @@ -419,6 +256,18 @@ pub struct ZkappProver { pub step_proof_prover: Arc>, } +impl BlockProver { + /// Called must make sure that before calling this, + /// `BlockProver::make` call was made. + pub fn get_once_made() -> Self { + Self { + block_step_prover: BLOCK_STEP_PROVER.wait().clone(), + block_wrap_prover: BLOCK_WRAP_PROVER.wait().clone(), + tx_wrap_prover: TX_WRAP_PROVER.wait().clone(), + } + } +} + fn decode_constraints_data( mut internal_vars: &[u8], mut rows_rev: &[u8], @@ -471,31 +320,15 @@ fn decode_constraints_data( Some((internal_vars, rows_rev)) } -#[cfg(not(target_family = "wasm"))] fn read_constraints_data( internal_vars_path: &Path, rows_rev_path: &Path, ) -> Option<(InternalVars, Vec>>)> { // ((Fp.t * V.t) list * Fp.t option) - let internal_vars = circuit_blobs::fetch(&internal_vars_path).ok()?; + let internal_vars = circuit_blobs::fetch_blocking(&internal_vars_path).ok()?; // V.t option array list - let rows_rev = circuit_blobs::fetch(&rows_rev_path).ok()?; + let rows_rev = circuit_blobs::fetch_blocking(&rows_rev_path).ok()?; decode_constraints_data(internal_vars.as_slice(), rows_rev.as_slice()) } - -#[cfg(target_family = "wasm")] -async fn read_constraints_data( - internal_vars_path: &Path, - rows_rev_path: &Path, -) -> Option<(InternalVars, Vec>>)> { - // ((Fp.t * V.t) list * Fp.t option) - let internal_vars = circuit_blobs::fetch(&internal_vars_path).await.ok()?; - - // V.t option array list - let rows_rev = circuit_blobs::fetch(&rows_rev_path).await.ok()?; - // std::fs::read(Path::new(env!("CARGO_MANIFEST_DIR")).join("rows_rev.bin")).unwrap(); - - decode_constraints_data(internal_vars.as_ref(), rows_rev.as_ref()) -} diff --git a/ledger/src/proofs/verifiers.rs b/ledger/src/proofs/verifiers.rs index 3ede6d9be7..a0acd68096 100644 --- a/ledger/src/proofs/verifiers.rs +++ b/ledger/src/proofs/verifiers.rs @@ -71,7 +71,7 @@ fn cache_path(kind: Kind) -> Option { macro_rules! read_cache { ($kind: expr, $digest: expr) => {{ #[cfg(not(target_family = "wasm"))] - let data = super::circuit_blobs::fetch(&cache_filename($kind)) + let data = super::circuit_blobs::fetch_blocking(&cache_filename($kind)) .context("fetching verifier index failed")?; #[cfg(target_family = "wasm")] let data = super::circuit_blobs::fetch(&cache_filename($kind)) diff --git a/node/common/src/service/block_producer/mod.rs b/node/common/src/service/block_producer/mod.rs index afb8288b79..e8a365ad39 100644 --- a/node/common/src/service/block_producer/mod.rs +++ b/node/common/src/service/block_producer/mod.rs @@ -16,16 +16,16 @@ use node::{ use crate::EventSender; pub struct BlockProducerService { - provers: BlockProver, + provers: Option, keypair: AccountSecretKey, vrf_evaluation_sender: mpsc::UnboundedSender, } impl BlockProducerService { pub fn new( - provers: BlockProver, keypair: AccountSecretKey, vrf_evaluation_sender: mpsc::UnboundedSender, + provers: Option, ) -> Self { Self { provers, @@ -35,9 +35,9 @@ impl BlockProducerService { } pub fn start( - provers: BlockProver, event_sender: EventSender, keypair: AccountSecretKey, + provers: Option, ) -> Self { let (vrf_evaluation_sender, vrf_evaluation_receiver) = mpsc::unbounded_channel::(); @@ -54,7 +54,7 @@ impl BlockProducerService { }) .unwrap(); - BlockProducerService::new(provers, keypair, vrf_evaluation_sender) + BlockProducerService::new(keypair, vrf_evaluation_sender, provers) } pub fn keypair(&self) -> AccountSecretKey { @@ -103,6 +103,7 @@ impl node::service::BlockProducerService for crate::NodeService { .expect("provers shouldn't be needed if block producer isn't initialized") .provers .clone() + .unwrap_or_else(|| BlockProver::get_once_made()) } fn prove(&mut self, block_hash: StateHash, input: Box) { diff --git a/node/common/src/service/builder.rs b/node/common/src/service/builder.rs index 6ecbc9e3af..6b724ae3c3 100644 --- a/node/common/src/service/builder.rs +++ b/node/common/src/service/builder.rs @@ -80,13 +80,13 @@ impl NodeServiceCommonBuilder { pub fn block_producer_init( &mut self, - provers: BlockProver, keypair: AccountSecretKey, + provers: Option, ) -> &mut Self { self.block_producer = Some(BlockProducerService::start( - provers, self.event_sender.clone(), keypair, + provers, )); self } diff --git a/node/native/src/node/builder.rs b/node/native/src/node/builder.rs index f8400cd91b..dfe21a6406 100644 --- a/node/native/src/node/builder.rs +++ b/node/native/src/node/builder.rs @@ -174,27 +174,31 @@ impl NodeBuilder { } /// Set up block producer. - pub fn block_producer(&mut self, provers: BlockProver, key: AccountSecretKey) -> &mut Self { + pub fn block_producer( + &mut self, + key: AccountSecretKey, + provers: Option, + ) -> &mut Self { let config = BlockProducerConfig { pub_key: key.public_key().into(), custom_coinbase_receiver: None, proposed_protocol_version: None, }; self.block_producer = Some(config); - self.service.block_producer_init(provers, key); + self.service.block_producer_init(key, provers); self } /// Set up block producer using keys from file. pub fn block_producer_from_file( &mut self, - provers: BlockProver, path: impl AsRef, password: &str, + provers: Option, ) -> anyhow::Result<&mut Self> { let key = AccountSecretKey::from_encrypted_file(path, password) .context("Failed to decrypt secret key file")?; - Ok(self.block_producer(provers, key)) + Ok(self.block_producer(key, provers)) } /// Receive block producer's coinbase reward to another account. diff --git a/node/native/src/service/builder.rs b/node/native/src/service/builder.rs index a258f6f6ab..abb4d156ec 100644 --- a/node/native/src/service/builder.rs +++ b/node/native/src/service/builder.rs @@ -46,10 +46,10 @@ impl NodeServiceBuilder { pub fn block_producer_init( &mut self, - provers: BlockProver, keypair: AccountSecretKey, + provers: Option, ) -> &mut Self { - self.common.block_producer_init(provers, keypair); + self.common.block_producer_init(keypair, provers); self } diff --git a/node/testing/src/cluster/mod.rs b/node/testing/src/cluster/mod.rs index 8a39c95553..c117ef07bf 100644 --- a/node/testing/src/cluster/mod.rs +++ b/node/testing/src/cluster/mod.rs @@ -321,7 +321,7 @@ impl Cluster { if let Some(keypair) = block_producer_sec_key { let provers = BlockProver::make(None, None); - service_builder.block_producer_init(provers, keypair); + service_builder.block_producer_init(keypair, provers); } let real_service = service_builder diff --git a/node/web/src/lib.rs b/node/web/src/lib.rs index 5d8c0a5f89..d7c4c10946 100644 --- a/node/web/src/lib.rs +++ b/node/web/src/lib.rs @@ -45,7 +45,7 @@ pub async fn run(block_producer: Option) -> RpcSender { node.run_forever().await; }); - wasm_bindgen::throw_str("Cursed hack to keep workers alive. See https://github.com/rustwasm/wasm-bindgen/issues/2945"); + keep_worker_alive_cursed_hack(); }); rpc_sender_rx.await.unwrap() @@ -64,9 +64,10 @@ async fn setup_node( .work_verifier_index(work_verifier_index.clone()); if let Some(bp_key) = block_producer { - let provers = - BlockProver::make(Some(block_verifier_index), Some(work_verifier_index)).await; - node_builder.block_producer(provers, bp_key); + thread::spawn(move || { + BlockProver::make(Some(block_verifier_index), Some(work_verifier_index)); + }); + node_builder.block_producer(bp_key, None); } node_builder @@ -75,3 +76,7 @@ async fn setup_node( node_builder.gather_stats(); node_builder.build().context("node build failed!").unwrap() } + +fn keep_worker_alive_cursed_hack() { + wasm_bindgen::throw_str("Cursed hack to keep workers alive. See https://github.com/rustwasm/wasm-bindgen/issues/2945"); +} diff --git a/node/web/src/node/builder.rs b/node/web/src/node/builder.rs index 70c9a119c8..b9e93beac6 100644 --- a/node/web/src/node/builder.rs +++ b/node/web/src/node/builder.rs @@ -117,14 +117,18 @@ impl NodeBuilder { } /// Set up block producer. - pub fn block_producer(&mut self, provers: BlockProver, key: AccountSecretKey) -> &mut Self { + pub fn block_producer( + &mut self, + key: AccountSecretKey, + provers: Option, + ) -> &mut Self { let config = BlockProducerConfig { pub_key: key.public_key().into(), custom_coinbase_receiver: None, proposed_protocol_version: None, }; self.block_producer = Some(config); - self.service.block_producer_init(provers, key); + self.service.block_producer_init(key, provers); self } From 336c842935fceb977541adca6d7629061bffc156 Mon Sep 17 00:00:00 2001 From: Zura Benashvili Date: Sat, 2 Nov 2024 20:21:39 +0400 Subject: [PATCH 012/175] fix(node): producing genesis proof even when not needed Unable to calculate current slot without getting synced first --- node/common/src/service/block_producer/mod.rs | 2 +- node/src/state.rs | 11 ++++++----- node/testing/src/cluster/mod.rs | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/node/common/src/service/block_producer/mod.rs b/node/common/src/service/block_producer/mod.rs index e8a365ad39..df6427f2b2 100644 --- a/node/common/src/service/block_producer/mod.rs +++ b/node/common/src/service/block_producer/mod.rs @@ -103,7 +103,7 @@ impl node::service::BlockProducerService for crate::NodeService { .expect("provers shouldn't be needed if block producer isn't initialized") .provers .clone() - .unwrap_or_else(|| BlockProver::get_once_made()) + .unwrap_or_else(BlockProver::get_once_made) } fn prove(&mut self, block_hash: StateHash, input: Box) { diff --git a/node/src/state.rs b/node/src/state.rs index f62f3abdd3..a0b4735aed 100644 --- a/node/src/state.rs +++ b/node/src/state.rs @@ -290,12 +290,12 @@ impl State { } fn cur_slot(&self, initial_slot: impl FnOnce(&ArcBlockWithHash) -> u32) -> Option { - let best_tip = self.transition_frontier.best_tip()?; - let best_tip_ms = u64::from(best_tip.timestamp()) / 1_000_000; + let genesis = self.genesis_block()?; + let initial_ms = u64::from(genesis.timestamp()) / 1_000_000; let now_ms = u64::from(self.time()) / 1_000_000; - let ms = now_ms.saturating_sub(best_tip_ms); + let ms = now_ms.saturating_sub(initial_ms); let slots = ms / constraint_constants().block_window_duration_ms; - Some(initial_slot(best_tip) + slots as u32) + Some(initial_slot(&genesis) + slots as u32) } /// Current global slot based on constants and current time. @@ -306,7 +306,8 @@ impl State { } pub fn current_slot(&self) -> Option { - self.cur_slot(|b| b.global_slot() % b.constants().slots_per_epoch.as_u32()) + let slots_per_epoch = self.genesis_block()?.constants().slots_per_epoch.as_u32(); + Some(self.cur_global_slot()? % slots_per_epoch) } pub fn cur_global_slot_since_genesis(&self) -> Option { diff --git a/node/testing/src/cluster/mod.rs b/node/testing/src/cluster/mod.rs index c117ef07bf..69f976b924 100644 --- a/node/testing/src/cluster/mod.rs +++ b/node/testing/src/cluster/mod.rs @@ -321,7 +321,7 @@ impl Cluster { if let Some(keypair) = block_producer_sec_key { let provers = BlockProver::make(None, None); - service_builder.block_producer_init(keypair, provers); + service_builder.block_producer_init(keypair, Some(provers)); } let real_service = service_builder From c09a53825d7920ff58a7ce91d711922c6ef0e754 Mon Sep 17 00:00:00 2001 From: Zura Benashvili Date: Sat, 2 Nov 2024 20:37:05 +0400 Subject: [PATCH 013/175] fix(logging): disconnection reason not logged --- node/src/block_producer/block_producer_actions.rs | 1 + node/src/p2p/callbacks/p2p_callbacks_reducer.rs | 4 +--- p2p/src/disconnection/p2p_disconnection_actions.rs | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/node/src/block_producer/block_producer_actions.rs b/node/src/block_producer/block_producer_actions.rs index e234f33495..5b33c7f7cf 100644 --- a/node/src/block_producer/block_producer_actions.rs +++ b/node/src/block_producer/block_producer_actions.rs @@ -60,6 +60,7 @@ pub enum BlockProducerAction { proof: Box, }, BlockProduced, + #[action_event(level = trace)] BlockInject, BlockInjected, } diff --git a/node/src/p2p/callbacks/p2p_callbacks_reducer.rs b/node/src/p2p/callbacks/p2p_callbacks_reducer.rs index 2b24054fba..d540a11d20 100644 --- a/node/src/p2p/callbacks/p2p_callbacks_reducer.rs +++ b/node/src/p2p/callbacks/p2p_callbacks_reducer.rs @@ -287,9 +287,7 @@ impl crate::State { let response = None.or_else(|| { let best_tip = best_chain.last()?; let mut chain_iter = best_chain.iter(); - let root_block = chain_iter.next(); - // when our best tip is genesis block. - let root_block = root_block.unwrap_or(best_tip); + let root_block = chain_iter.next()?; // TODO(binier): cache body hashes let Ok(body_hashes) = chain_iter .map(|b| b.header().protocol_state.body.try_hash()) diff --git a/p2p/src/disconnection/p2p_disconnection_actions.rs b/p2p/src/disconnection/p2p_disconnection_actions.rs index aec90cef41..6a9b011e8d 100644 --- a/p2p/src/disconnection/p2p_disconnection_actions.rs +++ b/p2p/src/disconnection/p2p_disconnection_actions.rs @@ -7,7 +7,7 @@ use crate::{P2pPeerStatus, P2pState, PeerId}; pub type P2pDisconnectionActionWithMetaRef<'a> = redux::ActionWithMeta<&'a P2pDisconnectionAction>; #[derive(Serialize, Deserialize, Debug, Clone, ActionEvent)] -#[action_event(fields(display(peer_id), display(reason)), level = info)] +#[action_event(level = debug)] pub enum P2pDisconnectionAction { /// Initialize disconnection. #[action_event(fields(display(peer_id), display(reason)), level = info)] @@ -16,7 +16,7 @@ pub enum P2pDisconnectionAction { reason: P2pDisconnectionReason, }, /// Finish disconnecting from a peer. - #[action_event(level = debug)] + #[action_event(fields(display(peer_id)), level = debug)] Finish { peer_id: PeerId }, } From 096241eaf656905f7115f48a43dcc864d828cdd6 Mon Sep 17 00:00:00 2001 From: Zura Benashvili Date: Sat, 2 Nov 2024 21:26:48 +0400 Subject: [PATCH 014/175] fix(docker/frontend): download verifier indexes to assets as well --- frontend/docker/startup.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/docker/startup.sh b/frontend/docker/startup.sh index 7f167362f2..f92157a6d1 100644 --- a/frontend/docker/startup.sh +++ b/frontend/docker/startup.sh @@ -27,6 +27,8 @@ download_circuit_files() { CIRCUITS_VERSION="3.0.1devnet" DEVNET_CIRCUIT_FILES=( + "block_verifier_index.postcard" + "transaction_verifier_index.postcard" "step-step-proving-key-blockchain-snark-step-0-55f640777b6486a6fd3fdbc3fcffcc60_gates.json" "step-step-proving-key-blockchain-snark-step-0-55f640777b6486a6fd3fdbc3fcffcc60_internal_vars.bin" "step-step-proving-key-blockchain-snark-step-0-55f640777b6486a6fd3fdbc3fcffcc60_rows_rev.bin" From e877d0c2b2dc8ab6eed25c92937e0e5f7b28bff0 Mon Sep 17 00:00:00 2001 From: Vladislav Melnik Date: Mon, 4 Nov 2024 11:55:10 +0100 Subject: [PATCH 015/175] (fix): prefer declared external IP instead of observed address --- p2p/src/connection/outgoing/mod.rs | 38 ++++++++++++++++++++++++ p2p/src/identify/p2p_identify_reducer.rs | 4 +++ 2 files changed, 42 insertions(+) diff --git a/p2p/src/connection/outgoing/mod.rs b/p2p/src/connection/outgoing/mod.rs index 6bed5c356e..e9b6bdf6ea 100644 --- a/p2p/src/connection/outgoing/mod.rs +++ b/p2p/src/connection/outgoing/mod.rs @@ -6,11 +6,13 @@ pub use p2p_connection_outgoing_actions::*; mod p2p_connection_outgoing_reducer; +use std::net::IpAddr; #[cfg(feature = "p2p-libp2p")] use std::net::SocketAddr; use std::{fmt, str::FromStr}; use binprot_derive::{BinProtRead, BinProtWrite}; +use multiaddr::{Multiaddr, Protocol}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -44,6 +46,42 @@ pub struct P2pConnectionOutgoingInitLibp2pOpts { pub port: u16, } +impl P2pConnectionOutgoingInitLibp2pOpts { + /// If the current host is local and there is a better host among the `addrs`, + /// replace the current one with the better one. + pub fn update_host_if_needed<'a>(&mut self, mut addrs: impl Iterator) { + fn is_local(ip: impl Into) -> bool { + match ip.into() { + IpAddr::V4(ip) => ip.is_loopback() || ip.is_private(), + IpAddr::V6(ip) => ip.is_loopback(), + } + } + + // if current dial opts is not good enough + let update = match &self.host { + Host::Domain(_) => false, + Host::Ipv4(ip) => is_local(*ip), + Host::Ipv6(ip) => is_local(*ip), + }; + if update { + // if new options is better + let new = addrs.find_map(|x| { + x.iter().find_map(|x| match x { + Protocol::Dns4(hostname) | Protocol::Dns6(hostname) => { + Some(Host::Domain(hostname.into_owned())) + } + Protocol::Ip4(ip) if !is_local(ip) => Some(Host::Ipv4(ip)), + Protocol::Ip6(ip) if !is_local(ip) => Some(Host::Ipv6(ip)), + _ => None, + }) + }); + if let Some(new) = new { + self.host = new; + } + } + } +} + pub(crate) mod libp2p_opts { use std::net::{IpAddr, SocketAddr}; diff --git a/p2p/src/identify/p2p_identify_reducer.rs b/p2p/src/identify/p2p_identify_reducer.rs index d0a5c1f0d4..0b887f2268 100644 --- a/p2p/src/identify/p2p_identify_reducer.rs +++ b/p2p/src/identify/p2p_identify_reducer.rs @@ -2,6 +2,7 @@ use openmina_core::{bug_condition, Substate}; use redux::ActionWithMeta; use crate::{ + connection::outgoing::P2pConnectionOutgoingInitOpts, disconnection::{P2pDisconnectionAction, P2pDisconnectionReason}, token::{BroadcastAlgorithm, DiscoveryAlgorithm, IdentifyAlgorithm, RpcAlgorithm, StreamKind}, P2pNetworkConnectionMuxState, P2pNetworkKadRequestAction, P2pNetworkKadState, @@ -60,6 +61,9 @@ impl P2pState { let info = *info; if let Some(peer) = p2p_state.peers.get_mut(&peer_id) { peer.identify = Some(info.clone()); + if let Some(P2pConnectionOutgoingInitOpts::LibP2P(opts)) = &mut peer.dial_opts { + opts.update_host_if_needed(info.listen_addrs.iter()); + } } else { bug_condition!( "Peer state not found for `P2pIdentifyAction::UpdatePeerInformation`" From 63d6e531a500ed5065d7157be18f9898723801a4 Mon Sep 17 00:00:00 2001 From: directcuteo <37619567+directcuteo@users.noreply.github.com> Date: Mon, 4 Nov 2024 13:46:49 +0200 Subject: [PATCH 016/175] Frontend - Caching for Webnode (#865) --- frontend/docker/startup.sh | 33 +++++++++++++++++++--- frontend/httpd.conf | 25 ++++++++++++++++ frontend/package.json | 5 ++-- frontend/scripts/update-webnode-version.js | 21 ++++++++++++++ frontend/src/index.html | 10 +++++-- 5 files changed, 85 insertions(+), 9 deletions(-) create mode 100644 frontend/scripts/update-webnode-version.js diff --git a/frontend/docker/startup.sh b/frontend/docker/startup.sh index f92157a6d1..98378a56fc 100644 --- a/frontend/docker/startup.sh +++ b/frontend/docker/startup.sh @@ -79,15 +79,15 @@ download_wasm_files() { echo "Error: OPENMINA_WASM_VERSION is not set. Exiting." exit 1 fi - + WASM_URL="$OPENMINA_BASE_URL/openmina/releases/download/$OPENMINA_WASM_VERSION/openmina-$OPENMINA_WASM_VERSION-webnode-wasm.tar.gz" TARGET_DIR="/usr/local/apache2/htdocs/assets/webnode/pkg" - + mkdir -p "$TARGET_DIR" echo "Downloading WASM files from $WASM_URL..." curl -s -L --retry 3 --retry-delay 5 -o "/tmp/openmina-$OPENMINA_WASM_VERSION-webnode-wasm.tar.gz" "$WASM_URL" - + if [[ $? -ne 0 ]]; then echo "Failed to download the WASM file after 3 attempts, exiting." exit 1 @@ -95,19 +95,44 @@ download_wasm_files() { echo "WASM file downloaded successfully. Extracting to $TARGET_DIR..." tar -xzf "/tmp/openmina-$OPENMINA_WASM_VERSION-webnode-wasm.tar.gz" -C "$TARGET_DIR" - + # Check if the extraction was successful if [[ $? -ne 0 ]]; then echo "Failed to extract the WASM file, exiting." exit 1 else echo "WASM files extracted successfully to $TARGET_DIR" + + # Inject caching logic into openmina_node_web.js + OPENMINA_JS="$TARGET_DIR/openmina_node_web.js" + inject_caching_logic "$OPENMINA_JS" fi fi rm "/tmp/openmina-$OPENMINA_WASM_VERSION-webnode-wasm.tar.gz" } +inject_caching_logic() { + local js_file="$1" + if [ -f "$js_file" ]; then + echo "Injecting caching logic into $js_file" + + # Generate a unique hash + local hash=$(openssl rand -hex 8) + + sed -i "/module_or_path = fetch(module_or_path);/i\ module_or_path += \"\?v=${hash}\";" "$js_file" + sed -i 's/module_or_path = fetch(module_or_path);/module_or_path = fetch(module_or_path, { cache: "force-cache", headers: { "Cache-Control": "max-age=31536000, immutable" } });/' "$js_file" + if [[ $? -ne 0 ]]; then + echo "Failed to inject caching logic into $js_file" + else + echo "Successfully injected caching logic into $js_file" + fi + else + echo "Warning: $js_file not found. Caching logic not injected." + fi +} + + if [ -n "$OPENMINA_FRONTEND_ENVIRONMENT" ]; then echo "Using environment: $OPENMINA_FRONTEND_ENVIRONMENT" cp -f /usr/local/apache2/htdocs/assets/environments/"$OPENMINA_FRONTEND_ENVIRONMENT".js \ diff --git a/frontend/httpd.conf b/frontend/httpd.conf index f82a328e46..e6c0570e75 100644 --- a/frontend/httpd.conf +++ b/frontend/httpd.conf @@ -228,6 +228,31 @@ SSLProxyEngine On + +# Define development environments +SetEnvIf Host "localhost|127.0.0.1" DEVELOPMENT + +# Cache rules for WebNode assets + + # Development environment - no cache + Header set Cache-Control "no-store, no-cache, must-revalidate" env=DEVELOPMENT + + # Production environment - aggressive caching + Header set Cache-Control "public, max-age=31536000, immutable" env=!DEVELOPMENT + + + # Development environment - no cache + Header set Cache-Control "no-store, no-cache, must-revalidate" env=DEVELOPMENT + + # Production environment - aggressive caching + Header set Cache-Control "public, max-age=31536000, immutable" env=!DEVELOPMENT + + +# Make sure mod_headers is enabled + + LoadModule headers_module modules/mod_headers.so + + # # If you wish httpd to run as a different user or group, you must run # httpd as root initially and it will switch. diff --git a/frontend/package.json b/frontend/package.json index 925c2379d1..c9f5218f60 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,11 +6,12 @@ "start": "npm install && ng serve --configuration local --open", "start:dev": "ng serve --configuration development", "build": "ng build", - "build:prod": "ng build --configuration production", + "build:prod": "npm run prebuild && ng build --configuration production", "tests": "npx cypress open --config baseUrl=http://localhost:4200", "tests:headless": "npx cypress run --headless --config baseUrl=http://localhost:4200", "docker": "npm run build:prod && docker buildx build --platform linux/amd64 -t openmina/frontend:latest . && docker push openmina/frontend:latest", - "start:bundle": "npx http-server dist/frontend -p 4200" + "start:bundle": "npx http-server dist/frontend -p 4200", + "prebuild": "node scripts/update-webnode-version.js" }, "private": true, "dependencies": { diff --git a/frontend/scripts/update-webnode-version.js b/frontend/scripts/update-webnode-version.js new file mode 100644 index 0000000000..8e18ab29dc --- /dev/null +++ b/frontend/scripts/update-webnode-version.js @@ -0,0 +1,21 @@ +const fs = require('fs'); +const crypto = require('crypto'); + +// Generate a random hash +const hash = crypto.randomBytes(16).toString('hex'); // Generates a 32-character random hex string + +// Read and update index.html +const indexPath = './src/index.html'; +let indexHtml = fs.readFileSync(indexPath, 'utf8'); + +// Enhanced Regex Pattern +// Match 'const WEBNODE_VERSION = ' with optional whitespace around the equals and between quotes +const versionRegex = /const\s+WEBNODE_VERSION\s*=\s*['"][^'"]*['"];/; + +// Perform replacement +indexHtml = indexHtml.replace(versionRegex, `const WEBNODE_VERSION = '${hash}';`); + +// Write updated content to index.html +fs.writeFileSync(indexPath, indexHtml); + +console.log(`Updated WEBNODE_VERSION in ${indexPath} to ${hash}`); diff --git a/frontend/src/index.html b/frontend/src/index.html index 0ae6d07c19..b6768d9bf3 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -42,11 +42,15 @@ From 6a95fbae73b66fdba08922db373f704580677563 Mon Sep 17 00:00:00 2001 From: directcuteo <37619567+directcuteo@users.noreply.github.com> Date: Wed, 6 Nov 2024 09:45:31 +0200 Subject: [PATCH 022/175] Frontend - Improve web node loading (#873) --- .idea/vcs.xml | 1 + frontend/package.json | 1 + .../web-node-demo-dashboard.component.html | 8 ++++---- .../web-node-demo-dashboard.component.scss | 13 ++++++------ .../web-node-demo-dashboard.component.ts | 20 +++++++++++-------- 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 35eb1ddfbb..c4f6642370 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 7ade4400c2..11a9fc103a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,6 +5,7 @@ "install:deps": "npm install", "start": "npm install && ng serve --configuration local --open", "start:dev": "ng serve --configuration development", + "start:dev:mobile": "ng serve --configuration development --host 0.0.0.0", "build": "ng build", "build:prod": "ng build --configuration production", "tests": "npx cypress open --config baseUrl=http://localhost:4200", diff --git a/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.html b/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.html index fffd2a9436..0dd03375e7 100644 --- a/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.html +++ b/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.html @@ -1,11 +1,11 @@
-
-
+
+
Produce blocks, -  right in your browser + right in your browser
@@ -21,7 +21,7 @@
-
+
@for (item of loading; track $index) { diff --git a/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.scss b/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.scss index 0fb76960f3..574f231162 100644 --- a/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.scss +++ b/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.scss @@ -17,10 +17,9 @@ $white: #000000; .data-wrapper { height: calc(100% - 56px - 72px); max-width: 568px; + padding: 10vw 0; .header { - margin-bottom: 56px; - .loading-webnode { font-size: 24px; line-height: 32px; @@ -40,8 +39,10 @@ $white: #000000; } .progress { - height: 380px; - margin: 64px 0; + min-height: 380px; + @media (max-width: 768px) { + min-height: 36vh; + } } } @@ -80,9 +81,9 @@ $white: #000000; } } -@media (max-width: 768px) { +@media (max-width: 568px) { .font-16 { - font-size: 15px; + font-size: 14px; } .loading-webnode span { display: block; diff --git a/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.ts b/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.ts index 4d74dc10ac..30f82835d8 100644 --- a/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.ts +++ b/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, ElementRef, NgZone, OnInit, ViewChild } from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { untilDestroyed } from '@ngneat/until-destroy'; import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; import { WebNodeService } from '@core/services/web-node.service'; @@ -6,7 +6,7 @@ import { GlobalErrorHandlerService } from '@openmina/shared'; import { NgClass, NgForOf, NgIf, NgOptimizedImage } from '@angular/common'; import { Router } from '@angular/router'; import { getFirstFeature } from '@shared/constants/config'; -import { trigger, state, style, transition, animate } from '@angular/animations'; +import { animate, state, style, transition, trigger } from '@angular/animations'; import { filter, switchMap, timer } from 'rxjs'; import { LoadingSpinnerComponent } from '@shared/loading-spinner/loading-spinner.component'; import { FileProgressHelper } from '@core/helpers/file-progress.helper'; @@ -53,7 +53,7 @@ export interface WebNodeLoadingStep { ]), ], }) -export class WebNodeDemoDashboardComponent extends StoreDispatcher implements OnInit { +export class WebNodeDemoDashboardComponent extends StoreDispatcher implements OnInit, AfterViewInit { protected readonly WebNodeStepStatus = WebNodeStepStatus; readonly loading: WebNodeLoadingStep[] = [ @@ -87,6 +87,9 @@ export class WebNodeDemoDashboardComponent extends StoreDispatcher implements On this.listenToErrorIssuing(); this.checkWebNodeProgress(); this.fetchPeersInformation(); + } + + ngAfterViewInit(): void { this.buildProgressBar(); } @@ -196,10 +199,10 @@ export class WebNodeDemoDashboardComponent extends StoreDispatcher implements On downloaded: (progress.downloaded / 1e6).toFixed(1), total: (progress.totalSize / 1e6).toFixed(1), }; - // this step is only 1 out of 3. But it counts as 80% of the 100% progress. - // So we need to calculate the total progress based on the current step. - const totalProgress = (progress.progress * this.stepsPercentages[0]) / 100; - this.updateProgressBar(totalProgress); + if (this.svg) { + const totalProgress = (progress.progress * this.stepsPercentages[0]) / 100; + this.updateProgressBar(totalProgress); + } this.detect(); }); } @@ -259,7 +262,8 @@ export class WebNodeDemoDashboardComponent extends StoreDispatcher implements On this.progressBar.append('text') .attr('text-anchor', 'middle') - .attr('dominant-baseline', 'middle') + .attr('alignment-baseline', 'central') + .attr('dominant-baseline', 'central') .attr('font-size', '40px') .attr('fill', 'var(--base-primary)') .attr('opacity', 0.8) From 83f1df5b0a949a4397bc20cedcdf9705c122393e Mon Sep 17 00:00:00 2001 From: directcuteo <37619567+directcuteo@users.noreply.github.com> Date: Wed, 6 Nov 2024 16:06:32 +0200 Subject: [PATCH 023/175] Frontend - Web Node Loading Screen Improvements (#874) --- frontend/src/app/app.module.ts | 1 + .../src/app/core/services/web-node.service.ts | 43 +++++---- .../web-node-demo-dashboard.component.html | 85 +++++++---------- .../web-node-demo-dashboard.component.scss | 59 +++++++----- .../web-node-demo-dashboard.component.ts | 95 ++++++++++++------- frontend/src/index.html | 25 ++--- 6 files changed, 170 insertions(+), 138 deletions(-) diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index e63bed102f..c04c0ff09e 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -44,6 +44,7 @@ export class AppGlobalErrorhandler implements ErrorHandler { constructor(private errorHandlerService: GlobalErrorHandlerService) {} handleError(error: any): void { + console.log(123123123); Sentry.captureException(error); this.errorHandlerService.handleError(error); console.error(error); diff --git a/frontend/src/app/core/services/web-node.service.ts b/frontend/src/app/core/services/web-node.service.ts index b53ae44d16..8763276c60 100644 --- a/frontend/src/app/core/services/web-node.service.ts +++ b/frontend/src/app/core/services/web-node.service.ts @@ -1,5 +1,18 @@ import { Inject, Injectable } from '@angular/core'; -import { BehaviorSubject, catchError, filter, from, fromEvent, map, merge, Observable, of, switchMap, tap } from 'rxjs'; +import { + BehaviorSubject, + catchError, + EMPTY, + filter, + from, + fromEvent, + map, + merge, + Observable, + of, + switchMap, + tap, +} from 'rxjs'; import base from 'base-x'; import { any } from '@openmina/shared'; import { HttpClient } from '@angular/common/http'; @@ -55,8 +68,8 @@ export class WebNodeService { } loadWasm$(): Observable { - this.loadWebnodeJs(); - sendSentryEvent('Loading WebNode JS'); + this.webNodeStartTime = Date.now(); + // this.loadWebnodeJs(); return merge( of(any(window).webnode).pipe(filter(Boolean)), fromEvent(window, 'webNodeLoaded'), @@ -64,7 +77,6 @@ export class WebNodeService { switchMap(() => this.http.get<{ publicKey: string, privateKey: string }>('assets/webnode/web-node-secrets.json')), tap(data => { this.webNodeKeyPair = data; - sendSentryEvent('WebNode JS Loaded. Loading WebNode Wasm'); }), map(() => void 0), ); @@ -75,19 +87,16 @@ export class WebNodeService { .pipe( switchMap((wasm: any) => from(wasm.default('assets/webnode/pkg/openmina_node_web_bg.wasm')).pipe(map(() => wasm))), switchMap((wasm) => { - sendSentryEvent('WebNode Wasm loaded. Starting WebNode'); this.webnodeProgress$.next('Loaded'); return from(wasm.run(this.webNodeKeyPair.privateKey)); }), tap((webnode: any) => { - sendSentryEvent('WebNode Started'); - this.webNodeStartTime = Date.now(); (window as any)['webnode'] = webnode; this.webnode$.next(webnode); this.webnodeProgress$.next('Started'); }), catchError((error) => { - sendSentryEvent('WebNode failed to start'); + sendSentryEvent('WebNode failed to start: ' + error.message); console.error(error); return of(null); }), @@ -115,18 +124,16 @@ export class WebNodeService { filter(Boolean), switchMap(handle => from(any(handle).state().peers())), tap((peers) => { - if (!this.sentryEvents.sentNoPeersEvent && Date.now() - this.webNodeStartTime >= 5000 && peers.length === 0) { - sendSentryEvent('WebNode has no peers after 5 seconds from startup.'); - this.sentryEvents.sentNoPeersEvent = true; - } - if (!this.sentryEvents.sentPeersEvent && peers.length > 0) { - const seconds = (Date.now() - this.webNodeStartTime) / 1000; - sendSentryEvent(`WebNode found its first peer after ${seconds}s`); - this.sentryEvents.sentPeersEvent = true; - } + // if (!this.sentryEvents.sentNoPeersEvent && Date.now() - this.webNodeStartTime >= 5000 && peers.length === 0) { + // sendSentryEvent('WebNode has no peers after 5 seconds from startup.'); + // this.sentryEvents.sentNoPeersEvent = true; + // } + // if (!this.sentryEvents.sentPeersEvent && peers.length > 0) { + // this.sentryEvents.sentPeersEvent = true; + // } if (!this.sentryEvents.firstPeerConnected && peers.some((p: any) => p.connection_status === DashboardPeerStatus.CONNECTED)) { const seconds = (Date.now() - this.webNodeStartTime) / 1000; - sendSentryEvent(`WebNode connected to its first peer after ${seconds}s`); + sendSentryEvent(`WebNode connected in ${seconds.toFixed(1)}s`); this.sentryEvents.firstPeerConnected = true; this.webnodeProgress$.next('Connected'); } diff --git a/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.html b/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.html index 0dd03375e7..4bdfc683d5 100644 --- a/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.html +++ b/frontend/src/app/features/webnode/web-node-demo-dashboard/web-node-demo-dashboard.component.html @@ -1,74 +1,61 @@
-
-
-
- Produce blocks, - right in your browser -
+
+
+
+
+ Produce blocks, + right in your browser +
-
- @if (!loading[loading.length - 1].loaded) { - @if (loading[0].status === WebNodeStepStatus.LOADING) { - Downloading... - } @else { - ~7 seconds left - } - } @else { - Web Node is ready - } +
{{ loadingMessage }}
-
-
+
-
- @for (item of loading; track $index) { -
- @if (item.status === WebNodeStepStatus.LOADING) { - - } @else { - check_circle - } -
{{ item.name }} -
- @if (item.data) { - @if (item.data.total) { - {{ item.data.downloaded }} of {{ item.data.total }} MB +
+ @for (item of loading; track $index) { +
+ @if (item.status === WebNodeStepStatus.LOADING) { + } @else { - {{ item.data.est }} + check_circle } - } -
- } +
{{ item.name }} +
+
+ } +
@if (!loading[loading.length - 1].loaded && errors.length) { -
-
-
It appears there are some errors.
-
-
- @for (error of errors; track $index) { -
{{ error }}
- } +
+
+
+
It appears there are some errors.
+
+
+ @for (error of errors; track $index) { +
{{ error }}
+ } +
}