github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/execution/storehouse/in_memory_register_store_test.go (about)

     1  package storehouse
     2  
     3  import (
     4  	"fmt"
     5  	"math/rand"
     6  	"sync"
     7  	"testing"
     8  
     9  	"github.com/stretchr/testify/require"
    10  
    11  	"github.com/onflow/flow-go/model/flow"
    12  	"github.com/onflow/flow-go/utils/unittest"
    13  )
    14  
    15  // 1. SaveRegisters should fail if height is below or equal to pruned height
    16  func TestInMemoryRegisterStore(t *testing.T) {
    17  	t.Run("FailBelowOrEqualPrunedHeight", func(t *testing.T) {
    18  		t.Parallel()
    19  		// 1.
    20  		pruned := uint64(10)
    21  		lastID := unittest.IdentifierFixture()
    22  		store := NewInMemoryRegisterStore(pruned, lastID)
    23  		err := store.SaveRegisters(
    24  			pruned-1, // below pruned pruned, will fail
    25  			unittest.IdentifierFixture(),
    26  			unittest.IdentifierFixture(),
    27  			flow.RegisterEntries{},
    28  		)
    29  		require.Error(t, err)
    30  		require.Contains(t, err.Error(), "<= pruned height")
    31  
    32  		err = store.SaveRegisters(
    33  			pruned, // equal to pruned height, will fail
    34  			lastID,
    35  			unittest.IdentifierFixture(),
    36  			flow.RegisterEntries{},
    37  		)
    38  		require.Error(t, err)
    39  		require.Contains(t, err.Error(), "<= pruned height")
    40  	})
    41  
    42  	//  2. SaveRegisters should fail if its parent block doesn't exist and it is not the pruned block
    43  	//     SaveRegisters should succeed if height is above pruned height and block is not saved,
    44  	//     the updates can be retrieved by GetUpdatedRegisters
    45  	//     GetRegister should return PrunedError if the queried key is not updated since pruned height
    46  	//     GetRegister should return PrunedError if the queried height is below pruned height
    47  	//     GetRegister should return ErrNotExecuted if the block is unknown
    48  	t.Run("FailParentNotExist", func(t *testing.T) {
    49  		t.Parallel()
    50  		pruned := uint64(10)
    51  		lastID := unittest.IdentifierFixture()
    52  		store := NewInMemoryRegisterStore(pruned, lastID)
    53  
    54  		height := pruned + 1 // above the pruned pruned
    55  		blockID := unittest.IdentifierFixture()
    56  		notExistParent := unittest.IdentifierFixture()
    57  		reg := unittest.RegisterEntryFixture()
    58  		err := store.SaveRegisters(
    59  			height,
    60  			blockID,
    61  			notExistParent, // should fail because parent doesn't exist
    62  			flow.RegisterEntries{reg},
    63  		)
    64  		require.Error(t, err)
    65  		require.Contains(t, err.Error(), "but its parent")
    66  	})
    67  
    68  	t.Run("StoreOK", func(t *testing.T) {
    69  		t.Parallel()
    70  		// 3.
    71  		pruned := uint64(10)
    72  		lastID := unittest.IdentifierFixture()
    73  		store := NewInMemoryRegisterStore(pruned, lastID)
    74  
    75  		height := pruned + 1 // above the pruned pruned
    76  		blockID := unittest.IdentifierFixture()
    77  		reg := unittest.RegisterEntryFixture()
    78  		err := store.SaveRegisters(
    79  			height,
    80  			blockID,
    81  			lastID,
    82  			flow.RegisterEntries{reg},
    83  		)
    84  		require.NoError(t, err)
    85  
    86  		val, err := store.GetRegister(height, blockID, reg.Key)
    87  		require.NoError(t, err)
    88  		require.Equal(t, reg.Value, val)
    89  
    90  		// unknown key
    91  		_, err = store.GetRegister(height, blockID, unknownKey)
    92  		require.Error(t, err)
    93  		pe, ok := IsPrunedError(err)
    94  		require.True(t, ok)
    95  		require.Equal(t, pe.PrunedHeight, pruned)
    96  		require.Equal(t, pe.Height, height)
    97  
    98  		// unknown block with unknown height
    99  		_, err = store.GetRegister(height+1, unknownBlock, reg.Key)
   100  		require.Error(t, err)
   101  		require.ErrorIs(t, err, ErrNotExecuted)
   102  
   103  		// unknown block with known height
   104  		_, err = store.GetRegister(height, unknownBlock, reg.Key)
   105  		require.Error(t, err)
   106  		require.ErrorIs(t, err, ErrNotExecuted)
   107  
   108  		// too low height
   109  		_, err = store.GetRegister(height-1, unknownBlock, reg.Key)
   110  		require.Error(t, err)
   111  		pe, ok = IsPrunedError(err)
   112  		require.True(t, ok)
   113  		require.Equal(t, pe.PrunedHeight, pruned)
   114  		require.Equal(t, pe.Height, height-1)
   115  	})
   116  
   117  	// 3. SaveRegisters should fail if the block is already saved
   118  	t.Run("StoreFailAlreadyExist", func(t *testing.T) {
   119  		t.Parallel()
   120  		pruned := uint64(10)
   121  		lastID := unittest.IdentifierFixture()
   122  		store := NewInMemoryRegisterStore(pruned, lastID)
   123  
   124  		height := pruned + 1 // above the pruned pruned
   125  		blockID := unittest.IdentifierFixture()
   126  		reg := unittest.RegisterEntryFixture()
   127  		err := store.SaveRegisters(
   128  			height,
   129  			blockID,
   130  			lastID,
   131  			flow.RegisterEntries{reg},
   132  		)
   133  		require.NoError(t, err)
   134  
   135  		// saving again should fail
   136  		err = store.SaveRegisters(
   137  			height,
   138  			blockID,
   139  			lastID,
   140  			flow.RegisterEntries{reg},
   141  		)
   142  		require.Error(t, err)
   143  		require.Contains(t, err.Error(), "already exists")
   144  	})
   145  
   146  	//  4. SaveRegisters should succeed if a different block at the same height was saved before,
   147  	//     updates for different blocks can be retrieved by their blockID
   148  	t.Run("StoreOKDifferentBlockSameParent", func(t *testing.T) {
   149  		t.Parallel()
   150  		pruned := uint64(10)
   151  		lastID := unittest.IdentifierFixture()
   152  		store := NewInMemoryRegisterStore(pruned, lastID)
   153  
   154  		// 10 <- A
   155  		//    ^- B
   156  		height := pruned + 1 // above the pruned pruned
   157  		blockA := unittest.IdentifierFixture()
   158  		regA := unittest.RegisterEntryFixture()
   159  		err := store.SaveRegisters(
   160  			height,
   161  			blockA,
   162  			lastID,
   163  			flow.RegisterEntries{regA},
   164  		)
   165  		require.NoError(t, err)
   166  
   167  		blockB := unittest.IdentifierFixture()
   168  		regB := unittest.RegisterEntryFixture()
   169  		err = store.SaveRegisters(
   170  			height,
   171  			blockB, // different block
   172  			lastID, // same parent
   173  			flow.RegisterEntries{regB},
   174  		)
   175  		require.NoError(t, err)
   176  
   177  		valA, err := store.GetRegister(height, blockA, regA.Key)
   178  		require.NoError(t, err)
   179  		require.Equal(t, regA.Value, valA)
   180  
   181  		valB, err := store.GetRegister(height, blockB, regB.Key)
   182  		require.NoError(t, err)
   183  		require.Equal(t, regB.Value, valB)
   184  	})
   185  
   186  	t.Run("IsBlockExecuted", func(t *testing.T) {
   187  		t.Parallel()
   188  		pruned := uint64(10)
   189  		lastID := unittest.IdentifierFixture()
   190  		store := NewInMemoryRegisterStore(pruned, lastID)
   191  
   192  		height := pruned + 1 // above the pruned pruned
   193  		blockID := unittest.IdentifierFixture()
   194  		reg := unittest.RegisterEntryFixture()
   195  		err := store.SaveRegisters(
   196  			height,
   197  			blockID,
   198  			lastID,
   199  			flow.RegisterEntries{reg},
   200  		)
   201  		require.NoError(t, err)
   202  
   203  		// above the pruned height and is executed
   204  		executed, err := store.IsBlockExecuted(height, blockID)
   205  		require.NoError(t, err)
   206  		require.True(t, executed)
   207  
   208  		// above the pruned height, and is not executed
   209  		executed, err = store.IsBlockExecuted(pruned+1, unittest.IdentifierFixture())
   210  		require.NoError(t, err)
   211  		require.False(t, executed)
   212  
   213  		executed, err = store.IsBlockExecuted(pruned+2, unittest.IdentifierFixture())
   214  		require.NoError(t, err)
   215  		require.False(t, executed)
   216  
   217  		// below the pruned height
   218  		_, err = store.IsBlockExecuted(pruned-1, unittest.IdentifierFixture())
   219  		require.Error(t, err)
   220  
   221  		// equal to the pruned height and is the pruned block
   222  		executed, err = store.IsBlockExecuted(pruned, lastID)
   223  		require.NoError(t, err)
   224  		require.True(t, executed)
   225  
   226  		// equal to the pruned height, but is not the pruned block
   227  		executed, err = store.IsBlockExecuted(pruned, unittest.IdentifierFixture())
   228  		require.NoError(t, err)
   229  		require.False(t, executed)
   230  
   231  		// prune a new block
   232  		require.NoError(t, store.Prune(height, blockID))
   233  		// equal to the pruned height and is the pruned block
   234  		executed, err = store.IsBlockExecuted(height, blockID)
   235  		require.NoError(t, err)
   236  		require.True(t, executed)
   237  
   238  		// equal to the pruned height, but is not the pruned block
   239  		executed, err = store.IsBlockExecuted(height, unittest.IdentifierFixture())
   240  		require.NoError(t, err)
   241  		require.False(t, executed)
   242  
   243  		// below the pruned height
   244  		_, err = store.IsBlockExecuted(pruned, lastID)
   245  		require.Error(t, err)
   246  	})
   247  
   248  	// 5. Given A(X: 1, Y: 2), GetRegister(A, X) should return 1, GetRegister(A, X) should return 2
   249  	t.Run("GetRegistersOK", func(t *testing.T) {
   250  		t.Parallel()
   251  		pruned := uint64(10)
   252  		lastID := unittest.IdentifierFixture()
   253  		store := NewInMemoryRegisterStore(pruned, lastID)
   254  
   255  		// 10 <- A (X: 1, Y: 2)
   256  		height := pruned + 1 // above the pruned pruned
   257  		blockA := unittest.IdentifierFixture()
   258  		regX := makeReg("X", "1")
   259  		regY := makeReg("Y", "2")
   260  		err := store.SaveRegisters(
   261  			height,
   262  			blockA,
   263  			lastID,
   264  			flow.RegisterEntries{regX, regY},
   265  		)
   266  		require.NoError(t, err)
   267  
   268  		valX, err := store.GetRegister(height, blockA, regX.Key)
   269  		require.NoError(t, err)
   270  		require.Equal(t, regX.Value, valX)
   271  
   272  		valY, err := store.GetRegister(height, blockA, regY.Key)
   273  		require.NoError(t, err)
   274  		require.Equal(t, regY.Value, valY)
   275  	})
   276  
   277  	//  6. Given A(X: 1, Y: 2) <- B(Y: 3),
   278  	//     GetRegister(B, X) should return 1, because X is not updated in B
   279  	//     GetRegister(B, Y) should return 3, because Y is updated in B
   280  	//     GetRegister(A, Y) should return 2, because the query queries the value at A, not B
   281  	//     GetRegister(B, Z) should return PrunedError, because register is unknown
   282  	//     GetRegister(C, X) should return BlockNotExecuted, because block is not executed (unexecuted)
   283  	t.Run("GetLatestValueOK", func(t *testing.T) {
   284  		t.Parallel()
   285  		pruned := uint64(10)
   286  		lastID := unittest.IdentifierFixture()
   287  		store := NewInMemoryRegisterStore(pruned, lastID)
   288  
   289  		// 10 <- A (X: 1, Y: 2) <- B (Y: 3)
   290  		blockA := unittest.IdentifierFixture()
   291  		regX := makeReg("X", "1")
   292  		regY := makeReg("Y", "2")
   293  		err := store.SaveRegisters(
   294  			pruned+1,
   295  			blockA,
   296  			lastID,
   297  			flow.RegisterEntries{regX, regY},
   298  		)
   299  		require.NoError(t, err)
   300  
   301  		blockB := unittest.IdentifierFixture()
   302  		regY3 := makeReg("Y", "3")
   303  		err = store.SaveRegisters(
   304  			pruned+2,
   305  			blockB,
   306  			blockA,
   307  			flow.RegisterEntries{regY3},
   308  		)
   309  		require.NoError(t, err)
   310  
   311  		val, err := store.GetRegister(pruned+2, blockB, regX.Key)
   312  		require.NoError(t, err)
   313  		require.Equal(t, regX.Value, val) // X is not updated in B
   314  
   315  		val, err = store.GetRegister(pruned+2, blockB, regY.Key)
   316  		require.NoError(t, err)
   317  		require.Equal(t, regY3.Value, val) // Y is updated in B
   318  
   319  		val, err = store.GetRegister(pruned+1, blockA, regY.Key)
   320  		require.NoError(t, err)
   321  		require.Equal(t, regY.Value, val) // Y's old value at A
   322  
   323  		_, err = store.GetRegister(pruned+2, blockB, unknownKey)
   324  		require.Error(t, err)
   325  		pe, ok := IsPrunedError(err)
   326  		require.True(t, ok)
   327  		require.Equal(t, pe.PrunedHeight, pruned)
   328  		require.Equal(t, pe.Height, pruned+2)
   329  
   330  		_, err = store.GetRegister(pruned+3, unittest.IdentifierFixture(), regX.Key)
   331  		require.Error(t, err)
   332  		require.ErrorIs(t, err, ErrNotExecuted) // unknown block
   333  	})
   334  
   335  	//  7. Given the following tree:
   336  	//     Pruned <- A(X:1) <- B(Y:2)
   337  	//     .......^- C(X:3) <- D(Y:4)
   338  	//     GetRegister(D, X) should return 3
   339  	t.Run("StoreMultiForkOK", func(t *testing.T) {
   340  		t.Parallel()
   341  		pruned := uint64(10)
   342  		lastID := unittest.IdentifierFixture()
   343  		store := NewInMemoryRegisterStore(pruned, lastID)
   344  
   345  		// 10 <- A (X: 1) <- B (Y: 2)
   346  		//		^- C (X: 3) <- D (Y: 4)
   347  		blockA := unittest.IdentifierFixture()
   348  		blockB := unittest.IdentifierFixture()
   349  		blockC := unittest.IdentifierFixture()
   350  		blockD := unittest.IdentifierFixture()
   351  
   352  		require.NoError(t, store.SaveRegisters(
   353  			pruned+1,
   354  			blockA,
   355  			lastID,
   356  			flow.RegisterEntries{makeReg("X", "1")},
   357  		))
   358  
   359  		require.NoError(t, store.SaveRegisters(
   360  			pruned+2,
   361  			blockB,
   362  			blockA,
   363  			flow.RegisterEntries{makeReg("Y", "2")},
   364  		))
   365  
   366  		require.NoError(t, store.SaveRegisters(
   367  			pruned+1,
   368  			blockC,
   369  			lastID,
   370  			flow.RegisterEntries{makeReg("X", "3")},
   371  		))
   372  
   373  		require.NoError(t, store.SaveRegisters(
   374  			pruned+2,
   375  			blockD,
   376  			blockC,
   377  			flow.RegisterEntries{makeReg("Y", "4")},
   378  		))
   379  
   380  		reg := makeReg("X", "3")
   381  		val, err := store.GetRegister(pruned+2, blockD, reg.Key)
   382  		require.NoError(t, err)
   383  		require.Equal(t, reg.Value, val)
   384  	})
   385  
   386  	//  8. Given the following tree:
   387  	//     Pruned <- A(X:1) <- B(Y:2), B is not executed
   388  	//     GetUpdatedRegisters(B) should return ErrNotExecuted
   389  	t.Run("GetUpdatedRegisters", func(t *testing.T) {
   390  		t.Parallel()
   391  		pruned := uint64(10)
   392  		lastID := unittest.IdentifierFixture()
   393  		store := NewInMemoryRegisterStore(pruned, lastID)
   394  
   395  		// 10 <- A (X: 1) <- B (Y: 2)
   396  		blockA := unittest.IdentifierFixture()
   397  		blockB := unittest.IdentifierFixture()
   398  
   399  		require.NoError(t, store.SaveRegisters(
   400  			pruned+1,
   401  			blockA,
   402  			lastID,
   403  			flow.RegisterEntries{makeReg("X", "1")},
   404  		))
   405  
   406  		reg, err := store.GetUpdatedRegisters(pruned+1, blockA)
   407  		require.NoError(t, err)
   408  		require.Equal(t, flow.RegisterEntries{makeReg("X", "1")}, reg)
   409  
   410  		_, err = store.GetUpdatedRegisters(pruned+2, blockB)
   411  		require.Error(t, err)
   412  		require.ErrorIs(t, err, ErrNotExecuted)
   413  	})
   414  
   415  	//  9. Prune should fail if the block is unknown
   416  	//     Prune should succeed if the block is known, and GetUpdatedRegisters should return err
   417  	//     Prune should prune up to the pruned height.
   418  	//     Given Pruned <- A(X:1) <- B(X:2) <- C(X:3) <- D(X:4)
   419  	//     after Prune(B), GetRegister(C, X) should return 3, GetRegister(B, X) should return err
   420  	t.Run("StorePrune", func(t *testing.T) {
   421  		t.Parallel()
   422  		pruned := uint64(10)
   423  		lastID := unittest.IdentifierFixture()
   424  		store := NewInMemoryRegisterStore(pruned, lastID)
   425  
   426  		blockA := unittest.IdentifierFixture()
   427  		blockB := unittest.IdentifierFixture()
   428  		blockC := unittest.IdentifierFixture()
   429  		blockD := unittest.IdentifierFixture()
   430  
   431  		require.NoError(t, store.SaveRegisters(
   432  			pruned+1,
   433  			blockA,
   434  			lastID,
   435  			flow.RegisterEntries{makeReg("X", "1")},
   436  		))
   437  
   438  		require.NoError(t, store.SaveRegisters(
   439  			pruned+2,
   440  			blockB,
   441  			blockA,
   442  			flow.RegisterEntries{makeReg("X", "2")},
   443  		))
   444  
   445  		require.NoError(t, store.SaveRegisters(
   446  			pruned+3,
   447  			blockC,
   448  			blockB,
   449  			flow.RegisterEntries{makeReg("X", "3")},
   450  		))
   451  
   452  		require.NoError(t, store.SaveRegisters(
   453  			pruned+4,
   454  			blockD,
   455  			blockC,
   456  			flow.RegisterEntries{makeReg("X", "4")},
   457  		))
   458  
   459  		err := store.Prune(pruned+1, unknownBlock) // block is unknown
   460  		require.Error(t, err)
   461  
   462  		err = store.Prune(pruned+1, blockB) // block is known, but height is wrong
   463  		require.Error(t, err)
   464  
   465  		err = store.Prune(pruned+4, unknownBlock) // height is unknown
   466  		require.Error(t, err)
   467  
   468  		err = store.Prune(pruned+1, blockA) // prune next block
   469  		require.NoError(t, err)
   470  
   471  		require.Equal(t, pruned+1, store.PrunedHeight())
   472  
   473  		reg := makeReg("X", "3")
   474  		val, err := store.GetRegister(pruned+3, blockC, reg.Key)
   475  		require.NoError(t, err)
   476  		require.Equal(t, reg.Value, val)
   477  
   478  		_, err = store.GetRegister(pruned+1, blockA, reg.Key) // A is pruned
   479  		require.Error(t, err)
   480  		pe, ok := IsPrunedError(err)
   481  		require.True(t, ok)
   482  		require.Equal(t, pe.PrunedHeight, pruned+1)
   483  		require.Equal(t, pe.Height, pruned+1)
   484  
   485  		err = store.Prune(pruned+3, blockC) // prune both B and C
   486  		require.NoError(t, err)
   487  
   488  		require.Equal(t, pruned+3, store.PrunedHeight())
   489  
   490  		reg = makeReg("X", "4")
   491  		val, err = store.GetRegister(pruned+4, blockD, reg.Key) // can still get X at block D
   492  		require.NoError(t, err)
   493  		require.Equal(t, reg.Value, val)
   494  	})
   495  
   496  	//  10. Prune should prune conflicting forks
   497  	//     Given Pruned <- A(X:1) <- B(X:2)
   498  	//     .................. ^----- E(X:5)
   499  	//     ............ ^- C(X:3) <- D(X:4)
   500  	//     Prune(A) should prune C and D, and GetUpdatedRegisters(C) should return out of range error,
   501  	//     GetUpdatedRegisters(D) should return NotFound
   502  	t.Run("PruneConflictingForks", func(t *testing.T) {
   503  		t.Parallel()
   504  		pruned := uint64(10)
   505  		lastID := unittest.IdentifierFixture()
   506  		store := NewInMemoryRegisterStore(pruned, lastID)
   507  
   508  		blockA := unittest.IdentifierFixture()
   509  		blockB := unittest.IdentifierFixture()
   510  		blockC := unittest.IdentifierFixture()
   511  		blockD := unittest.IdentifierFixture()
   512  		blockE := unittest.IdentifierFixture()
   513  
   514  		require.NoError(t, store.SaveRegisters(
   515  			pruned+1,
   516  			blockA,
   517  			lastID,
   518  			flow.RegisterEntries{makeReg("X", "1")},
   519  		))
   520  
   521  		require.NoError(t, store.SaveRegisters(
   522  			pruned+2,
   523  			blockB,
   524  			blockA,
   525  			flow.RegisterEntries{makeReg("X", "2")},
   526  		))
   527  
   528  		require.NoError(t, store.SaveRegisters(
   529  			pruned+1,
   530  			blockC,
   531  			lastID,
   532  			flow.RegisterEntries{makeReg("X", "3")},
   533  		))
   534  
   535  		require.NoError(t, store.SaveRegisters(
   536  			pruned+2,
   537  			blockD,
   538  			blockC,
   539  			flow.RegisterEntries{makeReg("X", "4")},
   540  		))
   541  
   542  		require.NoError(t, store.SaveRegisters(
   543  			pruned+2,
   544  			blockE,
   545  			blockA,
   546  			flow.RegisterEntries{makeReg("X", "5")},
   547  		))
   548  
   549  		err := store.Prune(pruned+1, blockA) // prune A should prune C and D
   550  		require.NoError(t, err)
   551  
   552  		_, err = store.GetUpdatedRegisters(pruned+2, blockD)
   553  		require.Error(t, err)
   554  		require.Contains(t, err.Error(), "not found")
   555  
   556  		_, err = store.GetUpdatedRegisters(pruned+2, blockE)
   557  		require.NoError(t, err)
   558  	})
   559  
   560  	// 11. Concurrency: SaveRegisters can happen concurrently with GetUpdatedRegisters, and GetRegister
   561  	t.Run("ConcurrentSaveAndGet", func(t *testing.T) {
   562  		t.Parallel()
   563  		pruned := uint64(10)
   564  		lastID := unittest.IdentifierFixture()
   565  		store := NewInMemoryRegisterStore(pruned, lastID)
   566  
   567  		// prepare a chain of 101 blocks with the first as lastID
   568  		count := 100
   569  		blocks := make(map[uint64]flow.Identifier, count)
   570  		blocks[pruned] = lastID
   571  		for i := 1; i < count; i++ {
   572  			block := unittest.IdentifierFixture()
   573  			blocks[pruned+uint64(i)] = block
   574  		}
   575  
   576  		reg := makeReg("X", "0")
   577  
   578  		var wg sync.WaitGroup
   579  		for i := 1; i < count; i++ {
   580  			height := pruned + uint64(i)
   581  			require.NoError(t, store.SaveRegisters(
   582  				height,
   583  				blocks[height],
   584  				blocks[height-1],
   585  				flow.RegisterEntries{makeReg("X", fmt.Sprintf("%v", height))},
   586  			))
   587  
   588  			// concurrently query get registers for past registers
   589  			wg.Add(1)
   590  			go func(i int) {
   591  				defer wg.Done()
   592  
   593  				rdHeight := randBetween(pruned+1, pruned+uint64(i)+1)
   594  				val, err := store.GetRegister(rdHeight, blocks[rdHeight], reg.Key)
   595  				require.NoError(t, err)
   596  				r := makeReg("X", fmt.Sprintf("%v", rdHeight))
   597  				require.Equal(t, r.Value, val)
   598  			}(i)
   599  
   600  			// concurrently query updated registers
   601  			wg.Add(1)
   602  			go func(i int) {
   603  				defer wg.Done()
   604  
   605  				rdHeight := randBetween(pruned+1, pruned+uint64(i)+1)
   606  				vals, err := store.GetUpdatedRegisters(rdHeight, blocks[rdHeight])
   607  				require.NoError(t, err)
   608  				r := makeReg("X", fmt.Sprintf("%v", rdHeight))
   609  				require.Equal(t, flow.RegisterEntries{r}, vals)
   610  			}(i)
   611  		}
   612  
   613  		wg.Wait()
   614  	})
   615  
   616  	// 12. Concurrency: Prune can happen concurrently with GetUpdatedRegisters, and GetRegister
   617  	t.Run("ConcurrentSaveAndPrune", func(t *testing.T) {
   618  		t.Parallel()
   619  		pruned := uint64(10)
   620  		lastID := unittest.IdentifierFixture()
   621  		store := NewInMemoryRegisterStore(pruned, lastID)
   622  
   623  		// prepare a chain of 101 blocks with the first as lastID
   624  		count := 100
   625  		blocks := make(map[uint64]flow.Identifier, count)
   626  		blocks[pruned] = lastID
   627  		for i := 1; i < count; i++ {
   628  			block := unittest.IdentifierFixture()
   629  			blocks[pruned+uint64(i)] = block
   630  		}
   631  
   632  		var wg sync.WaitGroup
   633  		savedHeights := make(chan uint64, 100)
   634  
   635  		wg.Add(1)
   636  		go func() {
   637  			defer wg.Done()
   638  
   639  			lastPrunedHeight := pruned
   640  			for savedHeight := range savedHeights {
   641  				if savedHeight%10 != 0 {
   642  					continue
   643  				}
   644  				rdHeight := randBetween(lastPrunedHeight+1, savedHeight+1)
   645  				err := store.Prune(rdHeight, blocks[rdHeight])
   646  				require.NoError(t, err)
   647  				lastPrunedHeight = rdHeight
   648  			}
   649  		}()
   650  
   651  		// save 100 blocks
   652  		for i := 1; i < count; i++ {
   653  			height := pruned + uint64(i)
   654  			require.NoError(t, store.SaveRegisters(
   655  				height,
   656  				blocks[height],
   657  				blocks[height-1],
   658  				flow.RegisterEntries{makeReg("X", fmt.Sprintf("%v", i))},
   659  			))
   660  			savedHeights <- height
   661  		}
   662  
   663  		close(savedHeights)
   664  
   665  		wg.Wait()
   666  	})
   667  
   668  	t.Run("PrunedError", func(t *testing.T) {
   669  		e := NewPrunedError(1, 2, unittest.IdentifierFixture())
   670  		pe, ok := IsPrunedError(e)
   671  		require.True(t, ok)
   672  		require.Equal(t, uint64(1), pe.Height)
   673  		require.Equal(t, uint64(2), pe.PrunedHeight)
   674  	})
   675  }
   676  
   677  func randBetween(min, max uint64) uint64 {
   678  	return uint64(rand.Intn(int(max)-int(min))) + min
   679  }
   680  
   681  func makeReg(key string, value string) flow.RegisterEntry {
   682  	return unittest.MakeOwnerReg(key, value)
   683  }
   684  
   685  var unknownBlock = unittest.IdentifierFixture()
   686  var unknownKey = flow.RegisterID{
   687  	Owner: "unknown",
   688  	Key:   "unknown",
   689  }