From c5397286410f8be359a879e2e1e03399a4b2d2ea Mon Sep 17 00:00:00 2001 From: aarifullin Date: Wed, 3 Apr 2024 15:34:40 +0300 Subject: [PATCH] [#62] morph: List morph rules chains by traversing iterator * Make `ListMorphRuleChains` methods use `commonclient.ReadIteratorItems`. * Introduce `ContractStorageActor` interface. * Iterators are used because listing by `ListChainsByPrefix` may cause stack overflow from neo-go side (len(items) > 1024). Signed-off-by: Airat Arifullin --- go.mod | 2 +- go.sum | 4 +- pkg/morph/policy/policy_contract_storage.go | 92 ++++++++++++++------- 3 files changed, 63 insertions(+), 35 deletions(-) diff --git a/go.mod b/go.mod index 38d7657..47caa64 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.frostfs.info/TrueCloudLab/policy-engine go 1.20 require ( - git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.0 + git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240409111539-e7a05a49ff45 github.com/google/uuid v1.3.1 github.com/mailru/easyjson v0.7.7 github.com/nspcc-dev/neo-go v0.105.0 diff --git a/go.sum b/go.sum index 72b62cc..8c5781f 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.0 h1:FzurjElUwC7InY9v5rzXReKbfBL5yRJKSWJPq6BKhH0= -git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.0/go.mod h1:F/fe1OoIDKr5Bz99q4sriuHDuf3aZefZy9ZsCqEtgxc= +git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240409111539-e7a05a49ff45 h1:Tp4I+XOLp3VCJORfxSamQtj3RZNISbaLM4WD5iIzXxg= +git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240409111539-e7a05a49ff45/go.mod h1:F/fe1OoIDKr5Bz99q4sriuHDuf3aZefZy9ZsCqEtgxc= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= diff --git a/pkg/morph/policy/policy_contract_storage.go b/pkg/morph/policy/policy_contract_storage.go index d120988..ed5a062 100644 --- a/pkg/morph/policy/policy_contract_storage.go +++ b/pkg/morph/policy/policy_contract_storage.go @@ -6,6 +6,7 @@ import ( "math/big" "strings" + "git.frostfs.info/TrueCloudLab/frostfs-contract/commonclient" "git.frostfs.info/TrueCloudLab/frostfs-contract/policy" client "git.frostfs.info/TrueCloudLab/frostfs-contract/rpcclient/policy" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" @@ -13,6 +14,7 @@ import ( "github.com/google/uuid" "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" + neoinvoker "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/wallet" @@ -26,6 +28,10 @@ var ( // ContractStorage is the interface to manage chain rules within Policy contract. type ContractStorage struct { + hash util.Uint160 + + actor ContractStorageActor + contractInterface *client.Contract } @@ -33,23 +39,49 @@ var _ engine.MorphRuleChainStorage = (*ContractStorage)(nil) // ContractStorageReader is the interface to read data from Policy contract. type ContractStorageReader struct { + hash util.Uint160 + + invoker ContractStorageInvoker + contractReaderInterface *client.ContractReader } +type ContractStorageActor interface { + client.Actor + GetRPCInvoker() neoinvoker.RPCInvoke +} + var _ engine.MorphRuleChainStorageReader = (*ContractStorageReader)(nil) -func NewContractStorage(actor client.Actor, contract util.Uint160) *ContractStorage { +func NewContractStorage(actor ContractStorageActor, contract util.Uint160) *ContractStorage { return &ContractStorage{ + hash: contract, + actor: actor, contractInterface: client.New(actor, contract), } } +type contractStorageActorImpl struct { + client.Actor + rpcActor actor.RPCActor +} + +var _ ContractStorageActor = &contractStorageActorImpl{} + +func (c *contractStorageActorImpl) GetRPCInvoker() neoinvoker.RPCInvoke { + return c.rpcActor +} + +// NewContractStorageWithSimpleActor constructs core actor from `rpcActor`. +// +// Note: NewContractStorageWithSimpleActor is appropriate only for call-only-once cases (for example, in CLIs). Otherwise, it is unsafe, +// because core actor may use invalidated `rpcActor` if some connection errors occurred. func NewContractStorageWithSimpleActor(rpcActor actor.RPCActor, acc *wallet.Account, contract util.Uint160) (*ContractStorage, error) { act, err := actor.NewSimple(rpcActor, acc) if err != nil { return nil, fmt.Errorf("failed to create simple actor: %w", err) } - return NewContractStorage(act, contract), nil + return NewContractStorage(&contractStorageActorImpl{Actor: act, rpcActor: rpcActor}, contract), nil } func (s *ContractStorage) AddMorphRuleChain(name chain.Name, target engine.Target, c *chain.Chain) (txHash util.Uint256, vub uint32, err error) { @@ -98,17 +130,26 @@ func (s *ContractStorage) RemoveMorphRuleChainsByTarget(name chain.Name, target return } -func (s *ContractStorage) ListMorphRuleChains(name chain.Name, target engine.Target) ([]*chain.Chain, error) { +func listChains(name chain.Name, target engine.Target, rpcInvoker neoinvoker.RPCInvoke, hash util.Uint160) ([]*chain.Chain, error) { kind, err := policyKind(target.Type) if err != nil { return nil, err } - items, err := s.contractInterface.ListChainsByPrefix(big.NewInt(int64(kind)), target.Name, []byte(name)) - if err != nil { - return nil, err + const ( + method = "iteratorChainsByPrefix" + batchSize = neoinvoker.DefaultIteratorResultItems + ) + + inv := neoinvoker.New(rpcInvoker, nil) + params := []any{ + big.NewInt(int64(kind)), target.Name, []byte(name), } + items, err := commonclient.ReadIteratorItems(inv, batchSize, hash, method, params...) + if err != nil { + return nil, fmt.Errorf("read items error: %w", err) + } var chains []*chain.Chain for _, item := range items { serialized, err := bytesFromStackItem(item) @@ -121,10 +162,13 @@ func (s *ContractStorage) ListMorphRuleChains(name chain.Name, target engine.Tar } chains = append(chains, c) } - return chains, nil } +func (s *ContractStorage) ListMorphRuleChains(name chain.Name, target engine.Target) ([]*chain.Chain, error) { + return listChains(name, target, s.actor.GetRPCInvoker(), s.hash) +} + func (s *ContractStorage) ListTargetsIterator(targetType engine.TargetType) (uuid.UUID, result.Iterator, error) { kind, err := policyKind(targetType) if err != nil { @@ -141,37 +185,21 @@ func (s *ContractStorage) SetAdmin(addr util.Uint160) (util.Uint256, uint32, err return s.contractInterface.SetAdmin(addr) } -func NewContractStorageReader(inv client.Invoker, contract util.Uint160) *ContractStorageReader { +type ContractStorageInvoker interface { + client.Invoker + GetRPCInvoker() neoinvoker.RPCInvoke +} + +func NewContractStorageReader(inv ContractStorageInvoker, contract util.Uint160) *ContractStorageReader { return &ContractStorageReader{ + hash: contract, + invoker: inv, contractReaderInterface: client.NewReader(inv, contract), } } func (s *ContractStorageReader) ListMorphRuleChains(name chain.Name, target engine.Target) ([]*chain.Chain, error) { - kind, err := policyKind(target.Type) - if err != nil { - return nil, err - } - - items, err := s.contractReaderInterface.ListChainsByPrefix(big.NewInt(int64(kind)), target.Name, []byte(name)) - if err != nil { - return nil, err - } - - var chains []*chain.Chain - for _, item := range items { - serialized, err := bytesFromStackItem(item) - if err != nil { - return nil, err - } - c := new(chain.Chain) - if err := c.DecodeBytes(serialized); err != nil { - return nil, err - } - chains = append(chains, c) - } - - return chains, nil + return listChains(name, target, s.invoker.GetRPCInvoker(), s.hash) } func (s *ContractStorageReader) GetAdmin() (util.Uint160, error) {