github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/mpt/billet_test.go (about)

     1  package mpt
     2  
     3  import (
     4  	"encoding/binary"
     5  	"testing"
     6  
     7  	"github.com/nspcc-dev/neo-go/pkg/core/storage"
     8  	"github.com/nspcc-dev/neo-go/pkg/io"
     9  	"github.com/nspcc-dev/neo-go/pkg/util"
    10  	"github.com/stretchr/testify/require"
    11  )
    12  
    13  func TestBillet_RestoreHashNode(t *testing.T) {
    14  	check := func(t *testing.T, tr *Billet, expectedRoot Node, expectedNode Node, expectedRefCount uint32) {
    15  		_ = expectedRoot.Hash()
    16  		_ = tr.root.Hash()
    17  		require.Equal(t, expectedRoot, tr.root)
    18  		expectedBytes, err := tr.Store.Get(makeStorageKey(expectedNode.Hash()))
    19  		if expectedRefCount != 0 {
    20  			require.NoError(t, err)
    21  			require.Equal(t, expectedRefCount, binary.LittleEndian.Uint32(expectedBytes[len(expectedBytes)-4:]))
    22  		} else {
    23  			require.ErrorIs(t, err, storage.ErrKeyNotFound)
    24  		}
    25  	}
    26  
    27  	t.Run("parent is Extension", func(t *testing.T) {
    28  		t.Run("restore Branch", func(t *testing.T) {
    29  			b := NewBranchNode()
    30  			b.Children[0] = NewExtensionNode([]byte{0x01}, NewLeafNode([]byte{0xAB, 0xCD}))
    31  			b.Children[5] = NewExtensionNode([]byte{0x01}, NewLeafNode([]byte{0xAB, 0xDE}))
    32  			path := toNibbles([]byte{0xAC})
    33  			e := NewExtensionNode(path, NewHashNode(b.Hash()))
    34  			tr := NewBillet(e.Hash(), ModeLatest, storage.STTempStorage, newTestStore())
    35  			tr.root = e
    36  
    37  			// OK
    38  			n := new(NodeObject)
    39  			n.DecodeBinary(io.NewBinReaderFromBuf(b.Bytes()))
    40  			require.NoError(t, tr.RestoreHashNode(path, n.Node))
    41  			expected := NewExtensionNode(path, n.Node)
    42  			check(t, tr, expected, n.Node, 1)
    43  
    44  			// One more time (already restored) => panic expected, no refcount changes
    45  			require.Panics(t, func() {
    46  				_ = tr.RestoreHashNode(path, n.Node)
    47  			})
    48  			check(t, tr, expected, n.Node, 1)
    49  
    50  			// Same path, but wrong hash => error expected, no refcount changes
    51  			require.ErrorIs(t, tr.RestoreHashNode(path, NewBranchNode()), ErrRestoreFailed)
    52  			check(t, tr, expected, n.Node, 1)
    53  
    54  			// New path (changes in the MPT structure are not allowed) => error expected, no refcount changes
    55  			require.ErrorIs(t, tr.RestoreHashNode(toNibbles([]byte{0xAB}), n.Node), ErrRestoreFailed)
    56  			check(t, tr, expected, n.Node, 1)
    57  		})
    58  
    59  		t.Run("restore Leaf", func(t *testing.T) {
    60  			l := NewLeafNode([]byte{0xAB, 0xCD})
    61  			path := toNibbles([]byte{0xAC})
    62  			e := NewExtensionNode(path, NewHashNode(l.Hash()))
    63  			tr := NewBillet(e.Hash(), ModeLatest, storage.STTempStorage, newTestStore())
    64  			tr.root = e
    65  
    66  			// OK
    67  			require.NoError(t, tr.RestoreHashNode(path, l))
    68  			expected := NewHashNode(e.Hash()) // leaf should be collapsed immediately => extension should also be collapsed
    69  			expected.Collapsed = true
    70  			check(t, tr, expected, l, 1)
    71  
    72  			// One more time (already restored and collapsed) => error expected, no refcount changes
    73  			require.Error(t, tr.RestoreHashNode(path, l))
    74  			check(t, tr, expected, l, 1)
    75  
    76  			// Same path, but wrong hash => error expected, no refcount changes
    77  			require.ErrorIs(t, tr.RestoreHashNode(path, NewLeafNode([]byte{0xAB, 0xEF})), ErrRestoreFailed)
    78  			check(t, tr, expected, l, 1)
    79  
    80  			// New path (changes in the MPT structure are not allowed) => error expected, no refcount changes
    81  			require.ErrorIs(t, tr.RestoreHashNode(toNibbles([]byte{0xAB}), l), ErrRestoreFailed)
    82  			check(t, tr, expected, l, 1)
    83  		})
    84  
    85  		t.Run("restore Hash", func(t *testing.T) {
    86  			h := NewHashNode(util.Uint256{1, 2, 3})
    87  			path := toNibbles([]byte{0xAC})
    88  			e := NewExtensionNode(path, h)
    89  			tr := NewBillet(e.Hash(), ModeLatest, storage.STTempStorage, newTestStore())
    90  			tr.root = e
    91  
    92  			// no-op
    93  			require.ErrorIs(t, tr.RestoreHashNode(path, h), ErrRestoreFailed)
    94  			check(t, tr, e, h, 0)
    95  		})
    96  	})
    97  
    98  	t.Run("parent is Leaf", func(t *testing.T) {
    99  		l := NewLeafNode([]byte{0xAB, 0xCD})
   100  		path := []byte{}
   101  		tr := NewBillet(l.Hash(), ModeLatest, storage.STTempStorage, newTestStore())
   102  		tr.root = l
   103  
   104  		// Already restored => panic expected
   105  		require.Panics(t, func() {
   106  			_ = tr.RestoreHashNode(path, l)
   107  		})
   108  
   109  		// Same path, but wrong hash => error expected, no refcount changes
   110  		require.ErrorIs(t, tr.RestoreHashNode(path, NewLeafNode([]byte{0xAB, 0xEF})), ErrRestoreFailed)
   111  
   112  		// Non-nil path, but MPT structure can't be changed => error expected, no refcount changes
   113  		require.ErrorIs(t, tr.RestoreHashNode(toNibbles([]byte{0xAC}), NewLeafNode([]byte{0xAB, 0xEF})), ErrRestoreFailed)
   114  	})
   115  
   116  	t.Run("parent is Branch", func(t *testing.T) {
   117  		t.Run("middle child", func(t *testing.T) {
   118  			l1 := NewLeafNode([]byte{0xAB, 0xCD})
   119  			l2 := NewLeafNode([]byte{0xAB, 0xDE})
   120  			b := NewBranchNode()
   121  			b.Children[5] = NewHashNode(l1.Hash())
   122  			b.Children[lastChild] = NewHashNode(l2.Hash())
   123  			tr := NewBillet(b.Hash(), ModeLatest, storage.STTempStorage, newTestStore())
   124  			tr.root = b
   125  
   126  			// OK
   127  			path := []byte{0x05}
   128  			require.NoError(t, tr.RestoreHashNode(path, l1))
   129  			check(t, tr, b, l1, 1)
   130  
   131  			// One more time (already restored) => panic expected.
   132  			// It's an MPT pool duty to avoid such situations during real restore process.
   133  			require.Panics(t, func() {
   134  				_ = tr.RestoreHashNode(path, l1)
   135  			})
   136  			// No refcount changes expected.
   137  			check(t, tr, b, l1, 1)
   138  
   139  			// Same path, but wrong hash => error expected, no refcount changes
   140  			require.ErrorIs(t, tr.RestoreHashNode(path, NewLeafNode([]byte{0xAD})), ErrRestoreFailed)
   141  			check(t, tr, b, l1, 1)
   142  
   143  			// New path pointing to the empty HashNode (changes in the MPT structure are not allowed) => error expected, no refcount changes
   144  			require.ErrorIs(t, tr.RestoreHashNode([]byte{0x01}, l1), ErrRestoreFailed)
   145  			check(t, tr, b, l1, 1)
   146  		})
   147  
   148  		t.Run("last child", func(t *testing.T) {
   149  			l1 := NewLeafNode([]byte{0xAB, 0xCD})
   150  			l2 := NewLeafNode([]byte{0xAB, 0xDE})
   151  			b := NewBranchNode()
   152  			b.Children[5] = NewHashNode(l1.Hash())
   153  			b.Children[lastChild] = NewHashNode(l2.Hash())
   154  			tr := NewBillet(b.Hash(), ModeLatest, storage.STTempStorage, newTestStore())
   155  			tr.root = b
   156  
   157  			// OK
   158  			path := []byte{}
   159  			require.NoError(t, tr.RestoreHashNode(path, l2))
   160  			check(t, tr, b, l2, 1)
   161  
   162  			// One more time (already restored) => panic expected.
   163  			// It's an MPT pool duty to avoid such situations during real restore process.
   164  			require.Panics(t, func() {
   165  				_ = tr.RestoreHashNode(path, l2)
   166  			})
   167  			// No refcount changes expected.
   168  			check(t, tr, b, l2, 1)
   169  
   170  			// Same path, but wrong hash => error expected, no refcount changes
   171  			require.ErrorIs(t, tr.RestoreHashNode(path, NewLeafNode([]byte{0xAD})), ErrRestoreFailed)
   172  			check(t, tr, b, l2, 1)
   173  		})
   174  
   175  		t.Run("two children with same hash", func(t *testing.T) {
   176  			l := NewLeafNode([]byte{0xAB, 0xCD})
   177  			b := NewBranchNode()
   178  			// two same hashnodes => leaf's refcount expected to be 2 in the end.
   179  			b.Children[3] = NewHashNode(l.Hash())
   180  			b.Children[4] = NewHashNode(l.Hash())
   181  			tr := NewBillet(b.Hash(), ModeLatest, storage.STTempStorage, newTestStore())
   182  			tr.root = b
   183  
   184  			// OK
   185  			require.NoError(t, tr.RestoreHashNode([]byte{0x03}, l))
   186  			expected := b
   187  			expected.Children[3].(*HashNode).Collapsed = true
   188  			check(t, tr, b, l, 1)
   189  
   190  			// Restore another node with the same hash => no error expected, refcount should be incremented.
   191  			// Branch node should be collapsed.
   192  			require.NoError(t, tr.RestoreHashNode([]byte{0x04}, l))
   193  			res := NewHashNode(b.Hash())
   194  			res.Collapsed = true
   195  			check(t, tr, res, l, 2)
   196  		})
   197  	})
   198  
   199  	t.Run("parent is Hash", func(t *testing.T) {
   200  		l := NewLeafNode([]byte{0xAB, 0xCD})
   201  		b := NewBranchNode()
   202  		b.Children[3] = NewHashNode(l.Hash())
   203  		b.Children[4] = NewHashNode(l.Hash())
   204  		tr := NewBillet(b.Hash(), ModeLatest, storage.STTempStorage, newTestStore())
   205  
   206  		// Should fail, because if it's a hash node with non-empty path, then the node
   207  		// has already been collapsed.
   208  		require.Error(t, tr.RestoreHashNode([]byte{0x03}, l))
   209  	})
   210  }