From 0039ebf05e36a4cd5905754146787b781cf71abd Mon Sep 17 00:00:00 2001 From: ptrus Date: Mon, 20 Nov 2023 16:28:49 +0100 Subject: [PATCH] interop: add interop tests for the consensus roothash state --- .../apps/beacon/state/interop/interop.go | 2 +- .../apps/roothash/state/interop/interop.go | 73 ++++++++++++++ .../mkvs/interop/fixtures/consensus_mock.go | 9 ++ runtime/src/consensus/state/beacon.rs | 2 +- runtime/src/consensus/state/keymanager.rs | 2 +- runtime/src/consensus/state/registry.rs | 2 +- runtime/src/consensus/state/roothash.rs | 95 +++++++++++++++++++ runtime/src/consensus/state/staking.rs | 2 +- 8 files changed, 182 insertions(+), 5 deletions(-) create mode 100644 go/consensus/cometbft/apps/roothash/state/interop/interop.go diff --git a/go/consensus/cometbft/apps/beacon/state/interop/interop.go b/go/consensus/cometbft/apps/beacon/state/interop/interop.go index 3ccb566702c..3de282669fa 100644 --- a/go/consensus/cometbft/apps/beacon/state/interop/interop.go +++ b/go/consensus/cometbft/apps/beacon/state/interop/interop.go @@ -7,7 +7,7 @@ import ( "github.com/oasisprotocol/oasis-core/go/storage/mkvs" ) -// InitializeTestBeaconState must be keet in sync with tests in runtimes/consensus/state/beacon.rs. +// InitializeTestBeaconState must be kept in sync with tests in runtimes/consensus/state/beacon.rs. func InitializeTestBeaconState(ctx context.Context, mkvs mkvs.Tree) error { state := beaconState.NewMutableState(mkvs) diff --git a/go/consensus/cometbft/apps/roothash/state/interop/interop.go b/go/consensus/cometbft/apps/roothash/state/interop/interop.go new file mode 100644 index 00000000000..62ba567571a --- /dev/null +++ b/go/consensus/cometbft/apps/roothash/state/interop/interop.go @@ -0,0 +1,73 @@ +package interop + +import ( + "context" + "fmt" + + "github.com/oasisprotocol/oasis-core/go/common" + "github.com/oasisprotocol/oasis-core/go/common/crypto/hash" + roothashState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/roothash/state" + registry "github.com/oasisprotocol/oasis-core/go/registry/api" + "github.com/oasisprotocol/oasis-core/go/roothash/api" + "github.com/oasisprotocol/oasis-core/go/roothash/api/block" + "github.com/oasisprotocol/oasis-core/go/storage/mkvs" +) + +// InitializeTestRoothashState must be kept in sync with tests in runtimes/consensus/state/roothash.rs. +func InitializeTestRoothashState(ctx context.Context, mkvs mkvs.Tree) error { + var runtimeID common.Namespace + if err := runtimeID.UnmarshalHex("8000000000000000000000000000000000000000000000000000000000000010"); err != nil { + return err + } + + state := roothashState.NewMutableState(mkvs) + + if err := state.SetConsensusParameters(ctx, &api.ConsensusParameters{ + MaxPastRootsStored: 100, + }); err != nil { + return err + } + + // Prepare initial runtime state. + // TODO: fill the rest if needed for interop tests in future. + runtimeState := &api.RuntimeState{ + Runtime: ®istry.Runtime{ + ID: runtimeID, + }, + Suspended: false, + GenesisBlock: &block.Block{Header: block.Header{ + Round: 1, + IORoot: hash.NewFromBytes([]byte("genesis")), + StateRoot: hash.NewFromBytes([]byte("genesis")), + }}, + LastBlock: &block.Block{Header: block.Header{ + Round: 1, + IORoot: hash.NewFromBytes([]byte("genesis")), + StateRoot: hash.NewFromBytes([]byte("genesis")), + }}, + LastBlockHeight: 1, + LastNormalRound: 1, + LastNormalHeight: 1, + } + if err := state.SetRuntimeState(ctx, runtimeState); err != nil { + return err + } + + // Save some runtime state rounds, so we fill past roots state. + for i := 0; i < 10; i++ { + runtimeState.LastBlock = &block.Block{Header: block.Header{ + Round: uint64(i + 1), + IORoot: hash.NewFromBytes([]byte(fmt.Sprintf("io %d", i+1))), + StateRoot: hash.NewFromBytes([]byte(fmt.Sprintf("state %d", i+1))), + }} + runtimeState.LastNormalRound = uint64(i + 1) + runtimeState.LastBlockHeight = int64(i * 10) + runtimeState.LastNormalHeight = int64(i * 10) + + if err := state.SetRuntimeState(ctx, runtimeState); err != nil { + return err + } + } + + return nil +} diff --git a/go/storage/mkvs/interop/fixtures/consensus_mock.go b/go/storage/mkvs/interop/fixtures/consensus_mock.go index 5557bab1a04..5b044286ae1 100644 --- a/go/storage/mkvs/interop/fixtures/consensus_mock.go +++ b/go/storage/mkvs/interop/fixtures/consensus_mock.go @@ -3,11 +3,14 @@ package fixtures import ( "context" "fmt" + "time" "github.com/oasisprotocol/oasis-core/go/common" + "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api" beaconInterop "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/beacon/state/interop" keymanagerInterop "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/state/interop" registryInterop "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/registry/state/interop" + roothashInterop "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/roothash/state/interop" stakingInterop "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/staking/state/interop" storage "github.com/oasisprotocol/oasis-core/go/storage/api" "github.com/oasisprotocol/oasis-core/go/storage/mkvs" @@ -32,6 +35,9 @@ func (c *consensusMock) Populate(ctx context.Context, ndb db.NodeDB) (*node.Root Version: 1, } + // Use a dummy ABCI InitChain context, as SetConsensusParameters methods require a specific ABCI context. + ctx = api.NewContext(ctx, api.ContextInitChain, time.Time{}, nil, nil, nil, 0, nil, 0) + mkvsTree := mkvs.New(nil, ndb, node.RootTypeState, mkvs.WithoutWriteLog()) if err = stakingInterop.InitializeTestStakingState(ctx, mkvsTree); err != nil { return nil, fmt.Errorf("consensus-mock: failed to initialize staking state: %w", err) @@ -45,6 +51,9 @@ func (c *consensusMock) Populate(ctx context.Context, ndb db.NodeDB) (*node.Root if err = keymanagerInterop.InitializeTestKeyManagerState(ctx, mkvsTree); err != nil { return nil, fmt.Errorf("consensus-mock: failed to initialize key manager state: %w", err) } + if err = roothashInterop.InitializeTestRoothashState(ctx, mkvsTree); err != nil { + return nil, fmt.Errorf("consensus-mock: failed to initialize roothash state: %w", err) + } _, testRoot.Hash, err = mkvsTree.Commit(ctx, common.Namespace{}, 1) if err != nil { return nil, fmt.Errorf("consensus-mock: failed to committ tree: %w", err) diff --git a/runtime/src/consensus/state/beacon.rs b/runtime/src/consensus/state/beacon.rs index d381a54f012..b590c898b14 100644 --- a/runtime/src/consensus/state/beacon.rs +++ b/runtime/src/consensus/state/beacon.rs @@ -154,7 +154,7 @@ mod test { let mock_consensus_root = Root { version: 1, root_type: RootType::State, - hash: Hash::from("123d46d530ebb004f6de9da7e1f41f7acde10b824e79ca8e718651dab2047c23"), + hash: Hash::from("f637a80b24e3ffaaf3de0da96f1dfd94d0a135348f40006d578d557d70d5fa42"), ..Default::default() }; let mkvs = Tree::builder() diff --git a/runtime/src/consensus/state/keymanager.rs b/runtime/src/consensus/state/keymanager.rs index 14a02032369..72d27b80348 100644 --- a/runtime/src/consensus/state/keymanager.rs +++ b/runtime/src/consensus/state/keymanager.rs @@ -166,7 +166,7 @@ mod test { let mock_consensus_root = Root { version: 1, root_type: RootType::State, - hash: Hash::from("123d46d530ebb004f6de9da7e1f41f7acde10b824e79ca8e718651dab2047c23"), + hash: Hash::from("f637a80b24e3ffaaf3de0da96f1dfd94d0a135348f40006d578d557d70d5fa42"), ..Default::default() }; let mkvs = Tree::builder() diff --git a/runtime/src/consensus/state/registry.rs b/runtime/src/consensus/state/registry.rs index c77181b017f..d259cd257c1 100644 --- a/runtime/src/consensus/state/registry.rs +++ b/runtime/src/consensus/state/registry.rs @@ -131,7 +131,7 @@ mod test { let mock_consensus_root = Root { version: 1, root_type: RootType::State, - hash: Hash::from("123d46d530ebb004f6de9da7e1f41f7acde10b824e79ca8e718651dab2047c23"), + hash: Hash::from("f637a80b24e3ffaaf3de0da96f1dfd94d0a135348f40006d578d557d70d5fa42"), ..Default::default() }; let mkvs = Tree::builder() diff --git a/runtime/src/consensus/state/roothash.rs b/runtime/src/consensus/state/roothash.rs index 1a6f0420ab0..2a212b8736d 100644 --- a/runtime/src/consensus/state/roothash.rs +++ b/runtime/src/consensus/state/roothash.rs @@ -98,3 +98,98 @@ impl<'a, T: ImmutableMKVS> ImmutableState<'a, T> { Ok(result) } } + +#[cfg(test)] +mod test { + use crate::{ + common::crypto::hash::Hash, + storage::mkvs::{ + interop::{Fixture, ProtocolServer}, + Root, RootType, Tree, + }, + }; + + use super::*; + #[test] + fn test_roothash_state_interop() { + // Keep in sync with go/consensus/cometbft/apps/roothash/state/interop/interop.go. + // If mock consensus state changes, update the root hash bellow. + // See protocol server stdout for hash. + // To make the hash show up during tests, run "cargo test" as + // "cargo test -- --nocapture". + + // Setup protocol server with initialized mock consensus state. + let server = ProtocolServer::new(Fixture::ConsensusMock.into()); + let mock_consensus_root = Root { + version: 1, + root_type: RootType::State, + hash: Hash::from("f637a80b24e3ffaaf3de0da96f1dfd94d0a135348f40006d578d557d70d5fa42"), + ..Default::default() + }; + let mkvs = Tree::builder() + .with_capacity(100_000, 10_000_000) + .with_root(mock_consensus_root) + .build(server.read_sync()); + let state = ImmutableState::new(&mkvs); + + let runtime_id = + Namespace::from("8000000000000000000000000000000000000000000000000000000000000010"); + + // Test fetching past round roots. + let past_round_roots = state + .past_round_roots(runtime_id) + .expect("past round roots query should work"); + assert_eq!( + 10, + past_round_roots.len(), + "expected number of roots should match" + ); + past_round_roots.iter().for_each(|(round, roots)| { + assert_eq!( + RoundRoots { + state_root: Hash::digest_bytes(format!("state {}", round).as_bytes()), + io_root: Hash::digest_bytes(format!("io {}", round).as_bytes()) + }, + *roots, + "expected roots should match" + ); + }); + + // Test fetching latest round roots. + let round_roots = state + .round_roots(runtime_id, 100) + .expect("round roots query should work"); + assert_eq!(None, round_roots, "round root should be missing"); + + let round_roots = state + .round_roots(runtime_id, 10) + .expect("round roots query should work"); + assert_eq!( + Some(RoundRoots { + state_root: Hash::digest_bytes(format!("state {}", 10).as_bytes()), + io_root: Hash::digest_bytes(format!("io {}", 10).as_bytes()) + }), + round_roots, + "round root should be missing" + ); + + // Test non-existing runtime. + let runtime_id = + Namespace::from("8000000000000000000000000000000000000000000000000000000000000000"); + let past_round_roots = state + .past_round_roots(runtime_id) + .expect("past round roots query should work"); + assert_eq!( + 0, + past_round_roots.len(), + "there should be no roots for non-existing runtime" + ); + let round_roots = state + .round_roots(runtime_id, 10) + .expect("round roots query should work"); + assert_eq!( + None, round_roots, + "round root should be missing for non-existing runtime" + ) + } +} diff --git a/runtime/src/consensus/state/staking.rs b/runtime/src/consensus/state/staking.rs index 659b38629e0..8728cba9730 100644 --- a/runtime/src/consensus/state/staking.rs +++ b/runtime/src/consensus/state/staking.rs @@ -221,7 +221,7 @@ mod test { let mock_consensus_root = Root { version: 1, root_type: RootType::State, - hash: Hash::from("123d46d530ebb004f6de9da7e1f41f7acde10b824e79ca8e718651dab2047c23"), + hash: Hash::from("f637a80b24e3ffaaf3de0da96f1dfd94d0a135348f40006d578d557d70d5fa42"), ..Default::default() }; let mkvs = Tree::builder()