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,
|
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{
|
updateContractsCmd = &cobra.Command{
|
||||||
Use: "update-contracts",
|
Use: "update-contracts",
|
||||||
Short: "Update NeoFS contracts.",
|
Short: "Update NeoFS contracts.",
|
||||||
|
@ -209,6 +218,13 @@ func init() {
|
||||||
RootCmd.AddCommand(dumpNetworkConfigCmd)
|
RootCmd.AddCommand(dumpNetworkConfigCmd)
|
||||||
dumpNetworkConfigCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
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)
|
RootCmd.AddCommand(updateContractsCmd)
|
||||||
updateContractsCmd.Flags().String(alphabetWalletsFlag, "", "path to alphabet wallets dir")
|
updateContractsCmd.Flags().String(alphabetWalletsFlag, "", "path to alphabet wallets dir")
|
||||||
updateContractsCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
updateContractsCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||||
|
|
Loading…
Reference in a new issue