github.com/onflow/flow-go@v0.33.17/fvm/storage/derived/table.go (about) 1 package derived 2 3 import ( 4 "fmt" 5 "sync" 6 7 "github.com/hashicorp/go-multierror" 8 9 "github.com/onflow/flow-go/fvm/storage/errors" 10 "github.com/onflow/flow-go/fvm/storage/logical" 11 "github.com/onflow/flow-go/fvm/storage/snapshot" 12 "github.com/onflow/flow-go/fvm/storage/state" 13 ) 14 15 // ValueComputer is used by DerivedDataTable's GetOrCompute to compute the 16 // derived value when the value is not in DerivedDataTable (i.e., "cache miss"). 17 type ValueComputer[TKey any, TVal any] interface { 18 Compute(txnState state.NestedTransactionPreparer, key TKey) (TVal, error) 19 } 20 21 type invalidatableEntry[TVal any] struct { 22 Value TVal // immutable after initialization. 23 ExecutionSnapshot *snapshot.ExecutionSnapshot // immutable after initialization. 24 25 isInvalid bool // Guarded by DerivedDataTable' lock. 26 } 27 28 // DerivedDataTable is a rudimentary fork-aware OCC database table for 29 // "caching" homogeneous (TKey, TVal) pairs for a particular block. 30 // 31 // The database table enforces atomicity and isolation, but not consistency and 32 // durability. Consistency depends on the user correctly implementing the 33 // table's invalidator. Durability is not needed since the values are derived 34 // from ledger and can be computed on the fly (This assumes that recomputation 35 // is idempotent). 36 // 37 // Furthermore, because data are derived, transaction validation looks 38 // a bit unusual when compared with a textbook OCC implementation. In 39 // particular, the transaction's invalidator represents "real" writes to the 40 // canonical source, whereas the transaction's readSet/writeSet entries 41 // represent "real" reads from the canonical source. 42 // 43 // Multiple tables are grouped together via Validate/Commit 2 phase commit to 44 // form the complete derived data database. 45 type DerivedDataTable[TKey comparable, TVal any] struct { 46 lock sync.RWMutex 47 items map[TKey]*invalidatableEntry[TVal] 48 49 latestCommitExecutionTime logical.Time 50 51 invalidators chainedTableInvalidators[TKey, TVal] // Guarded by lock. 52 } 53 54 type TableTransaction[TKey comparable, TVal any] struct { 55 table *DerivedDataTable[TKey, TVal] 56 57 // The start time when the snapshot first becomes readable (i.e., the 58 // "snapshotTime - 1"'s transaction committed the snapshot view). 59 snapshotTime logical.Time 60 61 // The transaction (or script)'s execution start time (aka TxIndex). 62 executionTime logical.Time 63 64 // toValidateTime is used to amortize cost of repeated Validate calls. 65 // Each Validate call will only validate the time range 66 // [toValidateTime, executionTime), and will advance toValidateTime to 67 // latestCommitExecutionTime + 1 if Validate succeeded. 68 // 69 // Note that since newly derived values are computed based on snapshotTime's 70 // view, each time a newly derived value is added to the transaction, 71 // toValidateTime is reset back to snapshotTime. 72 toValidateTime logical.Time 73 74 readSet map[TKey]*invalidatableEntry[TVal] 75 writeSet map[TKey]*invalidatableEntry[TVal] 76 77 // When isSnapshotReadTransaction is true, invalidators must be empty. 78 isSnapshotReadTransaction bool 79 invalidators chainedTableInvalidators[TKey, TVal] 80 } 81 82 func NewEmptyTable[ 83 TKey comparable, 84 TVal any, 85 ]( 86 initialSnapshotTime logical.Time, 87 ) *DerivedDataTable[TKey, TVal] { 88 return &DerivedDataTable[TKey, TVal]{ 89 items: map[TKey]*invalidatableEntry[TVal]{}, 90 latestCommitExecutionTime: initialSnapshotTime - 1, 91 invalidators: nil, 92 } 93 } 94 95 func (table *DerivedDataTable[TKey, TVal]) NewChildTable() *DerivedDataTable[TKey, TVal] { 96 table.lock.RLock() 97 defer table.lock.RUnlock() 98 99 items := make( 100 map[TKey]*invalidatableEntry[TVal], 101 len(table.items)) 102 103 for key, entry := range table.items { 104 // Note: We need to deep copy the invalidatableEntry here since the 105 // entry may be valid in the parent table, but invalid in the child 106 // table. 107 items[key] = &invalidatableEntry[TVal]{ 108 Value: entry.Value, 109 ExecutionSnapshot: entry.ExecutionSnapshot, 110 isInvalid: false, 111 } 112 } 113 114 return &DerivedDataTable[TKey, TVal]{ 115 items: items, 116 latestCommitExecutionTime: logical.ParentBlockTime, 117 invalidators: nil, 118 } 119 } 120 121 func (table *DerivedDataTable[TKey, TVal]) NextTxIndexForTestingOnly() uint32 { 122 return uint32(table.LatestCommitExecutionTimeForTestingOnly()) + 1 123 } 124 125 func (table *DerivedDataTable[TKey, TVal]) LatestCommitExecutionTimeForTestingOnly() logical.Time { 126 table.lock.RLock() 127 defer table.lock.RUnlock() 128 129 return table.latestCommitExecutionTime 130 } 131 132 func (table *DerivedDataTable[TKey, TVal]) EntriesForTestingOnly() map[TKey]*invalidatableEntry[TVal] { 133 table.lock.RLock() 134 defer table.lock.RUnlock() 135 136 entries := make( 137 map[TKey]*invalidatableEntry[TVal], 138 len(table.items)) 139 for key, entry := range table.items { 140 entries[key] = entry 141 } 142 143 return entries 144 } 145 146 func (table *DerivedDataTable[TKey, TVal]) InvalidatorsForTestingOnly() chainedTableInvalidators[TKey, TVal] { 147 table.lock.RLock() 148 defer table.lock.RUnlock() 149 150 return table.invalidators 151 } 152 153 func (table *DerivedDataTable[TKey, TVal]) GetForTestingOnly( 154 key TKey, 155 ) *invalidatableEntry[TVal] { 156 return table.get(key) 157 } 158 159 func (table *DerivedDataTable[TKey, TVal]) get( 160 key TKey, 161 ) *invalidatableEntry[TVal] { 162 table.lock.RLock() 163 defer table.lock.RUnlock() 164 165 return table.items[key] 166 } 167 168 func (table *DerivedDataTable[TKey, TVal]) unsafeValidate( 169 txn *TableTransaction[TKey, TVal], 170 ) error { 171 if txn.isSnapshotReadTransaction && 172 txn.invalidators.ShouldInvalidateEntries() { 173 174 return fmt.Errorf( 175 "invalid TableTransaction: snapshot read can't invalidate") 176 } 177 178 if table.latestCommitExecutionTime >= txn.executionTime { 179 return fmt.Errorf( 180 "invalid TableTransaction: non-increasing time (%v >= %v)", 181 table.latestCommitExecutionTime, 182 txn.executionTime) 183 } 184 185 for _, entry := range txn.readSet { 186 if entry.isInvalid { 187 if txn.snapshotTime == txn.executionTime { 188 // This should never happen since the transaction is 189 // sequentially executed. 190 return fmt.Errorf( 191 "invalid TableTransaction: unrecoverable outdated read set") 192 } 193 194 return errors.NewRetryableConflictError( 195 "invalid TableTransaction: outdated read set") 196 } 197 } 198 199 applicable := table.invalidators.ApplicableInvalidators( 200 txn.toValidateTime) 201 shouldInvalidateEntries := applicable.ShouldInvalidateEntries() 202 203 for key, entry := range txn.writeSet { 204 current, ok := table.items[key] 205 if ok && current != entry { 206 // The derived data table must always return the same item for a given key, 207 // otherwise the cadence runtime will have issues comparing resolved cadence types. 208 // 209 // for example: 210 // two transactions are run concurrently, first loads (cadence contracts) 211 // A and B where B depends on A. The second transaction also loads A and C, 212 // where C depends on A. The first transaction commits first. 213 // The A from the second transaction is equivalent to the A from 214 // the first transaction but it is not the same object. 215 // 216 // Overwriting A with the A from the second transaction will cause program B 217 // to break because it will not know the types from A returned from 218 // the cache in the future. 219 // Not overwriting A will cause program C to break because it will not know 220 // the types from A returned from the cache in the future. 221 // 222 // The solution is to treat this as a conflict and retry the transaction. 223 // When the transaction is retried, the A from the first transaction will 224 // be used to load C in the second transaction. 225 226 return errors.NewRetryableConflictError( 227 "invalid TableTransaction: write conflict") 228 } 229 230 if !shouldInvalidateEntries || 231 !applicable.ShouldInvalidateEntry( 232 key, 233 entry.Value, 234 entry.ExecutionSnapshot, 235 ) { 236 continue 237 } 238 239 if txn.snapshotTime == txn.executionTime { 240 // This should never happen since the transaction is 241 // sequentially executed. 242 return fmt.Errorf( 243 "invalid TableTransaction: unrecoverable outdated " + 244 "write set") 245 } 246 247 return errors.NewRetryableConflictError( 248 "invalid TableTransaction: outdated write set") 249 250 } 251 252 txn.toValidateTime = table.latestCommitExecutionTime + 1 253 254 return nil 255 } 256 257 func (table *DerivedDataTable[TKey, TVal]) validate( 258 txn *TableTransaction[TKey, TVal], 259 ) error { 260 table.lock.RLock() 261 defer table.lock.RUnlock() 262 263 return table.unsafeValidate(txn) 264 } 265 266 func (table *DerivedDataTable[TKey, TVal]) commit( 267 txn *TableTransaction[TKey, TVal], 268 ) error { 269 table.lock.Lock() 270 defer table.lock.Unlock() 271 272 if !txn.isSnapshotReadTransaction && 273 table.latestCommitExecutionTime+1 < txn.snapshotTime { 274 275 return fmt.Errorf( 276 "invalid TableTransaction: missing commit range [%v, %v)", 277 table.latestCommitExecutionTime+1, 278 txn.snapshotTime) 279 } 280 281 // NOTE: Instead of throwing out all the write entries, we can commit 282 // the valid write entries then return error. 283 err := table.unsafeValidate(txn) 284 if err != nil { 285 return err 286 } 287 288 // Don't perform actual commit for snapshot read transaction. This is 289 // safe since all values are derived from the primary source. 290 if txn.isSnapshotReadTransaction { 291 return nil 292 } 293 294 for key, entry := range txn.writeSet { 295 _, ok := table.items[key] 296 if ok { 297 // A previous transaction already committed an equivalent 298 // TableTransaction entry. Since both TableTransaction entry are 299 // valid, just reuse the existing one for future transactions. 300 continue 301 } 302 303 table.items[key] = entry 304 } 305 306 if txn.invalidators.ShouldInvalidateEntries() { 307 for key, entry := range table.items { 308 if txn.invalidators.ShouldInvalidateEntry( 309 key, 310 entry.Value, 311 entry.ExecutionSnapshot) { 312 313 entry.isInvalid = true 314 delete(table.items, key) 315 } 316 } 317 318 table.invalidators = append( 319 table.invalidators, 320 txn.invalidators...) 321 } 322 323 table.latestCommitExecutionTime = txn.executionTime 324 return nil 325 } 326 327 func (table *DerivedDataTable[TKey, TVal]) newTableTransaction( 328 snapshotTime logical.Time, 329 executionTime logical.Time, 330 isSnapshotReadTransaction bool, 331 ) *TableTransaction[TKey, TVal] { 332 return &TableTransaction[TKey, TVal]{ 333 table: table, 334 snapshotTime: snapshotTime, 335 executionTime: executionTime, 336 toValidateTime: snapshotTime, 337 readSet: map[TKey]*invalidatableEntry[TVal]{}, 338 writeSet: map[TKey]*invalidatableEntry[TVal]{}, 339 isSnapshotReadTransaction: isSnapshotReadTransaction, 340 } 341 } 342 343 func (table *DerivedDataTable[TKey, TVal]) NewSnapshotReadTableTransaction() *TableTransaction[TKey, TVal] { 344 return table.newTableTransaction( 345 logical.EndOfBlockExecutionTime, 346 logical.EndOfBlockExecutionTime, 347 true) 348 } 349 350 func (table *DerivedDataTable[TKey, TVal]) NewTableTransaction( 351 snapshotTime logical.Time, 352 executionTime logical.Time, 353 ) ( 354 *TableTransaction[TKey, TVal], 355 error, 356 ) { 357 if executionTime < 0 || 358 executionTime > logical.LargestNormalTransactionExecutionTime { 359 360 return nil, fmt.Errorf( 361 "invalid TableTransactions: execution time out of bound: %v", 362 executionTime) 363 } 364 365 if snapshotTime > executionTime { 366 return nil, fmt.Errorf( 367 "invalid TableTransactions: snapshot > execution: %v > %v", 368 snapshotTime, 369 executionTime) 370 } 371 372 return table.newTableTransaction( 373 snapshotTime, 374 executionTime, 375 false), nil 376 } 377 378 // Note: use GetOrCompute instead of Get/Set whenever possible. 379 func (txn *TableTransaction[TKey, TVal]) get(key TKey) ( 380 TVal, 381 *snapshot.ExecutionSnapshot, 382 bool, 383 ) { 384 385 writeEntry, ok := txn.writeSet[key] 386 if ok { 387 return writeEntry.Value, writeEntry.ExecutionSnapshot, true 388 } 389 390 readEntry := txn.readSet[key] 391 if readEntry != nil { 392 return readEntry.Value, readEntry.ExecutionSnapshot, true 393 } 394 395 readEntry = txn.table.get(key) 396 if readEntry != nil { 397 txn.readSet[key] = readEntry 398 return readEntry.Value, readEntry.ExecutionSnapshot, true 399 } 400 401 var defaultValue TVal 402 return defaultValue, nil, false 403 } 404 405 func (txn *TableTransaction[TKey, TVal]) GetForTestingOnly(key TKey) ( 406 TVal, 407 *snapshot.ExecutionSnapshot, 408 bool, 409 ) { 410 return txn.get(key) 411 } 412 413 func (txn *TableTransaction[TKey, TVal]) set( 414 key TKey, 415 value TVal, 416 snapshot *snapshot.ExecutionSnapshot, 417 ) { 418 txn.writeSet[key] = &invalidatableEntry[TVal]{ 419 Value: value, 420 ExecutionSnapshot: snapshot, 421 isInvalid: false, 422 } 423 424 // Since value is derived from snapshot's view. We need to reset the 425 // toValidateTime back to snapshot time to re-validate the entry. 426 txn.toValidateTime = txn.snapshotTime 427 } 428 429 func (txn *TableTransaction[TKey, TVal]) SetForTestingOnly( 430 key TKey, 431 value TVal, 432 snapshot *snapshot.ExecutionSnapshot, 433 ) { 434 txn.set(key, value, snapshot) 435 } 436 437 // GetOrCompute returns the key's value. If a pre-computed value is available, 438 // then the pre-computed value is returned and the cached state is replayed on 439 // txnState. Otherwise, the value is computed using valFunc; both the value 440 // and the states used to compute the value are captured. 441 // 442 // Note: valFunc must be an idempotent function and it must not modify 443 // txnState's values. 444 func (txn *TableTransaction[TKey, TVal]) GetOrCompute( 445 txnState state.NestedTransactionPreparer, 446 key TKey, 447 computer ValueComputer[TKey, TVal], 448 ) ( 449 TVal, 450 error, 451 ) { 452 var defaultVal TVal 453 454 val, state, ok := txn.get(key) 455 if ok { 456 err := txnState.AttachAndCommitNestedTransaction(state) 457 if err != nil { 458 return defaultVal, fmt.Errorf( 459 "failed to replay cached state: %w", 460 err) 461 } 462 463 return val, nil 464 } 465 466 nestedTxId, err := txnState.BeginNestedTransaction() 467 if err != nil { 468 return defaultVal, fmt.Errorf("failed to start nested txn: %w", err) 469 } 470 471 val, err = computer.Compute(txnState, key) 472 473 // Commit the nested transaction, even if the computation fails. 474 committedState, commitErr := txnState.CommitNestedTransaction(nestedTxId) 475 if commitErr != nil { 476 err = multierror.Append(err, 477 fmt.Errorf("failed to commit nested txn: %w", commitErr), 478 ).ErrorOrNil() 479 } 480 481 if err != nil { 482 return defaultVal, fmt.Errorf("failed to derive value: %w", err) 483 } 484 485 txn.set(key, val, committedState) 486 487 return val, nil 488 } 489 490 func (txn *TableTransaction[TKey, TVal]) AddInvalidator( 491 invalidator TableInvalidator[TKey, TVal], 492 ) { 493 if invalidator == nil || !invalidator.ShouldInvalidateEntries() { 494 return 495 } 496 497 txn.invalidators = append( 498 txn.invalidators, 499 tableInvalidatorAtTime[TKey, TVal]{ 500 TableInvalidator: invalidator, 501 executionTime: txn.executionTime, 502 }) 503 } 504 505 func (txn *TableTransaction[TKey, TVal]) Validate() error { 506 return txn.table.validate(txn) 507 } 508 509 func (txn *TableTransaction[TKey, TVal]) Commit() error { 510 return txn.table.commit(txn) 511 } 512 513 func (txn *TableTransaction[TKey, TVal]) ToValidateTimeForTestingOnly() logical.Time { 514 return txn.toValidateTime 515 }