From 33eea119d05933f52062dc0d0a8a7fd16b3875f0 Mon Sep 17 00:00:00 2001 From: Patrick Lee <patrick.lee@dapperlabs.com> Date: Mon, 13 Mar 2023 11:17:12 -0700 Subject: [PATCH] Replace View with SnapshotTree as storage representation This simplify parallel execution (the tree is immutable and the list of write sets can be used for OCC validation), but at the expense of less efficient value lookups (the cost is amortized by compaction). --- .../computation/computer/computer.go | 68 ++++----- .../computation/computer/computer_test.go | 121 ++++++++++------ .../computation/computer/result_collector.go | 28 ++-- fvm/storage/snapshot_tree.go | 79 +++++++++++ fvm/storage/snapshot_tree_test.go | 131 ++++++++++++++++++ module/chunks/chunkVerifier.go | 32 +++-- 6 files changed, 360 insertions(+), 99 deletions(-) create mode 100644 fvm/storage/snapshot_tree.go create mode 100644 fvm/storage/snapshot_tree_test.go diff --git a/engine/execution/computation/computer/computer.go b/engine/execution/computation/computer/computer.go index 2d217fa1687..46ff1832b6a 100644 --- a/engine/execution/computation/computer/computer.go +++ b/engine/execution/computation/computer/computer.go @@ -12,12 +12,12 @@ import ( "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/engine/execution" "github.com/onflow/flow-go/engine/execution/computation/result" - "github.com/onflow/flow-go/engine/execution/state/delta" "github.com/onflow/flow-go/engine/execution/utils" "github.com/onflow/flow-go/fvm" "github.com/onflow/flow-go/fvm/blueprints" "github.com/onflow/flow-go/fvm/derived" "github.com/onflow/flow-go/fvm/state" + "github.com/onflow/flow-go/fvm/storage" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/module/executiondatasync/provider" @@ -272,7 +272,7 @@ func (e *blockComputer) executeBlock( ctx context.Context, parentBlockExecutionResultID flow.Identifier, block *entity.ExecutableBlock, - snapshot state.StorageSnapshot, + baseSnapshot state.StorageSnapshot, derivedBlockData *derived.DerivedBlockData, ) ( *execution.ComputationResult, @@ -311,9 +311,13 @@ func (e *blockComputer) executeBlock( e.colResCons) defer collector.Stop() - stateView := delta.NewDeltaView(snapshot) + snapshotTree := storage.NewSnapshotTree(baseSnapshot) for _, txn := range transactions { - err := e.executeTransaction(blockSpan, txn, stateView, collector) + txnExecutionSnapshot, output, err := e.executeTransaction( + blockSpan, + txn, + snapshotTree, + collector) if err != nil { prefix := "" if txn.isSystemTransaction { @@ -326,6 +330,9 @@ func (e *blockComputer) executeBlock( txn.txnIndex, err) } + + collector.AddTransactionResult(txn, txnExecutionSnapshot, output) + snapshotTree = snapshotTree.Append(txnExecutionSnapshot) } res, err := collector.Finalize(ctx) @@ -345,9 +352,13 @@ func (e *blockComputer) executeBlock( func (e *blockComputer) executeTransaction( parentSpan otelTrace.Span, txn transaction, - stateView state.View, + storageSnapshot state.StorageSnapshot, collector *resultCollector, -) error { +) ( + *state.ExecutionSnapshot, + fvm.ProcedureOutput, + error, +) { startedAt := time.Now() memAllocBefore := debug.GetHeapAllocsBytes() @@ -374,10 +385,13 @@ func (e *blockComputer) executeTransaction( txn.ctx = fvm.NewContextFromParent(txn.ctx, fvm.WithSpan(txSpan)) - txView := stateView.NewChild() - err := e.vm.Run(txn.ctx, txn.TransactionProcedure, txView) + executionSnapshot, output, err := e.vm.RunV2( + txn.ctx, + txn.TransactionProcedure, + storageSnapshot) if err != nil { - return fmt.Errorf("failed to execute transaction %v for block %s at height %v: %w", + return nil, fvm.ProcedureOutput{}, fmt.Errorf( + "failed to execute transaction %v for block %s at height %v: %w", txn.txnIdStr, txn.blockIdStr, txn.ctx.BlockHeader.Height, @@ -387,33 +401,19 @@ func (e *blockComputer) executeTransaction( postProcessSpan := e.tracer.StartSpanFromParent(txSpan, trace.EXEPostProcessTransaction) defer postProcessSpan.End() - // always merge the view, fvm take cares of reverting changes - // of failed transaction invocation - - txnSnapshot := txView.Finalize() - collector.AddTransactionResult(txn, txnSnapshot) - - err = stateView.Merge(txnSnapshot) - if err != nil { - return fmt.Errorf( - "merging tx view to collection view failed for tx %v: %w", - txn.txnIdStr, - err) - } - memAllocAfter := debug.GetHeapAllocsBytes() logger = logger.With(). - Uint64("computation_used", txn.ComputationUsed). - Uint64("memory_used", txn.MemoryEstimate). + Uint64("computation_used", output.ComputationUsed). + Uint64("memory_used", output.MemoryEstimate). Uint64("mem_alloc", memAllocAfter-memAllocBefore). Int64("time_spent_in_ms", time.Since(startedAt).Milliseconds()). Logger() - if txn.Err != nil { + if output.Err != nil { logger = logger.With(). - Str("error_message", txn.Err.Error()). - Uint16("error_code", uint16(txn.Err.Code())). + Str("error_message", output.Err.Error()). + Uint16("error_code", uint16(output.Err.Code())). Logger() logger.Info().Msg("transaction execution failed") @@ -434,12 +434,12 @@ func (e *blockComputer) executeTransaction( e.metrics.ExecutionTransactionExecuted( time.Since(startedAt), - txn.ComputationUsed, - txn.MemoryEstimate, + output.ComputationUsed, + output.MemoryEstimate, memAllocAfter-memAllocBefore, - len(txn.Events), - flow.EventsList(txn.Events).ByteSize(), - txn.Err != nil, + len(output.Events), + flow.EventsList(output.Events).ByteSize(), + output.Err != nil, ) - return nil + return executionSnapshot, output, nil } diff --git a/engine/execution/computation/computer/computer_test.go b/engine/execution/computation/computer/computer_test.go index 22f2d739635..902e048dd78 100644 --- a/engine/execution/computation/computer/computer_test.go +++ b/engine/execution/computation/computer/computer_test.go @@ -101,24 +101,10 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) { fvm.WithDerivedBlockData(derived.NewEmptyDerivedBlockData()), ) - vm := new(fvmmock.VM) - vm.On("Run", mock.Anything, mock.Anything, mock.Anything). - Return(nil). - Run(func(args mock.Arguments) { - ctx := args[0].(fvm.Context) - tx := args[1].(*fvm.TransactionProcedure) - view := args[2].(state.View) - - tx.Events = generateEvents(1, tx.TxIndex) - - derivedTxnData, err := ctx.DerivedBlockData.NewDerivedTransactionData( - tx.ExecutionTime(), - tx.ExecutionTime()) - require.NoError(t, err) - - getSetAProgram(t, view, derivedTxnData) - }). - Times(2 + 1) // 2 txs in collection + system chunk + vm := &testVM{ + t: t, + eventsPerTransaction: 1, + } committer := &fakeCommitter{ callCount: 0, @@ -283,7 +269,7 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) { assert.NotNil(t, chunkExecutionData2.TrieUpdate) assert.Equal(t, byte(2), chunkExecutionData2.TrieUpdate.RootHash[0]) - vm.AssertExpectations(t) + assert.Equal(t, 3, vm.callCount) }) t.Run("empty block still computes system chunk", func(t *testing.T) { @@ -320,8 +306,11 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) { block := generateBlock(0, 0, rag) derivedBlockData := derived.NewEmptyDerivedBlockData() - vm.On("Run", mock.Anything, mock.Anything, mock.Anything). - Return(nil). + vm.On("RunV2", mock.Anything, mock.Anything, mock.Anything). + Return( + &state.ExecutionSnapshot{}, + fvm.ProcedureOutput{}, + nil). Once() // just system chunk committer.On("CommitView", mock.Anything, mock.Anything). @@ -431,7 +420,6 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) { t.Run("multiple collections", func(t *testing.T) { execCtx := fvm.NewContext() - vm := new(fvmmock.VM) committer := new(computermock.ViewCommitter) bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) @@ -445,6 +433,15 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) { trackerStorage, ) + eventsPerTransaction := 2 + vm := &testVM{ + t: t, + eventsPerTransaction: eventsPerTransaction, + err: fvmErrors.NewInvalidAddressErrorf( + flow.EmptyAddress, + "no payer address provided"), + } + exe, err := computer.NewBlockComputer( vm, execCtx, @@ -459,7 +456,6 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) { collectionCount := 2 transactionsPerCollection := 2 - eventsPerTransaction := 2 eventsPerCollection := eventsPerTransaction * transactionsPerCollection totalTransactionCount := (collectionCount * transactionsPerCollection) + 1 // +1 for system chunk // totalEventCount := eventsPerTransaction * totalTransactionCount @@ -468,19 +464,6 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) { block := generateBlock(collectionCount, transactionsPerCollection, rag) derivedBlockData := derived.NewEmptyDerivedBlockData() - vm.On("Run", mock.Anything, mock.Anything, mock.Anything). - Run(func(args mock.Arguments) { - tx := args[1].(*fvm.TransactionProcedure) - - tx.Err = fvmErrors.NewInvalidAddressErrorf( - flow.EmptyAddress, - "no payer address provided") - // create dummy events - tx.Events = generateEvents(eventsPerTransaction, tx.TxIndex) - }). - Return(nil). - Times(totalTransactionCount) - committer.On("CommitView", mock.Anything, mock.Anything). Return(nil, nil, nil, nil). Times(collectionCount + 1) @@ -536,7 +519,7 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) { assertEventHashesMatch(t, collectionCount+1, result) - vm.AssertExpectations(t) + assert.Equal(t, totalTransactionCount, vm.callCount) }) t.Run("service events are emitted", func(t *testing.T) { @@ -1250,6 +1233,58 @@ func generateCollection(transactionCount int, addressGenerator flow.AddressGener } } +type testVM struct { + t *testing.T + eventsPerTransaction int + + callCount int + err fvmErrors.CodedError +} + +func (vm *testVM) RunV2( + ctx fvm.Context, + proc fvm.Procedure, + storageSnapshot state.StorageSnapshot, +) ( + *state.ExecutionSnapshot, + fvm.ProcedureOutput, + error, +) { + vm.callCount += 1 + + txn := proc.(*fvm.TransactionProcedure) + + derivedTxnData, err := ctx.DerivedBlockData.NewDerivedTransactionData( + txn.ExecutionTime(), + txn.ExecutionTime()) + require.NoError(vm.t, err) + + getSetAProgram(vm.t, storageSnapshot, derivedTxnData) + + snapshot := &state.ExecutionSnapshot{} + output := fvm.ProcedureOutput{ + Events: generateEvents(vm.eventsPerTransaction, txn.TxIndex), + Err: vm.err, + } + + return snapshot, output, nil +} + +func (testVM) Run(_ fvm.Context, _ fvm.Procedure, _ state.View) error { + panic("not implemented") +} + +func (testVM) GetAccount( + _ fvm.Context, + _ flow.Address, + _ state.StorageSnapshot, +) ( + *flow.Account, + error, +) { + panic("not implemented") +} + func generateEvents(eventCount int, txIndex uint32) []flow.Event { events := make([]flow.Event, eventCount) for i := 0; i < eventCount; i++ { @@ -1260,16 +1295,22 @@ func generateEvents(eventCount int, txIndex uint32) []flow.Event { return events } -func getSetAProgram(t *testing.T, view state.View, derivedTxnData derived.DerivedTransactionCommitter) { +func getSetAProgram( + t *testing.T, + storageSnapshot state.StorageSnapshot, + derivedTxnData derived.DerivedTransactionCommitter, +) { - txState := state.NewTransactionState(view, state.DefaultParameters()) + txnState := state.NewTransactionState( + delta.NewDeltaView(storageSnapshot), + state.DefaultParameters()) loc := common.AddressLocation{ Name: "SomeContract", Address: common.MustBytesToAddress([]byte{0x1}), } _, err := derivedTxnData.GetOrComputeProgram( - txState, + txnState, loc, &programLoader{ load: func() (*derived.Program, error) { diff --git a/engine/execution/computation/computer/result_collector.go b/engine/execution/computation/computer/result_collector.go index a58e9fa3038..f0faa91e164 100644 --- a/engine/execution/computation/computer/result_collector.go +++ b/engine/execution/computation/computer/result_collector.go @@ -13,6 +13,7 @@ import ( "github.com/onflow/flow-go/engine/execution" "github.com/onflow/flow-go/engine/execution/computation/result" "github.com/onflow/flow-go/engine/execution/state/delta" + "github.com/onflow/flow-go/fvm" "github.com/onflow/flow-go/fvm/state" "github.com/onflow/flow-go/ledger" "github.com/onflow/flow-go/model/flow" @@ -40,6 +41,7 @@ type ViewCommitter interface { type transactionResult struct { transaction *state.ExecutionSnapshot + fvm.ProcedureOutput } // TODO(ramtin): move committer and other folks to consumers layer @@ -244,32 +246,33 @@ func (collector *resultCollector) commitCollection( func (collector *resultCollector) processTransactionResult( txn transaction, txnExecutionSnapshot *state.ExecutionSnapshot, + output fvm.ProcedureOutput, ) error { collector.convertedServiceEvents = append( collector.convertedServiceEvents, - txn.ConvertedServiceEvents...) + output.ConvertedServiceEvents...) collector.result.Events[txn.collectionIndex] = append( collector.result.Events[txn.collectionIndex], - txn.Events...) + output.Events...) collector.result.ServiceEvents = append( collector.result.ServiceEvents, - txn.ServiceEvents...) + output.ServiceEvents...) txnResult := flow.TransactionResult{ TransactionID: txn.ID, - ComputationUsed: txn.ComputationUsed, - MemoryUsed: txn.MemoryEstimate, + ComputationUsed: output.ComputationUsed, + MemoryUsed: output.MemoryEstimate, } - if txn.Err != nil { - txnResult.ErrorMessage = txn.Err.Error() + if output.Err != nil { + txnResult.ErrorMessage = output.Err.Error() } collector.result.TransactionResults = append( collector.result.TransactionResults, txnResult) - for computationKind, intensity := range txn.ComputationIntensities { + for computationKind, intensity := range output.ComputationIntensities { collector.result.ComputationIntensities[computationKind] += intensity } @@ -278,8 +281,8 @@ func (collector *resultCollector) processTransactionResult( return fmt.Errorf("failed to merge into collection view: %w", err) } - collector.currentCollectionStats.ComputationUsed += txn.ComputationUsed - collector.currentCollectionStats.MemoryUsed += txn.MemoryEstimate + collector.currentCollectionStats.ComputationUsed += output.ComputationUsed + collector.currentCollectionStats.MemoryUsed += output.MemoryEstimate collector.currentCollectionStats.NumberOfTransactions += 1 if !txn.lastTransactionInCollection { @@ -295,10 +298,12 @@ func (collector *resultCollector) processTransactionResult( func (collector *resultCollector) AddTransactionResult( txn transaction, snapshot *state.ExecutionSnapshot, + output fvm.ProcedureOutput, ) { result := transactionResult{ transaction: txn, ExecutionSnapshot: snapshot, + ProcedureOutput: output, } select { @@ -315,7 +320,8 @@ func (collector *resultCollector) runResultProcessor() { for result := range collector.processorInputChan { err := collector.processTransactionResult( result.transaction, - result.ExecutionSnapshot) + result.ExecutionSnapshot, + result.ProcedureOutput) if err != nil { collector.processorError = err return diff --git a/fvm/storage/snapshot_tree.go b/fvm/storage/snapshot_tree.go new file mode 100644 index 00000000000..2dd3f1b97e9 --- /dev/null +++ b/fvm/storage/snapshot_tree.go @@ -0,0 +1,79 @@ +package storage + +import ( + "github.com/onflow/flow-go/fvm/state" + "github.com/onflow/flow-go/model/flow" +) + +const ( + compactThreshold = 10 +) + +type updateLog []map[flow.RegisterID]flow.RegisterValue + +// SnapshotTree is a simple LSM tree representation of the key/value storage +// at a given point in time. +type SnapshotTree struct { + base state.StorageSnapshot + + fullLog updateLog + compactedLog updateLog +} + +// NewSnapshotTree returns a tree with keys/values initialized to the base +// storage snapshot. +func NewSnapshotTree(base state.StorageSnapshot) SnapshotTree { + return SnapshotTree{ + base: base, + fullLog: nil, + compactedLog: nil, + } +} + +// Append returns a new tree with updates from the execution snapshot "applied" +// to the original original tree. +func (tree SnapshotTree) Append( + update *state.ExecutionSnapshot, +) SnapshotTree { + compactedLog := tree.compactedLog + if len(update.WriteSet) > 0 { + compactedLog = append(tree.compactedLog, update.WriteSet) + if len(compactedLog) > compactThreshold { + size := 0 + for _, set := range compactedLog { + size += len(set) + } + + mergedSet := make(map[flow.RegisterID]flow.RegisterValue, size) + for _, set := range compactedLog { + for id, value := range set { + mergedSet[id] = value + } + } + + compactedLog = updateLog{mergedSet} + } + } + + return SnapshotTree{ + base: tree.base, + fullLog: append(tree.fullLog, update.WriteSet), + compactedLog: compactedLog, + } +} + +// Get returns the register id's value. +func (tree SnapshotTree) Get(id flow.RegisterID) (flow.RegisterValue, error) { + for idx := len(tree.compactedLog) - 1; idx >= 0; idx-- { + value, ok := tree.compactedLog[idx][id] + if ok { + return value, nil + } + } + + if tree.base != nil { + return tree.base.Get(id) + } + + return nil, nil +} diff --git a/fvm/storage/snapshot_tree_test.go b/fvm/storage/snapshot_tree_test.go new file mode 100644 index 00000000000..025195ccf86 --- /dev/null +++ b/fvm/storage/snapshot_tree_test.go @@ -0,0 +1,131 @@ +package storage + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/fvm/state" + "github.com/onflow/flow-go/model/flow" +) + +func TestSnapshotTree(t *testing.T) { + id1 := flow.NewRegisterID("1", "") + id2 := flow.NewRegisterID("2", "") + id3 := flow.NewRegisterID("3", "") + missingId := flow.NewRegisterID("missing", "") + + value1v0 := flow.RegisterValue("1v0") + + // entries: + // 1 -> 1v0 + tree0 := NewSnapshotTree( + state.MapStorageSnapshot{ + id1: value1v0, + }) + + expected0 := map[flow.RegisterID]flow.RegisterValue{ + id1: value1v0, + id2: nil, + id3: nil, + missingId: nil, + } + + value2v1 := flow.RegisterValue("2v1") + + tree1 := tree0.Append( + &state.ExecutionSnapshot{ + WriteSet: map[flow.RegisterID]flow.RegisterValue{ + id2: value2v1, + }, + }) + + expected1 := map[flow.RegisterID]flow.RegisterValue{ + id1: value1v0, + id2: value2v1, + id3: nil, + missingId: nil, + } + + value1v1 := flow.RegisterValue("1v1") + value3v1 := flow.RegisterValue("3v1") + + tree2 := tree1.Append( + &state.ExecutionSnapshot{ + WriteSet: map[flow.RegisterID]flow.RegisterValue{ + id1: value1v1, + id3: value3v1, + }, + }) + + expected2 := map[flow.RegisterID]flow.RegisterValue{ + id1: value1v1, + id2: value2v1, + id3: value3v1, + missingId: nil, + } + + value2v2 := flow.RegisterValue("2v2") + + tree3 := tree2.Append( + &state.ExecutionSnapshot{ + WriteSet: map[flow.RegisterID]flow.RegisterValue{ + id2: value2v2, + }, + }) + + expected3 := map[flow.RegisterID]flow.RegisterValue{ + id1: value1v1, + id2: value2v2, + id3: value3v1, + missingId: nil, + } + + expectedCompacted := map[flow.RegisterID]flow.RegisterValue{ + id1: value1v1, + id2: value2v2, + id3: value3v1, + missingId: nil, + } + + compactedTree := tree3 + numExtraUpdates := 2*compactThreshold + 1 + for i := 0; i < numExtraUpdates; i++ { + value := []byte(fmt.Sprintf("compacted %d", i)) + expectedCompacted[id3] = value + compactedTree = compactedTree.Append( + &state.ExecutionSnapshot{ + WriteSet: map[flow.RegisterID]flow.RegisterValue{ + id3: value, + }, + }) + } + + check := func( + tree SnapshotTree, + expected map[flow.RegisterID]flow.RegisterValue, + fullLogLen int, + compactedLogLen int, + ) { + require.Len(t, tree.fullLog, fullLogLen) + require.Len(t, tree.compactedLog, compactedLogLen) + + for key, expectedValue := range expected { + value, err := tree.Get(key) + require.NoError(t, err) + require.Equal(t, value, expectedValue, string(expectedValue)) + } + } + + check(tree0, expected0, 0, 0) + check(tree1, expected1, 1, 1) + check(tree2, expected2, 2, 2) + check(tree3, expected3, 3, 3) + check(compactedTree, expectedCompacted, 3+numExtraUpdates, 4) + + emptyTree := NewSnapshotTree(nil) + value, err := emptyTree.Get(id1) + require.NoError(t, err) + require.Nil(t, value) +} diff --git a/module/chunks/chunkVerifier.go b/module/chunks/chunkVerifier.go index 6f2f3cc1013..84c4e3449cf 100644 --- a/module/chunks/chunkVerifier.go +++ b/module/chunks/chunkVerifier.go @@ -15,6 +15,7 @@ import ( "github.com/onflow/flow-go/fvm" "github.com/onflow/flow-go/fvm/derived" fvmState "github.com/onflow/flow-go/fvm/state" + "github.com/onflow/flow-go/fvm/storage" "github.com/onflow/flow-go/ledger" "github.com/onflow/flow-go/ledger/partial" chmodels "github.com/onflow/flow-go/model/chunks" @@ -172,20 +173,22 @@ func (fcv *ChunkVerifier) verifyTransactionsInContext( // unknown register tracks access to parts of the partial trie which // are not expanded and values are unknown. unknownRegTouch := make(map[flow.RegisterID]struct{}) - chunkView := delta.NewDeltaView( + snapshotTree := storage.NewSnapshotTree( &partialLedgerStorageSnapshot{ snapshot: executionState.NewLedgerStorageSnapshot( psmt, chunkDataPack.StartState), unknownRegTouch: unknownRegTouch, }) + chunkView := delta.NewDeltaView(nil) var problematicTx flow.Identifier // executes all transactions in this chunk for i, tx := range transactions { - txView := chunkView.NewChild() - - err := fcv.vm.Run(context, tx, txView) + executionSnapshot, output, err := fcv.vm.RunV2( + context, + tx, + snapshotTree) if err != nil { // this covers unexpected and very rare cases (e.g. system memory issues...), // so we shouldn't be here even if transaction naturally fails (e.g. permission, runtime ... ) @@ -196,13 +199,13 @@ func (fcv *ChunkVerifier) verifyTransactionsInContext( problematicTx = tx.ID } - events = append(events, tx.Events...) - serviceEvents = append(serviceEvents, tx.ConvertedServiceEvents...) + events = append(events, output.Events...) + serviceEvents = append(serviceEvents, output.ConvertedServiceEvents...) - // always merge back the tx view (fvm is responsible for changes on tx errors) - err = chunkView.Merge(txView.Finalize()) + snapshotTree = snapshotTree.Append(executionSnapshot) + err = chunkView.Merge(executionSnapshot) if err != nil { - return nil, nil, fmt.Errorf("failed to execute transaction: %d (%w)", i, err) + return nil, nil, fmt.Errorf("failed to merge: %d (%w)", i, err) } } @@ -251,11 +254,12 @@ func (fcv *ChunkVerifier) verifyTransactionsInContext( } } - // applying chunk delta (register updates at chunk level) to the partial trie - // this returns the expected end state commitment after updates and the list of - // register keys that was not provided by the chunk data package (err). + // Applying chunk updates to the partial trie. This returns the expected + // end state commitment after updates and the list of register keys that + // was not provided by the chunk data package (err). + chunkExecutionSnapshot := chunkView.Finalize() keys, values := executionState.RegisterEntriesToKeysValues( - chunkView.Delta().UpdatedRegisters()) + chunkExecutionSnapshot.UpdatedRegisters()) update, err := ledger.NewUpdate( ledger.State(chunkDataPack.StartState), @@ -285,5 +289,5 @@ func (fcv *ChunkVerifier) verifyTransactionsInContext( if flow.StateCommitment(expEndStateComm) != endState { return nil, chmodels.NewCFNonMatchingFinalState(flow.StateCommitment(expEndStateComm), endState, chIndex, execResID), nil } - return chunkView.SpockSecret(), nil, nil + return chunkExecutionSnapshot.SpockSecret, nil, nil }