github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/ledger/partial/ptrie/partialTrie_test.go (about)

     1  package ptrie
     2  
     3  import (
     4  	"math/rand"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/assert"
     8  	"github.com/stretchr/testify/require"
     9  
    10  	"github.com/onflow/flow-go/ledger"
    11  	"github.com/onflow/flow-go/ledger/common/testutils"
    12  	"github.com/onflow/flow-go/ledger/complete/mtrie"
    13  	"github.com/onflow/flow-go/module/metrics"
    14  )
    15  
    16  func withForest(
    17  	t *testing.T,
    18  	pathByteSize int,
    19  	numberOfActiveTries int, f func(t *testing.T, f *mtrie.Forest)) {
    20  
    21  	forest, err := mtrie.NewForest(numberOfActiveTries, &metrics.NoopCollector{}, nil)
    22  	require.NoError(t, err)
    23  
    24  	f(t, forest)
    25  }
    26  
    27  func TestPartialTrieEmptyTrie(t *testing.T) {
    28  
    29  	pathByteSize := 32
    30  	withForest(t, pathByteSize, 10, func(t *testing.T, f *mtrie.Forest) {
    31  
    32  		// add path1 to the empty trie
    33  		// 00000000...0 (0)
    34  		path1 := testutils.PathByUint16(0)
    35  		payload1 := testutils.LightPayload('A', 'a')
    36  
    37  		paths := []ledger.Path{path1}
    38  		payloads := []*ledger.Payload{payload1}
    39  
    40  		rootHash := f.GetEmptyRootHash()
    41  		r := &ledger.TrieRead{RootHash: rootHash, Paths: paths}
    42  		bp, err := f.Proofs(r)
    43  		require.NoError(t, err, "error getting proofs values")
    44  
    45  		psmt, err := NewPSMT(rootHash, bp)
    46  		require.NoError(t, err, "error building partial trie")
    47  		ensureRootHash(t, rootHash, psmt)
    48  
    49  		u := &ledger.TrieUpdate{RootHash: rootHash, Paths: paths, Payloads: payloads}
    50  		rootHash, err = f.Update(u)
    51  		require.NoError(t, err, "error updating trie")
    52  
    53  		_, err = psmt.Update(paths, payloads)
    54  		require.NoError(t, err, "error updating psmt")
    55  		ensureRootHash(t, rootHash, psmt)
    56  
    57  		updatedPayload1 := testutils.LightPayload('B', 'b')
    58  		payloads = []*ledger.Payload{updatedPayload1}
    59  
    60  		u = &ledger.TrieUpdate{RootHash: rootHash, Paths: paths, Payloads: payloads}
    61  		rootHash, err = f.Update(u)
    62  		require.NoError(t, err, "error updating trie")
    63  
    64  		_, err = psmt.Update(paths, payloads)
    65  		require.NoError(t, err, "error updating psmt")
    66  		ensureRootHash(t, rootHash, psmt)
    67  	})
    68  }
    69  
    70  // TestPartialTrieGet gets payloads from existent and non-existent paths.
    71  func TestPartialTrieGet(t *testing.T) {
    72  
    73  	pathByteSize := 32
    74  	withForest(t, pathByteSize, 10, func(t *testing.T, f *mtrie.Forest) {
    75  
    76  		path1 := testutils.PathByUint16(0)
    77  		payload1 := testutils.LightPayload('A', 'a')
    78  
    79  		path2 := testutils.PathByUint16(1)
    80  		payload2 := testutils.LightPayload('B', 'b')
    81  
    82  		paths := []ledger.Path{path1, path2}
    83  		payloads := []*ledger.Payload{payload1, payload2}
    84  
    85  		u := &ledger.TrieUpdate{RootHash: f.GetEmptyRootHash(), Paths: paths, Payloads: payloads}
    86  		rootHash, err := f.Update(u)
    87  		require.NoError(t, err, "error updating trie")
    88  
    89  		r := &ledger.TrieRead{RootHash: rootHash, Paths: paths}
    90  		bp, err := f.Proofs(r)
    91  		require.NoError(t, err, "error getting batch proof")
    92  
    93  		psmt, err := NewPSMT(rootHash, bp)
    94  		require.NoError(t, err, "error building partial trie")
    95  		ensureRootHash(t, rootHash, psmt)
    96  
    97  		t.Run("non-existent key", func(t *testing.T) {
    98  			path3 := testutils.PathByUint16(2)
    99  			path4 := testutils.PathByUint16(4)
   100  
   101  			nonExistentPaths := []ledger.Path{path3, path4}
   102  			retPayloads, err := psmt.Get(nonExistentPaths)
   103  			require.Nil(t, retPayloads)
   104  
   105  			e, ok := err.(*ErrMissingPath)
   106  			require.True(t, ok)
   107  			assert.Equal(t, 2, len(e.Paths))
   108  			require.Equal(t, path3, e.Paths[0])
   109  			require.Equal(t, path4, e.Paths[1])
   110  		})
   111  
   112  		t.Run("existent key", func(t *testing.T) {
   113  			retPayloads, err := psmt.Get(paths)
   114  			require.NoError(t, err)
   115  			require.Equal(t, len(paths), len(retPayloads))
   116  			require.Equal(t, payload1, retPayloads[0])
   117  			require.Equal(t, payload2, retPayloads[1])
   118  		})
   119  
   120  		t.Run("mix of existent and non-existent keys", func(t *testing.T) {
   121  			path3 := testutils.PathByUint16(2)
   122  			path4 := testutils.PathByUint16(4)
   123  
   124  			retPayloads, err := psmt.Get([]ledger.Path{path1, path2, path3, path4})
   125  			require.Nil(t, retPayloads)
   126  
   127  			e, ok := err.(*ErrMissingPath)
   128  			require.True(t, ok)
   129  			assert.Equal(t, 2, len(e.Paths))
   130  			require.Equal(t, path3, e.Paths[0])
   131  			require.Equal(t, path4, e.Paths[1])
   132  		})
   133  	})
   134  }
   135  
   136  // TestPartialTrieGetSinglePayload gets single payload from existent/non-existent path.
   137  func TestPartialTrieGetSinglePayload(t *testing.T) {
   138  
   139  	pathByteSize := 32
   140  	withForest(t, pathByteSize, 10, func(t *testing.T, f *mtrie.Forest) {
   141  
   142  		path1 := testutils.PathByUint16(0)
   143  		payload1 := testutils.LightPayload('A', 'a')
   144  
   145  		path2 := testutils.PathByUint16(1)
   146  		payload2 := testutils.LightPayload('B', 'b')
   147  
   148  		paths := []ledger.Path{path1, path2}
   149  		payloads := []*ledger.Payload{payload1, payload2}
   150  
   151  		u := &ledger.TrieUpdate{RootHash: f.GetEmptyRootHash(), Paths: paths, Payloads: payloads}
   152  		rootHash, err := f.Update(u)
   153  		require.NoError(t, err, "error updating trie")
   154  
   155  		r := &ledger.TrieRead{RootHash: rootHash, Paths: paths}
   156  		bp, err := f.Proofs(r)
   157  		require.NoError(t, err, "error getting batch proof")
   158  
   159  		psmt, err := NewPSMT(rootHash, bp)
   160  		require.NoError(t, err, "error building partial trie")
   161  		ensureRootHash(t, rootHash, psmt)
   162  
   163  		retPayload, err := psmt.GetSinglePayload(path1)
   164  		require.NoError(t, err)
   165  		require.Equal(t, payload1, retPayload)
   166  
   167  		retPayload, err = psmt.GetSinglePayload(path2)
   168  		require.NoError(t, err)
   169  		require.Equal(t, payload2, retPayload)
   170  
   171  		path3 := testutils.PathByUint16(2)
   172  
   173  		retPayload, err = psmt.GetSinglePayload(path3)
   174  		require.Nil(t, retPayload)
   175  
   176  		var errMissingPath *ErrMissingPath
   177  		require.ErrorAs(t, err, &errMissingPath)
   178  		missingPath := err.(*ErrMissingPath)
   179  		require.Equal(t, 1, len(missingPath.Paths))
   180  		require.Equal(t, path3, missingPath.Paths[0])
   181  	})
   182  }
   183  
   184  func TestPartialTrieLeafUpdates(t *testing.T) {
   185  
   186  	pathByteSize := 32
   187  	withForest(t, pathByteSize, 10, func(t *testing.T, f *mtrie.Forest) {
   188  
   189  		path1 := testutils.PathByUint16(0)
   190  		payload1 := testutils.LightPayload('A', 'a')
   191  		updatedPayload1 := testutils.LightPayload('B', 'b')
   192  
   193  		path2 := testutils.PathByUint16(1)
   194  		payload2 := testutils.LightPayload('C', 'c')
   195  		updatedPayload2 := testutils.LightPayload('D', 'd')
   196  
   197  		path3 := testutils.PathByUint16(2)
   198  		payload3 := testutils.LightPayload('E', 'e')
   199  
   200  		paths := []ledger.Path{path1, path2}
   201  		payloads := []*ledger.Payload{payload1, payload2}
   202  
   203  		u := &ledger.TrieUpdate{RootHash: f.GetEmptyRootHash(), Paths: paths, Payloads: payloads}
   204  		rootHash, err := f.Update(u)
   205  		require.NoError(t, err, "error updating trie")
   206  
   207  		r := &ledger.TrieRead{RootHash: rootHash, Paths: paths}
   208  		bp, err := f.Proofs(r)
   209  		require.NoError(t, err, "error getting batch proof")
   210  
   211  		psmt, err := NewPSMT(rootHash, bp)
   212  		require.NoError(t, err, "error building partial trie")
   213  		ensureRootHash(t, rootHash, psmt)
   214  
   215  		payloads = []*ledger.Payload{updatedPayload1, updatedPayload2}
   216  		rootHash, err = f.Update(&ledger.TrieUpdate{RootHash: rootHash, Paths: paths, Payloads: payloads})
   217  		require.NoError(t, err, "error updating trie")
   218  
   219  		_, err = psmt.Update(paths, payloads)
   220  		require.NoError(t, err, "error updating psmt")
   221  		ensureRootHash(t, rootHash, psmt)
   222  
   223  		// Update on non-existent leafs
   224  		_, err = psmt.Update([]ledger.Path{path3}, []*ledger.Payload{payload3})
   225  		missingPathErr, ok := err.(*ErrMissingPath)
   226  		require.True(t, ok)
   227  		require.Equal(t, 1, len(missingPathErr.Paths))
   228  		require.Equal(t, path3, missingPathErr.Paths[0])
   229  	})
   230  
   231  }
   232  
   233  func TestPartialTrieMiddleBranching(t *testing.T) {
   234  
   235  	pathByteSize := 32
   236  	withForest(t, pathByteSize, 10, func(t *testing.T, f *mtrie.Forest) {
   237  
   238  		path1 := testutils.PathByUint16(0)
   239  		payload1 := testutils.LightPayload('A', 'a')
   240  		updatedPayload1 := testutils.LightPayload('B', 'b')
   241  
   242  		path2 := testutils.PathByUint16(2)
   243  		payload2 := testutils.LightPayload('C', 'c')
   244  		updatedPayload2 := testutils.LightPayload('D', 'd')
   245  
   246  		path3 := testutils.PathByUint16(8)
   247  		payload3 := testutils.LightPayload('E', 'e')
   248  		updatedPayload3 := testutils.LightPayload('F', 'f')
   249  
   250  		paths := []ledger.Path{path1, path2, path3}
   251  		payloads := []*ledger.Payload{payload1, payload2, payload3}
   252  
   253  		rootHash := f.GetEmptyRootHash()
   254  		bp, err := f.Proofs(&ledger.TrieRead{RootHash: rootHash, Paths: paths})
   255  		require.NoError(t, err, "error getting batch proof")
   256  
   257  		psmt, err := NewPSMT(rootHash, bp)
   258  		require.NoError(t, err, "error building partial trie")
   259  		ensureRootHash(t, f.GetEmptyRootHash(), psmt)
   260  
   261  		// first update
   262  		rootHash, err = f.Update(&ledger.TrieUpdate{RootHash: rootHash, Paths: paths, Payloads: payloads})
   263  		require.NoError(t, err, "error updating trie")
   264  
   265  		_, err = psmt.Update(paths, payloads)
   266  		require.NoError(t, err, "error updating psmt")
   267  		ensureRootHash(t, rootHash, psmt)
   268  
   269  		// second update
   270  		payloads = []*ledger.Payload{updatedPayload1, updatedPayload2, updatedPayload3}
   271  		rootHash, err = f.Update(&ledger.TrieUpdate{RootHash: rootHash, Paths: paths, Payloads: payloads})
   272  		require.NoError(t, err, "error updating trie")
   273  
   274  		_, err = psmt.Update(paths, payloads)
   275  		require.NoError(t, err, "error updating psmt")
   276  		ensureRootHash(t, rootHash, psmt)
   277  	})
   278  
   279  }
   280  
   281  func TestPartialTrieRootUpdates(t *testing.T) {
   282  
   283  	pathByteSize := 32
   284  	withForest(t, pathByteSize, 10, func(t *testing.T, f *mtrie.Forest) {
   285  
   286  		path1 := testutils.PathByUint16(0)
   287  		payload1 := testutils.LightPayload('A', 'a')
   288  		updatedPayload1 := testutils.LightPayload('B', 'b')
   289  		//  10000....0
   290  		path2 := testutils.PathByUint16(32768)
   291  		payload2 := testutils.LightPayload('C', 'c')
   292  		updatedPayload2 := testutils.LightPayload('D', 'd')
   293  
   294  		paths := []ledger.Path{path1, path2}
   295  		payloads := []*ledger.Payload{payload1, payload2}
   296  
   297  		rootHash := f.GetEmptyRootHash()
   298  		bp, err := f.Proofs(&ledger.TrieRead{RootHash: rootHash, Paths: paths})
   299  		require.NoError(t, err, "error getting batch proof")
   300  
   301  		psmt, err := NewPSMT(rootHash, bp)
   302  		require.NoError(t, err, "error building partial trie")
   303  		ensureRootHash(t, rootHash, psmt)
   304  
   305  		// first update
   306  		rootHash, err = f.Update(&ledger.TrieUpdate{RootHash: rootHash, Paths: paths, Payloads: payloads})
   307  		require.NoError(t, err, "error updating trie")
   308  
   309  		pRootHash, err := psmt.Update(paths, payloads)
   310  		require.NoError(t, err, "error updating psmt")
   311  		assert.Equal(t, rootHash, pRootHash, "rootNode hash doesn't match [after update]")
   312  
   313  		// second update
   314  		payloads = []*ledger.Payload{updatedPayload1, updatedPayload2}
   315  		rootHash, err = f.Update(&ledger.TrieUpdate{RootHash: rootHash, Paths: paths, Payloads: payloads})
   316  		require.NoError(t, err, "error updating trie")
   317  
   318  		pRootHash, err = psmt.Update(paths, payloads)
   319  		require.NoError(t, err, "error updating psmt")
   320  		assert.Equal(t, rootHash, pRootHash, "rootNode hash doesn't match [after second update]")
   321  	})
   322  
   323  }
   324  
   325  func TestMixProof(t *testing.T) {
   326  	pathByteSize := 32
   327  	withForest(t, pathByteSize, 10, func(t *testing.T, f *mtrie.Forest) {
   328  
   329  		path1 := testutils.PathByUint16(0)
   330  		payload1 := testutils.LightPayload('A', 'a')
   331  
   332  		path2 := testutils.PathByUint16(2)
   333  		updatedPayload2 := testutils.LightPayload('D', 'd')
   334  
   335  		path3 := testutils.PathByUint16(8)
   336  		payload3 := testutils.LightPayload('E', 'e')
   337  
   338  		paths := []ledger.Path{path1, path3}
   339  		payloads := []*ledger.Payload{payload1, payload3}
   340  
   341  		rootHash := f.GetEmptyRootHash()
   342  		rootHash, err := f.Update(&ledger.TrieUpdate{RootHash: rootHash, Paths: paths, Payloads: payloads})
   343  		require.NoError(t, err, "error updating trie")
   344  
   345  		paths = []ledger.Path{path1, path2, path3}
   346  
   347  		bp, err := f.Proofs(&ledger.TrieRead{RootHash: rootHash, Paths: paths})
   348  		require.NoError(t, err, "error getting batch proof")
   349  
   350  		psmt, err := NewPSMT(rootHash, bp)
   351  		require.NoError(t, err, "error building partial trie")
   352  		ensureRootHash(t, rootHash, psmt)
   353  
   354  		paths = []ledger.Path{path2, path3}
   355  		payloads = []*ledger.Payload{updatedPayload2, updatedPayload2}
   356  
   357  		rootHash, err = f.Update(&ledger.TrieUpdate{RootHash: rootHash, Paths: paths, Payloads: payloads})
   358  		require.NoError(t, err, "error updating trie")
   359  
   360  		pRootHash, err := psmt.Update(paths, payloads)
   361  		require.NoError(t, err, "error updating partial trie")
   362  		ensureRootHash(t, rootHash, psmt)
   363  		assert.Equal(t, rootHash, pRootHash, "root2 hash doesn't match [%x] != [%x]", rootHash, pRootHash)
   364  	})
   365  
   366  }
   367  
   368  func TestRandomProofs(t *testing.T) {
   369  	pathByteSize := 32 // key size of 16 bits
   370  	minPayloadSize := 2
   371  	maxPayloadSize := 10
   372  	experimentRep := 20
   373  	for e := 0; e < experimentRep; e++ {
   374  		withForest(t, pathByteSize, experimentRep+1, func(t *testing.T, f *mtrie.Forest) {
   375  
   376  			// generate some random paths and payloads
   377  			numberOfPaths := rand.Intn(256) + 1
   378  			paths := testutils.RandomPaths(numberOfPaths)
   379  			payloads := testutils.RandomPayloads(numberOfPaths, minPayloadSize, maxPayloadSize)
   380  			// keep a subset as initial insert and keep the rest for reading default values
   381  			split := rand.Intn(numberOfPaths)
   382  			insertPaths := paths[:split]
   383  			insertPayloads := payloads[:split]
   384  
   385  			rootHash, err := f.Update(&ledger.TrieUpdate{RootHash: f.GetEmptyRootHash(), Paths: insertPaths, Payloads: insertPayloads})
   386  			require.NoError(t, err, "error updating trie")
   387  
   388  			// shuffle paths for read
   389  			rand.Shuffle(len(paths), func(i, j int) {
   390  				paths[i], paths[j] = paths[j], paths[i]
   391  				payloads[i], payloads[j] = payloads[j], payloads[i]
   392  			})
   393  
   394  			bp, err := f.Proofs(&ledger.TrieRead{RootHash: rootHash, Paths: paths})
   395  			require.NoError(t, err, "error getting batch proof")
   396  
   397  			psmt, err := NewPSMT(rootHash, bp)
   398  			require.NoError(t, err, "error building partial trie")
   399  			ensureRootHash(t, rootHash, psmt)
   400  
   401  			// select a subset of shuffled paths for random updates
   402  			split = rand.Intn(numberOfPaths)
   403  			updatePaths := paths[:split]
   404  			updatePayloads := payloads[:split]
   405  			// random updates
   406  			rand.Shuffle(len(updatePayloads), func(i, j int) {
   407  				updatePayloads[i], updatePayloads[j] = updatePayloads[j], updatePayloads[i]
   408  			})
   409  
   410  			rootHash2, err := f.Update(&ledger.TrieUpdate{RootHash: rootHash, Paths: updatePaths, Payloads: updatePayloads})
   411  			require.NoError(t, err, "error updating trie")
   412  
   413  			pRootHash2, err := psmt.Update(updatePaths, updatePayloads)
   414  			require.NoError(t, err, "error updating partial trie")
   415  			assert.Equal(t, pRootHash2, rootHash2, "root2 hash doesn't match [%x] != [%x]", rootHash2, pRootHash2)
   416  		})
   417  	}
   418  }
   419  
   420  // TODO add test for incompatible proofs [Byzantine milestone]
   421  // TODO add test key not exist [Byzantine milestone]
   422  
   423  func ensureRootHash(t *testing.T, expectedRootHash ledger.RootHash, psmt *PSMT) {
   424  	if expectedRootHash != ledger.RootHash(psmt.root.Hash()) {
   425  		t.Fatal("rootNode hash doesn't match")
   426  	}
   427  	if expectedRootHash != psmt.RootHash() {
   428  		t.Fatal("rootNode hash doesn't match")
   429  	}
   430  	if expectedRootHash != ledger.RootHash(psmt.root.forceComputeHash()) {
   431  		t.Fatal("rootNode hash doesn't match")
   432  	}
   433  }