github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/fvm/storage/derived/table_test.go (about) 1 package derived 2 3 import ( 4 "fmt" 5 "testing" 6 7 "github.com/stretchr/testify/assert" 8 "github.com/stretchr/testify/require" 9 10 "github.com/onflow/flow-go/fvm/storage/errors" 11 "github.com/onflow/flow-go/fvm/storage/logical" 12 "github.com/onflow/flow-go/fvm/storage/snapshot" 13 "github.com/onflow/flow-go/fvm/storage/state" 14 "github.com/onflow/flow-go/model/flow" 15 "github.com/onflow/flow-go/utils/unittest" 16 ) 17 18 func newEmptyTestBlock() *DerivedDataTable[string, *string] { 19 return NewEmptyTable[string, *string](0) 20 } 21 22 func TestDerivedDataTableWithTransactionOffset(t *testing.T) { 23 block := NewEmptyTable[string, *string](18) 24 25 require.Equal( 26 t, 27 logical.Time(17), 28 block.LatestCommitExecutionTimeForTestingOnly()) 29 } 30 31 func TestDerivedDataTableNormalTransactionInvalidExecutionTimeBound( 32 t *testing.T, 33 ) { 34 block := newEmptyTestBlock() 35 36 _, err := block.NewTableTransaction(-1, -1) 37 require.ErrorContains(t, err, "execution time out of bound") 38 39 _, err = block.NewTableTransaction(0, 0) 40 require.NoError(t, err) 41 42 _, err = block.NewTableTransaction(0, logical.EndOfBlockExecutionTime) 43 require.ErrorContains(t, err, "execution time out of bound") 44 45 _, err = block.NewTableTransaction(0, logical.EndOfBlockExecutionTime-1) 46 require.NoError(t, err) 47 } 48 49 func TestDerivedDataTableNormalTransactionInvalidSnapshotTime(t *testing.T) { 50 block := newEmptyTestBlock() 51 52 _, err := block.NewTableTransaction(10, 0) 53 require.ErrorContains(t, err, "snapshot > execution") 54 55 _, err = block.NewTableTransaction(10, 10) 56 require.NoError(t, err) 57 58 _, err = block.NewTableTransaction(999, 998) 59 require.ErrorContains(t, err, "snapshot > execution") 60 61 _, err = block.NewTableTransaction(999, 999) 62 require.NoError(t, err) 63 } 64 65 func TestDerivedDataTableToValidateTime(t *testing.T) { 66 block := NewEmptyTable[string, *string](8) 67 require.Equal( 68 t, 69 logical.Time(7), 70 block.LatestCommitExecutionTimeForTestingOnly()) 71 72 testTxnSnapshotTime := logical.Time(5) 73 74 testTxn, err := block.NewTableTransaction(testTxnSnapshotTime, 20) 75 require.NoError(t, err) 76 require.Equal( 77 t, 78 testTxnSnapshotTime, 79 testTxn.ToValidateTimeForTestingOnly()) 80 81 testTxn.SetForTestingOnly("key1", nil, nil) 82 require.Equal( 83 t, 84 testTxnSnapshotTime, 85 testTxn.ToValidateTimeForTestingOnly()) 86 87 err = testTxn.Validate() 88 require.NoError(t, err) 89 require.Equal( 90 t, 91 logical.Time(8), 92 testTxn.ToValidateTimeForTestingOnly()) 93 94 testSetupTxn, err := block.NewTableTransaction(8, 8) 95 require.NoError(t, err) 96 97 invalidator1 := &testInvalidator{invalidateName: "blah"} 98 99 testSetupTxn.AddInvalidator(invalidator1) 100 err = testSetupTxn.Commit() 101 require.NoError(t, err) 102 103 err = testTxn.Validate() 104 require.NoError(t, err) 105 require.Equal( 106 t, 107 logical.Time(9), 108 testTxn.ToValidateTimeForTestingOnly()) 109 110 require.Equal(t, 1, invalidator1.callCount) 111 112 // Multiple transactions committed between validate calls 113 114 testSetupTxn, err = block.NewTableTransaction(6, 9) 115 require.NoError(t, err) 116 117 invalidator2 := &testInvalidator{invalidateName: "blah"} 118 119 testSetupTxn.AddInvalidator(invalidator2) 120 err = testSetupTxn.Commit() 121 require.NoError(t, err) 122 123 testSetupTxn, err = block.NewTableTransaction(8, 10) 124 require.NoError(t, err) 125 126 invalidator3 := &testInvalidator{invalidateName: "blah"} 127 128 testSetupTxn.AddInvalidator(invalidator3) 129 err = testSetupTxn.Commit() 130 require.NoError(t, err) 131 132 err = testTxn.Validate() 133 require.NoError(t, err) 134 require.Equal( 135 t, 136 logical.Time(11), 137 testTxn.ToValidateTimeForTestingOnly()) 138 139 require.Equal(t, 1, invalidator1.callCount) 140 require.Equal(t, 1, invalidator2.callCount) 141 require.Equal(t, 1, invalidator3.callCount) 142 143 // No validate time advancement 144 145 err = testTxn.Validate() 146 require.NoError(t, err) 147 require.Equal( 148 t, 149 logical.Time(11), 150 testTxn.ToValidateTimeForTestingOnly()) 151 152 require.Equal(t, 1, invalidator1.callCount) 153 require.Equal(t, 1, invalidator2.callCount) 154 require.Equal(t, 1, invalidator3.callCount) 155 156 // Setting a value derived from snapshot time will reset the validate time 157 158 testTxn.SetForTestingOnly("key2", nil, nil) 159 require.Equal( 160 t, 161 testTxnSnapshotTime, 162 testTxn.ToValidateTimeForTestingOnly()) 163 164 err = testTxn.Validate() 165 require.NoError(t, err) 166 require.Equal( 167 t, 168 logical.Time(11), 169 testTxn.ToValidateTimeForTestingOnly()) 170 171 // callCount = 3 because key1 is validated twice, key2 validated once. 172 require.Equal(t, 3, invalidator1.callCount) 173 require.Equal(t, 3, invalidator2.callCount) 174 require.Equal(t, 3, invalidator3.callCount) 175 176 // validate error does not advance validated time 177 178 testSetupTxn, err = block.NewTableTransaction(11, 11) 179 require.NoError(t, err) 180 181 invalidator4 := &testInvalidator{invalidateName: "blah"} 182 183 testSetupTxn.AddInvalidator(invalidator4) 184 err = testSetupTxn.Commit() 185 require.NoError(t, err) 186 187 testSetupTxn, err = block.NewTableTransaction(12, 12) 188 require.NoError(t, err) 189 190 invalidator5 := &testInvalidator{invalidateAll: true} 191 192 testSetupTxn.AddInvalidator(invalidator5) 193 err = testSetupTxn.Commit() 194 require.NoError(t, err) 195 196 for i := 1; i < 10; i++ { 197 err = testTxn.Validate() 198 require.Error(t, err) 199 require.Equal( 200 t, 201 logical.Time(11), 202 testTxn.ToValidateTimeForTestingOnly()) 203 204 require.Equal(t, 3, invalidator1.callCount) 205 require.Equal(t, 3, invalidator2.callCount) 206 require.Equal(t, 3, invalidator3.callCount) 207 require.Equal(t, i, invalidator4.callCount) 208 require.Equal(t, i, invalidator5.callCount) 209 } 210 } 211 212 func TestDerivedDataTableOutOfOrderValidate(t *testing.T) { 213 block := newEmptyTestBlock() 214 215 testTxn1, err := block.NewTableTransaction(0, 0) 216 require.NoError(t, err) 217 218 testTxn2, err := block.NewTableTransaction(1, 1) 219 require.NoError(t, err) 220 221 testTxn3, err := block.NewTableTransaction(2, 2) 222 require.NoError(t, err) 223 224 testTxn4, err := block.NewTableTransaction(3, 3) 225 require.NoError(t, err) 226 227 // Validate can be called in any order as long as the transactions 228 // are committed in the correct order. 229 230 validateErr := testTxn4.Validate() 231 require.NoError(t, validateErr) 232 233 validateErr = testTxn2.Validate() 234 require.NoError(t, validateErr) 235 236 validateErr = testTxn3.Validate() 237 require.NoError(t, validateErr) 238 239 validateErr = testTxn1.Validate() 240 require.NoError(t, validateErr) 241 242 err = testTxn1.Commit() 243 require.NoError(t, err) 244 245 validateErr = testTxn2.Validate() 246 require.NoError(t, validateErr) 247 248 validateErr = testTxn3.Validate() 249 require.NoError(t, validateErr) 250 251 validateErr = testTxn4.Validate() 252 require.NoError(t, validateErr) 253 254 validateErr = testTxn2.Validate() 255 require.NoError(t, validateErr) 256 } 257 258 func TestDerivedDataTableValidateRejectOutOfOrderCommit(t *testing.T) { 259 block := newEmptyTestBlock() 260 261 testTxn, err := block.NewTableTransaction(0, 0) 262 require.NoError(t, err) 263 264 testSetupTxn, err := block.NewTableTransaction(0, 1) 265 require.NoError(t, err) 266 267 validateErr := testTxn.Validate() 268 require.NoError(t, validateErr) 269 270 err = testSetupTxn.Commit() 271 require.NoError(t, err) 272 273 validateErr = testTxn.Validate() 274 require.ErrorContains(t, validateErr, "non-increasing time") 275 require.False(t, errors.IsRetryableConflictError(validateErr)) 276 } 277 278 func TestDerivedDataTableValidateRejectNonIncreasingExecutionTime(t *testing.T) { 279 block := newEmptyTestBlock() 280 281 testSetupTxn, err := block.NewTableTransaction(0, 0) 282 require.NoError(t, err) 283 284 err = testSetupTxn.Commit() 285 require.NoError(t, err) 286 287 testTxn, err := block.NewTableTransaction(0, 0) 288 require.NoError(t, err) 289 290 validateErr := testTxn.Validate() 291 require.ErrorContains(t, validateErr, "non-increasing time") 292 require.False(t, errors.IsRetryableConflictError(validateErr)) 293 } 294 295 func TestDerivedDataTableValidateRejectOutdatedReadSet(t *testing.T) { 296 block := newEmptyTestBlock() 297 298 testSetupTxn1, err := block.NewTableTransaction(0, 0) 299 require.NoError(t, err) 300 301 testSetupTxn2, err := block.NewTableTransaction(0, 1) 302 require.NoError(t, err) 303 304 testTxn, err := block.NewTableTransaction(0, 2) 305 require.NoError(t, err) 306 307 key := "abc" 308 valueString := "value" 309 expectedValue := &valueString 310 expectedSnapshot := &snapshot.ExecutionSnapshot{} 311 312 testSetupTxn1.SetForTestingOnly(key, expectedValue, expectedSnapshot) 313 314 testSetupTxn1.AddInvalidator(&testInvalidator{}) 315 316 err = testSetupTxn1.Commit() 317 require.NoError(t, err) 318 319 validateErr := testTxn.Validate() 320 require.NoError(t, validateErr) 321 322 actualProg, actualSnapshot, ok := testTxn.GetForTestingOnly(key) 323 require.True(t, ok) 324 require.Same(t, expectedValue, actualProg) 325 require.Same(t, expectedSnapshot, actualSnapshot) 326 327 validateErr = testTxn.Validate() 328 require.NoError(t, validateErr) 329 330 testSetupTxn2.AddInvalidator(&testInvalidator{invalidateAll: true}) 331 332 err = testSetupTxn2.Commit() 333 require.NoError(t, err) 334 335 validateErr = testTxn.Validate() 336 require.ErrorContains(t, validateErr, "outdated read set") 337 require.True(t, errors.IsRetryableConflictError(validateErr)) 338 } 339 340 func TestDerivedDataTableValidateRejectOutdatedWriteSet(t *testing.T) { 341 block := newEmptyTestBlock() 342 343 testSetupTxn, err := block.NewTableTransaction(0, 0) 344 require.NoError(t, err) 345 346 testSetupTxn.AddInvalidator(&testInvalidator{invalidateAll: true}) 347 348 err = testSetupTxn.Commit() 349 require.NoError(t, err) 350 351 require.Equal(t, 1, len(block.InvalidatorsForTestingOnly())) 352 353 testTxn, err := block.NewTableTransaction(0, 1) 354 require.NoError(t, err) 355 356 value := "value" 357 testTxn.SetForTestingOnly("key", &value, &snapshot.ExecutionSnapshot{}) 358 359 validateErr := testTxn.Validate() 360 require.ErrorContains(t, validateErr, "outdated write set") 361 require.True(t, errors.IsRetryableConflictError(validateErr)) 362 } 363 364 func TestDerivedDataTableValidateIgnoreInvalidatorsOlderThanSnapshot(t *testing.T) { 365 block := newEmptyTestBlock() 366 367 testSetupTxn, err := block.NewTableTransaction(0, 0) 368 require.NoError(t, err) 369 370 testSetupTxn.AddInvalidator(&testInvalidator{invalidateAll: true}) 371 err = testSetupTxn.Commit() 372 require.NoError(t, err) 373 374 require.Equal(t, 1, len(block.InvalidatorsForTestingOnly())) 375 376 testTxn, err := block.NewTableTransaction(1, 1) 377 require.NoError(t, err) 378 379 value := "value" 380 testTxn.SetForTestingOnly("key", &value, &snapshot.ExecutionSnapshot{}) 381 382 err = testTxn.Validate() 383 require.NoError(t, err) 384 } 385 386 func TestDerivedDataTableCommitWriteOnlyTransactionNoInvalidation(t *testing.T) { 387 block := newEmptyTestBlock() 388 389 testTxn, err := block.NewTableTransaction(0, 0) 390 require.NoError(t, err) 391 392 key := "234" 393 394 actualValue, actualSnapshot, ok := testTxn.GetForTestingOnly(key) 395 require.False(t, ok) 396 require.Nil(t, actualValue) 397 require.Nil(t, actualSnapshot) 398 399 valueString := "stuff" 400 expectedValue := &valueString 401 expectedSnapshot := &snapshot.ExecutionSnapshot{} 402 403 testTxn.SetForTestingOnly(key, expectedValue, expectedSnapshot) 404 405 actualValue, actualSnapshot, ok = testTxn.GetForTestingOnly(key) 406 require.True(t, ok) 407 require.Same(t, expectedValue, actualValue) 408 require.Same(t, expectedSnapshot, actualSnapshot) 409 410 testTxn.AddInvalidator(&testInvalidator{}) 411 412 err = testTxn.Commit() 413 require.NoError(t, err) 414 415 // Sanity check 416 417 require.Equal( 418 t, 419 logical.Time(0), 420 block.LatestCommitExecutionTimeForTestingOnly()) 421 422 require.Equal(t, 0, len(block.InvalidatorsForTestingOnly())) 423 424 entries := block.EntriesForTestingOnly() 425 require.Equal(t, 1, len(entries)) 426 427 entry, ok := entries[key] 428 require.True(t, ok) 429 require.False(t, entry.isInvalid) 430 require.Same(t, expectedValue, entry.Value) 431 require.Same(t, expectedSnapshot, entry.ExecutionSnapshot) 432 } 433 434 func TestDerivedDataTableCommitWriteOnlyTransactionWithInvalidation(t *testing.T) { 435 block := newEmptyTestBlock() 436 437 testTxnTime := logical.Time(47) 438 testTxn, err := block.NewTableTransaction(0, testTxnTime) 439 require.NoError(t, err) 440 441 key := "999" 442 443 actualValue, actualSnapshot, ok := testTxn.GetForTestingOnly(key) 444 require.False(t, ok) 445 require.Nil(t, actualValue) 446 require.Nil(t, actualSnapshot) 447 448 valueString := "blah" 449 expectedValue := &valueString 450 expectedSnapshot := &snapshot.ExecutionSnapshot{} 451 452 testTxn.SetForTestingOnly(key, expectedValue, expectedSnapshot) 453 454 actualValue, actualSnapshot, ok = testTxn.GetForTestingOnly(key) 455 require.True(t, ok) 456 require.Same(t, expectedValue, actualValue) 457 require.Same(t, expectedSnapshot, actualSnapshot) 458 459 invalidator := &testInvalidator{invalidateAll: true} 460 461 testTxn.AddInvalidator(invalidator) 462 463 err = testTxn.Commit() 464 require.NoError(t, err) 465 466 // Sanity check 467 468 require.Equal( 469 t, 470 testTxnTime, 471 block.LatestCommitExecutionTimeForTestingOnly()) 472 473 require.Equal( 474 t, 475 chainedTableInvalidators[string, *string]{ 476 { 477 TableInvalidator: invalidator, 478 executionTime: testTxnTime, 479 }, 480 }, 481 block.InvalidatorsForTestingOnly()) 482 483 require.Equal(t, 0, len(block.EntriesForTestingOnly())) 484 } 485 486 func TestDerivedDataTableCommitErrorOnDuplicateWriteEntries(t *testing.T) { 487 block := newEmptyTestBlock() 488 489 testSetupTxn, err := block.NewTableTransaction(0, 11) 490 require.NoError(t, err) 491 492 testTxn, err := block.NewTableTransaction(10, 12) 493 require.NoError(t, err) 494 495 key := "17" 496 valueString := "foo" 497 expectedValue := &valueString 498 expectedSnapshot := &snapshot.ExecutionSnapshot{} 499 500 testSetupTxn.SetForTestingOnly(key, expectedValue, expectedSnapshot) 501 502 err = testSetupTxn.Commit() 503 require.NoError(t, err) 504 505 entries := block.EntriesForTestingOnly() 506 require.Equal(t, 1, len(entries)) 507 508 expectedEntry, ok := entries[key] 509 require.True(t, ok) 510 511 otherString := "other" 512 otherValue := &otherString 513 otherSnapshot := &snapshot.ExecutionSnapshot{} 514 515 testTxn.SetForTestingOnly(key, otherValue, otherSnapshot) 516 517 err = testTxn.Commit() 518 519 require.Error(t, err) 520 require.True(t, errors.IsRetryableConflictError(err)) 521 522 entries = block.EntriesForTestingOnly() 523 require.Equal(t, 1, len(entries)) 524 525 actualEntry, ok := entries[key] 526 require.True(t, ok) 527 528 require.Same(t, expectedEntry, actualEntry) 529 } 530 531 func TestDerivedDataTableCommitReadOnlyTransactionNoInvalidation(t *testing.T) { 532 block := newEmptyTestBlock() 533 534 testSetupTxn, err := block.NewTableTransaction(0, 0) 535 require.NoError(t, err) 536 537 testTxn, err := block.NewTableTransaction(0, 1) 538 require.NoError(t, err) 539 540 key1 := "key1" 541 valStr1 := "value1" 542 expectedValue1 := &valStr1 543 expectedSnapshot1 := &snapshot.ExecutionSnapshot{} 544 545 testSetupTxn.SetForTestingOnly(key1, expectedValue1, expectedSnapshot1) 546 547 key2 := "key2" 548 valStr2 := "value2" 549 expectedValue2 := &valStr2 550 expectedSnapshot2 := &snapshot.ExecutionSnapshot{} 551 552 testSetupTxn.SetForTestingOnly(key2, expectedValue2, expectedSnapshot2) 553 554 err = testSetupTxn.Commit() 555 require.NoError(t, err) 556 557 actualValue, actualSnapshot, ok := testTxn.GetForTestingOnly(key1) 558 require.True(t, ok) 559 require.Same(t, expectedValue1, actualValue) 560 require.Same(t, expectedSnapshot1, actualSnapshot) 561 562 actualValue, actualSnapshot, ok = testTxn.GetForTestingOnly(key2) 563 require.True(t, ok) 564 require.Same(t, expectedValue2, actualValue) 565 require.Same(t, expectedSnapshot2, actualSnapshot) 566 567 actualValue, actualSnapshot, ok = testTxn.GetForTestingOnly("key3") 568 require.False(t, ok) 569 require.Nil(t, actualValue) 570 require.Nil(t, actualSnapshot) 571 572 testTxn.AddInvalidator(&testInvalidator{}) 573 574 err = testTxn.Commit() 575 require.NoError(t, err) 576 577 // Sanity check 578 579 require.Equal( 580 t, 581 logical.Time(1), 582 block.LatestCommitExecutionTimeForTestingOnly()) 583 584 require.Equal(t, 0, len(block.InvalidatorsForTestingOnly())) 585 586 entries := block.EntriesForTestingOnly() 587 require.Equal(t, 2, len(entries)) 588 589 entry, ok := entries[key1] 590 require.True(t, ok) 591 require.False(t, entry.isInvalid) 592 require.Same(t, expectedValue1, entry.Value) 593 require.Same(t, expectedSnapshot1, entry.ExecutionSnapshot) 594 595 entry, ok = entries[key2] 596 require.True(t, ok) 597 require.False(t, entry.isInvalid) 598 require.Same(t, expectedValue2, entry.Value) 599 require.Same(t, expectedSnapshot2, entry.ExecutionSnapshot) 600 } 601 602 func TestDerivedDataTableCommitReadOnlyTransactionWithInvalidation(t *testing.T) { 603 block := newEmptyTestBlock() 604 605 testSetupTxn1Time := logical.Time(2) 606 testSetupTxn1, err := block.NewTableTransaction(0, testSetupTxn1Time) 607 require.NoError(t, err) 608 609 testSetupTxn2, err := block.NewTableTransaction(0, 4) 610 require.NoError(t, err) 611 612 testTxnTime := logical.Time(6) 613 testTxn, err := block.NewTableTransaction(0, testTxnTime) 614 require.NoError(t, err) 615 616 testSetupTxn1Invalidator := &testInvalidator{ 617 invalidateName: "blah", 618 } 619 testSetupTxn1.AddInvalidator(testSetupTxn1Invalidator) 620 621 err = testSetupTxn1.Commit() 622 require.NoError(t, err) 623 624 key1 := "key1" 625 valStr1 := "v1" 626 expectedValue1 := &valStr1 627 expectedSnapshot1 := &snapshot.ExecutionSnapshot{} 628 629 testSetupTxn2.SetForTestingOnly(key1, expectedValue1, expectedSnapshot1) 630 631 key2 := "key2" 632 valStr2 := "v2" 633 expectedValue2 := &valStr2 634 expectedSnapshot2 := &snapshot.ExecutionSnapshot{} 635 636 testSetupTxn2.SetForTestingOnly(key2, expectedValue2, expectedSnapshot2) 637 638 err = testSetupTxn2.Commit() 639 require.NoError(t, err) 640 641 actualValue, actualSnapshot, ok := testTxn.GetForTestingOnly(key1) 642 require.True(t, ok) 643 require.Same(t, expectedValue1, actualValue) 644 require.Same(t, expectedSnapshot1, actualSnapshot) 645 646 actualValue, actualSnapshot, ok = testTxn.GetForTestingOnly(key2) 647 require.True(t, ok) 648 require.Same(t, expectedValue2, actualValue) 649 require.Same(t, expectedSnapshot2, actualSnapshot) 650 651 actualValue, actualSnapshot, ok = testTxn.GetForTestingOnly("key3") 652 require.False(t, ok) 653 require.Nil(t, actualValue) 654 require.Nil(t, actualSnapshot) 655 656 testTxnInvalidator := &testInvalidator{invalidateAll: true} 657 testTxn.AddInvalidator(testTxnInvalidator) 658 659 err = testTxn.Commit() 660 require.NoError(t, err) 661 662 // Sanity check 663 664 require.Equal( 665 t, 666 testTxnTime, 667 block.LatestCommitExecutionTimeForTestingOnly()) 668 669 require.Equal( 670 t, 671 chainedTableInvalidators[string, *string]{ 672 { 673 TableInvalidator: testSetupTxn1Invalidator, 674 executionTime: testSetupTxn1Time, 675 }, 676 { 677 TableInvalidator: testTxnInvalidator, 678 executionTime: testTxnTime, 679 }, 680 }, 681 block.InvalidatorsForTestingOnly()) 682 683 require.Equal(t, 0, len(block.EntriesForTestingOnly())) 684 } 685 686 func TestDerivedDataTableCommitValidateError(t *testing.T) { 687 block := newEmptyTestBlock() 688 689 testSetupTxn, err := block.NewTableTransaction(0, 10) 690 require.NoError(t, err) 691 692 err = testSetupTxn.Commit() 693 require.NoError(t, err) 694 695 testTxn, err := block.NewTableTransaction(10, 10) 696 require.NoError(t, err) 697 698 commitErr := testTxn.Commit() 699 require.ErrorContains(t, commitErr, "non-increasing time") 700 require.False(t, errors.IsRetryableConflictError(commitErr)) 701 } 702 703 func TestDerivedDataTableCommitRejectCommitGapForNormalTxn(t *testing.T) { 704 block := newEmptyTestBlock() 705 706 commitTime := logical.Time(5) 707 testSetupTxn, err := block.NewTableTransaction(0, commitTime) 708 require.NoError(t, err) 709 710 err = testSetupTxn.Commit() 711 require.NoError(t, err) 712 713 require.Equal( 714 t, 715 commitTime, 716 block.LatestCommitExecutionTimeForTestingOnly()) 717 718 testTxn, err := block.NewTableTransaction(10, 10) 719 require.NoError(t, err) 720 721 err = testTxn.Validate() 722 require.NoError(t, err) 723 724 commitErr := testTxn.Commit() 725 require.ErrorContains(t, commitErr, "missing commit range [6, 10)") 726 require.False(t, errors.IsRetryableConflictError(commitErr)) 727 } 728 729 func TestDerivedDataTableCommitSnapshotReadDontAdvanceTime(t *testing.T) { 730 block := newEmptyTestBlock() 731 732 commitTime := logical.Time(71) 733 testSetupTxn, err := block.NewTableTransaction(0, commitTime) 734 require.NoError(t, err) 735 736 err = testSetupTxn.Commit() 737 require.NoError(t, err) 738 739 for i := 0; i < 10; i++ { 740 txn := block.NewSnapshotReadTableTransaction() 741 742 err = txn.Commit() 743 require.NoError(t, err) 744 } 745 746 require.Equal( 747 t, 748 commitTime, 749 block.LatestCommitExecutionTimeForTestingOnly()) 750 } 751 752 func TestDerivedDataTableCommitBadSnapshotReadInvalidator(t *testing.T) { 753 block := newEmptyTestBlock() 754 755 testTxn := block.NewSnapshotReadTableTransaction() 756 757 testTxn.AddInvalidator(&testInvalidator{invalidateAll: true}) 758 759 commitErr := testTxn.Commit() 760 require.ErrorContains(t, commitErr, "snapshot read can't invalidate") 761 require.False(t, errors.IsRetryableConflictError(commitErr)) 762 } 763 764 func TestDerivedDataTableCommitFineGrainInvalidation(t *testing.T) { 765 block := newEmptyTestBlock() 766 767 // Setup the database with two read entries 768 769 testSetupTxn, err := block.NewTableTransaction(0, 0) 770 require.NoError(t, err) 771 772 readKey1 := "read-key-1" 773 readValStr1 := "read-value-1" 774 readValue1 := &readValStr1 775 readSnapshot1 := &snapshot.ExecutionSnapshot{} 776 777 readKey2 := "read-key-2" 778 readValStr2 := "read-value-2" 779 readValue2 := &readValStr2 780 readSnapshot2 := &snapshot.ExecutionSnapshot{} 781 782 testSetupTxn.SetForTestingOnly(readKey1, readValue1, readSnapshot1) 783 testSetupTxn.SetForTestingOnly(readKey2, readValue2, readSnapshot2) 784 785 err = testSetupTxn.Commit() 786 require.NoError(t, err) 787 788 // Setup the test transaction by read both existing entries and writing 789 // two new ones, 790 791 testTxnTime := logical.Time(15) 792 testTxn, err := block.NewTableTransaction(1, testTxnTime) 793 require.NoError(t, err) 794 795 actualValue, actualSnapshot, ok := testTxn.GetForTestingOnly(readKey1) 796 require.True(t, ok) 797 require.Same(t, readValue1, actualValue) 798 require.Same(t, readSnapshot1, actualSnapshot) 799 800 actualValue, actualSnapshot, ok = testTxn.GetForTestingOnly(readKey2) 801 require.True(t, ok) 802 require.Same(t, readValue2, actualValue) 803 require.Same(t, readSnapshot2, actualSnapshot) 804 805 writeKey1 := "write key 1" 806 writeValStr1 := "write value 1" 807 writeValue1 := &writeValStr1 808 writeSnapshot1 := &snapshot.ExecutionSnapshot{} 809 810 writeKey2 := "write key 2" 811 writeValStr2 := "write value 2" 812 writeValue2 := &writeValStr2 813 writeSnapshot2 := &snapshot.ExecutionSnapshot{} 814 815 testTxn.SetForTestingOnly(writeKey1, writeValue1, writeSnapshot1) 816 testTxn.SetForTestingOnly(writeKey2, writeValue2, writeSnapshot2) 817 818 // Actual test. Invalidate one pre-existing entry and one new entry. 819 820 invalidator1 := &testInvalidator{ 821 invalidateName: readKey1, 822 } 823 invalidator2 := &testInvalidator{ 824 invalidateName: writeKey1, 825 } 826 testTxn.AddInvalidator(nil) 827 testTxn.AddInvalidator(invalidator1) 828 testTxn.AddInvalidator(&testInvalidator{}) 829 testTxn.AddInvalidator(invalidator2) 830 testTxn.AddInvalidator(&testInvalidator{}) 831 832 err = testTxn.Commit() 833 require.NoError(t, err) 834 835 require.Equal( 836 t, 837 testTxnTime, 838 block.LatestCommitExecutionTimeForTestingOnly()) 839 840 require.Equal( 841 t, 842 chainedTableInvalidators[string, *string]{ 843 { 844 TableInvalidator: invalidator1, 845 executionTime: testTxnTime, 846 }, 847 { 848 TableInvalidator: invalidator2, 849 executionTime: testTxnTime, 850 }, 851 }, 852 block.InvalidatorsForTestingOnly()) 853 854 entries := block.EntriesForTestingOnly() 855 require.Equal(t, 2, len(entries)) 856 857 entry, ok := entries[readKey2] 858 require.True(t, ok) 859 require.False(t, entry.isInvalid) 860 require.Same(t, readValue2, entry.Value) 861 require.Same(t, readSnapshot2, entry.ExecutionSnapshot) 862 863 entry, ok = entries[writeKey2] 864 require.True(t, ok) 865 require.False(t, entry.isInvalid) 866 require.Same(t, writeValue2, entry.Value) 867 require.Same(t, writeSnapshot2, entry.ExecutionSnapshot) 868 } 869 870 func TestDerivedDataTableNewChildDerivedBlockData(t *testing.T) { 871 parentBlock := newEmptyTestBlock() 872 873 require.Equal( 874 t, 875 logical.ParentBlockTime, 876 parentBlock.LatestCommitExecutionTimeForTestingOnly()) 877 require.Equal(t, 0, len(parentBlock.InvalidatorsForTestingOnly())) 878 require.Equal(t, 0, len(parentBlock.EntriesForTestingOnly())) 879 880 txn, err := parentBlock.NewTableTransaction(0, 0) 881 require.NoError(t, err) 882 883 txn.AddInvalidator(&testInvalidator{invalidateAll: true}) 884 885 err = txn.Commit() 886 require.NoError(t, err) 887 888 txn, err = parentBlock.NewTableTransaction(1, 1) 889 require.NoError(t, err) 890 891 key := "foo bar" 892 valStr := "zzz" 893 value := &valStr 894 state := &snapshot.ExecutionSnapshot{} 895 896 txn.SetForTestingOnly(key, value, state) 897 898 err = txn.Commit() 899 require.NoError(t, err) 900 901 // Sanity check parent block 902 903 require.Equal( 904 t, 905 logical.Time(1), 906 parentBlock.LatestCommitExecutionTimeForTestingOnly()) 907 908 require.Equal(t, 1, len(parentBlock.InvalidatorsForTestingOnly())) 909 910 parentEntries := parentBlock.EntriesForTestingOnly() 911 require.Equal(t, 1, len(parentEntries)) 912 913 parentEntry, ok := parentEntries[key] 914 require.True(t, ok) 915 require.False(t, parentEntry.isInvalid) 916 require.Same(t, value, parentEntry.Value) 917 require.Same(t, state, parentEntry.ExecutionSnapshot) 918 919 // Verify child is correctly initialized 920 921 childBlock := parentBlock.NewChildTable() 922 923 require.Equal( 924 t, 925 logical.ParentBlockTime, 926 childBlock.LatestCommitExecutionTimeForTestingOnly()) 927 928 require.Equal(t, 0, len(childBlock.InvalidatorsForTestingOnly())) 929 930 childEntries := childBlock.EntriesForTestingOnly() 931 require.Equal(t, 1, len(childEntries)) 932 933 childEntry, ok := childEntries[key] 934 require.True(t, ok) 935 require.False(t, childEntry.isInvalid) 936 require.Same(t, value, childEntry.Value) 937 require.Same(t, state, childEntry.ExecutionSnapshot) 938 939 require.NotSame(t, parentEntry, childEntry) 940 } 941 942 type testValueComputer struct { 943 valueFunc func() (int, error) 944 called bool 945 } 946 947 func (computer *testValueComputer) Compute( 948 txnState state.NestedTransactionPreparer, 949 key flow.RegisterID, 950 ) ( 951 int, 952 error, 953 ) { 954 computer.called = true 955 _, err := txnState.Get(key) 956 if err != nil { 957 return 0, err 958 } 959 960 return computer.valueFunc() 961 } 962 963 func TestDerivedDataTableGetOrCompute(t *testing.T) { 964 blockDerivedData := NewEmptyTable[flow.RegisterID, int](0) 965 966 key := flow.NewRegisterID(unittest.RandomAddressFixture(), "key") 967 value := 12345 968 969 t.Run("compute value", func(t *testing.T) { 970 txnState := state.NewTransactionState( 971 nil, 972 state.DefaultParameters()) 973 974 txnDerivedData, err := blockDerivedData.NewTableTransaction(0, 0) 975 assert.NoError(t, err) 976 977 // first attempt to compute the value returns an error. 978 // But it's perfectly safe to handle the error and try again with the same txnState. 979 computer := &testValueComputer{ 980 valueFunc: func() (int, error) { return 0, fmt.Errorf("compute error") }, 981 } 982 _, err = txnDerivedData.GetOrCompute(txnState, key, computer) 983 assert.Error(t, err) 984 assert.Equal(t, 0, txnState.NumNestedTransactions()) 985 986 // second attempt to compute the value succeeds. 987 988 computer = &testValueComputer{ 989 valueFunc: func() (int, error) { return value, nil }, 990 } 991 val, err := txnDerivedData.GetOrCompute(txnState, key, computer) 992 assert.NoError(t, err) 993 assert.Equal(t, value, val) 994 assert.True(t, computer.called) 995 996 snapshot, err := txnState.FinalizeMainTransaction() 997 assert.NoError(t, err) 998 999 _, found := snapshot.ReadSet[key] 1000 assert.True(t, found) 1001 1002 // Commit to setup the next test. 1003 err = txnDerivedData.Commit() 1004 assert.Nil(t, err) 1005 }) 1006 1007 t.Run("get value", func(t *testing.T) { 1008 txnState := state.NewTransactionState( 1009 nil, 1010 state.DefaultParameters()) 1011 1012 txnDerivedData, err := blockDerivedData.NewTableTransaction(1, 1) 1013 assert.NoError(t, err) 1014 1015 computer := &testValueComputer{ 1016 valueFunc: func() (int, error) { return value, nil }, 1017 } 1018 val, err := txnDerivedData.GetOrCompute(txnState, key, computer) 1019 assert.NoError(t, err) 1020 assert.Equal(t, value, val) 1021 assert.False(t, computer.called) 1022 1023 snapshot, err := txnState.FinalizeMainTransaction() 1024 assert.NoError(t, err) 1025 1026 _, found := snapshot.ReadSet[key] 1027 assert.True(t, found) 1028 }) 1029 }