github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/ledger/complete/mtrie/trie/trie_test.go (about)

     1  package trie_test
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"encoding/hex"
     7  	"math"
     8  	"sort"
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/require"
    12  	"gotest.tools/assert"
    13  
    14  	"github.com/onflow/flow-go/ledger"
    15  	"github.com/onflow/flow-go/ledger/common/bitutils"
    16  	"github.com/onflow/flow-go/ledger/common/hash"
    17  	"github.com/onflow/flow-go/ledger/common/testutils"
    18  	"github.com/onflow/flow-go/ledger/complete/mtrie/trie"
    19  	"github.com/onflow/flow-go/utils/unittest"
    20  )
    21  
    22  // TestEmptyTrie tests whether the root hash of an empty trie matches the formal specification.
    23  func Test_EmptyTrie(t *testing.T) {
    24  	// Make new Trie (independently of MForest):
    25  	emptyTrie := trie.NewEmptyMTrie()
    26  	rootHash := emptyTrie.RootHash()
    27  	require.Equal(t, ledger.GetDefaultHashForHeight(ledger.NodeMaxHeight), hash.Hash(rootHash))
    28  
    29  	// verify root hash
    30  	expectedRootHashHex := "568f4ec740fe3b5de88034cb7b1fbddb41548b068f31aebc8ae9189e429c5749"
    31  	require.Equal(t, expectedRootHashHex, hashToString(rootHash))
    32  
    33  	// check String() method does not panic:
    34  	_ = emptyTrie.String()
    35  }
    36  
    37  // Test_TrieWithLeftRegister tests whether the root hash of trie with only the left-most
    38  // register populated matches the formal specification.
    39  // The expected value is coming from a reference implementation in python and is hard-coded here.
    40  func Test_TrieWithLeftRegister(t *testing.T) {
    41  	// Make new Trie (independently of MForest):
    42  	emptyTrie := trie.NewEmptyMTrie()
    43  	path := testutils.PathByUint16LeftPadded(0)
    44  	payload := testutils.LightPayload(11, 12345)
    45  	leftPopulatedTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, []ledger.Path{path}, []ledger.Payload{*payload}, true)
    46  	require.NoError(t, err)
    47  	require.Equal(t, uint16(0), maxDepthTouched)
    48  	require.Equal(t, uint64(1), leftPopulatedTrie.AllocatedRegCount())
    49  	require.Equal(t, uint64(payload.Size()), leftPopulatedTrie.AllocatedRegSize())
    50  	expectedRootHashHex := "b30c99cc3e027a6ff463876c638041b1c55316ed935f1b3699e52a2c3e3eaaab"
    51  	require.Equal(t, expectedRootHashHex, hashToString(leftPopulatedTrie.RootHash()))
    52  }
    53  
    54  // Test_TrieWithRightRegister tests whether the root hash of trie with only the right-most
    55  // register populated matches the formal specification.
    56  // The expected value is coming from a reference implementation in python and is hard-coded here.
    57  func Test_TrieWithRightRegister(t *testing.T) {
    58  	// Make new Trie (independently of MForest):
    59  	emptyTrie := trie.NewEmptyMTrie()
    60  	// build a path with all 1s
    61  	var path ledger.Path
    62  	for i := 0; i < len(path); i++ {
    63  		path[i] = uint8(255)
    64  	}
    65  	payload := testutils.LightPayload(12346, 54321)
    66  	rightPopulatedTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, []ledger.Path{path}, []ledger.Payload{*payload}, true)
    67  	require.NoError(t, err)
    68  	require.Equal(t, uint16(0), maxDepthTouched)
    69  	require.Equal(t, uint64(1), rightPopulatedTrie.AllocatedRegCount())
    70  	require.Equal(t, uint64(payload.Size()), rightPopulatedTrie.AllocatedRegSize())
    71  	expectedRootHashHex := "4313d22bcabbf21b1cfb833d38f1921f06a91e7198a6672bc68fa24eaaa1a961"
    72  	require.Equal(t, expectedRootHashHex, hashToString(rightPopulatedTrie.RootHash()))
    73  }
    74  
    75  // Test_TrieWithMiddleRegister tests the root hash of trie holding only a single
    76  // allocated register somewhere in the middle.
    77  // The expected value is coming from a reference implementation in python and is hard-coded here.
    78  func Test_TrieWithMiddleRegister(t *testing.T) {
    79  	// Make new Trie (independently of MForest):
    80  	emptyTrie := trie.NewEmptyMTrie()
    81  
    82  	path := testutils.PathByUint16LeftPadded(56809)
    83  	payload := testutils.LightPayload(12346, 59656)
    84  	leftPopulatedTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, []ledger.Path{path}, []ledger.Payload{*payload}, true)
    85  	require.Equal(t, uint16(0), maxDepthTouched)
    86  	require.Equal(t, uint64(1), leftPopulatedTrie.AllocatedRegCount())
    87  	require.Equal(t, uint64(payload.Size()), leftPopulatedTrie.AllocatedRegSize())
    88  	require.NoError(t, err)
    89  	expectedRootHashHex := "4a29dad0b7ae091a1f035955e0c9aab0692b412f60ae83290b6290d4bf3eb296"
    90  	require.Equal(t, expectedRootHashHex, hashToString(leftPopulatedTrie.RootHash()))
    91  }
    92  
    93  // Test_TrieWithManyRegisters tests whether the root hash of a trie storing 12001 randomly selected registers
    94  // matches the formal specification.
    95  // The expected value is coming from a reference implementation in python and is hard-coded here.
    96  func Test_TrieWithManyRegisters(t *testing.T) {
    97  	// Make new Trie (independently of MForest):
    98  	emptyTrie := trie.NewEmptyMTrie()
    99  	// allocate single random register
   100  	rng := &LinearCongruentialGenerator{seed: 0}
   101  	paths, payloads := deduplicateWrites(sampleRandomRegisterWrites(rng, 12001))
   102  	var totalPayloadSize uint64
   103  	for _, p := range payloads {
   104  		totalPayloadSize += uint64(p.Size())
   105  	}
   106  	updatedTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths, payloads, true)
   107  	require.NoError(t, err)
   108  	require.Equal(t, uint16(255), maxDepthTouched)
   109  	require.Equal(t, uint64(12001), updatedTrie.AllocatedRegCount())
   110  	require.Equal(t, totalPayloadSize, updatedTrie.AllocatedRegSize())
   111  	expectedRootHashHex := "74f748dbe563bb5819d6c09a34362a048531fd9647b4b2ea0b6ff43f200198aa"
   112  	require.Equal(t, expectedRootHashHex, hashToString(updatedTrie.RootHash()))
   113  }
   114  
   115  // Test_FullTrie tests whether the root hash of a trie,
   116  // whose left-most 65536 registers are populated, matches the formal specification.
   117  // The expected value is coming from a reference implementation in python and is hard-coded here.
   118  func Test_FullTrie(t *testing.T) {
   119  	// Make new Trie (independently of MForest):
   120  	emptyTrie := trie.NewEmptyMTrie()
   121  
   122  	// allocate 65536 left-most registers
   123  	numberRegisters := 65536
   124  	rng := &LinearCongruentialGenerator{seed: 0}
   125  	paths := make([]ledger.Path, 0, numberRegisters)
   126  	payloads := make([]ledger.Payload, 0, numberRegisters)
   127  	var totalPayloadSize uint64
   128  	for i := 0; i < numberRegisters; i++ {
   129  		paths = append(paths, testutils.PathByUint16LeftPadded(uint16(i)))
   130  		temp := rng.next()
   131  		payload := testutils.LightPayload(temp, temp)
   132  		payloads = append(payloads, *payload)
   133  		totalPayloadSize += uint64(payload.Size())
   134  	}
   135  	updatedTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths, payloads, true)
   136  	require.NoError(t, err)
   137  	require.Equal(t, uint16(256), maxDepthTouched)
   138  	require.Equal(t, uint64(numberRegisters), updatedTrie.AllocatedRegCount())
   139  	require.Equal(t, totalPayloadSize, updatedTrie.AllocatedRegSize())
   140  	expectedRootHashHex := "6b3a48d672744f5586c571c47eae32d7a4a3549c1d4fa51a0acfd7b720471de9"
   141  	require.Equal(t, expectedRootHashHex, hashToString(updatedTrie.RootHash()))
   142  }
   143  
   144  // TestUpdateTrie tests whether iteratively updating a Trie matches the formal specification.
   145  // The expected root hashes are coming from a reference implementation in python and is hard-coded here.
   146  func Test_UpdateTrie(t *testing.T) {
   147  	expectedRootHashes := []string{
   148  		"08db9aeed2b9fcc66b63204a26a4c28652e44e3035bd87ba0ed632a227b3f6dd",
   149  		"2f4b0f490fa05e5b3bbd43176e367c3e9b64cdb710e45d4508fff11759d7a08e",
   150  		"668811792995cd960e7e343540a360682ac375f7ec5533f774c464cd6b34adc9",
   151  		"169c145eaeda2038a0e409068a12cb26bde5e890115ad1ef624f422007fb2d2a",
   152  		"8f87b503a706d9eaf50873030e0e627850c841cc0cf382187b81ba26cec57588",
   153  		"faacc057336e10e13ff6f5667aefc3ac9d9d390b34ee50391a6f7f305dfdf761",
   154  		"049e035735a13fee09a3c36a7f567daf05baee419ac90ade538108492d80b279",
   155  		"bb8340a9772ab6d6aa4862b23c8bb830da226cdf6f6c26f1e1e850077be600af",
   156  		"8b9b7eb5c489bf4aeffd86d3a215dc045856094d0abe5cf7b4cc3f835d499168",
   157  		"6514743e986f20fcf22a02e50ba352a5bfde50fe949b57b990aeb863cfcd81d1",
   158  		"33c3d386e1c7c707f727fdeb65c52117537d175da9ab3f60a0a576301d20756e",
   159  		"09df0bc6eee9d0f76df05d19b2ac550cde8c4294cd6eafaa1332718bd62e912f",
   160  		"8b1fccbf7d1eca093441305ebff72d3f12b8b7cce5b4f89d6f464fc5df83b0d3",
   161  		"0830e2d015742e284c56075050e94d3ff9618a46f28aa9066379f012e45c05fc",
   162  		"9d95255bb75dddc317deda4e45223aa4a5ac02eaa537dc9e602d6f03fa26d626",
   163  		"74f748dbe563bb5819d6c09a34362a048531fd9647b4b2ea0b6ff43f200198aa",
   164  		"c06903580432a27dee461e9022a6546cb4ddec2f8598c48429e9ba7a96a892da",
   165  		"a117f94e9cc6114e19b7639eaa630304788979cf92037736bbeb23ed1504638a",
   166  		"d382c97020371d8788d4c27971a89f1617f9bbf21c49c922f1b683cc36a4646c",
   167  		"ce633e9ca6329d6984c37a46e0a479bb1841674c2db00970dacfe035882d4aba",
   168  	}
   169  
   170  	// Make new Trie (independently of MForest):
   171  	emptyTrie := trie.NewEmptyMTrie()
   172  
   173  	// allocate single random register
   174  	rng := &LinearCongruentialGenerator{seed: 0}
   175  	path := testutils.PathByUint16LeftPadded(rng.next())
   176  	temp := rng.next()
   177  	payload := testutils.LightPayload(temp, temp)
   178  	updatedTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, []ledger.Path{path}, []ledger.Payload{*payload}, true)
   179  	require.NoError(t, err)
   180  	require.Equal(t, uint16(0), maxDepthTouched)
   181  	require.Equal(t, uint64(1), updatedTrie.AllocatedRegCount())
   182  	require.Equal(t, uint64(payload.Size()), updatedTrie.AllocatedRegSize())
   183  	expectedRootHashHex := "08db9aeed2b9fcc66b63204a26a4c28652e44e3035bd87ba0ed632a227b3f6dd"
   184  	require.Equal(t, expectedRootHashHex, hashToString(updatedTrie.RootHash()))
   185  
   186  	var paths []ledger.Path
   187  	var payloads []ledger.Payload
   188  	parentTrieRegCount := updatedTrie.AllocatedRegCount()
   189  	parentTrieRegSize := updatedTrie.AllocatedRegSize()
   190  	for r := 0; r < 20; r++ {
   191  		paths, payloads = deduplicateWrites(sampleRandomRegisterWrites(rng, r*100))
   192  		var totalPayloadSize uint64
   193  		for _, p := range payloads {
   194  			totalPayloadSize += uint64(p.Size())
   195  		}
   196  		updatedTrie, maxDepthTouched, err = trie.NewTrieWithUpdatedRegisters(updatedTrie, paths, payloads, true)
   197  		require.NoError(t, err)
   198  		switch r {
   199  		case 0:
   200  			require.Equal(t, uint16(0), maxDepthTouched)
   201  		case 1:
   202  			require.Equal(t, uint16(254), maxDepthTouched)
   203  		default:
   204  			require.Equal(t, uint16(255), maxDepthTouched)
   205  		}
   206  		require.Equal(t, parentTrieRegCount+uint64(len(payloads)), updatedTrie.AllocatedRegCount())
   207  		require.Equal(t, parentTrieRegSize+totalPayloadSize, updatedTrie.AllocatedRegSize())
   208  		require.Equal(t, expectedRootHashes[r], hashToString(updatedTrie.RootHash()))
   209  
   210  		parentTrieRegCount = updatedTrie.AllocatedRegCount()
   211  		parentTrieRegSize = updatedTrie.AllocatedRegSize()
   212  	}
   213  	// update with the same registers with the same values
   214  	newTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(updatedTrie, paths, payloads, true)
   215  	require.NoError(t, err)
   216  	require.Equal(t, uint16(255), maxDepthTouched)
   217  	require.Equal(t, updatedTrie.AllocatedRegCount(), newTrie.AllocatedRegCount())
   218  	require.Equal(t, updatedTrie.AllocatedRegSize(), newTrie.AllocatedRegSize())
   219  	require.Equal(t, expectedRootHashes[19], hashToString(updatedTrie.RootHash()))
   220  	// check the root node pointers are equal
   221  	require.True(t, updatedTrie.RootNode() == newTrie.RootNode())
   222  }
   223  
   224  // Test_UnallocateRegisters tests whether unallocating registers matches the formal specification.
   225  // Unallocating here means, to set the stored register value to an empty byte slice.
   226  // The expected value is coming from a reference implementation in python and is hard-coded here.
   227  func Test_UnallocateRegisters(t *testing.T) {
   228  	rng := &LinearCongruentialGenerator{seed: 0}
   229  	emptyTrie := trie.NewEmptyMTrie()
   230  
   231  	// we first draw 99 random key-value pairs that will be first allocated and later unallocated:
   232  	paths1, payloads1 := deduplicateWrites(sampleRandomRegisterWrites(rng, 99))
   233  	var totalPayloadSize1 uint64
   234  	for _, p := range payloads1 {
   235  		totalPayloadSize1 += uint64(p.Size())
   236  	}
   237  	updatedTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths1, payloads1, true)
   238  	require.NoError(t, err)
   239  	require.Equal(t, uint16(254), maxDepthTouched)
   240  	require.Equal(t, uint64(len(payloads1)), updatedTrie.AllocatedRegCount())
   241  	require.Equal(t, totalPayloadSize1, updatedTrie.AllocatedRegSize())
   242  
   243  	// we then write an additional 117 registers
   244  	paths2, payloads2 := deduplicateWrites(sampleRandomRegisterWrites(rng, 117))
   245  	var totalPayloadSize2 uint64
   246  	for _, p := range payloads2 {
   247  		totalPayloadSize2 += uint64(p.Size())
   248  	}
   249  	updatedTrie, maxDepthTouched, err = trie.NewTrieWithUpdatedRegisters(updatedTrie, paths2, payloads2, true)
   250  	require.Equal(t, uint16(254), maxDepthTouched)
   251  	require.Equal(t, uint64(len(payloads1)+len(payloads2)), updatedTrie.AllocatedRegCount())
   252  	require.Equal(t, totalPayloadSize1+totalPayloadSize2, updatedTrie.AllocatedRegSize())
   253  	require.NoError(t, err)
   254  
   255  	// and now we override the first 99 registers with default values, i.e. unallocate them
   256  	payloads0 := make([]ledger.Payload, len(payloads1))
   257  	updatedTrie, maxDepthTouched, err = trie.NewTrieWithUpdatedRegisters(updatedTrie, paths1, payloads0, true)
   258  	require.Equal(t, uint16(254), maxDepthTouched)
   259  	require.Equal(t, uint64(len(payloads2)), updatedTrie.AllocatedRegCount())
   260  	require.Equal(t, totalPayloadSize2, updatedTrie.AllocatedRegSize())
   261  	require.NoError(t, err)
   262  
   263  	// this should be identical to the first 99 registers never been written
   264  	expectedRootHashHex := "d81e27a93f2bef058395f70e00fb5d3c8e426e22b3391d048b34017e1ecb483e"
   265  	comparisonTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths2, payloads2, true)
   266  	require.NoError(t, err)
   267  	require.Equal(t, uint16(254), maxDepthTouched)
   268  	require.Equal(t, uint64(len(payloads2)), comparisonTrie.AllocatedRegCount())
   269  	require.Equal(t, totalPayloadSize2, comparisonTrie.AllocatedRegSize())
   270  	require.Equal(t, expectedRootHashHex, hashToString(comparisonTrie.RootHash()))
   271  	require.Equal(t, expectedRootHashHex, hashToString(updatedTrie.RootHash()))
   272  }
   273  
   274  // simple Linear congruential RNG
   275  // https://en.wikipedia.org/wiki/Linear_congruential_generator
   276  // with configuration for 16bit output used by Microsoft Visual Basic 6 and earlier
   277  type LinearCongruentialGenerator struct {
   278  	seed uint64
   279  }
   280  
   281  func (rng *LinearCongruentialGenerator) next() uint16 {
   282  	rng.seed = (rng.seed*1140671485 + 12820163) % 65536
   283  	return uint16(rng.seed)
   284  }
   285  
   286  // sampleRandomRegisterWrites generates path-payload tuples for `number` randomly selected registers;
   287  // caution: registers might repeat
   288  func sampleRandomRegisterWrites(rng *LinearCongruentialGenerator, number int) ([]ledger.Path, []ledger.Payload) {
   289  	paths := make([]ledger.Path, 0, number)
   290  	payloads := make([]ledger.Payload, 0, number)
   291  	for i := 0; i < number; i++ {
   292  		path := testutils.PathByUint16LeftPadded(rng.next())
   293  		paths = append(paths, path)
   294  		t := rng.next()
   295  		payload := testutils.LightPayload(t, t)
   296  		payloads = append(payloads, *payload)
   297  	}
   298  	return paths, payloads
   299  }
   300  
   301  // sampleRandomRegisterWritesWithPrefix generates path-payload tuples for `number` randomly selected registers;
   302  // each path is starting with the specified `prefix` and is filled to the full length with random bytes
   303  // caution: register paths might repeat
   304  func sampleRandomRegisterWritesWithPrefix(rng *LinearCongruentialGenerator, number int, prefix []byte) ([]ledger.Path, []ledger.Payload) {
   305  	prefixLen := len(prefix)
   306  	if prefixLen >= hash.HashLen {
   307  		panic("prefix must be shorter than full path length, so there is some space left for random path segment")
   308  	}
   309  
   310  	paths := make([]ledger.Path, 0, number)
   311  	payloads := make([]ledger.Payload, 0, number)
   312  	nextRandomBytes := make([]byte, 2)
   313  	nextRandomByteIndex := 2 // index of next unused byte in nextRandomBytes; if value is >= 2, we need to generate new random bytes
   314  	for i := 0; i < number; i++ {
   315  		var p ledger.Path
   316  		copy(p[:prefixLen], prefix)
   317  		for b := prefixLen; b < hash.HashLen; b++ {
   318  			if nextRandomByteIndex >= 2 {
   319  				// pre-generate next 2 bytes
   320  				binary.BigEndian.PutUint16(nextRandomBytes, rng.next())
   321  				nextRandomByteIndex = 0
   322  			}
   323  			p[b] = nextRandomBytes[nextRandomByteIndex]
   324  			nextRandomByteIndex++
   325  		}
   326  		paths = append(paths, p)
   327  
   328  		t := rng.next()
   329  		payload := testutils.LightPayload(t, t)
   330  		payloads = append(payloads, *payload)
   331  	}
   332  	return paths, payloads
   333  }
   334  
   335  // deduplicateWrites retains only the last register write
   336  func deduplicateWrites(paths []ledger.Path, payloads []ledger.Payload) ([]ledger.Path, []ledger.Payload) {
   337  	payloadMapping := make(map[ledger.Path]int)
   338  	if len(paths) != len(payloads) {
   339  		panic("size mismatch (paths and payloads)")
   340  	}
   341  	for i, path := range paths {
   342  		// we override the latest in the slice
   343  		payloadMapping[path] = i
   344  	}
   345  	dedupedPaths := make([]ledger.Path, 0, len(payloadMapping))
   346  	dedupedPayloads := make([]ledger.Payload, 0, len(payloadMapping))
   347  	for path := range payloadMapping {
   348  		dedupedPaths = append(dedupedPaths, path)
   349  		dedupedPayloads = append(dedupedPayloads, payloads[payloadMapping[path]])
   350  	}
   351  	return dedupedPaths, dedupedPayloads
   352  }
   353  
   354  func TestSplitByPath(t *testing.T) {
   355  	rand := unittest.GetPRG(t)
   356  
   357  	const pathsNumber = 100
   358  	const redundantPaths = 10
   359  	const pathsSize = 32
   360  	randomIndex := rand.Intn(pathsSize)
   361  
   362  	// create path slice with redundant paths
   363  	paths := make([]ledger.Path, 0, pathsNumber)
   364  	for i := 0; i < pathsNumber-redundantPaths; i++ {
   365  		var p ledger.Path
   366  		_, err := rand.Read(p[:])
   367  		require.NoError(t, err)
   368  		paths = append(paths, p)
   369  	}
   370  	for i := 0; i < redundantPaths; i++ {
   371  		paths = append(paths, paths[i])
   372  	}
   373  
   374  	// save a sorted paths copy for later check
   375  	sortedPaths := make([]ledger.Path, len(paths))
   376  	copy(sortedPaths, paths)
   377  	sort.Slice(sortedPaths, func(i, j int) bool {
   378  		return bytes.Compare(sortedPaths[i][:], sortedPaths[j][:]) < 0
   379  	})
   380  
   381  	// split paths
   382  	index := trie.SplitPaths(paths, randomIndex)
   383  
   384  	// check correctness
   385  	for i := 0; i < index; i++ {
   386  		assert.Equal(t, bitutils.ReadBit(paths[i][:], randomIndex), 0)
   387  	}
   388  	for i := index; i < len(paths); i++ {
   389  		assert.Equal(t, bitutils.ReadBit(paths[i][:], randomIndex), 1)
   390  	}
   391  
   392  	// check the multi-set didn't change
   393  	sort.Slice(paths, func(i, j int) bool {
   394  		return bytes.Compare(paths[i][:], paths[j][:]) < 0
   395  	})
   396  	for i := index; i < len(paths); i++ {
   397  		assert.Equal(t, paths[i], sortedPaths[i])
   398  	}
   399  }
   400  
   401  // Test_DifferentiateEmptyVsLeaf tests correct behaviour for a very specific edge case for pruning:
   402  //   - By convention, a node in the trie is a leaf if both children are nil.
   403  //   - Therefore, we consider a completely unallocated subtrie also as a potential leaf.
   404  //
   405  // An edge case can now arise when unallocating a previously allocated leaf (see vertex '■' in the illustration below):
   406  //
   407  //   - Before the update, both children of the leaf are nil (because it is a leaf)
   408  //   - After the update-algorithm updated the sub-Trie with root ■, both children of the updated vertex are
   409  //     also nil. But the sub-trie has now changed: the register previously represented by ■ is now gone.
   410  //
   411  // This case must be explicitly handled by the update algorithm:
   412  //
   413  //   - (i)  If the vertex is an interim node, i.e. it had at least one child, it is legal to re-use the vertex if neither
   414  //     of its child-subtries were affected by the update.
   415  //   - (ii) If the vertex is a leaf, only checking that neither child-subtries were affected by the update is insufficient.
   416  //     This is because the register the leaf represents might itself be affected by the update.
   417  //
   418  // Condition (ii) is particularly subtle, if there are register updates in the subtrie of the leaf:
   419  //
   420  //   - From an API perspective, it is a legal operation to set an unallocated register to nil (essentially a no-op).
   421  //
   422  //   - Though, the Trie-update algorithm only realizes that the register is already unallocated, once it traverses
   423  //     into the respective sub-trie. When bubbling up from the recursion, nothing has changed in the children of ■
   424  //     but the vertex ■ itself has changed from an allocated leaf register to an unallocated register.
   425  func Test_DifferentiateEmptyVsLeaf(t *testing.T) {
   426  	//           ⋮  commonPrefix29bytes 101 ....
   427  	//           o
   428  	//          / \
   429  	//        /    \
   430  	//       /      \
   431  	//      ■        o
   432  	//    Left      / \
   433  	//  SubTrie     ⋮  ⋮
   434  	//             Right
   435  	//            SubTrie
   436  	// Left Sub-Trie (■) is a single compactified leaf
   437  	// Right Sub-Trie contains multiple (18) allocated registers
   438  
   439  	commonPrefix29bytes := "a0115ce6d49ffe0c9c3d8382826bbec896a9555e4c7720c45b558e7a9e"
   440  	leftSubTriePrefix, _ := hex.DecodeString(commonPrefix29bytes + "0")  // in total 30 bytes
   441  	rightSubTriePrefix, _ := hex.DecodeString(commonPrefix29bytes + "1") // in total 30 bytes
   442  
   443  	rng := &LinearCongruentialGenerator{seed: 0}
   444  	leftSubTriePath, leftSubTriePayload := sampleRandomRegisterWritesWithPrefix(rng, 1, leftSubTriePrefix)
   445  	rightSubTriePath, rightSubTriePayload := deduplicateWrites(sampleRandomRegisterWritesWithPrefix(rng, 18, rightSubTriePrefix))
   446  
   447  	// initialize Trie to the depicted state
   448  	paths := append(leftSubTriePath, rightSubTriePath...)
   449  	payloads := append(leftSubTriePayload, rightSubTriePayload...)
   450  	var leftSubTriePayloadSize, rightSubTriePayloadSize uint64
   451  	for _, p := range leftSubTriePayload {
   452  		leftSubTriePayloadSize += uint64(p.Size())
   453  	}
   454  	for _, p := range rightSubTriePayload {
   455  		rightSubTriePayloadSize += uint64(p.Size())
   456  	}
   457  	startTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(trie.NewEmptyMTrie(), paths, payloads, true)
   458  	require.NoError(t, err)
   459  	require.Equal(t, uint16(241), maxDepthTouched)
   460  	require.Equal(t, uint64(len(payloads)), startTrie.AllocatedRegCount())
   461  	require.Equal(t, leftSubTriePayloadSize+rightSubTriePayloadSize, startTrie.AllocatedRegSize())
   462  	expectedRootHashHex := "8cf6659db0af7626ab0991e2a49019353d549aa4a8c4be1b33e8953d1a9b7fdd"
   463  	require.Equal(t, expectedRootHashHex, hashToString(startTrie.RootHash()))
   464  
   465  	// Register update:
   466  	//  * de-allocate the compactified leaf (■), i.e. set its payload to nil.
   467  	//  * also set a previously already unallocated register to nil as well
   468  	unallocatedRegister := leftSubTriePath[0]            // copy path to leaf and modify it (next line)
   469  	unallocatedRegister[len(unallocatedRegister)-1] ^= 1 // path differs only in the last byte, i.e. register is also in the left Sub-Trie
   470  	updatedPaths := append(leftSubTriePath, unallocatedRegister)
   471  	updatedPayloads := []ledger.Payload{*ledger.EmptyPayload(), *ledger.EmptyPayload()}
   472  	updatedTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(startTrie, updatedPaths, updatedPayloads, true)
   473  	require.Equal(t, uint16(256), maxDepthTouched)
   474  	require.Equal(t, uint64(len(rightSubTriePayload)), updatedTrie.AllocatedRegCount())
   475  	require.Equal(t, rightSubTriePayloadSize, updatedTrie.AllocatedRegSize())
   476  	require.NoError(t, err)
   477  
   478  	// The updated trie should equal to a trie containing only the right sub-Trie
   479  	expectedUpdatedRootHashHex := "576e12a7ef5c760d5cc808ce50e9297919b21b87656b0cc0d9fe8a1a589cf42c"
   480  	require.Equal(t, expectedUpdatedRootHashHex, hashToString(updatedTrie.RootHash()))
   481  	referenceTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(trie.NewEmptyMTrie(), rightSubTriePath, rightSubTriePayload, true)
   482  	require.NoError(t, err)
   483  	require.Equal(t, uint16(241), maxDepthTouched)
   484  	require.Equal(t, uint64(len(rightSubTriePayload)), referenceTrie.AllocatedRegCount())
   485  	require.Equal(t, rightSubTriePayloadSize, referenceTrie.AllocatedRegSize())
   486  	require.Equal(t, expectedUpdatedRootHashHex, hashToString(referenceTrie.RootHash()))
   487  }
   488  
   489  func Test_Pruning(t *testing.T) {
   490  	rand := unittest.GetPRG(t)
   491  	emptyTrie := trie.NewEmptyMTrie()
   492  
   493  	path1 := testutils.PathByUint16(1 << 12)       // 000100...
   494  	path2 := testutils.PathByUint16(1 << 13)       // 001000...
   495  	path4 := testutils.PathByUint16(1<<14 + 1<<13) // 01100...
   496  	path6 := testutils.PathByUint16(1 << 15)       // 1000...
   497  
   498  	payload1 := testutils.LightPayload(2, 1)
   499  	payload2 := testutils.LightPayload(2, 2)
   500  	payload4 := testutils.LightPayload(2, 4)
   501  	payload6 := testutils.LightPayload(2, 6)
   502  	emptyPayload := ledger.EmptyPayload()
   503  
   504  	paths := []ledger.Path{path1, path2, path4, path6}
   505  	payloads := []ledger.Payload{*payload1, *payload2, *payload4, *payload6}
   506  
   507  	var totalPayloadSize uint64
   508  	for _, p := range payloads {
   509  		totalPayloadSize += uint64(p.Size())
   510  	}
   511  
   512  	//                    n7
   513  	//                   / \
   514  	//                 /     \
   515  	//             n5         n6 (path6/payload6) // 1000
   516  	//            /  \
   517  	//          /      \
   518  	//         /         \
   519  	//        n3          n4 (path4/payload4) // 01100...
   520  	//      /     \
   521  	//    /          \
   522  	//  /              \
   523  	// n1 (path1,       n2 (path2)
   524  	//     payload1)        /payload2)
   525  
   526  	baseTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths, payloads, true)
   527  	require.NoError(t, err)
   528  	require.Equal(t, uint16(3), maxDepthTouched)
   529  	require.Equal(t, uint64(len(payloads)), baseTrie.AllocatedRegCount())
   530  	require.Equal(t, totalPayloadSize, baseTrie.AllocatedRegSize())
   531  
   532  	t.Run("leaf update with pruning test", func(t *testing.T) {
   533  		expectedRegCount := baseTrie.AllocatedRegCount() - 1
   534  		expectedRegSize := baseTrie.AllocatedRegSize() - uint64(payload1.Size())
   535  
   536  		trie1, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(baseTrie, []ledger.Path{path1}, []ledger.Payload{*emptyPayload}, false)
   537  		require.NoError(t, err)
   538  		require.Equal(t, uint16(3), maxDepthTouched)
   539  		require.Equal(t, expectedRegCount, trie1.AllocatedRegCount())
   540  		require.Equal(t, expectedRegSize, trie1.AllocatedRegSize())
   541  
   542  		trie1withpruning, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(baseTrie, []ledger.Path{path1}, []ledger.Payload{*emptyPayload}, true)
   543  		require.NoError(t, err)
   544  		require.Equal(t, uint16(3), maxDepthTouched)
   545  		require.Equal(t, expectedRegCount, trie1withpruning.AllocatedRegCount())
   546  		require.Equal(t, expectedRegSize, trie1withpruning.AllocatedRegSize())
   547  		require.True(t, trie1withpruning.RootNode().VerifyCachedHash())
   548  
   549  		// after pruning
   550  		//                    n7
   551  		//                   / \
   552  		//                 /     \
   553  		//             n5         n6 (path6/payload6) // 1000
   554  		//            /  \
   555  		//          /      \
   556  		//         /         \
   557  		//     n3 (path2       n4 (path4
   558  		//        /payload2)      /payload4) // 01100...
   559  		require.Equal(t, trie1.RootHash(), trie1withpruning.RootHash())
   560  	})
   561  
   562  	t.Run("leaf update with two level pruning test", func(t *testing.T) {
   563  		expectedRegCount := baseTrie.AllocatedRegCount() - 1
   564  		expectedRegSize := baseTrie.AllocatedRegSize() - uint64(payload4.Size())
   565  
   566  		// setting path4 to zero from baseTrie
   567  		trie2, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(baseTrie, []ledger.Path{path4}, []ledger.Payload{*emptyPayload}, false)
   568  		require.NoError(t, err)
   569  		require.Equal(t, uint16(2), maxDepthTouched)
   570  		require.Equal(t, expectedRegCount, trie2.AllocatedRegCount())
   571  		require.Equal(t, expectedRegSize, trie2.AllocatedRegSize())
   572  
   573  		// pruning is not activated here because n3 is not a leaf node
   574  		trie2withpruning, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(baseTrie, []ledger.Path{path4}, []ledger.Payload{*emptyPayload}, true)
   575  		require.NoError(t, err)
   576  		require.Equal(t, uint16(2), maxDepthTouched)
   577  		require.Equal(t, expectedRegCount, trie2withpruning.AllocatedRegCount())
   578  		require.Equal(t, expectedRegSize, trie2withpruning.AllocatedRegSize())
   579  		require.True(t, trie2withpruning.RootNode().VerifyCachedHash())
   580  
   581  		require.Equal(t, trie2.RootHash(), trie2withpruning.RootHash())
   582  
   583  		// now setting path2 to zero should do the pruning for two levels
   584  		expectedRegCount -= 1
   585  		expectedRegSize -= uint64(payload2.Size())
   586  
   587  		trie22, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(trie2, []ledger.Path{path2}, []ledger.Payload{*emptyPayload}, false)
   588  		require.NoError(t, err)
   589  		require.Equal(t, uint16(3), maxDepthTouched)
   590  		require.Equal(t, expectedRegCount, trie22.AllocatedRegCount())
   591  		require.Equal(t, expectedRegSize, trie22.AllocatedRegSize())
   592  
   593  		trie22withpruning, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(trie2withpruning, []ledger.Path{path2}, []ledger.Payload{*emptyPayload}, true)
   594  		require.NoError(t, err)
   595  		require.Equal(t, uint16(3), maxDepthTouched)
   596  		require.Equal(t, expectedRegCount, trie22withpruning.AllocatedRegCount())
   597  		require.Equal(t, expectedRegSize, trie22withpruning.AllocatedRegSize())
   598  
   599  		// after pruning
   600  		//                     n7
   601  		//                   /   \
   602  		//                 /       \
   603  		//             n5 (path1,   n6 (path6/payload6) // 1000
   604  		//                 /payload1)
   605  
   606  		require.Equal(t, trie22.RootHash(), trie22withpruning.RootHash())
   607  		require.True(t, trie22withpruning.RootNode().VerifyCachedHash())
   608  
   609  	})
   610  
   611  	t.Run("several updates at the same time", func(t *testing.T) {
   612  		// setting path4 to zero from baseTrie
   613  		trie3, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(baseTrie, []ledger.Path{path2, path4, path6}, []ledger.Payload{*emptyPayload, *emptyPayload, *emptyPayload}, false)
   614  		require.NoError(t, err)
   615  		require.Equal(t, uint16(3), maxDepthTouched)
   616  		require.Equal(t, uint64(1), trie3.AllocatedRegCount())
   617  		require.Equal(t, uint64(payload1.Size()), trie3.AllocatedRegSize())
   618  
   619  		// this should prune two levels
   620  		trie3withpruning, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(baseTrie, []ledger.Path{path2, path4, path6}, []ledger.Payload{*emptyPayload, *emptyPayload, *emptyPayload}, true)
   621  		require.NoError(t, err)
   622  		require.Equal(t, uint16(3), maxDepthTouched)
   623  		require.Equal(t, uint64(1), trie3withpruning.AllocatedRegCount())
   624  		require.Equal(t, uint64(payload1.Size()), trie3withpruning.AllocatedRegSize())
   625  
   626  		// after pruning
   627  		//       n7  (path1/payload1)
   628  		require.Equal(t, trie3.RootHash(), trie3withpruning.RootHash())
   629  		require.True(t, trie3withpruning.RootNode().VerifyCachedHash())
   630  	})
   631  
   632  	t.Run("smoke testing trie pruning", func(t *testing.T) {
   633  		unittest.SkipUnless(t, unittest.TEST_LONG_RUNNING, "skipping trie pruning smoke testing as its not needed to always run")
   634  
   635  		numberOfSteps := 1000
   636  		numberOfUpdates := 750
   637  		numberOfRemovals := 750
   638  
   639  		var err error
   640  		activeTrie := trie.NewEmptyMTrie()
   641  		activeTrieWithPruning := trie.NewEmptyMTrie()
   642  		allPaths := make(map[ledger.Path]ledger.Payload)
   643  		var maxDepthTouched, maxDepthTouchedWithPruning uint16
   644  		var parentTrieRegCount, parentTrieRegSize uint64
   645  
   646  		for step := 0; step < numberOfSteps; step++ {
   647  
   648  			updatePaths := make([]ledger.Path, 0)
   649  			updatePayloads := make([]ledger.Payload, 0)
   650  
   651  			var expectedRegCountDelta int64
   652  			var expectedRegSizeDelta int64
   653  
   654  			for i := 0; i < numberOfUpdates; {
   655  				var path ledger.Path
   656  				_, err := rand.Read(path[:])
   657  				require.NoError(t, err)
   658  				// deduplicate
   659  				if _, found := allPaths[path]; !found {
   660  					payload := testutils.RandomPayload(1, 100)
   661  					updatePaths = append(updatePaths, path)
   662  					updatePayloads = append(updatePayloads, *payload)
   663  					expectedRegCountDelta++
   664  					expectedRegSizeDelta += int64(payload.Size())
   665  					i++
   666  				}
   667  			}
   668  
   669  			i := 0
   670  			samplesNeeded := int(math.Min(float64(numberOfRemovals), float64(len(allPaths))))
   671  			for p, pl := range allPaths {
   672  				updatePaths = append(updatePaths, p)
   673  				updatePayloads = append(updatePayloads, *emptyPayload)
   674  				expectedRegCountDelta--
   675  				expectedRegSizeDelta -= int64(pl.Size())
   676  				delete(allPaths, p)
   677  				i++
   678  				if i > samplesNeeded {
   679  					break
   680  				}
   681  			}
   682  
   683  			// only set it for the updates
   684  			for i := 0; i < numberOfUpdates; i++ {
   685  				allPaths[updatePaths[i]] = updatePayloads[i]
   686  			}
   687  
   688  			activeTrie, maxDepthTouched, err = trie.NewTrieWithUpdatedRegisters(activeTrie, updatePaths, updatePayloads, false)
   689  			require.NoError(t, err)
   690  			require.Equal(t, uint64(int64(parentTrieRegCount)+expectedRegCountDelta), activeTrie.AllocatedRegCount())
   691  			require.Equal(t, uint64(int64(parentTrieRegSize)+expectedRegSizeDelta), activeTrie.AllocatedRegSize())
   692  
   693  			activeTrieWithPruning, maxDepthTouchedWithPruning, err = trie.NewTrieWithUpdatedRegisters(activeTrieWithPruning, updatePaths, updatePayloads, true)
   694  			require.NoError(t, err)
   695  			require.True(t, maxDepthTouched >= maxDepthTouchedWithPruning)
   696  			require.Equal(t, uint64(int64(parentTrieRegCount)+expectedRegCountDelta), activeTrieWithPruning.AllocatedRegCount())
   697  			require.Equal(t, uint64(int64(parentTrieRegSize)+expectedRegSizeDelta), activeTrieWithPruning.AllocatedRegSize())
   698  
   699  			require.Equal(t, activeTrie.RootHash(), activeTrieWithPruning.RootHash())
   700  
   701  			parentTrieRegCount = activeTrie.AllocatedRegCount()
   702  			parentTrieRegSize = activeTrie.AllocatedRegSize()
   703  
   704  			// fetch all values and compare
   705  			queryPaths := make([]ledger.Path, 0)
   706  			for path := range allPaths {
   707  				queryPaths = append(queryPaths, path)
   708  			}
   709  
   710  			payloads := activeTrie.UnsafeRead(queryPaths)
   711  			for i, pp := range payloads {
   712  				expectedPayload := allPaths[queryPaths[i]]
   713  				require.True(t, pp.Equals(&expectedPayload))
   714  			}
   715  
   716  			payloads = activeTrieWithPruning.UnsafeRead(queryPaths)
   717  			for i, pp := range payloads {
   718  				expectedPayload := allPaths[queryPaths[i]]
   719  				require.True(t, pp.Equals(&expectedPayload))
   720  			}
   721  
   722  		}
   723  	})
   724  }
   725  
   726  func hashToString(hash ledger.RootHash) string {
   727  	return hex.EncodeToString(hash[:])
   728  }
   729  
   730  // TestValueSizes tests value sizes of existent and non-existent paths for trie of different layouts.
   731  func TestValueSizes(t *testing.T) {
   732  
   733  	emptyTrie := trie.NewEmptyMTrie()
   734  
   735  	// Test value sizes for non-existent path in empty trie
   736  	t.Run("empty trie", func(t *testing.T) {
   737  		path := testutils.PathByUint16LeftPadded(0)
   738  		pathsToGetValueSize := []ledger.Path{path}
   739  		sizes := emptyTrie.UnsafeValueSizes(pathsToGetValueSize)
   740  		require.Equal(t, len(pathsToGetValueSize), len(sizes))
   741  		require.Equal(t, 0, sizes[0])
   742  	})
   743  
   744  	// Test value sizes for a mix of existent and non-existent paths
   745  	// in trie with compact leaf as root node.
   746  	t.Run("compact leaf as root", func(t *testing.T) {
   747  		path1 := testutils.PathByUint16LeftPadded(0)
   748  		payload1 := testutils.RandomPayload(1, 100)
   749  
   750  		path2 := testutils.PathByUint16LeftPadded(1) // This path will not be inserted into trie.
   751  
   752  		paths := []ledger.Path{path1}
   753  		payloads := []ledger.Payload{*payload1}
   754  
   755  		newTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths, payloads, true)
   756  		require.NoError(t, err)
   757  		require.Equal(t, uint16(0), maxDepthTouched)
   758  
   759  		pathsToGetValueSize := []ledger.Path{path1, path2}
   760  
   761  		sizes := newTrie.UnsafeValueSizes(pathsToGetValueSize)
   762  		require.Equal(t, len(pathsToGetValueSize), len(sizes))
   763  		require.Equal(t, payload1.Value().Size(), sizes[0])
   764  		require.Equal(t, 0, sizes[1])
   765  	})
   766  
   767  	// Test value sizes for a mix of existent and non-existent paths in partial trie.
   768  	t.Run("partial trie", func(t *testing.T) {
   769  		path1 := testutils.PathByUint16(1 << 12) // 000100...
   770  		path2 := testutils.PathByUint16(1 << 13) // 001000...
   771  
   772  		payload1 := testutils.RandomPayload(1, 100)
   773  		payload2 := testutils.RandomPayload(1, 100)
   774  
   775  		paths := []ledger.Path{path1, path2}
   776  		payloads := []ledger.Payload{*payload1, *payload2}
   777  
   778  		// Create a new trie with 2 leaf nodes (n1 and n2) at height 253.
   779  		newTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths, payloads, true)
   780  		require.NoError(t, err)
   781  		require.Equal(t, uint16(3), maxDepthTouched)
   782  
   783  		//                  n5
   784  		//                 /
   785  		//                /
   786  		//              n4
   787  		//             /
   788  		//            /
   789  		//           n3
   790  		//        /     \
   791  		//      /         \
   792  		//   n1 (path1/     n2 (path2/
   793  		//       payload1)      payload2)
   794  		//
   795  
   796  		// Populate pathsToGetValueSize with all possible paths for the first 4 bits.
   797  		pathsToGetValueSize := make([]ledger.Path, 16)
   798  		for i := 0; i < 16; i++ {
   799  			pathsToGetValueSize[i] = testutils.PathByUint16(uint16(i << 12))
   800  		}
   801  
   802  		// Test value sizes for a mix of existent and non-existent paths.
   803  		sizes := newTrie.UnsafeValueSizes(pathsToGetValueSize)
   804  		require.Equal(t, len(pathsToGetValueSize), len(sizes))
   805  		for i, p := range pathsToGetValueSize {
   806  			switch p {
   807  			case path1:
   808  				require.Equal(t, payload1.Value().Size(), sizes[i])
   809  			case path2:
   810  				require.Equal(t, payload2.Value().Size(), sizes[i])
   811  			default:
   812  				// Test value size for non-existent path
   813  				require.Equal(t, 0, sizes[i])
   814  			}
   815  		}
   816  
   817  		// Test value size for a single existent path
   818  		pathsToGetValueSize = []ledger.Path{path1}
   819  		sizes = newTrie.UnsafeValueSizes(pathsToGetValueSize)
   820  		require.Equal(t, len(pathsToGetValueSize), len(sizes))
   821  		require.Equal(t, payload1.Value().Size(), sizes[0])
   822  
   823  		// Test value size for a single non-existent path
   824  		pathsToGetValueSize = []ledger.Path{testutils.PathByUint16(3 << 12)}
   825  		sizes = newTrie.UnsafeValueSizes(pathsToGetValueSize)
   826  		require.Equal(t, len(pathsToGetValueSize), len(sizes))
   827  		require.Equal(t, 0, sizes[0])
   828  	})
   829  }
   830  
   831  // TestValueSizesWithDuplicatePaths tests value sizes of duplicate existent and non-existent paths.
   832  func TestValueSizesWithDuplicatePaths(t *testing.T) {
   833  	path1 := testutils.PathByUint16(0)
   834  	path2 := testutils.PathByUint16(1)
   835  	path3 := testutils.PathByUint16(2) // This path will not be inserted into trie.
   836  
   837  	payload1 := testutils.RandomPayload(1, 100)
   838  	payload2 := testutils.RandomPayload(1, 100)
   839  
   840  	paths := []ledger.Path{path1, path2}
   841  	payloads := []ledger.Payload{*payload1, *payload2}
   842  
   843  	emptyTrie := trie.NewEmptyMTrie()
   844  	newTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths, payloads, true)
   845  	require.NoError(t, err)
   846  	require.Equal(t, uint16(16), maxDepthTouched)
   847  
   848  	// pathsToGetValueSize is a mix of duplicate existent and nonexistent paths.
   849  	pathsToGetValueSize := []ledger.Path{
   850  		path1, path2, path3,
   851  		path1, path2, path3,
   852  	}
   853  
   854  	sizes := newTrie.UnsafeValueSizes(pathsToGetValueSize)
   855  	require.Equal(t, len(pathsToGetValueSize), len(sizes))
   856  	for i, p := range pathsToGetValueSize {
   857  		switch p {
   858  		case path1:
   859  			require.Equal(t, payload1.Value().Size(), sizes[i])
   860  		case path2:
   861  			require.Equal(t, payload2.Value().Size(), sizes[i])
   862  		default:
   863  			// Test payload size for non-existent path
   864  			require.Equal(t, 0, sizes[i])
   865  		}
   866  	}
   867  }
   868  
   869  // TestTrieAllocatedRegCountRegSize tests allocated register count and register size for updated trie.
   870  // It tests the following updates with prune flag set to true:
   871  //   - update empty trie with new paths and payloads
   872  //   - update trie with existing paths and updated payload
   873  //   - update trie with new paths and empty payloads
   874  //   - update trie with existing path and empty payload one by one until trie is empty
   875  //
   876  // It also tests the following updates with prune flag set to false:
   877  //   - update trie with existing path and empty payload one by one until trie is empty
   878  //   - update trie with removed paths and empty payloads
   879  //   - update trie with removed paths and non-empty payloads
   880  func TestTrieAllocatedRegCountRegSize(t *testing.T) {
   881  
   882  	rng := &LinearCongruentialGenerator{seed: 0}
   883  
   884  	// Allocate 255 registers
   885  	numberRegisters := 255
   886  	paths := make([]ledger.Path, numberRegisters)
   887  	payloads := make([]ledger.Payload, numberRegisters)
   888  	var totalPayloadSize uint64
   889  	for i := 0; i < numberRegisters; i++ {
   890  		var p ledger.Path
   891  		p[0] = byte(i)
   892  
   893  		payload := testutils.LightPayload(rng.next(), rng.next())
   894  		paths[i] = p
   895  		payloads[i] = *payload
   896  
   897  		totalPayloadSize += uint64(payload.Size())
   898  	}
   899  
   900  	// Update trie with registers to test reg count and size with new registers.
   901  	updatedTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(trie.NewEmptyMTrie(), paths, payloads, true)
   902  	require.NoError(t, err)
   903  	require.True(t, maxDepthTouched <= 256)
   904  	require.Equal(t, uint64(len(payloads)), updatedTrie.AllocatedRegCount())
   905  	require.Equal(t, totalPayloadSize, updatedTrie.AllocatedRegSize())
   906  
   907  	// Update trie with existing paths and updated payloads
   908  	// to test reg count and size with updated registers
   909  	// (old payload size > 0 and new payload size > 0).
   910  	for i := 0; i < len(payloads); i += 2 {
   911  		newPayload := testutils.LightPayload(rng.next(), rng.next())
   912  		oldPayload := payloads[i]
   913  		payloads[i] = *newPayload
   914  		totalPayloadSize += uint64(newPayload.Size()) - uint64(oldPayload.Size())
   915  	}
   916  
   917  	updatedTrie, maxDepthTouched, err = trie.NewTrieWithUpdatedRegisters(updatedTrie, paths, payloads, true)
   918  	require.NoError(t, err)
   919  	require.True(t, maxDepthTouched <= 256)
   920  	require.Equal(t, uint64(len(payloads)), updatedTrie.AllocatedRegCount())
   921  	require.Equal(t, totalPayloadSize, updatedTrie.AllocatedRegSize())
   922  
   923  	rootHash := updatedTrie.RootHash()
   924  
   925  	// Update trie with new paths and empty payloads
   926  	// to test reg count and size with new empty registers.
   927  	newPaths := []ledger.Path{}
   928  	newPayloads := []ledger.Payload{}
   929  	for i := 0; i < len(paths); i++ {
   930  		oldPath := paths[i]
   931  
   932  		path1, _ := ledger.ToPath(oldPath[:])
   933  		path1[1] = 1
   934  		payload1 := *ledger.NewPayload(
   935  			ledger.Key{KeyParts: []ledger.KeyPart{{Type: 0, Value: []byte{0x00, byte(i)}}}},
   936  			nil,
   937  		)
   938  
   939  		path2, _ := ledger.ToPath(oldPath[:])
   940  		path2[1] = 2
   941  		payload2 := ledger.EmptyPayload()
   942  
   943  		newPaths = append(newPaths, oldPath, path1, path2)
   944  		newPayloads = append(newPayloads, payloads[i], payload1, *payload2)
   945  	}
   946  
   947  	updatedTrie, maxDepthTouched, err = trie.NewTrieWithUpdatedRegisters(updatedTrie, newPaths, newPayloads, true)
   948  	require.NoError(t, err)
   949  	require.Equal(t, rootHash, updatedTrie.RootHash())
   950  	require.True(t, maxDepthTouched <= 256)
   951  	require.Equal(t, uint64(len(payloads)), updatedTrie.AllocatedRegCount())
   952  	require.Equal(t, totalPayloadSize, updatedTrie.AllocatedRegSize())
   953  
   954  	t.Run("pruning", func(t *testing.T) {
   955  		expectedRegCount := uint64(len(payloads))
   956  		expectedRegSize := totalPayloadSize
   957  
   958  		updatedTrieWithPruning := updatedTrie
   959  
   960  		// Remove register one by one to test reg count and size with empty registers
   961  		// (old payload size > 0 and new payload size == 0)
   962  		for i := 0; i < len(paths); i++ {
   963  			newPaths := []ledger.Path{paths[i]}
   964  			newPayloads := []ledger.Payload{*ledger.EmptyPayload()}
   965  
   966  			expectedRegCount--
   967  			expectedRegSize -= uint64(payloads[i].Size())
   968  
   969  			updatedTrieWithPruning, maxDepthTouched, err = trie.NewTrieWithUpdatedRegisters(updatedTrieWithPruning, newPaths, newPayloads, true)
   970  			require.NoError(t, err)
   971  			require.True(t, maxDepthTouched <= 256)
   972  			require.Equal(t, expectedRegCount, updatedTrieWithPruning.AllocatedRegCount())
   973  			require.Equal(t, expectedRegSize, updatedTrieWithPruning.AllocatedRegSize())
   974  		}
   975  
   976  		// After all registered are removed, reg count and size should be 0.
   977  		require.Equal(t, trie.EmptyTrieRootHash(), updatedTrieWithPruning.RootHash())
   978  		require.Equal(t, uint64(0), updatedTrieWithPruning.AllocatedRegSize())
   979  		require.Equal(t, uint64(0), updatedTrieWithPruning.AllocatedRegSize())
   980  	})
   981  
   982  	t.Run("no pruning", func(t *testing.T) {
   983  		expectedRegCount := uint64(len(payloads))
   984  		expectedRegSize := totalPayloadSize
   985  
   986  		updatedTrieNoPruning := updatedTrie
   987  
   988  		// Remove register one by one to test reg count and size with empty registers
   989  		// (old payload size > 0 and new payload size == 0)
   990  		for i := 0; i < len(paths); i++ {
   991  			newPaths := []ledger.Path{paths[i]}
   992  			newPayloads := []ledger.Payload{*ledger.EmptyPayload()}
   993  
   994  			expectedRegCount--
   995  			expectedRegSize -= uint64(payloads[i].Size())
   996  
   997  			updatedTrieNoPruning, maxDepthTouched, err = trie.NewTrieWithUpdatedRegisters(updatedTrieNoPruning, newPaths, newPayloads, false)
   998  			require.NoError(t, err)
   999  			require.True(t, maxDepthTouched <= 256)
  1000  			require.Equal(t, expectedRegCount, updatedTrieNoPruning.AllocatedRegCount())
  1001  			require.Equal(t, expectedRegSize, updatedTrieNoPruning.AllocatedRegSize())
  1002  		}
  1003  
  1004  		// After all registered are removed, reg count and size should be 0.
  1005  		require.Equal(t, trie.EmptyTrieRootHash(), updatedTrieNoPruning.RootHash())
  1006  		require.Equal(t, uint64(0), updatedTrieNoPruning.AllocatedRegCount())
  1007  		require.Equal(t, uint64(0), updatedTrieNoPruning.AllocatedRegSize())
  1008  
  1009  		// Update with removed paths and empty payloads
  1010  		// (old payload size == 0 and new payload size == 0)
  1011  		newPayloads := make([]ledger.Payload, len(paths))
  1012  		for i := 0; i < len(paths); i++ {
  1013  			newPayloads[i] = *ledger.EmptyPayload()
  1014  		}
  1015  
  1016  		updatedTrieNoPruning, maxDepthTouched, err = trie.NewTrieWithUpdatedRegisters(updatedTrieNoPruning, paths, newPayloads, false)
  1017  		require.NoError(t, err)
  1018  		require.True(t, maxDepthTouched <= 256)
  1019  		require.Equal(t, trie.EmptyTrieRootHash(), updatedTrieNoPruning.RootHash())
  1020  		require.Equal(t, uint64(0), updatedTrieNoPruning.AllocatedRegCount())
  1021  		require.Equal(t, uint64(0), updatedTrieNoPruning.AllocatedRegSize())
  1022  
  1023  		// Update with removed paths and non-empty payloads
  1024  		// (old payload size == 0 and new payload size > 0)
  1025  		updatedTrieNoPruning, maxDepthTouched, err = trie.NewTrieWithUpdatedRegisters(updatedTrieNoPruning, paths, payloads, false)
  1026  		require.NoError(t, err)
  1027  		require.Equal(t, rootHash, updatedTrie.RootHash())
  1028  		require.True(t, maxDepthTouched <= 256)
  1029  		require.Equal(t, uint64(len(payloads)), updatedTrieNoPruning.AllocatedRegCount())
  1030  		require.Equal(t, totalPayloadSize, updatedTrieNoPruning.AllocatedRegSize())
  1031  	})
  1032  }
  1033  
  1034  // TestTrieAllocatedRegCountRegSizeWithMixedPruneFlag tests allocated register count and size
  1035  // for updated trie with mixed pruning flag.
  1036  // It tests the following updates:
  1037  //   - step 1 : update empty trie with new paths and payloads (255 allocated registers)
  1038  //   - step 2 : remove a payload without pruning (254 allocated registers)
  1039  //   - step 3a: remove previously removed payload with pruning (254 allocated registers)
  1040  //   - step 3b: update trie from step 2 with a new payload (sibling of removed payload)
  1041  //     with pruning (255 allocated registers)
  1042  func TestTrieAllocatedRegCountRegSizeWithMixedPruneFlag(t *testing.T) {
  1043  	rng := &LinearCongruentialGenerator{seed: 0}
  1044  
  1045  	// Allocate 255 registers
  1046  	numberRegisters := 255
  1047  	paths := make([]ledger.Path, numberRegisters)
  1048  	payloads := make([]ledger.Payload, numberRegisters)
  1049  	var totalPayloadSize uint64
  1050  	for i := 0; i < numberRegisters; i++ {
  1051  		var p ledger.Path
  1052  		p[0] = byte(i)
  1053  
  1054  		payload := testutils.LightPayload(rng.next(), rng.next())
  1055  		paths[i] = p
  1056  		payloads[i] = *payload
  1057  
  1058  		totalPayloadSize += uint64(payload.Size())
  1059  	}
  1060  
  1061  	expectedAllocatedRegCount := uint64(len(payloads))
  1062  	expectedAllocatedRegSize := totalPayloadSize
  1063  
  1064  	// Update trie with registers to test reg count and size with new registers.
  1065  	baseTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(trie.NewEmptyMTrie(), paths, payloads, true)
  1066  	require.NoError(t, err)
  1067  	require.True(t, maxDepthTouched <= 256)
  1068  	require.Equal(t, expectedAllocatedRegCount, baseTrie.AllocatedRegCount())
  1069  	require.Equal(t, expectedAllocatedRegSize, baseTrie.AllocatedRegSize())
  1070  
  1071  	// Remove one payload without pruning
  1072  	expectedAllocatedRegCount--
  1073  	expectedAllocatedRegSize -= uint64(payloads[0].Size())
  1074  
  1075  	removePaths := []ledger.Path{paths[0]}
  1076  	removePayloads := []ledger.Payload{*ledger.EmptyPayload()}
  1077  	unprunedTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(baseTrie, removePaths, removePayloads, false)
  1078  	require.NoError(t, err)
  1079  	require.True(t, maxDepthTouched <= 256)
  1080  	require.Equal(t, expectedAllocatedRegCount, unprunedTrie.AllocatedRegCount())
  1081  	require.Equal(t, expectedAllocatedRegSize, unprunedTrie.AllocatedRegSize())
  1082  
  1083  	// Remove the same payload (no affect) from unprunedTrie with pruning
  1084  	// expected reg count and reg size remain unchanged.
  1085  	updatedTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(unprunedTrie, removePaths, removePayloads, true)
  1086  	require.NoError(t, err)
  1087  	require.True(t, maxDepthTouched <= 256)
  1088  	require.Equal(t, expectedAllocatedRegCount, updatedTrie.AllocatedRegCount())
  1089  	require.Equal(t, expectedAllocatedRegSize, updatedTrie.AllocatedRegSize())
  1090  
  1091  	// Add sibling of removed path from unprunedTrie with pruning
  1092  	newPath := paths[0]
  1093  	bitutils.SetBit(newPath[:], ledger.PathLen*8-1)
  1094  	newPaths := []ledger.Path{newPath}
  1095  	newPayloads := []ledger.Payload{*testutils.LightPayload(rng.next(), rng.next())}
  1096  
  1097  	// expected reg count is incremented and expected reg size is increase by new payload size.
  1098  	expectedAllocatedRegCount++
  1099  	expectedAllocatedRegSize += uint64(newPayloads[0].Size())
  1100  
  1101  	updatedTrie, maxDepthTouched, err = trie.NewTrieWithUpdatedRegisters(unprunedTrie, newPaths, newPayloads, true)
  1102  	require.NoError(t, err)
  1103  	require.True(t, maxDepthTouched <= 256)
  1104  	require.Equal(t, expectedAllocatedRegCount, updatedTrie.AllocatedRegCount())
  1105  	require.Equal(t, expectedAllocatedRegSize, updatedTrie.AllocatedRegSize())
  1106  }
  1107  
  1108  // TestReadSinglePayload tests reading a single payload of existent/non-existent path for trie of different layouts.
  1109  func TestReadSinglePayload(t *testing.T) {
  1110  
  1111  	emptyTrie := trie.NewEmptyMTrie()
  1112  
  1113  	// Test reading payload in empty trie
  1114  	t.Run("empty trie", func(t *testing.T) {
  1115  		savedRootHash := emptyTrie.RootHash()
  1116  
  1117  		path := testutils.PathByUint16LeftPadded(0)
  1118  		payload := emptyTrie.ReadSinglePayload(path)
  1119  		require.True(t, payload.IsEmpty())
  1120  		require.Equal(t, savedRootHash, emptyTrie.RootHash())
  1121  	})
  1122  
  1123  	// Test reading payload for existent/non-existent path
  1124  	// in trie with compact leaf as root node.
  1125  	t.Run("compact leaf as root", func(t *testing.T) {
  1126  		path1 := testutils.PathByUint16LeftPadded(0)
  1127  		payload1 := testutils.RandomPayload(1, 100)
  1128  
  1129  		paths := []ledger.Path{path1}
  1130  		payloads := []ledger.Payload{*payload1}
  1131  
  1132  		newTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths, payloads, true)
  1133  		require.NoError(t, err)
  1134  		require.Equal(t, uint16(0), maxDepthTouched)
  1135  
  1136  		savedRootHash := newTrie.RootHash()
  1137  
  1138  		// Get payload for existent path path
  1139  		retPayload := newTrie.ReadSinglePayload(path1)
  1140  		require.Equal(t, payload1, retPayload)
  1141  		require.Equal(t, savedRootHash, newTrie.RootHash())
  1142  
  1143  		// Get payload for non-existent path
  1144  		path2 := testutils.PathByUint16LeftPadded(1)
  1145  		retPayload = newTrie.ReadSinglePayload(path2)
  1146  		require.True(t, retPayload.IsEmpty())
  1147  		require.Equal(t, savedRootHash, newTrie.RootHash())
  1148  	})
  1149  
  1150  	// Test reading payload for existent/non-existent path in an unpruned trie.
  1151  	t.Run("trie", func(t *testing.T) {
  1152  		path1 := testutils.PathByUint16(1 << 12) // 000100...
  1153  		path2 := testutils.PathByUint16(1 << 13) // 001000...
  1154  		path3 := testutils.PathByUint16(1 << 14) // 010000...
  1155  
  1156  		payload1 := testutils.RandomPayload(1, 100)
  1157  		payload2 := testutils.RandomPayload(1, 100)
  1158  		payload3 := ledger.EmptyPayload()
  1159  
  1160  		paths := []ledger.Path{path1, path2, path3}
  1161  		payloads := []ledger.Payload{*payload1, *payload2, *payload3}
  1162  
  1163  		// Create an unpruned trie with 3 leaf nodes (n1, n2, n3).
  1164  		newTrie, maxDepthTouched, err := trie.NewTrieWithUpdatedRegisters(emptyTrie, paths, payloads, false)
  1165  		require.NoError(t, err)
  1166  		require.Equal(t, uint16(3), maxDepthTouched)
  1167  
  1168  		savedRootHash := newTrie.RootHash()
  1169  
  1170  		//                  n5
  1171  		//                 /
  1172  		//                /
  1173  		//              n4
  1174  		//             /  \
  1175  		//            /    \
  1176  		//           n3      n3 (path3/
  1177  		//        /     \        payload3)
  1178  		//      /         \
  1179  		//   n1 (path1/     n2 (path2/
  1180  		//       payload1)      payload2)
  1181  		//
  1182  
  1183  		// Test reading payload for all possible paths for the first 4 bits.
  1184  		for i := 0; i < 16; i++ {
  1185  			path := testutils.PathByUint16(uint16(i << 12))
  1186  
  1187  			retPayload := newTrie.ReadSinglePayload(path)
  1188  			require.Equal(t, savedRootHash, newTrie.RootHash())
  1189  			switch path {
  1190  			case path1:
  1191  				require.Equal(t, payload1, retPayload)
  1192  			case path2:
  1193  				require.Equal(t, payload2, retPayload)
  1194  			default:
  1195  				require.True(t, retPayload.IsEmpty())
  1196  			}
  1197  		}
  1198  	})
  1199  }