github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/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 // ignoreLatestCommitExecutionTime is used to bypass latestCommitExecutionTime checks during 82 // commit. This is used when operating in caching mode with scripts since "commits" are all done 83 // at the end of the block and are not expected to progress the execution time. 84 ignoreLatestCommitExecutionTime bool 85 } 86 87 func NewEmptyTable[ 88 TKey comparable, 89 TVal any, 90 ]( 91 initialSnapshotTime logical.Time, 92 ) *DerivedDataTable[TKey, TVal] { 93 return &DerivedDataTable[TKey, TVal]{ 94 items: map[TKey]*invalidatableEntry[TVal]{}, 95 latestCommitExecutionTime: initialSnapshotTime - 1, 96 invalidators: nil, 97 } 98 } 99 100 func (table *DerivedDataTable[TKey, TVal]) NewChildTable() *DerivedDataTable[TKey, TVal] { 101 table.lock.RLock() 102 defer table.lock.RUnlock() 103 104 items := make( 105 map[TKey]*invalidatableEntry[TVal], 106 len(table.items)) 107 108 for key, entry := range table.items { 109 // Note: We need to deep copy the invalidatableEntry here since the 110 // entry may be valid in the parent table, but invalid in the child 111 // table. 112 items[key] = &invalidatableEntry[TVal]{ 113 Value: entry.Value, 114 ExecutionSnapshot: entry.ExecutionSnapshot, 115 isInvalid: false, 116 } 117 } 118 119 return &DerivedDataTable[TKey, TVal]{ 120 items: items, 121 latestCommitExecutionTime: logical.ParentBlockTime, 122 invalidators: nil, 123 } 124 } 125 126 func (table *DerivedDataTable[TKey, TVal]) NextTxIndexForTestingOnly() uint32 { 127 return uint32(table.LatestCommitExecutionTimeForTestingOnly()) + 1 128 } 129 130 func (table *DerivedDataTable[TKey, TVal]) LatestCommitExecutionTimeForTestingOnly() logical.Time { 131 table.lock.RLock() 132 defer table.lock.RUnlock() 133 134 return table.latestCommitExecutionTime 135 } 136 137 func (table *DerivedDataTable[TKey, TVal]) EntriesForTestingOnly() map[TKey]*invalidatableEntry[TVal] { 138 table.lock.RLock() 139 defer table.lock.RUnlock() 140 141 entries := make( 142 map[TKey]*invalidatableEntry[TVal], 143 len(table.items)) 144 for key, entry := range table.items { 145 entries[key] = entry 146 } 147 148 return entries 149 } 150 151 func (table *DerivedDataTable[TKey, TVal]) InvalidatorsForTestingOnly() chainedTableInvalidators[TKey, TVal] { 152 table.lock.RLock() 153 defer table.lock.RUnlock() 154 155 return table.invalidators 156 } 157 158 func (table *DerivedDataTable[TKey, TVal]) GetForTestingOnly( 159 key TKey, 160 ) *invalidatableEntry[TVal] { 161 return table.get(key) 162 } 163 164 func (table *DerivedDataTable[TKey, TVal]) get( 165 key TKey, 166 ) *invalidatableEntry[TVal] { 167 table.lock.RLock() 168 defer table.lock.RUnlock() 169 170 return table.items[key] 171 } 172 173 func (table *DerivedDataTable[TKey, TVal]) unsafeValidate( 174 txn *TableTransaction[TKey, TVal], 175 ) error { 176 if txn.isSnapshotReadTransaction && 177 txn.invalidators.ShouldInvalidateEntries() { 178 179 return fmt.Errorf( 180 "invalid TableTransaction: snapshot read can't invalidate") 181 } 182 183 if table.latestCommitExecutionTime >= txn.executionTime { 184 return fmt.Errorf( 185 "invalid TableTransaction: non-increasing time (%v >= %v)", 186 table.latestCommitExecutionTime, 187 txn.executionTime) 188 } 189 190 for _, entry := range txn.readSet { 191 if entry.isInvalid { 192 if txn.snapshotTime == txn.executionTime { 193 // This should never happen since the transaction is 194 // sequentially executed. 195 return fmt.Errorf( 196 "invalid TableTransaction: unrecoverable outdated read set") 197 } 198 199 return errors.NewRetryableConflictError( 200 "invalid TableTransaction: outdated read set") 201 } 202 } 203 204 applicable := table.invalidators.ApplicableInvalidators( 205 txn.toValidateTime) 206 shouldInvalidateEntries := applicable.ShouldInvalidateEntries() 207 208 for key, entry := range txn.writeSet { 209 current, ok := table.items[key] 210 if ok && current != entry { 211 // The derived data table must always return the same item for a given key, 212 // otherwise the cadence runtime will have issues comparing resolved cadence types. 213 // 214 // for example: 215 // two transactions are run concurrently, first loads (cadence contracts) 216 // A and B where B depends on A. The second transaction also loads A and C, 217 // where C depends on A. The first transaction commits first. 218 // The A from the second transaction is equivalent to the A from 219 // the first transaction but it is not the same object. 220 // 221 // Overwriting A with the A from the second transaction will cause program B 222 // to break because it will not know the types from A returned from 223 // the cache in the future. 224 // Not overwriting A will cause program C to break because it will not know 225 // the types from A returned from the cache in the future. 226 // 227 // The solution is to treat this as a conflict and retry the transaction. 228 // When the transaction is retried, the A from the first transaction will 229 // be used to load C in the second transaction. 230 231 return errors.NewRetryableConflictError( 232 "invalid TableTransaction: write conflict") 233 } 234 235 if !shouldInvalidateEntries || 236 !applicable.ShouldInvalidateEntry( 237 key, 238 entry.Value, 239 entry.ExecutionSnapshot, 240 ) { 241 continue 242 } 243 244 if txn.snapshotTime == txn.executionTime { 245 // This should never happen since the transaction is 246 // sequentially executed. 247 return fmt.Errorf( 248 "invalid TableTransaction: unrecoverable outdated " + 249 "write set") 250 } 251 252 return errors.NewRetryableConflictError( 253 "invalid TableTransaction: outdated write set") 254 255 } 256 257 txn.toValidateTime = table.latestCommitExecutionTime + 1 258 259 return nil 260 } 261 262 func (table *DerivedDataTable[TKey, TVal]) validate( 263 txn *TableTransaction[TKey, TVal], 264 ) error { 265 table.lock.RLock() 266 defer table.lock.RUnlock() 267 268 return table.unsafeValidate(txn) 269 } 270 271 func (table *DerivedDataTable[TKey, TVal]) commit( 272 txn *TableTransaction[TKey, TVal], 273 ) error { 274 table.lock.Lock() 275 defer table.lock.Unlock() 276 277 if !txn.isSnapshotReadTransaction && 278 !txn.ignoreLatestCommitExecutionTime && 279 table.latestCommitExecutionTime+1 < txn.snapshotTime { 280 281 return fmt.Errorf( 282 "invalid TableTransaction: missing commit range [%v, %v)", 283 table.latestCommitExecutionTime+1, 284 txn.snapshotTime) 285 } 286 287 // NOTE: Instead of throwing out all the write entries, we can commit 288 // the valid write entries then return error. 289 err := table.unsafeValidate(txn) 290 if err != nil { 291 return err 292 } 293 294 // Don't perform actual commit for snapshot read transaction. This is 295 // safe since all values are derived from the primary source. 296 if txn.isSnapshotReadTransaction { 297 return nil 298 } 299 300 for key, entry := range txn.writeSet { 301 _, ok := table.items[key] 302 if ok { 303 // A previous transaction already committed an equivalent 304 // TableTransaction entry. Since both TableTransaction entry are 305 // valid, just reuse the existing one for future transactions. 306 continue 307 } 308 309 table.items[key] = entry 310 } 311 312 if txn.invalidators.ShouldInvalidateEntries() { 313 for key, entry := range table.items { 314 if txn.invalidators.ShouldInvalidateEntry( 315 key, 316 entry.Value, 317 entry.ExecutionSnapshot) { 318 319 entry.isInvalid = true 320 delete(table.items, key) 321 } 322 } 323 324 table.invalidators = append( 325 table.invalidators, 326 txn.invalidators...) 327 } 328 329 table.latestCommitExecutionTime = txn.executionTime 330 return nil 331 } 332 333 func (table *DerivedDataTable[TKey, TVal]) newTableTransaction( 334 snapshotTime logical.Time, 335 executionTime logical.Time, 336 isSnapshotReadTransaction bool, 337 ignoreLatestCommitExecutionTime bool, 338 ) *TableTransaction[TKey, TVal] { 339 return &TableTransaction[TKey, TVal]{ 340 table: table, 341 snapshotTime: snapshotTime, 342 executionTime: executionTime, 343 toValidateTime: snapshotTime, 344 readSet: map[TKey]*invalidatableEntry[TVal]{}, 345 writeSet: map[TKey]*invalidatableEntry[TVal]{}, 346 isSnapshotReadTransaction: isSnapshotReadTransaction, 347 ignoreLatestCommitExecutionTime: ignoreLatestCommitExecutionTime, 348 } 349 } 350 351 func (table *DerivedDataTable[TKey, TVal]) NewSnapshotReadTableTransaction() *TableTransaction[TKey, TVal] { 352 return table.newTableTransaction( 353 logical.EndOfBlockExecutionTime, 354 logical.EndOfBlockExecutionTime, 355 true, 356 false) 357 } 358 359 func (table *DerivedDataTable[TKey, TVal]) NewCachingSnapshotReadTableTransaction() *TableTransaction[TKey, TVal] { 360 return table.newTableTransaction( 361 logical.EndOfBlockExecutionTime, 362 logical.EndOfBlockExecutionTime, 363 false, 364 true) 365 } 366 367 func (table *DerivedDataTable[TKey, TVal]) NewTableTransaction( 368 snapshotTime logical.Time, 369 executionTime logical.Time, 370 ) ( 371 *TableTransaction[TKey, TVal], 372 error, 373 ) { 374 if executionTime < 0 || 375 executionTime > logical.LargestNormalTransactionExecutionTime { 376 377 return nil, fmt.Errorf( 378 "invalid TableTransactions: execution time out of bound: %v", 379 executionTime) 380 } 381 382 if snapshotTime > executionTime { 383 return nil, fmt.Errorf( 384 "invalid TableTransactions: snapshot > execution: %v > %v", 385 snapshotTime, 386 executionTime) 387 } 388 389 return table.newTableTransaction( 390 snapshotTime, 391 executionTime, 392 false, 393 false), nil 394 } 395 396 // Note: use GetOrCompute instead of Get/Set whenever possible. 397 func (txn *TableTransaction[TKey, TVal]) get(key TKey) ( 398 TVal, 399 *snapshot.ExecutionSnapshot, 400 bool, 401 ) { 402 403 writeEntry, ok := txn.writeSet[key] 404 if ok { 405 return writeEntry.Value, writeEntry.ExecutionSnapshot, true 406 } 407 408 readEntry := txn.readSet[key] 409 if readEntry != nil { 410 return readEntry.Value, readEntry.ExecutionSnapshot, true 411 } 412 413 readEntry = txn.table.get(key) 414 if readEntry != nil { 415 txn.readSet[key] = readEntry 416 return readEntry.Value, readEntry.ExecutionSnapshot, true 417 } 418 419 var defaultValue TVal 420 return defaultValue, nil, false 421 } 422 423 func (txn *TableTransaction[TKey, TVal]) GetForTestingOnly(key TKey) ( 424 TVal, 425 *snapshot.ExecutionSnapshot, 426 bool, 427 ) { 428 return txn.get(key) 429 } 430 431 func (txn *TableTransaction[TKey, TVal]) set( 432 key TKey, 433 value TVal, 434 snapshot *snapshot.ExecutionSnapshot, 435 ) { 436 txn.writeSet[key] = &invalidatableEntry[TVal]{ 437 Value: value, 438 ExecutionSnapshot: snapshot, 439 isInvalid: false, 440 } 441 442 // Since value is derived from snapshot's view. We need to reset the 443 // toValidateTime back to snapshot time to re-validate the entry. 444 txn.toValidateTime = txn.snapshotTime 445 } 446 447 func (txn *TableTransaction[TKey, TVal]) SetForTestingOnly( 448 key TKey, 449 value TVal, 450 snapshot *snapshot.ExecutionSnapshot, 451 ) { 452 txn.set(key, value, snapshot) 453 } 454 455 // GetOrCompute returns the key's value. If a pre-computed value is available, 456 // then the pre-computed value is returned and the cached state is replayed on 457 // txnState. Otherwise, the value is computed using valFunc; both the value 458 // and the states used to compute the value are captured. 459 // 460 // Note: valFunc must be an idempotent function and it must not modify 461 // txnState's values. 462 func (txn *TableTransaction[TKey, TVal]) GetOrCompute( 463 txnState state.NestedTransactionPreparer, 464 key TKey, 465 computer ValueComputer[TKey, TVal], 466 ) ( 467 TVal, 468 error, 469 ) { 470 var defaultVal TVal 471 472 val, state, ok := txn.get(key) 473 if ok { 474 err := txnState.AttachAndCommitNestedTransaction(state) 475 if err != nil { 476 return defaultVal, fmt.Errorf( 477 "failed to replay cached state: %w", 478 err) 479 } 480 481 return val, nil 482 } 483 484 nestedTxId, err := txnState.BeginNestedTransaction() 485 if err != nil { 486 return defaultVal, fmt.Errorf("failed to start nested txn: %w", err) 487 } 488 489 val, err = computer.Compute(txnState, key) 490 491 // Commit the nested transaction, even if the computation fails. 492 committedState, commitErr := txnState.CommitNestedTransaction(nestedTxId) 493 if commitErr != nil { 494 err = multierror.Append(err, 495 fmt.Errorf("failed to commit nested txn: %w", commitErr), 496 ).ErrorOrNil() 497 } 498 499 if err != nil { 500 return defaultVal, fmt.Errorf("failed to derive value: %w", err) 501 } 502 503 txn.set(key, val, committedState) 504 505 return val, nil 506 } 507 508 func (txn *TableTransaction[TKey, TVal]) AddInvalidator( 509 invalidator TableInvalidator[TKey, TVal], 510 ) { 511 if invalidator == nil || !invalidator.ShouldInvalidateEntries() { 512 return 513 } 514 515 txn.invalidators = append( 516 txn.invalidators, 517 tableInvalidatorAtTime[TKey, TVal]{ 518 TableInvalidator: invalidator, 519 executionTime: txn.executionTime, 520 }) 521 } 522 523 func (txn *TableTransaction[TKey, TVal]) Validate() error { 524 return txn.table.validate(txn) 525 } 526 527 func (txn *TableTransaction[TKey, TVal]) Commit() error { 528 return txn.table.commit(txn) 529 } 530 531 func (txn *TableTransaction[TKey, TVal]) ToValidateTimeForTestingOnly() logical.Time { 532 return txn.toValidateTime 533 }