233 lines
6.1 KiB
Go
233 lines
6.1 KiB
Go
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/rpcclient/waiter"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
|
)
|
|
|
|
type Client struct {
|
|
actor *actor.Actor
|
|
waiter waiter.Waiter
|
|
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
|
|
|
|
// Waiter contains options for awaiting blockchain transactions
|
|
// submitted via the contract client.
|
|
Waiter commonclient.WaiterOptions
|
|
}
|
|
|
|
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,
|
|
waiter: commonclient.NewWaiter(act, cfg.Waiter),
|
|
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.waiter.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.waiter.Wait(c.actor.SendRun(scripts[i])); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|