forked from TrueCloudLab/neoneo-go
core: mandate passing from
as a subprefix for (*Trie).Find
However, we need to distinguish empty subprefix and nil subprefix (no start specified) to match the C# behaviour.
This commit is contained in:
parent
8e7c76827b
commit
892eadf86d
4 changed files with 37 additions and 19 deletions
|
@ -394,13 +394,16 @@ func TestCompatibility_Find(t *testing.T) {
|
||||||
})
|
})
|
||||||
t.Run("from is not in tree", func(t *testing.T) {
|
t.Run("from is not in tree", func(t *testing.T) {
|
||||||
t.Run("matching", func(t *testing.T) {
|
t.Run("matching", func(t *testing.T) {
|
||||||
check(t, []byte("aa30"), 1)
|
check(t, []byte("30"), 1)
|
||||||
})
|
})
|
||||||
t.Run("non-matching", func(t *testing.T) {
|
t.Run("non-matching", func(t *testing.T) {
|
||||||
check(t, []byte("aa60"), 0)
|
check(t, []byte("60"), 0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
t.Run("from is in tree", func(t *testing.T) {
|
t.Run("from is in tree", func(t *testing.T) {
|
||||||
check(t, []byte("aa10"), 1) // without `from` key
|
check(t, []byte("10"), 1) // without `from` key
|
||||||
|
})
|
||||||
|
t.Run("from matching start", func(t *testing.T) {
|
||||||
|
check(t, []byte{}, 2) // without `from` key
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -526,22 +526,19 @@ func collapse(depth int, node Node) Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find returns list of storage key-value pairs whose key is prefixed by the specified
|
// Find returns list of storage key-value pairs whose key is prefixed by the specified
|
||||||
// prefix starting from the specified path (not including the item at the specified path
|
// prefix starting from the specified `prefix`+`from` path (not including the item at
|
||||||
// if so). The `max` number of elements is returned at max.
|
// the specified `prefix`+`from` path if so). The `max` number of elements is returned at max.
|
||||||
func (t *Trie) Find(prefix, from []byte, max int) ([]storage.KeyValue, error) {
|
func (t *Trie) Find(prefix, from []byte, max int) ([]storage.KeyValue, error) {
|
||||||
if len(prefix) > MaxKeyLength {
|
if len(prefix) > MaxKeyLength {
|
||||||
return nil, errors.New("invalid prefix length")
|
return nil, errors.New("invalid prefix length")
|
||||||
}
|
}
|
||||||
if len(from) > MaxKeyLength {
|
if len(from) > MaxKeyLength-len(prefix) {
|
||||||
return nil, errors.New("invalid from length")
|
return nil, errors.New("invalid from length")
|
||||||
}
|
}
|
||||||
prefixP := toNibbles(prefix)
|
prefixP := toNibbles(prefix)
|
||||||
fromP := []byte{}
|
fromP := []byte{}
|
||||||
if len(from) > 0 {
|
if len(from) > 0 {
|
||||||
if !bytes.HasPrefix(from, prefix) {
|
fromP = toNibbles(from)
|
||||||
return nil, errors.New("`from` argument doesn't match specified prefix")
|
|
||||||
}
|
|
||||||
fromP = toNibbles(from)[len(prefixP):]
|
|
||||||
}
|
}
|
||||||
_, start, path, err := t.getWithPath(t.root, prefixP, false)
|
_, start, path, err := t.getWithPath(t.root, prefixP, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -572,10 +569,9 @@ func (t *Trie) Find(prefix, from []byte, max int) ([]storage.KeyValue, error) {
|
||||||
b := NewBillet(t.root.Hash(), false, t.Store)
|
b := NewBillet(t.root.Hash(), false, 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 {
|
||||||
key := append(prefix, pathToNode...)
|
if from == nil || !bytes.Equal(pathToNode, from) { // (*Billet).traverse includes `from` path into result if so. Need to filter out manually.
|
||||||
if !bytes.Equal(key, from) { // (*Billet).traverse includes `from` path into result if so. Need to filter out manually.
|
|
||||||
res = append(res, storage.KeyValue{
|
res = append(res, storage.KeyValue{
|
||||||
Key: key,
|
Key: append(slice.Copy(prefix), pathToNode...),
|
||||||
Value: slice.Copy(leaf.value),
|
Value: slice.Copy(leaf.value),
|
||||||
})
|
})
|
||||||
count++
|
count++
|
||||||
|
|
|
@ -1095,6 +1095,15 @@ func (s *Server) findStates(ps request.Params) (interface{}, *response.Error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.WrapErrorWithData(response.ErrInvalidParams, errors.New("invalid key"))
|
return nil, response.WrapErrorWithData(response.ErrInvalidParams, errors.New("invalid key"))
|
||||||
}
|
}
|
||||||
|
if len(key) > 0 {
|
||||||
|
if !bytes.HasPrefix(key, prefix) {
|
||||||
|
return nil, response.WrapErrorWithData(response.ErrInvalidParams, errors.New("key doesn't match prefix"))
|
||||||
|
}
|
||||||
|
key = key[len(prefix):]
|
||||||
|
} else {
|
||||||
|
// empty ("") key shouldn't exclude item matching prefix from the result
|
||||||
|
key = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(ps) > 4 {
|
if len(ps) > 4 {
|
||||||
count, err = ps.Value(4).GetInt()
|
count, err = ps.Value(4).GetInt()
|
||||||
|
@ -1110,11 +1119,7 @@ func (s *Server) findStates(ps request.Params) (interface{}, *response.Error) {
|
||||||
return nil, respErr
|
return nil, respErr
|
||||||
}
|
}
|
||||||
pKey := makeStorageKey(cs.ID, prefix)
|
pKey := makeStorageKey(cs.ID, prefix)
|
||||||
var sKey []byte
|
kvs, err := s.chain.GetStateModule().FindStates(root, pKey, key, count+1) // +1 to define result truncation
|
||||||
if len(key) > 0 {
|
|
||||||
sKey = makeStorageKey(cs.ID, key)
|
|
||||||
}
|
|
||||||
kvs, err := s.chain.GetStateModule().FindStates(root, pKey, sKey, count+1) // +1 to define result truncation
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.NewInternalServerError("failed to find historical items", err)
|
return nil, response.NewInternalServerError("failed to find historical items", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1515,6 +1515,20 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
|
||||||
Truncated: false,
|
Truncated: false,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
t.Run("good: empty prefix, no limit", func(t *testing.T) {
|
||||||
|
// empty prefix should be considered as no prefix specified.
|
||||||
|
root, err := e.chain.GetStateModule().GetStateRoot(16)
|
||||||
|
require.NoError(t, err)
|
||||||
|
params := fmt.Sprintf(`"%s", "%s", "%s", ""`, root.Root.StringLE(), testContractHash, base64.StdEncoding.EncodeToString([]byte("aa")))
|
||||||
|
testFindStates(t, params, root.Root, result.FindStates{
|
||||||
|
Results: []result.KeyValue{
|
||||||
|
{Key: []byte("aa10"), Value: []byte("v2")},
|
||||||
|
{Key: []byte("aa50"), Value: []byte("v3")},
|
||||||
|
{Key: []byte("aa"), Value: []byte("v1")},
|
||||||
|
},
|
||||||
|
Truncated: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
t.Run("good: with prefix, no limit", func(t *testing.T) {
|
t.Run("good: with prefix, no limit", func(t *testing.T) {
|
||||||
// pairs for this test where put to the contract storage at block #16
|
// pairs for this test where put to the contract storage at block #16
|
||||||
root, err := e.chain.GetStateModule().GetStateRoot(16)
|
root, err := e.chain.GetStateModule().GetStateRoot(16)
|
||||||
|
@ -1527,7 +1541,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
|
||||||
Truncated: false,
|
Truncated: false,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
t.Run("good: no prefix, with limit", func(t *testing.T) {
|
t.Run("good: empty prefix, with limit", func(t *testing.T) {
|
||||||
for limit := 2; limit < 5; limit++ {
|
for limit := 2; limit < 5; limit++ {
|
||||||
// pairs for this test where put to the contract storage at block #16
|
// pairs for this test where put to the contract storage at block #16
|
||||||
root, err := e.chain.GetStateModule().GetStateRoot(16)
|
root, err := e.chain.GetStateModule().GetStateRoot(16)
|
||||||
|
|
Loading…
Add table
Reference in a new issue