github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/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 transaction: 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) NewCachingSnapshotReadTransactionData( 108 parameters state.StateParameters, 109 ) *TransactionData { 110 return block.newTransactionData( 111 false, 112 logical.EndOfBlockExecutionTime, 113 parameters) 114 } 115 116 func (block *BlockData) NewSnapshotReadTransactionData( 117 parameters state.StateParameters, 118 ) *TransactionData { 119 return block.newTransactionData( 120 true, 121 logical.EndOfBlockExecutionTime, 122 parameters) 123 } 124 125 func (txn *TransactionData) SnapshotTime() logical.Time { 126 return txn.snapshot.SnapshotTime() 127 } 128 129 func (txn *TransactionData) validate( 130 latestSnapshot timestampedSnapshotTree, 131 ) error { 132 validatedSnapshotTime := txn.SnapshotTime() 133 134 if latestSnapshot.SnapshotTime() <= validatedSnapshotTime { 135 // transaction's snapshot is up-to-date. 136 return nil 137 } 138 139 var readSet map[flow.RegisterID]struct{} 140 if txn.finalizedExecutionSnapshot != nil { 141 readSet = txn.finalizedExecutionSnapshot.ReadSet 142 } else { 143 readSet = txn.InterimReadSet() 144 } 145 146 updates, err := latestSnapshot.UpdatesSince(validatedSnapshotTime) 147 if err != nil { 148 return fmt.Errorf("invalid transaction: %w", err) 149 } 150 151 for i, writeSet := range updates { 152 hasConflict, registerId := intersect(writeSet, readSet) 153 if hasConflict { 154 return errors.NewRetryableConflictError( 155 conflictErrorTemplate, 156 validatedSnapshotTime+logical.Time(i), 157 txn.executionTime, 158 validatedSnapshotTime, 159 registerId) 160 } 161 } 162 163 txn.snapshot.Rebase(latestSnapshot) 164 return nil 165 } 166 167 func (txn *TransactionData) Validate() error { 168 return txn.validate(txn.block.LatestSnapshot()) 169 } 170 171 func (txn *TransactionData) Finalize() error { 172 executionSnapshot, err := txn.FinalizeMainTransaction() 173 if err != nil { 174 return err 175 } 176 177 // NOTE: Since cadence does not support the notion of read only execution, 178 // snapshot read transaction execution can inadvertently produce a non-empty 179 // write set. We'll just drop these updates. 180 if txn.isSnapshotReadTransaction { 181 executionSnapshot.WriteSet = nil 182 } 183 184 txn.finalizedExecutionSnapshot = executionSnapshot 185 return nil 186 } 187 188 func (block *BlockData) commit(txn *TransactionData) error { 189 if txn.finalizedExecutionSnapshot == nil { 190 return fmt.Errorf("invalid transaction: transaction not finalized.") 191 } 192 193 block.mutex.Lock() 194 defer block.mutex.Unlock() 195 196 err := txn.validate(block.latestSnapshot) 197 if err != nil { 198 return err 199 } 200 201 // Don't perform actual commit for snapshot read transaction since they 202 // do not advance logical time. 203 if txn.isSnapshotReadTransaction { 204 return nil 205 } 206 207 latestSnapshotTime := block.latestSnapshot.SnapshotTime() 208 209 if latestSnapshotTime < txn.executionTime { 210 // i.e., transactions are committed out-of-order. 211 return fmt.Errorf( 212 "invalid transaction: missing commit range [%v, %v)", 213 latestSnapshotTime, 214 txn.executionTime) 215 } 216 217 if block.latestSnapshot.SnapshotTime() > txn.executionTime { 218 // i.e., re-commiting an already committed transaction. 219 return fmt.Errorf( 220 "invalid transaction: non-increasing time (%v >= %v)", 221 latestSnapshotTime-1, 222 txn.executionTime) 223 } 224 225 block.latestSnapshot = block.latestSnapshot.Append( 226 txn.finalizedExecutionSnapshot) 227 228 return nil 229 } 230 231 func (txn *TransactionData) Commit() ( 232 *snapshot.ExecutionSnapshot, 233 error, 234 ) { 235 err := txn.block.commit(txn) 236 if err != nil { 237 return nil, err 238 } 239 240 return txn.finalizedExecutionSnapshot, nil 241 }