github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/fvm/storage/primary/block_data_test.go (about) 1 package primary 2 3 import ( 4 "fmt" 5 "testing" 6 7 "github.com/stretchr/testify/require" 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 "github.com/onflow/flow-go/model/flow" 14 ) 15 16 func TestBlockDataWithTransactionOffset(t *testing.T) { 17 key := flow.RegisterID{ 18 Owner: "", 19 Key: "key", 20 } 21 expectedValue := flow.RegisterValue([]byte("value")) 22 23 snapshotTime := logical.Time(18) 24 25 block := NewBlockData( 26 snapshot.MapStorageSnapshot{ 27 key: expectedValue, 28 }, 29 snapshotTime) 30 31 snapshot := block.LatestSnapshot() 32 require.Equal(t, snapshotTime, snapshot.SnapshotTime()) 33 34 value, err := snapshot.Get(key) 35 require.NoError(t, err) 36 require.Equal(t, expectedValue, value) 37 } 38 39 func TestBlockDataNormalTransactionInvalidExecutionTime(t *testing.T) { 40 snapshotTime := logical.Time(5) 41 block := NewBlockData(nil, snapshotTime) 42 43 txn, err := block.NewTransactionData(-1, state.DefaultParameters()) 44 require.ErrorContains(t, err, "execution time out of bound") 45 require.Nil(t, txn) 46 47 txn, err = block.NewTransactionData( 48 logical.EndOfBlockExecutionTime, 49 state.DefaultParameters()) 50 require.ErrorContains(t, err, "execution time out of bound") 51 require.Nil(t, txn) 52 53 txn, err = block.NewTransactionData( 54 snapshotTime-1, 55 state.DefaultParameters()) 56 require.ErrorContains(t, err, "snapshot > execution: 5 > 4") 57 require.Nil(t, txn) 58 } 59 60 func testBlockDataValidate( 61 t *testing.T, 62 shouldFinalize bool, 63 ) { 64 baseSnapshotTime := logical.Time(11) 65 block := NewBlockData(nil, baseSnapshotTime) 66 67 // Commit a key before the actual test txn (which read the same key). 68 69 testSetupTxn, err := block.NewTransactionData( 70 baseSnapshotTime, 71 state.DefaultParameters()) 72 require.NoError(t, err) 73 74 registerId1 := flow.RegisterID{ 75 Owner: "", 76 Key: "key1", 77 } 78 expectedValue1 := flow.RegisterValue([]byte("value1")) 79 80 err = testSetupTxn.Set(registerId1, expectedValue1) 81 require.NoError(t, err) 82 83 err = testSetupTxn.Finalize() 84 require.NoError(t, err) 85 86 _, err = testSetupTxn.Commit() 87 require.NoError(t, err) 88 89 require.Equal( 90 t, 91 baseSnapshotTime+1, 92 block.LatestSnapshot().SnapshotTime()) 93 94 value, err := block.LatestSnapshot().Get(registerId1) 95 require.NoError(t, err) 96 require.Equal(t, expectedValue1, value) 97 98 // Start the test transaction at an "older" snapshot to ensure valdiate 99 // works as expected. 100 101 testTxn, err := block.NewTransactionData( 102 baseSnapshotTime+3, 103 state.DefaultParameters()) 104 require.NoError(t, err) 105 106 // Commit a bunch of unrelated transactions. 107 108 testSetupTxn, err = block.NewTransactionData( 109 baseSnapshotTime+1, 110 state.DefaultParameters()) 111 require.NoError(t, err) 112 113 registerId2 := flow.RegisterID{ 114 Owner: "", 115 Key: "key2", 116 } 117 expectedValue2 := flow.RegisterValue([]byte("value2")) 118 119 err = testSetupTxn.Set(registerId2, expectedValue2) 120 require.NoError(t, err) 121 122 err = testSetupTxn.Finalize() 123 require.NoError(t, err) 124 125 _, err = testSetupTxn.Commit() 126 require.NoError(t, err) 127 128 testSetupTxn, err = block.NewTransactionData( 129 baseSnapshotTime+2, 130 state.DefaultParameters()) 131 require.NoError(t, err) 132 133 registerId3 := flow.RegisterID{ 134 Owner: "", 135 Key: "key3", 136 } 137 expectedValue3 := flow.RegisterValue([]byte("value3")) 138 139 err = testSetupTxn.Set(registerId3, expectedValue3) 140 require.NoError(t, err) 141 142 err = testSetupTxn.Finalize() 143 require.NoError(t, err) 144 145 _, err = testSetupTxn.Commit() 146 require.NoError(t, err) 147 148 // Actual test 149 150 _, err = testTxn.Get(registerId1) 151 require.NoError(t, err) 152 153 if shouldFinalize { 154 err = testTxn.Finalize() 155 require.NoError(t, err) 156 157 require.NotNil(t, testTxn.finalizedExecutionSnapshot) 158 } else { 159 require.Nil(t, testTxn.finalizedExecutionSnapshot) 160 } 161 162 // Check the original snapshot tree before calling validate. 163 require.Equal(t, baseSnapshotTime+1, testTxn.SnapshotTime()) 164 165 value, err = testTxn.snapshot.Get(registerId1) 166 require.NoError(t, err) 167 require.Equal(t, expectedValue1, value) 168 169 value, err = testTxn.snapshot.Get(registerId2) 170 require.NoError(t, err) 171 require.Nil(t, value) 172 173 value, err = testTxn.snapshot.Get(registerId3) 174 require.NoError(t, err) 175 require.Nil(t, value) 176 177 // Validate should not detect any conflict and should rebase the snapshot. 178 err = testTxn.Validate() 179 require.NoError(t, err) 180 181 // Ensure validate rebase to a new snapshot tree. 182 require.Equal(t, baseSnapshotTime+3, testTxn.SnapshotTime()) 183 184 value, err = testTxn.snapshot.Get(registerId1) 185 require.NoError(t, err) 186 require.Equal(t, expectedValue1, value) 187 188 value, err = testTxn.snapshot.Get(registerId2) 189 require.NoError(t, err) 190 require.Equal(t, expectedValue2, value) 191 192 value, err = testTxn.snapshot.Get(registerId3) 193 require.NoError(t, err) 194 require.Equal(t, expectedValue3, value) 195 196 // Note: we can't make additional Get calls on a finalized transaction. 197 if shouldFinalize { 198 _, err = testTxn.Get(registerId1) 199 require.ErrorContains(t, err, "cannot Get on a finalized state") 200 201 _, err = testTxn.Get(registerId2) 202 require.ErrorContains(t, err, "cannot Get on a finalized state") 203 204 _, err = testTxn.Get(registerId3) 205 require.ErrorContains(t, err, "cannot Get on a finalized state") 206 } else { 207 value, err = testTxn.Get(registerId1) 208 require.NoError(t, err) 209 require.Equal(t, expectedValue1, value) 210 211 value, err = testTxn.Get(registerId2) 212 require.NoError(t, err) 213 require.Equal(t, expectedValue2, value) 214 215 value, err = testTxn.Get(registerId3) 216 require.NoError(t, err) 217 require.Equal(t, expectedValue3, value) 218 } 219 } 220 221 func TestBlockDataValidateInterim(t *testing.T) { 222 testBlockDataValidate(t, false) 223 } 224 225 func TestBlockDataValidateFinalized(t *testing.T) { 226 testBlockDataValidate(t, true) 227 } 228 229 func testBlockDataValidateRejectConflict( 230 t *testing.T, 231 shouldFinalize bool, 232 conflictTxn int, // [1, 2, 3] 233 ) { 234 baseSnapshotTime := logical.Time(32) 235 block := NewBlockData(nil, baseSnapshotTime) 236 237 // Commit a bunch of unrelated updates 238 239 for ; baseSnapshotTime < 42; baseSnapshotTime++ { 240 testSetupTxn, err := block.NewTransactionData( 241 baseSnapshotTime, 242 state.DefaultParameters()) 243 require.NoError(t, err) 244 245 err = testSetupTxn.Set( 246 flow.RegisterID{ 247 Owner: "", 248 Key: fmt.Sprintf("other key - %d", baseSnapshotTime), 249 }, 250 []byte("blah")) 251 require.NoError(t, err) 252 253 err = testSetupTxn.Finalize() 254 require.NoError(t, err) 255 256 _, err = testSetupTxn.Commit() 257 require.NoError(t, err) 258 } 259 260 // Start the test transaction at an "older" snapshot to ensure valdiate 261 // works as expected. 262 263 testTxnTime := baseSnapshotTime + 3 264 testTxn, err := block.NewTransactionData( 265 testTxnTime, 266 state.DefaultParameters()) 267 require.NoError(t, err) 268 269 // Commit one key per test setup transaction. One of these keys will 270 // conflicts with the test txn. 271 272 txn1Time := baseSnapshotTime 273 testSetupTxn, err := block.NewTransactionData( 274 txn1Time, 275 state.DefaultParameters()) 276 require.NoError(t, err) 277 278 registerId1 := flow.RegisterID{ 279 Owner: "", 280 Key: "key1", 281 } 282 283 err = testSetupTxn.Set(registerId1, []byte("value1")) 284 require.NoError(t, err) 285 286 err = testSetupTxn.Finalize() 287 require.NoError(t, err) 288 289 _, err = testSetupTxn.Commit() 290 require.NoError(t, err) 291 292 txn2Time := baseSnapshotTime + 1 293 testSetupTxn, err = block.NewTransactionData( 294 txn2Time, 295 state.DefaultParameters()) 296 require.NoError(t, err) 297 298 registerId2 := flow.RegisterID{ 299 Owner: "", 300 Key: "key2", 301 } 302 303 err = testSetupTxn.Set(registerId2, []byte("value2")) 304 require.NoError(t, err) 305 306 err = testSetupTxn.Finalize() 307 require.NoError(t, err) 308 309 _, err = testSetupTxn.Commit() 310 require.NoError(t, err) 311 312 txn3Time := baseSnapshotTime + 2 313 testSetupTxn, err = block.NewTransactionData( 314 txn3Time, 315 state.DefaultParameters()) 316 require.NoError(t, err) 317 318 registerId3 := flow.RegisterID{ 319 Owner: "", 320 Key: "key3", 321 } 322 323 err = testSetupTxn.Set(registerId3, []byte("value3")) 324 require.NoError(t, err) 325 326 err = testSetupTxn.Finalize() 327 require.NoError(t, err) 328 329 _, err = testSetupTxn.Commit() 330 require.NoError(t, err) 331 332 // Actual test 333 334 var conflictTxnTime logical.Time 335 var conflictRegisterId flow.RegisterID 336 switch conflictTxn { 337 case 1: 338 conflictTxnTime = txn1Time 339 conflictRegisterId = registerId1 340 case 2: 341 conflictTxnTime = txn2Time 342 conflictRegisterId = registerId2 343 case 3: 344 conflictTxnTime = txn3Time 345 conflictRegisterId = registerId3 346 } 347 348 value, err := testTxn.Get(conflictRegisterId) 349 require.NoError(t, err) 350 require.Nil(t, value) 351 352 if shouldFinalize { 353 err = testTxn.Finalize() 354 require.NoError(t, err) 355 356 require.NotNil(t, testTxn.finalizedExecutionSnapshot) 357 } else { 358 require.Nil(t, testTxn.finalizedExecutionSnapshot) 359 } 360 361 // Check the original snapshot tree before calling validate. 362 require.Equal(t, baseSnapshotTime, testTxn.SnapshotTime()) 363 364 err = testTxn.Validate() 365 require.ErrorContains( 366 t, 367 err, 368 fmt.Sprintf( 369 conflictErrorTemplate, 370 conflictTxnTime, 371 testTxnTime, 372 baseSnapshotTime, 373 conflictRegisterId)) 374 require.True(t, errors.IsRetryableConflictError(err)) 375 376 // Validate should not rebase the snapshot tree on error 377 require.Equal(t, baseSnapshotTime, testTxn.SnapshotTime()) 378 } 379 380 func TestBlockDataValidateInterimRejectConflict(t *testing.T) { 381 testBlockDataValidateRejectConflict(t, false, 1) 382 testBlockDataValidateRejectConflict(t, false, 2) 383 testBlockDataValidateRejectConflict(t, false, 3) 384 } 385 386 func TestBlockDataValidateFinalizedRejectConflict(t *testing.T) { 387 testBlockDataValidateRejectConflict(t, true, 1) 388 testBlockDataValidateRejectConflict(t, true, 2) 389 testBlockDataValidateRejectConflict(t, true, 3) 390 } 391 392 func TestBlockDataCommit(t *testing.T) { 393 block := NewBlockData(nil, 0) 394 395 // Start test txn at an "older" snapshot. 396 txn, err := block.NewTransactionData(3, state.DefaultParameters()) 397 require.NoError(t, err) 398 399 // Commit a bunch of unrelated updates 400 401 for i := logical.Time(0); i < 3; i++ { 402 testSetupTxn, err := block.NewTransactionData( 403 i, 404 state.DefaultParameters()) 405 require.NoError(t, err) 406 407 err = testSetupTxn.Set( 408 flow.RegisterID{ 409 Owner: "", 410 Key: fmt.Sprintf("other key - %d", i), 411 }, 412 []byte("blah")) 413 require.NoError(t, err) 414 415 err = testSetupTxn.Finalize() 416 require.NoError(t, err) 417 418 _, err = testSetupTxn.Commit() 419 require.NoError(t, err) 420 } 421 422 // "resume" test txn 423 424 writeRegisterId := flow.RegisterID{ 425 Owner: "", 426 Key: "write", 427 } 428 expectedValue := flow.RegisterValue([]byte("value")) 429 430 err = txn.Set(writeRegisterId, expectedValue) 431 require.NoError(t, err) 432 433 readRegisterId := flow.RegisterID{ 434 Owner: "", 435 Key: "read", 436 } 437 value, err := txn.Get(readRegisterId) 438 require.NoError(t, err) 439 require.Nil(t, value) 440 441 err = txn.Finalize() 442 require.NoError(t, err) 443 444 // Actual test. Ensure the transaction is committed. 445 446 require.Equal(t, logical.Time(0), txn.SnapshotTime()) 447 require.Equal(t, logical.Time(3), block.LatestSnapshot().SnapshotTime()) 448 449 executionSnapshot, err := txn.Commit() 450 require.NoError(t, err) 451 require.NotNil(t, executionSnapshot) 452 require.Equal( 453 t, 454 map[flow.RegisterID]struct{}{ 455 readRegisterId: struct{}{}, 456 }, 457 executionSnapshot.ReadSet) 458 require.Equal( 459 t, 460 map[flow.RegisterID]flow.RegisterValue{ 461 writeRegisterId: expectedValue, 462 }, 463 executionSnapshot.WriteSet) 464 465 require.Equal(t, logical.Time(4), block.LatestSnapshot().SnapshotTime()) 466 467 value, err = block.LatestSnapshot().Get(writeRegisterId) 468 require.NoError(t, err) 469 require.Equal(t, expectedValue, value) 470 } 471 472 func TestBlockDataCommitSnapshotReadDontAdvanceTime(t *testing.T) { 473 baseRegisterId := flow.RegisterID{ 474 Owner: "", 475 Key: "base", 476 } 477 baseValue := flow.RegisterValue([]byte("original")) 478 479 baseSnapshotTime := logical.Time(16) 480 481 block := NewBlockData( 482 snapshot.MapStorageSnapshot{ 483 baseRegisterId: baseValue, 484 }, 485 baseSnapshotTime) 486 487 txn := block.NewSnapshotReadTransactionData(state.DefaultParameters()) 488 489 readRegisterId := flow.RegisterID{ 490 Owner: "", 491 Key: "read", 492 } 493 value, err := txn.Get(readRegisterId) 494 require.NoError(t, err) 495 require.Nil(t, value) 496 497 err = txn.Set(baseRegisterId, []byte("bad")) 498 require.NoError(t, err) 499 500 err = txn.Finalize() 501 require.NoError(t, err) 502 503 require.Equal(t, baseSnapshotTime, block.LatestSnapshot().SnapshotTime()) 504 505 executionSnapshot, err := txn.Commit() 506 require.NoError(t, err) 507 508 require.NotNil(t, executionSnapshot) 509 510 require.Equal( 511 t, 512 map[flow.RegisterID]struct{}{ 513 readRegisterId: struct{}{}, 514 }, 515 executionSnapshot.ReadSet) 516 517 // Ensure we have dropped the write set internally. 518 require.Nil(t, executionSnapshot.WriteSet) 519 520 // Ensure block snapshot is not updated. 521 require.Equal(t, baseSnapshotTime, block.LatestSnapshot().SnapshotTime()) 522 523 value, err = block.LatestSnapshot().Get(baseRegisterId) 524 require.NoError(t, err) 525 require.Equal(t, baseValue, value) 526 } 527 528 func TestBlockDataCommitRejectNotFinalized(t *testing.T) { 529 block := NewBlockData(nil, 0) 530 531 txn, err := block.NewTransactionData(0, state.DefaultParameters()) 532 require.NoError(t, err) 533 534 executionSnapshot, err := txn.Commit() 535 require.ErrorContains(t, err, "transaction not finalized") 536 require.False(t, errors.IsRetryableConflictError(err)) 537 require.Nil(t, executionSnapshot) 538 } 539 540 func TestBlockDataCommitRejectConflict(t *testing.T) { 541 block := NewBlockData(nil, 0) 542 543 registerId := flow.RegisterID{ 544 Owner: "", 545 Key: "key1", 546 } 547 548 // Start test txn at an "older" snapshot. 549 testTxn, err := block.NewTransactionData(1, state.DefaultParameters()) 550 require.NoError(t, err) 551 552 // Commit a conflicting key 553 testSetupTxn, err := block.NewTransactionData(0, state.DefaultParameters()) 554 require.NoError(t, err) 555 556 err = testSetupTxn.Set(registerId, []byte("value")) 557 require.NoError(t, err) 558 559 err = testSetupTxn.Finalize() 560 require.NoError(t, err) 561 562 executionSnapshot, err := testSetupTxn.Commit() 563 require.NoError(t, err) 564 require.NotNil(t, executionSnapshot) 565 566 // Actual test 567 568 require.Equal(t, logical.Time(1), block.LatestSnapshot().SnapshotTime()) 569 570 value, err := testTxn.Get(registerId) 571 require.NoError(t, err) 572 require.Nil(t, value) 573 574 err = testTxn.Finalize() 575 require.NoError(t, err) 576 577 executionSnapshot, err = testTxn.Commit() 578 require.Error(t, err) 579 require.True(t, errors.IsRetryableConflictError(err)) 580 require.Nil(t, executionSnapshot) 581 582 // testTxn is not committed to block. 583 require.Equal(t, logical.Time(1), block.LatestSnapshot().SnapshotTime()) 584 } 585 586 func TestBlockDataCommitRejectCommitGap(t *testing.T) { 587 block := NewBlockData(nil, 1) 588 589 for i := logical.Time(2); i < 5; i++ { 590 txn, err := block.NewTransactionData(i, state.DefaultParameters()) 591 require.NoError(t, err) 592 593 err = txn.Finalize() 594 require.NoError(t, err) 595 596 executionSnapshot, err := txn.Commit() 597 require.ErrorContains( 598 t, 599 err, 600 fmt.Sprintf("missing commit range [1, %d)", i)) 601 require.False(t, errors.IsRetryableConflictError(err)) 602 require.Nil(t, executionSnapshot) 603 604 // testTxn is not committed to block. 605 require.Equal(t, logical.Time(1), block.LatestSnapshot().SnapshotTime()) 606 } 607 } 608 609 func TestBlockDataCommitRejectNonIncreasingExecutionTime1(t *testing.T) { 610 block := NewBlockData(nil, 0) 611 612 testTxn, err := block.NewTransactionData(5, state.DefaultParameters()) 613 require.NoError(t, err) 614 615 err = testTxn.Finalize() 616 require.NoError(t, err) 617 618 // Commit a bunch of unrelated transactions. 619 for i := logical.Time(0); i < 10; i++ { 620 txn, err := block.NewTransactionData(i, state.DefaultParameters()) 621 require.NoError(t, err) 622 623 err = txn.Finalize() 624 require.NoError(t, err) 625 626 _, err = txn.Commit() 627 require.NoError(t, err) 628 } 629 630 // sanity check before testing commit. 631 require.Equal(t, logical.Time(10), block.LatestSnapshot().SnapshotTime()) 632 633 // "re-commit" an already committed transaction 634 executionSnapshot, err := testTxn.Commit() 635 require.ErrorContains(t, err, "non-increasing time (9 >= 5)") 636 require.False(t, errors.IsRetryableConflictError(err)) 637 require.Nil(t, executionSnapshot) 638 639 // testTxn is not committed to block. 640 require.Equal(t, logical.Time(10), block.LatestSnapshot().SnapshotTime()) 641 } 642 643 func TestBlockDataCommitRejectNonIncreasingExecutionTime2(t *testing.T) { 644 block := NewBlockData(nil, 13) 645 646 testTxn, err := block.NewTransactionData(13, state.DefaultParameters()) 647 require.NoError(t, err) 648 649 err = testTxn.Finalize() 650 require.NoError(t, err) 651 652 executionSnapshot, err := testTxn.Commit() 653 require.NoError(t, err) 654 require.NotNil(t, executionSnapshot) 655 656 // "re-commit" an already committed transaction 657 executionSnapshot, err = testTxn.Commit() 658 require.ErrorContains(t, err, "non-increasing time (13 >= 13)") 659 require.False(t, errors.IsRetryableConflictError(err)) 660 require.Nil(t, executionSnapshot) 661 }