package contract import ( "context" "errors" "fmt" "math/big" "git.frostfs.info/TrueCloudLab/frostfs-contract/commonclient" policycontract "git.frostfs.info/TrueCloudLab/frostfs-contract/policy" policyclient "git.frostfs.info/TrueCloudLab/frostfs-contract/rpcclient/policy" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/policy" frostfsutil "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/util" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/rpcclient" "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" "github.com/nspcc-dev/neo-go/pkg/rpcclient/notary" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/wallet" ) type Client struct { actor *actor.Actor policyContract *policyclient.Contract contractHash util.Uint160 } type Config struct { // RPCAddress is an endpoint to connect to neo rpc. RPCAddress string // Contract is hash of contract or its name in NNS. Contract string // ProxyContract is hash of proxy contract or its name in NNS to interact with policy. ProxyContract string // Key is used to interact with policy contract. // If this is nil than random key will be generated. Key *keys.PrivateKey } const ( batchSize = 100 iteratorChainsByPrefix = "iteratorChainsByPrefix" ) var _ policy.Contract = (*Client)(nil) // New creates new Policy contract wrapper. func New(ctx context.Context, cfg Config) (*Client, error) { contractHash, err := frostfsutil.ResolveContractHash(cfg.Contract, cfg.RPCAddress) if err != nil { return nil, fmt.Errorf("resolve frostfs contract hash: %w", err) } key := cfg.Key if key == nil { if key, err = keys.NewPrivateKey(); err != nil { return nil, fmt.Errorf("generate anon private key for policy: %w", err) } } rpcCli, err := rpcclient.New(ctx, cfg.RPCAddress, rpcclient.Options{}) if err != nil { return nil, fmt.Errorf("create policy rpc client: %w", err) } proxyContractHash, err := frostfsutil.ResolveContractHash(cfg.ProxyContract, cfg.RPCAddress) if err != nil { return nil, fmt.Errorf("resolve frostfs contract hash: %w", err) } act, err := actor.New(rpcCli, getSigners(key, proxyContractHash, contractHash)) if err != nil { return nil, fmt.Errorf("create new actor: %w", err) } return &Client{ actor: act, policyContract: policyclient.New(act, contractHash), contractHash: contractHash, }, nil } func getSigners(key *keys.PrivateKey, proxyHash, contractHash util.Uint160) []actor.SignerAccount { acc := wallet.NewAccountFromPrivateKey(key) return []actor.SignerAccount{ { Signer: transaction.Signer{ Account: proxyHash, Scopes: transaction.CustomContracts, AllowedContracts: []util.Uint160{contractHash}, }, Account: notary.FakeContractAccount(proxyHash), }, { Signer: transaction.Signer{ Account: acc.Contract.ScriptHash(), Scopes: transaction.CalledByEntry, }, Account: acc, }, } } func (c *Client) AddChain(kind policycontract.Kind, entity string, name []byte, chain []byte) (util.Uint256, uint32, error) { return c.policyContract.AddChain(big.NewInt(int64(kind)), entity, name, chain) } func (c *Client) GetChain(kind policycontract.Kind, entity string, name []byte) ([]byte, error) { return c.policyContract.GetChain(big.NewInt(int64(kind)), entity, name) } func (c *Client) RemoveChain(kind policycontract.Kind, entity string, name []byte) (util.Uint256, uint32, error) { return c.policyContract.RemoveChain(big.NewInt(int64(kind)), entity, name) } func (c *Client) ListChains(kind policycontract.Kind, entity string, name []byte) ([][]byte, error) { items, err := commonclient.ReadIteratorItems(c.actor, batchSize, c.contractHash, iteratorChainsByPrefix, big.NewInt(int64(kind)), entity, name) if err != nil { return nil, err } res := make([][]byte, len(items)) for i, item := range items { res[i], err = item.TryBytes() if err != nil { return nil, err } } return res, nil } func (c *Client) Wait(tx util.Uint256, vub uint32, err error) error { _, err = c.actor.Wait(tx, vub, err) return err } type multiTX struct { contractHash util.Uint160 txs []*commonclient.Transaction err error } func (m *multiTX) AddChain(entity policycontract.Kind, entityName string, name []byte, chain []byte) { m.wrapCall("addChain", []any{big.NewInt(int64(entity)), entityName, name, chain}) } func (m *multiTX) RemoveChain(entity policycontract.Kind, entityName string, name []byte) { m.wrapCall("removeChain", []any{big.NewInt(int64(entity)), entityName, name}) } func (m *multiTX) Scripts() ([][]byte, error) { if m.err != nil { return nil, m.err } if len(m.txs) == 0 { return nil, errors.New("tx isn't initialized") } res := make([][]byte, 0, len(m.txs)) for _, tx := range m.txs { script, err := tx.Bytes() if err != nil { return nil, err } res = append(res, script) } return res, nil } func (m *multiTX) wrapCall(method string, args []any) { if m.err != nil { return } if len(m.txs) == 0 { m.err = errors.New("multi tx isn't initialized") return } err := m.txs[len(m.txs)-1].WrapCall(method, args) if err == nil { return } if !errors.Is(err, commonclient.ErrTransactionTooLarge) { m.err = err return } tx := commonclient.NewTransaction(m.contractHash) m.err = tx.WrapCall(method, args) if m.err == nil { m.txs = append(m.txs, tx) } } func (c *Client) StartTx() policy.MultiTransaction { return &multiTX{ txs: []*commonclient.Transaction{commonclient.NewTransaction(c.contractHash)}, contractHash: c.contractHash, } } func (c *Client) SendTx(mtx policy.MultiTransaction) error { var err error scripts, err := mtx.Scripts() if err != nil { return err } for i := range scripts { if _, err = c.actor.Wait(c.actor.SendRun(scripts[i])); err != nil { return err } } return nil }