mpt: add the notion of MPT mode

It directly affects the storage format, so it's important. ModeGC is not used
at the moment, but defined for future extensibility.
This commit is contained in:
Roman Khimov 2022-01-28 11:56:33 +03:00
parent de99c3acdb
commit 86cb4ed80f
13 changed files with 140 additions and 96 deletions

View file

@ -320,7 +320,7 @@ func (bc *Blockchain) init() error {
if err != nil { if err != nil {
return err return err
} }
if err := bc.stateRoot.Init(0, bc.config.KeepOnlyLatestState); err != nil { if err := bc.stateRoot.Init(0); err != nil {
return fmt.Errorf("can't init MPT: %w", err) return fmt.Errorf("can't init MPT: %w", err)
} }
return bc.storeBlock(genesisBlock, nil) return bc.storeBlock(genesisBlock, nil)
@ -426,7 +426,7 @@ func (bc *Blockchain) init() error {
} }
bc.blockHeight = bHeight bc.blockHeight = bHeight
bc.persistedHeight = bHeight bc.persistedHeight = bHeight
if err = bc.stateRoot.Init(bHeight, bc.config.KeepOnlyLatestState); err != nil { if err = bc.stateRoot.Init(bHeight); err != nil {
return fmt.Errorf("can't init MPT at height %d: %w", bHeight, err) return fmt.Errorf("can't init MPT at height %d: %w", bHeight, err)
} }
@ -599,7 +599,7 @@ func (bc *Blockchain) jumpToStateInternal(p uint32, stage stateJumpStage) error
if err = bc.stateRoot.JumpToState(&state.MPTRoot{ if err = bc.stateRoot.JumpToState(&state.MPTRoot{
Index: p, Index: p,
Root: block.PrevStateRoot, Root: block.PrevStateRoot,
}, bc.config.KeepOnlyLatestState); err != nil { }); err != nil {
return fmt.Errorf("can't perform MPT jump to height %d: %w", p, err) return fmt.Errorf("can't perform MPT jump to height %d: %w", p, err)
} }

View file

@ -57,7 +57,7 @@ func testIncompletePut(t *testing.T, ps pairs, n int, tr1, tr2 *Trie) {
t.Run("test restore", func(t *testing.T) { t.Run("test restore", func(t *testing.T) {
tr2.Flush() tr2.Flush()
tr3 := NewTrie(NewHashNode(tr2.StateRoot()), false, storage.NewMemCachedStore(tr2.Store)) tr3 := NewTrie(NewHashNode(tr2.StateRoot()), ModeAll, storage.NewMemCachedStore(tr2.Store))
for _, p := range ps[:n] { for _, p := range ps[:n] {
val, err := tr3.Get(p[0]) val, err := tr3.Get(p[0])
if p[1] == nil { if p[1] == nil {
@ -76,8 +76,8 @@ func testPut(t *testing.T, ps pairs, tr1, tr2 *Trie) {
func TestTrie_PutBatchLeaf(t *testing.T) { func TestTrie_PutBatchLeaf(t *testing.T) {
prepareLeaf := func(t *testing.T) (*Trie, *Trie) { prepareLeaf := func(t *testing.T) (*Trie, *Trie) {
tr1 := NewTrie(EmptyNode{}, false, newTestStore()) tr1 := NewTrie(EmptyNode{}, ModeAll, newTestStore())
tr2 := NewTrie(EmptyNode{}, false, newTestStore()) tr2 := NewTrie(EmptyNode{}, ModeAll, newTestStore())
require.NoError(t, tr1.Put([]byte{0}, []byte("value"))) require.NoError(t, tr1.Put([]byte{0}, []byte("value")))
require.NoError(t, tr2.Put([]byte{0}, []byte("value"))) require.NoError(t, tr2.Put([]byte{0}, []byte("value")))
return tr1, tr2 return tr1, tr2
@ -118,8 +118,8 @@ func TestTrie_PutBatchLeaf(t *testing.T) {
func TestTrie_PutBatchExtension(t *testing.T) { func TestTrie_PutBatchExtension(t *testing.T) {
prepareExtension := func(t *testing.T) (*Trie, *Trie) { prepareExtension := func(t *testing.T) (*Trie, *Trie) {
tr1 := NewTrie(EmptyNode{}, false, newTestStore()) tr1 := NewTrie(EmptyNode{}, ModeAll, newTestStore())
tr2 := NewTrie(EmptyNode{}, false, newTestStore()) tr2 := NewTrie(EmptyNode{}, ModeAll, newTestStore())
require.NoError(t, tr1.Put([]byte{1, 2}, []byte("value1"))) require.NoError(t, tr1.Put([]byte{1, 2}, []byte("value1")))
require.NoError(t, tr2.Put([]byte{1, 2}, []byte("value1"))) require.NoError(t, tr2.Put([]byte{1, 2}, []byte("value1")))
return tr1, tr2 return tr1, tr2
@ -170,8 +170,8 @@ func TestTrie_PutBatchExtension(t *testing.T) {
func TestTrie_PutBatchBranch(t *testing.T) { func TestTrie_PutBatchBranch(t *testing.T) {
prepareBranch := func(t *testing.T) (*Trie, *Trie) { prepareBranch := func(t *testing.T) (*Trie, *Trie) {
tr1 := NewTrie(EmptyNode{}, false, newTestStore()) tr1 := NewTrie(EmptyNode{}, ModeAll, newTestStore())
tr2 := NewTrie(EmptyNode{}, false, newTestStore()) tr2 := NewTrie(EmptyNode{}, ModeAll, newTestStore())
require.NoError(t, tr1.Put([]byte{0x00, 2}, []byte("value1"))) require.NoError(t, tr1.Put([]byte{0x00, 2}, []byte("value1")))
require.NoError(t, tr2.Put([]byte{0x00, 2}, []byte("value1"))) require.NoError(t, tr2.Put([]byte{0x00, 2}, []byte("value1")))
require.NoError(t, tr1.Put([]byte{0x10, 3}, []byte("value2"))) require.NoError(t, tr1.Put([]byte{0x10, 3}, []byte("value2")))
@ -201,8 +201,8 @@ func TestTrie_PutBatchBranch(t *testing.T) {
require.IsType(t, (*ExtensionNode)(nil), tr1.root) require.IsType(t, (*ExtensionNode)(nil), tr1.root)
}) })
t.Run("non-empty child is last node", func(t *testing.T) { t.Run("non-empty child is last node", func(t *testing.T) {
tr1 := NewTrie(EmptyNode{}, false, newTestStore()) tr1 := NewTrie(EmptyNode{}, ModeAll, newTestStore())
tr2 := NewTrie(EmptyNode{}, false, newTestStore()) tr2 := NewTrie(EmptyNode{}, ModeAll, newTestStore())
require.NoError(t, tr1.Put([]byte{0x00, 2}, []byte("value1"))) require.NoError(t, tr1.Put([]byte{0x00, 2}, []byte("value1")))
require.NoError(t, tr2.Put([]byte{0x00, 2}, []byte("value1"))) require.NoError(t, tr2.Put([]byte{0x00, 2}, []byte("value1")))
require.NoError(t, tr1.Put([]byte{0x00}, []byte("value2"))) require.NoError(t, tr1.Put([]byte{0x00}, []byte("value2")))
@ -248,8 +248,8 @@ func TestTrie_PutBatchBranch(t *testing.T) {
func TestTrie_PutBatchHash(t *testing.T) { func TestTrie_PutBatchHash(t *testing.T) {
prepareHash := func(t *testing.T) (*Trie, *Trie) { prepareHash := func(t *testing.T) (*Trie, *Trie) {
tr1 := NewTrie(EmptyNode{}, false, newTestStore()) tr1 := NewTrie(EmptyNode{}, ModeAll, newTestStore())
tr2 := NewTrie(EmptyNode{}, false, newTestStore()) tr2 := NewTrie(EmptyNode{}, ModeAll, newTestStore())
require.NoError(t, tr1.Put([]byte{0x10}, []byte("value1"))) require.NoError(t, tr1.Put([]byte{0x10}, []byte("value1")))
require.NoError(t, tr2.Put([]byte{0x10}, []byte("value1"))) require.NoError(t, tr2.Put([]byte{0x10}, []byte("value1")))
require.NoError(t, tr1.Put([]byte{0x20}, []byte("value2"))) require.NoError(t, tr1.Put([]byte{0x20}, []byte("value2")))
@ -283,8 +283,8 @@ func TestTrie_PutBatchHash(t *testing.T) {
func TestTrie_PutBatchEmpty(t *testing.T) { func TestTrie_PutBatchEmpty(t *testing.T) {
t.Run("good", func(t *testing.T) { t.Run("good", func(t *testing.T) {
tr1 := NewTrie(EmptyNode{}, false, newTestStore()) tr1 := NewTrie(EmptyNode{}, ModeAll, newTestStore())
tr2 := NewTrie(EmptyNode{}, false, newTestStore()) tr2 := NewTrie(EmptyNode{}, ModeAll, newTestStore())
var ps = pairs{ var ps = pairs{
{[]byte{0}, []byte("value0")}, {[]byte{0}, []byte("value0")},
{[]byte{1}, []byte("value1")}, {[]byte{1}, []byte("value1")},
@ -299,15 +299,15 @@ func TestTrie_PutBatchEmpty(t *testing.T) {
{[]byte{2}, nil}, {[]byte{2}, nil},
{[]byte{3}, []byte("replace3")}, {[]byte{3}, []byte("replace3")},
} }
tr1 := NewTrie(EmptyNode{}, false, newTestStore()) tr1 := NewTrie(EmptyNode{}, ModeAll, newTestStore())
tr2 := NewTrie(EmptyNode{}, false, newTestStore()) tr2 := NewTrie(EmptyNode{}, ModeAll, newTestStore())
testIncompletePut(t, ps, 4, tr1, tr2) testIncompletePut(t, ps, 4, tr1, tr2)
}) })
} }
// For the sake of coverage. // For the sake of coverage.
func TestTrie_InvalidNodeType(t *testing.T) { func TestTrie_InvalidNodeType(t *testing.T) {
tr := NewTrie(EmptyNode{}, false, newTestStore()) tr := NewTrie(EmptyNode{}, ModeAll, newTestStore())
var b Batch var b Batch
b.Add([]byte{1}, []byte("value")) b.Add([]byte{1}, []byte("value"))
tr.root = Node(nil) tr.root = Node(nil)
@ -315,8 +315,8 @@ func TestTrie_InvalidNodeType(t *testing.T) {
} }
func TestTrie_PutBatch(t *testing.T) { func TestTrie_PutBatch(t *testing.T) {
tr1 := NewTrie(EmptyNode{}, false, newTestStore()) tr1 := NewTrie(EmptyNode{}, ModeAll, newTestStore())
tr2 := NewTrie(EmptyNode{}, false, newTestStore()) tr2 := NewTrie(EmptyNode{}, ModeAll, newTestStore())
var ps = pairs{ var ps = pairs{
{[]byte{1}, []byte{1}}, {[]byte{1}, []byte{1}},
{[]byte{2}, []byte{3}}, {[]byte{2}, []byte{3}},

View file

@ -32,19 +32,19 @@ type Billet struct {
Store *storage.MemCachedStore Store *storage.MemCachedStore
root Node root Node
refcountEnabled bool mode TrieMode
} }
// NewBillet returns new billet for MPT trie restoring. It accepts a MemCachedStore // NewBillet returns new billet for MPT trie restoring. It accepts a MemCachedStore
// to decouple storage errors from logic errors so that all storage errors are // to decouple storage errors from logic errors so that all storage errors are
// processed during `store.Persist()` at the caller. This also has the benefit, // processed during `store.Persist()` at the caller. This also has the benefit,
// that every `Put` can be considered an atomic operation. // that every `Put` can be considered an atomic operation.
func NewBillet(rootHash util.Uint256, enableRefCount bool, prefix storage.KeyPrefix, store *storage.MemCachedStore) *Billet { func NewBillet(rootHash util.Uint256, mode TrieMode, prefix storage.KeyPrefix, store *storage.MemCachedStore) *Billet {
return &Billet{ return &Billet{
TempStoragePrefix: prefix, TempStoragePrefix: prefix,
Store: store, Store: store,
root: NewHashNode(rootHash), root: NewHashNode(rootHash),
refcountEnabled: enableRefCount, mode: mode,
} }
} }
@ -178,7 +178,7 @@ func (b *Billet) putIntoHash(curr *HashNode, path []byte, val Node) (Node, error
func (b *Billet) incrementRefAndStore(h util.Uint256, bs []byte) { func (b *Billet) incrementRefAndStore(h util.Uint256, bs []byte) {
key := makeStorageKey(h) key := makeStorageKey(h)
if b.refcountEnabled { if b.mode.RC() {
var ( var (
err error err error
data []byte data []byte
@ -337,7 +337,7 @@ func (b *Billet) GetFromStore(h util.Uint256) (Node, error) {
return nil, r.Err return nil, r.Err
} }
if b.refcountEnabled { if b.mode.RC() {
data = data[:len(data)-4] data = data[:len(data)-4]
} }
n.Node.(flushedNode).setCache(data, h) n.Node.(flushedNode).setCache(data, h)

View file

@ -32,7 +32,7 @@ func TestBillet_RestoreHashNode(t *testing.T) {
b.Children[5] = NewExtensionNode([]byte{0x01}, NewLeafNode([]byte{0xAB, 0xDE})) b.Children[5] = NewExtensionNode([]byte{0x01}, NewLeafNode([]byte{0xAB, 0xDE}))
path := toNibbles([]byte{0xAC}) path := toNibbles([]byte{0xAC})
e := NewExtensionNode(path, NewHashNode(b.Hash())) e := NewExtensionNode(path, NewHashNode(b.Hash()))
tr := NewBillet(e.Hash(), true, storage.STTempStorage, newTestStore()) tr := NewBillet(e.Hash(), ModeLatest, storage.STTempStorage, newTestStore())
tr.root = e tr.root = e
// OK // OK
@ -61,7 +61,7 @@ func TestBillet_RestoreHashNode(t *testing.T) {
l := NewLeafNode([]byte{0xAB, 0xCD}) l := NewLeafNode([]byte{0xAB, 0xCD})
path := toNibbles([]byte{0xAC}) path := toNibbles([]byte{0xAC})
e := NewExtensionNode(path, NewHashNode(l.Hash())) e := NewExtensionNode(path, NewHashNode(l.Hash()))
tr := NewBillet(e.Hash(), true, storage.STTempStorage, newTestStore()) tr := NewBillet(e.Hash(), ModeLatest, storage.STTempStorage, newTestStore())
tr.root = e tr.root = e
// OK // OK
@ -87,7 +87,7 @@ func TestBillet_RestoreHashNode(t *testing.T) {
h := NewHashNode(util.Uint256{1, 2, 3}) h := NewHashNode(util.Uint256{1, 2, 3})
path := toNibbles([]byte{0xAC}) path := toNibbles([]byte{0xAC})
e := NewExtensionNode(path, h) e := NewExtensionNode(path, h)
tr := NewBillet(e.Hash(), true, storage.STTempStorage, newTestStore()) tr := NewBillet(e.Hash(), ModeLatest, storage.STTempStorage, newTestStore())
tr.root = e tr.root = e
// no-op // no-op
@ -99,7 +99,7 @@ func TestBillet_RestoreHashNode(t *testing.T) {
t.Run("parent is Leaf", func(t *testing.T) { t.Run("parent is Leaf", func(t *testing.T) {
l := NewLeafNode([]byte{0xAB, 0xCD}) l := NewLeafNode([]byte{0xAB, 0xCD})
path := []byte{} path := []byte{}
tr := NewBillet(l.Hash(), true, storage.STTempStorage, newTestStore()) tr := NewBillet(l.Hash(), ModeLatest, storage.STTempStorage, newTestStore())
tr.root = l tr.root = l
// Already restored => panic expected // Already restored => panic expected
@ -121,7 +121,7 @@ func TestBillet_RestoreHashNode(t *testing.T) {
b := NewBranchNode() b := NewBranchNode()
b.Children[5] = NewHashNode(l1.Hash()) b.Children[5] = NewHashNode(l1.Hash())
b.Children[lastChild] = NewHashNode(l2.Hash()) b.Children[lastChild] = NewHashNode(l2.Hash())
tr := NewBillet(b.Hash(), true, storage.STTempStorage, newTestStore()) tr := NewBillet(b.Hash(), ModeLatest, storage.STTempStorage, newTestStore())
tr.root = b tr.root = b
// OK // OK
@ -152,7 +152,7 @@ func TestBillet_RestoreHashNode(t *testing.T) {
b := NewBranchNode() b := NewBranchNode()
b.Children[5] = NewHashNode(l1.Hash()) b.Children[5] = NewHashNode(l1.Hash())
b.Children[lastChild] = NewHashNode(l2.Hash()) b.Children[lastChild] = NewHashNode(l2.Hash())
tr := NewBillet(b.Hash(), true, storage.STTempStorage, newTestStore()) tr := NewBillet(b.Hash(), ModeLatest, storage.STTempStorage, newTestStore())
tr.root = b tr.root = b
// OK // OK
@ -179,7 +179,7 @@ func TestBillet_RestoreHashNode(t *testing.T) {
// two same hashnodes => leaf's refcount expected to be 2 in the end. // two same hashnodes => leaf's refcount expected to be 2 in the end.
b.Children[3] = NewHashNode(l.Hash()) b.Children[3] = NewHashNode(l.Hash())
b.Children[4] = NewHashNode(l.Hash()) b.Children[4] = NewHashNode(l.Hash())
tr := NewBillet(b.Hash(), true, storage.STTempStorage, newTestStore()) tr := NewBillet(b.Hash(), ModeLatest, storage.STTempStorage, newTestStore())
tr.root = b tr.root = b
// OK // OK
@ -202,7 +202,7 @@ func TestBillet_RestoreHashNode(t *testing.T) {
b := NewBranchNode() b := NewBranchNode()
b.Children[3] = NewHashNode(l.Hash()) b.Children[3] = NewHashNode(l.Hash())
b.Children[4] = NewHashNode(l.Hash()) b.Children[4] = NewHashNode(l.Hash())
tr := NewBillet(b.Hash(), true, storage.STTempStorage, newTestStore()) tr := NewBillet(b.Hash(), ModeLatest, storage.STTempStorage, newTestStore())
// Should fail, because if it's a hash node with non-empty path, then the node // Should fail, because if it's a hash node with non-empty path, then the node
// has already been collapsed. // has already been collapsed.

View file

@ -23,7 +23,7 @@ func prepareMPTCompat() *Trie {
b.Children[16] = v2 b.Children[16] = v2
b.Children[15] = NewHashNode(e4.Hash()) b.Children[15] = NewHashNode(e4.Hash())
tr := NewTrie(r, true, newTestStore()) tr := NewTrie(r, ModeLatest, newTestStore())
tr.putToStore(r) tr.putToStore(r)
tr.putToStore(b) tr.putToStore(b)
tr.putToStore(e1) tr.putToStore(e1)
@ -132,7 +132,7 @@ func TestCompatibility(t *testing.T) {
b.Children[0] = e1 b.Children[0] = e1
b.Children[15] = NewHashNode(e4.Hash()) b.Children[15] = NewHashNode(e4.Hash())
tr := NewTrie(NewHashNode(r.Hash()), false, newTestStore()) tr := NewTrie(NewHashNode(r.Hash()), ModeAll, newTestStore())
tr.putToStore(r) tr.putToStore(r)
tr.putToStore(b) tr.putToStore(b)
tr.putToStore(e1) tr.putToStore(e1)
@ -152,7 +152,7 @@ func TestCompatibility(t *testing.T) {
tr.testHas(t, []byte{0xac, 0x02}, []byte{0xab, 0xcd}) tr.testHas(t, []byte{0xac, 0x02}, []byte{0xab, 0xcd})
tr.Flush() tr.Flush()
tr2 := NewTrie(NewHashNode(tr.root.Hash()), false, tr.Store) tr2 := NewTrie(NewHashNode(tr.root.Hash()), ModeAll, tr.Store)
tr2.testHas(t, []byte{0xac, 0x02}, []byte{0xab, 0xcd}) tr2.testHas(t, []byte{0xac, 0x02}, []byte{0xab, 0xcd})
}) })
@ -189,7 +189,7 @@ func TestCompatibility(t *testing.T) {
b.Children[16] = v2 b.Children[16] = v2
b.Children[15] = NewHashNode(e4.Hash()) b.Children[15] = NewHashNode(e4.Hash())
tr := NewTrie(NewHashNode(r.Hash()), true, mainTrie.Store) tr := NewTrie(NewHashNode(r.Hash()), ModeLatest, mainTrie.Store)
require.Equal(t, r.Hash(), tr.root.Hash()) require.Equal(t, r.Hash(), tr.root.Hash())
// Tail bytes contain reference counter thus check for prefix. // Tail bytes contain reference counter thus check for prefix.
@ -352,7 +352,7 @@ func TestCompatibility(t *testing.T) {
} }
func copyTrie(t *Trie) *Trie { func copyTrie(t *Trie) *Trie {
return NewTrie(NewHashNode(t.root.Hash()), t.refcountEnabled, t.Store) return NewTrie(NewHashNode(t.root.Hash()), t.mode, t.Store)
} }
func checkBatchSize(t *testing.T, tr *Trie, n int) { func checkBatchSize(t *testing.T, tr *Trie, n int) {
@ -372,7 +372,7 @@ func testGetProof(t *testing.T, tr *Trie, key []byte, size int) [][]byte {
} }
func newFilledTrie(t *testing.T, args ...[]byte) *Trie { func newFilledTrie(t *testing.T, args ...[]byte) *Trie {
tr := NewTrie(nil, true, newTestStore()) tr := NewTrie(nil, ModeLatest, newTestStore())
for i := 0; i < len(args); i += 2 { for i := 0; i < len(args); i += 2 {
require.NoError(t, tr.Put(args[i], args[i+1])) require.NoError(t, tr.Put(args[i], args[i+1]))
} }
@ -381,7 +381,7 @@ func newFilledTrie(t *testing.T, args ...[]byte) *Trie {
func TestCompatibility_Find(t *testing.T) { func TestCompatibility_Find(t *testing.T) {
check := func(t *testing.T, from []byte, expectedResLen int) { check := func(t *testing.T, from []byte, expectedResLen int) {
tr := NewTrie(nil, false, newTestStore()) tr := NewTrie(nil, ModeAll, newTestStore())
require.NoError(t, tr.Put([]byte("aa"), []byte("02"))) require.NoError(t, tr.Put([]byte("aa"), []byte("02")))
require.NoError(t, tr.Put([]byte("aa10"), []byte("03"))) require.NoError(t, tr.Put([]byte("aa10"), []byte("03")))
require.NoError(t, tr.Put([]byte("aa50"), []byte("04"))) require.NoError(t, tr.Put([]byte("aa50"), []byte("04")))
@ -407,7 +407,7 @@ func TestCompatibility_Find(t *testing.T) {
check(t, []byte{}, 2) // without `from` key check(t, []byte{}, 2) // without `from` key
}) })
t.Run("TestFindStatesIssue652", func(t *testing.T) { t.Run("TestFindStatesIssue652", func(t *testing.T) {
tr := NewTrie(nil, false, newTestStore()) tr := NewTrie(nil, ModeAll, newTestStore())
// root is an extension node with key=abc; next=branch // root is an extension node with key=abc; next=branch
require.NoError(t, tr.Put([]byte("abc1"), []byte("01"))) require.NoError(t, tr.Put([]byte("abc1"), []byte("01")))
require.NoError(t, tr.Put([]byte("abc3"), []byte("02"))) require.NoError(t, tr.Put([]byte("abc3"), []byte("02")))

View file

@ -93,7 +93,7 @@ func TestNode_Serializable(t *testing.T) {
// https://github.com/neo-project/neo/blob/neox-2.x/neo.UnitTests/UT_MPTTrie.cs#L198 // https://github.com/neo-project/neo/blob/neox-2.x/neo.UnitTests/UT_MPTTrie.cs#L198
func TestJSONSharp(t *testing.T) { func TestJSONSharp(t *testing.T) {
tr := NewTrie(nil, false, newTestStore()) tr := NewTrie(nil, ModeAll, newTestStore())
require.NoError(t, tr.Put([]byte{0xac, 0x11}, []byte{0xac, 0x11})) require.NoError(t, tr.Put([]byte{0xac, 0x11}, []byte{0xac, 0x11}))
require.NoError(t, tr.Put([]byte{0xac, 0x22}, []byte{0xac, 0x22})) require.NoError(t, tr.Put([]byte{0xac, 0x22}, []byte{0xac, 0x22}))
require.NoError(t, tr.Put([]byte{0xac}, []byte{0xac})) require.NoError(t, tr.Put([]byte{0xac}, []byte{0xac}))

View file

@ -66,7 +66,7 @@ func (t *Trie) getProof(curr Node, path []byte, proofs *[][]byte) (Node, error)
// It also returns value for the key. // It also returns value for the key.
func VerifyProof(rh util.Uint256, key []byte, proofs [][]byte) ([]byte, bool) { func VerifyProof(rh util.Uint256, key []byte, proofs [][]byte) ([]byte, bool) {
path := toNibbles(key) path := toNibbles(key)
tr := NewTrie(NewHashNode(rh), false, storage.NewMemCachedStore(storage.NewMemoryStore())) tr := NewTrie(NewHashNode(rh), ModeAll, storage.NewMemCachedStore(storage.NewMemoryStore()))
for i := range proofs { for i := range proofs {
h := hash.DoubleSha256(proofs[i]) h := hash.DoubleSha256(proofs[i])
// no errors in Put to memory store // no errors in Put to memory store

View file

@ -15,7 +15,7 @@ func newProofTrie(t *testing.T, missingHashNode bool) *Trie {
b.Children[4] = NewHashNode(e.Hash()) b.Children[4] = NewHashNode(e.Hash())
b.Children[5] = e2 b.Children[5] = e2
tr := NewTrie(b, false, newTestStore()) tr := NewTrie(b, ModeAll, newTestStore())
require.NoError(t, tr.Put([]byte{0x12, 0x31}, []byte("value1"))) require.NoError(t, tr.Put([]byte{0x12, 0x31}, []byte("value1")))
require.NoError(t, tr.Put([]byte{0x12, 0x32}, []byte("value2"))) require.NoError(t, tr.Put([]byte{0x12, 0x32}, []byte("value2")))
tr.putToStore(l) tr.putToStore(l)

View file

@ -12,12 +12,28 @@ import (
"github.com/nspcc-dev/neo-go/pkg/util/slice" "github.com/nspcc-dev/neo-go/pkg/util/slice"
) )
// TrieMode is the storage mode of trie, it affects the DB scheme.
type TrieMode byte
// TrieMode is the storage mode of trie.
const (
// ModeAll is used to store everything.
ModeAll TrieMode = 0
// ModeLatest is used to only store the latest root.
ModeLatest TrieMode = 0x01
// ModeGCFlag is a flag for GC.
ModeGCFlag TrieMode = 0x02
// ModeGC is used to store a set of roots with GC possible, it combines
// GCFlag and Latest (because it needs RC, but it has GC enabled).
ModeGC TrieMode = 0x03
)
// Trie is an MPT trie storing all key-value pairs. // Trie is an MPT trie storing all key-value pairs.
type Trie struct { type Trie struct {
Store *storage.MemCachedStore Store *storage.MemCachedStore
root Node root Node
refcountEnabled bool mode TrieMode
refcount map[util.Uint256]*cachedNode refcount map[util.Uint256]*cachedNode
} }
@ -30,10 +46,20 @@ type cachedNode struct {
// ErrNotFound is returned when requested trie item is missing. // ErrNotFound is returned when requested trie item is missing.
var ErrNotFound = errors.New("item not found") var ErrNotFound = errors.New("item not found")
// RC returns true when reference counting is enabled.
func (m TrieMode) RC() bool {
return m&ModeLatest != 0
}
// GC returns true when garbage collection is enabled.
func (m TrieMode) GC() bool {
return m&ModeGCFlag != 0
}
// NewTrie returns new MPT trie. It accepts a MemCachedStore to decouple storage errors from logic errors // NewTrie returns new MPT trie. It accepts a MemCachedStore to decouple storage errors from logic errors
// so that all storage errors are processed during `store.Persist()` at the caller. // so that all storage errors are processed during `store.Persist()` at the caller.
// This also has the benefit, that every `Put` can be considered an atomic operation. // This also has the benefit, that every `Put` can be considered an atomic operation.
func NewTrie(root Node, enableRefCount bool, store *storage.MemCachedStore) *Trie { func NewTrie(root Node, mode TrieMode, store *storage.MemCachedStore) *Trie {
if root == nil { if root == nil {
root = EmptyNode{} root = EmptyNode{}
} }
@ -42,7 +68,7 @@ func NewTrie(root Node, enableRefCount bool, store *storage.MemCachedStore) *Tri
Store: store, Store: store,
root: root, root: root,
refcountEnabled: enableRefCount, mode: mode,
refcount: make(map[util.Uint256]*cachedNode), refcount: make(map[util.Uint256]*cachedNode),
} }
} }
@ -386,7 +412,7 @@ func (t *Trie) Flush() {
if node.bytes == nil { if node.bytes == nil {
panic("item not in trie") panic("item not in trie")
} }
if t.refcountEnabled { if t.mode.RC() {
node.initial = t.updateRefCount(h) node.initial = t.updateRefCount(h)
if node.initial == 0 { if node.initial == 0 {
delete(t.refcount, h) delete(t.refcount, h)
@ -403,7 +429,7 @@ func (t *Trie) Flush() {
// updateRefCount should be called only when refcounting is enabled. // updateRefCount should be called only when refcounting is enabled.
func (t *Trie) updateRefCount(h util.Uint256) int32 { func (t *Trie) updateRefCount(h util.Uint256) int32 {
if !t.refcountEnabled { if !t.mode.RC() {
panic("`updateRefCount` is called, but GC is disabled") panic("`updateRefCount` is called, but GC is disabled")
} }
var data []byte var data []byte
@ -478,7 +504,7 @@ func (t *Trie) getFromStore(h util.Uint256) (Node, error) {
return nil, r.Err return nil, r.Err
} }
if t.refcountEnabled { if t.mode.RC() {
data = data[:len(data)-4] data = data[:len(data)-4]
node := t.refcount[h] node := t.refcount[h]
if node != nil { if node != nil {
@ -566,7 +592,7 @@ func (t *Trie) Find(prefix, from []byte, max int) ([]storage.KeyValue, error) {
res []storage.KeyValue res []storage.KeyValue
count int count int
) )
b := NewBillet(t.root.Hash(), false, 0, t.Store) b := NewBillet(t.root.Hash(), t.mode, 0, t.Store)
process := func(pathToNode []byte, node Node, _ []byte) bool { process := func(pathToNode []byte, node Node, _ []byte) bool {
if leaf, ok := node.(*LeafNode); ok { if leaf, ok := node.(*LeafNode); ok {
if from == nil || !bytes.Equal(pathToNode, from) { // (*Billet).traverse includes `from` path into result if so. Need to filter out manually. if from == nil || !bytes.Equal(pathToNode, from) { // (*Billet).traverse includes `from` path into result if so. Need to filter out manually.

View file

@ -29,7 +29,7 @@ func newTestTrie(t *testing.T) *Trie {
b.Children[10] = NewExtensionNode([]byte{0x0e}, h) b.Children[10] = NewExtensionNode([]byte{0x0e}, h)
e := NewExtensionNode(toNibbles([]byte{0xAC}), b) e := NewExtensionNode(toNibbles([]byte{0xAC}), b)
tr := NewTrie(e, false, newTestStore()) tr := NewTrie(e, ModeAll, newTestStore())
tr.putToStore(e) tr.putToStore(e)
tr.putToStore(b) tr.putToStore(b)
@ -46,7 +46,7 @@ func newTestTrie(t *testing.T) *Trie {
} }
func testTrieRefcount(t *testing.T, key1, key2 []byte) { func testTrieRefcount(t *testing.T, key1, key2 []byte) {
tr := NewTrie(nil, true, storage.NewMemCachedStore(storage.NewMemoryStore())) tr := NewTrie(nil, ModeLatest, storage.NewMemCachedStore(storage.NewMemoryStore()))
require.NoError(t, tr.Put(key1, []byte{1})) require.NoError(t, tr.Put(key1, []byte{1}))
tr.Flush() tr.Flush()
require.NoError(t, tr.Put(key2, []byte{1})) require.NoError(t, tr.Put(key2, []byte{1}))
@ -89,7 +89,7 @@ func TestTrie_PutIntoBranchNode(t *testing.T) {
l := NewLeafNode([]byte{0x8}) l := NewLeafNode([]byte{0x8})
b.Children[0x7] = NewHashNode(l.Hash()) b.Children[0x7] = NewHashNode(l.Hash())
b.Children[0x8] = NewHashNode(random.Uint256()) b.Children[0x8] = NewHashNode(random.Uint256())
tr := NewTrie(b, false, newTestStore()) tr := NewTrie(b, ModeAll, newTestStore())
// empty hash node child // empty hash node child
require.NoError(t, tr.Put([]byte{0x66}, value)) require.NoError(t, tr.Put([]byte{0x66}, value))
@ -119,7 +119,7 @@ func TestTrie_PutIntoExtensionNode(t *testing.T) {
l := NewLeafNode([]byte{0x11}) l := NewLeafNode([]byte{0x11})
key := []byte{0x12} key := []byte{0x12}
e := NewExtensionNode(toNibbles(key), NewHashNode(l.Hash())) e := NewExtensionNode(toNibbles(key), NewHashNode(l.Hash()))
tr := NewTrie(e, false, newTestStore()) tr := NewTrie(e, ModeAll, newTestStore())
// missing hash // missing hash
require.Error(t, tr.Put(key, value)) require.Error(t, tr.Put(key, value))
@ -145,7 +145,7 @@ func TestTrie_PutIntoHashNode(t *testing.T) {
e := NewExtensionNode([]byte{0x02}, l) e := NewExtensionNode([]byte{0x02}, l)
b.Children[1] = NewHashNode(e.Hash()) b.Children[1] = NewHashNode(e.Hash())
b.Children[9] = NewHashNode(random.Uint256()) b.Children[9] = NewHashNode(random.Uint256())
tr := NewTrie(b, false, newTestStore()) tr := NewTrie(b, ModeAll, newTestStore())
tr.putToStore(e) tr.putToStore(e)
@ -174,7 +174,7 @@ func TestTrie_PutIntoHashNode(t *testing.T) {
func TestTrie_Put(t *testing.T) { func TestTrie_Put(t *testing.T) {
trExp := newTestTrie(t) trExp := newTestTrie(t)
trAct := NewTrie(nil, false, newTestStore()) trAct := NewTrie(nil, ModeAll, newTestStore())
require.NoError(t, trAct.Put([]byte{0xAC, 0x01}, []byte{0xAB, 0xCD})) require.NoError(t, trAct.Put([]byte{0xAC, 0x01}, []byte{0xAB, 0xCD}))
require.NoError(t, trAct.Put([]byte{0xAC, 0x13}, []byte{})) require.NoError(t, trAct.Put([]byte{0xAC, 0x13}, []byte{}))
require.NoError(t, trAct.Put([]byte{0xAC, 0x99}, []byte{0x22, 0x22})) require.NoError(t, trAct.Put([]byte{0xAC, 0x99}, []byte{0x22, 0x22}))
@ -186,7 +186,7 @@ func TestTrie_Put(t *testing.T) {
} }
func TestTrie_PutInvalid(t *testing.T) { func TestTrie_PutInvalid(t *testing.T) {
tr := NewTrie(nil, false, newTestStore()) tr := NewTrie(nil, ModeAll, newTestStore())
key, value := []byte("key"), []byte("value") key, value := []byte("key"), []byte("value")
// empty key // empty key
@ -204,7 +204,7 @@ func TestTrie_PutInvalid(t *testing.T) {
} }
func TestTrie_BigPut(t *testing.T) { func TestTrie_BigPut(t *testing.T) {
tr := NewTrie(nil, false, newTestStore()) tr := NewTrie(nil, ModeAll, newTestStore())
items := []struct{ k, v string }{ items := []struct{ k, v string }{
{"item with long key", "value1"}, {"item with long key", "value1"},
{"item with matching prefix", "value2"}, {"item with matching prefix", "value2"},
@ -244,7 +244,7 @@ func (tr *Trie) putToStore(n Node) {
if n.Type() == HashT { if n.Type() == HashT {
panic("can't put hash node in trie") panic("can't put hash node in trie")
} }
if tr.refcountEnabled { if tr.mode.RC() {
tr.refcount[n.Hash()] = &cachedNode{ tr.refcount[n.Hash()] = &cachedNode{
bytes: n.Bytes(), bytes: n.Bytes(),
refcount: 1, refcount: 1,
@ -298,7 +298,7 @@ func TestTrie_Get(t *testing.T) {
}) })
t.Run("UnfoldRoot", func(t *testing.T) { t.Run("UnfoldRoot", func(t *testing.T) {
tr := newTestTrie(t) tr := newTestTrie(t)
single := NewTrie(NewHashNode(tr.root.Hash()), false, tr.Store) single := NewTrie(NewHashNode(tr.root.Hash()), ModeAll, tr.Store)
single.testHas(t, []byte{0xAC}, nil) single.testHas(t, []byte{0xAC}, nil)
single.testHas(t, []byte{0xAC, 0x01}, []byte{0xAB, 0xCD}) single.testHas(t, []byte{0xAC, 0x01}, []byte{0xAB, 0xCD})
single.testHas(t, []byte{0xAC, 0x99}, []byte{0x22, 0x22}) single.testHas(t, []byte{0xAC, 0x99}, []byte{0x22, 0x22})
@ -313,13 +313,13 @@ func TestTrie_Flush(t *testing.T) {
"key2": []byte("value2"), "key2": []byte("value2"),
} }
tr := NewTrie(nil, false, newTestStore()) tr := NewTrie(nil, ModeAll, newTestStore())
for k, v := range pairs { for k, v := range pairs {
require.NoError(t, tr.Put([]byte(k), v)) require.NoError(t, tr.Put([]byte(k), v))
} }
tr.Flush() tr.Flush()
tr = NewTrie(NewHashNode(tr.StateRoot()), false, tr.Store) tr = NewTrie(NewHashNode(tr.StateRoot()), ModeAll, tr.Store)
for k, v := range pairs { for k, v := range pairs {
actual, err := tr.Get([]byte(k)) actual, err := tr.Get([]byte(k))
require.NoError(t, err) require.NoError(t, err)
@ -337,10 +337,14 @@ func TestTrie_Delete(t *testing.T) {
} }
func testTrieDelete(t *testing.T, enableGC bool) { func testTrieDelete(t *testing.T, enableGC bool) {
var mode TrieMode
if enableGC {
mode = ModeLatest
}
t.Run("Hash", func(t *testing.T) { t.Run("Hash", func(t *testing.T) {
t.Run("FromStore", func(t *testing.T) { t.Run("FromStore", func(t *testing.T) {
l := NewLeafNode([]byte{0x12}) l := NewLeafNode([]byte{0x12})
tr := NewTrie(NewHashNode(l.Hash()), enableGC, newTestStore()) tr := NewTrie(NewHashNode(l.Hash()), mode, newTestStore())
t.Run("NotInStore", func(t *testing.T) { t.Run("NotInStore", func(t *testing.T) {
require.Error(t, tr.Delete([]byte{})) require.Error(t, tr.Delete([]byte{}))
}) })
@ -352,7 +356,7 @@ func testTrieDelete(t *testing.T, enableGC bool) {
}) })
t.Run("Empty", func(t *testing.T) { t.Run("Empty", func(t *testing.T) {
tr := NewTrie(nil, enableGC, newTestStore()) tr := NewTrie(nil, mode, newTestStore())
require.NoError(t, tr.Delete([]byte{})) require.NoError(t, tr.Delete([]byte{}))
}) })
}) })
@ -360,7 +364,7 @@ func testTrieDelete(t *testing.T, enableGC bool) {
t.Run("Leaf", func(t *testing.T) { t.Run("Leaf", func(t *testing.T) {
check := func(t *testing.T, value []byte) { check := func(t *testing.T, value []byte) {
l := NewLeafNode(value) l := NewLeafNode(value)
tr := NewTrie(l, enableGC, newTestStore()) tr := NewTrie(l, mode, newTestStore())
t.Run("NonExistentKey", func(t *testing.T) { t.Run("NonExistentKey", func(t *testing.T) {
require.NoError(t, tr.Delete([]byte{0x12})) require.NoError(t, tr.Delete([]byte{0x12}))
tr.testHas(t, []byte{}, value) tr.testHas(t, []byte{}, value)
@ -381,7 +385,7 @@ func testTrieDelete(t *testing.T, enableGC bool) {
check := func(t *testing.T, value []byte) { check := func(t *testing.T, value []byte) {
l := NewLeafNode(value) l := NewLeafNode(value)
e := NewExtensionNode([]byte{0x0A, 0x0B}, l) e := NewExtensionNode([]byte{0x0A, 0x0B}, l)
tr := NewTrie(e, enableGC, newTestStore()) tr := NewTrie(e, mode, newTestStore())
t.Run("NonExistentKey", func(t *testing.T) { t.Run("NonExistentKey", func(t *testing.T) {
require.NoError(t, tr.Delete([]byte{})) require.NoError(t, tr.Delete([]byte{}))
@ -405,7 +409,7 @@ func testTrieDelete(t *testing.T, enableGC bool) {
b.Children[0] = NewExtensionNode([]byte{0x01}, NewLeafNode(value)) b.Children[0] = NewExtensionNode([]byte{0x01}, NewLeafNode(value))
b.Children[6] = NewExtensionNode([]byte{0x07}, NewLeafNode([]byte{0x56, 0x78})) b.Children[6] = NewExtensionNode([]byte{0x07}, NewLeafNode([]byte{0x56, 0x78}))
e := NewExtensionNode([]byte{0x01, 0x02}, b) e := NewExtensionNode([]byte{0x01, 0x02}, b)
tr := NewTrie(e, enableGC, newTestStore()) tr := NewTrie(e, mode, newTestStore())
h := e.Hash() h := e.Hash()
require.NoError(t, tr.Delete([]byte{0x12, 0x01})) require.NoError(t, tr.Delete([]byte{0x12, 0x01}))
@ -432,7 +436,7 @@ func testTrieDelete(t *testing.T, enableGC bool) {
b.Children[lastChild] = NewLeafNode([]byte{0x12}) b.Children[lastChild] = NewLeafNode([]byte{0x12})
b.Children[0] = NewExtensionNode([]byte{0x01}, NewLeafNode([]byte{0x34})) b.Children[0] = NewExtensionNode([]byte{0x01}, NewLeafNode([]byte{0x34}))
b.Children[1] = NewExtensionNode([]byte{0x06}, NewLeafNode(value)) b.Children[1] = NewExtensionNode([]byte{0x06}, NewLeafNode(value))
tr := NewTrie(b, enableGC, newTestStore()) tr := NewTrie(b, mode, newTestStore())
require.NoError(t, tr.Delete([]byte{0x16})) require.NoError(t, tr.Delete([]byte{0x16}))
tr.testHas(t, []byte{}, []byte{0x12}) tr.testHas(t, []byte{}, []byte{0x12})
tr.testHas(t, []byte{0x01}, []byte{0x34}) tr.testHas(t, []byte{0x01}, []byte{0x34})
@ -454,7 +458,7 @@ func testTrieDelete(t *testing.T, enableGC bool) {
l := NewLeafNode([]byte{0x34}) l := NewLeafNode([]byte{0x34})
e := NewExtensionNode([]byte{0x06}, l) e := NewExtensionNode([]byte{0x06}, l)
b.Children[5] = NewHashNode(e.Hash()) b.Children[5] = NewHashNode(e.Hash())
tr := NewTrie(b, enableGC, newTestStore()) tr := NewTrie(b, mode, newTestStore())
tr.putToStore(l) tr.putToStore(l)
tr.putToStore(e) tr.putToStore(e)
require.NoError(t, tr.Delete([]byte{})) require.NoError(t, tr.Delete([]byte{}))
@ -478,7 +482,7 @@ func testTrieDelete(t *testing.T, enableGC bool) {
b.Children[3] = NewExtensionNode([]byte{4}, NewLeafNode(value)) b.Children[3] = NewExtensionNode([]byte{4}, NewLeafNode(value))
b.Children[lastChild] = NewHashNode(h) b.Children[lastChild] = NewHashNode(h)
tr := NewTrie(NewExtensionNode([]byte{1, 2}, b), enableGC, newTestStore()) tr := NewTrie(NewExtensionNode([]byte{1, 2}, b), mode, newTestStore())
tr.putToStore(ch) tr.putToStore(ch)
require.NoError(t, tr.Delete([]byte{0x12, 0x34})) require.NoError(t, tr.Delete([]byte{0x12, 0x34}))
@ -505,7 +509,7 @@ func testTrieDelete(t *testing.T, enableGC bool) {
b := NewBranchNode() b := NewBranchNode()
b.Children[lastChild] = NewLeafNode(value) b.Children[lastChild] = NewLeafNode(value)
b.Children[5] = c b.Children[5] = c
tr := NewTrie(b, enableGC, newTestStore()) tr := NewTrie(b, mode, newTestStore())
require.NoError(t, tr.Delete([]byte{})) require.NoError(t, tr.Delete([]byte{}))
tr.testHas(t, []byte{}, nil) tr.testHas(t, []byte{}, nil)
@ -529,7 +533,7 @@ func testTrieDelete(t *testing.T, enableGC bool) {
l := NewLeafNode(value) l := NewLeafNode(value)
e := NewExtensionNode([]byte{0x06}, l) e := NewExtensionNode([]byte{0x06}, l)
b.Children[5] = NewHashNode(e.Hash()) b.Children[5] = NewHashNode(e.Hash())
tr := NewTrie(b, enableGC, newTestStore()) tr := NewTrie(b, mode, newTestStore())
tr.putToStore(l) tr.putToStore(l)
tr.putToStore(e) tr.putToStore(e)
require.NoError(t, tr.Delete([]byte{0x56})) require.NoError(t, tr.Delete([]byte{0x56}))
@ -579,7 +583,7 @@ func TestTrie_Collapse(t *testing.T) {
b.Children[0] = e b.Children[0] = e
hb := b.Hash() hb := b.Hash()
tr := NewTrie(b, false, newTestStore()) tr := NewTrie(b, ModeAll, newTestStore())
tr.Collapse(1) tr.Collapse(1)
newb, ok := tr.root.(*BranchNode) newb, ok := tr.root.(*BranchNode)
@ -593,7 +597,7 @@ func TestTrie_Collapse(t *testing.T) {
hl := l.Hash() hl := l.Hash()
e := NewExtensionNode([]byte{0x01}, l) e := NewExtensionNode([]byte{0x01}, l)
h := e.Hash() h := e.Hash()
tr := NewTrie(e, false, newTestStore()) tr := NewTrie(e, ModeAll, newTestStore())
tr.Collapse(1) tr.Collapse(1)
newe, ok := tr.root.(*ExtensionNode) newe, ok := tr.root.(*ExtensionNode)
@ -604,19 +608,19 @@ func TestTrie_Collapse(t *testing.T) {
}) })
t.Run("Leaf", func(t *testing.T) { t.Run("Leaf", func(t *testing.T) {
l := NewLeafNode([]byte("value")) l := NewLeafNode([]byte("value"))
tr := NewTrie(l, false, newTestStore()) tr := NewTrie(l, ModeAll, newTestStore())
tr.Collapse(10) tr.Collapse(10)
require.Equal(t, NewLeafNode([]byte("value")), tr.root) require.Equal(t, NewLeafNode([]byte("value")), tr.root)
}) })
t.Run("Empty Leaf", func(t *testing.T) { t.Run("Empty Leaf", func(t *testing.T) {
l := NewLeafNode([]byte{}) l := NewLeafNode([]byte{})
tr := NewTrie(l, false, newTestStore()) tr := NewTrie(l, ModeAll, newTestStore())
tr.Collapse(10) tr.Collapse(10)
require.Equal(t, NewLeafNode([]byte{}), tr.root) require.Equal(t, NewLeafNode([]byte{}), tr.root)
}) })
t.Run("Hash", func(t *testing.T) { t.Run("Hash", func(t *testing.T) {
t.Run("EmptyNode", func(t *testing.T) { t.Run("EmptyNode", func(t *testing.T) {
tr := NewTrie(EmptyNode{}, false, newTestStore()) tr := NewTrie(EmptyNode{}, ModeAll, newTestStore())
require.NotPanics(t, func() { tr.Collapse(1) }) require.NotPanics(t, func() { tr.Collapse(1) })
_, ok := tr.root.(EmptyNode) _, ok := tr.root.(EmptyNode)
require.True(t, ok) require.True(t, ok)
@ -624,7 +628,7 @@ func TestTrie_Collapse(t *testing.T) {
h := random.Uint256() h := random.Uint256()
hn := NewHashNode(h) hn := NewHashNode(h)
tr := NewTrie(hn, false, newTestStore()) tr := NewTrie(hn, ModeAll, newTestStore())
tr.Collapse(10) tr.Collapse(10)
newRoot, ok := tr.root.(*HashNode) newRoot, ok := tr.root.(*HashNode)

View file

@ -28,6 +28,7 @@ type (
Store *storage.MemCachedStore Store *storage.MemCachedStore
network netmode.Magic network netmode.Magic
srInHead bool srInHead bool
mode mpt.TrieMode
mpt *mpt.Trie mpt *mpt.Trie
verifier VerifierFunc verifier VerifierFunc
log *zap.Logger log *zap.Logger
@ -52,9 +53,14 @@ type (
// NewModule returns new instance of stateroot module. // NewModule returns new instance of stateroot module.
func NewModule(cfg config.ProtocolConfiguration, verif VerifierFunc, log *zap.Logger, s *storage.MemCachedStore) *Module { func NewModule(cfg config.ProtocolConfiguration, verif VerifierFunc, log *zap.Logger, s *storage.MemCachedStore) *Module {
var mode mpt.TrieMode
if cfg.KeepOnlyLatestState {
mode |= mpt.ModeLatest
}
return &Module{ return &Module{
network: cfg.Magic, network: cfg.Magic,
srInHead: cfg.StateRootInHeader, srInHead: cfg.StateRootInHeader,
mode: mode,
verifier: verif, verifier: verif,
log: log, log: log,
Store: s, Store: s,
@ -63,7 +69,7 @@ func NewModule(cfg config.ProtocolConfiguration, verif VerifierFunc, log *zap.Lo
// GetState returns value at the specified key fom the MPT with the specified root. // GetState returns value at the specified key fom the MPT with the specified root.
func (s *Module) GetState(root util.Uint256, key []byte) ([]byte, error) { func (s *Module) GetState(root util.Uint256, key []byte) ([]byte, error) {
tr := mpt.NewTrie(mpt.NewHashNode(root), false, storage.NewMemCachedStore(s.Store)) tr := mpt.NewTrie(mpt.NewHashNode(root), s.mode, storage.NewMemCachedStore(s.Store))
return tr.Get(key) return tr.Get(key)
} }
@ -73,13 +79,13 @@ func (s *Module) GetState(root util.Uint256, key []byte) ([]byte, error) {
// item with key equals to prefix is included into result; if empty `start` specified, // item with key equals to prefix is included into result; if empty `start` specified,
// then item with key equals to prefix is not included into result. // then item with key equals to prefix is not included into result.
func (s *Module) FindStates(root util.Uint256, prefix, start []byte, max int) ([]storage.KeyValue, error) { func (s *Module) FindStates(root util.Uint256, prefix, start []byte, max int) ([]storage.KeyValue, error) {
tr := mpt.NewTrie(mpt.NewHashNode(root), false, storage.NewMemCachedStore(s.Store)) tr := mpt.NewTrie(mpt.NewHashNode(root), s.mode, storage.NewMemCachedStore(s.Store))
return tr.Find(prefix, start, max) return tr.Find(prefix, start, max)
} }
// GetStateProof returns proof of having key in the MPT with the specified root. // GetStateProof returns proof of having key in the MPT with the specified root.
func (s *Module) GetStateProof(root util.Uint256, key []byte) ([][]byte, error) { func (s *Module) GetStateProof(root util.Uint256, key []byte) ([][]byte, error) {
tr := mpt.NewTrie(mpt.NewHashNode(root), false, storage.NewMemCachedStore(s.Store)) tr := mpt.NewTrie(mpt.NewHashNode(root), s.mode, storage.NewMemCachedStore(s.Store))
return tr.GetProof(key) return tr.GetProof(key)
} }
@ -104,14 +110,14 @@ func (s *Module) CurrentValidatedHeight() uint32 {
} }
// Init initializes state root module at the given height. // Init initializes state root module at the given height.
func (s *Module) Init(height uint32, enableRefCount bool) error { func (s *Module) Init(height uint32) error {
data, err := s.Store.Get([]byte{byte(storage.DataMPT), prefixValidated}) data, err := s.Store.Get([]byte{byte(storage.DataMPT), prefixValidated})
if err == nil { if err == nil {
s.validatedHeight.Store(binary.LittleEndian.Uint32(data)) s.validatedHeight.Store(binary.LittleEndian.Uint32(data))
} }
if height == 0 { if height == 0 {
s.mpt = mpt.NewTrie(nil, enableRefCount, s.Store) s.mpt = mpt.NewTrie(nil, s.mode, s.Store)
s.currentLocal.Store(util.Uint256{}) s.currentLocal.Store(util.Uint256{})
return nil return nil
} }
@ -121,7 +127,7 @@ func (s *Module) Init(height uint32, enableRefCount bool) error {
} }
s.currentLocal.Store(r.Root) s.currentLocal.Store(r.Root)
s.localHeight.Store(r.Index) s.localHeight.Store(r.Index)
s.mpt = mpt.NewTrie(mpt.NewHashNode(r.Root), enableRefCount, s.Store) s.mpt = mpt.NewTrie(mpt.NewHashNode(r.Root), s.mode, s.Store)
return nil return nil
} }
@ -157,7 +163,7 @@ func (s *Module) CleanStorage() error {
} }
// JumpToState performs jump to the state specified by given stateroot index. // JumpToState performs jump to the state specified by given stateroot index.
func (s *Module) JumpToState(sr *state.MPTRoot, enableRefCount bool) error { func (s *Module) JumpToState(sr *state.MPTRoot) error {
if err := s.addLocalStateRoot(s.Store, sr); err != nil { if err := s.addLocalStateRoot(s.Store, sr); err != nil {
return fmt.Errorf("failed to store local state root: %w", err) return fmt.Errorf("failed to store local state root: %w", err)
} }
@ -171,7 +177,7 @@ func (s *Module) JumpToState(sr *state.MPTRoot, enableRefCount bool) error {
s.currentLocal.Store(sr.Root) s.currentLocal.Store(sr.Root)
s.localHeight.Store(sr.Index) s.localHeight.Store(sr.Index)
s.mpt = mpt.NewTrie(mpt.NewHashNode(sr.Root), enableRefCount, s.Store) s.mpt = mpt.NewTrie(mpt.NewHashNode(sr.Root), s.mode, s.Store)
return nil return nil
} }

View file

@ -221,7 +221,11 @@ func (s *Module) defineSyncStage() error {
if err != nil { if err != nil {
return fmt.Errorf("failed to get header to initialize MPT billet: %w", err) return fmt.Errorf("failed to get header to initialize MPT billet: %w", err)
} }
s.billet = mpt.NewBillet(header.PrevStateRoot, s.bc.GetConfig().KeepOnlyLatestState, var mode mpt.TrieMode
if s.bc.GetConfig().KeepOnlyLatestState {
mode |= mpt.ModeLatest
}
s.billet = mpt.NewBillet(header.PrevStateRoot, mode,
TemporaryPrefix(s.dao.Version.StoragePrefix), s.dao.Store) TemporaryPrefix(s.dao.Version.StoragePrefix), s.dao.Store)
s.log.Info("MPT billet initialized", s.log.Info("MPT billet initialized",
zap.Uint32("height", s.syncPoint), zap.Uint32("height", s.syncPoint),
@ -494,7 +498,11 @@ func (s *Module) Traverse(root util.Uint256, process func(node mpt.Node, nodeByt
s.lock.RLock() s.lock.RLock()
defer s.lock.RUnlock() defer s.lock.RUnlock()
b := mpt.NewBillet(root, s.bc.GetConfig().KeepOnlyLatestState, 0, storage.NewMemCachedStore(s.dao.Store)) var mode mpt.TrieMode
if s.bc.GetConfig().KeepOnlyLatestState {
mode |= mpt.ModeLatest
}
b := mpt.NewBillet(root, mode, 0, storage.NewMemCachedStore(s.dao.Store))
return b.Traverse(func(pathToNode []byte, node mpt.Node, nodeBytes []byte) bool { return b.Traverse(func(pathToNode []byte, node mpt.Node, nodeBytes []byte) bool {
return process(node, nodeBytes) return process(node, nodeBytes)
}, false) }, false)

View file

@ -15,7 +15,7 @@ import (
func TestModule_PR2019_discussion_r689629704(t *testing.T) { func TestModule_PR2019_discussion_r689629704(t *testing.T) {
expectedStorage := storage.NewMemCachedStore(storage.NewMemoryStore()) expectedStorage := storage.NewMemCachedStore(storage.NewMemoryStore())
tr := mpt.NewTrie(nil, true, expectedStorage) tr := mpt.NewTrie(nil, mpt.ModeLatest, expectedStorage)
require.NoError(t, tr.Put([]byte{0x03}, []byte("leaf1"))) require.NoError(t, tr.Put([]byte{0x03}, []byte("leaf1")))
require.NoError(t, tr.Put([]byte{0x01, 0xab, 0x02}, []byte("leaf2"))) require.NoError(t, tr.Put([]byte{0x01, 0xab, 0x02}, []byte("leaf2")))
require.NoError(t, tr.Put([]byte{0x01, 0xab, 0x04}, []byte("leaf3"))) require.NoError(t, tr.Put([]byte{0x01, 0xab, 0x04}, []byte("leaf3")))
@ -57,7 +57,7 @@ func TestModule_PR2019_discussion_r689629704(t *testing.T) {
dao: dao.NewSimple(actualStorage, true, false), dao: dao.NewSimple(actualStorage, true, false),
mptpool: NewPool(), mptpool: NewPool(),
} }
stateSync.billet = mpt.NewBillet(sr, true, stateSync.billet = mpt.NewBillet(sr, mpt.ModeLatest,
TemporaryPrefix(stateSync.dao.Version.StoragePrefix), actualStorage) TemporaryPrefix(stateSync.dao.Version.StoragePrefix), actualStorage)
stateSync.mptpool.Add(sr, []byte{}) stateSync.mptpool.Add(sr, []byte{})