github.com/koko1123/flow-go-1@v0.29.6/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/koko1123/flow-go-1/ledger" 10 "github.com/koko1123/flow-go-1/ledger/common/hash" 11 "github.com/koko1123/flow-go-1/ledger/common/testutils" 12 "github.com/koko1123/flow-go-1/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 }