forked from TrueCloudLab/frostfs-node
[#1308] neofs-adm: Add command to dump GAS balances
Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
parent
f1e91313db
commit
606dfa3414
2 changed files with 274 additions and 0 deletions
258
cmd/neofs-adm/internal/modules/morph/balance.go
Normal file
258
cmd/neofs-adm/internal/modules/morph/balance.go
Normal file
|
@ -0,0 +1,258 @@
|
|||
package morph
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"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/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/nspcc-dev/neofs-contract/nns"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type accBalancePair struct {
|
||||
scriptHash util.Uint160
|
||||
balance *big.Int
|
||||
}
|
||||
|
||||
const (
|
||||
dumpBalancesStorageFlag = "storage"
|
||||
dumpBalancesAlphabetFlag = "alphabet"
|
||||
dumpBalancesProxyFlag = "proxy"
|
||||
dumpBalancesUseScriptHashFlag = "script-hash"
|
||||
|
||||
// notaryEnabled signifies whether contracts were deployed in a notary-enabled environment.
|
||||
// The setting is here to simplify testing and building the command for testnet (notary currently disabled).
|
||||
// It will be removed eventually.
|
||||
notaryEnabled = true
|
||||
)
|
||||
|
||||
func dumpBalances(cmd *cobra.Command, _ []string) error {
|
||||
var (
|
||||
dumpStorage, _ = cmd.Flags().GetBool(dumpBalancesStorageFlag)
|
||||
dumpAlphabet, _ = cmd.Flags().GetBool(dumpBalancesAlphabetFlag)
|
||||
dumpProxy, _ = cmd.Flags().GetBool(dumpBalancesProxyFlag)
|
||||
nnsCs *state.Contract
|
||||
nmHash util.Uint160
|
||||
)
|
||||
|
||||
c, err := getN3Client(viper.GetViper())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gasHash, err := c.GetNativeContractHash(nativenames.Gas)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch hash of the GAS contract: %w", err)
|
||||
}
|
||||
|
||||
if !notaryEnabled || dumpStorage || dumpAlphabet || dumpProxy {
|
||||
nnsCs, err = c.GetContractStateByID(1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get NNS contract info: %w", err)
|
||||
}
|
||||
|
||||
nmHash, err = nnsResolveHash(c, nnsCs.Hash, netmapContract+".neofs")
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get netmap contract hash: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
irList, err := fetchIRNodes(c, nmHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fetchBalances(c, gasHash, irList); err != nil {
|
||||
return err
|
||||
}
|
||||
printBalances(cmd, "Inner ring nodes balances:", irList)
|
||||
|
||||
if dumpStorage {
|
||||
res, err := c.InvokeFunction(nmHash, "netmap", []smartcontract.Parameter{}, nil)
|
||||
if err != nil || res.State != vm.HaltState.String() || len(res.Stack) == 0 {
|
||||
return errors.New("can't fetch the list of storage nodes")
|
||||
}
|
||||
arr, ok := res.Stack[0].Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
return errors.New("can't fetch the list of storage nodes")
|
||||
}
|
||||
|
||||
snList := make([]accBalancePair, len(arr))
|
||||
for i := range arr {
|
||||
node, ok := arr[i].Value().([]stackitem.Item)
|
||||
if !ok || len(node) == 0 {
|
||||
return errors.New("can't parse the list of storage nodes")
|
||||
}
|
||||
bs, err := node[0].TryBytes()
|
||||
if err != nil {
|
||||
return errors.New("can't parse the list of storage nodes")
|
||||
}
|
||||
var ni netmap.NodeInfo
|
||||
if err := ni.Unmarshal(bs); err != nil {
|
||||
return fmt.Errorf("can't parse the list of storage nodes: %w", err)
|
||||
}
|
||||
pub, err := keys.NewPublicKeyFromBytes(ni.PublicKey(), elliptic.P256())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't parse storage node public key: %w", err)
|
||||
}
|
||||
snList[i].scriptHash = pub.GetScriptHash()
|
||||
}
|
||||
|
||||
if err := fetchBalances(c, gasHash, snList); err != nil {
|
||||
return err
|
||||
}
|
||||
printBalances(cmd, "\nStorage node balances:", snList)
|
||||
}
|
||||
|
||||
if dumpProxy {
|
||||
h, err := nnsResolveHash(c, nnsCs.Hash, proxyContract+".neofs")
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get hash of the proxy contract: %w", err)
|
||||
}
|
||||
|
||||
proxyList := []accBalancePair{{scriptHash: h}}
|
||||
if err := fetchBalances(c, gasHash, proxyList); err != nil {
|
||||
return err
|
||||
}
|
||||
printBalances(cmd, "\nProxy contract balance:", proxyList)
|
||||
}
|
||||
|
||||
if dumpAlphabet {
|
||||
alphaList := make([]accBalancePair, len(irList))
|
||||
|
||||
w := io.NewBufBinWriter()
|
||||
for i := range alphaList {
|
||||
emit.AppCall(w.BinWriter, nnsCs.Hash, "resolve", callflag.ReadOnly,
|
||||
getAlphabetNNSDomain(i),
|
||||
int64(nns.TXT))
|
||||
}
|
||||
if w.Err != nil {
|
||||
panic(w.Err)
|
||||
}
|
||||
|
||||
alphaRes, err := c.InvokeScript(w.Bytes(), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch info from NNS: %w", err)
|
||||
}
|
||||
|
||||
for i := range alphaList {
|
||||
h, err := parseNNSResolveResult(alphaRes.Stack[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't fetch the alphabet contract #%d hash: %w", i, err)
|
||||
}
|
||||
alphaList[i].scriptHash = h
|
||||
}
|
||||
|
||||
if err := fetchBalances(c, gasHash, alphaList); err != nil {
|
||||
return err
|
||||
}
|
||||
printBalances(cmd, "\nAlphabet contracts balances:", alphaList)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fetchIRNodes(c *client.Client, nmHash util.Uint160) ([]accBalancePair, error) {
|
||||
var irList []accBalancePair
|
||||
|
||||
if notaryEnabled {
|
||||
height, err := c.GetBlockCount()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't get block height: %w", err)
|
||||
}
|
||||
|
||||
arr, err := c.GetDesignatedByRole(noderoles.NeoFSAlphabet, height)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't fetch list of IR nodes from the netmap contract")
|
||||
}
|
||||
|
||||
irList = make([]accBalancePair, len(arr))
|
||||
for i := range arr {
|
||||
irList[i].scriptHash = arr[i].GetScriptHash()
|
||||
}
|
||||
} else {
|
||||
res, err := c.InvokeFunction(nmHash, "innerRingList", []smartcontract.Parameter{}, nil)
|
||||
if err != nil || res.State != vm.HaltState.String() || len(res.Stack) == 0 {
|
||||
return nil, errors.New("can't fetch list of IR nodes from the netmap contract")
|
||||
}
|
||||
|
||||
arr, ok := res.Stack[0].Value().([]stackitem.Item)
|
||||
if !ok || len(arr) == 0 {
|
||||
return nil, errors.New("can't fetch list of IR nodes: invalid response")
|
||||
}
|
||||
|
||||
irList = make([]accBalancePair, len(arr))
|
||||
for i := range arr {
|
||||
node, ok := arr[i].Value().([]stackitem.Item)
|
||||
if !ok || len(arr) == 0 {
|
||||
return nil, errors.New("can't fetch list of IR nodes: invalid response")
|
||||
}
|
||||
bs, err := node[0].TryBytes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't fetch list of IR nodes: %w", err)
|
||||
}
|
||||
pub, err := keys.NewPublicKeyFromBytes(bs, elliptic.P256())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't parse IR node public key: %w", err)
|
||||
}
|
||||
irList[i].scriptHash = pub.GetScriptHash()
|
||||
}
|
||||
}
|
||||
return irList, nil
|
||||
}
|
||||
|
||||
func printBalances(cmd *cobra.Command, prefix string, accounts []accBalancePair) {
|
||||
useScriptHash, _ := cmd.Flags().GetBool(dumpBalancesUseScriptHashFlag)
|
||||
|
||||
cmd.Println(prefix)
|
||||
for i := range accounts {
|
||||
var addr string
|
||||
if useScriptHash {
|
||||
addr = accounts[i].scriptHash.StringLE()
|
||||
} else {
|
||||
addr = address.Uint160ToString(accounts[i].scriptHash)
|
||||
}
|
||||
cmd.Printf("%s: %s\n", addr, fixedn.ToString(accounts[i].balance, 8))
|
||||
}
|
||||
}
|
||||
|
||||
func fetchBalances(c *client.Client, gasHash util.Uint160, accounts []accBalancePair) error {
|
||||
w := io.NewBufBinWriter()
|
||||
for i := range accounts {
|
||||
emit.AppCall(w.BinWriter, gasHash, "balanceOf", callflag.ReadStates, accounts[i].scriptHash)
|
||||
}
|
||||
if w.Err != nil {
|
||||
panic(w.Err)
|
||||
}
|
||||
|
||||
res, err := c.InvokeScript(w.Bytes(), nil)
|
||||
if err != nil || res.State != vm.HaltState.String() || len(res.Stack) != len(accounts) {
|
||||
return errors.New("can't fetch account balances")
|
||||
}
|
||||
|
||||
for i := range accounts {
|
||||
bal, err := res.Stack[i].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't parse account balance: %w", err)
|
||||
}
|
||||
accounts[i].balance = bal
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -135,6 +135,15 @@ var (
|
|||
RunE: dumpNetworkConfig,
|
||||
}
|
||||
|
||||
dumpBalancesCmd = &cobra.Command{
|
||||
Use: "dump-balances",
|
||||
Short: "Dump GAS balances",
|
||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
|
||||
},
|
||||
RunE: dumpBalances,
|
||||
}
|
||||
|
||||
updateContractsCmd = &cobra.Command{
|
||||
Use: "update-contracts",
|
||||
Short: "Update NeoFS contracts.",
|
||||
|
@ -209,6 +218,13 @@ func init() {
|
|||
RootCmd.AddCommand(dumpNetworkConfigCmd)
|
||||
dumpNetworkConfigCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||
|
||||
RootCmd.AddCommand(dumpBalancesCmd)
|
||||
dumpBalancesCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||
dumpBalancesCmd.Flags().BoolP(dumpBalancesStorageFlag, "s", false, "dump balances of storage nodes from the current netmap")
|
||||
dumpBalancesCmd.Flags().BoolP(dumpBalancesAlphabetFlag, "a", false, "dump balances of alphabet contracts")
|
||||
dumpBalancesCmd.Flags().BoolP(dumpBalancesProxyFlag, "p", false, "dump balances of the proxy contract")
|
||||
dumpBalancesCmd.Flags().Bool(dumpBalancesUseScriptHashFlag, false, "use script-hash format for addresses")
|
||||
|
||||
RootCmd.AddCommand(updateContractsCmd)
|
||||
updateContractsCmd.Flags().String(alphabetWalletsFlag, "", "path to alphabet wallets dir")
|
||||
updateContractsCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||
|
|
Loading…
Reference in a new issue