Skip to content

Commit

Permalink
go/consensus: Store runtime state roots in a single key
Browse files Browse the repository at this point in the history
That makes it easier to construct proofs starting at the consensus state root
that prove what the state root of a specific runtime is. You can then chain
these proofs to prove something about runtime state.
  • Loading branch information
kostko committed Mar 16, 2021
1 parent 3679481 commit 28586d0
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changelog/3782.breaking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
go/consensus: Store runtime state roots in a single key

That makes it easier to construct proofs starting at the consensus state root
that prove what the state root of a specific runtime is. You can then chain
these proofs to prove something about runtime state.
52 changes: 50 additions & 2 deletions go/consensus/tendermint/apps/roothash/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ var (
//
// Key format is: 0x24 <H(runtime-id) (hash.Hash)> <round (uint64)> <evidence-hash (hash.Hash)>
evidenceKeyFmt = keyformat.New(0x24, keyformat.H(&common.Namespace{}), uint64(0), &hash.Hash{})
// stateRootKeyFmt is the key format used for runtime state roots.
//
// Value is the runtime's latest state root.
stateRootKeyFmt = keyformat.New(0x25, keyformat.H(&common.Namespace{}))
// ioRootKeyFmt is the key format used for runtime I/O roots.
//
// Value is the runtime's latest I/O root.
ioRootKeyFmt = keyformat.New(0x26, keyformat.H(&common.Namespace{}))

cborTrue = cbor.Marshal(true)
)
Expand Down Expand Up @@ -114,6 +122,32 @@ func (s *ImmutableState) RuntimeState(ctx context.Context, id common.Namespace)
return &state, nil
}

func (s *ImmutableState) getRoot(ctx context.Context, id common.Namespace, kf *keyformat.KeyFormat) (hash.Hash, error) {
raw, err := s.is.Get(ctx, kf.Encode(&id))
if err != nil {
return hash.Hash{}, api.UnavailableStateError(err)
}
if raw == nil {
return hash.Hash{}, roothash.ErrInvalidRuntime
}

var h hash.Hash
if err = h.UnmarshalBinary(raw); err != nil {
return hash.Hash{}, api.UnavailableStateError(err)
}
return h, nil
}

// StateRoot returns the state root for a specific runtime.
func (s *ImmutableState) StateRoot(ctx context.Context, id common.Namespace) (hash.Hash, error) {
return s.getRoot(ctx, id, stateRootKeyFmt)
}

// IORoot returns the state root for a specific runtime.
func (s *ImmutableState) IORoot(ctx context.Context, id common.Namespace) (hash.Hash, error) {
return s.getRoot(ctx, id, ioRootKeyFmt)
}

// Runtimes returns the list of all roothash runtime states.
func (s *ImmutableState) Runtimes(ctx context.Context) ([]*roothash.RuntimeState, error) {
it := s.is.NewIterator(ctx)
Expand Down Expand Up @@ -193,8 +227,22 @@ func NewMutableState(tree mkvs.KeyValueTree) *MutableState {

// SetRuntimeState sets a runtime's roothash state.
func (s *MutableState) SetRuntimeState(ctx context.Context, state *roothash.RuntimeState) error {
err := s.ms.Insert(ctx, runtimeKeyFmt.Encode(&state.Runtime.ID), cbor.Marshal(state))
return api.UnavailableStateError(err)
if err := s.ms.Insert(ctx, runtimeKeyFmt.Encode(&state.Runtime.ID), cbor.Marshal(state)); err != nil {
return api.UnavailableStateError(err)
}

// Store the current state and I/O roots separately to make them easier to retrieve when
// constructing proofs of runtime state.
stateRoot, _ := state.CurrentBlock.Header.StateRoot.MarshalBinary()
ioRoot, _ := state.CurrentBlock.Header.IORoot.MarshalBinary()

if err := s.ms.Insert(ctx, stateRootKeyFmt.Encode(&state.Runtime.ID), stateRoot); err != nil {
return api.UnavailableStateError(err)
}
if err := s.ms.Insert(ctx, ioRootKeyFmt.Encode(&state.Runtime.ID), ioRoot); err != nil {
return api.UnavailableStateError(err)
}
return nil
}

// SetConsensusParameters sets roothash consensus parameters.
Expand Down
40 changes: 40 additions & 0 deletions go/consensus/tendermint/apps/roothash/state/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (

"github.com/oasisprotocol/oasis-core/go/common"
abciAPI "github.com/oasisprotocol/oasis-core/go/consensus/tendermint/api"
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"
)

func TestEvidence(t *testing.T) {
Expand Down Expand Up @@ -111,3 +113,41 @@ func TestEvidence(t *testing.T) {
require.NoError(err, "EvidenceHashExists")
require.True(b, "Not expired evidence hash should still exist")
}

func TestSeparateRuntimeRoots(t *testing.T) {
require := require.New(t)

now := time.Unix(1580461674, 0)
appState := abciAPI.NewMockApplicationState(&abciAPI.MockApplicationStateConfig{})
ctx := appState.NewContext(abciAPI.ContextBeginBlock, now)
defer ctx.Close()

st := NewMutableState(ctx.State())

var runtime registry.Runtime
err := runtime.ID.UnmarshalHex("8000000000000000000000000000000000000000000000000000000000000000")
require.NoError(err, "UnmarshalHex")

blk := block.NewGenesisBlock(runtime.ID, 0)
err = blk.Header.StateRoot.UnmarshalHex("0000000000000000000000000000000000000000000000000000000000000001")
require.NoError(err, "UnmarshalHex")
err = blk.Header.IORoot.UnmarshalHex("0000000000000000000000000000000000000000000000000000000000000002")
require.NoError(err, "UnmarshalHex")
err = st.SetRuntimeState(ctx, &api.RuntimeState{
Runtime: &runtime,
GenesisBlock: blk,
CurrentBlock: blk,
CurrentBlockHeight: 1,
LastNormalRound: 0,
LastNormalHeight: 1,
})
require.NoError(err, "SetRuntimeState")

stateRoot, err := st.StateRoot(ctx, runtime.ID)
require.NoError(err, "StateRoot")
require.EqualValues(blk.Header.StateRoot, stateRoot)

ioRoot, err := st.IORoot(ctx, runtime.ID)
require.NoError(err, "IORoot")
require.EqualValues(blk.Header.IORoot, ioRoot)
}

0 comments on commit 28586d0

Please sign in to comment.