github.com/onflow/flow-go@v0.33.17/fvm/storage/state/transaction_state.go (about) 1 package state 2 3 import ( 4 "fmt" 5 6 "github.com/onflow/cadence/runtime/common" 7 8 "github.com/onflow/flow-go/fvm/meter" 9 "github.com/onflow/flow-go/fvm/storage/snapshot" 10 "github.com/onflow/flow-go/model/flow" 11 ) 12 13 // Opaque identifier used for Restarting nested transactions 14 type NestedTransactionId struct { 15 state *ExecutionState 16 } 17 18 func (id NestedTransactionId) StateForTestingOnly() *ExecutionState { 19 return id.state 20 } 21 22 type Meter interface { 23 MeterComputation(kind common.ComputationKind, intensity uint) error 24 ComputationAvailable(kind common.ComputationKind, intensity uint) bool 25 ComputationIntensities() meter.MeteredComputationIntensities 26 TotalComputationLimit() uint 27 TotalComputationUsed() uint64 28 29 MeterMemory(kind common.MemoryKind, intensity uint) error 30 MemoryIntensities() meter.MeteredMemoryIntensities 31 TotalMemoryEstimate() uint64 32 33 InteractionUsed() uint64 34 35 MeterEmittedEvent(byteSize uint64) error 36 TotalEmittedEventBytes() uint64 37 38 // RunWithAllLimitsDisabled runs f with limits disabled 39 RunWithAllLimitsDisabled(f func()) 40 } 41 42 // NestedTransactionPreparer provides active transaction states and facilitates 43 // common state management operations. 44 type NestedTransactionPreparer interface { 45 Meter 46 47 // NumNestedTransactions returns the number of uncommitted nested 48 // transactions. Note that the main transaction is not considered a 49 // nested transaction. 50 NumNestedTransactions() int 51 52 // IsParseRestricted returns true if the current nested transaction is in 53 // parse resticted access mode. 54 IsParseRestricted() bool 55 56 MainTransactionId() NestedTransactionId 57 58 // IsCurrent returns true if the provide id refers to the current (nested) 59 // transaction. 60 IsCurrent(id NestedTransactionId) bool 61 62 // InterimReadSet returns the current read set aggregated from all 63 // outstanding nested transactions. 64 InterimReadSet() map[flow.RegisterID]struct{} 65 66 // FinalizeMainTransaction finalizes the main transaction and returns 67 // its execution snapshot. The finalized main transaction will not accept 68 // any new commits after this point. This returns an error if there are 69 // outstanding nested transactions. 70 FinalizeMainTransaction() (*snapshot.ExecutionSnapshot, error) 71 72 // BeginNestedTransaction creates a unrestricted nested transaction within 73 // the current unrestricted (nested) transaction. The meter parameters are 74 // inherited from the current transaction. This returns error if the 75 // current nested transaction is program restricted. 76 BeginNestedTransaction() ( 77 NestedTransactionId, 78 error, 79 ) 80 81 // BeginNestedTransactionWithMeterParams creates a unrestricted nested 82 // transaction within the current unrestricted (nested) transaction, using 83 // the provided meter parameters. This returns error if the current nested 84 // transaction is program restricted. 85 BeginNestedTransactionWithMeterParams( 86 params meter.MeterParameters, 87 ) ( 88 NestedTransactionId, 89 error, 90 ) 91 92 // BeginParseRestrictedNestedTransaction creates a restricted nested 93 // transaction within the current (nested) transaction. The meter 94 // parameters are inherited from the current transaction. 95 BeginParseRestrictedNestedTransaction( 96 location common.AddressLocation, 97 ) ( 98 NestedTransactionId, 99 error, 100 ) 101 102 // CommitNestedTransaction commits the changes in the current unrestricted 103 // nested transaction to the parent (nested) transaction. This returns 104 // error if the expectedId does not match the current nested transaction. 105 // This returns the committed execution snapshot otherwise. 106 // 107 // Note: The returned committed execution snapshot may be reused by another 108 // transaction via AttachAndCommitNestedTransaction to update the 109 // transaction bookkeeping, but the caller must manually invalidate the 110 // state. 111 // USE WITH EXTREME CAUTION. 112 CommitNestedTransaction( 113 expectedId NestedTransactionId, 114 ) ( 115 *snapshot.ExecutionSnapshot, 116 error, 117 ) 118 119 // CommitParseRestrictedNestedTransaction commits the changes in the 120 // current restricted nested transaction to the parent (nested) 121 // transaction. This returns error if the specified location does not 122 // match the tracked location. This returns the committed execution 123 // snapshot otherwise. 124 // 125 // Note: The returned committed execution snapshot may be reused by another 126 // transaction via AttachAndCommitNestedTransaction to update the 127 // transaction bookkeeping, but the caller must manually invalidate the 128 // state. 129 // USE WITH EXTREME CAUTION. 130 CommitParseRestrictedNestedTransaction( 131 location common.AddressLocation, 132 ) ( 133 *snapshot.ExecutionSnapshot, 134 error, 135 ) 136 137 // AttachAndCommitNestedTransaction commits the changes from the cached 138 // nested transaction execution snapshot to the current (nested) 139 // transaction. 140 AttachAndCommitNestedTransaction( 141 cachedSnapshot *snapshot.ExecutionSnapshot, 142 ) error 143 144 // RestartNestedTransaction merges all changes that belongs to the nested 145 // transaction about to be restart (for spock/meter bookkeeping), then 146 // wipes its view changes. 147 RestartNestedTransaction( 148 id NestedTransactionId, 149 ) error 150 151 Get(id flow.RegisterID) (flow.RegisterValue, error) 152 153 Set(id flow.RegisterID, value flow.RegisterValue) error 154 } 155 156 type nestedTransactionStackFrame struct { 157 *ExecutionState 158 159 // When nil, the subtransaction will have unrestricted access to the runtime 160 // environment. When non-nil, the subtransaction will only have access to 161 // the parts of the runtime environment necessary for importing/parsing the 162 // program, specifically, environment.ContractReader and 163 // environment.Programs. 164 parseRestriction *common.AddressLocation 165 } 166 167 type transactionState struct { 168 // NOTE: The first frame is always the main transaction, and is not 169 // poppable during the course of the transaction. 170 nestedTransactions []nestedTransactionStackFrame 171 } 172 173 // NewTransactionState constructs a new state transaction which manages nested 174 // transactions. 175 func NewTransactionState( 176 snapshot snapshot.StorageSnapshot, 177 params StateParameters, 178 ) NestedTransactionPreparer { 179 startState := NewExecutionState(snapshot, params) 180 return &transactionState{ 181 nestedTransactions: []nestedTransactionStackFrame{ 182 nestedTransactionStackFrame{ 183 ExecutionState: startState, 184 parseRestriction: nil, 185 }, 186 }, 187 } 188 } 189 190 func (txnState *transactionState) current() nestedTransactionStackFrame { 191 return txnState.nestedTransactions[txnState.NumNestedTransactions()] 192 } 193 194 func (txnState *transactionState) NumNestedTransactions() int { 195 return len(txnState.nestedTransactions) - 1 196 } 197 198 func (txnState *transactionState) IsParseRestricted() bool { 199 return txnState.current().parseRestriction != nil 200 } 201 202 func (txnState *transactionState) MainTransactionId() NestedTransactionId { 203 return NestedTransactionId{ 204 state: txnState.nestedTransactions[0].ExecutionState, 205 } 206 } 207 208 func (txnState *transactionState) IsCurrent(id NestedTransactionId) bool { 209 return txnState.current().ExecutionState == id.state 210 } 211 212 func (txnState *transactionState) InterimReadSet() map[flow.RegisterID]struct{} { 213 sizeEstimate := 0 214 for _, frame := range txnState.nestedTransactions { 215 sizeEstimate += frame.readSetSize() 216 } 217 218 result := make(map[flow.RegisterID]struct{}, sizeEstimate) 219 220 // Note: the interim read set must be accumulated in reverse order since 221 // the parent frame's write set will override the child frame's read set. 222 for i := len(txnState.nestedTransactions) - 1; i >= 0; i-- { 223 txnState.nestedTransactions[i].interimReadSet(result) 224 } 225 226 return result 227 } 228 229 func (txnState *transactionState) FinalizeMainTransaction() ( 230 *snapshot.ExecutionSnapshot, 231 error, 232 ) { 233 if len(txnState.nestedTransactions) > 1 { 234 return nil, fmt.Errorf( 235 "cannot finalize with outstanding nested transaction(s)") 236 } 237 238 return txnState.nestedTransactions[0].Finalize(), nil 239 } 240 241 func (txnState *transactionState) BeginNestedTransaction() ( 242 NestedTransactionId, 243 error, 244 ) { 245 if txnState.IsParseRestricted() { 246 return NestedTransactionId{}, fmt.Errorf( 247 "cannot begin a unrestricted nested transaction inside a " + 248 "program restricted nested transaction", 249 ) 250 } 251 252 child := txnState.current().NewChild() 253 txnState.push(child, nil) 254 255 return NestedTransactionId{ 256 state: child, 257 }, nil 258 } 259 260 func (txnState *transactionState) BeginNestedTransactionWithMeterParams( 261 params meter.MeterParameters, 262 ) ( 263 NestedTransactionId, 264 error, 265 ) { 266 if txnState.IsParseRestricted() { 267 return NestedTransactionId{}, fmt.Errorf( 268 "cannot begin a unrestricted nested transaction inside a " + 269 "program restricted nested transaction", 270 ) 271 } 272 273 child := txnState.current().NewChildWithMeterParams(params) 274 txnState.push(child, nil) 275 276 return NestedTransactionId{ 277 state: child, 278 }, nil 279 } 280 281 func (txnState *transactionState) BeginParseRestrictedNestedTransaction( 282 location common.AddressLocation, 283 ) ( 284 NestedTransactionId, 285 error, 286 ) { 287 child := txnState.current().NewChild() 288 txnState.push(child, &location) 289 290 return NestedTransactionId{ 291 state: child, 292 }, nil 293 } 294 295 func (txnState *transactionState) push( 296 child *ExecutionState, 297 location *common.AddressLocation, 298 ) { 299 txnState.nestedTransactions = append( 300 txnState.nestedTransactions, 301 nestedTransactionStackFrame{ 302 ExecutionState: child, 303 parseRestriction: location, 304 }, 305 ) 306 } 307 308 func (txnState *transactionState) pop(op string) (*ExecutionState, error) { 309 if len(txnState.nestedTransactions) < 2 { 310 return nil, fmt.Errorf("cannot %s the main transaction", op) 311 } 312 313 child := txnState.current() 314 txnState.nestedTransactions = txnState.nestedTransactions[:len(txnState.nestedTransactions)-1] 315 316 return child.ExecutionState, nil 317 } 318 319 func (txnState *transactionState) mergeIntoParent() ( 320 *snapshot.ExecutionSnapshot, 321 error, 322 ) { 323 childState, err := txnState.pop("commit") 324 if err != nil { 325 return nil, err 326 } 327 328 childSnapshot := childState.Finalize() 329 330 err = txnState.current().Merge(childSnapshot) 331 if err != nil { 332 return nil, err 333 } 334 335 return childSnapshot, nil 336 } 337 338 func (txnState *transactionState) CommitNestedTransaction( 339 expectedId NestedTransactionId, 340 ) ( 341 *snapshot.ExecutionSnapshot, 342 error, 343 ) { 344 if !txnState.IsCurrent(expectedId) { 345 return nil, fmt.Errorf( 346 "cannot commit unexpected nested transaction: id mismatch", 347 ) 348 } 349 350 if txnState.IsParseRestricted() { 351 // This is due to a programming error. 352 return nil, fmt.Errorf( 353 "cannot commit unexpected nested transaction: parse restricted", 354 ) 355 } 356 357 return txnState.mergeIntoParent() 358 } 359 360 func (txnState *transactionState) CommitParseRestrictedNestedTransaction( 361 location common.AddressLocation, 362 ) ( 363 *snapshot.ExecutionSnapshot, 364 error, 365 ) { 366 currentFrame := txnState.current() 367 if currentFrame.parseRestriction == nil || 368 *currentFrame.parseRestriction != location { 369 370 // This is due to a programming error. 371 return nil, fmt.Errorf( 372 "cannot commit unexpected nested transaction %v != %v", 373 currentFrame.parseRestriction, 374 location, 375 ) 376 } 377 378 return txnState.mergeIntoParent() 379 } 380 381 func (txnState *transactionState) AttachAndCommitNestedTransaction( 382 cachedSnapshot *snapshot.ExecutionSnapshot, 383 ) error { 384 return txnState.current().Merge(cachedSnapshot) 385 } 386 387 func (txnState *transactionState) RestartNestedTransaction( 388 id NestedTransactionId, 389 ) error { 390 391 // NOTE: We need to verify the id is valid before any merge operation or 392 // else we would accidently merge everything into the main transaction. 393 found := false 394 for _, frame := range txnState.nestedTransactions { 395 if frame.ExecutionState == id.state { 396 found = true 397 break 398 } 399 } 400 401 if !found { 402 return fmt.Errorf( 403 "cannot restart nested transaction: nested transaction not found") 404 } 405 406 for txnState.current().ExecutionState != id.state { 407 _, err := txnState.mergeIntoParent() 408 if err != nil { 409 return fmt.Errorf("cannot restart nested transaction: %w", err) 410 } 411 } 412 413 return txnState.current().DropChanges() 414 } 415 416 func (txnState *transactionState) Get( 417 id flow.RegisterID, 418 ) ( 419 flow.RegisterValue, 420 error, 421 ) { 422 return txnState.current().Get(id) 423 } 424 425 func (txnState *transactionState) Set( 426 id flow.RegisterID, 427 value flow.RegisterValue, 428 ) error { 429 return txnState.current().Set(id, value) 430 } 431 432 func (txnState *transactionState) MeterComputation( 433 kind common.ComputationKind, 434 intensity uint, 435 ) error { 436 return txnState.current().MeterComputation(kind, intensity) 437 } 438 439 func (txnState *transactionState) ComputationAvailable( 440 kind common.ComputationKind, 441 intensity uint, 442 ) bool { 443 return txnState.current().ComputationAvailable(kind, intensity) 444 } 445 446 func (txnState *transactionState) MeterMemory( 447 kind common.MemoryKind, 448 intensity uint, 449 ) error { 450 return txnState.current().MeterMemory(kind, intensity) 451 } 452 453 func (txnState *transactionState) ComputationIntensities() meter.MeteredComputationIntensities { 454 return txnState.current().ComputationIntensities() 455 } 456 457 func (txnState *transactionState) TotalComputationLimit() uint { 458 return txnState.current().TotalComputationLimit() 459 } 460 461 func (txnState *transactionState) TotalComputationUsed() uint64 { 462 return txnState.current().TotalComputationUsed() 463 } 464 465 func (txnState *transactionState) MemoryIntensities() meter.MeteredMemoryIntensities { 466 return txnState.current().MemoryIntensities() 467 } 468 469 func (txnState *transactionState) TotalMemoryEstimate() uint64 { 470 return txnState.current().TotalMemoryEstimate() 471 } 472 473 func (txnState *transactionState) InteractionUsed() uint64 { 474 return txnState.current().InteractionUsed() 475 } 476 477 func (txnState *transactionState) MeterEmittedEvent(byteSize uint64) error { 478 return txnState.current().MeterEmittedEvent(byteSize) 479 } 480 481 func (txnState *transactionState) TotalEmittedEventBytes() uint64 { 482 return txnState.current().TotalEmittedEventBytes() 483 } 484 485 func (txnState *transactionState) RunWithAllLimitsDisabled(f func()) { 486 txnState.current().RunWithAllLimitsDisabled(f) 487 }