github.com/koko1123/flow-go-1@v0.29.6/fvm/derived/table.go (about) 1 package derived 2 3 import ( 4 "fmt" 5 "sync" 6 7 "github.com/koko1123/flow-go-1/fvm/state" 8 ) 9 10 // ValueComputer is used by DerivedDataTable's GetOrCompute to compute the 11 // derived value when the value is not in DerivedDataTable (i.e., "cache miss"). 12 type ValueComputer[TKey any, TVal any] interface { 13 Compute(txnState *state.TransactionState, key TKey) (TVal, error) 14 } 15 16 type invalidatableEntry[TVal any] struct { 17 Value TVal // immutable after initialization. 18 State *state.State // immutable after initialization. 19 20 isInvalid bool // Guarded by DerivedDataTable' lock. 21 } 22 23 // DerivedDataTable is a rudimentary fork-aware OCC database table for 24 // "caching" homogeneous (TKey, TVal) pairs for a particular block. 25 // 26 // The database table enforces atomicity and isolation, but not consistency and 27 // durability. Consistency depends on the user correctly implementing the 28 // table's invalidator. Durability is not needed since the values are derived 29 // from ledger and can be computed on the fly (This assumes that recomputation 30 // is idempotent). 31 // 32 // Furthermore, because data are derived, transaction validation looks 33 // a bit unusual when compared with a textbook OCC implementation. In 34 // particular, the transaction's invalidator represents "real" writes to the 35 // canonical source, whereas the transaction's readSet/writeSet entries 36 // represent "real" reads from the canonical source. 37 // 38 // Multiple tables are grouped together via Validate/Commit 2 phase commit to 39 // form the complete derived data database. 40 type DerivedDataTable[TKey comparable, TVal any] struct { 41 lock sync.RWMutex 42 items map[TKey]*invalidatableEntry[TVal] 43 44 latestCommitExecutionTime LogicalTime 45 46 invalidators chainedTableInvalidators[TKey, TVal] // Guarded by lock. 47 } 48 49 type TableTransaction[TKey comparable, TVal any] struct { 50 table *DerivedDataTable[TKey, TVal] 51 52 // The start time when the snapshot first becomes readable (i.e., the 53 // "snapshotTime - 1"'s transaction committed the snapshot view) 54 snapshotTime LogicalTime 55 56 // The transaction (or script)'s execution start time (aka TxIndex). 57 executionTime LogicalTime 58 59 readSet map[TKey]*invalidatableEntry[TVal] 60 writeSet map[TKey]*invalidatableEntry[TVal] 61 62 // When isSnapshotReadTransaction is true, invalidators must be empty. 63 isSnapshotReadTransaction bool 64 invalidators chainedTableInvalidators[TKey, TVal] 65 } 66 67 func newEmptyTable[TKey comparable, TVal any]( 68 latestCommit LogicalTime, 69 ) *DerivedDataTable[TKey, TVal] { 70 return &DerivedDataTable[TKey, TVal]{ 71 items: map[TKey]*invalidatableEntry[TVal]{}, 72 latestCommitExecutionTime: latestCommit, 73 invalidators: nil, 74 } 75 } 76 77 func NewEmptyTable[TKey comparable, TVal any]() *DerivedDataTable[TKey, TVal] { 78 return newEmptyTable[TKey, TVal](ParentBlockTime) 79 } 80 81 // This variant is needed by the chunk verifier, which does not start at the 82 // beginning of the block. 83 func NewEmptyTableWithOffset[TKey comparable, TVal any](offset uint32) *DerivedDataTable[TKey, TVal] { 84 return newEmptyTable[TKey, TVal](LogicalTime(offset) - 1) 85 } 86 87 func (table *DerivedDataTable[TKey, TVal]) NewChildTable() *DerivedDataTable[TKey, TVal] { 88 table.lock.RLock() 89 defer table.lock.RUnlock() 90 91 items := make( 92 map[TKey]*invalidatableEntry[TVal], 93 len(table.items)) 94 95 for key, entry := range table.items { 96 // Note: We need to deep copy the invalidatableEntry here since the 97 // entry may be valid in the parent table, but invalid in the child 98 // table. 99 items[key] = &invalidatableEntry[TVal]{ 100 Value: entry.Value, 101 State: entry.State, 102 isInvalid: false, 103 } 104 } 105 106 return &DerivedDataTable[TKey, TVal]{ 107 items: items, 108 latestCommitExecutionTime: ParentBlockTime, 109 invalidators: nil, 110 } 111 } 112 113 func (table *DerivedDataTable[TKey, TVal]) NextTxIndexForTestingOnly() uint32 { 114 return uint32(table.LatestCommitExecutionTimeForTestingOnly()) + 1 115 } 116 117 func (table *DerivedDataTable[TKey, TVal]) LatestCommitExecutionTimeForTestingOnly() LogicalTime { 118 table.lock.RLock() 119 defer table.lock.RUnlock() 120 121 return table.latestCommitExecutionTime 122 } 123 124 func (table *DerivedDataTable[TKey, TVal]) EntriesForTestingOnly() map[TKey]*invalidatableEntry[TVal] { 125 table.lock.RLock() 126 defer table.lock.RUnlock() 127 128 entries := make( 129 map[TKey]*invalidatableEntry[TVal], 130 len(table.items)) 131 for key, entry := range table.items { 132 entries[key] = entry 133 } 134 135 return entries 136 } 137 138 func (table *DerivedDataTable[TKey, TVal]) InvalidatorsForTestingOnly() chainedTableInvalidators[TKey, TVal] { 139 table.lock.RLock() 140 defer table.lock.RUnlock() 141 142 return table.invalidators 143 } 144 145 func (table *DerivedDataTable[TKey, TVal]) GetForTestingOnly( 146 key TKey, 147 ) *invalidatableEntry[TVal] { 148 return table.get(key) 149 } 150 151 func (table *DerivedDataTable[TKey, TVal]) get( 152 key TKey, 153 ) *invalidatableEntry[TVal] { 154 table.lock.RLock() 155 defer table.lock.RUnlock() 156 157 return table.items[key] 158 } 159 160 func (table *DerivedDataTable[TKey, TVal]) unsafeValidate( 161 item *TableTransaction[TKey, TVal], 162 ) RetryableError { 163 if item.isSnapshotReadTransaction && 164 item.invalidators.ShouldInvalidateEntries() { 165 166 return newNotRetryableError( 167 "invalid TableTransaction: snapshot read can't invalidate") 168 } 169 170 if table.latestCommitExecutionTime >= item.executionTime { 171 return newNotRetryableError( 172 "invalid TableTransaction: non-increasing time (%v >= %v)", 173 table.latestCommitExecutionTime, 174 item.executionTime) 175 } 176 177 if table.latestCommitExecutionTime+1 < item.snapshotTime && 178 (!item.isSnapshotReadTransaction || 179 item.snapshotTime != EndOfBlockExecutionTime) { 180 181 return newNotRetryableError( 182 "invalid TableTransaction: missing commit range [%v, %v)", 183 table.latestCommitExecutionTime+1, 184 item.snapshotTime) 185 } 186 187 for _, entry := range item.readSet { 188 if entry.isInvalid { 189 return newRetryableError( 190 "invalid TableTransactions. outdated read set") 191 } 192 } 193 194 applicable := table.invalidators.ApplicableInvalidators( 195 item.snapshotTime) 196 if applicable.ShouldInvalidateEntries() { 197 for key, entry := range item.writeSet { 198 if applicable.ShouldInvalidateEntry(key, entry.Value, entry.State) { 199 return newRetryableError( 200 "invalid TableTransactions. outdated write set") 201 } 202 } 203 } 204 205 return nil 206 } 207 208 func (table *DerivedDataTable[TKey, TVal]) validate( 209 item *TableTransaction[TKey, TVal], 210 ) RetryableError { 211 table.lock.RLock() 212 defer table.lock.RUnlock() 213 214 return table.unsafeValidate(item) 215 } 216 217 func (table *DerivedDataTable[TKey, TVal]) commit( 218 txn *TableTransaction[TKey, TVal], 219 ) RetryableError { 220 table.lock.Lock() 221 defer table.lock.Unlock() 222 223 // NOTE: Instead of throwing out all the write entries, we can commit 224 // the valid write entries then return error. 225 err := table.unsafeValidate(txn) 226 if err != nil { 227 return err 228 } 229 230 for key, entry := range txn.writeSet { 231 _, ok := table.items[key] 232 if ok { 233 // A previous transaction already committed an equivalent TableTransaction 234 // entry. Since both TableTransaction entry are valid, just reuse the 235 // existing one for future transactions. 236 continue 237 } 238 239 table.items[key] = entry 240 } 241 242 if txn.invalidators.ShouldInvalidateEntries() { 243 for key, entry := range table.items { 244 if txn.invalidators.ShouldInvalidateEntry( 245 key, 246 entry.Value, 247 entry.State) { 248 249 entry.isInvalid = true 250 delete(table.items, key) 251 } 252 } 253 254 table.invalidators = append( 255 table.invalidators, 256 txn.invalidators...) 257 } 258 259 // NOTE: We cannot advance commit time when we encounter a snapshot read 260 // (aka script) transaction since these transactions don't generate new 261 // snapshots. It is safe to commit the entries since snapshot read 262 // transactions never invalidate entries. 263 if !txn.isSnapshotReadTransaction { 264 table.latestCommitExecutionTime = txn.executionTime 265 } 266 return nil 267 } 268 269 func (table *DerivedDataTable[TKey, TVal]) newTableTransaction( 270 upperBoundExecutionTime LogicalTime, 271 snapshotTime LogicalTime, 272 executionTime LogicalTime, 273 isSnapshotReadTransaction bool, 274 ) ( 275 *TableTransaction[TKey, TVal], 276 error, 277 ) { 278 if executionTime < 0 || executionTime > upperBoundExecutionTime { 279 return nil, fmt.Errorf( 280 "invalid TableTransactions: execution time out of bound: %v", 281 executionTime) 282 } 283 284 if snapshotTime > executionTime { 285 return nil, fmt.Errorf( 286 "invalid TableTransactions: snapshot > execution: %v > %v", 287 snapshotTime, 288 executionTime) 289 } 290 291 return &TableTransaction[TKey, TVal]{ 292 table: table, 293 snapshotTime: snapshotTime, 294 executionTime: executionTime, 295 readSet: map[TKey]*invalidatableEntry[TVal]{}, 296 writeSet: map[TKey]*invalidatableEntry[TVal]{}, 297 isSnapshotReadTransaction: isSnapshotReadTransaction, 298 }, nil 299 } 300 301 func (table *DerivedDataTable[TKey, TVal]) NewSnapshotReadTableTransaction( 302 snapshotTime LogicalTime, 303 executionTime LogicalTime, 304 ) ( 305 *TableTransaction[TKey, TVal], 306 error, 307 ) { 308 return table.newTableTransaction( 309 LargestSnapshotReadTransactionExecutionTime, 310 snapshotTime, 311 executionTime, 312 true) 313 } 314 315 func (table *DerivedDataTable[TKey, TVal]) NewTableTransaction( 316 snapshotTime LogicalTime, 317 executionTime LogicalTime, 318 ) ( 319 *TableTransaction[TKey, TVal], 320 error, 321 ) { 322 return table.newTableTransaction( 323 LargestNormalTransactionExecutionTime, 324 snapshotTime, 325 executionTime, 326 false) 327 } 328 329 // Note: use GetOrCompute instead of Get/Set whenever possible. 330 func (txn *TableTransaction[TKey, TVal]) Get(key TKey) ( 331 TVal, 332 *state.State, 333 bool, 334 ) { 335 336 writeEntry, ok := txn.writeSet[key] 337 if ok { 338 return writeEntry.Value, writeEntry.State, true 339 } 340 341 readEntry := txn.readSet[key] 342 if readEntry != nil { 343 return readEntry.Value, readEntry.State, true 344 } 345 346 readEntry = txn.table.get(key) 347 if readEntry != nil { 348 txn.readSet[key] = readEntry 349 return readEntry.Value, readEntry.State, true 350 } 351 352 var defaultValue TVal 353 return defaultValue, nil, false 354 } 355 356 // Note: use GetOrCompute instead of Get/Set whenever possible. 357 func (txn *TableTransaction[TKey, TVal]) Set( 358 key TKey, 359 value TVal, 360 state *state.State, 361 ) { 362 txn.writeSet[key] = &invalidatableEntry[TVal]{ 363 Value: value, 364 State: state, 365 isInvalid: false, 366 } 367 } 368 369 // GetOrCompute returns the key's value. If a pre-computed value is available, 370 // then the pre-computed value is returned and the cached state is replayed on 371 // txnState. Otherwise, the value is computed using valFunc; both the value 372 // and the states used to compute the value are captured. 373 // 374 // Note: valFunc must be an idempotent function and it must not modify 375 // txnState's values. 376 func (txn *TableTransaction[TKey, TVal]) GetOrCompute( 377 txnState *state.TransactionState, 378 key TKey, 379 computer ValueComputer[TKey, TVal], 380 ) ( 381 TVal, 382 error, 383 ) { 384 var defaultVal TVal 385 386 val, state, ok := txn.Get(key) 387 if ok { 388 err := txnState.AttachAndCommit(state) 389 if err != nil { 390 return defaultVal, fmt.Errorf( 391 "failed to replay cached state: %w", 392 err) 393 } 394 395 return val, nil 396 } 397 398 nestedTxId, err := txnState.BeginNestedTransaction() 399 if err != nil { 400 return defaultVal, fmt.Errorf("failed to start nested txn: %w", err) 401 } 402 403 val, err = computer.Compute(txnState, key) 404 if err != nil { 405 return defaultVal, fmt.Errorf("failed to derive value: %w", err) 406 } 407 408 committedState, err := txnState.Commit(nestedTxId) 409 if err != nil { 410 return defaultVal, fmt.Errorf("failed to commit nested txn: %w", err) 411 } 412 413 txn.Set(key, val, committedState) 414 415 return val, nil 416 } 417 418 func (txn *TableTransaction[TKey, TVal]) AddInvalidator( 419 invalidator TableInvalidator[TKey, TVal], 420 ) { 421 if invalidator == nil || !invalidator.ShouldInvalidateEntries() { 422 return 423 } 424 425 txn.invalidators = append( 426 txn.invalidators, 427 tableInvalidatorAtTime[TKey, TVal]{ 428 TableInvalidator: invalidator, 429 executionTime: txn.executionTime, 430 }) 431 } 432 433 func (txn *TableTransaction[TKey, TVal]) Validate() RetryableError { 434 return txn.table.validate(txn) 435 } 436 437 func (txn *TableTransaction[TKey, TVal]) Commit() RetryableError { 438 return txn.table.commit(txn) 439 }