diff --git a/cmd/neofs-net-monitor/config.go b/cmd/neofs-net-monitor/config.go index e9baf5e..3b90466 100644 --- a/cmd/neofs-net-monitor/config.go +++ b/cmd/neofs-net-monitor/config.go @@ -12,11 +12,7 @@ const ( delimiter = "." // contracts scripthash - cfgNetmapContract = "contracts.netmap" - cfgProxyContract = "contracts.proxy" - cfgBalanceContract = "contracts.balance" - cfgNeoFSContract = "contracts.neofs" - cfgContainerContract = "contracts.container" + cfgNeoFSContract = "contracts.neofs" // neo rpc node related config values mainPrefix = "mainnet" @@ -38,9 +34,6 @@ const ( ) func DefaultConfiguration(cfg *viper.Viper) { - cfg.SetDefault(cfgNetmapContract, "") - cfg.SetDefault(cfgProxyContract, "") - cfg.SetDefault(sidePrefix+delimiter+cfgNeoRPCEndpoint, "") cfg.SetDefault(sidePrefix+delimiter+cfgNeoRPCDialTimeout, 5*time.Second) diff --git a/cmd/neofs-net-monitor/monitor.go b/cmd/neofs-net-monitor/monitor.go index 4834bfb..a179b41 100644 --- a/cmd/neofs-net-monitor/monitor.go +++ b/cmd/neofs-net-monitor/monitor.go @@ -2,13 +2,10 @@ package main import ( "context" - "errors" "fmt" - "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" - "github.com/nspcc-dev/neofs-contract/nns" + rpcnns "github.com/nspcc-dev/neofs-contract/rpc/nns" "github.com/nspcc-dev/neofs-net-monitor/pkg/locode" "github.com/nspcc-dev/neofs-net-monitor/pkg/monitor" "github.com/nspcc-dev/neofs-net-monitor/pkg/morphchain" @@ -52,12 +49,12 @@ func New(ctx context.Context, cfg *viper.Viper) (*monitor.Monitor, error) { return nil, fmt.Errorf("can't create main chain neo-go client: %w", err) } - netmapContract, err := getScriptHash(cfg, sideNeogoClient, "netmap.neofs", cfgNetmapContract) + netmapContract, err := sideNeogoClient.ResolveContract(rpcnns.NameNetmap) if err != nil { return nil, fmt.Errorf("can't read netmap scripthash: %w", err) } - containerContract, err := getScriptHash(cfg, sideNeogoClient, "container.neofs", cfgContainerContract) + containerContract, err := sideNeogoClient.ResolveContract(rpcnns.NameContainer) if err != nil { return nil, fmt.Errorf("can't read container scripthash: %w", err) } @@ -104,12 +101,12 @@ func New(ctx context.Context, cfg *viper.Viper) (*monitor.Monitor, error) { neofs *util.Uint160 ) - balance, err = getScriptHash(cfg, sideNeogoClient, "balance.neofs", cfgBalanceContract) + balance, err = sideNeogoClient.ResolveContract(rpcnns.NameBalance) if err != nil { return nil, fmt.Errorf("balance contract is not available: %w", err) } - proxyContract, err := getScriptHash(cfg, sideNeogoClient, "proxy.neofs", cfgProxyContract) + proxyContract, err := sideNeogoClient.ResolveContract(rpcnns.NameProxy) if err != nil { logger.Info("proxy disabled") } else { @@ -149,53 +146,3 @@ func New(ctx context.Context, cfg *viper.Viper) (*monitor.Monitor, error) { CnrFetcher: cnrFetcher, }), nil } - -const nnsContractID = 1 - -func getScriptHash(cfg *viper.Viper, cli *pool.Pool, nnsKey, configKey string) (sh util.Uint160, err error) { - cs, err := cli.GetContractStateByID(nnsContractID) - if err != nil { - return sh, fmt.Errorf("NNS contract state: %w", err) - } - - hash := cfg.GetString(configKey) - if len(hash) == 0 { - sh, err = nnsResolve(cli, cs.Hash, nnsKey) - if err != nil { - return sh, fmt.Errorf("NNS.resolve: %w", err) - } - } else { - sh, err = util.Uint160DecodeStringLE(hash) - if err != nil { - return sh, fmt.Errorf("NNS u160 decode: %w", err) - } - } - - return sh, nil -} - -func nnsResolve(c *pool.Pool, nnsHash util.Uint160, domain string) (util.Uint160, error) { - item, err := unwrap.Item(c.Call(nnsHash, "resolve", domain, int64(nns.TXT))) - if err != nil { - return util.Uint160{}, fmt.Errorf("contract invocation: %w", err) - } - - if _, ok := item.(stackitem.Null); ok { - return util.Uint160{}, errors.New("NNS record is missing") - } - - // Parse the result of resolving NNS record. - // It works with multiple formats (corresponding to multiple NNS versions). - // If array of hashes is provided, it returns only the first one. - if arr, ok := item.Value().([]stackitem.Item); ok { - if len(arr) == 0 { - return util.Uint160{}, errors.New("NNS record is missing") - } - item = arr[0] - } - bs, err := item.TryBytes() - if err != nil { - return util.Uint160{}, fmt.Errorf("malformed response: %w", err) - } - return util.Uint160DecodeStringLE(string(bs)) -} diff --git a/pkg/morphchain/balance.go b/pkg/morphchain/balance.go index d1ef15f..5e428f6 100644 --- a/pkg/morphchain/balance.go +++ b/pkg/morphchain/balance.go @@ -4,6 +4,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/rpcclient/gas" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17" "github.com/nspcc-dev/neo-go/pkg/rpcclient/notary" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neofs-net-monitor/pkg/pool" @@ -11,9 +12,7 @@ import ( type ( BalanceFetcher struct { - cli *pool.Pool - gas util.Uint160 - notary util.Uint160 + cli *pool.Pool } BalanceFetcherArgs struct { @@ -23,9 +22,7 @@ type ( func NewBalanceFetcher(p BalanceFetcherArgs) (*BalanceFetcher, error) { return &BalanceFetcher{ - cli: p.Cli, - gas: gas.Hash, - notary: notary.Hash, + cli: p.Cli, }, nil } @@ -42,13 +39,28 @@ func (b BalanceFetcher) FetchNotary(key keys.PublicKey) (int64, error) { } func (b BalanceFetcher) FetchGASByScriptHash(sh util.Uint160) (int64, error) { - return b.cli.NEP17BalanceOf(b.gas, sh) + res, err := gas.NewReader(b.cli).BalanceOf(sh) + if err != nil { + return 0, err + } + + return res.Int64(), nil } func (b BalanceFetcher) FetchNotaryByScriptHash(sh util.Uint160) (int64, error) { - return b.cli.NEP17BalanceOf(b.notary, sh) + res, err := notary.NewReader(b.cli).BalanceOf(sh) + if err != nil { + return 0, err + } + + return res.Int64(), nil } func (b BalanceFetcher) FetchNEP17TotalSupply(tokenHash util.Uint160) (int64, error) { - return b.cli.NEP17TotalSupply(tokenHash) + res, err := nep17.NewReader(b.cli, tokenHash).TotalSupply() + if err != nil { + return 0, err + } + + return res.Int64(), nil } diff --git a/pkg/morphchain/contracts/container.go b/pkg/morphchain/contracts/container.go index 4db0a7c..0a87217 100644 --- a/pkg/morphchain/contracts/container.go +++ b/pkg/morphchain/contracts/container.go @@ -1,28 +1,29 @@ package contracts import ( - "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" + "fmt" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neofs-contract/rpc/container" "github.com/nspcc-dev/neofs-net-monitor/pkg/pool" ) type Container struct { - pool *pool.Pool - contractHash util.Uint160 + contractReader *container.ContractReader } -const ( - count = "count" -) - // NewContainer creates Container to interact with 'container' contract in morph chain. func NewContainer(p *pool.Pool, contractHash util.Uint160) (*Container, error) { return &Container{ - pool: p, - contractHash: contractHash, + contractReader: container.NewReader(p, contractHash), }, nil } func (c *Container) Total() (int64, error) { - return unwrap.Int64(c.pool.Call(c.contractHash, count)) + amount, err := c.contractReader.Count() + if err != nil { + return 0, fmt.Errorf("count: %w", err) + } + + return amount.Int64(), nil } diff --git a/pkg/morphchain/contracts/netmap.go b/pkg/morphchain/contracts/netmap.go index e4d4aee..ae01e4d 100644 --- a/pkg/morphchain/contracts/netmap.go +++ b/pkg/morphchain/contracts/netmap.go @@ -11,9 +11,9 @@ import ( "github.com/nspcc-dev/hrw" "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neofs-api-go/v2/rpc/client" + rpcnetmap "github.com/nspcc-dev/neofs-contract/rpc/netmap" "github.com/nspcc-dev/neofs-net-monitor/pkg/monitor" "github.com/nspcc-dev/neofs-net-monitor/pkg/pool" "github.com/nspcc-dev/neofs-sdk-go/netmap" @@ -23,9 +23,10 @@ import ( type ( Netmap struct { pool *pool.Pool - contractHash util.Uint160 logger *zap.Logger notaryDisabled bool + + contractReader *rpcnetmap.ContractReader } NetmapArgs struct { @@ -35,19 +36,12 @@ type ( } ) -const ( - epochMethod = "epoch" - netmapMethod = "netmap" - netmapCandidatesMethod = "netmapCandidates" - innerRingListMethod = "innerRingList" -) - // NewNetmap creates Netmap to interact with 'netmap' contract in morph chain. func NewNetmap(p NetmapArgs) (*Netmap, error) { return &Netmap{ - pool: p.Pool, - contractHash: p.NetmapContract, - logger: p.Logger, + pool: p.Pool, + logger: p.Logger, + contractReader: rpcnetmap.NewReader(p.Pool, p.NetmapContract), }, nil } @@ -122,53 +116,34 @@ func (c *Netmap) FetchInnerRingKeys() (keys.PublicKeys, error) { } func (c *Netmap) Epoch() (int64, error) { - return unwrap.Int64(c.pool.Call(c.contractHash, epochMethod)) + e, err := c.contractReader.Epoch() + if err != nil { + return 0, fmt.Errorf("epoch: %w", err) + } + + return e.Int64(), nil } func (c *Netmap) Netmap() ([]*netmap.NodeInfo, error) { - return c.getNodesInfo(netmapMethod) + return c.parsedNodes((*rpcnetmap.ContractReader).Netmap) } func (c *Netmap) NetmapCandidates() ([]*netmap.NodeInfo, error) { - return c.getNodesInfo(netmapCandidatesMethod) + return c.parsedNodes((*rpcnetmap.ContractReader).NetmapCandidates) } -func (c *Netmap) getNodesInfo(method string) ([]*netmap.NodeInfo, error) { - arr, err := unwrap.Array(c.pool.Call(c.contractHash, method)) +func (c *Netmap) parsedNodes(f func(reader *rpcnetmap.ContractReader) ([]*rpcnetmap.NetmapNode, error)) ([]*netmap.NodeInfo, error) { + data, err := f(c.contractReader) if err != nil { - return nil, err - } - - nodes := make([]*netmap.NodeInfo, 0, len(arr)) - for _, item := range arr { - nodeInfo, err := parseNodeInfo(item) - if err != nil { - return nil, err - } - - nodes = append(nodes, nodeInfo) + return nil, fmt.Errorf("contract reader: %w", err) } - return nodes, nil -} - -func (c *Netmap) InnerRingList() (keys.PublicKeys, error) { - arr, err := unwrap.Array(c.pool.Call(c.contractHash, innerRingListMethod)) + nodes, err := parseContractNodes(data) if err != nil { - return nil, err - } - - irKeys := make(keys.PublicKeys, 0, len(arr)) - - for _, item := range arr { - irKey, err := parseIRNode(item) - if err != nil { - return nil, err - } - irKeys = append(irKeys, irKey) + return nil, fmt.Errorf("parseContractNodes: %w", err) } - return irKeys, nil + return nodes, nil } func processNode(logger *zap.Logger, node *netmap.NodeInfo) (*monitor.Node, error) { diff --git a/pkg/morphchain/contracts/utils.go b/pkg/morphchain/contracts/utils.go index db168c7..d6c2f49 100644 --- a/pkg/morphchain/contracts/utils.go +++ b/pkg/morphchain/contracts/utils.go @@ -1,83 +1,40 @@ package contracts import ( - "crypto/elliptic" "fmt" - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" v2netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap" netmapContract "github.com/nspcc-dev/neofs-contract/netmap" + rcpnetmap "github.com/nspcc-dev/neofs-contract/rpc/netmap" "github.com/nspcc-dev/neofs-sdk-go/netmap" ) -func getInt64(st []stackitem.Item) (int64, error) { - index := len(st) - 1 // top stack element is last in the array - bi, err := st[index].TryInteger() - if err != nil { - return 0, err - } - return bi.Int64(), nil -} - -func parseNodeInfo(st stackitem.Item) (*netmap.NodeInfo, error) { - values, ok := st.Value().([]stackitem.Item) - if !ok { - return nil, fmt.Errorf("invalid netmap node") - } - - if len(values) != 2 { - return nil, fmt.Errorf("invalid netmap node") - } - - rawNode, err := values[0].TryBytes() - if err != nil { - return nil, fmt.Errorf("failed to get node field: %w", err) - } - - var nodeInfoV2 v2netmap.NodeInfo - if err = nodeInfoV2.Unmarshal(rawNode); err != nil { - return nil, fmt.Errorf("can't unmarshal peer info: %w", err) - } - - state, err := getInt64(values[1:2]) - if err != nil { - return nil, fmt.Errorf("failed to get state field: %w", err) - } - - switch state { - case int64(netmapContract.NodeStateOnline): - nodeInfoV2.SetState(v2netmap.Online) - case int64(netmapContract.NodeStateOffline): - nodeInfoV2.SetState(v2netmap.Offline) - case int64(netmapContract.NodeStateMaintenance): - nodeInfoV2.SetState(v2netmap.Maintenance) - default: - nodeInfoV2.SetState(v2netmap.UnspecifiedState) - } - - var nodeInfo netmap.NodeInfo - if err = nodeInfo.ReadFromV2(nodeInfoV2); err != nil { - return nil, fmt.Errorf("failed to read node info from v2: %w", err) - } - - return &nodeInfo, nil -} - -func parseIRNode(st stackitem.Item) (*keys.PublicKey, error) { - values, ok := st.Value().([]stackitem.Item) - if !ok { - return nil, fmt.Errorf("invalid ir node") - } - - if len(values) < 1 { - return nil, fmt.Errorf("invalid ir node") - } - - rawKey, err := values[0].TryBytes() - if err != nil { - return nil, fmt.Errorf("failed to get node field: %w", err) - } - - return keys.NewPublicKeyFromBytes(rawKey, elliptic.P256()) +func parseContractNodes(data []*rcpnetmap.NetmapNode) ([]*netmap.NodeInfo, error) { + nodes := make([]*netmap.NodeInfo, 0, len(data)) + for _, d := range data { + var nodeInfoV2 v2netmap.NodeInfo + if err := nodeInfoV2.Unmarshal(d.BLOB); err != nil { + return nil, fmt.Errorf("can't unmarshal peer info: %w", err) + } + + switch d.State.Int64() { + case int64(netmapContract.NodeStateOnline): + nodeInfoV2.SetState(v2netmap.Online) + case int64(netmapContract.NodeStateOffline): + nodeInfoV2.SetState(v2netmap.Offline) + case int64(netmapContract.NodeStateMaintenance): + nodeInfoV2.SetState(v2netmap.Maintenance) + default: + nodeInfoV2.SetState(v2netmap.UnspecifiedState) + } + + var nodeInfo netmap.NodeInfo + if err := nodeInfo.ReadFromV2(nodeInfoV2); err != nil { + return nil, fmt.Errorf("failed to read node info from v2: %w", err) + } + + nodes = append(nodes, &nodeInfo) + } + + return nodes, nil } diff --git a/pkg/pool/pool.go b/pkg/pool/pool.go index 62ce009..b3f6ca9 100644 --- a/pkg/pool/pool.go +++ b/pkg/pool/pool.go @@ -2,20 +2,23 @@ package pool import ( "context" + "errors" "fmt" "sync" "sync/atomic" "time" + "github.com/google/uuid" "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/rpcclient" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" - "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17" "github.com/nspcc-dev/neo-go/pkg/rpcclient/rolemgmt" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + rpcnns "github.com/nspcc-dev/neofs-contract/rpc/nns" ) // Pool represent virtual connection to the Neo network to communicate @@ -94,8 +97,7 @@ func (p *Pool) nextConnection() (*rpcclient.Client, bool, error) { return p.conn(), true, nil } -// nextInvoker returns invoker wrapper on healthy connection, -// the second resp value is true if current connection was updated. +// nextInvoker returns invoker wrapper on healthy connection. // Returns error if there are no healthy connections. func (p *Pool) nextInvoker() (*invoker.Invoker, error) { if p.isCurrentHealthy() { @@ -119,46 +121,42 @@ func (p *Pool) GetContractStateByID(id int32) (*state.Contract, error) { return conn.GetContractStateByID(id) } -// NEP17BalanceOf invokes `balanceOf` NEP17 method on a specified contract. -func (p *Pool) NEP17BalanceOf(tokenHash, acc util.Uint160) (int64, error) { - invokerConn, err := p.nextInvoker() - if err != nil { - return 0, err - } - - res, err := nep17.NewReader(invokerConn, tokenHash).BalanceOf(acc) +// Call returns the results after calling the smart contract scripthash +// with the given operation and parameters. +// NOTE: this is test invoke and will not affect the blockchain. +func (p *Pool) Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error) { + conn, err := p.nextInvoker() if err != nil { - return 0, err + return nil, err } - return res.Int64(), nil + return conn.Call(contract, operation, params...) } -// NEP17TotalSupply invokes `totalSupply` NEP17 method on a specified contract. -func (p *Pool) NEP17TotalSupply(tokenHash util.Uint160) (int64, error) { - invokerConn, err := p.nextInvoker() - if err != nil { - return 0, err - } - - res, err := nep17.NewReader(invokerConn, tokenHash).TotalSupply() +// CallAndExpandIterator creates a script containing a call of the specified method +// of a contract with given parameters (similar to how Call operates). But then this +// script contains additional code that expects that the result of the first call is +// an iterator. +func (p *Pool) CallAndExpandIterator(contract util.Uint160, method string, maxItems int, params ...any) (*result.Invoke, error) { + conn, err := p.nextInvoker() if err != nil { - return 0, err + return nil, err } - return res.Int64(), nil + return conn.CallAndExpandIterator(contract, method, maxItems, params...) } -// Call returns the results after calling the smart contract scripthash -// with the given operation and parameters. -// NOTE: this is test invoke and will not affect the blockchain. -func (p *Pool) Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error) { - conn, err := p.nextInvoker() - if err != nil { - return nil, err - } +// TerminateSession closes the given session, returning an error if anything +// goes wrong. It's not strictly required to close the session (it'll expire on +// the server anyway), but it helps to release server resources earlier. +func (p *Pool) TerminateSession(_ uuid.UUID) error { + return errors.New("unsupported") +} - return conn.Call(contract, operation, params...) +// TraverseIterator allows to retrieve the next batch of items from the given +// iterator in the given session (previously returned from Call). +func (p *Pool) TraverseIterator(_ uuid.UUID, _ *result.Iterator, _ int) ([]stackitem.Item, error) { + return nil, errors.New("unsupported") } // GetBlockCount returns the number of blocks in the main chain. @@ -233,3 +231,20 @@ func neoGoClient(ctx context.Context, endpoint string, opts rpcclient.Options) ( return cli, nil } + +// ResolveContract helps to take contract address by contract name. Name list can be taken from contract wrappers, +// for instance [rpcnns.NameNetmap]. +func (p *Pool) ResolveContract(contractName string) (util.Uint160, error) { + nnsHash, err := rpcnns.InferHash(p) + if err != nil { + return util.Uint160{}, fmt.Errorf("GetContractStateByID: %w", err) + } + + nnsReader := rpcnns.NewReader(p, nnsHash) + addr, err := nnsReader.ResolveFSContract(contractName) + if err != nil { + return util.Uint160{}, fmt.Errorf("ResolveFSContract [%s]: %w", contractName, err) + } + + return addr, nil +}