policy-engine/pkg/morph/policy/policy_contract_storage.go
aarifullin c539728641 [#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 <aarifullin@yadro.com>
2024-04-26 06:20:43 +00:00

248 lines
7.2 KiB
Go

package policy
import (
"errors"
"fmt"
"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"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
"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"
)
var (
ErrEmptyChainID = errors.New("chain id is not set")
ErrEngineTargetTypeUnsupported = errors.New("this target type is not supported yet")
)
// ContractStorage is the interface to manage chain rules within Policy contract.
type ContractStorage struct {
hash util.Uint160
actor ContractStorageActor
contractInterface *client.Contract
}
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 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(&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) {
if len(c.ID) == 0 {
err = ErrEmptyChainID
return
}
var kind policy.Kind
kind, err = policyKind(target.Type)
if err != nil {
return
}
fullName := prefixedChainName(name, c.ID)
txHash, vub, err = s.contractInterface.AddChain(big.NewInt(int64(kind)), target.Name, fullName, c.Bytes())
return
}
func (s *ContractStorage) RemoveMorphRuleChain(name chain.Name, target engine.Target, chainID chain.ID) (txHash util.Uint256, vub uint32, err error) {
if len(chainID) == 0 {
err = ErrEmptyChainID
return
}
var kind policy.Kind
kind, err = policyKind(target.Type)
if err != nil {
return
}
fullName := prefixedChainName(name, chainID)
txHash, vub, err = s.contractInterface.RemoveChain(big.NewInt(int64(kind)), target.Name, fullName)
return
}
func (s *ContractStorage) RemoveMorphRuleChainsByTarget(name chain.Name, target engine.Target) (txHash util.Uint256, vub uint32, err error) {
var kind policy.Kind
kind, err = policyKind(target.Type)
if err != nil {
return
}
fullName := prefixedChainName(name, nil)
txHash, vub, err = s.contractInterface.RemoveChainsByPrefix(big.NewInt(int64(kind)), target.Name, fullName)
return
}
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
}
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)
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
}
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 {
return uuid.UUID{}, result.Iterator{}, err
}
return s.contractInterface.ListTargets(big.NewInt(int64(kind)))
}
func (s *ContractStorage) GetAdmin() (util.Uint160, error) {
return s.contractInterface.GetAdmin()
}
func (s *ContractStorage) SetAdmin(addr util.Uint160) (util.Uint256, uint32, error) {
return s.contractInterface.SetAdmin(addr)
}
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) {
return listChains(name, target, s.invoker.GetRPCInvoker(), s.hash)
}
func (s *ContractStorageReader) GetAdmin() (util.Uint160, error) {
return s.contractReaderInterface.GetAdmin()
}
func (s *ContractStorageReader) ListTargetsIterator(targetType engine.TargetType) (uuid.UUID, result.Iterator, error) {
kind, err := policyKind(targetType)
if err != nil {
return uuid.UUID{}, result.Iterator{}, err
}
return s.contractReaderInterface.ListTargets(big.NewInt(int64(kind)))
}
func bytesFromStackItem(param stackitem.Item) ([]byte, error) {
switch param.Type() {
case stackitem.BufferT, stackitem.ByteArrayT, stackitem.IntegerT:
return param.TryBytes()
case stackitem.AnyT:
if param.Value() == nil {
return nil, nil
}
fallthrough
default:
return nil, fmt.Errorf("chain/client: %s is not a byte array type", param.Type())
}
}
func prefixedChainName(name chain.Name, chainID chain.ID) []byte {
return []byte(strings.ToLower(fmt.Sprintf("%s:%s", name, chainID)))
}
func policyKind(typ engine.TargetType) (policy.Kind, error) {
switch typ {
case engine.Namespace:
return policy.Namespace, nil
case engine.Container:
return policy.Container, nil
case engine.User:
return policy.Kind(engine.User), nil
case engine.Group:
return policy.Kind(engine.Group), nil
default:
return policy.Kind(0), ErrEngineTargetTypeUnsupported
}
}