core: refactor callers of MemCachedStore.Seek

MemCachedStore.Seek now sorts results, so its callers may omit sorting.
This commit is contained in:
Anna Shaleva 2021-09-24 17:22:45 +03:00
parent 7ba88e98e2
commit 72726d46d3
11 changed files with 73 additions and 68 deletions

View file

@ -332,7 +332,7 @@ func (chain *FakeChain) GetTestVM(t trigger.Type, tx *transaction.Transaction, b
} }
// GetStorageItems implements Blockchainer interface. // GetStorageItems implements Blockchainer interface.
func (chain *FakeChain) GetStorageItems(id int32) (map[string]state.StorageItem, error) { func (chain *FakeChain) GetStorageItems(id int32) ([]state.StorageItemWithKey, error) {
panic("TODO") panic("TODO")
} }

View file

@ -1494,7 +1494,7 @@ func (bc *Blockchain) GetStorageItem(id int32, key []byte) state.StorageItem {
} }
// GetStorageItems returns all storage items for a given contract id. // GetStorageItems returns all storage items for a given contract id.
func (bc *Blockchain) GetStorageItems(id int32) (map[string]state.StorageItem, error) { func (bc *Blockchain) GetStorageItems(id int32) ([]state.StorageItemWithKey, error) {
return bc.dao.GetStorageItems(id) return bc.dao.GetStorageItems(id)
} }

View file

@ -58,7 +58,7 @@ type Blockchainer interface {
GetStateModule() StateRoot GetStateModule() StateRoot
GetStateSyncModule() StateSync GetStateSyncModule() StateSync
GetStorageItem(id int32, key []byte) state.StorageItem GetStorageItem(id int32, key []byte) state.StorageItem
GetStorageItems(id int32) (map[string]state.StorageItem, error) GetStorageItems(id int32) ([]state.StorageItemWithKey, error)
GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) *vm.VM GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) *vm.VM
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error) GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
SetOracle(service services.Oracle) SetOracle(service services.Oracle)

View file

@ -48,8 +48,8 @@ type DAO interface {
GetStateSyncPoint() (uint32, error) GetStateSyncPoint() (uint32, error)
GetStateSyncCurrentBlockHeight() (uint32, error) GetStateSyncCurrentBlockHeight() (uint32, error)
GetStorageItem(id int32, key []byte) state.StorageItem GetStorageItem(id int32, key []byte) state.StorageItem
GetStorageItems(id int32) (map[string]state.StorageItem, error) GetStorageItems(id int32) ([]state.StorageItemWithKey, error)
GetStorageItemsWithPrefix(id int32, prefix []byte) (map[string]state.StorageItem, error) GetStorageItemsWithPrefix(id int32, prefix []byte) ([]state.StorageItemWithKey, error)
GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error) GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error)
GetVersion() (string, error) GetVersion() (string, error)
GetWrapped() DAO GetWrapped() DAO
@ -313,28 +313,31 @@ func (dao *Simple) DeleteStorageItem(id int32, key []byte) error {
} }
// GetStorageItems returns all storage items for a given id. // GetStorageItems returns all storage items for a given id.
func (dao *Simple) GetStorageItems(id int32) (map[string]state.StorageItem, error) { func (dao *Simple) GetStorageItems(id int32) ([]state.StorageItemWithKey, error) {
return dao.GetStorageItemsWithPrefix(id, nil) return dao.GetStorageItemsWithPrefix(id, nil)
} }
// GetStorageItemsWithPrefix returns all storage items with given id for a // GetStorageItemsWithPrefix returns all storage items with given id for a
// given scripthash. // given scripthash.
func (dao *Simple) GetStorageItemsWithPrefix(id int32, prefix []byte) (map[string]state.StorageItem, error) { func (dao *Simple) GetStorageItemsWithPrefix(id int32, prefix []byte) ([]state.StorageItemWithKey, error) {
var siMap = make(map[string]state.StorageItem) var siArr []state.StorageItemWithKey
saveToMap := func(k, v []byte) { saveToArr := func(k, v []byte) {
// Cut prefix and hash. // Cut prefix and hash.
// Must copy here, #1468. // Must copy here, #1468.
key := slice.Copy(k) key := slice.Copy(k)
val := slice.Copy(v) val := slice.Copy(v)
siMap[string(key)] = state.StorageItem(val) siArr = append(siArr, state.StorageItemWithKey{
Key: key,
Item: state.StorageItem(val),
})
} }
dao.Seek(id, prefix, saveToMap) dao.Seek(id, prefix, saveToArr)
return siMap, nil return siArr, nil
} }
// Seek executes f for all items with a given prefix. // Seek executes f for all items with a given prefix.
// If key is to be used outside of f, they must be copied. // If key is to be used outside of f, they may not be copied.
func (dao *Simple) Seek(id int32, prefix []byte, f func(k, v []byte)) { func (dao *Simple) Seek(id int32, prefix []byte, f func(k, v []byte)) {
lookupKey := makeStorageItemKey(id, nil) lookupKey := makeStorageItemKey(id, nil)
if prefix != nil { if prefix != nil {

View file

@ -1,13 +1,11 @@
package core package core
import ( import (
"bytes"
"crypto/elliptic" "crypto/elliptic"
"errors" "errors"
"fmt" "fmt"
"math" "math"
"math/big" "math/big"
"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/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
@ -188,26 +186,23 @@ func storageFind(ic *interop.Context) error {
if opts&istorage.FindDeserialize == 0 && (opts&istorage.FindPick0 != 0 || opts&istorage.FindPick1 != 0) { if opts&istorage.FindDeserialize == 0 && (opts&istorage.FindPick0 != 0 || opts&istorage.FindPick1 != 0) {
return fmt.Errorf("%w: PickN is specified without Deserialize", errFindInvalidOptions) return fmt.Errorf("%w: PickN is specified without Deserialize", errFindInvalidOptions)
} }
siMap, err := ic.DAO.GetStorageItemsWithPrefix(stc.ID, prefix) siArr, err := ic.DAO.GetStorageItemsWithPrefix(stc.ID, prefix)
if err != nil { if err != nil {
return err return err
} }
arr := make([]stackitem.MapElement, 0, len(siMap)) arr := make([]stackitem.MapElement, 0, len(siArr))
for k, v := range siMap { for _, kv := range siArr {
keycopy := make([]byte, len(k)+len(prefix)) keycopy := make([]byte, len(kv.Key)+len(prefix))
copy(keycopy, prefix) copy(keycopy, prefix)
copy(keycopy[len(prefix):], k) copy(keycopy[len(prefix):], kv.Key)
arr = append(arr, stackitem.MapElement{ arr = append(arr, stackitem.MapElement{
Key: stackitem.NewByteArray(keycopy), Key: stackitem.NewByteArray(keycopy),
Value: stackitem.NewByteArray(v), Value: stackitem.NewByteArray(kv.Item),
}) })
} }
sort.Slice(arr, func(i, j int) bool { // Items in arr should be sorted by key, but GetStorageItemsWithPrefix returns
k1 := arr[i].Key.Value().([]byte) // sorted items, so no need to sort them one more time.
k2 := arr[j].Key.Value().([]byte)
return bytes.Compare(k1, k2) == -1
})
filteredMap := stackitem.NewMapWithValue(arr) filteredMap := stackitem.NewMapWithValue(arr)
item := istorage.NewIterator(filteredMap, len(prefix), opts) item := istorage.NewIterator(filteredMap, len(prefix), opts)

View file

@ -257,17 +257,22 @@ func (s *Designate) GetDesignatedByRole(d dao.DAO, r noderoles.Role, index uint3
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
var ns NodeList var (
var bestIndex uint32 ns NodeList
var resSi state.StorageItem bestIndex uint32
for k, si := range kvs { resSi state.StorageItem
if len(k) < 4 { )
// kvs are sorted by key (BE index bytes) in ascending way, so iterate backwards to get the latest designated.
for i := len(kvs) - 1; i >= 0; i-- {
kv := kvs[i]
if len(kv.Key) < 4 {
continue continue
} }
siInd := binary.BigEndian.Uint32([]byte(k)) siInd := binary.BigEndian.Uint32(kv.Key)
if (resSi == nil || siInd > bestIndex) && siInd <= index { if siInd <= index {
bestIndex = siInd bestIndex = siInd
resSi = si resSi = kv.Item
break
} }
} }
if resSi != nil { if resSi != nil {

View file

@ -391,12 +391,12 @@ func (m *Management) Destroy(d dao.DAO, hash util.Uint160) error {
if err != nil { if err != nil {
return err return err
} }
siMap, err := d.GetStorageItems(contract.ID) siArr, err := d.GetStorageItems(contract.ID)
if err != nil { if err != nil {
return err return err
} }
for k := range siMap { for _, kv := range siArr {
err := d.DeleteStorageItem(contract.ID, []byte(k)) err := d.DeleteStorageItem(contract.ID, []byte(kv.Key))
if err != nil { if err != nil {
return err return err
} }

View file

@ -471,24 +471,20 @@ func (n *NEO) getGASPerBlock(ic *interop.Context, _ []stackitem.Item) stackitem.
} }
func (n *NEO) getSortedGASRecordFromDAO(d dao.DAO) (gasRecord, error) { func (n *NEO) getSortedGASRecordFromDAO(d dao.DAO) (gasRecord, error) {
grMap, err := d.GetStorageItemsWithPrefix(n.ID, []byte{prefixGASPerBlock}) grArr, err := d.GetStorageItemsWithPrefix(n.ID, []byte{prefixGASPerBlock})
if err != nil { if err != nil {
return gasRecord{}, fmt.Errorf("failed to get gas records from storage: %w", err) return gasRecord{}, fmt.Errorf("failed to get gas records from storage: %w", err)
} }
var ( var gr = make(gasRecord, len(grArr))
i int for i, kv := range grArr {
gr = make(gasRecord, len(grMap)) indexBytes, gasValue := kv.Key, kv.Item
)
for indexBytes, gasValue := range grMap {
gr[i] = gasIndexPair{ gr[i] = gasIndexPair{
Index: binary.BigEndian.Uint32([]byte(indexBytes)), Index: binary.BigEndian.Uint32([]byte(indexBytes)),
GASPerBlock: *bigint.FromBytes(gasValue), GASPerBlock: *bigint.FromBytes(gasValue),
} }
i++
} }
sort.Slice(gr, func(i, j int) bool { // GAS records should be sorted by index, but GetStorageItemsWithPrefix returns
return gr[i].Index < gr[j].Index // values sorted by BE bytes of index, so we're OK with that.
})
return gr, nil return gr, nil
} }
@ -836,21 +832,21 @@ func (n *NEO) ModifyAccountVotes(acc *state.NEOBalance, d dao.DAO, value *big.In
} }
func (n *NEO) getCandidates(d dao.DAO, sortByKey bool) ([]keyWithVotes, error) { func (n *NEO) getCandidates(d dao.DAO, sortByKey bool) ([]keyWithVotes, error) {
siMap, err := d.GetStorageItemsWithPrefix(n.ID, []byte{prefixCandidate}) siArr, err := d.GetStorageItemsWithPrefix(n.ID, []byte{prefixCandidate})
if err != nil { if err != nil {
return nil, err return nil, err
} }
arr := make([]keyWithVotes, 0, len(siMap)) arr := make([]keyWithVotes, 0, len(siArr))
for key, si := range siMap { for _, kv := range siArr {
c := new(candidate).FromBytes(si) c := new(candidate).FromBytes(kv.Item)
if c.Registered { if c.Registered {
arr = append(arr, keyWithVotes{Key: key, Votes: &c.Votes}) arr = append(arr, keyWithVotes{Key: string(kv.Key), Votes: &c.Votes})
} }
} }
if sortByKey { if !sortByKey {
// Sort by serialized key bytes (that's the way keys are stored and retrieved from the storage by default). // sortByKey assumes to sort by serialized key bytes (that's the way keys
sort.Slice(arr, func(i, j int) bool { return strings.Compare(arr[i].Key, arr[j].Key) == -1 }) // are stored and retrieved from the storage by default). Otherwise, need
} else { // to sort using big.Int comparator.
sort.Slice(arr, func(i, j int) bool { sort.Slice(arr, func(i, j int) bool {
// The most-voted validators should end up in the front of the list. // The most-voted validators should end up in the front of the list.
cmp := arr[i].Votes.Cmp(arr[j].Votes) cmp := arr[i].Votes.Cmp(arr[j].Votes)

View file

@ -483,21 +483,21 @@ func (o *Oracle) getOriginalTxID(d dao.DAO, tx *transaction.Transaction) util.Ui
// getRequests returns all requests which have not been finished yet. // getRequests returns all requests which have not been finished yet.
func (o *Oracle) getRequests(d dao.DAO) (map[uint64]*state.OracleRequest, error) { func (o *Oracle) getRequests(d dao.DAO) (map[uint64]*state.OracleRequest, error) {
m, err := d.GetStorageItemsWithPrefix(o.ID, prefixRequest) arr, err := d.GetStorageItemsWithPrefix(o.ID, prefixRequest)
if err != nil { if err != nil {
return nil, err return nil, err
} }
reqs := make(map[uint64]*state.OracleRequest, len(m)) reqs := make(map[uint64]*state.OracleRequest, len(arr))
for k, si := range m { for _, kv := range arr {
if len(k) != 8 { if len(kv.Key) != 8 {
return nil, errors.New("invalid request ID") return nil, errors.New("invalid request ID")
} }
req := new(state.OracleRequest) req := new(state.OracleRequest)
err = stackitem.DeserializeConvertible(si, req) err = stackitem.DeserializeConvertible(kv.Item, req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
id := binary.BigEndian.Uint64([]byte(k)) id := binary.BigEndian.Uint64([]byte(kv.Key))
reqs[id] = req reqs[id] = req
} }
return reqs, nil return reqs, nil

View file

@ -162,20 +162,20 @@ func (p *Policy) PostPersist(ic *interop.Context) error {
p.storagePrice = uint32(getIntWithKey(p.ID, ic.DAO, storagePriceKey)) p.storagePrice = uint32(getIntWithKey(p.ID, ic.DAO, storagePriceKey))
p.blockedAccounts = make([]util.Uint160, 0) p.blockedAccounts = make([]util.Uint160, 0)
siMap, err := ic.DAO.GetStorageItemsWithPrefix(p.ID, []byte{blockedAccountPrefix}) siArr, err := ic.DAO.GetStorageItemsWithPrefix(p.ID, []byte{blockedAccountPrefix})
if err != nil { if err != nil {
return fmt.Errorf("failed to get blocked accounts from storage: %w", err) return fmt.Errorf("failed to get blocked accounts from storage: %w", err)
} }
for key := range siMap { for _, kv := range siArr {
hash, err := util.Uint160DecodeBytesBE([]byte(key)) hash, err := util.Uint160DecodeBytesBE([]byte(kv.Key))
if err != nil { if err != nil {
return fmt.Errorf("failed to decode blocked account hash: %w", err) return fmt.Errorf("failed to decode blocked account hash: %w", err)
} }
p.blockedAccounts = append(p.blockedAccounts, hash) p.blockedAccounts = append(p.blockedAccounts, hash)
} }
sort.Slice(p.blockedAccounts, func(i, j int) bool { // blockedAccounts should be sorted by account BE bytes, but GetStorageItemsWithPrefix
return p.blockedAccounts[i].Less(p.blockedAccounts[j]) // returns values sorted by key (which is account's BE bytes), so don't need to sort
}) // one more time.
p.isValid = true p.isValid = true
return nil return nil

View file

@ -2,3 +2,9 @@ package state
// StorageItem is the value to be stored with read-only flag. // StorageItem is the value to be stored with read-only flag.
type StorageItem []byte type StorageItem []byte
// StorageItemWithKey is a storage item with corresponding key.
type StorageItemWithKey struct {
Key []byte
Item StorageItem
}