forked from TrueCloudLab/neoneo-go
storage: expose private storage map for more efficient MPT batch
It couldn't be done previously with two maps and mixed storage, but now all of the storage changes are located in a single map, so it's trivial to do exact slice allocations and avoid string->[]byte conversions.
This commit is contained in:
parent
7dc8fc443f
commit
f80680187e
6 changed files with 44 additions and 48 deletions
|
@ -20,6 +20,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/mempool"
|
"github.com/nspcc-dev/neo-go/pkg/core/mempool"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/mpt"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
|
@ -1138,7 +1139,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
||||||
appExecResults = append(appExecResults, aer)
|
appExecResults = append(appExecResults, aer)
|
||||||
aerchan <- aer
|
aerchan <- aer
|
||||||
close(aerchan)
|
close(aerchan)
|
||||||
b := cache.GetMPTBatch()
|
b := mpt.MapToMPTBatch(cache.Store.GetStorageChanges())
|
||||||
mpt, sr, err := bc.stateRoot.AddMPTBatch(block.Index, b, cache.Store)
|
mpt, sr, err := bc.stateRoot.AddMPTBatch(block.Index, b, cache.Store)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Release goroutines, don't care about errors, we already have one.
|
// Release goroutines, don't care about errors, we already have one.
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/mpt"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
@ -771,12 +770,3 @@ func (dao *Simple) Persist() (int, error) {
|
||||||
func (dao *Simple) PersistSync() (int, error) {
|
func (dao *Simple) PersistSync() (int, error) {
|
||||||
return dao.Store.PersistSync()
|
return dao.Store.PersistSync()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMPTBatch storage changes to be applied to MPT.
|
|
||||||
func (dao *Simple) GetMPTBatch() mpt.Batch {
|
|
||||||
var b mpt.Batch
|
|
||||||
dao.Store.SeekAll([]byte{byte(dao.Version.StoragePrefix)}, func(k, v []byte) {
|
|
||||||
b.Add(k[1:], v)
|
|
||||||
})
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
|
@ -16,23 +16,19 @@ type keyValue struct {
|
||||||
value []byte
|
value []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add adds key-value pair to batch.
|
// MapToMPTBatch makes a Batch from unordered set of storage changes.
|
||||||
// If there is an item with the specified key, it is replaced.
|
func MapToMPTBatch(m map[string][]byte) Batch {
|
||||||
func (b *Batch) Add(key []byte, value []byte) {
|
var b Batch
|
||||||
path := toNibbles(key)
|
|
||||||
i := sort.Search(len(b.kv), func(i int) bool {
|
b.kv = make([]keyValue, 0, len(m))
|
||||||
return bytes.Compare(path, b.kv[i].key) <= 0
|
|
||||||
})
|
for k, v := range m {
|
||||||
if i == len(b.kv) {
|
b.kv = append(b.kv, keyValue{strToNibbles(k), v}) // Strip storage prefix.
|
||||||
b.kv = append(b.kv, keyValue{path, value})
|
|
||||||
} else if bytes.Equal(b.kv[i].key, path) {
|
|
||||||
b.kv[i].value = value
|
|
||||||
} else {
|
|
||||||
b.kv = append(b.kv, keyValue{})
|
|
||||||
copy(b.kv[i+1:], b.kv[i:])
|
|
||||||
b.kv[i].key = path
|
|
||||||
b.kv[i].value = value
|
|
||||||
}
|
}
|
||||||
|
sort.Slice(b.kv, func(i, j int) bool {
|
||||||
|
return bytes.Compare(b.kv[i].key, b.kv[j].key) < 0
|
||||||
|
})
|
||||||
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutBatch puts batch to trie.
|
// PutBatch puts batch to trie.
|
||||||
|
|
|
@ -10,12 +10,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBatchAdd(t *testing.T) {
|
func TestBatchAdd(t *testing.T) {
|
||||||
b := new(Batch)
|
b := MapToMPTBatch(map[string][]byte{
|
||||||
b.Add([]byte{1}, []byte{2})
|
"a\x01": {2},
|
||||||
b.Add([]byte{2, 16}, []byte{3})
|
"a\x02\x10": {3},
|
||||||
b.Add([]byte{2, 0}, []byte{4})
|
"a\x00\x01": {5},
|
||||||
b.Add([]byte{0, 1}, []byte{5})
|
"a\x02\x00": {6},
|
||||||
b.Add([]byte{2, 0}, []byte{6})
|
})
|
||||||
|
|
||||||
expected := []keyValue{
|
expected := []keyValue{
|
||||||
{[]byte{0, 0, 0, 1}, []byte{5}},
|
{[]byte{0, 0, 0, 1}, []byte{5}},
|
||||||
{[]byte{0, 1}, []byte{2}},
|
{[]byte{0, 1}, []byte{2}},
|
||||||
|
@ -28,7 +29,7 @@ func TestBatchAdd(t *testing.T) {
|
||||||
type pairs = [][2][]byte
|
type pairs = [][2][]byte
|
||||||
|
|
||||||
func testIncompletePut(t *testing.T, ps pairs, n int, tr1, tr2 *Trie) {
|
func testIncompletePut(t *testing.T, ps pairs, n int, tr1, tr2 *Trie) {
|
||||||
var b Batch
|
var m = make(map[string][]byte)
|
||||||
for i, p := range ps {
|
for i, p := range ps {
|
||||||
if i < n {
|
if i < n {
|
||||||
if p[1] == nil {
|
if p[1] == nil {
|
||||||
|
@ -43,9 +44,10 @@ func testIncompletePut(t *testing.T, ps pairs, n int, tr1, tr2 *Trie) {
|
||||||
require.Error(t, tr1.Put(p[0], p[1]), "item %d", i)
|
require.Error(t, tr1.Put(p[0], p[1]), "item %d", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b.Add(p[0], p[1])
|
m["a"+string(p[0])] = p[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b := MapToMPTBatch(m)
|
||||||
num, err := tr2.PutBatch(b)
|
num, err := tr2.PutBatch(b)
|
||||||
if n == len(ps) {
|
if n == len(ps) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -308,8 +310,10 @@ func TestTrie_PutBatchEmpty(t *testing.T) {
|
||||||
// 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{}, ModeAll, newTestStore())
|
tr := NewTrie(EmptyNode{}, ModeAll, newTestStore())
|
||||||
var b Batch
|
var b = Batch{kv: []keyValue{{
|
||||||
b.Add([]byte{1}, []byte("value"))
|
key: []byte{0, 1},
|
||||||
|
value: []byte("value"),
|
||||||
|
}}}
|
||||||
tr.root = Node(nil)
|
tr.root = Node(nil)
|
||||||
require.Panics(t, func() { _, _ = tr.PutBatch(b) })
|
require.Panics(t, func() { _, _ = tr.PutBatch(b) })
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,17 @@ func toNibbles(path []byte) []byte {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// strToNibbles mangles path by splitting every byte into 2 containing low- and high- 4-byte part,
|
||||||
|
// ignoring the first byte (prefix).
|
||||||
|
func strToNibbles(path string) []byte {
|
||||||
|
result := make([]byte, (len(path)-1)*2)
|
||||||
|
for i := 0; i < len(path)-1; i++ {
|
||||||
|
result[i*2] = path[i+1] >> 4
|
||||||
|
result[i*2+1] = path[i+1] & 0x0F
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// fromNibbles performs operation opposite to toNibbles and does no path validity checks.
|
// fromNibbles performs operation opposite to toNibbles and does no path validity checks.
|
||||||
func fromNibbles(path []byte) []byte {
|
func fromNibbles(path []byte) []byte {
|
||||||
result := make([]byte, len(path)/2)
|
result := make([]byte, len(path)/2)
|
||||||
|
|
|
@ -156,19 +156,13 @@ func (s *MemCachedStore) Seek(rng SeekRange, f func(k, v []byte) bool) {
|
||||||
s.seek(context.Background(), rng, false, f)
|
s.seek(context.Background(), rng, false, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SeekAll is like seek but also iterates over deleted items.
|
// GetStorageChanges returns all current storage changes. It can only be done for private
|
||||||
func (s *MemCachedStore) SeekAll(key []byte, f func(k, v []byte)) {
|
// MemCachedStore.
|
||||||
|
func (s *MemCachedStore) GetStorageChanges() map[string][]byte {
|
||||||
if !s.private {
|
if !s.private {
|
||||||
s.mut.RLock()
|
panic("GetStorageChanges called on shared MemCachedStore")
|
||||||
defer s.mut.RUnlock()
|
|
||||||
}
|
|
||||||
sk := string(key)
|
|
||||||
m := s.chooseMap(key)
|
|
||||||
for k, v := range m {
|
|
||||||
if strings.HasPrefix(k, sk) {
|
|
||||||
f([]byte(k), v)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return s.stor
|
||||||
}
|
}
|
||||||
|
|
||||||
// SeekAsync returns non-buffered channel with matching KeyValue pairs. Key and
|
// SeekAsync returns non-buffered channel with matching KeyValue pairs. Key and
|
||||||
|
|
Loading…
Reference in a new issue