diff --git a/policy/config.yml b/policy/config.yml index 94ab0d3..cdde275 100644 --- a/policy/config.yml +++ b/policy/config.yml @@ -4,4 +4,5 @@ safemethods: - "listChains" - "getChain" - "listChainsByPrefix" - - "version" + - "iteratorChainsByPrefix" + - "version" \ No newline at end of file diff --git a/policy/policy_contract.go b/policy/policy_contract.go index f66646b..a3dd976 100644 --- a/policy/policy_contract.go +++ b/policy/policy_contract.go @@ -150,3 +150,9 @@ func ListChainsByPrefix(entity Kind, entityName string, prefix []byte) [][]byte return result } + +func IteratorChainsByPrefix(entity Kind, entityName string, prefix []byte) iterator.Iterator { + ctx := storage.GetReadOnlyContext() + keyPrefix := storageKey(entity, entityName, prefix) + return storage.Find(ctx, keyPrefix, storage.ValuesOnly) +} diff --git a/rpcclient/policy/client.go b/rpcclient/policy/client.go index 4174dcb..2b99562 100644 --- a/rpcclient/policy/client.go +++ b/rpcclient/policy/client.go @@ -4,6 +4,7 @@ package ape import ( + "github.com/google/uuid" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" @@ -15,6 +16,9 @@ import ( // Invoker is used by ContractReader to call various safe methods. type Invoker interface { Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error) + CallAndExpandIterator(contract util.Uint160, method string, maxItems int, params ...any) (*result.Invoke, error) + TerminateSession(sessionID uuid.UUID) error + TraverseIterator(sessionID uuid.UUID, iterator *result.Iterator, num int) ([]stackitem.Item, error) } // Actor is used by Contract to call state-changing methods. @@ -62,6 +66,20 @@ func (c *ContractReader) GetChain(entity *big.Int, entityName string, name []byt return unwrap.Bytes(c.invoker.Call(c.hash, "getChain", entity, entityName, name)) } +// IteratorChainsByPrefix invokes `iteratorChainsByPrefix` method of contract. +func (c *ContractReader) IteratorChainsByPrefix(entity *big.Int, entityName string, prefix []byte) (uuid.UUID, result.Iterator, error) { + return unwrap.SessionIterator(c.invoker.Call(c.hash, "iteratorChainsByPrefix", entity, entityName, prefix)) +} + +// IteratorChainsByPrefixExpanded is similar to IteratorChainsByPrefix (uses the same contract +// method), but can be useful if the server used doesn't support sessions and +// doesn't expand iterators. It creates a script that will get the specified +// number of result items from the iterator right in the VM and return them to +// you. It's only limited by VM stack and GAS available for RPC invocations. +func (c *ContractReader) IteratorChainsByPrefixExpanded(entity *big.Int, entityName string, prefix []byte, _numOfIteratorItems int) ([]stackitem.Item, error) { + return unwrap.Array(c.invoker.CallAndExpandIterator(c.hash, "iteratorChainsByPrefix", _numOfIteratorItems, entity, entityName, prefix)) +} + // ListChains invokes `listChains` method of contract. func (c *ContractReader) ListChains(namespace string, container string, name []byte) ([]stackitem.Item, error) { return unwrap.Array(c.invoker.Call(c.hash, "listChains", namespace, container, name)) diff --git a/tests/policy_test.go b/tests/policy_test.go index 9cc2bee..8fa1891 100644 --- a/tests/policy_test.go +++ b/tests/policy_test.go @@ -6,6 +6,7 @@ import ( "testing" "git.frostfs.info/TrueCloudLab/frostfs-contract/policy" + "github.com/nspcc-dev/neo-go/pkg/core/interop/storage" "github.com/nspcc-dev/neo-go/pkg/neotest" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" @@ -56,6 +57,9 @@ func TestPolicy(t *testing.T) { checkChainsByPrefix(t, e, policy.Container, "cnr1", "", [][]byte{p2, p33}) checkChainsByPrefix(t, e, policy.IAM, "", "", nil) + checkChainsIteratorByPrefix(t, e, policy.Container, "cnr1", "ingress:myrule3", [][]byte{p33}) + checkChainsIteratorByPrefix(t, e, policy.Container, "cnr1", "ingress", [][]byte{p2, p33}) + t.Run("removal", func(t *testing.T) { t.Run("wrong name", func(t *testing.T) { e.Invoke(t, stackitem.Null{}, "removeChain", policy.Namespace, "mynamespace", "ingress") @@ -103,6 +107,25 @@ func checkChainsByPrefix(t *testing.T, e *neotest.ContractInvoker, kind byte, en checksChainsOnStack(t, s, expected) } +func checkChainsIteratorByPrefix(t *testing.T, e *neotest.ContractInvoker, kind byte, entityName, prefix string, expected [][]byte) { + s, err := e.TestInvoke(t, "iteratorChainsByPrefix", kind, entityName, prefix) + require.NoError(t, err) + + if s.Len() == 0 { + t.Fatal("Stack is empty") + } + + iteratorItem := s.Pop().Value().(*storage.Iterator) + policys := iteratorToArray(iteratorItem) + require.Equal(t, len(expected), len(policys)) + + for i := range expected { + bytesPolicy, err := policys[i].TryBytes() + require.NoError(t, err) + require.Equal(t, expected[i], bytesPolicy) + } +} + func checksChainsOnStack(t *testing.T, s *vm.Stack, expected [][]byte) { require.Equal(t, 1, s.Len())