Skip to content

Commit

Permalink
Merge pull request #3782 from oasisprotocol/kostko/feature/roothash-r…
Browse files Browse the repository at this point in the history
…t-quick-state-root

go/consensus: Store runtime state roots in a single key
  • Loading branch information
kostko authored Mar 16, 2021
2 parents d06b845 + fec78f0 commit ac7e3d1
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 ac7e3d1

Please sign in to comment.