forked from TrueCloudLab/policy-engine
c539728641
* 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>
248 lines
7.2 KiB
Go
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
|
|
}
|
|
}
|