Skip to content

Commit

Permalink
Merge pull request #2462 from oasislabs/matevz/feature/rename-blocks-…
Browse files Browse the repository at this point in the history
…field-in-genesis

roothash/api: replace Genesis.Blocks -> RuntimeStates
  • Loading branch information
matevz authored Dec 24, 2019
2 parents b4d818b + a3940c4 commit 87d942d
Show file tree
Hide file tree
Showing 23 changed files with 299 additions and 155 deletions.
7 changes: 7 additions & 0 deletions .changelog/2426.breaking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Refactoring of roothash genesis block for runtime.

- `RuntimeGenesis.Round` field was added to the roothash block for the runtime
which can be set by `--runtime.genesis.round` flag.
- The `RuntimeGenesis.StorageReceipt` field was replaced by `StorageReceipts` list,
one for each storage node.
- Support for `base64` encoding/decoding of `Bytes` was added in rust.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 19 additions & 19 deletions go/consensus/tendermint/apps/roothash/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import (
"github.com/oasislabs/oasis-core/go/common/crypto/signature"
"github.com/oasislabs/oasis-core/go/consensus/tendermint/abci"
registryState "github.com/oasislabs/oasis-core/go/consensus/tendermint/apps/registry/state"
genesisApi "github.com/oasislabs/oasis-core/go/genesis/api"
roothash "github.com/oasislabs/oasis-core/go/roothash/api"
"github.com/oasislabs/oasis-core/go/roothash/api/block"
genesisAPI "github.com/oasislabs/oasis-core/go/genesis/api"
"github.com/oasislabs/oasis-core/go/registry/api"
roothashAPI "github.com/oasislabs/oasis-core/go/roothash/api"
storageAPI "github.com/oasislabs/oasis-core/go/storage/api"
)

func (app *rootHashApplication) InitChain(ctx *abci.Context, request types.RequestInitChain, doc *genesisApi.Document) error {
func (app *rootHashApplication) InitChain(ctx *abci.Context, request types.RequestInitChain, doc *genesisAPI.Document) error {
st := doc.RootHash

// The per-runtime roothash state is done primarily via DeliverTx, but
Expand All @@ -35,26 +36,25 @@ func (app *rootHashApplication) InitChain(ctx *abci.Context, request types.Reque
return nil
}

func (rq *rootHashQuerier) Genesis(ctx context.Context) (*roothash.Genesis, error) {
func (rq *rootHashQuerier) Genesis(ctx context.Context) (*roothashAPI.Genesis, error) {
runtimes := rq.state.Runtimes()

// Get per-runtime blocks.
blocks := make(map[signature.PublicKey]*block.Block)
// Get per-runtime states.
rtStates := make(map[signature.PublicKey]*api.RuntimeGenesis)
for _, rt := range runtimes {
blk := *rt.CurrentBlock
// Header should be a normal header for genesis.
blk.Header.HeaderType = block.Normal
// There should be no previous hash.
blk.Header.PreviousHash.Empty()
// No messages.
blk.Header.Messages = nil
// No storage signatures.
blk.Header.StorageSignatures = []signature.Signature{}
blocks[rt.Runtime.ID] = &blk
rtState := api.RuntimeGenesis{
StateRoot: rt.CurrentBlock.Header.StateRoot,
// State is always empty in Genesis regardless of StateRoot.
State: storageAPI.WriteLog{},
StorageReceipts: []signature.Signature{},
Round: rt.CurrentBlock.Header.Round,
}

rtStates[rt.Runtime.ID] = &rtState
}

genesis := &roothash.Genesis{
Blocks: blocks,
genesis := &roothashAPI.Genesis{
RuntimeStates: rtStates,
}
return genesis, nil
}
18 changes: 12 additions & 6 deletions go/consensus/tendermint/apps/roothash/roothash.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,12 +382,18 @@ func (app *rootHashApplication) onNewRuntime(ctx *abci.Context, runtime *registr
}

// Create genesis block.
genesisBlock := genesis.Blocks[runtime.ID]
if genesisBlock == nil {
now := ctx.Now().Unix()
genesisBlock = block.NewGenesisBlock(runtime.ID, uint64(now))
if !runtime.Genesis.StateRoot.IsEmpty() {
genesisBlock.Header.StateRoot = runtime.Genesis.StateRoot
now := ctx.Now().Unix()
genesisBlock := block.NewGenesisBlock(runtime.ID, uint64(now))
// Fill the Header fields with Genesis runtime states, if this was called during InitChain().
genesisBlock.Header.Round = runtime.Genesis.Round
genesisBlock.Header.StateRoot = runtime.Genesis.StateRoot
genesisBlock.Header.StorageSignatures = runtime.Genesis.StorageReceipts
if ctx.IsInitChain() {
genesisRts := genesis.RuntimeStates[runtime.ID]
if genesisRts != nil {
genesisBlock.Header.Round = genesisRts.Round
genesisBlock.Header.StateRoot = genesisRts.StateRoot
genesisBlock.Header.StorageSignatures = runtime.Genesis.StorageReceipts
}
}

Expand Down
125 changes: 68 additions & 57 deletions go/genesis/tests/tester.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ import (
keymanager "github.com/oasislabs/oasis-core/go/keymanager/api"
cmdFlags "github.com/oasislabs/oasis-core/go/oasis-node/cmd/common/flags"
registry "github.com/oasislabs/oasis-core/go/registry/api"
"github.com/oasislabs/oasis-core/go/roothash/api/block"
roothashAPI "github.com/oasislabs/oasis-core/go/roothash/api"
scheduler "github.com/oasislabs/oasis-core/go/scheduler/api"
staking "github.com/oasislabs/oasis-core/go/staking/api"
stakingTests "github.com/oasislabs/oasis-core/go/staking/tests/debug"
storage "github.com/oasislabs/oasis-core/go/storage/api"
)

var testDoc = &genesis.Document{
Expand Down Expand Up @@ -111,6 +112,7 @@ func TestGenesisSanityCheck(t *testing.T) {

// First, set up a few things we'll need in the tests below.
signer := memorySigner.NewTestSigner("genesis sanity checks signer")
signer2 := memorySigner.NewTestSigner("another genesis sanity checks signer")
validPK := signer.Public()

invalidPK := hex2pk("c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a")
Expand All @@ -119,6 +121,8 @@ func TestGenesisSanityCheck(t *testing.T) {

var emptyHash hash.Hash
emptyHash.Empty()
var nonEmptyHash hash.Hash
_ = nonEmptyHash.UnmarshalHex("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef")

// Note that this test entity has no nodes by design, those will be added
// later by various tests.
Expand Down Expand Up @@ -233,74 +237,81 @@ func TestGenesisSanityCheck(t *testing.T) {
require.Error(d.SanityCheck(), "invalid keymanager ID should be rejected")

// Test roothash genesis checks.
d = *testDoc
d.RootHash.Blocks = make(map[signature.PublicKey]*block.Block)
d.RootHash.Blocks[validPK] = &block.Block{
Header: block.Header{
HeaderType: 123,
},
// First we define a helper function for calling the SanityCheck() on RuntimeStates.
rtsSanityCheck := func(g roothashAPI.Genesis, isGenesis bool) error {
for _, rts := range g.RuntimeStates {
if err = rts.SanityCheck(isGenesis); err != nil {
return err
}
}
return nil
}
require.Error(d.SanityCheck(), "invalid block header should be rejected")

d = *testDoc
d.RootHash.Blocks = make(map[signature.PublicKey]*block.Block)
d.RootHash.Blocks[validPK] = &block.Block{
Header: block.Header{
HeaderType: block.Normal,
PreviousHash: hash.Hash{},
},
d.RootHash.RuntimeStates = make(map[signature.PublicKey]*registry.RuntimeGenesis)
d.RootHash.RuntimeStates[validPK] = &registry.RuntimeGenesis{
StateRoot: nonEmptyHash,
// Empty list of storage receipts.
StorageReceipts: []signature.Signature{},
}
require.Error(d.SanityCheck(), "invalid previous hash should be rejected")
require.Error(rtsSanityCheck(d.RootHash, false), "empty StorageReceipts for StateRoot should be rejected")
require.NoError(rtsSanityCheck(d.RootHash, true), "empty StorageReceipts for StateRoot should be ignored, if isGenesis=true")

d = *testDoc
d.RootHash.Blocks = make(map[signature.PublicKey]*block.Block)
d.RootHash.Blocks[validPK] = &block.Block{
Header: block.Header{
HeaderType: block.Normal,
PreviousHash: emptyHash,
Timestamp: uint64(time.Now().Unix() + 62*60),
},
d.RootHash.RuntimeStates = make(map[signature.PublicKey]*registry.RuntimeGenesis)
d.RootHash.RuntimeStates[validPK] = &registry.RuntimeGenesis{
StateRoot: nonEmptyHash,
// List with one empty (invalid) storage receipt.
StorageReceipts: []signature.Signature{signature.Signature{}},
}
require.Error(d.SanityCheck(), "invalid timestamp should be rejected")

d = *testDoc
sigCtx := signature.NewContext("genesis sanity check storage sig test")
sig, grr := signature.Sign(signer, sigCtx, []byte{1, 2, 3})
require.NoError(grr, "should be able to sign")
d.RootHash.Blocks = make(map[signature.PublicKey]*block.Block)
d.RootHash.Blocks[validPK] = &block.Block{
Header: block.Header{
HeaderType: block.Normal,
PreviousHash: emptyHash,
Timestamp: uint64(time.Now().Unix()),
StorageSignatures: []signature.Signature{*sig},
},
require.Error(rtsSanityCheck(d.RootHash, false), "empty StorageReceipt for StateRoot should be rejected")
require.NoError(rtsSanityCheck(d.RootHash, true), "empty StorageReceipt for StateRoot should be ignored, if isGenesis=true")

d = *testDoc
signature.SetChainContext("test: oasis-core tests")
stateRootSig, _ := signature.Sign(signer, storage.ReceiptSignatureContext, nonEmptyHash[:])
stateRootSig2, _ := signature.Sign(signer2, storage.ReceiptSignatureContext, nonEmptyHash[:])
wrongSig, _ := signature.Sign(signer, storage.ReceiptSignatureContext, []byte{1, 2, 3})
d.RootHash.RuntimeStates = make(map[signature.PublicKey]*registry.RuntimeGenesis)
d.RootHash.RuntimeStates[validPK] = &registry.RuntimeGenesis{
StateRoot: nonEmptyHash,
// Some non-empty signature, but not related to StateRoot.
StorageReceipts: []signature.Signature{*wrongSig, *stateRootSig, *stateRootSig2},
}
require.Error(d.SanityCheck(), "non-empty storage signature array should be rejected")
require.Error(rtsSanityCheck(d.RootHash, false), "some incorrect StorageReceipt for StateRoot should be rejected")
require.NoError(rtsSanityCheck(d.RootHash, true), "some incorrect StorageReceipt for StateRoot should be ignored, if isGenesis=true")

d = *testDoc
d.RootHash.Blocks = make(map[signature.PublicKey]*block.Block)
d.RootHash.Blocks[validPK] = &block.Block{
Header: block.Header{
HeaderType: block.Normal,
PreviousHash: emptyHash,
Timestamp: uint64(time.Now().Unix()),
StorageSignatures: []signature.Signature{},
Messages: []*block.Message{nil, nil, nil},
},
d.RootHash.RuntimeStates = make(map[signature.PublicKey]*registry.RuntimeGenesis)
d.RootHash.RuntimeStates[validPK] = &registry.RuntimeGenesis{
StateRoot: nonEmptyHash,
StorageReceipts: []signature.Signature{*stateRootSig, *stateRootSig2},
}
require.Error(d.SanityCheck(), "non-empty roothash message array should be rejected")

d = *testDoc
d.RootHash.Blocks = make(map[signature.PublicKey]*block.Block)
d.RootHash.Blocks[validPK] = &block.Block{
Header: block.Header{
HeaderType: block.Normal,
PreviousHash: emptyHash,
Timestamp: uint64(time.Now().Unix()),
},
require.NoError(rtsSanityCheck(d.RootHash, false), "non-empty StateRoot with all correct StorageReceipts should pass")
require.NoError(rtsSanityCheck(d.RootHash, true), "non-empty StateRoot with all correct StorageReceipts should pass, if isGenesis=true")

d = *testDoc
nonEmptyState := storage.WriteLog{storage.LogEntry{
Key: []byte{1, 2, 3},
Value: []byte{1, 2, 3},
}}
d.RootHash.RuntimeStates = make(map[signature.PublicKey]*registry.RuntimeGenesis)
d.RootHash.RuntimeStates[validPK] = &registry.RuntimeGenesis{
State: nonEmptyState,
StateRoot: nonEmptyHash,
StorageReceipts: []signature.Signature{*wrongSig, *stateRootSig, *stateRootSig2},
}
require.NoError(rtsSanityCheck(d.RootHash, false), "non-empty StateRoot with non-empty State and some invalid StorageReceipt should pass")
require.NoError(rtsSanityCheck(d.RootHash, true), "non-empty StateRoot with non-empty State and some invalid StorageReceipt should pass, if isGenesis=true")

d.RootHash.RuntimeStates = make(map[signature.PublicKey]*registry.RuntimeGenesis)
d.RootHash.RuntimeStates[validPK] = &registry.RuntimeGenesis{
State: nonEmptyState,
StateRoot: nonEmptyHash,
StorageReceipts: []signature.Signature{*stateRootSig, *stateRootSig2},
}
require.NoError(d.SanityCheck(), "well-formed block should pass")
require.NoError(rtsSanityCheck(d.RootHash, false), "non-empty StateRoot with non-empty State and all valid StorageReceipts should pass")
require.NoError(rtsSanityCheck(d.RootHash, true), "non-empty StateRoot with non-empty State and all valid StorageReceipts should pass, if isGenesis=true")

// Test registry genesis checks.
d = *testDoc
Expand Down
1 change: 1 addition & 0 deletions go/oasis-net-runner/fixtures/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ func NewDefaultFixture() (*oasis.NetworkFixture, error) {
},
Storage: registry.StorageParameters{GroupSize: 1},
GenesisState: viper.GetString(cfgRuntimeGenesisState),
GenesisRound: 0,
},
},
Validators: []oasis.ValidatorFixture{
Expand Down
14 changes: 9 additions & 5 deletions go/oasis-node/cmd/debug/storage/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,20 @@ func doExport(cmd *cobra.Command, args []string) {
defer storageBackend.Cleanup()

// For each storage root.
for runtimeID, blk := range genesisDoc.RootHash.Blocks {
for runtimeID, rtg := range genesisDoc.RootHash.RuntimeStates {
logger.Info("fetching checkpoint write log",
"runtime_id", runtimeID,
)

// Use RuntimeID for the Roothash namespace.
var ns common.Namespace
_ = ns.UnmarshalBinary(runtimeID[:])

// Get the checkpoint iterator.
root := storageAPI.Root{
Namespace: blk.Header.Namespace,
Round: blk.Header.Round,
Hash: blk.Header.StateRoot,
Namespace: ns,
Round: rtg.Round,
Hash: rtg.StateRoot,
}
it, err := storageBackend.GetCheckpoint(context.Background(),
&storageAPI.GetCheckpointRequest{
Expand All @@ -108,7 +112,7 @@ func doExport(cmd *cobra.Command, args []string) {

fn := fmt.Sprintf("storage-dump-%v-%d.json",
runtimeID.String(),
blk.Header.Round,
rtg.Round,
)
fn = filepath.Join(destDir, fn)
if err = exportIterator(fn, &root, it); err != nil {
Expand Down
27 changes: 12 additions & 15 deletions go/oasis-node/cmd/genesis/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import (
cmdGrpc "github.com/oasislabs/oasis-core/go/oasis-node/cmd/common/grpc"
registry "github.com/oasislabs/oasis-core/go/registry/api"
roothash "github.com/oasislabs/oasis-core/go/roothash/api"
"github.com/oasislabs/oasis-core/go/roothash/api/block"
scheduler "github.com/oasislabs/oasis-core/go/scheduler/api"
staking "github.com/oasislabs/oasis-core/go/staking/api"
stakingTests "github.com/oasislabs/oasis-core/go/staking/tests/debug"
Expand Down Expand Up @@ -406,39 +405,37 @@ func AppendRegistryState(doc *genesis.Document, entities, runtimes, nodes []stri
// of exported roothash blocks.
func AppendRootHashState(doc *genesis.Document, exports []string, l *logging.Logger) error {
rootSt := roothash.Genesis{
Blocks: make(map[signature.PublicKey]*block.Block),
RuntimeStates: make(map[signature.PublicKey]*registry.RuntimeGenesis),
}

for _, v := range exports {
b, err := ioutil.ReadFile(v)
if err != nil {
l.Error("failed to load genesis roothash blocks",
l.Error("failed to load genesis roothash runtime states",
"err", err,
"filename", v,
)
return err
}

var blocks []*block.Block
if err = json.Unmarshal(b, &blocks); err != nil {
l.Error("failed to parse genesis roothash blocks",
var rtStates map[signature.PublicKey]*registry.RuntimeGenesis
if err = json.Unmarshal(b, &rtStates); err != nil {
l.Error("failed to parse genesis roothash runtime states",
"err", err,
"filename", v,
)
return err
}

for _, blk := range blocks {
var key signature.PublicKey
copy(key[:], blk.Header.Namespace[:])
if _, ok := rootSt.Blocks[key]; ok {
l.Error("duplicate genesis roothash block",
"runtime_id", blk.Header.Namespace,
"block", blk,
for key, rtg := range rtStates {
if _, ok := rootSt.RuntimeStates[key]; ok {
l.Error("duplicate genesis roothash runtime state",
"runtime_id", key,
"block", rtg,
)
return errors.New("duplicate genesis roothash block")
return errors.New("duplicate genesis roothash runtime states")
}
rootSt.Blocks[key] = blk
rootSt.RuntimeStates[key] = rtg
}
}

Expand Down
3 changes: 3 additions & 0 deletions go/oasis-node/cmd/registry/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const (
CfgID = "runtime.id"
CfgTEEHardware = "runtime.tee_hardware"
CfgGenesisState = "runtime.genesis.state"
CfgGenesisRound = "runtime.genesis.round"
CfgKind = "runtime.kind"
CfgKeyManager = "runtime.keymanager"
cfgOutput = "runtime.genesis.file"
Expand Down Expand Up @@ -264,6 +265,7 @@ func runtimeFromFlags() (*registry.Runtime, signature.Signer, error) {

// TODO: Support root upload when registering.
gen := registry.RuntimeGenesis{}
gen.Round = viper.GetUint64(CfgGenesisRound)
switch state := viper.GetString(CfgGenesisState); state {
case "":
gen.StateRoot.Empty()
Expand Down Expand Up @@ -431,6 +433,7 @@ func init() {
runtimeFlags.String(CfgID, "", "Runtime ID")
runtimeFlags.String(CfgTEEHardware, "invalid", "Type of TEE hardware. Supported values are \"invalid\" and \"intel-sgx\"")
runtimeFlags.String(CfgGenesisState, "", "Runtime state at genesis")
runtimeFlags.Uint64(CfgGenesisRound, 0, "Runtime round at genesis")
runtimeFlags.String(CfgKeyManager, "", "Key Manager Runtime ID")
runtimeFlags.String(CfgKind, "compute", "Kind of runtime. Supported values are \"compute\" and \"keymanager\"")
runtimeFlags.String(CfgVersion, "", "Runtime version. Value is 64-bit hex e.g. 0x0000000100020003 for 1.2.3")
Expand Down
Loading

0 comments on commit 87d942d

Please sign in to comment.