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

     1  package node_test
     2  
     3  import (
     4  	"encoding/hex"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/require"
     8  
     9  	"github.com/onflow/flow-go/ledger"
    10  	"github.com/onflow/flow-go/ledger/common/hash"
    11  	"github.com/onflow/flow-go/ledger/common/testutils"
    12  	"github.com/onflow/flow-go/ledger/complete/mtrie/node"
    13  )
    14  
    15  // Test_ProperLeaf verifies that the hash value of a proper leaf (at height 0) is computed correctly
    16  func Test_ProperLeaf(t *testing.T) {
    17  	path := testutils.PathByUint16(56809)
    18  	payload := testutils.LightPayload(56810, 59656)
    19  	n := node.NewLeaf(path, payload, 0)
    20  	expectedRootHashHex := "0ee164bc69981088186b5ceeb666e90e8e11bb15a1427aa56f47a484aedf73b4"
    21  	require.Equal(t, expectedRootHashHex, hashToString(n.Hash()))
    22  	require.True(t, n.VerifyCachedHash())
    23  }
    24  
    25  // Test_CompactifiedLeaf verifies that the hash value of a compactified leaf (at height > 0) is computed correctly.
    26  // We test the hash at the lowest-possible height (1), for the leaf to be still compactified,
    27  // at an interim height (9) and the max possible height (256)
    28  func Test_CompactifiedLeaf(t *testing.T) {
    29  	path := testutils.PathByUint16(56809)
    30  	payload := testutils.LightPayload(56810, 59656)
    31  	n := node.NewLeaf(path, payload, 1)
    32  	expectedRootHashHex := "aa496f68adbbf43197f7e4b6ba1a63a47b9ce19b1587ca9ce587a7f29cad57d5"
    33  	require.Equal(t, expectedRootHashHex, hashToString(n.Hash()))
    34  
    35  	n = node.NewLeaf(path, payload, 9)
    36  	expectedRootHashHex = "606aa23fdc40443de85b75768b847f94ff1d726e0bafde037833fe27543bb988"
    37  	require.Equal(t, expectedRootHashHex, hashToString(n.Hash()))
    38  
    39  	n = node.NewLeaf(path, payload, 256)
    40  	expectedRootHashHex = "d2536303495a9325037d247cbb2b9be4d6cb3465986ea2c4481d8770ff16b6b0"
    41  	require.Equal(t, expectedRootHashHex, hashToString(n.Hash()))
    42  }
    43  
    44  // Test_InterimNodeWithoutChildren verifies that the hash value of an interim node without children is computed correctly.
    45  // We test the hash at the lowest-possible height (0), at an interim height (9) and (16)
    46  func Test_InterimNodeWithoutChildren(t *testing.T) {
    47  	n := node.NewInterimNode(0, nil, nil)
    48  	expectedRootHashHex := "18373b4b038cbbf37456c33941a7e346e752acd8fafa896933d4859002b62619"
    49  	require.Equal(t, expectedRootHashHex, hashToString(n.Hash()))
    50  	require.Equal(t, ledger.GetDefaultHashForHeight(0), n.Hash())
    51  
    52  	n = node.NewInterimNode(9, nil, nil)
    53  	expectedRootHashHex = "a37f98dbac56e315fbd4b9f9bc85fbd1b138ed4ae453b128c22c99401495af6d"
    54  	require.Equal(t, expectedRootHashHex, hashToString(n.Hash()))
    55  	require.Equal(t, ledger.GetDefaultHashForHeight(9), n.Hash())
    56  
    57  	n = node.NewInterimNode(16, nil, nil)
    58  	expectedRootHashHex = "6e24e2397f130d9d17bef32b19a77b8f5bcf03fb7e9e75fd89b8a455675d574a"
    59  	require.Equal(t, expectedRootHashHex, hashToString(n.Hash()))
    60  	require.Equal(t, ledger.GetDefaultHashForHeight(16), n.Hash())
    61  }
    62  
    63  // Test_InterimNodeWithOneChild verifies that the hash value of an interim node with
    64  // only one child (left or right) is computed correctly.
    65  func Test_InterimNodeWithOneChild(t *testing.T) {
    66  	path := testutils.PathByUint16(56809)
    67  	payload := testutils.LightPayload(56810, 59656)
    68  	c := node.NewLeaf(path, payload, 0)
    69  
    70  	n := node.NewInterimNode(1, c, nil)
    71  	expectedRootHashHex := "aa496f68adbbf43197f7e4b6ba1a63a47b9ce19b1587ca9ce587a7f29cad57d5"
    72  	require.Equal(t, expectedRootHashHex, hashToString(n.Hash()))
    73  
    74  	n = node.NewInterimNode(1, nil, c)
    75  	expectedRootHashHex = "9845f2c9e9c067ec6efba06ffb7c1be387b2a893ae979b1f6cb091bda1b7e12d"
    76  	require.Equal(t, expectedRootHashHex, hashToString(n.Hash()))
    77  }
    78  
    79  // Test_InterimNodeWithBothChildren verifies that the hash value of an interim node with
    80  // both children (left and right) is computed correctly.
    81  func Test_InterimNodeWithBothChildren(t *testing.T) {
    82  	leftPath := testutils.PathByUint16(56809)
    83  	leftPayload := testutils.LightPayload(56810, 59656)
    84  	leftChild := node.NewLeaf(leftPath, leftPayload, 0)
    85  
    86  	rightPath := testutils.PathByUint16(2)
    87  	rightPayload := testutils.LightPayload(11, 22)
    88  	rightChild := node.NewLeaf(rightPath, rightPayload, 0)
    89  
    90  	n := node.NewInterimNode(1, leftChild, rightChild)
    91  	expectedRootHashHex := "1e4754fb35ec011b6192e205de403c1031d8ce64bd3d1ff8f534a20595af90c3"
    92  	require.Equal(t, expectedRootHashHex, hashToString(n.Hash()))
    93  }
    94  
    95  func Test_AllPayloads(t *testing.T) {
    96  	path := testutils.PathByUint16(1)
    97  	payload := testutils.LightPayload(2, 3)
    98  	n1 := node.NewLeaf(path, payload, 0)
    99  	n2 := node.NewLeaf(path, payload, 0)
   100  	n3 := node.NewLeaf(path, payload, 1)
   101  	n4 := node.NewInterimNode(1, n1, n2)
   102  	n5 := node.NewInterimNode(2, n4, n3)
   103  	require.Equal(t, 3, len(n5.AllPayloads()))
   104  }
   105  
   106  func Test_VerifyCachedHash(t *testing.T) {
   107  	path := testutils.PathByUint16(1)
   108  	payload := testutils.LightPayload(2, 3)
   109  	n1 := node.NewLeaf(path, payload, 0)
   110  	n2 := node.NewLeaf(path, payload, 0)
   111  	n3 := node.NewLeaf(path, payload, 1)
   112  	n4 := node.NewInterimNode(1, n1, n2)
   113  	n5 := node.NewInterimNode(2, n4, n3)
   114  	require.True(t, n5.VerifyCachedHash())
   115  }
   116  
   117  // Test_Compactify_EmptySubtrie tests constructing an interim node
   118  // with pruning/compactification, where both children are empty. We expect
   119  // the compactified node to be nil, as it represents a completely empty subtrie
   120  func Test_Compactify_EmptySubtrie(t *testing.T) {
   121  	//      n3
   122  	//    /   \
   123  	// n1(-)  n2(-)
   124  	n1 := node.NewLeaf(testutils.PathByUint16LeftPadded(0), &ledger.Payload{}, 4)    // path: ...0000 0000
   125  	n2 := node.NewLeaf(testutils.PathByUint16LeftPadded(1<<4), &ledger.Payload{}, 4) // path: ...0001 0000
   126  
   127  	t.Run("both children empty", func(t *testing.T) {
   128  		require.Nil(t, node.NewInterimCompactifiedNode(5, n1, n2))
   129  	})
   130  
   131  	t.Run("one child nil and one child empty", func(t *testing.T) {
   132  		require.Nil(t, node.NewInterimCompactifiedNode(5, nil, n2))
   133  		require.Nil(t, node.NewInterimCompactifiedNode(5, n1, nil))
   134  	})
   135  
   136  	t.Run("both children nil", func(t *testing.T) {
   137  		require.Nil(t, node.NewInterimCompactifiedNode(5, nil, nil))
   138  	})
   139  }
   140  
   141  // Test_Compactify_ToLeaf tests constructing an interim node with pruning/compactification,
   142  // where one child is empty and the other child is a leaf. We expect the compactified node
   143  // to be a leaf, as it only contains a single allocated register.
   144  func Test_Compactify_ToLeaf(t *testing.T) {
   145  	path1 := testutils.PathByUint16LeftPadded(0)      // ...0000 0000
   146  	path2 := testutils.PathByUint16LeftPadded(1 << 4) // ...0001 0000
   147  	emptyPayload := &ledger.Payload{}
   148  	payloadA := testutils.LightPayload(2, 2)
   149  
   150  	t.Run("left child empty", func(t *testing.T) {
   151  		// constructing an un-pruned tree first as reference:
   152  		//      n3
   153  		//    /   \
   154  		// n1(-)  n2(A)
   155  		n1 := node.NewLeaf(path1, emptyPayload, 4)
   156  		n2 := node.NewLeaf(path2, payloadA, 4)
   157  		n3 := node.NewInterimNode(5, n1, n2)
   158  
   159  		// Constructing a trie with pruning/compactification should result in
   160  		//       nn3(A)
   161  		// while keeping the root hash invariant
   162  		nn3 := node.NewInterimCompactifiedNode(5, n1, n2)
   163  		requireIsLeafWithHash(t, nn3, n3.Hash())
   164  
   165  		nn3 = node.NewInterimCompactifiedNode(5, nil, n2)
   166  		requireIsLeafWithHash(t, nn3, n3.Hash())
   167  	})
   168  
   169  	t.Run("right child empty", func(t *testing.T) {
   170  		// constructing an un-pruned tree first as reference:
   171  		//      n3
   172  		//    /   \
   173  		// n1(A)  n2(-)
   174  		n1 := node.NewLeaf(path1, payloadA, 4)
   175  		n2 := node.NewLeaf(path2, emptyPayload, 4)
   176  		n3 := node.NewInterimNode(5, n1, n2)
   177  
   178  		// Constructing a trie with pruning/compactification should result in
   179  		//       nn3(A)
   180  		// while keeping the root hash invariant
   181  		nn3 := node.NewInterimCompactifiedNode(5, n1, n2)
   182  		requireIsLeafWithHash(t, nn3, n3.Hash())
   183  
   184  		nn3 = node.NewInterimCompactifiedNode(5, n1, nil)
   185  		requireIsLeafWithHash(t, nn3, n3.Hash())
   186  	})
   187  }
   188  
   189  // Test_Compactify_EmptyChild tests constructing an interim node with pruning/compactification,
   190  // where one child is empty and the other child holds _multiple_ allocated registers (more than one).
   191  // We expect in the compactified node, the empty subtrie is completely removed and replaced by nil.
   192  func Test_Compactify_EmptyChild(t *testing.T) {
   193  	payloadA := testutils.LightPayload(2, 2)
   194  	payloadB := testutils.LightPayload(4, 4)
   195  	emptyPayload := &ledger.Payload{}
   196  
   197  	t.Run("right child empty", func(t *testing.T) {
   198  		// constructing an un-pruned tree first as reference:
   199  		//          n5
   200  		//       /     \
   201  		//      n3      n4(-)
   202  		//   /    \
   203  		// n1(A)  n2(B)
   204  		n1 := node.NewLeaf(testutils.PathByUint16LeftPadded(0), payloadA, 4)    // path: ...0000 0000
   205  		n2 := node.NewLeaf(testutils.PathByUint16LeftPadded(1<<4), payloadB, 4) // path: ...0001 0000
   206  		n3 := node.NewInterimNode(5, n1, n2)
   207  		n4 := node.NewLeaf(testutils.PathByUint16LeftPadded(3<<4), emptyPayload, 5) // path: ...0011 0000
   208  		n5 := node.NewInterimNode(6, n3, n4)
   209  
   210  		// Constructing a trie with pruning/compactification should result
   211  		// in n4 being replaced with nil, while keeping the root hash invariant.
   212  		nn5 := node.NewInterimCompactifiedNode(6, n3, n4)
   213  		require.Equal(t, n3, nn5.LeftChild())
   214  		require.Nil(t, nn5.RightChild())
   215  		require.True(t, nn5.VerifyCachedHash())
   216  		require.Equal(t, n5.Hash(), nn5.Hash())
   217  	})
   218  
   219  	t.Run("left child empty", func(t *testing.T) {
   220  		// constructing an un-pruned tree first as reference:
   221  		//          n5
   222  		//       /     \
   223  		//    n3(-)    n4
   224  		//           /   \
   225  		//        n1(A)  n2(B)
   226  		n1 := node.NewLeaf(testutils.PathByUint16LeftPadded(2<<4), payloadA, 4)  // path: ...0010 0000
   227  		n2 := node.NewLeaf(testutils.PathByUint16LeftPadded(3<<4), payloadB, 4)  // path: ...0011 0000
   228  		n3 := node.NewLeaf(testutils.PathByUint16LeftPadded(0), emptyPayload, 5) // path: ...0000 0000
   229  		n4 := node.NewInterimNode(5, n1, n2)
   230  		n5 := node.NewInterimNode(6, n3, n4)
   231  
   232  		// Constructing a trie with pruning/compactification should result
   233  		// in n4 being replaced with nil, while keeping the root hash invariant.
   234  		nn5 := node.NewInterimCompactifiedNode(6, n3, n4)
   235  		require.Nil(t, nn5.LeftChild())
   236  		require.Equal(t, n4, nn5.RightChild())
   237  		require.True(t, nn5.VerifyCachedHash())
   238  		require.Equal(t, n5.Hash(), nn5.Hash())
   239  	})
   240  
   241  }
   242  
   243  // Test_Compactify_BothChildrenPopulated tests some cases, where both children are populated
   244  func Test_Compactify_BothChildrenPopulated(t *testing.T) {
   245  	//          n5
   246  	//       /     \
   247  	//      n3      n4(C)
   248  	//   /    \
   249  	// n1(A)  n2(B)
   250  	path1 := testutils.PathByUint16LeftPadded(0)      // ...0000 0000
   251  	path2 := testutils.PathByUint16LeftPadded(1 << 4) // ...0001 0000
   252  	path4 := testutils.PathByUint16LeftPadded(3 << 4) // ...0011 0000
   253  	payloadA := testutils.LightPayload(2, 2)
   254  	payloadB := testutils.LightPayload(3, 3)
   255  	payloadC := testutils.LightPayload(4, 4)
   256  
   257  	// constructing an un-pruned tree first as reference:
   258  	n1 := node.NewLeaf(path1, payloadA, 4)
   259  	n2 := node.NewLeaf(path2, payloadB, 4)
   260  	n3 := node.NewInterimNode(5, n1, n2)
   261  	n4 := node.NewLeaf(path4, payloadC, 5)
   262  	n5 := node.NewInterimNode(6, n3, n4)
   263  
   264  	// Constructing a trie with pruning/compactification should result
   265  	// reproduce exactly the same trie as no pruning/compactification is possible
   266  	nn3 := node.NewInterimCompactifiedNode(5, n1, n2)
   267  	require.Equal(t, n1, nn3.LeftChild())
   268  	require.Equal(t, n2, nn3.RightChild())
   269  	require.True(t, nn3.VerifyCachedHash())
   270  	require.Equal(t, n3.Hash(), nn3.Hash())
   271  
   272  	nn5 := node.NewInterimCompactifiedNode(6, nn3, n4)
   273  	require.Equal(t, nn3, nn5.LeftChild())
   274  	require.Equal(t, n4, nn5.RightChild())
   275  	require.True(t, nn5.VerifyCachedHash())
   276  	require.Equal(t, n5.Hash(), nn5.Hash())
   277  }
   278  
   279  func hashToString(hash hash.Hash) string {
   280  	return hex.EncodeToString(hash[:])
   281  }
   282  
   283  // requireIsLeafWithHash verifies that `node` is a leaf node, whose hash equals `expectedHash`.
   284  // We perform the following checks:
   285  // * both children must be nil
   286  // * depth is zero
   287  // * number of registers in the sub-trie is 1
   288  // * pre-computed hash matches the `expectedHash`
   289  // * re-computing the hash from the children yields the pre-computed value
   290  // * node reports itself as a leaf
   291  func requireIsLeafWithHash(t *testing.T, node *node.Node, expectedHash hash.Hash) {
   292  	require.Nil(t, node.LeftChild())
   293  	require.Nil(t, node.RightChild())
   294  	require.Equal(t, expectedHash, node.Hash())
   295  	require.True(t, node.VerifyCachedHash())
   296  	require.True(t, node.IsLeaf())
   297  }