diff --git a/.changelog/6289.breaking.0.md b/.changelog/6289.breaking.0.md new file mode 100644 index 00000000000..edf8560ad30 --- /dev/null +++ b/.changelog/6289.breaking.0.md @@ -0,0 +1,4 @@ +go/consensus/cometbft/abci: Include all events in the events root + +The events root in block metadata system transaction now includes all events, +not just provable ones. diff --git a/.changelog/6289.breaking.1.md b/.changelog/6289.breaking.1.md new file mode 100644 index 00000000000..d33365a2df9 --- /dev/null +++ b/.changelog/6289.breaking.1.md @@ -0,0 +1,4 @@ +go/consensus/cometbft/abci: Add results hash to block metadata tx + +The results hash was added to the block metadata system transaction so that +stateless clients can partially verify the latest block’s results. diff --git a/go/consensus/api/meta.go b/go/consensus/api/meta.go index 79a483e4035..437867c0680 100644 --- a/go/consensus/api/meta.go +++ b/go/consensus/api/meta.go @@ -23,8 +23,10 @@ const BlockMetadataMaxSize = 16_384 type BlockMetadata struct { // StateRoot is the state root after executing all logic in the block. StateRoot hash.Hash `json:"state_root"` - // EventsRoot is the provable events root. + // EventsRoot is the root hash of all events emitted in the block. EventsRoot []byte `json:"events_root"` + // ResultsHash is the hash of transaction results in the block. + ResultsHash []byte `json:"results_hash,omitempty"` } // ValidateBasic performs basic block metadata structure validation. @@ -32,6 +34,9 @@ func (bm *BlockMetadata) ValidateBasic() error { if len(bm.EventsRoot) != 32 { return fmt.Errorf("malformed events root") } + if bm.ResultsHash != nil && len(bm.ResultsHash) != 32 { + return fmt.Errorf("malformed results hash") + } return nil } diff --git a/go/consensus/cometbft/abci/mux.go b/go/consensus/cometbft/abci/mux.go index 1f7ccdd159b..31b5282b04d 100644 --- a/go/consensus/cometbft/abci/mux.go +++ b/go/consensus/cometbft/abci/mux.go @@ -433,8 +433,15 @@ func (mux *abciMux) PrepareProposal(req types.RequestPrepareProposal) types.Resp return types.ResponsePrepareProposal{} } - // Inject system transactions at the end of the block. - systemTxs, systemTxResults, err := mux.prepareSystemTxs() + // Inject system transactions and their results at the end of the block. + // + // It is important to inject the results first, so that the results hash + // in the block metadata transaction includes all results, including results + // from system transactions. + systemTxResults := mux.prepareSystemTxResults() + mux.state.proposal.resultsDeliverTx = append(mux.state.proposal.resultsDeliverTx, systemTxResults...) + + systemTxs, err := mux.prepareSystemTxs() if err != nil { mux.logger.Error("failed to prepare system transactions", "height", req.Height, @@ -444,7 +451,6 @@ func (mux *abciMux) PrepareProposal(req types.RequestPrepareProposal) types.Resp return types.ResponsePrepareProposal{} } txs = append(txs, systemTxs...) - mux.state.proposal.resultsDeliverTx = append(mux.state.proposal.resultsDeliverTx, systemTxResults...) // Record proposal inputs so we can compare in ProcessProposal. p := mux.state.proposal @@ -531,28 +537,25 @@ func (mux *abciMux) executeProposal( mux.state.resetProposal() mux.state.proposal.hash = hash - resultsBeginBlock := mux.BeginBlock(types.RequestBeginBlock{ + // The proposal is updated inside every call, as the end block phase + // requires these results immediately to validate system transactions. + mux.BeginBlock(types.RequestBeginBlock{ Hash: hash, Header: header, LastCommitInfo: lastCommit, ByzantineValidators: misbehavior, }) - resultsDeliverTx := make([]*types.ResponseDeliverTx, 0, len(txs)) for _, tx := range txs { - resp := mux.DeliverTx(types.RequestDeliverTx{ + mux.DeliverTx(types.RequestDeliverTx{ Tx: tx, }) - resultsDeliverTx = append(resultsDeliverTx, &resp) } - resultsEndBlock := mux.EndBlock(types.RequestEndBlock{ + mux.EndBlock(types.RequestEndBlock{ Height: header.Height, }) - // Update the proposal with results, marking the proposal as executed. - mux.state.proposal.setResults(&resultsBeginBlock, resultsDeliverTx, &resultsEndBlock) - return nil } @@ -638,19 +641,22 @@ func (mux *abciMux) BeginBlock(req types.RequestBeginBlock) types.ResponseBeginB } } - response := mux.BaseApplication.BeginBlock(req) + result := mux.BaseApplication.BeginBlock(req) // During the first block, also collect and prepend application events generated during // InitChain to BeginBlock events. if mux.state.BlockHeight() == 0 { - response.Events = append(response.Events, mux.state.initEvents...) + result.Events = append(result.Events, mux.state.initEvents...) } // Collect and return events from the application's BeginBlock calls. - response.Events = append(response.Events, ctx.GetEvents()...) + result.Events = append(result.Events, ctx.GetEvents()...) mux.processProvableEvents(ctx) - return response + // Update the proposal. + mux.state.proposal.resultsBeginBlock = &result + + return result } func (mux *abciMux) notifyInvalidatedCheckTx(txHash hash.Hash, err error) { @@ -715,7 +721,17 @@ func (mux *abciMux) DeliverTx(req types.RequestDeliverTx) types.ResponseDeliverT ctx := mux.state.NewContext(api.ContextDeliverTx) defer ctx.Close() - if err := mux.executeTx(ctx, req.Tx); err != nil { + var results types.ResponseDeliverTx + switch err := mux.executeTx(ctx, req.Tx); err { + case nil: + results = types.ResponseDeliverTx{ + Code: types.CodeTypeOK, + Data: cbor.Marshal(ctx.Data()), + Events: ctx.GetEvents(), + GasWanted: int64(ctx.Gas().GasWanted()), + GasUsed: int64(ctx.Gas().GasUsed()), + } + default: if api.IsUnavailableStateError(err) { // Make sure to not commit any transactions which include results based on unavailable // and/or corrupted state -- doing so can further corrupt state. @@ -726,9 +742,7 @@ func (mux *abciMux) DeliverTx(req types.RequestDeliverTx) types.ResponseDeliverT } module, code := errors.Code(err) - mux.processProvableEvents(ctx) - - return types.ResponseDeliverTx{ + results = types.ResponseDeliverTx{ Codespace: module, Code: code, Log: err.Error(), @@ -740,13 +754,10 @@ func (mux *abciMux) DeliverTx(req types.RequestDeliverTx) types.ResponseDeliverT mux.processProvableEvents(ctx) - return types.ResponseDeliverTx{ - Code: types.CodeTypeOK, - Data: cbor.Marshal(ctx.Data()), - Events: ctx.GetEvents(), - GasWanted: int64(ctx.Gas().GasWanted()), - GasUsed: int64(ctx.Gas().GasUsed()), - } + // Update the proposal. + mux.state.proposal.resultsDeliverTx = append(mux.state.proposal.resultsDeliverTx, &results) + + return results } func (mux *abciMux) EndBlock(req types.RequestEndBlock) types.ResponseEndBlock { @@ -768,9 +779,9 @@ func (mux *abciMux) EndBlock(req types.RequestEndBlock) types.ResponseEndBlock { defer ctx.Close() // Dispatch EndBlock to all applications. - resp := mux.BaseApplication.EndBlock(req) + results := mux.BaseApplication.EndBlock(req) for _, app := range mux.appsByLexOrder { - newResp, err := app.EndBlock(ctx) + newResults, err := app.EndBlock(ctx) if err != nil { mux.logger.Error("EndBlock: fatal error in application", "err", err, @@ -779,7 +790,7 @@ func (mux *abciMux) EndBlock(req types.RequestEndBlock) types.ResponseEndBlock { panic(fmt.Errorf("mux: EndBlock: fatal error in application: '%s': %w", app.Name(), err)) } if app.Blessed() { - resp = newResp + results = newResults } } @@ -798,22 +809,25 @@ func (mux *abciMux) EndBlock(req types.RequestEndBlock) types.ResponseEndBlock { } // Collect and return events. - resp.Events = ctx.GetEvents() + results.Events = ctx.GetEvents() mux.processProvableEvents(ctx) // Update version to what we are actually running. - resp.ConsensusParamUpdates = &cmtproto.ConsensusParams{ + results.ConsensusParamUpdates = &cmtproto.ConsensusParams{ Version: &cmtproto.VersionParams{ App: version.CometBFTAppVersion, }, } + // Update the proposal. + mux.state.proposal.resultsEndBlock = &results + // Validate system transactions included by the proposer. if err := mux.validateSystemTxs(); err != nil { panic(fmt.Errorf("proposed block has invalid system transactions: %w", err)) } - return resp + return results } func (mux *abciMux) Commit() types.ResponseCommit { diff --git a/go/consensus/cometbft/abci/state.go b/go/consensus/cometbft/abci/state.go index dcfd5785d32..fdb39e25144 100644 --- a/go/consensus/cometbft/abci/state.go +++ b/go/consensus/cometbft/abci/state.go @@ -112,17 +112,6 @@ func (ps *proposalState) needsExecution() bool { return ps.resultsBeginBlock == nil || ps.resultsDeliverTx == nil || ps.resultsEndBlock == nil } -// setResults sets the proposal execution results. -func (ps *proposalState) setResults( - resultsBeginBlock *types.ResponseBeginBlock, - resultsDeliverTx []*types.ResponseDeliverTx, - resultsEndBlock *types.ResponseEndBlock, -) { - ps.resultsBeginBlock = resultsBeginBlock - ps.resultsDeliverTx = resultsDeliverTx - ps.resultsEndBlock = resultsEndBlock -} - type applicationState struct { // nolint: maligned logger *logging.Logger diff --git a/go/consensus/cometbft/abci/system.go b/go/consensus/cometbft/abci/system.go index 45b49d30e3b..863ca6f5982 100644 --- a/go/consensus/cometbft/abci/system.go +++ b/go/consensus/cometbft/abci/system.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/cometbft/cometbft/abci/types" + cmttypes "github.com/cometbft/cometbft/types" "github.com/oasisprotocol/oasis-core/go/common/cbor" consensus "github.com/oasisprotocol/oasis-core/go/consensus/api" @@ -13,33 +14,46 @@ import ( "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api" cmtcrypto "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/crypto" "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/crypto/merkle" + "github.com/oasisprotocol/oasis-core/go/upgrade/migrations" ) -// prepareSystemTxs prepares a list of system transactions to be included in a proposed block in -// case where the node is currently the block proposer. -func (mux *abciMux) prepareSystemTxs() ([][]byte, []*types.ResponseDeliverTx, error) { - var ( - systemTxs [][]byte - systemTxResults []*types.ResponseDeliverTx - ) +// prepareSystemTxs prepares a list of system transactions to be included +// in a proposed block in case where the node is currently the block proposer. +func (mux *abciMux) prepareSystemTxs() ([][]byte, error) { + blockMetaTx, err := mux.prepareBlockMetaTx() + if err != nil { + return nil, fmt.Errorf("failed to prepare block metadata transaction: %w", err) + } + return [][]byte{blockMetaTx}, nil +} - // Append block metadata as a system transaction. +// prepareSystemTxResults prepares a list of system transaction results to be included +// in a proposed block in case where the node is currently the block proposer. +func (mux *abciMux) prepareSystemTxResults() []*types.ResponseDeliverTx { + blockMetaResults := mux.prepareBlockMetaTxResults() + return []*types.ResponseDeliverTx{blockMetaResults} +} + +// prepareBlockMetaTx prepares block metadata system transaction. +func (mux *abciMux) prepareBlockMetaTx() ([]byte, error) { stateRoot, err := mux.state.workingStateRoot() if err != nil { - return nil, nil, fmt.Errorf("failed to compute working state root: %w", err) + return nil, fmt.Errorf("failed to compute working state root: %w", err) } - eventsRoot, err := mux.computeProvableEventsRoot() + eventsRoot, err := mux.computeEventsRoot() if err != nil { - return nil, nil, fmt.Errorf("failed to compute provable events root: %w", err) + return nil, fmt.Errorf("failed to compute events root: %w", err) } + resultsHash := mux.computeResultsHash() blockMeta := consensus.NewBlockMetadataTx(&consensus.BlockMetadata{ - StateRoot: stateRoot, - EventsRoot: eventsRoot, + StateRoot: stateRoot, + EventsRoot: eventsRoot, + ResultsHash: resultsHash, }) sigBlockMeta, err := transaction.Sign(mux.state.identity.ConsensusSigner, blockMeta) if err != nil { - return nil, nil, fmt.Errorf("failed to sign block metadata transaction: %w", err) + return nil, fmt.Errorf("failed to sign block metadata transaction: %w", err) } sigBlockMetaRaw := cbor.Marshal(sigBlockMeta) if l := len(sigBlockMetaRaw); l > consensus.BlockMetadataMaxSize { @@ -47,15 +61,18 @@ func (mux *abciMux) prepareSystemTxs() ([][]byte, []*types.ResponseDeliverTx, er "meta_size", l, "max_meta_size", consensus.BlockMetadataMaxSize, ) - return nil, nil, fmt.Errorf("serialized block metadata would be oversized") + return nil, fmt.Errorf("serialized block metadata would be oversized") } - systemTxs = append(systemTxs, sigBlockMetaRaw) - systemTxResults = append(systemTxResults, &types.ResponseDeliverTx{ + + return sigBlockMetaRaw, nil +} + +// prepareBlockMetaTxResults prepares block metadata system transaction results. +func (mux *abciMux) prepareBlockMetaTxResults() *types.ResponseDeliverTx { + return &types.ResponseDeliverTx{ Code: types.CodeTypeOK, Data: cbor.Marshal(nil), - }) - - return systemTxs, systemTxResults, nil + } } // processSystemTx processes a system transaction in DeliverTx context. @@ -101,7 +118,7 @@ func (mux *abciMux) validateSystemTxs() error { for _, tx := range mux.state.blockCtx.SystemTransactions { switch tx.Method { case consensus.MethodMeta: - // Block metadata, verify state root. + // Decode block metadata. if hasBlockMetadata { return fmt.Errorf("duplicate block metadata in block") } @@ -124,18 +141,25 @@ func (mux *abciMux) validateSystemTxs() error { return fmt.Errorf("invalid state root in block metadata (expected: %s got: %s)", stateRoot, meta.StateRoot) } - // Verify provable events root. - eventsRoot, err := mux.computeProvableEventsRoot() + // Verify events root. + eventsRoot, err := mux.computeEventsRoot() if err != nil { - return fmt.Errorf("failed to compute provable events root: %w", err) + return fmt.Errorf("failed to compute events root: %w", err) } if !bytes.Equal(eventsRoot, meta.EventsRoot) { return fmt.Errorf("invalid events root in block metadata (expected: %x got: %x)", eventsRoot, meta.EventsRoot) } + // Verify results hash. + resultsHash := mux.computeResultsHash() + if !bytes.Equal(resultsHash, meta.ResultsHash) { + return fmt.Errorf("invalid results hash in block metadata (expected: %x got: %x)", resultsHash, meta.ResultsHash) + } + mux.logger.Debug("validated block metadata", "state_root", meta.StateRoot, "events_root", hex.EncodeToString(eventsRoot), + "results_hash", hex.EncodeToString(resultsHash), ) default: return fmt.Errorf("unknown system method: %s", tx.Method) @@ -148,11 +172,37 @@ func (mux *abciMux) validateSystemTxs() error { return nil } -func (mux *abciMux) computeProvableEventsRoot() ([]byte, error) { - provable := mux.state.blockCtx.ProvableEvents - provableEvents := make([][]byte, len(provable)) - for i, pe := range provable { - provableEvents[i] = cbor.Marshal(pe.ProvableRepresentation()) +func (mux *abciMux) computeEventsRoot() ([]byte, error) { + if !mux.state.ConsensusParameters().IsFeatureVersion(migrations.Version256) { + // Events root used to be computed only from provable events. + provable := mux.state.blockCtx.ProvableEvents + provableEvents := make([][]byte, len(provable)) + for i, pe := range provable { + provableEvents[i] = cbor.Marshal(pe.ProvableRepresentation()) + } + return merkle.RootHash(provableEvents), nil + } + + // Events root is now computed from all emitted events. + var events [][]byte + for _, ev := range mux.state.proposal.resultsBeginBlock.Events { + events = append(events, cbor.Marshal(ev)) + } + for _, res := range mux.state.proposal.resultsDeliverTx { + for _, ev := range res.Events { + events = append(events, cbor.Marshal(ev)) + } + } + for _, ev := range mux.state.proposal.resultsEndBlock.Events { + events = append(events, cbor.Marshal(ev)) + } + + return merkle.RootHash(events), nil +} + +func (mux *abciMux) computeResultsHash() []byte { + if !mux.state.ConsensusParameters().IsFeatureVersion(migrations.Version256) { + return nil } - return merkle.RootHash(provableEvents), nil + return cmttypes.NewResults(mux.state.proposal.resultsDeliverTx).Hash() } diff --git a/go/consensus/cometbft/api/context.go b/go/consensus/cometbft/api/context.go index 681d96a16ad..553f1b8449d 100644 --- a/go/consensus/cometbft/api/context.go +++ b/go/consensus/cometbft/api/context.go @@ -370,17 +370,6 @@ func (c *Context) HasEvent(app string, kind events.TypedAttribute) bool { return c.hasEvent(app, kind.EventKind()) } -// DecodeEvent decodes the given raw event as a specific typed event. -func (c *Context) DecodeEvent(index int, ev events.TypedAttribute) error { - raw := c.events[index] - for _, pair := range raw.Attributes { - if events.IsAttributeKind(pair.GetKey(), ev) { - return events.DecodeValue(pair.GetValue(), ev) - } - } - return fmt.Errorf("incompatible event") -} - // ProvableEvents returns the emitted provable events. func (c *Context) ProvableEvents() []events.Provable { return c.eventsProvable diff --git a/go/consensus/cometbft/apps/keymanager/churp/txs.go b/go/consensus/cometbft/apps/keymanager/churp/txs.go index be6ecb179fd..6323960a609 100644 --- a/go/consensus/cometbft/apps/keymanager/churp/txs.go +++ b/go/consensus/cometbft/apps/keymanager/churp/txs.go @@ -559,8 +559,8 @@ func resetHandoff(status *churp.Status, nextHandoff beacon.EpochTime) { } func verifyPolicy(ctx *tmapi.Context, policy *churp.SignedPolicySGX) error { - // Allow non-empty `MayQuery` field with the 24.2 release. - enabled, err := features.IsFeatureVersion(ctx, migrations.Version242) + // Allow non-empty `MayQuery` field when this feature is available. + enabled, err := features.IsFeatureVersion(ctx, migrations.Version256) if err != nil { return err } diff --git a/go/consensus/cometbft/apps/staking/state/state_test.go b/go/consensus/cometbft/apps/staking/state/state_test.go index 787ce91ba0d..7493aef6e3f 100644 --- a/go/consensus/cometbft/apps/staking/state/state_test.go +++ b/go/consensus/cometbft/apps/staking/state/state_test.go @@ -974,13 +974,6 @@ func TestTransfer(t *testing.T) { require.EqualValues(*quantity.NewFromUint64(250), a2.General.Balance, "amount in destination account should be correct") require.Len(ctx.GetEvents(), 1, "one event should be emitted") - var ev staking.TransferEvent - err = ctx.DecodeEvent(0, &ev) - require.NoError(err, "DecodeEvent") - require.EqualValues(addr1, ev.From, "event should have the correct source address") - require.EqualValues(addr2, ev.To, "event should have the correct destination address") - require.EqualValues(*quantity.NewFromUint64(50), ev.Amount, "event should have the correct amount") - // Test min transact balance checks. err = s.Transfer(ctx, addr1, addr2, quantity.NewFromUint64(101)) require.ErrorIs(err, staking.ErrBalanceTooLow, "Transfer leaving below min transact balance in source") diff --git a/go/oasis-test-runner/scenario/e2e/upgrade.go b/go/oasis-test-runner/scenario/e2e/upgrade.go index a630d045756..99ef1f9fde5 100644 --- a/go/oasis-test-runner/scenario/e2e/upgrade.go +++ b/go/oasis-test-runner/scenario/e2e/upgrade.go @@ -261,9 +261,9 @@ func (c *upgrade242Checker) PostUpgradeFn(ctx context.Context, ctrl *oasis.Contr if err != nil { return fmt.Errorf("can't get consensus parameters: %w", err) } - if consParams.Parameters.FeatureVersion == nil || *consParams.Parameters.FeatureVersion != migrations.Version242 { + if consParams.Parameters.FeatureVersion == nil || *consParams.Parameters.FeatureVersion != migrations.Version256 { return fmt.Errorf("consensus parameter FeatureVersion not updated correctly (expected: %s actual: %s)", - migrations.Version242, + migrations.Version256, consParams.Parameters.FeatureVersion, ) } @@ -279,7 +279,7 @@ var ( // NodeUpgradeConsensus240 is the node upgrade scenario for migrating to consensus 24.0. NodeUpgradeConsensus240 scenario.Scenario = newNodeUpgradeImpl(migrations.Consensus240, &upgrade240Checker{}, false) // NodeUpgradeConsensus242 is the node upgrade scenario for migrating to consensus 24.2. - NodeUpgradeConsensus242 scenario.Scenario = newNodeUpgradeImpl(migrations.Consensus242, &upgrade242Checker{}, false) + NodeUpgradeConsensus242 scenario.Scenario = newNodeUpgradeImpl(migrations.Consensus256, &upgrade242Checker{}, false) malformedDescriptor = []byte(`{ "v": 1, diff --git a/go/upgrade/migrations/consensus_242.go b/go/upgrade/migrations/consensus_256.go similarity index 60% rename from go/upgrade/migrations/consensus_242.go rename to go/upgrade/migrations/consensus_256.go index 32c348606c2..8ca21182ab5 100644 --- a/go/upgrade/migrations/consensus_242.go +++ b/go/upgrade/migrations/consensus_256.go @@ -8,33 +8,37 @@ import ( consensusState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/consensus/state" ) -// Consensus242 is the name of the upgrade that enables features introduced in Oasis Core 24.2. +// Consensus256 is the name of the upgrade that enables features introduced up to and including +// Oasis Core 25.6. // // This upgrade includes: // - The `MayQuery“ field in the CHURP SGX policy, which defines which enclave identities // are allowed to query runtime key shares. -const Consensus242 = "consensus242" +// - An updated events root in the block metadata system transaction to capture all events +// emitted in the block. +// - A results hash in the block metadata system transaction. +const Consensus256 = "consensus256" -// Version242 is the Oasis Core 24.2 version. -var Version242 = version.MustFromString("24.2") +// Version256 is the Oasis Core 25.6 version. +var Version256 = version.MustFromString("25.6") -var _ Handler = (*Handler242)(nil) +var _ Handler = (*Handler256)(nil) -// Handler242 is the upgrade handler that transitions Oasis Core from version 24.1 to 24.2. -type Handler242 struct{} +// Handler256 is the upgrade handler that transitions Oasis Core from version 24.1 to 25.6. +type Handler256 struct{} // HasStartupUpgrade implements Handler. -func (h *Handler242) HasStartupUpgrade() bool { +func (h *Handler256) HasStartupUpgrade() bool { return false } // StartupUpgrade implements Handler. -func (h *Handler242) StartupUpgrade() error { +func (h *Handler256) StartupUpgrade() error { return nil } // ConsensusUpgrade implements Handler. -func (h *Handler242) ConsensusUpgrade(privateCtx any) error { +func (h *Handler256) ConsensusUpgrade(privateCtx any) error { abciCtx := privateCtx.(*abciAPI.Context) switch abciCtx.Mode() { case abciAPI.ContextBeginBlock: @@ -47,7 +51,7 @@ func (h *Handler242) ConsensusUpgrade(privateCtx any) error { return fmt.Errorf("failed to load consensus parameters: %w", err) } - consParams.FeatureVersion = &Version242 + consParams.FeatureVersion = &Version256 if err = consState.SetConsensusParameters(abciCtx, consParams); err != nil { return fmt.Errorf("failed to set consensus parameters: %w", err) @@ -59,5 +63,5 @@ func (h *Handler242) ConsensusUpgrade(privateCtx any) error { } func init() { - Register(Consensus242, &Handler242{}) + Register(Consensus256, &Handler256{}) } diff --git a/runtime/src/consensus/mod.rs b/runtime/src/consensus/mod.rs index 2301315f5c0..ebff307116e 100644 --- a/runtime/src/consensus/mod.rs +++ b/runtime/src/consensus/mod.rs @@ -47,6 +47,9 @@ pub enum Event { pub struct BlockMetadata { /// State root after executing all logic in the block. pub state_root: Hash, - // EventsRoot is the provable events root. + // Root hash of all events emitted in the block. pub events_root: Vec, + // Hash of transaction results in the block. + #[cbor(optional)] + pub results_hash: Vec, }