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 }