github.com/koko1123/flow-go-1@v0.29.6/ledger/partial/ptrie/partialTrie_test.go (about)

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