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 } }