github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/fvm/storage/state/transaction_state_test.go (about) 1 package state_test 2 3 import ( 4 "math" 5 "testing" 6 7 "github.com/onflow/cadence/runtime/common" 8 "github.com/stretchr/testify/require" 9 10 "github.com/onflow/flow-go/fvm/meter" 11 "github.com/onflow/flow-go/fvm/storage/state" 12 "github.com/onflow/flow-go/model/flow" 13 "github.com/onflow/flow-go/utils/unittest" 14 ) 15 16 func newTestTransactionState() state.NestedTransactionPreparer { 17 return state.NewTransactionState( 18 nil, 19 state.DefaultParameters(), 20 ) 21 } 22 23 func TestUnrestrictedNestedTransactionBasic(t *testing.T) { 24 txn := newTestTransactionState() 25 26 mainState := txn.MainTransactionId().StateForTestingOnly() 27 28 require.Equal(t, 0, txn.NumNestedTransactions()) 29 require.False(t, txn.IsParseRestricted()) 30 31 id1, err := txn.BeginNestedTransaction() 32 require.NoError(t, err) 33 34 require.Equal(t, 1, txn.NumNestedTransactions()) 35 require.False(t, txn.IsParseRestricted()) 36 37 require.True(t, txn.IsCurrent(id1)) 38 39 nestedState1 := id1.StateForTestingOnly() 40 41 id2, err := txn.BeginNestedTransaction() 42 require.NoError(t, err) 43 44 require.Equal(t, 2, txn.NumNestedTransactions()) 45 require.False(t, txn.IsParseRestricted()) 46 47 require.False(t, txn.IsCurrent(id1)) 48 require.True(t, txn.IsCurrent(id2)) 49 50 nestedState2 := id2.StateForTestingOnly() 51 52 // Ensure the values are written to the correctly nested state 53 54 key := flow.NewRegisterID(unittest.RandomAddressFixture(), "key") 55 val := createByteArray(2) 56 57 err = txn.Set(key, val) 58 require.NoError(t, err) 59 60 v, err := nestedState2.Get(key) 61 require.NoError(t, err) 62 require.Equal(t, val, v) 63 64 v, err = nestedState1.Get(key) 65 require.NoError(t, err) 66 require.Nil(t, v) 67 68 v, err = mainState.Get(key) 69 require.NoError(t, err) 70 require.Nil(t, v) 71 72 // Ensure nested transactions are merged correctly 73 74 _, err = txn.CommitNestedTransaction(id2) 75 require.NoError(t, err) 76 77 require.Equal(t, 1, txn.NumNestedTransactions()) 78 require.True(t, txn.IsCurrent(id1)) 79 80 v, err = nestedState1.Get(key) 81 require.NoError(t, err) 82 require.Equal(t, val, v) 83 84 v, err = mainState.Get(key) 85 require.NoError(t, err) 86 require.Nil(t, v) 87 88 _, err = txn.CommitNestedTransaction(id1) 89 require.NoError(t, err) 90 91 require.Equal(t, 0, txn.NumNestedTransactions()) 92 require.True(t, txn.IsCurrent(txn.MainTransactionId())) 93 94 v, err = mainState.Get(key) 95 require.NoError(t, err) 96 require.Equal(t, val, v) 97 } 98 99 func TestUnrestrictedNestedTransactionDifferentMeterParams(t *testing.T) { 100 txn := newTestTransactionState() 101 102 mainState := txn.MainTransactionId().StateForTestingOnly() 103 104 require.Equal(t, uint(math.MaxUint), mainState.TotalMemoryLimit()) 105 106 id1, err := txn.BeginNestedTransactionWithMeterParams( 107 meter.DefaultParameters().WithMemoryLimit(1)) 108 require.NoError(t, err) 109 110 nestedState1 := id1.StateForTestingOnly() 111 112 require.Equal(t, uint(1), nestedState1.TotalMemoryLimit()) 113 114 id2, err := txn.BeginNestedTransactionWithMeterParams( 115 meter.DefaultParameters().WithMemoryLimit(2)) 116 require.NoError(t, err) 117 118 nestedState2 := id2.StateForTestingOnly() 119 120 require.Equal(t, uint(2), nestedState2.TotalMemoryLimit()) 121 122 // inherits memory limit from parent 123 124 id3, err := txn.BeginNestedTransaction() 125 require.NoError(t, err) 126 127 nestedState3 := id3.StateForTestingOnly() 128 129 require.Equal(t, uint(2), nestedState3.TotalMemoryLimit()) 130 } 131 132 func TestParseRestrictedNestedTransactionBasic(t *testing.T) { 133 txn := newTestTransactionState() 134 135 mainId := txn.MainTransactionId() 136 mainState := mainId.StateForTestingOnly() 137 138 require.Equal(t, 0, txn.NumNestedTransactions()) 139 require.False(t, txn.IsParseRestricted()) 140 141 id1, err := txn.BeginNestedTransaction() 142 require.NoError(t, err) 143 144 require.Equal(t, 1, txn.NumNestedTransactions()) 145 require.False(t, txn.IsParseRestricted()) 146 147 nestedState := id1.StateForTestingOnly() 148 149 loc1 := common.AddressLocation{ 150 Address: common.MustBytesToAddress([]byte{1, 1, 1}), 151 Name: "loc1", 152 } 153 154 restrictedId1, err := txn.BeginParseRestrictedNestedTransaction(loc1) 155 require.NoError(t, err) 156 157 require.Equal(t, 2, txn.NumNestedTransactions()) 158 require.True(t, txn.IsParseRestricted()) 159 160 restrictedNestedState1 := restrictedId1.StateForTestingOnly() 161 162 loc2 := common.AddressLocation{ 163 Address: common.MustBytesToAddress([]byte{2, 2, 2}), 164 Name: "loc2", 165 } 166 167 restrictedId2, err := txn.BeginParseRestrictedNestedTransaction(loc2) 168 require.NoError(t, err) 169 170 require.Equal(t, 3, txn.NumNestedTransactions()) 171 require.True(t, txn.IsParseRestricted()) 172 173 restrictedNestedState2 := restrictedId2.StateForTestingOnly() 174 175 // Sanity check 176 177 key := flow.NewRegisterID(unittest.RandomAddressFixture(), "key") 178 179 v, err := restrictedNestedState2.Get(key) 180 require.NoError(t, err) 181 require.Nil(t, v) 182 183 v, err = restrictedNestedState1.Get(key) 184 require.NoError(t, err) 185 require.Nil(t, v) 186 187 v, err = nestedState.Get(key) 188 require.NoError(t, err) 189 require.Nil(t, v) 190 191 v, err = mainState.Get(key) 192 require.NoError(t, err) 193 require.Nil(t, v) 194 195 // Ensures attaching and committing cached nested transaction works 196 197 val := createByteArray(2) 198 199 cachedState := state.NewExecutionState( 200 nil, 201 state.DefaultParameters(), 202 ) 203 204 err = cachedState.Set(key, val) 205 require.NoError(t, err) 206 207 err = txn.AttachAndCommitNestedTransaction(cachedState.Finalize()) 208 require.NoError(t, err) 209 210 require.Equal(t, 3, txn.NumNestedTransactions()) 211 require.True(t, txn.IsCurrent(restrictedId2)) 212 213 v, err = restrictedNestedState2.Get(key) 214 require.NoError(t, err) 215 require.Equal(t, val, v) 216 217 v, err = restrictedNestedState1.Get(key) 218 require.NoError(t, err) 219 require.Nil(t, v) 220 221 v, err = nestedState.Get(key) 222 require.NoError(t, err) 223 require.Nil(t, v) 224 225 v, err = mainState.Get(key) 226 require.NoError(t, err) 227 require.Nil(t, v) 228 229 // Ensure nested transactions are merged correctly 230 231 snapshot, err := txn.CommitParseRestrictedNestedTransaction(loc2) 232 require.NoError(t, err) 233 require.Equal(t, restrictedNestedState2.Finalize(), snapshot) 234 235 require.Equal(t, 2, txn.NumNestedTransactions()) 236 require.True(t, txn.IsCurrent(restrictedId1)) 237 238 v, err = restrictedNestedState1.Get(key) 239 require.NoError(t, err) 240 require.Equal(t, val, v) 241 242 v, err = nestedState.Get(key) 243 require.NoError(t, err) 244 require.Nil(t, v) 245 246 v, err = mainState.Get(key) 247 require.NoError(t, err) 248 require.Nil(t, v) 249 250 snapshot, err = txn.CommitParseRestrictedNestedTransaction(loc1) 251 require.NoError(t, err) 252 require.Equal(t, restrictedNestedState1.Finalize(), snapshot) 253 254 require.Equal(t, 1, txn.NumNestedTransactions()) 255 require.True(t, txn.IsCurrent(id1)) 256 257 v, err = nestedState.Get(key) 258 require.NoError(t, err) 259 require.Equal(t, val, v) 260 261 v, err = mainState.Get(key) 262 require.NoError(t, err) 263 require.Nil(t, v) 264 265 _, err = txn.CommitNestedTransaction(id1) 266 require.NoError(t, err) 267 268 require.Equal(t, 0, txn.NumNestedTransactions()) 269 require.True(t, txn.IsCurrent(mainId)) 270 271 v, err = mainState.Get(key) 272 require.NoError(t, err) 273 require.Equal(t, val, v) 274 } 275 276 func TestRestartNestedTransaction(t *testing.T) { 277 txn := newTestTransactionState() 278 279 require.Equal(t, 0, txn.NumNestedTransactions()) 280 281 id, err := txn.BeginNestedTransaction() 282 require.NoError(t, err) 283 284 key := flow.NewRegisterID(unittest.RandomAddressFixture(), "key") 285 val := createByteArray(2) 286 287 for i := 0; i < 10; i++ { 288 _, err := txn.BeginNestedTransaction() 289 require.NoError(t, err) 290 291 err = txn.Set(key, val) 292 require.NoError(t, err) 293 } 294 295 loc := common.AddressLocation{ 296 Address: common.MustBytesToAddress([]byte{1, 1, 1}), 297 Name: "loc", 298 } 299 300 for i := 0; i < 5; i++ { 301 _, err := txn.BeginParseRestrictedNestedTransaction(loc) 302 require.NoError(t, err) 303 304 err = txn.Set(key, val) 305 require.NoError(t, err) 306 } 307 308 require.Equal(t, 16, txn.NumNestedTransactions()) 309 310 state := id.StateForTestingOnly() 311 require.Equal(t, uint64(0), state.InteractionUsed()) 312 313 // Restart will merge the meter stat, but not the register updates 314 315 err = txn.RestartNestedTransaction(id) 316 require.NoError(t, err) 317 318 require.Equal(t, 1, txn.NumNestedTransactions()) 319 require.True(t, txn.IsCurrent(id)) 320 321 require.Greater(t, state.InteractionUsed(), uint64(0)) 322 323 v, err := state.Get(key) 324 require.NoError(t, err) 325 require.Nil(t, v) 326 } 327 328 func TestRestartNestedTransactionWithInvalidId(t *testing.T) { 329 txn := newTestTransactionState() 330 331 require.Equal(t, 0, txn.NumNestedTransactions()) 332 333 id, err := txn.BeginNestedTransaction() 334 require.NoError(t, err) 335 336 key := flow.NewRegisterID(unittest.RandomAddressFixture(), "key") 337 val := createByteArray(2) 338 339 err = txn.Set(key, val) 340 require.NoError(t, err) 341 342 var otherId state.NestedTransactionId 343 for i := 0; i < 10; i++ { 344 otherId, err = txn.BeginNestedTransaction() 345 require.NoError(t, err) 346 347 _, err = txn.CommitNestedTransaction(otherId) 348 require.NoError(t, err) 349 } 350 351 require.True(t, txn.IsCurrent(id)) 352 353 err = txn.RestartNestedTransaction(otherId) 354 require.Error(t, err) 355 356 require.True(t, txn.IsCurrent(id)) 357 358 v, err := txn.Get(key) 359 require.NoError(t, err) 360 require.Equal(t, val, v) 361 } 362 363 func TestUnrestrictedCannotCommitParseRestrictedNestedTransaction(t *testing.T) { 364 txn := newTestTransactionState() 365 366 loc := common.AddressLocation{ 367 Address: common.MustBytesToAddress([]byte{1, 1, 1}), 368 Name: "loc", 369 } 370 371 id, err := txn.BeginNestedTransaction() 372 require.NoError(t, err) 373 374 require.Equal(t, 1, txn.NumNestedTransactions()) 375 require.False(t, txn.IsParseRestricted()) 376 377 _, err = txn.CommitParseRestrictedNestedTransaction(loc) 378 require.Error(t, err) 379 380 require.Equal(t, 1, txn.NumNestedTransactions()) 381 require.True(t, txn.IsCurrent(id)) 382 } 383 384 func TestUnrestrictedCannotCommitMainTransaction(t *testing.T) { 385 txn := newTestTransactionState() 386 387 id1, err := txn.BeginNestedTransaction() 388 require.NoError(t, err) 389 390 id2, err := txn.BeginNestedTransaction() 391 require.NoError(t, err) 392 393 require.Equal(t, 2, txn.NumNestedTransactions()) 394 395 _, err = txn.CommitNestedTransaction(id1) 396 require.Error(t, err) 397 398 require.Equal(t, 2, txn.NumNestedTransactions()) 399 require.True(t, txn.IsCurrent(id2)) 400 } 401 402 func TestUnrestrictedCannotCommitUnexpectedNested(t *testing.T) { 403 txn := newTestTransactionState() 404 405 mainId := txn.MainTransactionId() 406 407 require.Equal(t, 0, txn.NumNestedTransactions()) 408 409 _, err := txn.CommitNestedTransaction(mainId) 410 require.Error(t, err) 411 412 require.Equal(t, 0, txn.NumNestedTransactions()) 413 require.True(t, txn.IsCurrent(mainId)) 414 } 415 416 func TestParseRestrictedCannotBeginUnrestrictedNestedTransaction(t *testing.T) { 417 txn := newTestTransactionState() 418 419 loc := common.AddressLocation{ 420 Address: common.MustBytesToAddress([]byte{1, 1, 1}), 421 Name: "loc", 422 } 423 424 id1, err := txn.BeginParseRestrictedNestedTransaction(loc) 425 require.NoError(t, err) 426 427 require.Equal(t, 1, txn.NumNestedTransactions()) 428 429 id2, err := txn.BeginNestedTransaction() 430 require.Error(t, err) 431 432 require.Equal(t, 1, txn.NumNestedTransactions()) 433 require.True(t, txn.IsCurrent(id1)) 434 require.False(t, txn.IsCurrent(id2)) 435 } 436 437 func TestParseRestrictedCannotCommitUnrestricted(t *testing.T) { 438 txn := newTestTransactionState() 439 440 loc := common.AddressLocation{ 441 Address: common.MustBytesToAddress([]byte{1, 1, 1}), 442 Name: "loc", 443 } 444 445 id, err := txn.BeginParseRestrictedNestedTransaction(loc) 446 require.NoError(t, err) 447 448 require.Equal(t, 1, txn.NumNestedTransactions()) 449 450 _, err = txn.CommitNestedTransaction(id) 451 require.Error(t, err) 452 453 require.Equal(t, 1, txn.NumNestedTransactions()) 454 require.True(t, txn.IsCurrent(id)) 455 } 456 457 func TestParseRestrictedCannotCommitLocationMismatch(t *testing.T) { 458 txn := newTestTransactionState() 459 460 loc := common.AddressLocation{ 461 Address: common.MustBytesToAddress([]byte{1, 1, 1}), 462 Name: "loc", 463 } 464 465 id, err := txn.BeginParseRestrictedNestedTransaction(loc) 466 require.NoError(t, err) 467 468 require.Equal(t, 1, txn.NumNestedTransactions()) 469 470 other := common.AddressLocation{ 471 Address: common.MustBytesToAddress([]byte{1, 1, 1}), 472 Name: "other", 473 } 474 475 cacheableSnapshot, err := txn.CommitParseRestrictedNestedTransaction(other) 476 require.Error(t, err) 477 require.Nil(t, cacheableSnapshot) 478 479 require.Equal(t, 1, txn.NumNestedTransactions()) 480 require.True(t, txn.IsCurrent(id)) 481 } 482 483 func TestFinalizeMainTransactionFailWithUnexpectedNestedTransactions( 484 t *testing.T, 485 ) { 486 txn := newTestTransactionState() 487 488 _, err := txn.BeginNestedTransaction() 489 require.NoError(t, err) 490 491 executionSnapshot, err := txn.FinalizeMainTransaction() 492 require.Error(t, err) 493 require.Nil(t, executionSnapshot) 494 } 495 496 func TestFinalizeMainTransaction(t *testing.T) { 497 txn := newTestTransactionState() 498 499 id1, err := txn.BeginNestedTransaction() 500 require.NoError(t, err) 501 502 registerId := flow.NewRegisterID(unittest.RandomAddressFixture(), "bar") 503 504 value, err := txn.Get(registerId) 505 require.NoError(t, err) 506 require.Nil(t, value) 507 508 _, err = txn.CommitNestedTransaction(id1) 509 require.NoError(t, err) 510 511 value, err = txn.Get(registerId) 512 require.NoError(t, err) 513 require.Nil(t, value) 514 515 executionSnapshot, err := txn.FinalizeMainTransaction() 516 require.NoError(t, err) 517 518 require.Equal( 519 t, 520 executionSnapshot.ReadSet, 521 map[flow.RegisterID]struct{}{ 522 registerId: struct{}{}, 523 }) 524 525 // Sanity check state is no longer accessible after FinalizeMainTransaction. 526 _, err = txn.Get(registerId) 527 require.ErrorContains(t, err, "cannot Get on a finalized state") 528 } 529 530 func TestInterimReadSet(t *testing.T) { 531 txn := newTestTransactionState() 532 533 // Setup test with a bunch of outstanding nested transaction. 534 535 readOwner := unittest.RandomAddressFixture() 536 writeOwner := unittest.RandomAddressFixture() 537 538 readRegisterId1 := flow.NewRegisterID(readOwner, "1") 539 readRegisterId2 := flow.NewRegisterID(readOwner, "2") 540 readRegisterId3 := flow.NewRegisterID(readOwner, "3") 541 readRegisterId4 := flow.NewRegisterID(readOwner, "4") 542 543 writeRegisterId1 := flow.NewRegisterID(writeOwner, "1") 544 writeValue1 := flow.RegisterValue([]byte("value1")) 545 546 writeRegisterId2 := flow.NewRegisterID(writeOwner, "2") 547 writeValue2 := flow.RegisterValue([]byte("value2")) 548 549 writeRegisterId3 := flow.NewRegisterID(writeOwner, "3") 550 writeValue3 := flow.RegisterValue([]byte("value3")) 551 552 err := txn.Set(writeRegisterId1, writeValue1) 553 require.NoError(t, err) 554 555 _, err = txn.Get(readRegisterId1) 556 require.NoError(t, err) 557 558 _, err = txn.Get(readRegisterId2) 559 require.NoError(t, err) 560 561 value, err := txn.Get(writeRegisterId1) 562 require.NoError(t, err) 563 require.Equal(t, writeValue1, value) 564 565 _, err = txn.BeginNestedTransaction() 566 require.NoError(t, err) 567 568 err = txn.Set(readRegisterId2, []byte("blah")) 569 require.NoError(t, err) 570 571 _, err = txn.Get(readRegisterId3) 572 require.NoError(t, err) 573 574 value, err = txn.Get(writeRegisterId1) 575 require.NoError(t, err) 576 require.Equal(t, writeValue1, value) 577 578 err = txn.Set(writeRegisterId2, writeValue2) 579 require.NoError(t, err) 580 581 _, err = txn.BeginNestedTransaction() 582 require.NoError(t, err) 583 584 err = txn.Set(writeRegisterId3, writeValue3) 585 require.NoError(t, err) 586 587 value, err = txn.Get(writeRegisterId1) 588 require.NoError(t, err) 589 require.Equal(t, writeValue1, value) 590 591 value, err = txn.Get(writeRegisterId2) 592 require.NoError(t, err) 593 require.Equal(t, writeValue2, value) 594 595 value, err = txn.Get(writeRegisterId3) 596 require.NoError(t, err) 597 require.Equal(t, writeValue3, value) 598 599 _, err = txn.Get(readRegisterId4) 600 require.NoError(t, err) 601 602 // Actual test 603 604 require.Equal( 605 t, 606 map[flow.RegisterID]struct{}{ 607 readRegisterId1: struct{}{}, 608 readRegisterId2: struct{}{}, 609 readRegisterId3: struct{}{}, 610 readRegisterId4: struct{}{}, 611 }, 612 txn.InterimReadSet()) 613 }