From d7ace2df5761d75498e139e62519e96486afa32a Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Fri, 25 Jul 2025 17:16:29 +0200 Subject: [PATCH 1/4] CI: add test-docs target to compile Rust code defined in docs --- .github/workflows/ci.yaml | 26 ++++++++++++++++++++++++++ Makefile | 5 +++++ 2 files changed, 31 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b335c2eb8..a239145b6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -115,6 +115,32 @@ jobs: run: | make test-p2p + docs-tests: + runs-on: ubuntu-22.04 + steps: + - name: Git checkout + uses: actions/checkout@v4 + + - name: Setup build dependencies + run: | + sudo apt update + sudo apt install -y protobuf-compiler + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + toolchain: 1.84 + + - name: Setup Rust Cache + uses: Swatinem/rust-cache@v2 + with: + prefix-key: "v0" + + - name: Test documentation code examples + run: | + make test-docs + build: # NOTE: If you add or remove platforms from this matrix, make sure to update # the documentation at website/docs/developers/getting-started.mdx diff --git a/Makefile b/Makefile index 685a7c92a..ab864ac4c 100644 --- a/Makefile +++ b/Makefile @@ -227,6 +227,11 @@ test-release: ## Run tests in release mode test-vrf: ## Run VRF tests, requires nightly Rust @cd vrf && cargo +nightly test --release -- -Z unstable-options --report-time +.PHONY: test-docs +test-docs: ## Run documentation tests to verify code examples compile and run + @echo "Testing documentation code examples..." + cargo test --doc + # Docker build targets .PHONY: docker-build-all From 865c33d24910e4d5559f7ef2c7ebb205331bc600 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Fri, 25 Jul 2025 17:16:56 +0200 Subject: [PATCH 2/4] Docs: fix make test-docs and add documentation for network conf --- Cargo.lock | 1 + core/src/chain_id.rs | 74 +++- core/src/constants.rs | 172 +++++++++ core/src/network.rs | 340 +++++++++++++++++- macros/Cargo.toml | 1 + macros/src/action_event.md | 90 ++++- mina-p2p-messages/src/rpc.rs | 4 +- node/testing/src/scenarios/driver.rs | 18 +- p2p/testing/src/cluster.rs | 3 +- .../docs/developers/network-configuration.md | 77 ++++ website/sidebars.ts | 7 + 11 files changed, 755 insertions(+), 32 deletions(-) create mode 100644 website/docs/developers/network-configuration.md diff --git a/Cargo.lock b/Cargo.lock index 1629fc80d..607cddfd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6047,6 +6047,7 @@ dependencies = [ "openmina-core", "proc-macro2 1.0.95", "quote 1.0.35", + "redux", "rust-format", "syn 2.0.96", "tracing", diff --git a/core/src/chain_id.rs b/core/src/chain_id.rs index cdd14afbc..cf4eff701 100644 --- a/core/src/chain_id.rs +++ b/core/src/chain_id.rs @@ -12,6 +12,18 @@ use std::{ use binprot::{BinProtRead, BinProtWrite}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +/// Unique identifier for a Mina blockchain network. +/// +/// The ChainId is a 32-byte hash that uniquely identifies a specific Mina network +/// configuration. It is computed from various network parameters including: +/// - Genesis state hash +/// - Constraint system digests (circuit verification hashes) +/// - Protocol constants (timing parameters, fees, etc.) +/// - Protocol version numbers +/// - Transaction pool configuration +/// +/// Different networks (mainnet, devnet) will have different ChainIds, and any +/// change to core protocol parameters will result in a new ChainId. #[derive(Clone, PartialEq, Eq)] pub struct ChainId([u8; 32]); @@ -45,6 +57,22 @@ fn hash_genesis_constants( } impl ChainId { + /// Compute a ChainId from network configuration parameters. + /// + /// This function deterministically generates a unique ChainId by hashing + /// together all the parameters that define a Mina network's behavior. + /// Any change to these parameters will result in a different ChainId. + /// + /// # Arguments + /// * `constraint_system_digests` - MD5 hashes of the zkSNARK circuits + /// * `genesis_state_hash` - Hash of the network's genesis block + /// * `genesis_constants` - Protocol timing and behavior constants + /// * `protocol_transaction_version` - Transaction format version + /// * `protocol_network_version` - Network protocol version + /// * `tx_max_pool_size` - Maximum transaction pool size + /// + /// # Returns + /// A new ChainId uniquely identifying this network configuration pub fn compute( constraint_system_digests: &[Md5], genesis_state_hash: &StateHash, @@ -68,7 +96,15 @@ impl ChainId { ChainId(hasher.finalize().try_into().unwrap()) } - /// Computes shared key for libp2p Pnet protocol. + /// Computes the preshared key for libp2p private network protocol. + /// + /// This creates a network-specific encryption key that ensures nodes + /// can only communicate with other nodes on the same Mina network. + /// The key is derived from the ChainId to automatically separate + /// different networks at the transport layer. + /// + /// # Returns + /// A 32-byte preshared key for libp2p Pnet pub fn preshared_key(&self) -> [u8; 32] { let mut hasher = Blake2b256::default(); hasher.update(b"/coda/0.0.1/"); @@ -79,10 +115,22 @@ impl ChainId { psk_fixed } + /// Convert the ChainId to a hexadecimal string representation. + /// + /// # Returns + /// A 64-character hex string (32 bytes * 2 hex chars per byte) pub fn to_hex(&self) -> String { hex::encode(self.0) } + /// Parse a ChainId from a hexadecimal string. + /// + /// # Arguments + /// * `s` - A 64-character hex string representing the ChainId + /// + /// # Returns + /// * `Ok(ChainId)` if the string is valid + /// * `Err(hex::FromHexError)` if the string is malformed pub fn from_hex(s: &str) -> Result { let h = hex::decode(s)?; let bs = h[..32] @@ -91,6 +139,16 @@ impl ChainId { Ok(ChainId(bs)) } + /// Create a ChainId from raw bytes. + /// + /// # Arguments + /// * `bytes` - A byte slice containing at least 32 bytes + /// + /// # Returns + /// A ChainId using the first 32 bytes of the input + /// + /// # Panics + /// Panics if the input contains fewer than 32 bytes pub fn from_bytes(bytes: &[u8]) -> ChainId { let mut arr = [0u8; 32]; arr.copy_from_slice(&bytes[..32]); @@ -147,11 +205,25 @@ impl Debug for ChainId { } } +/// The ChainId for the Mina devnet. +/// +/// This ChainId identifies the development/testing network and is computed +/// from devnet's specific configuration parameters. Nodes must use this +/// ChainId to participate in the devnet. +/// +/// Hex representation: `29936104443aaf264a7f0192ac64b1c7173198c1ed404c1bcff5e562e05eb7f6` pub const DEVNET_CHAIN_ID: ChainId = ChainId([ 0x29, 0x93, 0x61, 0x04, 0x44, 0x3a, 0xaf, 0x26, 0x4a, 0x7f, 0x01, 0x92, 0xac, 0x64, 0xb1, 0xc7, 0x17, 0x31, 0x98, 0xc1, 0xed, 0x40, 0x4c, 0x1b, 0xcf, 0xf5, 0xe5, 0x62, 0xe0, 0x5e, 0xb7, 0xf6, ]); +/// The ChainId for the Mina mainnet. +/// +/// This ChainId identifies the production Mina blockchain and is computed +/// from mainnet's specific configuration parameters. Nodes must use this +/// ChainId to participate in the mainnet. +/// +/// Hex representation: `a7351abc7ddf2ea92d1b38cc8e636c271c1dfd2c081c637f62ebc2af34eb7cc1` pub const MAINNET_CHAIN_ID: ChainId = ChainId([ 0xa7, 0x35, 0x1a, 0xbc, 0x7d, 0xdf, 0x2e, 0xa9, 0x2d, 0x1b, 0x38, 0xcc, 0x8e, 0x63, 0x6c, 0x27, 0x1c, 0x1d, 0xfd, 0x2c, 0x08, 0x1c, 0x63, 0x7f, 0x62, 0xeb, 0xc2, 0xaf, 0x34, 0xeb, 0x7c, 0xc1, diff --git a/core/src/constants.rs b/core/src/constants.rs index 070adfbdd..a90129ead 100644 --- a/core/src/constants.rs +++ b/core/src/constants.rs @@ -14,24 +14,196 @@ pub fn constraint_constants() -> &'static ConstraintConstants { NetworkConfig::global().constraint_constants } +/// Constants that define fork-specific blockchain state. +/// +/// Fork constants specify the blockchain state at which a protocol upgrade or fork occurred. +/// These are used to handle protocol changes and ensure compatibility across network upgrades. #[derive(Clone, Debug)] pub struct ForkConstants { + /// Hash of the blockchain state at the fork point pub state_hash: Fp, + + /// Blockchain length (number of blocks) at the fork point pub blockchain_length: u32, + + /// Global slot number since genesis at the fork point pub global_slot_since_genesis: u32, } +/// Protocol constraint constants that define core blockchain behavior. +/// +/// These constants configure fundamental aspects of the Mina protocol including consensus, +/// transaction processing, economic parameters, and ledger structure. They are compile-time +/// parameters that must be consistent across all nodes in a network. +/// +/// ## Consensus and Timing Parameters +/// +/// The consensus mechanism relies on slot-based timing where blocks are produced in discrete +/// time slots. The timing hierarchy is: +/// - **Slots**: Basic time units for block production +/// - **Sub-windows**: Groups of slots within an epoch +/// - **Windows**: Collections of sub-windows that define epoch structure +/// - **Epochs**: Complete consensus periods +/// +/// ## Economic Parameters +/// +/// The protocol defines economic incentives through fees and rewards: +/// - **Coinbase rewards**: Paid to block producers for successful blocks +/// - **Account creation fees**: Required to create new accounts on the ledger +/// - **Supercharged rewards**: Multiplier for enhanced block producer rewards +/// +/// ## Ledger and Transaction Structure +/// +/// The ledger uses a Merkle tree structure for efficient verification: +/// - **Ledger depth**: Determines the maximum number of accounts (2^depth) +/// - **Transaction capacity**: Limits transactions per block for performance +/// - **Pending coinbase**: Manages delayed coinbase payouts +/// +/// ## Usage Example +/// +/// ```rust +/// use openmina_core::constants::constraint_constants; +/// +/// // Access global constraint constants +/// let constants = constraint_constants(); +/// +/// // Calculate slots per window +/// let slots_per_window = constants.sub_windows_per_window; +/// println!("Sub-windows per window: {}", slots_per_window); +/// +/// // Get block timing +/// let block_time_ms = constants.block_window_duration_ms; +/// println!("Block time: {}ms", block_time_ms); +/// +/// // Check economic parameters +/// let coinbase_reward = constants.coinbase_amount; +/// let creation_fee = constants.account_creation_fee; +/// println!("Coinbase: {} nanomina, Account fee: {} nanomina", +/// coinbase_reward, creation_fee); +/// ``` +/// +/// ## Network Differences +/// +/// While most constraint constants are identical across networks, some parameters +/// may differ between mainnet and testnets for development purposes. +/// +/// Related OCaml implementation: +/// Protocol specification: #[derive(Clone, Debug)] pub struct ConstraintConstants { + /// Number of sub-windows that make up a complete window. + /// + /// Used in the consensus mechanism to structure epoch timing. Combined with + /// `slots_per_sub_window` from protocol constants, this determines the total + /// slots per window: `slots_per_window = slots_per_sub_window × sub_windows_per_window`. + /// + /// **Value**: 11 (both mainnet and devnet) pub sub_windows_per_window: u64, + + /// Depth of the account ledger Merkle tree. + /// + /// This determines the maximum number of accounts that can be stored in the ledger: + /// `max_accounts = 2^ledger_depth`. The depth affects proof sizes and verification time. + /// A larger depth allows more accounts but increases computational overhead. + /// + /// **Value**: 35 (supports ~34 billion accounts) + /// **Usage**: Account addressing, sparse ledger proofs, zkSNARK constraints pub ledger_depth: u64, + + /// Number of blocks to delay before SNARK work becomes available. + /// + /// This creates a buffer period between when a block is produced and when + /// the associated SNARK work can be included in subsequent blocks. This delay + /// helps ensure fair distribution of SNARK work opportunities. + /// + /// **Value**: 2 blocks + /// **Usage**: SNARK work scheduling, proof marketplace timing pub work_delay: u64, + + /// Duration of each block production slot in milliseconds. + /// + /// This is the fundamental time unit for the consensus protocol. Block producers + /// attempt to create blocks during their assigned slots. The duration affects + /// network synchronization requirements and transaction confirmation times. + /// + /// **Value**: 180,000ms (3 minutes) + /// **Usage**: Consensus timing, slot calculations, network synchronization pub block_window_duration_ms: u64, + + /// Log₂ of the maximum number of transactions per block. + /// + /// The actual transaction capacity is `2^transaction_capacity_log_2`. This logarithmic + /// representation is used because the value directly affects zkSNARK circuit constraints. + /// Higher capacity allows more transactions but increases block processing time. + /// + /// Corresponds to `transaction_capacity` in the protocol specification, which defines + /// the maximum transactions per block (represented as `two_to_the`). + /// + /// **Value**: 7 (supports 2^7 = 128 transactions per block) + /// **Usage**: Transaction pool management, block construction, circuit constraints pub transaction_capacity_log_2: u64, + + /// Number of confirmations before coinbase reward is spendable. + /// + /// Coinbase rewards are not immediately spendable and require a certain number + /// of block confirmations before they can be used. This parameter defines the + /// depth of the pending coinbase Merkle tree structure used to track these + /// delayed rewards until they mature. + /// + /// **Value**: 5 (coinbase rewards require 5 block confirmations) + /// **Usage**: Coinbase reward management, staged ledger operations, reward maturity pub pending_coinbase_depth: usize, + + /// Block reward amount in nanomina (10⁻⁹ MINA). + /// + /// This is the base reward paid to block producers for successfully creating a block. + /// The amount is specified in nanomina, where 1 MINA = 10⁹ nanomina. Block producers + /// may receive additional rewards through the supercharged coinbase mechanism. + /// + /// **Value**: 720,000,000,000 nanomina (720 MINA) + /// **Usage**: Block producer rewards, economic incentives, reward calculations pub coinbase_amount: u64, + + /// Multiplier for supercharged coinbase rewards. + /// + /// Supercharged rewards were designed to provide double block rewards (factor of 2) + /// to block producers staking with unlocked tokens during the early mainnet period + /// following the 2021 launch. This mechanism incentivized participation and orderly + /// markets after mainnet launch. + /// + /// **Historical values**: + /// - Original mainnet: 2 (double rewards for unlocked tokens) + /// - Berkeley hardfork (June 2024): 1 (supercharged rewards removed via MIP1) + /// + /// The removal was decided by community vote on January 1, 2023, as proposed by + /// community member Gareth Davies. This change ensures uniform rewards for all + /// tokens and reduces inflation, promoting a sustainable economic model. + /// + /// **References**: + /// - Berkeley Upgrade: + /// - Supercharged Rewards Removal: + /// - Original Proposal: + /// + /// **Usage**: Enhanced reward calculations, incentive mechanisms pub supercharged_coinbase_factor: u64, + + /// Fee required to create a new account in nanomina. + /// + /// When a transaction creates a new account that doesn't exist on the ledger, + /// this fee is charged in addition to the transaction fee. This prevents + /// spam account creation and manages ledger growth. + /// + /// **Value**: 1,000,000,000 nanomina (1 MINA) + /// **Usage**: Account creation, transaction validation, fee calculations pub account_creation_fee: u64, + + /// Optional fork constants defining a protocol upgrade point. + /// + /// When present, these constants specify the blockchain state at which a protocol + /// fork or upgrade occurred. This allows the protocol to handle transitions between + /// different versions while maintaining consensus. + /// + /// **Usage**: Protocol upgrades, compatibility handling, genesis configuration pub fork: Option, } #[derive(Clone, Debug, BinProtWrite)] diff --git a/core/src/network.rs b/core/src/network.rs index 53d670e0d..aa33f59cf 100644 --- a/core/src/network.rs +++ b/core/src/network.rs @@ -1,3 +1,199 @@ +//! # Network Configuration +//! +//! This module provides network-specific configurations for different Mina blockchain networks. +//! OpenMina supports multiple networks, each with distinct parameters that define their behavior, +//! security settings, and connectivity requirements. +//! +//! ## Overview +//! +//! Network configurations define all the parameters needed to participate in a specific Mina +//! blockchain network. Each network configuration includes: +//! +//! - **Chain ID**: Unique identifier computed from network parameters +//! - **Protocol Constants**: Timing, fees, and consensus parameters +//! - **Circuit Configurations**: zkSNARK proving keys and circuit hashes +//! - **Seed Peers**: Initial connection points for network discovery +//! - **Cryptographic Parameters**: Signature schemes and hash functions +//! +//! ## Supported Networks +//! +//! ### Devnet +//! +//! Devnet is the primary development and testing network for Mina Protocol: +//! +//! - **Network ID**: `TESTNET` (0x00) +//! - **Purpose**: Development, testing, and integration +//! - **Characteristics**: Regular resets, development-friendly parameters +//! - **Seed Nodes**: o1Labs-operated infrastructure +//! - **Chain ID**: `29936104443aaf264a7f0192ac64b1c7173198c1ed404c1bcff5e562e05eb7f6` +//! +//! ### Mainnet +//! +//! Mainnet is the production Mina blockchain where real value transactions occur: +//! +//! - **Network ID**: `MAINNET` (0x01) +//! - **Purpose**: Production blockchain operations +//! - **Characteristics**: Stable, long-term operation with production security +//! - **Seed Nodes**: Community-operated, distributed infrastructure +//! - **Chain ID**: `a7351abc7ddf2ea92d1b38cc8e636c271c1dfd2c081c637f62ebc2af34eb7cc1` +//! +//! ## Usage +//! +//! ### Basic Configuration +//! +//! Network configuration must be initialized once at application startup: +//! +//! ```rust +//! use openmina_core::network::NetworkConfig; +//! +//! # fn main() -> Result<(), String> { +//! // Initialize for devnet +//! NetworkConfig::init("devnet")?; +//! +//! // Or initialize for mainnet (don't do both in the same program) +//! // NetworkConfig::init("mainnet")?; +//! # Ok(()) +//! # } +//! ``` +//! +//! ### Accessing Configuration +//! +//! After initialization, access the global configuration: +//! +//! ```rust +//! use openmina_core::network::NetworkConfig; +//! # NetworkConfig::init("devnet").unwrap(); +//! +//! let config = NetworkConfig::global(); +//! println!("Connected to: {}", config.name); +//! println!("Network ID: {:?}", config.network_id); +//! ``` +//! +//! ### Chain ID Usage +//! +//! Chain IDs uniquely identify network configurations: +//! +//! ```rust +//! use openmina_core::{ChainId, DEVNET_CHAIN_ID, MAINNET_CHAIN_ID}; +//! +//! // Use predefined chain IDs +//! let devnet_id = DEVNET_CHAIN_ID; +//! println!("Devnet Chain ID: {}", devnet_id.to_hex()); +//! +//! // Generate preshared key for libp2p +//! let psk = devnet_id.preshared_key(); +//! println!("PSK length: {}", psk.len()); +//! ``` +//! +//! ### Network Differences +//! +//! Networks differ in several key parameters: +//! +//! | Parameter | Devnet | Mainnet | +//! |-----------|--------|---------| +//! | Signature Prefix | `CodaSignature` | `MinaSignatureMainnet` | +//! | zkApp Hash Param | `TestnetZkappBody` | `MainnetZkappBody` | +//! | Network ID | `TESTNET` (0x00) | `MAINNET` (0x01) | +//! +//! ## Implementation Details +//! +//! ### Chain ID Computation +//! +//! Chain IDs are computed deterministically from network parameters: +//! +//! ```rust +//! use openmina_core::ChainId; +//! use mina_p2p_messages::v2::{StateHash, UnsignedExtendedUInt32StableV1}; +//! # use openmina_core::constants::PROTOCOL_CONSTANTS; +//! # use openmina_core::network::devnet; +//! # use openmina_core::constants::{PROTOCOL_TRANSACTION_VERSION, PROTOCOL_NETWORK_VERSION, TX_POOL_MAX_SIZE}; +//! +//! // Example computation (simplified) +//! let genesis_state_hash: StateHash = "3NL93SipJfAMNDBRfQ8Uo8LPovC74mnJZfZYB5SK7mTtkL72dsPx".parse().unwrap(); +//! let chain_id = ChainId::compute( +//! &devnet::CONSTRAINT_SYSTEM_DIGESTS, +//! &genesis_state_hash, +//! &PROTOCOL_CONSTANTS, +//! PROTOCOL_TRANSACTION_VERSION, +//! PROTOCOL_NETWORK_VERSION, +//! &UnsignedExtendedUInt32StableV1::from(TX_POOL_MAX_SIZE), +//! ); +//! ``` +//! +//! ### Circuit Configuration +//! +//! Each network specifies its zkSNARK circuits: +//! +//! ```rust +//! use openmina_core::network::{NetworkConfig, CircuitsConfig}; +//! # NetworkConfig::init("devnet").unwrap(); +//! +//! let config = NetworkConfig::global(); +//! let circuits = config.circuits_config; +//! println!("Circuit directory: {}", circuits.directory_name); +//! println!("Transaction step circuit: {}", circuits.step_transaction_gates); +//! ``` +//! +//! ## Best Practices +//! +//! ### Development +//! +//! 1. **Use Devnet**: Always develop and test on devnet first +//! 2. **Verify Chain ID**: Ensure your node connects to the expected network +//! 3. **Check Peer Connectivity**: Verify seed peers are accessible +//! 4. **Monitor Network Changes**: Devnet may reset; monitor announcements +//! +//! ### Production +//! +//! 1. **Use Mainnet**: Only use mainnet for production applications +//! 2. **Diversify Peers**: Don't rely on single seed peer providers +//! 3. **Validate Configuration**: Double-check network parameters before deployment +//! 4. **Monitor Chain ID**: Ensure consistency across your infrastructure +//! +//! ### Configuration Management +//! +//! 1. **Initialize Early**: Set network configuration at application startup +//! 2. **Handle Errors**: Properly handle initialization failures +//! 3. **Log Network Info**: Log which network you're connecting to +//! 4. **Avoid Double Init**: Don't attempt to reinitialize network configuration +//! +//! ## Troubleshooting +//! +//! ### Common Issues +//! +//! **Wrong Network**: If you see unexpected behavior, verify the chain ID matches your intended network: +//! +//! ```rust +//! use openmina_core::network::NetworkConfig; +//! use openmina_core::{DEVNET_CHAIN_ID, MAINNET_CHAIN_ID}; +//! # NetworkConfig::init("devnet").unwrap(); +//! +//! // Check current network +//! let config = NetworkConfig::global(); +//! println!("Network: {}", config.name); +//! println!("Network ID: {:?}", config.network_id); +//! +//! // Verify chain ID +//! let expected_chain_id = match config.name { +//! "devnet" => DEVNET_CHAIN_ID, +//! "mainnet" => MAINNET_CHAIN_ID, +//! _ => panic!("Unknown network"), +//! }; +//! println!("Chain ID: {}", expected_chain_id.to_hex()); +//! ``` +//! +//! **Peer Connection Failures**: Check that seed peers are accessible from your network location. +//! +//! **Circuit Mismatches**: Ensure your proving keys match the network's constraint system digests. +//! +//! **Double Initialization**: Only call `NetworkConfig::init()` once per application instance. +//! +//! ## OCaml Compatibility +//! +//! OpenMina's network configurations maintain compatibility with the OCaml Mina implementation. +//! Related OCaml configuration files can be found at: +//! + use once_cell::sync::OnceCell; use poseidon::hash::{ legacy, @@ -6,46 +202,95 @@ use poseidon::hash::{ use crate::constants::ConstraintConstants; -// From mina-signer, to avoid dependency +/// Network identifier used to distinguish between different Mina networks. +/// This enum is derived from mina-signer to avoid dependency. #[derive(Debug, Clone)] pub enum NetworkId { - /// Id for all testnets + /// Identifier for all testnet networks (devnet, etc.) + /// Used for development and testing purposes TESTNET = 0x00, - /// Id for mainnet + /// Identifier for the main production network + /// Used for the live Mina blockchain MAINNET = 0x01, } +/// Complete network configuration for a Mina network. +/// +/// This struct contains all the network-specific parameters needed to configure +/// a node for a particular Mina network (mainnet, devnet, etc.). #[derive(Debug)] pub struct NetworkConfig { + /// Human-readable network name (e.g., "mainnet", "devnet") pub name: &'static str, + + /// Network identifier distinguishing mainnet from testnets pub network_id: NetworkId, + + /// Poseidon hash parameter for current signature scheme pub signature_prefix: &'static poseidon::hash::LazyParam, + + /// Poseidon hash parameter for legacy signature compatibility pub legacy_signature_prefix: &'static poseidon::hash::LazyParam, + + /// Hash parameter for zkApp account updates pub account_update_hash_param: &'static poseidon::hash::LazyParam, + + /// MD5 digests of constraint systems used for circuit verification. + /// Contains digests for: [transaction-merge, transaction-base, blockchain-step] pub constraint_system_digests: &'static [[u8; 16]; 3], + + /// List of default seed peers for initial network bootstrapping pub default_peers: Vec<&'static str>, + + /// Circuit-specific configuration including proving key filenames pub circuits_config: &'static CircuitsConfig, + + /// Protocol constants that define blockchain behavior pub constraint_constants: &'static ConstraintConstants, } +/// Configuration for zkSNARK circuits and proving keys. +/// +/// This struct contains the filenames and directory information for all +/// the proving keys required for different types of SNARK proofs. #[derive(Debug)] pub struct CircuitsConfig { + /// Directory name containing the proving keys for this network pub directory_name: &'static str, + /// Proving key for transaction SNARK step circuit pub step_transaction_gates: &'static str, + + /// Proving key for transaction SNARK wrap circuit pub wrap_transaction_gates: &'static str, + + /// Proving key for transaction merge step circuit pub step_merge_gates: &'static str, + + /// Proving key for blockchain step circuit pub step_blockchain_gates: &'static str, + + /// Proving key for blockchain wrap circuit pub wrap_blockchain_gates: &'static str, + + /// Proving key for optional signed transaction step circuit pub step_transaction_opt_signed_opt_signed_gates: &'static str, + + /// Proving key for optional signed transaction circuit pub step_transaction_opt_signed_gates: &'static str, + + /// Proving key for proved transaction circuit pub step_transaction_proved_gates: &'static str, } static CONFIG: OnceCell = OnceCell::new(); impl NetworkConfig { + /// Get the globally configured network configuration. + /// + /// If no network has been explicitly initialized via `init()`, this will + /// default to the devnet configuration and log a warning. pub fn global() -> &'static Self { CONFIG.get_or_init(|| { let config = Self::default_config(); @@ -59,6 +304,17 @@ impl NetworkConfig { }) } + /// Initialize the global network configuration. + /// + /// This must be called once at application startup to set the network + /// configuration. Subsequent calls will return an error. + /// + /// # Arguments + /// * `network_name` - Name of the network ("devnet" or "mainnet") + /// + /// # Returns + /// * `Ok(())` if initialization succeeded + /// * `Err(String)` if the network name is unknown or already initialized pub fn init(network_name: &str) -> Result<(), String> { let config = match network_name { "devnet" => Self::devnet_config(), @@ -106,18 +362,52 @@ impl NetworkConfig { } } -// Network constants - +/// Network-specific constants and configurations. +/// +/// This module contains the hardcoded parameters for each supported Mina network. +/// Each network has its own set of: +/// - Constraint system digests (for circuit verification) +/// - Protocol constants (timing, fees, etc.) +/// - Default peer addresses +/// - Circuit configurations +/// - Fork information +/// +/// Devnet network configuration. +/// +/// Devnet is the primary development and testing network for Mina Protocol. +/// It uses testnet parameters and is regularly reset for testing new features. +/// +/// Key characteristics: +/// - Uses TESTNET network ID +/// - Has development-friendly timing parameters +/// - Connects to o1Labs-hosted seed nodes +/// - Uses CodaSignature prefix for compatibility +/// +/// Related OCaml configuration: pub mod devnet { use super::{CircuitsConfig, NetworkId}; use crate::constants::{ConstraintConstants, ForkConstants}; use mina_hasher::Fp; + /// Network identifier for devnet (uses testnet ID) pub const NETWORK_ID: NetworkId = NetworkId::TESTNET; + + /// Human-readable name for this network pub const NAME: &str = "devnet"; + + /// Signature prefix used for Poseidon hashing (legacy compatibility) pub const SIGNATURE_PREFIX: &str = "CodaSignature"; + + /// Hash parameter for zkApp account updates on testnet pub const ACCOUNT_UPDATE_HASH_PARAM: &str = "TestnetZkappBody"; + /// MD5 digests of the constraint systems used for devnet circuit verification. + /// + /// These digests must match the circuits used by the network to ensure + /// compatibility. The array contains digests for: + /// - Index 0: transaction-merge circuit + /// - Index 1: transaction-base circuit + /// - Index 2: blockchain-step circuit pub const CONSTRAINT_SYSTEM_DIGESTS: [[u8; 16]; 3] = [ // transaction-merge [ @@ -179,6 +469,11 @@ pub mod devnet { "step-step-proving-key-transaction-snark-proved-4-0cafcbc6dffccddbc82f8c2519c16341", }; + /// Returns the list of default seed peers for devnet bootstrapping. + /// + /// These are o1Labs-operated seed nodes that help new nodes discover + /// the devnet network. DNS addresses are preferred over IP addresses + /// for resilience to infrastructure changes. pub fn default_peers() -> Vec<&'static str> { vec![ "/dns4/seed-1.devnet.gcp.o1test.net/tcp/10003/p2p/12D3KooWAdgYL6hv18M3iDBdaK1dRygPivSfAfBNDzie6YqydVbs", @@ -191,16 +486,45 @@ pub mod devnet { } } +/// Mainnet network configuration. +/// +/// Mainnet is the production Mina blockchain network where real value transactions +/// occur. It uses production-grade parameters and connects to a distributed +/// set of community and foundation-operated seed nodes. +/// +/// Key characteristics: +/// - Uses MAINNET network ID +/// - Has production timing and security parameters +/// - Connects to diverse community-operated seed nodes +/// - Uses MinaSignatureMainnet prefix +/// +/// Related OCaml configuration: pub mod mainnet { use super::{CircuitsConfig, NetworkId}; use crate::constants::{ConstraintConstants, ForkConstants}; use mina_hasher::Fp; + /// Network identifier for mainnet (uses mainnet ID) pub const NETWORK_ID: NetworkId = NetworkId::MAINNET; + + /// Human-readable name for this network pub const NAME: &str = "mainnet"; + + /// Signature prefix used for Poseidon hashing on mainnet pub const SIGNATURE_PREFIX: &str = "MinaSignatureMainnet"; + + /// Hash parameter for zkApp account updates on mainnet pub const ACCOUNT_UPDATE_HASH_PARAM: &str = "MainnetZkappBody"; + /// MD5 digests of the constraint systems used for mainnet circuit verification. + /// + /// These digests must match the circuits used by the network to ensure + /// compatibility. The array contains digests for: + /// - Index 0: transaction-merge circuit + /// - Index 1: transaction-base circuit + /// - Index 2: blockchain-step circuit + /// + /// Note: These differ from devnet to ensure network separation. pub const CONSTRAINT_SYSTEM_DIGESTS: [[u8; 16]; 3] = [ // transaction-merge [ @@ -262,6 +586,12 @@ pub mod mainnet { "step-step-proving-key-transaction-snark-proved-4-7bb3855dfcf14da4b3ffa7091adc0143", }; + /// Returns the list of default seed peers for mainnet bootstrapping. + /// + /// These include a diverse set of community-operated and foundation-operated + /// seed nodes to ensure decentralization and network resilience. The commented + /// DNS addresses show the original peer addresses, while the active list uses + /// IP addresses for immediate connectivity. pub fn default_peers() -> Vec<&'static str> { vec![ // /dns4/mina-seed.etonec.com/tcp/8302/p2p/12D3KooWKQ1YVtqZFzxDmSw8RASCPZpDCQBywnFz76RbrvZCXk5T diff --git a/macros/Cargo.toml b/macros/Cargo.toml index c94480878..423dd3d76 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -18,3 +18,4 @@ openmina-core = { path = "../core" } tracing = "0.1.40" rust-format = { version = "0.3", features = ["token_stream"] } anyhow = "1.0.81" +redux = { workspace = true } diff --git a/macros/src/action_event.md b/macros/src/action_event.md index 388b48849..0facc6d64 100644 --- a/macros/src/action_event.md +++ b/macros/src/action_event.md @@ -5,35 +5,53 @@ Derives `[openmina_core::ActionEvent]` trait implementation for action. For action containers, it simply delegates to inner actions. ```rust -# use openmina_core::ActionEvent; -# +use openmina_core::{ActionEvent, log::EventContext}; + +struct MockContext; + +impl EventContext for MockContext { + fn timestamp(&self) -> redux::Timestamp { redux::Timestamp::global_now() } + fn time(&self) -> &dyn tracing::Value { &"test" } + fn node_id(&self) -> &dyn tracing::Value { &"test_node" } + fn log_node_id(&self) -> bool { true } +} + +let context = MockContext; + #[derive(ActionEvent)] enum ActionContainer { SubAction1(Action1), } + #[derive(ActionEvent)] enum Action1 { Init, Done, } -ActionContainer::SubAction1(Action1::Init).action_event(context); +ActionContainer::SubAction1(Action1::Init).action_event(&context); ``` ```rust +use openmina_core::{ActionEvent, log::EventContext}; + +enum ActionContainer { SubAction1(Action1) } + +enum Action1 { Init, Done } + impl ActionEvent for ActionContainer { fn action_event(&self, context: &T) - where T: ActionContext + where T: EventContext { match self { - ActionContainer(action) => action.action_event(context), + ActionContainer::SubAction1(action) => action.action_event(context), } } } impl ActionEvent for Action1 { fn action_event(&self, context: &T) - where T: ActionContext + where T: EventContext { match self { Action1::Init => openmina_core::action_debug!(context), @@ -50,7 +68,9 @@ overriden by using `#[action_event(level = ...)]` attribute. Also, actions that names ends with `Error` or `Warn` will be traced with `warn` level. ```rust -#[derive(openmina_core::ActionEvent)] +use openmina_core::ActionEvent; + +#[derive(ActionEvent)] #[action_event(level = trace)] pub enum Action { ActionDefaultLevel, @@ -62,12 +82,15 @@ pub enum Action { ``` ```rust -impl openmina_core::ActionEvent for Action { +use openmina_core::{ActionEvent, log::EventContext}; + +enum Action { ActionDefaultLevel, ActionOverrideLevel, ActionWithError, ActionWithWarn } + +impl ActionEvent for Action { fn action_event(&self, context: &T) where - T: openmina_core::log::EventContext, + T: EventContext, { - #[allow(unused_variables)] match self { Action::ActionDefaultLevel => openmina_core::action_trace!(context), Action::ActionOverrideLevel => openmina_core::action_warn!(context), @@ -84,7 +107,8 @@ If an action has doc-comment, its first line will be used for `summary` field of tracing events for the action. ```rust -#[derive(openmina_core::ActionEvent)] +# use openmina_core::ActionEvent; +#[derive(ActionEvent)] pub enum Action { Unit, /// documentation @@ -98,10 +122,14 @@ pub enum Action { ``` ```rust -impl openmina_core::ActionEvent for Action { +use openmina_core::{ActionEvent, log::EventContext}; + +enum Action { Unit, UnitWithDoc, UnitWithMultilineDoc } + +impl ActionEvent for Action { fn action_event(&self, context: &T) where - T: openmina_core::log::EventContext, + T: EventContext, { match self { Action::Unit => openmina_core::action_debug!(context), @@ -118,7 +146,9 @@ Certain fields can be added to the tracing event, using `#[action_event(fields(...))]` attribute. ```rust -#[derive(openmina_core::ActionEvent)] +use openmina_core::ActionEvent; + +#[derive(ActionEvent)] pub enum Action { NoFields { f1: bool }, #[action_event(fields(f1))] @@ -133,10 +163,18 @@ pub enum Action { ``` ```rust -impl openmina_core::ActionEvent for Action { +use openmina_core::{ActionEvent, log::EventContext}; +enum Action { + NoFields { f1: bool }, + Field { f1: bool }, + FieldWithName { f1: bool }, + DebugField { f1: bool }, + DisplayField { f1: bool } +} +impl ActionEvent for Action { fn action_event(&self, context: &T) where - T: openmina_core::log::EventContext, + T: EventContext, { match self { Action::NoFields { f1 } => openmina_core::action_debug!(context), @@ -156,7 +194,13 @@ a field's enum variant), logging can be delegated to a function implementing that logic. ```rust -#[derive(openmina_core::ActionEvent)] +use openmina_core::ActionEvent; + +fn foo(_: &T) {} + +fn bar(_: &T, _: &bool) {} + +#[derive(ActionEvent)] pub enum Action { #[action_event(expr(foo(context)))] Unit, @@ -166,10 +210,18 @@ pub enum Action { ``` ```rust -impl openmina_core::ActionEvent for Action { +use openmina_core::{ActionEvent, log::EventContext}; + +fn foo(_: &T) {} + +fn bar(_: &T, _: &bool) {} + +enum Action { Unit, Named { f1: bool } } + +impl ActionEvent for Action { fn action_event(&self, context: &T) where - T: openmina_core::log::EventContext, + T: EventContext, { #[allow(unused_variables)] match self { diff --git a/mina-p2p-messages/src/rpc.rs b/mina-p2p-messages/src/rpc.rs index d0484ced5..63ff91090 100644 --- a/mina-p2p-messages/src/rpc.rs +++ b/mina-p2p-messages/src/rpc.rs @@ -186,9 +186,9 @@ mina_rpc!(GetEpochLedgerV2, "get_epoch_ledger", 2, LedgerHashV1, RpcResult Driver<'cluster> { } } - /// Waits for a specific event that satisfies the given predicate, executing all events encountered along the way. + /// Waits for a specific event that satisfies the given predicate, executing + /// all events encountered along the way. /// /// # Arguments /// /// * `duration` - Maximum time to wait for the event - /// * `f` - A predicate function that takes a node ID, event, and state, returning true when the desired event is found + /// * `f` - A predicate function that takes a node ID, event, and state, + /// returning true when the desired event is found /// /// # Returns /// /// Returns a Result containing: - /// * `Some((node_id, event))` - If an event satisfying the predicate is found before the timeout + /// * `Some((node_id, event))` - If an event satisfying the predicate is + /// found before the timeout /// * `None` - If no matching event is found within the timeout period /// /// # Example /// /// ```no_run + /// # use anyhow::Result; + /// # use node::event_source::Event; + /// # use openmina_node_testing::scenarios::Driver; + /// # use std::time::Duration; + /// # async fn example(driver: &mut Driver<'_>) -> Result<()> { /// driver.wait_for(Duration::from_secs(5), |node_id, event, state| { - /// matches!(event, Event::BlockReceived { .. }) + /// matches!(event, Event::P2p(_)) /// }).await?; + /// # Ok(()) + /// # } /// ``` pub async fn wait_for( &mut self, diff --git a/p2p/testing/src/cluster.rs b/p2p/testing/src/cluster.rs index c6c015992..ef2bfd786 100644 --- a/p2p/testing/src/cluster.rs +++ b/p2p/testing/src/cluster.rs @@ -231,10 +231,11 @@ impl Default for Ports { /// Declares a shared storage for ports. /// /// ``` +/// use p2p_testing::ports_store; /// ports_store!(GLOBAL_PORTS); /// /// #[tokio::test] -/// fn test1() { +/// async fn test1() { /// let cluster = ClusterBuilder::default() /// .ports(GLOBAL_PORTS.take(20).await.expect("enough ports")) /// .start() diff --git a/website/docs/developers/network-configuration.md b/website/docs/developers/network-configuration.md new file mode 100644 index 000000000..a29d1a854 --- /dev/null +++ b/website/docs/developers/network-configuration.md @@ -0,0 +1,77 @@ +--- +title: Network Configuration +description: Understanding how OpenMina configures different Mina networks +sidebar_position: 6 +--- + +# Network Configuration + +OpenMina supports multiple Mina networks (mainnet, devnet) with distinct +configurations that define their behavior, security parameters, and +connectivity. This page provides an overview and links to the detailed API +documentation. + +## Overview + +Network configurations in OpenMina define all the parameters needed to +participate in a specific Mina blockchain network, including: + +- **Chain ID**: Unique identifier computed from network parameters +- **Protocol Constants**: Timing, fees, and consensus parameters +- **Circuit Configurations**: zkSNARK proving keys and circuit hashes +- **Seed Peers**: Initial connection points for network discovery +- **Cryptographic Parameters**: Signature schemes and hash functions + +## API Documentation + +For comprehensive documentation including usage examples, implementation +details, and troubleshooting guides, see the Rust API documentation: + +**[📚 Network Configuration API Documentation](https://o1-labs.github.io/openmina/api-docs/openmina_core/network/index.html)** + +The API documentation includes: + +- **Complete Usage Examples**: Working code snippets for all features +- **Network Differences**: Detailed comparison between mainnet and devnet +- **Chain ID Computation**: How unique network identifiers are generated +- **Circuit Configuration**: zkSNARK proving keys and verification +- **Best Practices**: Guidelines for development and production use +- **Troubleshooting**: Common issues and solutions + +## Quick Start + +Here's a basic example of how to initialize network configuration: + +```rust +use openmina_core::network::NetworkConfig; + +// Initialize for devnet +NetworkConfig::init("devnet")?; + +// Access the configuration +let config = NetworkConfig::global(); +println!("Connected to: {}", config.name); +``` + +## Supported Networks + +### Devnet + +- **Purpose**: Development and testing +- **Network ID**: TESTNET (0x00) +- **Chain ID**: + `29936104443aaf264a7f0192ac64b1c7173198c1ed404c1bcff5e562e05eb7f6` + +### Mainnet + +- **Purpose**: Production blockchain +- **Network ID**: MAINNET (0x01) +- **Chain ID**: + `a7351abc7ddf2ea92d1b38cc8e636c271c1dfd2c081c637f62ebc2af34eb7cc1` + +## Further Reading + +- [Architecture Overview](./architecture.md) +- [Network Configuration API Documentation](https://o1-labs.github.io/openmina/api-docs/openmina_core/network/index.html) +- [Chain ID API Documentation](https://o1-labs.github.io/openmina/api-docs/openmina_core/chain_id/index.html) +- [OCaml Mina Configuration](https://github.com/MinaProtocol/mina/tree/compatible/src/config) diff --git a/website/sidebars.ts b/website/sidebars.ts index e0cc29321..a0a486b7c 100644 --- a/website/sidebars.ts +++ b/website/sidebars.ts @@ -76,6 +76,13 @@ const sidebars: SidebarsConfig = { 'developers/why-openmina', ], }, + { + type: 'category', + label: 'Configuration', + items: [ + 'developers/network-configuration', + ], + }, ], // Sidebar for researchers - focus on protocol and cryptography From 771d88b80795d06b06d99317736c3c1a296867bf Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Fri, 25 Jul 2025 19:32:48 +0200 Subject: [PATCH 3/4] Docs: add documentation about consensus based on specification --- core/src/consensus.rs | 385 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 383 insertions(+), 2 deletions(-) diff --git a/core/src/consensus.rs b/core/src/consensus.rs index 169ac9f75..272b4b486 100644 --- a/core/src/consensus.rs +++ b/core/src/consensus.rs @@ -31,15 +31,107 @@ pub enum ConsensusLongRangeForkDecisionReason { StateHash, } +/// Consensus timing information for a specific slot. +/// +/// Represents the temporal context of a consensus slot within the Ouroboros Samasika +/// protocol, including epoch boundaries and global slot numbering. This structure +/// is essential for coordinating consensus operations across the network. +/// +/// ## Related Specification +/// +/// Based on the [Mina Consensus Specification](https://github.com/MinaProtocol/mina/blob/compatible/docs/specs/consensus/README.md) +/// which defines the Ouroboros Samasika consensus mechanism. +/// +/// ## Timing Hierarchy +/// +/// The consensus protocol operates on a hierarchical timing structure: +/// - **Slots**: Basic time units (3 minutes each) +/// - **Epochs**: Collections of 7,140 slots (~14 days) +/// - **Global slots**: Continuous slot numbering since genesis +/// +/// ## Usage Example +/// +/// ```rust,no_run +/// use openmina_core::consensus::ConsensusTime; +/// use redux::Timestamp; +/// +/// # let slot_start_timestamp = Timestamp::global_now(); +/// # let slot_end_timestamp = Timestamp::new(1_000_000_000 + 180_000); // 3 minutes later +/// // Access timing information for current consensus state +/// let timing = ConsensusTime { +/// start_time: slot_start_timestamp, +/// end_time: slot_end_timestamp, +/// epoch: 42, +/// global_slot: 299_880, // slot within current epoch +/// slot: 0, // first slot of epoch 42 +/// }; +/// +/// println!("Epoch {}, Global slot {}", timing.epoch, timing.global_slot); +/// ``` #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConsensusTime { + /// Timestamp when this consensus slot begins. + /// + /// Each slot has a fixed duration of 3 minutes (180,000ms) as defined by + /// the `block_window_duration_ms` constraint constant. pub start_time: Timestamp, + + /// Timestamp when this consensus slot ends. + /// + /// Calculated as `start_time + block_window_duration_ms`. Block producers + /// must complete block creation and propagation within this timeframe. pub end_time: Timestamp, + + /// Current epoch number since genesis. + /// + /// Epochs are fundamental periods in the consensus protocol, each containing + /// exactly 7,140 slots (approximately 14 days). Epoch transitions trigger + /// stake distribution updates and other consensus state changes. pub epoch: u32, + + /// Global slot number since genesis. + /// + /// This provides a continuous, monotonically increasing slot counter that + /// never resets. Used for global timing calculations and cross-epoch + /// operations like checkpointing. pub global_slot: u32, + + /// Local slot number within the current epoch (0-7139). + /// + /// Resets to 0 at the beginning of each epoch. Used for epoch-local + /// operations like VRF evaluations and stake calculations. pub slot: u32, } +/// Determines if two consensus states represent a short-range fork. +/// +/// Short-range forks occur when two competing chains have a recent common ancestor, +/// typically within the same epoch or adjacent epochs. This is distinguished from +/// long-range forks which may span multiple epochs and require different resolution +/// mechanisms. +/// +/// ## Fork Classification +/// +/// According to the Ouroboros Samasika specification, forks are classified as: +/// - **Short-range**: Chains with recent common ancestry, resolved by chain length and VRF +/// - **Long-range**: Chains with distant common ancestry, resolved by sliding window density +/// +/// ## Detection Logic +/// +/// Two chains are considered short-range forks if: +/// 1. They are in the same epoch with matching staking epoch lock checkpoints, OR +/// 2. One chain is exactly one epoch ahead and the trailing chain is not in seed update range +/// +/// ## Parameters +/// +/// - `a`: First consensus state to compare +/// - `b`: Second consensus state to compare +/// +/// ## Returns +/// +/// `true` if the states represent a short-range fork, `false` for long-range forks. +/// +/// Related specification: [Fork Choice Rule](https://github.com/MinaProtocol/mina/blob/compatible/docs/specs/consensus/README.md) // TODO(binier): do we need to verify constants? Probably they are verified // using block proof verification, but check just to be sure. pub fn is_short_range_fork(a: &MinaConsensusState, b: &MinaConsensusState) -> bool { @@ -71,9 +163,38 @@ pub fn is_short_range_fork(a: &MinaConsensusState, b: &MinaConsensusState) -> bo } } -/// Relative minimum window density. +/// Calculates the relative minimum window density for long-range fork resolution. +/// +/// The relative minimum window density is a core metric in Ouroboros Samasika's +/// long-range fork choice rule. It measures the minimum density of blocks across +/// sliding windows, providing a security mechanism against long-range attacks. /// -/// See [specification](https://github.com/MinaProtocol/mina/tree/develop/docs/specs/consensus#5412-relative-minimum-window-density) +/// ## Purpose +/// +/// This metric ensures that: +/// 1. Honest chains maintain higher density than adversarial chains +/// 2. Long-range forks are resolved in favor of chains with better participation +/// 3. The protocol maintains security even when historical stakes are compromised +/// +/// ## Algorithm +/// +/// 1. Calculate the maximum global slot between the two states +/// 2. If within grace period, return the first state's minimum window density +/// 3. Otherwise, project the window densities forward and compute minimum +/// +/// ## Parameters +/// +/// - `b1`: First consensus state for comparison +/// - `b2`: Second consensus state for comparison +/// +/// ## Returns +/// +/// The relative minimum window density as a 32-bit unsigned integer. +/// +/// ## Specification Reference +/// +/// See [Relative Minimum Window Density](https://github.com/MinaProtocol/mina/blob/compatible/docs/specs/consensus/README.md#5412-relative-minimum-window-density) +/// in the consensus specification for detailed mathematical definitions. pub fn relative_min_window_density(b1: &MinaConsensusState, b2: &MinaConsensusState) -> u32 { use std::cmp::{max, min}; @@ -123,6 +244,35 @@ fn global_slot(b: &MinaConsensusState) -> u32 { b.curr_global_slot_since_hard_fork.slot_number.as_u32() } +/// Determines the winner of a short-range fork using the Ouroboros Samasika fork choice rule. +/// +/// Short-range forks are resolved using a hierarchical comparison mechanism that prioritizes +/// chain length, VRF output quality, and finally state hash as tiebreakers. This ensures +/// deterministic and secure fork resolution. +/// +/// ## Fork Choice Hierarchy +/// +/// 1. **Chain Length**: Longer chains are preferred (most work) +/// 2. **VRF Output**: Better VRF outputs indicate higher stake weight +/// 3. **State Hash**: Deterministic tiebreaker for identical chains +/// +/// ## Parameters +/// +/// - `tip_cs`: Current tip's consensus state +/// - `candidate_cs`: Candidate block's consensus state +/// - `tip_hash`: Hash of the current tip block +/// - `candidate_hash`: Hash of the candidate block +/// +/// ## Returns +/// +/// A tuple containing: +/// - `bool`: `true` if candidate should be adopted, `false` to keep current tip +/// - `ConsensusShortRangeForkDecisionReason`: The reason for the decision +/// +/// ## Related Specification +/// +/// Based on the short-range fork choice rule in the +/// [Mina Consensus Specification](https://github.com/MinaProtocol/mina/blob/compatible/docs/specs/consensus/README.md). pub fn short_range_fork_take( tip_cs: &MinaConsensusState, candidate_cs: &MinaConsensusState, @@ -151,6 +301,43 @@ pub fn short_range_fork_take( (candidate_hash > tip_hash, StateHash) } +/// Determines the winner of a long-range fork using sliding window density comparison. +/// +/// Long-range forks require special handling due to potential long-range attacks where +/// adversaries might try to rewrite distant history. The sliding window density mechanism +/// provides security by preferring chains with better historical block production density. +/// +/// ## Fork Choice Hierarchy +/// +/// 1. **Sub-window Density**: Higher minimum density across sliding windows +/// 2. **Chain Length**: Longer chains are preferred (if density is equal) +/// 3. **VRF Output**: Better VRF outputs indicate higher stake weight +/// 4. **State Hash**: Deterministic tiebreaker for identical chains +/// +/// ## Security Properties +/// +/// The density-based approach ensures that: +/// - Honest chains maintain higher density than adversarial chains +/// - Long-range attacks require controlling stake across extended periods +/// - Historical stake compromises don't enable arbitrary rewrites +/// +/// ## Parameters +/// +/// - `tip_cs`: Current tip's consensus state +/// - `candidate_cs`: Candidate block's consensus state +/// - `tip_hash`: Hash of the current tip block +/// - `candidate_hash`: Hash of the candidate block +/// +/// ## Returns +/// +/// A tuple containing: +/// - `bool`: `true` if candidate should be adopted, `false` to keep current tip +/// - `ConsensusLongRangeForkDecisionReason`: The reason for the decision +/// +/// ## Related Specification +/// +/// Based on the long-range fork choice rule in the +/// [Mina Consensus Specification](https://github.com/MinaProtocol/mina/blob/compatible/docs/specs/consensus/README.md). pub fn long_range_fork_take( tip_cs: &MinaConsensusState, candidate_cs: &MinaConsensusState, @@ -187,6 +374,50 @@ pub fn long_range_fork_take( (candidate_hash > tip_hash, StateHash) } +/// Main entry point for Ouroboros Samasika fork choice decisions. +/// +/// This function implements the complete fork choice rule by first determining whether +/// the fork is short-range or long-range, then applying the appropriate resolution +/// mechanism. This unified approach ensures consistent and secure fork resolution +/// across all scenarios. +/// +/// ## Fork Classification +/// +/// The function automatically: +/// 1. Classifies the fork as short-range or long-range using `is_short_range_fork` +/// 2. Applies the appropriate fork choice rule +/// 3. Returns a binary decision on whether to switch to the candidate +/// +/// ## Parameters +/// +/// - `tip_cs`: Current blockchain tip's consensus state +/// - `candidate_cs`: Candidate block's consensus state +/// - `tip_hash`: State hash of the current tip +/// - `candidate_hash`: State hash of the candidate block +/// +/// ## Returns +/// +/// `true` if the node should switch to the candidate block, `false` to keep the current tip. +/// +/// ## Usage Example +/// +/// ```rust,ignore +/// use openmina_core::consensus::consensus_take; +/// +/// // Assumes you have access to consensus states and hashes from somewhere else +/// if consensus_take(¤t_tip_cs, &candidate_cs, &tip_hash, &candidate_hash) { +/// // Switch to candidate block +/// update_best_tip(candidate_block); +/// } else { +/// // Keep current tip +/// maintain_current_chain(); +/// } +/// ``` +/// +/// ## Related Specification +/// +/// Implements the complete fork choice rule from the +/// [Mina Consensus Specification](https://github.com/MinaProtocol/mina/blob/compatible/docs/specs/consensus/README.md). pub fn consensus_take( tip_cs: &MinaConsensusState, candidate_cs: &MinaConsensusState, @@ -231,24 +462,174 @@ pub fn relative_sub_window(global_sub_window: u32) -> u32 { global_sub_window % constraint_constants().sub_windows_per_window as u32 } +/// Comprehensive consensus parameters derived from constraint and protocol constants. +/// +/// This structure consolidates all timing, security, and operational parameters +/// required by the Ouroboros Samasika consensus protocol. These constants define +/// the fundamental behavior of the blockchain's consensus mechanism. +/// +/// ## Consensus Protocol Overview +/// +/// Mina uses Ouroboros Samasika, a succinct blockchain consensus algorithm that provides: +/// 1. **High decentralization**: Broad participation without centralized coordination +/// 2. **Constant-time synchronization**: New nodes sync quickly regardless of history +/// 3. **Universal composability**: Formal security guarantees in concurrent environments +/// +/// ## Key Security Parameters +/// +/// - **k**: Security parameter defining finality depth (290 slots) +/// - **delta**: Network message delivery bound +/// - **Grace period**: Initial period with relaxed density requirements +/// +/// ## Timing Structure +/// +/// The protocol operates on a hierarchical timing model: +/// ```text +/// Epoch (7,140 slots, ~14 days) +/// └── Window (77 slots, ~3.85 hours) +/// └── Sub-window (7 slots, ~21 minutes) +/// └── Slot (3 minutes) +/// ``` +/// +/// ## Usage Example +/// +/// ```rust,no_run +/// use openmina_core::consensus::ConsensusConstants; +/// use openmina_core::constants::constraint_constants; +/// use mina_p2p_messages::v2::MinaBaseProtocolConstantsCheckedValueStableV1; +/// +/// # let protocol_constants = MinaBaseProtocolConstantsCheckedValueStableV1::default(); +/// let constants = ConsensusConstants::create(constraint_constants(), &protocol_constants); +/// +/// // Calculate epoch duration +/// let epoch_duration_hours = constants.epoch_duration / (1000 * 60 * 60); +/// println!("Epoch duration: {} hours", epoch_duration_hours); +/// +/// // Check if we're past grace period +/// let current_slot = 5000; // Example slot number +/// let past_grace = current_slot > constants.grace_period_end; +/// ``` +/// +/// ## Related Specification +/// +/// Based on the [Mina Consensus Specification](https://github.com/MinaProtocol/mina/blob/compatible/docs/specs/consensus/README.md) +/// which provides detailed mathematical definitions for all parameters. +/// // TODO: Move ledger/src/scan_state/currency.rs types to core and replace // primmitive types here with thoise numeric types. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ConsensusConstants { + /// Security parameter defining finality depth in slots. + /// + /// The `k` parameter determines when blocks are considered final. After `k` slots + /// (290 slots ≈ 14.5 hours), a block is considered immutable under the security + /// assumptions of the protocol. This provides the fundamental security guarantee + /// against reorganizations. + /// + /// **Value**: 290 slots pub k: u32, + + /// Network message delivery bound in slots. + /// + /// The `delta` parameter bounds the maximum time for honest messages to propagate + /// across the network. It ensures that all honest participants receive blocks + /// within `delta` slots, which is crucial for the security analysis of the protocol. + /// + /// **Typical value**: 0-3 slots pub delta: u32, + + /// Duration of each consensus slot in milliseconds. + /// + /// Each slot provides a fixed time window for block production and propagation. + /// This value directly impacts the blockchain's throughput and confirmation times. + /// + /// **Value**: 180,000ms (3 minutes) pub block_window_duration_ms: u64, + + /// Number of slots that comprise a sub-window. + /// + /// Sub-windows are the basic units for density calculations in the sliding window + /// mechanism used for long-range fork resolution. They provide fine-grained + /// density tracking within epochs. + /// + /// **Value**: 7 slots (~21 minutes) pub slots_per_sub_window: u32, + + /// Number of slots that comprise a complete window. + /// + /// Windows are collections of sub-windows used for consensus operations. + /// Calculated as `slots_per_sub_window × sub_windows_per_window`. + /// + /// **Value**: 77 slots (~3.85 hours) pub slots_per_window: u32, + + /// Number of sub-windows that make up a complete window. + /// + /// This determines the granularity of density measurements for fork choice. + /// More sub-windows provide finer density resolution but increase computational overhead. + /// + /// **Value**: 11 sub-windows per window pub sub_windows_per_window: u32, + + /// Total number of slots in a complete epoch. + /// + /// Epochs are fundamental periods for stake distribution updates and major + /// consensus state transitions. Each epoch contains exactly this many slots. + /// + /// **Value**: 7,140 slots (~14.85 days) pub slots_per_epoch: u32, + + /// Number of slots in the initial grace period. + /// + /// During the grace period after genesis, certain consensus rules are relaxed + /// to allow the network to bootstrap. This includes modified density requirements + /// for fork choice. + /// + /// **Typical value**: 1,440 slots (3 days) pub grace_period_slots: u32, + + /// Absolute slot number when the grace period ends. + /// + /// Calculated as `grace_period_slots + slots_per_window`. After this slot, + /// full consensus rules apply including strict density requirements. pub grace_period_end: u32, + + /// Duration of each slot in milliseconds (same as block_window_duration_ms). + /// + /// Provided for convenience and compatibility with different parts of the codebase + /// that may refer to slot duration directly. pub slot_duration_ms: u64, + + /// Total duration of an epoch in milliseconds. + /// + /// Calculated as `slots_per_epoch × block_window_duration_ms`. Useful for + /// converting between epoch-based and time-based calculations. + /// + /// **Value**: ~1,284,000,000ms (~14.85 days) pub epoch_duration: u64, + + /// Number of slots allocated for checkpointing per year. + /// + /// Used in the decentralized checkpointing mechanism to determine checkpoint + /// frequency and resource allocation. pub checkpoint_window_slots_per_year: u32, + + /// Size of each checkpoint window in slots. + /// + /// Checkpointing windows provide periodic state commitments that enable + /// efficient synchronization and pruning of historical data. pub checkpoint_window_size_in_slots: u32, + + /// Duration of the delta period in milliseconds. + /// + /// Calculated as `block_window_duration_ms × (delta + 1)`. Represents the + /// maximum time for message delivery plus one slot buffer. pub delta_duration: u64, + + /// Timestamp of the genesis block. + /// + /// All slot and timing calculations are relative to this genesis timestamp. + /// This establishes the absolute time reference for the entire blockchain. pub genesis_state_timestamp: BlockTimeTimeStableV1, } From 5be465c22cdedc46bf8992f15e210b6eded58d39 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Fri, 25 Jul 2025 19:44:30 +0200 Subject: [PATCH 4/4] Refactor: move inline use statements to top of file and use core instead of std Move inline `use` statements from function bodies to the top of consensus.rs to follow Rust best practices. Replace `std::cmp` with `core::cmp` for basic comparison functionality that doesn't require the full standard library, making the code more suitable for no_std environments. Changes: - Move `use std::cmp::{max, min}` from `relative_min_window_density()` to file top - Move `use std::cmp::Ordering::*` from fork choice functions to file top - Replace `std::cmp` with `core::cmp` - Use fully qualified `Ordering::` variants instead of wildcard imports - Update all match expressions to use explicit `Ordering::Greater/Less/Equal` All tests pass and documentation builds successfully. --- core/src/consensus.rs | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/core/src/consensus.rs b/core/src/consensus.rs index 272b4b486..8e42882a0 100644 --- a/core/src/consensus.rs +++ b/core/src/consensus.rs @@ -1,3 +1,4 @@ +use core::cmp::{max, min, Ordering}; use mina_p2p_messages::v2::{ self, BlockTimeTimeStableV1, ConsensusProofOfStakeDataConsensusStateValueStableV2 as MinaConsensusState, StateHash, @@ -196,8 +197,6 @@ pub fn is_short_range_fork(a: &MinaConsensusState, b: &MinaConsensusState) -> bo /// See [Relative Minimum Window Density](https://github.com/MinaProtocol/mina/blob/compatible/docs/specs/consensus/README.md#5412-relative-minimum-window-density) /// in the consensus specification for detailed mathematical definitions. pub fn relative_min_window_density(b1: &MinaConsensusState, b2: &MinaConsensusState) -> u32 { - use std::cmp::{max, min}; - let max_slot = max(global_slot(b1), global_slot(b2)); if max_slot < GRACE_PERIOD_END { @@ -279,23 +278,22 @@ pub fn short_range_fork_take( tip_hash: &StateHash, candidate_hash: &StateHash, ) -> (bool, ConsensusShortRangeForkDecisionReason) { - use std::cmp::Ordering::*; use ConsensusShortRangeForkDecisionReason::*; let tip_height = &tip_cs.blockchain_length; let candidate_height = &candidate_cs.blockchain_length; match candidate_height.cmp(tip_height) { - Greater => return (true, ChainLength), - Less => return (false, ChainLength), - Equal => {} + Ordering::Greater => return (true, ChainLength), + Ordering::Less => return (false, ChainLength), + Ordering::Equal => {} } let tip_vrf = tip_cs.last_vrf_output.blake2b(); let candidate_vrf = candidate_cs.last_vrf_output.blake2b(); match candidate_vrf.cmp(&tip_vrf) { - Greater => return (true, Vrf), - Less => return (false, Vrf), - Equal => {} + Ordering::Greater => return (true, Vrf), + Ordering::Less => return (false, Vrf), + Ordering::Equal => {} } (candidate_hash > tip_hash, StateHash) @@ -344,31 +342,30 @@ pub fn long_range_fork_take( tip_hash: &StateHash, candidate_hash: &StateHash, ) -> (bool, ConsensusLongRangeForkDecisionReason) { - use std::cmp::Ordering::*; use ConsensusLongRangeForkDecisionReason::*; let tip_density = relative_min_window_density(tip_cs, candidate_cs); let candidate_density = relative_min_window_density(candidate_cs, tip_cs); match candidate_density.cmp(&tip_density) { - Greater => return (true, SubWindowDensity), - Less => return (false, SubWindowDensity), - Equal => {} + Ordering::Greater => return (true, SubWindowDensity), + Ordering::Less => return (false, SubWindowDensity), + Ordering::Equal => {} } let tip_height = &tip_cs.blockchain_length; let candidate_height = &candidate_cs.blockchain_length; match candidate_height.cmp(tip_height) { - Greater => return (true, ChainLength), - Less => return (false, ChainLength), - Equal => {} + Ordering::Greater => return (true, ChainLength), + Ordering::Less => return (false, ChainLength), + Ordering::Equal => {} } let tip_vrf = tip_cs.last_vrf_output.blake2b(); let candidate_vrf = candidate_cs.last_vrf_output.blake2b(); match candidate_vrf.cmp(&tip_vrf) { - Greater => return (true, Vrf), - Less => return (false, Vrf), - Equal => {} + Ordering::Greater => return (true, Vrf), + Ordering::Less => return (false, Vrf), + Ordering::Equal => {} } (candidate_hash > tip_hash, StateHash)