Skip to content

Commit d352e86

Browse files
Merge #4054
4054: Replace View with SnapshotTree as storage representation r=pattyshack a=pattyshack 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). Co-authored-by: Patrick Lee <[email protected]>
2 parents 1f96328 + 33eea11 commit d352e86

File tree

6 files changed

+360
-99
lines changed

6 files changed

+360
-99
lines changed

engine/execution/computation/computer/computer.go

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ import (
1212
"github.com/onflow/flow-go/crypto/hash"
1313
"github.com/onflow/flow-go/engine/execution"
1414
"github.com/onflow/flow-go/engine/execution/computation/result"
15-
"github.com/onflow/flow-go/engine/execution/state/delta"
1615
"github.com/onflow/flow-go/engine/execution/utils"
1716
"github.com/onflow/flow-go/fvm"
1817
"github.com/onflow/flow-go/fvm/blueprints"
1918
"github.com/onflow/flow-go/fvm/derived"
2019
"github.com/onflow/flow-go/fvm/state"
20+
"github.com/onflow/flow-go/fvm/storage"
2121
"github.com/onflow/flow-go/model/flow"
2222
"github.com/onflow/flow-go/module"
2323
"github.com/onflow/flow-go/module/executiondatasync/provider"
@@ -272,7 +272,7 @@ func (e *blockComputer) executeBlock(
272272
ctx context.Context,
273273
parentBlockExecutionResultID flow.Identifier,
274274
block *entity.ExecutableBlock,
275-
snapshot state.StorageSnapshot,
275+
baseSnapshot state.StorageSnapshot,
276276
derivedBlockData *derived.DerivedBlockData,
277277
) (
278278
*execution.ComputationResult,
@@ -311,9 +311,13 @@ func (e *blockComputer) executeBlock(
311311
e.colResCons)
312312
defer collector.Stop()
313313

314-
stateView := delta.NewDeltaView(snapshot)
314+
snapshotTree := storage.NewSnapshotTree(baseSnapshot)
315315
for _, txn := range transactions {
316-
err := e.executeTransaction(blockSpan, txn, stateView, collector)
316+
txnExecutionSnapshot, output, err := e.executeTransaction(
317+
blockSpan,
318+
txn,
319+
snapshotTree,
320+
collector)
317321
if err != nil {
318322
prefix := ""
319323
if txn.isSystemTransaction {
@@ -326,6 +330,9 @@ func (e *blockComputer) executeBlock(
326330
txn.txnIndex,
327331
err)
328332
}
333+
334+
collector.AddTransactionResult(txn, txnExecutionSnapshot, output)
335+
snapshotTree = snapshotTree.Append(txnExecutionSnapshot)
329336
}
330337

331338
res, err := collector.Finalize(ctx)
@@ -345,9 +352,13 @@ func (e *blockComputer) executeBlock(
345352
func (e *blockComputer) executeTransaction(
346353
parentSpan otelTrace.Span,
347354
txn transaction,
348-
stateView state.View,
355+
storageSnapshot state.StorageSnapshot,
349356
collector *resultCollector,
350-
) error {
357+
) (
358+
*state.ExecutionSnapshot,
359+
fvm.ProcedureOutput,
360+
error,
361+
) {
351362
startedAt := time.Now()
352363
memAllocBefore := debug.GetHeapAllocsBytes()
353364

@@ -374,10 +385,13 @@ func (e *blockComputer) executeTransaction(
374385

375386
txn.ctx = fvm.NewContextFromParent(txn.ctx, fvm.WithSpan(txSpan))
376387

377-
txView := stateView.NewChild()
378-
err := e.vm.Run(txn.ctx, txn.TransactionProcedure, txView)
388+
executionSnapshot, output, err := e.vm.RunV2(
389+
txn.ctx,
390+
txn.TransactionProcedure,
391+
storageSnapshot)
379392
if err != nil {
380-
return fmt.Errorf("failed to execute transaction %v for block %s at height %v: %w",
393+
return nil, fvm.ProcedureOutput{}, fmt.Errorf(
394+
"failed to execute transaction %v for block %s at height %v: %w",
381395
txn.txnIdStr,
382396
txn.blockIdStr,
383397
txn.ctx.BlockHeader.Height,
@@ -387,33 +401,19 @@ func (e *blockComputer) executeTransaction(
387401
postProcessSpan := e.tracer.StartSpanFromParent(txSpan, trace.EXEPostProcessTransaction)
388402
defer postProcessSpan.End()
389403

390-
// always merge the view, fvm take cares of reverting changes
391-
// of failed transaction invocation
392-
393-
txnSnapshot := txView.Finalize()
394-
collector.AddTransactionResult(txn, txnSnapshot)
395-
396-
err = stateView.Merge(txnSnapshot)
397-
if err != nil {
398-
return fmt.Errorf(
399-
"merging tx view to collection view failed for tx %v: %w",
400-
txn.txnIdStr,
401-
err)
402-
}
403-
404404
memAllocAfter := debug.GetHeapAllocsBytes()
405405

406406
logger = logger.With().
407-
Uint64("computation_used", txn.ComputationUsed).
408-
Uint64("memory_used", txn.MemoryEstimate).
407+
Uint64("computation_used", output.ComputationUsed).
408+
Uint64("memory_used", output.MemoryEstimate).
409409
Uint64("mem_alloc", memAllocAfter-memAllocBefore).
410410
Int64("time_spent_in_ms", time.Since(startedAt).Milliseconds()).
411411
Logger()
412412

413-
if txn.Err != nil {
413+
if output.Err != nil {
414414
logger = logger.With().
415-
Str("error_message", txn.Err.Error()).
416-
Uint16("error_code", uint16(txn.Err.Code())).
415+
Str("error_message", output.Err.Error()).
416+
Uint16("error_code", uint16(output.Err.Code())).
417417
Logger()
418418
logger.Info().Msg("transaction execution failed")
419419

@@ -434,12 +434,12 @@ func (e *blockComputer) executeTransaction(
434434

435435
e.metrics.ExecutionTransactionExecuted(
436436
time.Since(startedAt),
437-
txn.ComputationUsed,
438-
txn.MemoryEstimate,
437+
output.ComputationUsed,
438+
output.MemoryEstimate,
439439
memAllocAfter-memAllocBefore,
440-
len(txn.Events),
441-
flow.EventsList(txn.Events).ByteSize(),
442-
txn.Err != nil,
440+
len(output.Events),
441+
flow.EventsList(output.Events).ByteSize(),
442+
output.Err != nil,
443443
)
444-
return nil
444+
return executionSnapshot, output, nil
445445
}

engine/execution/computation/computer/computer_test.go

Lines changed: 81 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -101,24 +101,10 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) {
101101
fvm.WithDerivedBlockData(derived.NewEmptyDerivedBlockData()),
102102
)
103103

104-
vm := new(fvmmock.VM)
105-
vm.On("Run", mock.Anything, mock.Anything, mock.Anything).
106-
Return(nil).
107-
Run(func(args mock.Arguments) {
108-
ctx := args[0].(fvm.Context)
109-
tx := args[1].(*fvm.TransactionProcedure)
110-
view := args[2].(state.View)
111-
112-
tx.Events = generateEvents(1, tx.TxIndex)
113-
114-
derivedTxnData, err := ctx.DerivedBlockData.NewDerivedTransactionData(
115-
tx.ExecutionTime(),
116-
tx.ExecutionTime())
117-
require.NoError(t, err)
118-
119-
getSetAProgram(t, view, derivedTxnData)
120-
}).
121-
Times(2 + 1) // 2 txs in collection + system chunk
104+
vm := &testVM{
105+
t: t,
106+
eventsPerTransaction: 1,
107+
}
122108

123109
committer := &fakeCommitter{
124110
callCount: 0,
@@ -283,7 +269,7 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) {
283269
assert.NotNil(t, chunkExecutionData2.TrieUpdate)
284270
assert.Equal(t, byte(2), chunkExecutionData2.TrieUpdate.RootHash[0])
285271

286-
vm.AssertExpectations(t)
272+
assert.Equal(t, 3, vm.callCount)
287273
})
288274

289275
t.Run("empty block still computes system chunk", func(t *testing.T) {
@@ -320,8 +306,11 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) {
320306
block := generateBlock(0, 0, rag)
321307
derivedBlockData := derived.NewEmptyDerivedBlockData()
322308

323-
vm.On("Run", mock.Anything, mock.Anything, mock.Anything).
324-
Return(nil).
309+
vm.On("RunV2", mock.Anything, mock.Anything, mock.Anything).
310+
Return(
311+
&state.ExecutionSnapshot{},
312+
fvm.ProcedureOutput{},
313+
nil).
325314
Once() // just system chunk
326315

327316
committer.On("CommitView", mock.Anything, mock.Anything).
@@ -431,7 +420,6 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) {
431420
t.Run("multiple collections", func(t *testing.T) {
432421
execCtx := fvm.NewContext()
433422

434-
vm := new(fvmmock.VM)
435423
committer := new(computermock.ViewCommitter)
436424

437425
bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore())))
@@ -445,6 +433,15 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) {
445433
trackerStorage,
446434
)
447435

436+
eventsPerTransaction := 2
437+
vm := &testVM{
438+
t: t,
439+
eventsPerTransaction: eventsPerTransaction,
440+
err: fvmErrors.NewInvalidAddressErrorf(
441+
flow.EmptyAddress,
442+
"no payer address provided"),
443+
}
444+
448445
exe, err := computer.NewBlockComputer(
449446
vm,
450447
execCtx,
@@ -459,7 +456,6 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) {
459456

460457
collectionCount := 2
461458
transactionsPerCollection := 2
462-
eventsPerTransaction := 2
463459
eventsPerCollection := eventsPerTransaction * transactionsPerCollection
464460
totalTransactionCount := (collectionCount * transactionsPerCollection) + 1 // +1 for system chunk
465461
// totalEventCount := eventsPerTransaction * totalTransactionCount
@@ -468,19 +464,6 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) {
468464
block := generateBlock(collectionCount, transactionsPerCollection, rag)
469465
derivedBlockData := derived.NewEmptyDerivedBlockData()
470466

471-
vm.On("Run", mock.Anything, mock.Anything, mock.Anything).
472-
Run(func(args mock.Arguments) {
473-
tx := args[1].(*fvm.TransactionProcedure)
474-
475-
tx.Err = fvmErrors.NewInvalidAddressErrorf(
476-
flow.EmptyAddress,
477-
"no payer address provided")
478-
// create dummy events
479-
tx.Events = generateEvents(eventsPerTransaction, tx.TxIndex)
480-
}).
481-
Return(nil).
482-
Times(totalTransactionCount)
483-
484467
committer.On("CommitView", mock.Anything, mock.Anything).
485468
Return(nil, nil, nil, nil).
486469
Times(collectionCount + 1)
@@ -536,7 +519,7 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) {
536519

537520
assertEventHashesMatch(t, collectionCount+1, result)
538521

539-
vm.AssertExpectations(t)
522+
assert.Equal(t, totalTransactionCount, vm.callCount)
540523
})
541524

542525
t.Run("service events are emitted", func(t *testing.T) {
@@ -1250,6 +1233,58 @@ func generateCollection(transactionCount int, addressGenerator flow.AddressGener
12501233
}
12511234
}
12521235

1236+
type testVM struct {
1237+
t *testing.T
1238+
eventsPerTransaction int
1239+
1240+
callCount int
1241+
err fvmErrors.CodedError
1242+
}
1243+
1244+
func (vm *testVM) RunV2(
1245+
ctx fvm.Context,
1246+
proc fvm.Procedure,
1247+
storageSnapshot state.StorageSnapshot,
1248+
) (
1249+
*state.ExecutionSnapshot,
1250+
fvm.ProcedureOutput,
1251+
error,
1252+
) {
1253+
vm.callCount += 1
1254+
1255+
txn := proc.(*fvm.TransactionProcedure)
1256+
1257+
derivedTxnData, err := ctx.DerivedBlockData.NewDerivedTransactionData(
1258+
txn.ExecutionTime(),
1259+
txn.ExecutionTime())
1260+
require.NoError(vm.t, err)
1261+
1262+
getSetAProgram(vm.t, storageSnapshot, derivedTxnData)
1263+
1264+
snapshot := &state.ExecutionSnapshot{}
1265+
output := fvm.ProcedureOutput{
1266+
Events: generateEvents(vm.eventsPerTransaction, txn.TxIndex),
1267+
Err: vm.err,
1268+
}
1269+
1270+
return snapshot, output, nil
1271+
}
1272+
1273+
func (testVM) Run(_ fvm.Context, _ fvm.Procedure, _ state.View) error {
1274+
panic("not implemented")
1275+
}
1276+
1277+
func (testVM) GetAccount(
1278+
_ fvm.Context,
1279+
_ flow.Address,
1280+
_ state.StorageSnapshot,
1281+
) (
1282+
*flow.Account,
1283+
error,
1284+
) {
1285+
panic("not implemented")
1286+
}
1287+
12531288
func generateEvents(eventCount int, txIndex uint32) []flow.Event {
12541289
events := make([]flow.Event, eventCount)
12551290
for i := 0; i < eventCount; i++ {
@@ -1260,16 +1295,22 @@ func generateEvents(eventCount int, txIndex uint32) []flow.Event {
12601295
return events
12611296
}
12621297

1263-
func getSetAProgram(t *testing.T, view state.View, derivedTxnData derived.DerivedTransactionCommitter) {
1298+
func getSetAProgram(
1299+
t *testing.T,
1300+
storageSnapshot state.StorageSnapshot,
1301+
derivedTxnData derived.DerivedTransactionCommitter,
1302+
) {
12641303

1265-
txState := state.NewTransactionState(view, state.DefaultParameters())
1304+
txnState := state.NewTransactionState(
1305+
delta.NewDeltaView(storageSnapshot),
1306+
state.DefaultParameters())
12661307

12671308
loc := common.AddressLocation{
12681309
Name: "SomeContract",
12691310
Address: common.MustBytesToAddress([]byte{0x1}),
12701311
}
12711312
_, err := derivedTxnData.GetOrComputeProgram(
1272-
txState,
1313+
txnState,
12731314
loc,
12741315
&programLoader{
12751316
load: func() (*derived.Program, error) {

0 commit comments

Comments
 (0)