Skip to content

Commit

Permalink
verify epoch in dispatcher using consensus verifier
Browse files Browse the repository at this point in the history
  • Loading branch information
ptrus committed Apr 20, 2022
1 parent 619fb5e commit 8f0eae7
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 9 deletions.
1 change: 1 addition & 0 deletions .changelog/4677.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
runtime: verify epoch in dispatcher using consensus verifier
20 changes: 20 additions & 0 deletions go/consensus/tendermint/apps/beacon/state/interop/interop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package interop

import (
"context"

beaconState "github.com/oasisprotocol/oasis-core/go/consensus/tendermint/apps/beacon/state"
"github.com/oasisprotocol/oasis-core/go/storage/mkvs"
)

// Keep this in sync with tests in runtimes/consensus/state/beacon.rs.
func InitializeTestBeaconState(ctx context.Context, mkvs mkvs.Tree) error {
state := beaconState.NewMutableState(mkvs)

// Populate beacon.
if err := state.SetEpoch(ctx, 42, 13); err != nil {
return err
}

return nil
}
6 changes: 5 additions & 1 deletion go/storage/mkvs/interop/fixtures/consensus_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"

"github.com/oasisprotocol/oasis-core/go/common"
beaconInterop "github.com/oasisprotocol/oasis-core/go/consensus/tendermint/apps/beacon/state/interop"
stakingInterop "github.com/oasisprotocol/oasis-core/go/consensus/tendermint/apps/staking/state/interop"
storage "github.com/oasisprotocol/oasis-core/go/storage/api"
"github.com/oasisprotocol/oasis-core/go/storage/mkvs"
Expand All @@ -31,7 +32,10 @@ func (c *consensusMock) Populate(ctx context.Context, ndb db.NodeDB) (*node.Root

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 state: %w", err)
return nil, fmt.Errorf("consensus-mock: failed to initialize staking state: %w", err)
}
if err = beaconInterop.InitializeTestBeaconState(ctx, mkvsTree); err != nil {
return nil, fmt.Errorf("consensus-mock: failed to initialize beacon state: %w", err)
}
_, testRoot.Hash, err = mkvsTree.Commit(ctx, common.Namespace{}, 1)
if err != nil {
Expand Down
89 changes: 89 additions & 0 deletions runtime/src/consensus/state/beacon.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//! Beacon state in the consensus layer.
use anyhow::anyhow;
use io_context::Context;

use crate::{
common::key_format::{KeyFormat, KeyFormatAtom},
consensus::{beacon::EpochTime, state::StateError},
key_format,
storage::mkvs::ImmutableMKVS,
};

#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, cbor::Encode, cbor::Decode)]
pub struct EpochTimeState {
pub epoch: EpochTime,
pub height: i64,
}

/// Consensus beacon state wrapper.
pub struct ImmutableState<'a, T: ImmutableMKVS> {
mkvs: &'a T,
}

impl<'a, T: ImmutableMKVS> ImmutableState<'a, T> {
/// Constructs a new ImmutableMKVS.
pub fn new(mkvs: &'a T) -> ImmutableState<'a, T> {
ImmutableState { mkvs }
}
}

key_format!(CurrentEpochKeyFmt, 0x40, ());

impl<'a, T: ImmutableMKVS> ImmutableState<'a, T> {
/// Returns the current epoch number.
pub fn epoch(&self, ctx: Context) -> Result<EpochTime, StateError> {
match self.mkvs.get(ctx, &CurrentEpochKeyFmt(()).encode()) {
Ok(Some(b)) => {
let state: EpochTimeState =
cbor::from_slice(&b).map_err(|err| StateError::Unavailable(anyhow!(err)))?;
Ok(state.epoch)
}
Ok(None) => Ok(EpochTime::default()),
Err(err) => Err(StateError::Unavailable(anyhow!(err))),
}
}
}

#[cfg(test)]
mod test {
use std::sync::Arc;

use crate::{
common::crypto::hash::Hash,
storage::mkvs::{
interop::{Fixture, ProtocolServer},
Root, RootType, Tree,
},
};

use super::*;

#[test]
fn test_beacon_state_interop() {
// Keep in sync with go/consensus/tendermint/apps/beacon/state/interop/interop.go.
// If mock consensus state changes, update the root hash bellow.
// See protocol server stdout for hash.

// 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("aadd72716ec331c4bc7b8945699cbc608038dd4f3fc2cc3f01fffdad24a47c6f"),
..Default::default()
};
let mkvs = Tree::builder()
.with_capacity(100_000, 10_000_000)
.with_root(mock_consensus_root)
.build(server.read_sync());
let beacon_state = ImmutableState::new(&mkvs);

let ctx = Arc::new(Context::background());

// Test current epoch number.
let epoch = beacon_state
.epoch(Context::create_child(&ctx))
.expect("eapoch query should work");
assert_eq!(42u64, epoch, "expected epoch should match");
}
}
1 change: 1 addition & 0 deletions runtime/src/consensus/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::{
types::HostStorageEndpoint,
};

pub mod beacon;
pub mod roothash;
pub mod staking;

Expand Down
2 changes: 1 addition & 1 deletion runtime/src/consensus/state/roothash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::{
storage::mkvs::ImmutableMKVS,
};

/// Consensus staking state wrapper.
/// Consensus roothash state wrapper.
pub struct ImmutableState<'a, T: ImmutableMKVS> {
mkvs: &'a T,
}
Expand Down
2 changes: 1 addition & 1 deletion runtime/src/consensus/state/staking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ mod test {
let mock_consensus_root = Root {
version: 1,
root_type: RootType::State,
hash: Hash::from("8bc9288a3394dae816cec993cfd9762d1fbf4b1136a32653ff8ff46c7322a33a"),
hash: Hash::from("aadd72716ec331c4bc7b8945699cbc608038dd4f3fc2cc3f01fffdad24a47c6f"),
..Default::default()
};
let mkvs = Tree::builder()
Expand Down
32 changes: 29 additions & 3 deletions runtime/src/consensus/tendermint/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,12 @@ use crate::{
time,
},
consensus::{
beacon::EpochTime,
roothash::{ComputeResultsHeader, Header},
state::{roothash::ImmutableState as RoothashState, ConsensusState},
state::{
beacon::ImmutableState as BeaconState, roothash::ImmutableState as RoothashState,
ConsensusState,
},
tendermint::{decode_light_block, LightBlockMeta, TENDERMINT_CONTEXT},
verifier::{self, Error, TrustRoot},
LightBlock, HEIGHT_LATEST,
Expand Down Expand Up @@ -82,6 +86,7 @@ impl verifier::Verifier for NopVerifier {
&self,
consensus_block: LightBlock,
_runtime_header: Header,
_epoch: EpochTime,
) -> Result<ConsensusState, Error> {
self.unverified_state(consensus_block)
}
Expand All @@ -107,6 +112,7 @@ enum Command {
Verify(
LightBlock,
Header,
EpochTime,
channel::Sender<Result<ConsensusState, Error>>,
),
Trust(ComputeResultsHeader, channel::Sender<Result<(), Error>>),
Expand Down Expand Up @@ -225,6 +231,7 @@ impl Verifier {
instance: &mut Instance,
consensus_block: LightBlock,
runtime_header: Header,
epoch: EpochTime,
) -> Result<ConsensusState, Error> {
// Verify runtime ID matches.
if runtime_header.namespace != self.trust_root.runtime_id {
Expand Down Expand Up @@ -270,6 +277,18 @@ impl Verifier {
)));
}

let beacon_state = BeaconState::new(&state);
let current_epoch = beacon_state.epoch(Context::background()).map_err(|err| {
Error::VerificationFailed(anyhow!("failed to retrieve epoch: {}", err))
})?;
if current_epoch != epoch {
return Err(Error::VerificationFailed(anyhow!(
"epoch number mismatch (expected: {} got: {})",
current_epoch,
epoch,
)));
}

// Cache verified runtime header.
cache
.verified_state_roots
Expand Down Expand Up @@ -440,13 +459,14 @@ impl Verifier {
.send(self.sync(&mut cache, &mut instance, height))
.map_err(|_| Error::Internal)?;
}
Command::Verify(consensus_block, runtime_header, sender) => {
Command::Verify(consensus_block, runtime_header, epoch, sender) => {
sender
.send(self.verify(
&mut cache,
&mut instance,
consensus_block,
runtime_header,
epoch,
))
.map_err(|_| Error::Internal)?;
}
Expand Down Expand Up @@ -487,10 +507,16 @@ impl verifier::Verifier for Handle {
&self,
consensus_block: LightBlock,
runtime_header: Header,
epoch: EpochTime,
) -> Result<ConsensusState, Error> {
let (sender, receiver) = channel::bounded(1);
self.command_sender
.send(Command::Verify(consensus_block, runtime_header, sender))
.send(Command::Verify(
consensus_block,
runtime_header,
epoch,
sender,
))
.map_err(|_| Error::Internal)?;

receiver.recv().map_err(|_| Error::Internal)?
Expand Down
2 changes: 2 additions & 0 deletions runtime/src/consensus/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
use thiserror::Error;

use super::{
beacon::EpochTime,
roothash::{ComputeResultsHeader, Header},
state::ConsensusState,
LightBlock,
Expand Down Expand Up @@ -44,6 +45,7 @@ pub trait Verifier: Send + Sync {
&self,
consensus_block: LightBlock,
runtime_header: Header,
epoch: EpochTime,
) -> Result<ConsensusState, Error>;

/// Return the consensus layer state accessor for the given consensus layer block WITHOUT
Expand Down
8 changes: 5 additions & 3 deletions runtime/src/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -555,9 +555,11 @@ impl Dispatcher {
state: TxDispatchState,
) -> Result<Body, Error> {
// Verify consensus state and runtime state root integrity before execution.
let consensus_state = state
.consensus_verifier
.verify(state.consensus_block, state.header.clone())?;
let consensus_state = state.consensus_verifier.verify(
state.consensus_block,
state.header.clone(),
state.epoch,
)?;

let header = &state.header;

Expand Down

0 comments on commit 8f0eae7

Please sign in to comment.