From e45eac652406679340608682ecbce44bae7c81b9 Mon Sep 17 00:00:00 2001 From: Jernej Kos Date: Mon, 21 Oct 2024 13:18:17 +0200 Subject: [PATCH] runtime: Add accessor for runtime state --- .changelog/5915.feature.md | 1 + runtime/src/consensus/roothash/mod.rs | 64 ++++++++++++++++++++++++- runtime/src/consensus/scheduler.rs | 2 + runtime/src/consensus/state/roothash.rs | 45 ++++++++++++++++- 4 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 .changelog/5915.feature.md diff --git a/.changelog/5915.feature.md b/.changelog/5915.feature.md new file mode 100644 index 00000000000..3d36d71b1ae --- /dev/null +++ b/.changelog/5915.feature.md @@ -0,0 +1 @@ +runtime: Add accessor for runtime state diff --git a/runtime/src/consensus/roothash/mod.rs b/runtime/src/consensus/roothash/mod.rs index 56c2825449e..e8a02a16bd9 100644 --- a/runtime/src/consensus/roothash/mod.rs +++ b/runtime/src/consensus/roothash/mod.rs @@ -11,7 +11,7 @@ use crate::{ crypto::{hash::Hash, signature::PublicKey}, namespace::Namespace, }, - consensus::state::StateError, + consensus::{registry::Runtime, scheduler::Committee, state::StateError}, }; // Modules. @@ -111,6 +111,68 @@ impl MessageEvent { } } +/// Per-runtime state. +#[derive(Clone, Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)] +#[cbor(allow_unknown)] +pub struct RuntimeState { + /// Latest per-epoch runtime descriptor. + pub runtime: Runtime, + /// Flag indicating whether the runtime is currently suspended. + #[cbor(optional)] + pub suspended: bool, + + // Runtime's first block. + pub genesis_block: Block, + + /// Runtime's most recently finalized block. + pub last_block: Block, + /// Height at which the runtime's most recent block was finalized. + pub last_block_height: i64, + + /// Runtime round which was normally processed by the runtime. This is also the round that + /// contains the message results for the last processed runtime messages. + pub last_normal_round: u64, + /// Consensus block height corresponding to `last_normal_round`. + pub last_normal_height: i64, + + /// Committee the executor pool is collecting commitments for. + #[cbor(optional)] + pub commitee: Option, + // NOTE: Commitment pool deserialization is currently not supported. + /// Consensus height at which the round is scheduled for forced finalization. + #[cbor(optional)] + pub next_timeout: i64, + + /// Liveness statistics for the current epoch. + pub liveness_stats: Option, +} + +/// Per-epoch liveness statistics for nodes. +#[derive(Clone, Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)] +pub struct LivenessStatistics { + /// Total number of rounds in the last epoch, excluding any rounds generated by the roothash + /// service itself. + pub total_rounds: u64, + + /// A list of counters, specified in committee order (e.g. counter at index i has the value for + /// node i in the committee). + pub good_rounds: Vec, + + /// A list that records the number of finalized rounds when a node acted as a proposed with the + /// highest rank. + /// + /// The list is ordered according to the committee arrangement (i.e., the counter at index i + /// holds the value for the node at index i in the committee). + pub finalized_proposals: Vec, + + /// A list that records the number of failed rounds when a node/ acted as a proposer with the + /// highest rank. + /// + /// The list is ordered according to the committee arrangement (i.e., the counter at index i + /// holds the value for the node at index i in the committee). + pub missed_proposals: Vec, +} + /// Information about how a particular round was executed by the consensus layer. #[derive(Clone, Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)] pub struct RoundResults { diff --git a/runtime/src/consensus/scheduler.rs b/runtime/src/consensus/scheduler.rs index 9f2089cde7b..e87a279166f 100644 --- a/runtime/src/consensus/scheduler.rs +++ b/runtime/src/consensus/scheduler.rs @@ -22,6 +22,7 @@ pub enum Role { } /// A node participating in a committee. +#[derive(Clone, Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)] pub struct CommitteeNode { /// The node's role in a committee. pub role: Role, @@ -44,6 +45,7 @@ pub enum CommitteeKind { } /// A per-runtime (instance) committee. +#[derive(Clone, Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)] pub struct Committee { /// The functionality a committee exists to provide. pub kind: CommitteeKind, diff --git a/runtime/src/consensus/state/roothash.rs b/runtime/src/consensus/state/roothash.rs index 1848fe4d354..8c7d64432fe 100644 --- a/runtime/src/consensus/state/roothash.rs +++ b/runtime/src/consensus/state/roothash.rs @@ -10,7 +10,7 @@ use crate::{ namespace::Namespace, }, consensus::{ - roothash::{Error, RoundResults, RoundRoots}, + roothash::{Error, RoundResults, RoundRoots, RuntimeState}, state::StateError, }, key_format, @@ -29,11 +29,25 @@ impl<'a, T: ImmutableMKVS> ImmutableState<'a, T> { } } +key_format!(RuntimeKeyFmt, 0x20, Hash); key_format!(StateRootKeyFmt, 0x25, Hash); key_format!(LastRoundResultsKeyFmt, 0x27, Hash); key_format!(PastRootsKeyFmt, 0x2a, (Hash, u64)); impl<'a, T: ImmutableMKVS> ImmutableState<'a, T> { + /// Returns the latest runtime state. + pub fn runtime_state(&self, id: Namespace) -> Result { + match self + .mkvs + .get(&RuntimeKeyFmt(Hash::digest_bytes(id.as_ref())).encode()) + { + Ok(Some(b)) => cbor::from_slice_non_strict(&b) + .map_err(|err| StateError::Unavailable(anyhow!(err)).into()), + Ok(None) => Err(Error::InvalidRuntime(id)), + Err(err) => Err(StateError::Unavailable(anyhow!(err)).into()), + } + } + /// Returns the state root for a specific runtime. pub fn state_root(&self, id: Namespace) -> Result { match self @@ -132,6 +146,35 @@ mod test { let runtime_id = Namespace::from("8000000000000000000000000000000000000000000000000000000000000010"); + // Test fetching runtime state. + let runtime_state = state + .runtime_state(runtime_id) + .expect("runtime state query should work"); + println!("{:?}", runtime_state); + assert_eq!(runtime_state.runtime.id, runtime_id); + assert_eq!(runtime_state.suspended, false); + assert_eq!(runtime_state.genesis_block.header.round, 1); + assert_eq!( + runtime_state.genesis_block.header.io_root, + Hash::digest_bytes(format!("genesis").as_bytes()) + ); + assert_eq!( + runtime_state.genesis_block.header.state_root, + Hash::digest_bytes(format!("genesis").as_bytes()) + ); + assert_eq!(runtime_state.last_block.header.round, 10); + assert_eq!( + runtime_state.last_block.header.io_root, + Hash::digest_bytes(format!("io 10").as_bytes()) + ); + assert_eq!( + runtime_state.last_block.header.state_root, + Hash::digest_bytes(format!("state 10").as_bytes()) + ); + assert_eq!(runtime_state.last_block_height, 90); + assert_eq!(runtime_state.last_normal_round, 10); + assert_eq!(runtime_state.last_normal_height, 90); + // Test fetching past round roots. let past_round_roots = state .past_round_roots(runtime_id)