Skip to content

Commit

Permalink
Update versioning doc with vote system description + tests (#4835)
Browse files Browse the repository at this point in the history
* Update versioning doc with vote system description + tests

* Add more documentation
  • Loading branch information
sydhds authored Feb 12, 2025
1 parent cf84cf1 commit 98fc123
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 46 deletions.
26 changes: 19 additions & 7 deletions massa-versioning/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,24 @@
//! # General description
//! MIP = Massa Improvement proposal (similar to Bitcoin Improvement Proposal - BIP or Ethereum - EIP)
//!
//! MIPInfo -> represent a MIP (name, versions, time ranges)
//! MipComponent -> A component that is going to be updated (e.g. Address v2)
//! MIPInfo -> represent a MIP (name, versions, time ranges, components)
//! MIPState -> Deployment state of a MIPInfo
//! MIPStore -> A map of MIPInfo -> MipState
//!
//! Check massa-versioning/src/mips.rs file for a list of already defined MIPInfo.
//!
//! # Notes on MipInfo versions
//!
//! There is 2 different 'versions':
//! * version == Network version -> This is the network version to announce and thus is stored in block header
//! * component_version -> This is the version for the associated component and is used in VersioningFactory (e.g KeyPair, Address, VM)
//! * component_version -> This is the version for the associated component (stored in a MIPInfo) and is used in VersioningFactory (e.g. KeyPair, Address, VM)
//!
//! # Notes on MipInfo timings and stats
//!
//! MipStore stats is in fact a voting system. Node runners can agree on a new version (a new list of MIPInfo) by
//! updating their node software. If a majority of node runners do not update, the new version will be rejected.
//!
//! So in the execution module and after a block become final, we update the MipStore stats (in a blocking way).
//! By updating the stats, we mean sending: (Slot timestamp, Option<(current: u32, announced: Option)>).
//! Using the slot timestamp, ensure that the trigger (and the trigger timeout) is a consensus by all nodes
Expand All @@ -29,7 +34,7 @@
//! so we need to make sure that everyone has seen as final the slot that triggered the locked-in state,
//! and the worst-case delay required for a slot to become final is the definition of a cycle.
//!
//! The activation delay counts how long we wait to activate after the vote threshold was reached
//! The activation delay counts how long we wait to activate after the vote threshold was reached,
//! and we entered into locked-in state. During that delay, and after it, the number of blocks considered
//! for the vote is not relevant. The only reason why we should consider a sufficient number of votes
//! is to get a reasonable p-value on the vote itself:
Expand All @@ -41,25 +46,26 @@
//!
//! MipState has:
//! * A state machine (stores the current state of deployment for a MipInfo)
//! * Usual state changes: Defined -> Started -> LockedIn -> Active
//! * A history (stores a list of `Advance` message that 'really' updated the state machine)
//!
//! A auto generated graph of the state machine can be found here:
//! An auto generated graph of the state machine can be found here:
//! * dot -Tpng ./target/machine/componentstate.dot > ./target/machine/componentstate.png
//! * xdg-open ./target/machine/componentstate.png
//!
//! History is there in order to:
//! * Query the state at any time, so you can query MipStore and ask the best version at any time
//! * Used a lot when merging 2 MipStore:
//! * By replaying the history of the states of the received MipStore (bootstrap), we can safely updates in the bootstrap process
//! * + When we init MipStore (at startup), this ensures that we have a time ranges & versions consistent list of MipInfo
//! * By replaying the history of the states of the received MipStore (bootstrap), we can safely update in the bootstrap process
//! * + When we initialize a MipStore (at startup), this ensures that we have a time ranges & versions consistent list of MipInfo
//! * For instance, this can avoid to have 2 MipInfo with the same name
//!
//! # Versioning Factory
//!
//! A Factory trait is there to ease the development of factory for Versioned component (e.g. address, block)
//!
//! All factories should query MIPStore in order to create a component with correct version; default implementation
//! are provided by the trait to avoid re writing these query functions.
//! are provided by the trait to avoid re-writing these query functions.
//!
//! Unit tests in versioning_factory.rs shows a basic but realistic implementation of a AddressFactory (impl the Factory trait)
//!
Expand All @@ -71,6 +77,12 @@
//!
//! By writing only 'Active' MIP in state_cf column, we ensure that the final state hash remains the same between
//! the versioning transition (e.g. User 1 has upgraded to network version 1 while User 2 has not yet upgraded)
//!
//! # Tests
//!
//! The massa-versioning module has a bunch of unit test in versioning.rs. Note that functional test,
//! can be found in https://github.com/massalabs/massa-functional-tests/blob/main/tests_versioning.py.
//! Note that those tests might not be up to date with the latest module code.
pub mod address_factory;
pub mod grpc_mapping;
Expand Down
78 changes: 39 additions & 39 deletions massa-versioning/src/versioning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,44 +198,6 @@ impl PartialEq for Advance {

impl Eq for Advance {}

// A Lightweight version of 'Advance' (used in MipState history)
#[derive(Clone, Debug)]
pub struct AdvanceLW {
/// % of past blocks with this version
pub threshold: Ratio<u64>,
/// Current time (timestamp)
pub now: MassaTime,
}

impl From<&Advance> for AdvanceLW {
fn from(value: &Advance) -> Self {
Self {
threshold: value.threshold,
now: value.now,
}
}
}

impl Ord for AdvanceLW {
fn cmp(&self, other: &Self) -> Ordering {
(self.now, self.threshold).cmp(&(other.now, other.threshold))
}
}

impl PartialOrd for AdvanceLW {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl PartialEq for AdvanceLW {
fn eq(&self, other: &Self) -> bool {
self.threshold == other.threshold && self.now == other.now
}
}

impl Eq for AdvanceLW {}

transitions!(ComponentState,
[
(Defined, Advance) => [Defined, Started, Failed],
Expand Down Expand Up @@ -299,6 +261,44 @@ impl Failed {
}
}

// A Lightweight version of 'Advance' (used in MipState history)
#[derive(Clone, Debug)]
pub struct AdvanceLW {
/// % of past blocks with this version
pub threshold: Ratio<u64>,
/// Current time (timestamp)
pub now: MassaTime,
}

impl From<&Advance> for AdvanceLW {
fn from(value: &Advance) -> Self {
Self {
threshold: value.threshold,
now: value.now,
}
}
}

impl Ord for AdvanceLW {
fn cmp(&self, other: &Self) -> Ordering {
(self.now, self.threshold).cmp(&(other.now, other.threshold))
}
}

impl PartialOrd for AdvanceLW {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl PartialEq for AdvanceLW {
fn eq(&self, other: &Self) -> bool {
self.threshold == other.threshold && self.now == other.now
}
}

impl Eq for AdvanceLW {}

/// Error returned by `MipState::is_consistent_with`
#[derive(Error, Debug, PartialEq)]
pub enum IsConsistentError {
Expand Down Expand Up @@ -1170,7 +1170,7 @@ impl MipStoreRaw {
// Network restart

/// Check if store is consistent with given last network shutdown
/// On a network shutdown, the MIP infos will be edited but we still need to check if this is consistent
/// On a network shutdown, the MIP infos will be edited, but we still need to check if this is consistent
fn is_consistent_with_shutdown_period(
&self,
shutdown_start: Slot,
Expand Down

0 comments on commit 98fc123

Please sign in to comment.