github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/mpt/compat_test.go (about) 1 package mpt 2 3 import ( 4 "testing" 5 6 "github.com/stretchr/testify/require" 7 ) 8 9 func prepareMPTCompat() *Trie { 10 b := NewBranchNode() 11 r := NewExtensionNode([]byte{0x0a, 0x0c}, b) 12 v1 := NewLeafNode([]byte{0xab, 0xcd}) //key=ac01 13 v2 := NewLeafNode([]byte{0x22, 0x22}) //key=ac 14 v3 := NewLeafNode([]byte("existing")) //key=acae 15 v4 := NewLeafNode([]byte("missing")) 16 h3 := NewHashNode(v3.Hash()) 17 e1 := NewExtensionNode([]byte{0x01}, v1) 18 e3 := NewExtensionNode([]byte{0x0e}, h3) 19 e4 := NewExtensionNode([]byte{0x01}, v4) 20 b.Children[0] = e1 21 b.Children[10] = e3 22 b.Children[16] = v2 23 b.Children[15] = NewHashNode(e4.Hash()) 24 25 tr := NewTrie(r, ModeLatest, newTestStore()) 26 tr.putToStore(r) 27 tr.putToStore(b) 28 tr.putToStore(e1) 29 tr.putToStore(e3) 30 tr.putToStore(v1) 31 tr.putToStore(v2) 32 tr.putToStore(v3) 33 34 return tr 35 } 36 37 // TestCompatibility contains tests present in C# implementation. 38 // https://github.com/neo-project/neo-modules/blob/master/tests/Neo.Plugins.StateService.Tests/MPT/UT_MPTTrie.cs 39 // There are some differences, though: 40 // 1. In our implementation, delete is silent, i.e. we do not return an error if the key is missing or empty. 41 // However, we do return an error when the contents of the hash node are missing from the store 42 // (corresponds to exception in C# implementation). However, if the key is too big, an error is returned 43 // (corresponds to exception in C# implementation). 44 // 2. In our implementation, put returns an error if something goes wrong, while C# implementation throws 45 // an exception and returns nothing. 46 // 3. In our implementation, get does not immediately return any error in case of an empty key. An error is returned 47 // only if the value is missing from the storage. C# implementation checks that the key is not empty and throws an error 48 // otherwise. However, if the key is too big, an error is returned (corresponds to exception in C# implementation). 49 func TestCompatibility(t *testing.T) { 50 mainTrie := prepareMPTCompat() 51 52 t.Run("TryGet", func(t *testing.T) { 53 tr := copyTrie(mainTrie) 54 tr.testHas(t, []byte{0xac, 0x01}, []byte{0xab, 0xcd}) 55 tr.testHas(t, []byte{0xac}, []byte{0x22, 0x22}) 56 tr.testHas(t, []byte{0xab, 0x99}, nil) 57 tr.testHas(t, []byte{0xac, 0x39}, nil) 58 tr.testHas(t, []byte{0xac, 0x02}, nil) 59 tr.testHas(t, []byte{0xac, 0x01, 0x00}, nil) 60 tr.testHas(t, []byte{0xac, 0x99, 0x10}, nil) 61 tr.testHas(t, []byte{0xac, 0xf1}, nil) 62 tr.testHas(t, make([]byte, MaxKeyLength), nil) 63 }) 64 65 t.Run("TryGetResolve", func(t *testing.T) { 66 tr := copyTrie(mainTrie) 67 tr.testHas(t, []byte{0xac, 0xae}, []byte("existing")) 68 }) 69 70 t.Run("TryPut", func(t *testing.T) { 71 tr := newFilledTrie(t, 72 []byte{0xac, 0x01}, []byte{0xab, 0xcd}, 73 []byte{0xac}, []byte{0x22, 0x22}, 74 []byte{0xac, 0xae}, []byte("existing"), 75 []byte{0xac, 0xf1}, []byte("missing")) 76 77 require.Equal(t, mainTrie.root.Hash(), tr.root.Hash()) 78 require.Error(t, tr.Put(nil, []byte{0x01})) 79 require.Error(t, tr.Put([]byte{0x01}, nil)) 80 require.Error(t, tr.Put(make([]byte, MaxKeyLength+1), nil)) 81 require.Error(t, tr.Put([]byte{0x01}, make([]byte, MaxValueLength+1))) 82 require.Equal(t, mainTrie.root.Hash(), tr.root.Hash()) 83 require.NoError(t, tr.Put([]byte{0x01}, []byte{})) 84 require.NoError(t, tr.Put([]byte{0xac, 0x01}, []byte{0xab})) 85 }) 86 87 t.Run("PutCantResolve", func(t *testing.T) { 88 tr := copyTrie(mainTrie) 89 require.Error(t, tr.Put([]byte{0xac, 0xf1, 0x11}, []byte{1})) 90 }) 91 92 t.Run("TryDelete", func(t *testing.T) { 93 tr := copyTrie(mainTrie) 94 tr.testHas(t, []byte{0xac}, []byte{0x22, 0x22}) 95 require.NoError(t, tr.Delete([]byte{0x0c, 0x99})) 96 require.NoError(t, tr.Delete(nil)) 97 require.NoError(t, tr.Delete([]byte{0xac, 0x20})) 98 99 require.Error(t, tr.Delete([]byte{0xac, 0xf1})) // error for can't resolve 100 require.Error(t, tr.Delete(make([]byte, MaxKeyLength+1))) // error for too big key 101 102 // In our implementation missing keys are ignored. 103 require.NoError(t, tr.Delete([]byte{0xac})) 104 require.NoError(t, tr.Delete([]byte{0xac, 0xae, 0x01})) 105 require.NoError(t, tr.Delete([]byte{0xac, 0xae})) 106 107 require.Equal(t, "cb06925428b7c727375c7fdd943a302fe2c818cf2e2eaf63a7932e3fd6cb3408", 108 tr.root.Hash().StringLE()) 109 }) 110 111 t.Run("DeleteRemainCanResolve", func(t *testing.T) { 112 tr := newFilledTrie(t, 113 []byte{0xac, 0x00}, []byte{0xab, 0xcd}, 114 []byte{0xac, 0x10}, []byte{0xab, 0xcd}) 115 tr.Flush(0) 116 117 tr2 := copyTrie(tr) 118 require.NoError(t, tr2.Delete([]byte{0xac, 0x00})) 119 120 tr2.Flush(0) 121 require.NoError(t, tr2.Delete([]byte{0xac, 0x10})) 122 }) 123 124 t.Run("DeleteRemainCantResolve", func(t *testing.T) { 125 b := NewBranchNode() 126 r := NewExtensionNode([]byte{0x0a, 0x0c}, b) 127 v1 := NewLeafNode([]byte{0xab, 0xcd}) 128 v4 := NewLeafNode([]byte("missing")) 129 e1 := NewExtensionNode([]byte{0x01}, v1) 130 e4 := NewExtensionNode([]byte{0x01}, v4) 131 b.Children[0] = e1 132 b.Children[15] = NewHashNode(e4.Hash()) 133 134 tr := NewTrie(NewHashNode(r.Hash()), ModeAll, newTestStore()) 135 tr.putToStore(r) 136 tr.putToStore(b) 137 tr.putToStore(e1) 138 tr.putToStore(v1) 139 140 require.Error(t, tr.Delete([]byte{0xac, 0x01})) 141 }) 142 143 t.Run("DeleteSameValue", func(t *testing.T) { 144 tr := newFilledTrie(t, 145 []byte{0xac, 0x01}, []byte{0xab, 0xcd}, 146 []byte{0xac, 0x02}, []byte{0xab, 0xcd}) 147 tr.testHas(t, []byte{0xac, 0x01}, []byte{0xab, 0xcd}) 148 tr.testHas(t, []byte{0xac, 0x02}, []byte{0xab, 0xcd}) 149 150 require.NoError(t, tr.Delete([]byte{0xac, 0x01})) 151 tr.testHas(t, []byte{0xac, 0x02}, []byte{0xab, 0xcd}) 152 tr.Flush(0) 153 154 tr2 := NewTrie(NewHashNode(tr.root.Hash()), ModeAll, tr.Store) 155 tr2.testHas(t, []byte{0xac, 0x02}, []byte{0xab, 0xcd}) 156 }) 157 158 t.Run("BranchNodeRemainValue", func(t *testing.T) { 159 tr := newFilledTrie(t, 160 []byte{0xac, 0x11}, []byte{0xac, 0x11}, 161 []byte{0xac, 0x22}, []byte{0xac, 0x22}, 162 []byte{0xac}, []byte{0xac}) 163 tr.Flush(0) 164 checkBatchSize(t, tr, 7) 165 166 require.NoError(t, tr.Delete([]byte{0xac, 0x11})) 167 tr.Flush(0) 168 checkBatchSize(t, tr, 5) 169 170 require.NoError(t, tr.Delete([]byte{0xac, 0x22})) 171 tr.Flush(0) 172 checkBatchSize(t, tr, 2) 173 }) 174 175 t.Run("GetProof", func(t *testing.T) { 176 b := NewBranchNode() 177 r := NewExtensionNode([]byte{0x0a, 0x0c}, b) 178 v1 := NewLeafNode([]byte{0xab, 0xcd}) //key=ac01 179 v2 := NewLeafNode([]byte{0x22, 0x22}) //key=ac 180 v3 := NewLeafNode([]byte("existing")) //key=acae 181 v4 := NewLeafNode([]byte("missing")) 182 h3 := NewHashNode(v3.Hash()) 183 e1 := NewExtensionNode([]byte{0x01}, v1) 184 e3 := NewExtensionNode([]byte{0x0e}, h3) 185 e4 := NewExtensionNode([]byte{0x01}, v4) 186 b.Children[0] = e1 187 b.Children[10] = e3 188 b.Children[16] = v2 189 b.Children[15] = NewHashNode(e4.Hash()) 190 191 tr := NewTrie(NewHashNode(r.Hash()), ModeLatest, mainTrie.Store) 192 require.Equal(t, r.Hash(), tr.root.Hash()) 193 194 proof := testGetProof(t, tr, []byte{0xac, 0x01}, 4) 195 require.Equal(t, r.Bytes(), proof[0]) 196 require.Equal(t, b.Bytes(), proof[1]) 197 require.Equal(t, e1.Bytes(), proof[2]) 198 require.Equal(t, v1.Bytes(), proof[3]) 199 200 testGetProof(t, tr, []byte{0xac}, 3) 201 testGetProof(t, tr, []byte{0xac, 0x10}, 0) 202 testGetProof(t, tr, []byte{0xac, 0xae}, 4) 203 testGetProof(t, tr, nil, 0) 204 testGetProof(t, tr, []byte{0xac, 0x01, 0x00}, 0) 205 testGetProof(t, tr, []byte{0xac, 0xf1}, 0) 206 testGetProof(t, tr, make([]byte, MaxKeyLength), 0) 207 }) 208 209 t.Run("VerifyProof", func(t *testing.T) { 210 tr := copyTrie(mainTrie) 211 proof := testGetProof(t, tr, []byte{0xac, 0x01}, 4) 212 value, ok := VerifyProof(tr.root.Hash(), []byte{0xac, 0x01}, proof) 213 require.True(t, ok) 214 require.Equal(t, []byte{0xab, 0xcd}, value) 215 }) 216 217 t.Run("AddLongerKey", func(t *testing.T) { 218 tr := newFilledTrie(t, 219 []byte{0xab}, []byte{0x01}, 220 []byte{0xab, 0xcd}, []byte{0x02}) 221 tr.testHas(t, []byte{0xab}, []byte{0x01}) 222 }) 223 224 t.Run("SplitKey", func(t *testing.T) { 225 tr := newFilledTrie(t, 226 []byte{0xab, 0xcd}, []byte{0x01}, 227 []byte{0xab}, []byte{0x02}) 228 testGetProof(t, tr, []byte{0xab, 0xcd}, 4) 229 230 tr2 := newFilledTrie(t, 231 []byte{0xab}, []byte{0x02}, 232 []byte{0xab, 0xcd}, []byte{0x01}) 233 testGetProof(t, tr, []byte{0xab, 0xcd}, 4) 234 235 require.Equal(t, tr.root.Hash(), tr2.root.Hash()) 236 }) 237 238 t.Run("Reference", func(t *testing.T) { 239 tr := newFilledTrie(t, 240 []byte{0xa1, 0x01}, []byte{0x01}, 241 []byte{0xa2, 0x01}, []byte{0x01}, 242 []byte{0xa3, 0x01}, []byte{0x01}) 243 tr.Flush(0) 244 245 tr2 := copyTrie(tr) 246 require.NoError(t, tr2.Delete([]byte{0xa3, 0x01})) 247 tr2.Flush(0) 248 249 tr3 := copyTrie(tr2) 250 require.NoError(t, tr3.Delete([]byte{0xa2, 0x01})) 251 tr3.testHas(t, []byte{0xa1, 0x01}, []byte{0x01}) 252 }) 253 254 t.Run("Reference2", func(t *testing.T) { 255 tr := newFilledTrie(t, 256 []byte{0xa1, 0x01}, []byte{0x01}, 257 []byte{0xa2, 0x01}, []byte{0x01}, 258 []byte{0xa3, 0x01}, []byte{0x01}) 259 tr.Flush(0) 260 checkBatchSize(t, tr, 4) 261 262 require.NoError(t, tr.Delete([]byte{0xa3, 0x01})) 263 tr.Flush(0) 264 checkBatchSize(t, tr, 4) 265 266 require.NoError(t, tr.Delete([]byte{0xa2, 0x01})) 267 tr.Flush(0) 268 checkBatchSize(t, tr, 2) 269 tr.testHas(t, []byte{0xa1, 0x01}, []byte{0x01}) 270 }) 271 272 t.Run("ExtensionDeleteDirty", func(t *testing.T) { 273 tr := newFilledTrie(t, 274 []byte{0xa1}, []byte{0x01}, 275 []byte{0xa2}, []byte{0x02}) 276 tr.Flush(0) 277 checkBatchSize(t, tr, 4) 278 279 tr1 := copyTrie(tr) 280 require.NoError(t, tr1.Delete([]byte{0xa1})) 281 tr1.Flush(0) 282 require.Equal(t, 2, len(tr1.Store.GetBatch().Put)) 283 284 tr2 := copyTrie(tr1) 285 require.NoError(t, tr2.Delete([]byte{0xa2})) 286 tr2.Flush(0) 287 require.Equal(t, 0, len(tr2.Store.GetBatch().Put)) 288 }) 289 290 t.Run("BranchDeleteDirty", func(t *testing.T) { 291 tr := newFilledTrie(t, 292 []byte{0x10}, []byte{0x01}, 293 []byte{0x20}, []byte{0x02}, 294 []byte{0x30}, []byte{0x03}) 295 tr.Flush(0) 296 checkBatchSize(t, tr, 7) 297 298 tr1 := copyTrie(tr) 299 require.NoError(t, tr1.Delete([]byte{0x10})) 300 tr1.Flush(0) 301 302 tr2 := copyTrie(tr1) 303 require.NoError(t, tr2.Delete([]byte{0x20})) 304 tr2.Flush(0) 305 require.Equal(t, 2, len(tr2.Store.GetBatch().Put)) 306 307 tr3 := copyTrie(tr2) 308 require.NoError(t, tr3.Delete([]byte{0x30})) 309 tr3.Flush(0) 310 require.Equal(t, 0, len(tr3.Store.GetBatch().Put)) 311 }) 312 313 t.Run("ExtensionPutDirty", func(t *testing.T) { 314 tr := newFilledTrie(t, 315 []byte{0xa1}, []byte{0x01}, 316 []byte{0xa2}, []byte{0x02}) 317 tr.Flush(0) 318 checkBatchSize(t, tr, 4) 319 320 tr1 := copyTrie(tr) 321 require.NoError(t, tr1.Put([]byte{0xa3}, []byte{0x03})) 322 tr1.Flush(0) 323 require.Equal(t, 5, len(tr1.Store.GetBatch().Put)) 324 }) 325 326 t.Run("BranchPutDirty", func(t *testing.T) { 327 tr := newFilledTrie(t, 328 []byte{0x10}, []byte{0x01}, 329 []byte{0x20}, []byte{0x02}) 330 tr.Flush(0) 331 checkBatchSize(t, tr, 5) 332 333 tr1 := copyTrie(tr) 334 require.NoError(t, tr1.Put([]byte{0x30}, []byte{0x03})) 335 tr1.Flush(0) 336 checkBatchSize(t, tr1, 7) 337 }) 338 339 t.Run("EmptyValueIssue633", func(t *testing.T) { 340 tr := newFilledTrie(t, 341 []byte{0x01}, []byte{}) 342 tr.Flush(0) 343 checkBatchSize(t, tr, 2) 344 345 proof := testGetProof(t, tr, []byte{0x01}, 2) 346 value, ok := VerifyProof(tr.root.Hash(), []byte{0x01}, proof) 347 require.True(t, ok) 348 require.Equal(t, []byte{}, value) 349 }) 350 } 351 352 func copyTrie(t *Trie) *Trie { 353 return NewTrie(NewHashNode(t.root.Hash()), t.mode, t.Store) 354 } 355 356 func checkBatchSize(t *testing.T, tr *Trie, n int) { 357 require.Equal(t, n, len(tr.Store.GetBatch().Put)) 358 } 359 360 func testGetProof(t *testing.T, tr *Trie, key []byte, size int) [][]byte { 361 proof, err := tr.GetProof(key) 362 if size == 0 { 363 require.Error(t, err) 364 return proof 365 } 366 367 require.NoError(t, err) 368 require.Equal(t, size, len(proof)) 369 return proof 370 } 371 372 func newFilledTrie(t *testing.T, args ...[]byte) *Trie { 373 tr := NewTrie(nil, ModeLatest, newTestStore()) 374 for i := 0; i < len(args); i += 2 { 375 require.NoError(t, tr.Put(args[i], args[i+1])) 376 } 377 return tr 378 } 379 380 func TestCompatibility_Find(t *testing.T) { 381 check := func(t *testing.T, from []byte, expectedResLen int) { 382 tr := NewTrie(nil, ModeAll, newTestStore()) 383 require.NoError(t, tr.Put([]byte("aa"), []byte("02"))) 384 require.NoError(t, tr.Put([]byte("aa10"), []byte("03"))) 385 require.NoError(t, tr.Put([]byte("aa50"), []byte("04"))) 386 res, err := tr.Find([]byte("aa"), from, 10) 387 require.NoError(t, err) 388 require.Equal(t, expectedResLen, len(res)) 389 } 390 t.Run("no from", func(t *testing.T) { 391 check(t, nil, 3) 392 }) 393 t.Run("from is not in tree", func(t *testing.T) { 394 t.Run("matching", func(t *testing.T) { 395 check(t, []byte("30"), 1) 396 }) 397 t.Run("non-matching", func(t *testing.T) { 398 check(t, []byte("60"), 0) 399 }) 400 }) 401 t.Run("from is in tree", func(t *testing.T) { 402 check(t, []byte("10"), 1) // without `from` key 403 }) 404 t.Run("from matching start", func(t *testing.T) { 405 check(t, []byte{}, 2) // without `from` key 406 }) 407 t.Run("TestFindStatesIssue652", func(t *testing.T) { 408 tr := NewTrie(nil, ModeAll, newTestStore()) 409 // root is an extension node with key=abc; next=branch 410 require.NoError(t, tr.Put([]byte("abc1"), []byte("01"))) 411 require.NoError(t, tr.Put([]byte("abc3"), []byte("02"))) 412 tr.Flush(0) 413 // find items with extension's key prefix 414 t.Run("from > start", func(t *testing.T) { 415 res, err := tr.Find([]byte("ab"), []byte("d2"), 100) 416 require.NoError(t, err) 417 // nothing should be found, because from[0]=`d` > key[2]=`c` 418 require.Equal(t, 0, len(res)) 419 }) 420 421 t.Run("from < start", func(t *testing.T) { 422 res, err := tr.Find([]byte("ab"), []byte("b2"), 100) 423 require.NoError(t, err) 424 // all items should be included into the result, because from[0]=`b` < key[2]=`c` 425 require.Equal(t, 2, len(res)) 426 }) 427 428 t.Run("from and start have common prefix", func(t *testing.T) { 429 res, err := tr.Find([]byte("ab"), []byte("c"), 100) 430 require.NoError(t, err) 431 // all items should be included into the result, because from[0] == key[2] 432 require.Equal(t, 2, len(res)) 433 }) 434 435 t.Run("from equals to item key", func(t *testing.T) { 436 res, err := tr.Find([]byte("ab"), []byte("c1"), 100) 437 require.NoError(t, err) 438 require.Equal(t, 1, len(res)) 439 }) 440 }) 441 }