@@ -397,6 +397,8 @@ func (s *state) SaveExecutionResults(
397
397
return nil
398
398
}
399
399
400
+ // saveExecutionResults saves all data related to the execution of a block.
401
+ // It is concurrent-safe
400
402
func (s * state ) saveExecutionResults (
401
403
ctx context.Context ,
402
404
result * execution.ComputationResult ,
@@ -408,73 +410,72 @@ func (s *state) saveExecutionResults(
408
410
return fmt .Errorf ("can not retrieve chunk data packs: %w" , err )
409
411
}
410
412
411
- err = s .chunkDataPacks .Store (chunks )
412
- if err != nil {
413
- return fmt .Errorf ("can not store multiple chunk data pack: %w" , err )
414
- }
413
+ // Acquire both locks to ensure it's concurrent safe when inserting the execution results and chunk data packs.
414
+ return storage .WithLocks (s .lockManager , []string {storage .LockInsertOwnReceipt , storage .LockInsertChunkDataPack }, func (lctx lockctx.Context ) error {
415
+ err := s .chunkDataPacks .StoreByChunkID (lctx , chunks )
416
+ if err != nil {
417
+ return fmt .Errorf ("can not store multiple chunk data pack: %w" , err )
418
+ }
415
419
416
- lctx := s .lockManager .NewContext ()
417
- defer lctx .Release ()
418
- err = lctx .AcquireLock (storage .LockInsertOwnReceipt )
419
- if err != nil {
420
- return err
421
- }
420
+ // Save entire execution result (including all chunk data packs) within one batch to minimize
421
+ // the number of database interactions.
422
+ return s .db .WithReaderBatchWriter (func (batch storage.ReaderBatchWriter ) error {
423
+ batch .AddCallback (func (err error ) {
424
+ // Rollback if an error occurs during batch operations
425
+ // Chunk data packs are saved in a separate database, there is a chance
426
+ // that execution result was failed to save, but chunk data packs was saved and
427
+ // didnt get removed.
428
+ // TODO(leo): when retrieving chunk data packs, we need to add a check to ensure the block
429
+ // has been executed before returning chunk data packs
430
+ if err != nil {
431
+ chunkIDs := make ([]flow.Identifier , 0 , len (chunks ))
432
+ for _ , chunk := range chunks {
433
+ chunkIDs = append (chunkIDs , chunk .ChunkID )
434
+ }
435
+ _ = s .chunkDataPacks .Remove (chunkIDs )
436
+ }
437
+ })
422
438
423
- // Save entire execution result (including all chunk data packs) within one batch to minimize
424
- // the number of database interactions. This is a large batch of data, which might not be
425
- // committed within a single operation (e.g. if using Badger DB as storage backend, which has
426
- // a size limit for its transactions).
427
- return s .db .WithReaderBatchWriter (func (batch storage.ReaderBatchWriter ) error {
428
- batch .AddCallback (func (err error ) {
429
- // Rollback if an error occurs during batch operations
439
+ err = s .events .BatchStore (blockID , []flow.EventsList {result .AllEvents ()}, batch )
430
440
if err != nil {
431
- chunkIDs := make ([]flow.Identifier , 0 , len (chunks ))
432
- for _ , chunk := range chunks {
433
- chunkIDs = append (chunkIDs , chunk .ChunkID )
434
- }
435
- _ = s .chunkDataPacks .Remove (chunkIDs )
441
+ return fmt .Errorf ("cannot store events: %w" , err )
436
442
}
437
- })
438
443
439
- err = s .events .BatchStore (blockID , []flow.EventsList {result .AllEvents ()}, batch )
440
- if err != nil {
441
- return fmt .Errorf ("cannot store events: %w" , err )
442
- }
443
-
444
- err = s .serviceEvents .BatchStore (blockID , result .AllServiceEvents (), batch )
445
- if err != nil {
446
- return fmt .Errorf ("cannot store service events: %w" , err )
447
- }
444
+ err = s .serviceEvents .BatchStore (blockID , result .AllServiceEvents (), batch )
445
+ if err != nil {
446
+ return fmt .Errorf ("cannot store service events: %w" , err )
447
+ }
448
448
449
- err = s .transactionResults .BatchStore (
450
- blockID ,
451
- result .AllTransactionResults (),
452
- batch )
453
- if err != nil {
454
- return fmt .Errorf ("cannot store transaction result: %w" , err )
455
- }
449
+ err = s .transactionResults .BatchStore (
450
+ blockID ,
451
+ result .AllTransactionResults (),
452
+ batch )
453
+ if err != nil {
454
+ return fmt .Errorf ("cannot store transaction result: %w" , err )
455
+ }
456
456
457
- executionResult := & result .ExecutionReceipt .ExecutionResult
458
- // saving my receipts will also save the execution result
459
- err = s .myReceipts .BatchStoreMyReceipt (lctx , result .ExecutionReceipt , batch )
460
- if err != nil {
461
- return fmt .Errorf ("could not persist execution result: %w" , err )
462
- }
457
+ executionResult := & result .ExecutionReceipt .ExecutionResult
458
+ // saving my receipts will also save the execution result
459
+ err = s .myReceipts .BatchStoreMyReceipt (lctx , result .ExecutionReceipt , batch )
460
+ if err != nil {
461
+ return fmt .Errorf ("could not persist execution result: %w" , err )
462
+ }
463
463
464
- err = s .results .BatchIndex (blockID , executionResult .ID (), batch )
465
- if err != nil {
466
- return fmt .Errorf ("cannot index execution result: %w" , err )
467
- }
464
+ err = s .results .BatchIndex (blockID , executionResult .ID (), batch )
465
+ if err != nil {
466
+ return fmt .Errorf ("cannot index execution result: %w" , err )
467
+ }
468
468
469
- // the state commitment is the last data item to be stored, so that
470
- // IsBlockExecuted can be implemented by checking whether state commitment exists
471
- // in the database
472
- err = s .commits .BatchStore (lctx , blockID , result .CurrentEndState (), batch )
473
- if err != nil {
474
- return fmt .Errorf ("cannot store state commitment: %w" , err )
475
- }
469
+ // the state commitment is the last data item to be stored, so that
470
+ // IsBlockExecuted can be implemented by checking whether state commitment exists
471
+ // in the database
472
+ err = s .commits .BatchStore (lctx , blockID , result .CurrentEndState (), batch )
473
+ if err != nil {
474
+ return fmt .Errorf ("cannot store state commitment: %w" , err )
475
+ }
476
476
477
- return nil
477
+ return nil
478
+ })
478
479
})
479
480
}
480
481
0 commit comments