Skip to content

Commit

Permalink
roothash: Store past runtime state and I/O roots in consensus state
Browse files Browse the repository at this point in the history
  • Loading branch information
abukosek committed Sep 13, 2023
1 parent cd4f98d commit 31f43e9
Show file tree
Hide file tree
Showing 8 changed files with 606 additions and 1 deletion.
6 changes: 6 additions & 0 deletions .changelog/5359.breaking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
roothash: Store past runtime state and I/O roots in consensus state

A new roothash consensus parameter was added (`MaxPastRootsStored`),
which enables storing runtime state and I/O roots for the past
`MaxPastRootsStored` rounds in the consensus state.
This enables easier cross-runtime communication.
331 changes: 331 additions & 0 deletions go/consensus/cometbft/apps/roothash/messages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import (
abciAPI "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api"
roothashState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/roothash/state"
governance "github.com/oasisprotocol/oasis-core/go/governance/api"
registry "github.com/oasisprotocol/oasis-core/go/registry/api"
roothash "github.com/oasisprotocol/oasis-core/go/roothash/api"
"github.com/oasisprotocol/oasis-core/go/roothash/api/block"
)

func TestChangeParameters(t *testing.T) {
Expand Down Expand Up @@ -88,3 +90,332 @@ func TestChangeParameters(t *testing.T) {
require.EqualError(err, "roothash: failed to validate consensus parameter changes: consensus parameter changes should not be empty")
})
}

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

// Prepare context.
appState := abciAPI.NewMockApplicationState(&abciAPI.MockApplicationStateConfig{})
ctx := appState.NewContext(abciAPI.ContextEndBlock)

// Setup state.
state := roothashState.NewMutableState(ctx.State())
app := &rootHashApplication{
state: appState,
}
params := &roothash.ConsensusParameters{
MaxPastRootsStored: 2,
}
err := state.SetConsensusParameters(ctx, params)
require.NoError(err, "setting consensus parameters should succeed")

cp, err := state.ConsensusParameters(ctx)
require.NoError(err, "fetching consensus parameters should succeed")
require.EqualValues(2, cp.MaxPastRootsStored, "consensus parameters should have been set")

// Switch context.
ctx.Close()
ctx = appState.NewContext(abciAPI.ContextBeginBlock)

// Round 0 (init, 1 root pair stored).
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 = state.SetRuntimeState(ctx, &roothash.RuntimeState{
Runtime: &runtime,
GenesisBlock: blk,
CurrentBlock: blk,
CurrentBlockHeight: 1,
LastNormalRound: 0,
LastNormalHeight: 1,
})
require.NoError(err, "SetRuntimeState")

roots, err := state.PastRoots(ctx, runtime.ID)
require.NoError(err, "PastRoots")
require.EqualValues(1, len(roots))
require.EqualValues(len(roots), state.PastRootsCount(ctx, runtime.ID))
require.EqualValues(blk.Header.StateRoot, roots[0].StateRoot)
require.EqualValues(blk.Header.IORoot, roots[0].IORoot)

rfr0, err := state.PastRootsForRound(ctx, runtime.ID, 0)
require.NoError(err, "PastRootsForRound 0")
require.EqualValues(blk.Header.StateRoot, rfr0.StateRoot)
require.EqualValues(blk.Header.IORoot, rfr0.IORoot)

// Round 1 (2 root pairs stored).
blk1 := block.NewEmptyBlock(blk, 1, block.Normal)
err = blk1.Header.StateRoot.UnmarshalHex("0000000000000000000000000000000000000000000000000000000000000003")
require.NoError(err, "UnmarshalHex")
err = blk1.Header.IORoot.UnmarshalHex("0000000000000000000000000000000000000000000000000000000000000004")
require.NoError(err, "UnmarshalHex")
err = state.SetRuntimeState(ctx, &roothash.RuntimeState{
Runtime: &runtime,
GenesisBlock: blk,
CurrentBlock: blk1,
CurrentBlockHeight: 2,
LastNormalRound: 1,
LastNormalHeight: 2,
})
require.NoError(err, "SetRuntimeState")

roots, err = state.PastRoots(ctx, runtime.ID)
require.NoError(err, "PastRoots")
require.EqualValues(2, len(roots))
require.EqualValues(len(roots), state.PastRootsCount(ctx, runtime.ID))
require.EqualValues(blk.Header.StateRoot, roots[0].StateRoot)
require.EqualValues(blk.Header.IORoot, roots[0].IORoot)
require.EqualValues(blk1.Header.StateRoot, roots[1].StateRoot)
require.EqualValues(blk1.Header.IORoot, roots[1].IORoot)

// Switch context.
ctx.Close()
ctx = appState.NewContext(abciAPI.ContextEndBlock)

// Prepare proposal for reducing the number of roots stored.
newMaxPRS := uint32(1)
changes := roothash.ConsensusParameterChanges{
MaxPastRootsStored: &newMaxPRS,
}
proposal := governance.ChangeParametersProposal{
Module: roothash.ModuleName,
Changes: cbor.Marshal(changes),
}

// Apply proposal.
res, err := app.changeParameters(ctx, &proposal, true)
require.NoError(err, "changing consensus parameters should succeed")
require.Equal(struct{}{}, res)

cp, err = state.ConsensusParameters(ctx)
require.NoError(err, "fetching consensus parameters should succeed")
require.Equal(newMaxPRS, cp.MaxPastRootsStored, "consensus parameters should change")

// Switch context.
ctx.Close()
ctx = appState.NewContext(abciAPI.ContextBeginBlock)

// Round 2 (1 root pair stored).
blk2 := block.NewEmptyBlock(blk1, 2, block.Normal)
err = blk2.Header.StateRoot.UnmarshalHex("0000000000000000000000000000000000000000000000000000000000000003")
require.NoError(err, "UnmarshalHex")
err = blk2.Header.IORoot.UnmarshalHex("0000000000000000000000000000000000000000000000000000000000000004")
require.NoError(err, "UnmarshalHex")
err = state.SetRuntimeState(ctx, &roothash.RuntimeState{
Runtime: &runtime,
GenesisBlock: blk,
CurrentBlock: blk2,
CurrentBlockHeight: 3,
LastNormalRound: 2,
LastNormalHeight: 3,
})
require.NoError(err, "SetRuntimeState")

roots, err = state.PastRoots(ctx, runtime.ID)
require.NoError(err, "PastRoots")
require.EqualValues(1, len(roots))
require.EqualValues(len(roots), state.PastRootsCount(ctx, runtime.ID))
require.EqualValues(blk2.Header.StateRoot, roots[2].StateRoot)
require.EqualValues(blk2.Header.IORoot, roots[2].IORoot)

rfr1, err := state.PastRootsForRound(ctx, runtime.ID, 1)
require.NoError(err, "PastRootsForRound 1")
require.Nil(rfr1, "roots for round 1 should have been deleted after changing the max")

// Round 3 (1 root pair stored).
blk3 := block.NewEmptyBlock(blk2, 3, block.Normal)
err = blk3.Header.StateRoot.UnmarshalHex("0000000000000000000000000000000000000000000000000000000000000004")
require.NoError(err, "UnmarshalHex")
err = blk3.Header.IORoot.UnmarshalHex("0000000000000000000000000000000000000000000000000000000000000005")
require.NoError(err, "UnmarshalHex")
err = state.SetRuntimeState(ctx, &roothash.RuntimeState{
Runtime: &runtime,
GenesisBlock: blk,
CurrentBlock: blk3,
CurrentBlockHeight: 4,
LastNormalRound: 3,
LastNormalHeight: 4,
})
require.NoError(err, "SetRuntimeState")

roots, err = state.PastRoots(ctx, runtime.ID)
require.NoError(err, "PastRoots")
require.EqualValues(1, len(roots))
require.EqualValues(len(roots), state.PastRootsCount(ctx, runtime.ID))
require.EqualValues(blk3.Header.StateRoot, roots[3].StateRoot)
require.EqualValues(blk3.Header.IORoot, roots[3].IORoot)

rfr2, err := state.PastRootsForRound(ctx, runtime.ID, 2)
require.NoError(err, "PastRootsForRound 2")
require.Nil(rfr2, "roots for round 2 should have been deleted in round 3")

// Switch context.
ctx.Close()
ctx = appState.NewContext(abciAPI.ContextEndBlock)

// Prepare proposal for increasing the number of roots stored.
newMaxPRS = uint32(2)
changes = roothash.ConsensusParameterChanges{
MaxPastRootsStored: &newMaxPRS,
}
proposal = governance.ChangeParametersProposal{
Module: roothash.ModuleName,
Changes: cbor.Marshal(changes),
}

// Apply proposal.
res, err = app.changeParameters(ctx, &proposal, true)
require.NoError(err, "changing consensus parameters should succeed")
require.Equal(struct{}{}, res)

cp, err = state.ConsensusParameters(ctx)
require.NoError(err, "fetching consensus parameters should succeed")
require.Equal(newMaxPRS, cp.MaxPastRootsStored, "consensus parameters should change")

// Switch context.
ctx.Close()
ctx = appState.NewContext(abciAPI.ContextBeginBlock)

// Round 4 (2 root pairs stored).
blk4 := block.NewEmptyBlock(blk3, 4, block.Normal)
err = blk4.Header.StateRoot.UnmarshalHex("0000000000000000000000000000000000000000000000000000000000000005")
require.NoError(err, "UnmarshalHex")
err = blk4.Header.IORoot.UnmarshalHex("0000000000000000000000000000000000000000000000000000000000000006")
require.NoError(err, "UnmarshalHex")
err = state.SetRuntimeState(ctx, &roothash.RuntimeState{
Runtime: &runtime,
GenesisBlock: blk,
CurrentBlock: blk4,
CurrentBlockHeight: 5,
LastNormalRound: 4,
LastNormalHeight: 5,
})
require.NoError(err, "SetRuntimeState")

roots, err = state.PastRoots(ctx, runtime.ID)
require.NoError(err, "PastRoots")
require.EqualValues(2, len(roots))
require.EqualValues(len(roots), state.PastRootsCount(ctx, runtime.ID))
require.EqualValues(blk3.Header.StateRoot, roots[3].StateRoot)
require.EqualValues(blk3.Header.IORoot, roots[3].IORoot)
require.EqualValues(blk4.Header.StateRoot, roots[4].StateRoot)
require.EqualValues(blk4.Header.IORoot, roots[4].IORoot)

// Round 5 (2 root pairs stored).
blk5 := block.NewEmptyBlock(blk4, 5, block.Normal)
err = blk5.Header.StateRoot.UnmarshalHex("0000000000000000000000000000000000000000000000000000000000000006")
require.NoError(err, "UnmarshalHex")
err = blk5.Header.IORoot.UnmarshalHex("0000000000000000000000000000000000000000000000000000000000000007")
require.NoError(err, "UnmarshalHex")
err = state.SetRuntimeState(ctx, &roothash.RuntimeState{
Runtime: &runtime,
GenesisBlock: blk,
CurrentBlock: blk5,
CurrentBlockHeight: 6,
LastNormalRound: 5,
LastNormalHeight: 6,
})
require.NoError(err, "SetRuntimeState")

roots, err = state.PastRoots(ctx, runtime.ID)
require.NoError(err, "PastRoots")
require.EqualValues(2, len(roots))
require.EqualValues(len(roots), state.PastRootsCount(ctx, runtime.ID))
require.EqualValues(blk4.Header.StateRoot, roots[4].StateRoot)
require.EqualValues(blk4.Header.IORoot, roots[4].IORoot)
require.EqualValues(blk5.Header.StateRoot, roots[5].StateRoot)
require.EqualValues(blk5.Header.IORoot, roots[5].IORoot)

rfr3, err := state.PastRootsForRound(ctx, runtime.ID, 3)
require.NoError(err, "PastRootsForRound 3")
require.Nil(rfr3, "roots for round 3 should have been deleted in round 5")

// Round 6 (2 root pairs stored).
blk6 := block.NewEmptyBlock(blk5, 6, block.Normal)
err = blk6.Header.StateRoot.UnmarshalHex("0000000000000000000000000000000000000000000000000000000000000007")
require.NoError(err, "UnmarshalHex")
err = blk6.Header.IORoot.UnmarshalHex("0000000000000000000000000000000000000000000000000000000000000008")
require.NoError(err, "UnmarshalHex")
err = state.SetRuntimeState(ctx, &roothash.RuntimeState{
Runtime: &runtime,
GenesisBlock: blk,
CurrentBlock: blk6,
CurrentBlockHeight: 7,
LastNormalRound: 6,
LastNormalHeight: 7,
})
require.NoError(err, "SetRuntimeState")

roots, err = state.PastRoots(ctx, runtime.ID)
require.NoError(err, "PastRoots")
require.EqualValues(2, len(roots))
require.EqualValues(len(roots), state.PastRootsCount(ctx, runtime.ID))
require.EqualValues(blk5.Header.StateRoot, roots[5].StateRoot)
require.EqualValues(blk5.Header.IORoot, roots[5].IORoot)
require.EqualValues(blk6.Header.StateRoot, roots[6].StateRoot)
require.EqualValues(blk6.Header.IORoot, roots[6].IORoot)

rfr4, err := state.PastRootsForRound(ctx, runtime.ID, 4)
require.NoError(err, "PastRootsForRound 4")
require.Nil(rfr4, "roots for round 4 should have been deleted in round 6")

// Switch context.
ctx.Close()
ctx = appState.NewContext(abciAPI.ContextEndBlock)

// Prepare proposal for disabling storing the roots.
newMaxPRS = uint32(0)
changes = roothash.ConsensusParameterChanges{
MaxPastRootsStored: &newMaxPRS,
}
proposal = governance.ChangeParametersProposal{
Module: roothash.ModuleName,
Changes: cbor.Marshal(changes),
}

// Apply proposal.
res, err = app.changeParameters(ctx, &proposal, true)
require.NoError(err, "changing consensus parameters should succeed")
require.Equal(struct{}{}, res)

cp, err = state.ConsensusParameters(ctx)
require.NoError(err, "fetching consensus parameters should succeed")
require.Equal(newMaxPRS, cp.MaxPastRootsStored, "consensus parameters should change")

// Switch context.
ctx.Close()
ctx = appState.NewContext(abciAPI.ContextBeginBlock)

// Round 7 (0 root pairs stored).
blk7 := block.NewEmptyBlock(blk6, 7, block.Normal)
err = blk7.Header.StateRoot.UnmarshalHex("0000000000000000000000000000000000000000000000000000000000000008")
require.NoError(err, "UnmarshalHex")
err = blk7.Header.IORoot.UnmarshalHex("0000000000000000000000000000000000000000000000000000000000000009")
require.NoError(err, "UnmarshalHex")
err = state.SetRuntimeState(ctx, &roothash.RuntimeState{
Runtime: &runtime,
GenesisBlock: blk,
CurrentBlock: blk7,
CurrentBlockHeight: 8,
LastNormalRound: 7,
LastNormalHeight: 8,
})
require.NoError(err, "SetRuntimeState")

roots, err = state.PastRoots(ctx, runtime.ID)
require.NoError(err, "PastRoots")
require.EqualValues(0, len(roots))
require.EqualValues(len(roots), state.PastRootsCount(ctx, runtime.ID))

rfr7, err := state.PastRootsForRound(ctx, runtime.ID, 7)
require.NoError(err, "PastRootsForRound 7")
require.Nil(rfr7, "roots for round 7 should not exist")

ctx.Close()
}
Loading

0 comments on commit 31f43e9

Please sign in to comment.