github.com/onflow/flow-go@v0.33.17/fvm/storage/primary/block_data.go (about) 1 package primary 2 3 import ( 4 "fmt" 5 "sync" 6 7 "github.com/onflow/flow-go/fvm/storage/errors" 8 "github.com/onflow/flow-go/fvm/storage/logical" 9 "github.com/onflow/flow-go/fvm/storage/snapshot" 10 "github.com/onflow/flow-go/fvm/storage/state" 11 "github.com/onflow/flow-go/model/flow" 12 ) 13 14 const ( 15 conflictErrorTemplate = "invalid transaction: committed txn %d conflicts " + 16 "with executing txn %d with snapshot at %d (Conflicting register: %v)" 17 ) 18 19 // BlockData is a rudimentary in-memory MVCC database for storing (RegisterID, 20 // RegisterValue) pairs for a particular block. The database enforces 21 // atomicity, consistency, and isolation, but not durability (The transactions 22 // are made durable by the block computer using aggregated execution snapshots). 23 type BlockData struct { 24 mutex sync.RWMutex 25 26 latestSnapshot timestampedSnapshotTree // Guarded by mutex 27 } 28 29 type TransactionData struct { 30 block *BlockData 31 32 executionTime logical.Time 33 isSnapshotReadTransaction bool 34 35 snapshot *rebaseableTimestampedSnapshotTree 36 37 state.NestedTransactionPreparer 38 39 finalizedExecutionSnapshot *snapshot.ExecutionSnapshot 40 } 41 42 // Note: storageSnapshot must be thread safe. 43 func NewBlockData( 44 storageSnapshot snapshot.StorageSnapshot, 45 snapshotTime logical.Time, 46 ) *BlockData { 47 return &BlockData{ 48 latestSnapshot: newTimestampedSnapshotTree( 49 storageSnapshot, 50 logical.Time(snapshotTime)), 51 } 52 } 53 54 func (block *BlockData) LatestSnapshot() timestampedSnapshotTree { 55 block.mutex.RLock() 56 defer block.mutex.RUnlock() 57 58 return block.latestSnapshot 59 } 60 61 func (block *BlockData) newTransactionData( 62 isSnapshotReadTransaction bool, 63 executionTime logical.Time, 64 parameters state.StateParameters, 65 ) *TransactionData { 66 snapshot := newRebaseableTimestampedSnapshotTree(block.LatestSnapshot()) 67 return &TransactionData{ 68 block: block, 69 executionTime: executionTime, 70 snapshot: snapshot, 71 isSnapshotReadTransaction: isSnapshotReadTransaction, 72 NestedTransactionPreparer: state.NewTransactionState( 73 snapshot, 74 parameters), 75 } 76 } 77 78 func (block *BlockData) NewTransactionData( 79 executionTime logical.Time, 80 parameters state.StateParameters, 81 ) ( 82 *TransactionData, 83 error, 84 ) { 85 if executionTime < 0 || 86 executionTime > logical.LargestNormalTransactionExecutionTime { 87 88 return nil, fmt.Errorf( 89 "invalid tranaction: execution time out of bound") 90 } 91 92 txn := block.newTransactionData( 93 false, 94 executionTime, 95 parameters) 96 97 if txn.SnapshotTime() > executionTime { 98 return nil, fmt.Errorf( 99 "invalid transaction: snapshot > execution: %v > %v", 100 txn.SnapshotTime(), 101 executionTime) 102 } 103 104 return txn, nil 105 } 106 107 func (block *BlockData) NewSnapshotReadTransactionData( 108 parameters state.StateParameters, 109 ) *TransactionData { 110 return block.newTransactionData( 111 true, 112 logical.EndOfBlockExecutionTime, 113 parameters) 114 } 115 116 func (txn *TransactionData) SnapshotTime() logical.Time { 117 return txn.snapshot.SnapshotTime() 118 } 119 120 func (txn *TransactionData) validate( 121 latestSnapshot timestampedSnapshotTree, 122 ) error { 123 validatedSnapshotTime := txn.SnapshotTime() 124 125 if latestSnapshot.SnapshotTime() <= validatedSnapshotTime { 126 // transaction's snapshot is up-to-date. 127 return nil 128 } 129 130 var readSet map[flow.RegisterID]struct{} 131 if txn.finalizedExecutionSnapshot != nil { 132 readSet = txn.finalizedExecutionSnapshot.ReadSet 133 } else { 134 readSet = txn.InterimReadSet() 135 } 136 137 updates, err := latestSnapshot.UpdatesSince(validatedSnapshotTime) 138 if err != nil { 139 return fmt.Errorf("invalid transaction: %w", err) 140 } 141 142 for i, writeSet := range updates { 143 hasConflict, registerId := intersect(writeSet, readSet) 144 if hasConflict { 145 return errors.NewRetryableConflictError( 146 conflictErrorTemplate, 147 validatedSnapshotTime+logical.Time(i), 148 txn.executionTime, 149 validatedSnapshotTime, 150 registerId) 151 } 152 } 153 154 txn.snapshot.Rebase(latestSnapshot) 155 return nil 156 } 157 158 func (txn *TransactionData) Validate() error { 159 return txn.validate(txn.block.LatestSnapshot()) 160 } 161 162 func (txn *TransactionData) Finalize() error { 163 executionSnapshot, err := txn.FinalizeMainTransaction() 164 if err != nil { 165 return err 166 } 167 168 // NOTE: Since cadence does not support the notion of read only execution, 169 // snapshot read transaction execution can inadvertently produce a non-empty 170 // write set. We'll just drop these updates. 171 if txn.isSnapshotReadTransaction { 172 executionSnapshot.WriteSet = nil 173 } 174 175 txn.finalizedExecutionSnapshot = executionSnapshot 176 return nil 177 } 178 179 func (block *BlockData) commit(txn *TransactionData) error { 180 if txn.finalizedExecutionSnapshot == nil { 181 return fmt.Errorf("invalid transaction: transaction not finalized.") 182 } 183 184 block.mutex.Lock() 185 defer block.mutex.Unlock() 186 187 err := txn.validate(block.latestSnapshot) 188 if err != nil { 189 return err 190 } 191 192 // Don't perform actual commit for snapshot read transaction since they 193 // do not advance logical time. 194 if txn.isSnapshotReadTransaction { 195 return nil 196 } 197 198 latestSnapshotTime := block.latestSnapshot.SnapshotTime() 199 200 if latestSnapshotTime < txn.executionTime { 201 // i.e., transactions are committed out-of-order. 202 return fmt.Errorf( 203 "invalid transaction: missing commit range [%v, %v)", 204 latestSnapshotTime, 205 txn.executionTime) 206 } 207 208 if block.latestSnapshot.SnapshotTime() > txn.executionTime { 209 // i.e., re-commiting an already committed transaction. 210 return fmt.Errorf( 211 "invalid transaction: non-increasing time (%v >= %v)", 212 latestSnapshotTime-1, 213 txn.executionTime) 214 } 215 216 block.latestSnapshot = block.latestSnapshot.Append( 217 txn.finalizedExecutionSnapshot) 218 219 return nil 220 } 221 222 func (txn *TransactionData) Commit() ( 223 *snapshot.ExecutionSnapshot, 224 error, 225 ) { 226 err := txn.block.commit(txn) 227 if err != nil { 228 return nil, err 229 } 230 231 return txn.finalizedExecutionSnapshot, nil 232 }