From d5d902ff5b20b4556d882e457efffe4959802506 Mon Sep 17 00:00:00 2001 From: ptrus Date: Fri, 9 Oct 2020 13:36:16 +0200 Subject: [PATCH] go/roothash: Optional root hashes in compute commitments headers --- .../tendermint/apps/roothash/roothash.go | 4 +-- go/oasis-node/cmd/debug/byzantine/executor.go | 6 ++-- go/roothash/api/commitment/executor.go | 25 +++++++++----- go/roothash/api/commitment/pool.go | 34 ++++++++++++++++++- go/roothash/api/commitment/pool_test.go | 31 +++++++++++------ go/roothash/tests/tester.go | 4 +-- go/runtime/host/mock/mock.go | 4 +-- go/worker/compute/executor/committee/node.go | 11 +++--- 8 files changed, 86 insertions(+), 33 deletions(-) diff --git a/go/consensus/tendermint/apps/roothash/roothash.go b/go/consensus/tendermint/apps/roothash/roothash.go index 5441f64ee2d..35078314763 100644 --- a/go/consensus/tendermint/apps/roothash/roothash.go +++ b/go/consensus/tendermint/apps/roothash/roothash.go @@ -454,8 +454,8 @@ func (app *rootHashApplication) tryFinalizeExecutorCommits( hdr := commit.ToDDResult().(commitment.ComputeResultsHeader) blk := block.NewEmptyBlock(rtState.CurrentBlock, uint64(ctx.Now().Unix()), block.Normal) - blk.Header.IORoot = hdr.IORoot - blk.Header.StateRoot = hdr.StateRoot + blk.Header.IORoot = *hdr.IORoot + blk.Header.StateRoot = *hdr.StateRoot // Messages omitted on purpose. // Timeout will be cleared by caller. diff --git a/go/oasis-node/cmd/debug/byzantine/executor.go b/go/oasis-node/cmd/debug/byzantine/executor.go index c041e44c03c..3b24ead6e85 100644 --- a/go/oasis-node/cmd/debug/byzantine/executor.go +++ b/go/oasis-node/cmd/debug/byzantine/executor.go @@ -260,8 +260,8 @@ func (cbc *computeBatchContext) createCommitment(id *identity.Identity, rak sign header := commitment.ComputeResultsHeader{ Round: cbc.bd.Header.Round + 1, PreviousHash: cbc.bd.Header.EncodedHash(), - IORoot: cbc.newIORoot, - StateRoot: cbc.newStateRoot, + IORoot: &cbc.newIORoot, + StateRoot: &cbc.newStateRoot, // TODO: allow script to set roothash messages? Messages: []*block.Message{}, } @@ -278,7 +278,7 @@ func (cbc *computeBatchContext) createCommitment(id *identity.Identity, rak sign return fmt.Errorf("signature Sign RAK: %w", err) } - computeBody.RakSig = rakSig.Signature + computeBody.RakSig = &rakSig.Signature } var err error cbc.commit, err = commitment.SignExecutorCommitment(id.NodeSigner, computeBody) diff --git a/go/roothash/api/commitment/executor.go b/go/roothash/api/commitment/executor.go index b66b81e4867..13010715700 100644 --- a/go/roothash/api/commitment/executor.go +++ b/go/roothash/api/commitment/executor.go @@ -32,11 +32,14 @@ var ( // // Keep the roothash RAK validation in sync with changes to this structure. type ComputeResultsHeader struct { - Round uint64 `json:"round"` - PreviousHash hash.Hash `json:"previous_hash"` - IORoot hash.Hash `json:"io_root"` - StateRoot hash.Hash `json:"state_root"` - Messages []*block.Message `json:"messages"` + Round uint64 `json:"round"` + PreviousHash hash.Hash `json:"previous_hash"` + + // Optional fields (may be absent for failure indication). + + IORoot *hash.Hash `json:"io_root,omitempty"` + StateRoot *hash.Hash `json:"state_root,omitempty"` + Messages []*block.Message `json:"messages,omitempty"` } // IsParentOf returns true iff the header is the parent of a child header. @@ -78,8 +81,8 @@ type ComputeBody struct { // Optional fields (may be absent for failure indication). - StorageSignatures []signature.Signature `json:"storage_signatures,omitempty"` - RakSig signature.RawSignature `json:"rak_sig,omitempty"` + StorageSignatures []signature.Signature `json:"storage_signatures,omitempty"` + RakSig *signature.RawSignature `json:"rak_sig,omitempty"` } // VerifyTxnSchedSignature rebuilds the batch dispatch message from the data @@ -98,9 +101,13 @@ func (m *ComputeBody) VerifyTxnSchedSignature(header block.Header) bool { // RootsForStorageReceipt gets the merkle roots that must be part of // a storage receipt. func (m *ComputeBody) RootsForStorageReceipt() []hash.Hash { + if m.Header.IORoot == nil || m.Header.StateRoot != nil { + return nil + } + return []hash.Hash{ - m.Header.IORoot, - m.Header.StateRoot, + *m.Header.IORoot, + *m.Header.StateRoot, } } diff --git a/go/roothash/api/commitment/pool.go b/go/roothash/api/commitment/pool.go index 80fcd8cdfc9..ce6833cab39 100644 --- a/go/roothash/api/commitment/pool.go +++ b/go/roothash/api/commitment/pool.go @@ -35,6 +35,7 @@ var ( ErrBadStorageReceipts = errors.New(moduleName, 14, "roothash/commitment: bad storage receipts") ErrTimeoutNotCorrectRound = errors.New(moduleName, 15, "roothash/commitment: timeout not for correct round") ErrNodeIsScheduler = errors.New(moduleName, 16, "roothash/commitment: node is scheduler") + ErrBadRootHashes = errors.New(moduleName, 17, "roothash/commitment: commitment has invalid root hashes") ) const ( @@ -240,8 +241,24 @@ func (p *Pool) addOpenExecutorCommitment( switch body.Failure { case FailureNone: + // Ensure header fields are present. + if header.IORoot == nil { + logger.Debug("executor commitment is missing IORoot") + return ErrBadRootHashes + } + + // Ensure header fields are present. + if header.StateRoot == nil { + logger.Debug("executor commitment is missing StateRoot") + return ErrBadRootHashes + } + // Verify RAK-attestation. if p.Runtime.TEEHardware != node.TEEHardwareInvalid { + if body.RakSig == nil { + logger.Debug("executor commitment is missing RAK signature") + return ErrRakSigInvalid + } n, err := nl.Node(ctx, id) if err != nil { // This should never happen as nodes cannot disappear mid-epoch. @@ -302,8 +319,23 @@ func (p *Pool) addOpenExecutorCommitment( return ErrBadStorageReceipts } + // Ensure header fields are empty. + if header.IORoot != nil { + logger.Debug("failure indicating executor commitment includes IORoot", + "io_root", header.IORoot, + ) + return ErrBadRootHashes + } + + if header.StateRoot != nil { + logger.Debug("failure indicating executor commitment includes StateRoot", + "io_root", header.IORoot, + ) + return ErrBadRootHashes + } + // In case of failure indicating commitment make sure RAK signature is empty. - if !body.RakSig.Equal(signature.RawSignature{}) { + if body.RakSig != nil { logger.Debug("failure indicating commitment includes RAK signature", "node_id", id, "rak_sig", body.RakSig.String(), diff --git a/go/roothash/api/commitment/pool_test.go b/go/roothash/api/commitment/pool_test.go index 1c90d9403b8..496472b54f3 100644 --- a/go/roothash/api/commitment/pool_test.go +++ b/go/roothash/api/commitment/pool_test.go @@ -90,8 +90,8 @@ func TestPoolDefault(t *testing.T) { Header: ComputeResultsHeader{ Round: blk.Header.Round, PreviousHash: blk.Header.PreviousHash, - IORoot: blk.Header.IORoot, - StateRoot: blk.Header.StateRoot, + IORoot: &blk.Header.IORoot, + StateRoot: &blk.Header.StateRoot, }, } commit, err := SignExecutorCommitment(sk, &body) @@ -176,7 +176,19 @@ func TestPoolSingleCommitment(t *testing.T) { {"BlockBadRound", func(b *ComputeBody) { b.Header.Round-- }, ErrNotBasedOnCorrectBlock}, {"BlockBadPreviousHash", func(b *ComputeBody) { b.Header.PreviousHash.FromBytes([]byte("invalid")) }, ErrNotBasedOnCorrectBlock}, {"StorageSigs1", func(b *ComputeBody) { b.StorageSignatures = nil }, ErrBadStorageReceipts}, + {"MissingIORootHash", func(b *ComputeBody) { b.Header.IORoot = nil }, ErrBadRootHashes}, + {"MissingStateRootHash", func(b *ComputeBody) { b.Header.StateRoot = nil }, ErrBadRootHashes}, {"FailureIndicatingWithStorageSigs", func(b *ComputeBody) { b.Failure = FailureStorageUnavailable }, ErrBadStorageReceipts}, + {"FailureIndicatingWithStateRootHash", func(b *ComputeBody) { + b.Failure = FailureStorageUnavailable + b.StorageSignatures = nil + b.Header.StateRoot = nil + }, ErrBadRootHashes}, + {"FailureIndicatingWithIORootHash", func(b *ComputeBody) { + b.Failure = FailureStorageUnavailable + b.StorageSignatures = nil + b.Header.IORoot = nil + }, ErrBadRootHashes}, } { _, _, invalidBody := generateComputeBody(t) invalidBody.StorageSignatures = append([]signature.Signature{}, body.StorageSignatures...) @@ -310,7 +322,7 @@ func TestPoolSingleCommitmentTEE(t *testing.T) { childBlk, _, body := generateComputeBody(t) rakSig, err := signature.Sign(skRAK, ComputeResultsHeaderSignatureContext, cbor.Marshal(body.Header)) require.NoError(t, err, "Sign") - body.RakSig = rakSig.Signature + body.RakSig = &rakSig.Signature commit, err := SignExecutorCommitment(sk, &body) require.NoError(t, err, "SignExecutorCommitment") @@ -414,7 +426,8 @@ func TestPoolTwoCommitments(t *testing.T) { correctHeader := body.Header // Update state root and fix the storage receipt. - body.Header.StateRoot.FromBytes([]byte("discrepancy")) + badHash := hash.NewFromBytes([]byte("discrepancy")) + body.Header.StateRoot = &badHash body.StorageSignatures = []signature.Signature{generateStorageReceiptSignature(t, parentBlk, &body)} commit2, err := SignExecutorCommitment(sk2, &body) @@ -604,9 +617,9 @@ func TestTryFinalize(t *testing.T) { correctHeader := body.Header // Update state root and fix the storage receipt. - body.Header.StateRoot.FromBytes([]byte("discrepancy")) + badHash := hash.NewFromBytes([]byte("discrepancy")) + body.Header.StateRoot = &badHash body.StorageSignatures = []signature.Signature{generateStorageReceiptSignature(t, parentBlk, &body)} - commit2, err := SignExecutorCommitment(sk2, &body) require.NoError(t, err, "SignExecutorCommitment") @@ -801,8 +814,6 @@ func TestPoolFailureIndicatingCommitment(t *testing.T) { Header: ComputeResultsHeader{ Round: body.Header.Round, PreviousHash: body.Header.PreviousHash, - IORoot: body.Header.IORoot, - StateRoot: body.Header.StateRoot, }, Failure: FailureStorageUnavailable, } @@ -936,8 +947,8 @@ func generateComputeBody(t *testing.T) (*block.Block, *block.Block, ComputeBody) Header: ComputeResultsHeader{ Round: parentBlk.Header.Round, PreviousHash: parentBlk.Header.PreviousHash, - IORoot: parentBlk.Header.IORoot, - StateRoot: parentBlk.Header.StateRoot, + IORoot: &parentBlk.Header.IORoot, + StateRoot: &parentBlk.Header.StateRoot, }, } diff --git a/go/roothash/tests/tester.go b/go/roothash/tests/tester.go index ce1e9bece00..5e52b2f6a62 100644 --- a/go/roothash/tests/tester.go +++ b/go/roothash/tests/tester.go @@ -307,8 +307,8 @@ func (s *runtimeState) generateExecutorCommitments(t *testing.T, consensus conse Header: commitment.ComputeResultsHeader{ Round: parent.Header.Round, PreviousHash: parent.Header.PreviousHash, - IORoot: parent.Header.IORoot, - StateRoot: parent.Header.StateRoot, + IORoot: &parent.Header.IORoot, + StateRoot: &parent.Header.StateRoot, }, StorageSignatures: parent.Header.StorageSignatures, InputRoot: hash.Hash{}, diff --git a/go/runtime/host/mock/mock.go b/go/runtime/host/mock/mock.go index 9bc01b32c65..6c7159735af 100644 --- a/go/runtime/host/mock/mock.go +++ b/go/runtime/host/mock/mock.go @@ -79,8 +79,8 @@ func (r *runtime) Call(ctx context.Context, body *protocol.Body) (*protocol.Body Header: commitment.ComputeResultsHeader{ Round: rq.Block.Header.Round + 1, PreviousHash: rq.Block.Header.EncodedHash(), - IORoot: ioRoot, - StateRoot: stateRoot, + IORoot: &ioRoot, + StateRoot: &stateRoot, }, IOWriteLog: ioWriteLog, }, diff --git a/go/worker/compute/executor/committee/node.go b/go/worker/compute/executor/committee/node.go index 649f61cc7e8..addc40ce4cb 100644 --- a/go/worker/compute/executor/committee/node.go +++ b/go/worker/compute/executor/committee/node.go @@ -1086,7 +1086,7 @@ func (n *Node) proposeBatchLocked(processedBatch *processedBatch) { // Generate proposed compute results. proposedResults := &commitment.ComputeBody{ Header: batch.Header, - RakSig: batch.RakSig, + RakSig: &batch.RakSig, TxnSchedSig: state.batch.txnSchedSignature, InputRoot: state.batch.ioRoot.Hash, InputStorageSigs: state.batch.storageSignatures, @@ -1111,14 +1111,14 @@ func (n *Node) proposeBatchLocked(processedBatch *processedBatch) { { SrcRound: lastHeader.Round + 1, SrcRoot: state.batch.ioRoot.Hash, - DstRoot: batch.Header.IORoot, + DstRoot: *batch.Header.IORoot, WriteLog: batch.IOWriteLog, }, // State root. { SrcRound: lastHeader.Round, SrcRoot: lastHeader.StateRoot, - DstRoot: batch.Header.StateRoot, + DstRoot: *batch.Header.StateRoot, WriteLog: batch.StateWriteLog, }, } @@ -1171,6 +1171,9 @@ func (n *Node) proposeBatchLocked(processedBatch *processedBatch) { n.logger.Error("storage failure, sibmitting failure indicating commitment", "err", storageErr, ) + proposedResults.RakSig = nil + proposedResults.Header.IORoot = nil + proposedResults.Header.StateRoot = nil proposedResults.Failure = commitment.FailureStorageUnavailable } @@ -1205,7 +1208,7 @@ func (n *Node) proposeBatchLocked(processedBatch *processedBatch) { n.transitionLocked(StateWaitingForFinalize{ batchStartTime: state.batchStartTime, raw: processedBatch.raw, - proposedIORoot: proposedResults.Header.IORoot, + proposedIORoot: *proposedResults.Header.IORoot, }) default: n.abortBatchLocked(err)