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  }