vm,dao: return storage iterator from DAO in Storage.Find interop

Reproduce behavior of the reference realization:
- if item was Put in cache after it was encountered during
  Storage.Find, it must appear twice
- checking if item is in cache must be performed in real-time
  during `Iterator.Next()`
This commit is contained in:
Evgenii Stratonikov 2020-05-27 09:27:43 +03:00
parent 79c87ca8a5
commit 776bd85ded
3 changed files with 93 additions and 14 deletions

View file

@ -315,6 +315,52 @@ func (cd *Cached) DeleteStorageItem(scripthash util.Uint160, key []byte) error {
return nil
}
// StorageIteratorFunc is a function returning key-value pair or error.
type StorageIteratorFunc func() ([]byte, []byte, error)
// GetStorageItemsIterator returns iterator over all storage items.
// Function returned can be called until first error.
func (cd *Cached) GetStorageItemsIterator(hash util.Uint160, prefix []byte) (StorageIteratorFunc, error) {
items, err := cd.DAO.GetStorageItems(hash)
if err != nil {
return nil, err
}
sort.Slice(items, func(i, j int) bool { return bytes.Compare(items[i].Key, items[j].Key) == -1 })
cache := cd.storage.getItems(hash)
var getItemFromCache StorageIteratorFunc
keyIndex := -1
getItemFromCache = func() ([]byte, []byte, error) {
keyIndex++
for ; keyIndex < len(cd.storage.keys[hash]); keyIndex++ {
k := cd.storage.keys[hash][keyIndex]
v := cache[k]
if v.State != delOp && bytes.HasPrefix([]byte(k), prefix) {
val := make([]byte, len(v.StorageItem.Value))
copy(val, v.StorageItem.Value)
return []byte(k), val, nil
}
}
return nil, nil, errors.New("no more items")
}
var f func() ([]byte, []byte, error)
index := -1
f = func() ([]byte, []byte, error) {
index++
for ; index < len(items); index++ {
_, ok := cache[string(items[index].Key)]
if !ok && bytes.HasPrefix(items[index].Key, prefix) {
return items[index].Key, items[index].Value, nil
}
}
return getItemFromCache()
}
return f, nil
}
// GetStorageItems returns all storage items for a given scripthash.
func (cd *Cached) GetStorageItems(hash util.Uint160) ([]StorageItemWithKey, error) {
items, err := cd.DAO.GetStorageItems(hash)

View file

@ -1,7 +1,6 @@
package core
import (
"bytes"
"errors"
"fmt"
"math"
@ -463,22 +462,12 @@ func (ic *interopContext) storageFind(v *vm.VM) error {
return err
}
pref := v.Estack().Pop().Bytes()
siMap, err := ic.dao.GetStorageItems(stc.ScriptHash)
next, err := ic.dao.GetStorageItemsIterator(stc.ScriptHash, pref)
if err != nil {
return err
}
filteredMap := vm.NewMapItem()
for i := range siMap {
k := siMap[i].Key
if bytes.HasPrefix(k, pref) {
filteredMap.Add(vm.NewByteArrayItem(siMap[i].Key),
vm.NewByteArrayItem(siMap[i].Value))
}
}
item := vm.NewMapIterator(filteredMap)
v.Estack().PushVal(item)
item := newStorageIterator(next)
v.Estack().PushVal(vm.NewInteropItem(item))
return nil
}

44
pkg/core/storage_find.go Normal file
View file

@ -0,0 +1,44 @@
package core
import (
"github.com/nspcc-dev/neo-go/pkg/core/dao"
"github.com/nspcc-dev/neo-go/pkg/vm"
)
type storageWrapper struct {
next dao.StorageIteratorFunc
key, value vm.StackItem
finished bool
}
// newStorageIterator returns new storage iterator from the `next()` callback.
func newStorageIterator(next dao.StorageIteratorFunc) *storageWrapper {
return &storageWrapper{
next: next,
}
}
// Next implements iterator interface.
func (s *storageWrapper) Next() bool {
if s.finished {
return false
}
key, value, err := s.next()
if err != nil {
s.finished = true
return false
}
s.key = vm.NewByteArrayItem(key)
s.value = vm.NewByteArrayItem(value)
return true
}
// Value implements iterator interface.
func (s *storageWrapper) Value() vm.StackItem {
return s.value
}
// Key implements iterator interface.
func (s *storageWrapper) Key() vm.StackItem {
return s.key
}