2021-01-12 10:39:31 +00:00
|
|
|
package storage
|
|
|
|
|
2021-10-04 14:01:42 +00:00
|
|
|
import (
|
2024-03-04 18:09:36 +00:00
|
|
|
"bytes"
|
2022-06-08 16:31:49 +00:00
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
2021-10-04 14:01:42 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
|
|
)
|
2021-01-12 10:39:31 +00:00
|
|
|
|
|
|
|
// Storage iterator options.
|
|
|
|
const (
|
|
|
|
FindDefault = 0
|
|
|
|
FindKeysOnly = 1 << 0
|
|
|
|
FindRemovePrefix = 1 << 1
|
|
|
|
FindValuesOnly = 1 << 2
|
2021-01-12 12:09:05 +00:00
|
|
|
FindDeserialize = 1 << 3
|
2021-01-12 12:32:27 +00:00
|
|
|
FindPick0 = 1 << 4
|
|
|
|
FindPick1 = 1 << 5
|
2023-03-24 16:32:06 +00:00
|
|
|
FindBackwards = 1 << 7
|
2021-01-12 10:39:31 +00:00
|
|
|
|
2021-01-12 12:09:05 +00:00
|
|
|
FindAll = FindDefault | FindKeysOnly | FindRemovePrefix | FindValuesOnly |
|
2023-03-24 16:32:06 +00:00
|
|
|
FindDeserialize | FindPick0 | FindPick1 | FindBackwards
|
2021-01-12 10:39:31 +00:00
|
|
|
)
|
|
|
|
|
2021-01-15 18:06:35 +00:00
|
|
|
// Iterator is an iterator state representation.
|
2021-01-12 10:39:31 +00:00
|
|
|
type Iterator struct {
|
2021-10-04 14:01:42 +00:00
|
|
|
seekCh chan storage.KeyValue
|
|
|
|
curr storage.KeyValue
|
|
|
|
next bool
|
|
|
|
opts int64
|
core: copy SC storage iterator prefix while returning values
It's a bug since Prefix is shared between all iterator items and
appending is not enough. If prefix has enough capacity, then new slice
won't be created and the previous item's prefix will be changed.
This commit fixes the following test failure caused by moving from
bytes.Clone to slice.Copy:
```
--- FAIL: TestComlileAndInvokeFunction/test_Storage.Find (0.02s)
--- FAIL: TestComlileAndInvokeFunction/test_Storage.Find/keys_only (0.01s)
contract_test.go:866:
Error Trace: /home/anna/Documents/GitProjects/nspcc-dev/neo-go/cli/smartcontract/contract_test.go:866
Error: Not equal:
expected: []stackitem.Item{(*stackitem.ByteArray)(0xc000a1cdf8), (*stackitem.ByteArray)(0xc000a1ce10)}
actual : []stackitem.Item{(*stackitem.ByteArray)(0xc000a1cdb0), (*stackitem.ByteArray)(0xc000a1cdc8)}
Diff:
--- Expected
+++ Actual
@@ -2,3 +2,3 @@
(*stackitem.ByteArray)((len=8) {
- 00000000 66 69 6e 64 6b 65 79 31 |findkey1|
+ 00000000 66 69 6e 64 6b 65 79 32 |findkey2|
}),
Test: TestComlileAndInvokeFunction/test_Storage.Find/keys_only
--- FAIL: TestComlileAndInvokeFunction/test_Storage.Find/both (0.01s)
contract_test.go:881:
Error Trace: /home/anna/Documents/GitProjects/nspcc-dev/neo-go/cli/smartcontract/contract_test.go:881
Error: Not equal:
expected: []stackitem.Item{(*stackitem.ByteArray)(0xc000515920), (*stackitem.ByteArray)(0xc000515938)}
actual : []stackitem.Item{(*stackitem.ByteArray)(0xc000515848), (*stackitem.ByteArray)(0xc000515860)}
Diff:
--- Expected
+++ Actual
@@ -2,3 +2,3 @@
(*stackitem.ByteArray)((len=8) {
- 00000000 66 69 6e 64 6b 65 79 31 |findkey1|
+ 00000000 66 69 6e 64 6b 65 79 32 |findkey2|
}),
Test: TestComlileAndInvokeFunction/test_Storage.Find/both
```
Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
2024-03-05 10:40:13 +00:00
|
|
|
// prefix is the storage item key prefix Find is performed with. It must be
|
|
|
|
// copied if no FindRemovePrefix option specified since it's shared between all
|
|
|
|
// iterator items.
|
2021-10-04 14:01:42 +00:00
|
|
|
prefix []byte
|
2021-01-12 10:39:31 +00:00
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// NewIterator creates a new Iterator with the given options for the given channel of store.Seek results.
|
2021-10-07 11:27:55 +00:00
|
|
|
func NewIterator(seekCh chan storage.KeyValue, prefix []byte, opts int64) *Iterator {
|
2021-01-12 10:39:31 +00:00
|
|
|
return &Iterator{
|
2021-10-04 14:01:42 +00:00
|
|
|
seekCh: seekCh,
|
|
|
|
opts: opts,
|
2024-03-04 18:09:36 +00:00
|
|
|
prefix: bytes.Clone(prefix),
|
2021-01-12 10:39:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-15 18:06:35 +00:00
|
|
|
// Next advances the iterator and returns true if Value can be called at the
|
|
|
|
// current position.
|
2021-01-12 10:39:31 +00:00
|
|
|
func (s *Iterator) Next() bool {
|
2021-10-04 14:01:42 +00:00
|
|
|
s.curr, s.next = <-s.seekCh
|
|
|
|
return s.next
|
2021-01-12 10:39:31 +00:00
|
|
|
}
|
|
|
|
|
2021-01-15 18:06:35 +00:00
|
|
|
// Value returns current iterators value (exact type depends on options this
|
|
|
|
// iterator was created with).
|
2021-01-12 10:39:31 +00:00
|
|
|
func (s *Iterator) Value() stackitem.Item {
|
2021-10-04 14:01:42 +00:00
|
|
|
if !s.next {
|
|
|
|
panic("iterator index out of range")
|
|
|
|
}
|
|
|
|
key := s.curr.Key
|
|
|
|
if s.opts&FindRemovePrefix == 0 {
|
2024-03-04 18:09:36 +00:00
|
|
|
key = append(bytes.Clone(s.prefix), key...)
|
2021-01-12 10:39:31 +00:00
|
|
|
}
|
|
|
|
if s.opts&FindKeysOnly != 0 {
|
|
|
|
return stackitem.NewByteArray(key)
|
|
|
|
}
|
2021-10-04 14:01:42 +00:00
|
|
|
value := stackitem.Item(stackitem.NewByteArray(s.curr.Value))
|
2021-01-12 12:09:05 +00:00
|
|
|
if s.opts&FindDeserialize != 0 {
|
2021-10-04 14:01:42 +00:00
|
|
|
bs := s.curr.Value
|
2021-01-12 12:09:05 +00:00
|
|
|
var err error
|
2021-07-06 16:56:23 +00:00
|
|
|
value, err = stackitem.Deserialize(bs)
|
2021-01-12 12:09:05 +00:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
2021-01-12 12:32:27 +00:00
|
|
|
if s.opts&FindPick0 != 0 {
|
|
|
|
value = value.Value().([]stackitem.Item)[0]
|
|
|
|
} else if s.opts&FindPick1 != 0 {
|
|
|
|
value = value.Value().([]stackitem.Item)[1]
|
|
|
|
}
|
2021-01-12 10:39:31 +00:00
|
|
|
if s.opts&FindValuesOnly != 0 {
|
2021-01-12 12:09:05 +00:00
|
|
|
return value
|
2021-01-12 10:39:31 +00:00
|
|
|
}
|
|
|
|
return stackitem.NewStruct([]stackitem.Item{
|
|
|
|
stackitem.NewByteArray(key),
|
2021-01-12 12:09:05 +00:00
|
|
|
value,
|
2021-01-12 10:39:31 +00:00
|
|
|
})
|
|
|
|
}
|
2022-06-08 16:31:49 +00:00
|
|
|
|
|
|
|
// Find finds stored key-value pair.
|
|
|
|
func Find(ic *interop.Context) error {
|
|
|
|
stcInterface := ic.VM.Estack().Pop().Value()
|
|
|
|
stc, ok := stcInterface.(*Context)
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("%T is not a storage,Context", stcInterface)
|
|
|
|
}
|
|
|
|
prefix := ic.VM.Estack().Pop().Bytes()
|
|
|
|
opts := ic.VM.Estack().Pop().BigInt().Int64()
|
|
|
|
if opts&^FindAll != 0 {
|
|
|
|
return fmt.Errorf("%w: unknown flag", errFindInvalidOptions)
|
|
|
|
}
|
|
|
|
if opts&FindKeysOnly != 0 &&
|
|
|
|
opts&(FindDeserialize|FindPick0|FindPick1) != 0 {
|
|
|
|
return fmt.Errorf("%w KeysOnly conflicts with other options", errFindInvalidOptions)
|
|
|
|
}
|
|
|
|
if opts&FindValuesOnly != 0 &&
|
|
|
|
opts&(FindKeysOnly|FindRemovePrefix) != 0 {
|
|
|
|
return fmt.Errorf("%w: KeysOnly conflicts with ValuesOnly", errFindInvalidOptions)
|
|
|
|
}
|
|
|
|
if opts&FindPick0 != 0 && opts&FindPick1 != 0 {
|
|
|
|
return fmt.Errorf("%w: Pick0 conflicts with Pick1", errFindInvalidOptions)
|
|
|
|
}
|
|
|
|
if opts&FindDeserialize == 0 && (opts&FindPick0 != 0 || opts&FindPick1 != 0) {
|
|
|
|
return fmt.Errorf("%w: PickN is specified without Deserialize", errFindInvalidOptions)
|
|
|
|
}
|
2023-03-24 16:32:06 +00:00
|
|
|
bkwrds := opts&FindBackwards != 0
|
2022-06-08 16:31:49 +00:00
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
2023-03-24 16:32:06 +00:00
|
|
|
seekres := ic.DAO.SeekAsync(ctx, stc.ID, storage.SeekRange{Prefix: prefix, Backwards: bkwrds})
|
2022-06-08 16:31:49 +00:00
|
|
|
item := NewIterator(seekres, prefix, opts)
|
|
|
|
ic.VM.Estack().PushItem(stackitem.NewInterop(item))
|
|
|
|
ic.RegisterCancelFunc(func() {
|
|
|
|
cancel()
|
|
|
|
// Underlying persistent store is likely to be a private MemCachedStore. Thus,
|
|
|
|
// to avoid concurrent map iteration and map write we need to wait until internal
|
|
|
|
// seek goroutine is finished, because it can access underlying persistent store.
|
2023-03-29 03:19:23 +00:00
|
|
|
for range seekres { //nolint:revive //empty-block
|
2022-06-08 16:31:49 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|