[#995] neofs-adm: implement deposit-notary command

Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
Evgenii Stratonikov 2021-11-30 14:57:58 +03:00 committed by Alex Vanin
parent fba8890224
commit 40b51b3586
5 changed files with 201 additions and 32 deletions

View file

@ -156,12 +156,9 @@ func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) error {
gasStr := viper.GetString(gasFlag) gasStr := viper.GetString(gasFlag)
gasAmount, err := fixedn.Fixed8FromString(gasStr) gasAmount, err := parseGASAmount(gasStr)
if err != nil { if err != nil {
return fmt.Errorf("invalid GAS amount %s: %w", gasStr, err) return err
}
if gasAmount <= 0 {
return fmt.Errorf("GAS amount must be positive (got %d)", gasAmount)
} }
wCtx, err := newInitializeContext(cmd, viper.GetViper()) wCtx, err := newInitializeContext(cmd, viper.GetViper())
@ -184,3 +181,14 @@ func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) error {
return wCtx.awaitTx() return wCtx.awaitTx()
} }
func parseGASAmount(s string) (fixedn.Fixed8, error) {
gasAmount, err := fixedn.Fixed8FromString(s)
if err != nil {
return 0, fmt.Errorf("invalid GAS amount %s: %w", s, err)
}
if gasAmount <= 0 {
return 0, fmt.Errorf("GAS amount must be positive (got %d)", gasAmount)
}
return gasAmount, nil
}

View file

@ -128,21 +128,9 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex
} }
} }
ns, err := c.GetNativeContracts() nativeHashes, err := getNativeHashes(c)
if err != nil { if err != nil {
return nil, fmt.Errorf("can't get native contract hashes: %w", err) return nil, err
}
notaryEnabled := false
nativeHashes := make(map[string]util.Uint160, len(ns))
for i := range ns {
if ns[i].Manifest.Name == nativenames.Notary {
notaryEnabled = len(ns[i].UpdateHistory) > 0
}
nativeHashes[ns[i].Manifest.Name] = ns[i].Hash
}
if !notaryEnabled {
return nil, errors.New("notary contract must be enabled")
} }
accounts := make([]*wallet.Account, len(wallets)) accounts := make([]*wallet.Account, len(wallets))
@ -155,11 +143,7 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex
} }
initCtx := &initializeContext{ initCtx := &initializeContext{
clientContext: clientContext{ clientContext: *defaultClientContext(c),
Client: c,
WaitDuration: time.Second * 30,
PollInterval: time.Second,
},
ConsensusAcc: consensusAcc, ConsensusAcc: consensusAcc,
CommitteeAcc: committeeAcc, CommitteeAcc: committeeAcc,
Wallets: wallets, Wallets: wallets,
@ -284,3 +268,23 @@ func getWalletAccount(w *wallet.Wallet, typ string) (*wallet.Account, error) {
} }
return nil, fmt.Errorf("account for '%s' not found", typ) return nil, fmt.Errorf("account for '%s' not found", typ)
} }
func getNativeHashes(c *client.Client) (map[string]util.Uint160, error) {
ns, err := c.GetNativeContracts()
if err != nil {
return nil, fmt.Errorf("can't get native contract hashes: %w", err)
}
notaryEnabled := false
nativeHashes := make(map[string]util.Uint160, len(ns))
for i := range ns {
if ns[i].Manifest.Name == nativenames.Notary {
notaryEnabled = len(ns[i].UpdateHistory) > 0
}
nativeHashes[ns[i].Manifest.Name] = ns[i].Hash
}
if !notaryEnabled {
return nil, errors.New("notary contract must be enabled")
}
return nativeHashes, nil
}

View file

@ -35,6 +35,14 @@ func getN3Client(v *viper.Viper) (*client.Client, error) {
return c, nil return c, nil
} }
func defaultClientContext(c *client.Client) *clientContext {
return &clientContext{
Client: c,
WaitDuration: time.Second * 30,
PollInterval: time.Second,
}
}
func (c *clientContext) sendTx(tx *transaction.Transaction, cmd *cobra.Command, await bool) error { func (c *clientContext) sendTx(tx *transaction.Transaction, cmd *cobra.Command, await bool) error {
h, err := c.Client.SendRawTransaction(tx) h, err := c.Client.SendRawTransaction(tx)
if err != nil { if err != nil {

View file

@ -1 +1,132 @@
package morph package morph
import (
"fmt"
"strconv"
"github.com/nspcc-dev/neo-go/cli/input"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"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/encoding/address"
"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/callflag"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// defaultNotaryDepositLifetime is an amount of blocks notary deposit stays valid.
// https://github.com/nspcc-dev/neo-go/blob/master/pkg/core/native/notary.go#L48
const defaultNotaryDepositLifetime = 5760
func depositNotary(cmd *cobra.Command, _ []string) error {
p, err := cmd.Flags().GetString(storageWalletFlag)
if err != nil {
return err
} else if p == "" {
return fmt.Errorf("missing wallet path (use '--%s <out.json>')", storageWalletFlag)
}
w, err := wallet.NewWalletFromFile(p)
if err != nil {
return fmt.Errorf("can't open wallet: %v", err)
}
accHash := w.GetChangeAddress()
if addr, err := cmd.Flags().GetString(walletAccountFlag); err == nil {
accHash, err = address.StringToUint160(addr)
if err != nil {
return fmt.Errorf("invalid address: %s", addr)
}
}
acc := w.GetAccount(accHash)
if acc == nil {
return fmt.Errorf("can't find account for %s", accHash)
}
prompt := fmt.Sprintf("Enter password for %s >", address.Uint160ToString(accHash))
pass, err := input.ReadPassword(prompt)
if err != nil {
return fmt.Errorf("can't get password: %v", err)
}
err = acc.Decrypt(pass, keys.NEP2ScryptParams())
if err != nil {
return fmt.Errorf("can't unlock account: %v", err)
}
gasStr, err := cmd.Flags().GetString(refillGasAmountFlag)
if err != nil {
return err
}
gasAmount, err := parseGASAmount(gasStr)
if err != nil {
return err
}
till := int64(defaultNotaryDepositLifetime)
tillStr, err := cmd.Flags().GetString(notaryDepositTillFlag)
if err != nil {
return err
}
if tillStr != "" {
till, err = strconv.ParseInt(tillStr, 10, 64)
if err != nil || till <= 0 {
return fmt.Errorf("notary deposit lifetime must be a positive integer")
}
}
c, err := getN3Client(viper.GetViper())
if err != nil {
return err
}
nhs, err := getNativeHashes(c)
if err != nil {
return fmt.Errorf("can't get native contract hashes: %w", err)
}
gasHash, ok := nhs[nativenames.Gas]
if !ok {
return fmt.Errorf("can't retrieve %s contract hash", nativenames.Gas)
}
notaryHash, ok := nhs[nativenames.Notary]
if !ok {
return fmt.Errorf("can't retrieve %s contract hash", nativenames.Notary)
}
height, err := c.GetBlockCount()
if err != nil {
return fmt.Errorf("can't get current height: %v", err)
}
bw := io.NewBufBinWriter()
emit.AppCall(bw.BinWriter, gasHash, "transfer", callflag.All,
accHash, notaryHash.BytesBE(), int64(gasAmount), []interface{}{nil, int64(height) + till})
if bw.Err != nil {
return fmt.Errorf("BUG: invalid transfer arguments: %w", bw.Err)
}
tx, err := c.CreateTxFromScript(bw.Bytes(), acc, -1, 0, []client.SignerAccount{{
Signer: transaction.Signer{
Account: acc.Contract.ScriptHash(),
Scopes: transaction.Global,
},
Account: acc,
}})
if err != nil {
return fmt.Errorf("can't create tx: %w", err)
}
err = acc.SignTx(c.GetNetwork(), tx)
if err != nil {
return fmt.Errorf("can't sign tx: %w", err)
}
cc := defaultClientContext(c)
return cc.sendTx(tx, cmd, true)
}

View file

@ -33,6 +33,8 @@ const (
containerContractFlag = "container-contract" containerContractFlag = "container-contract"
containerIDsFlag = "cid" containerIDsFlag = "cid"
refillGasAmountFlag = "gas" refillGasAmountFlag = "gas"
walletAccountFlag = "account"
notaryDepositTillFlag = "till"
) )
var ( var (
@ -150,6 +152,15 @@ var (
}, },
RunE: restoreContainers, RunE: restoreContainers,
} }
depositNotaryCmd = &cobra.Command{
Use: "deposit-notary",
Short: "Deposit GAS for notary service.",
PreRun: func(cmd *cobra.Command, _ []string) {
_ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag))
},
RunE: depositNotary,
}
) )
func init() { func init() {
@ -207,4 +218,11 @@ func init() {
refillGasCmd.Flags().String(refillGasAmountFlag, "", "additional amount of GAS to transfer") refillGasCmd.Flags().String(refillGasAmountFlag, "", "additional amount of GAS to transfer")
RootCmd.AddCommand(cmdSubnet) RootCmd.AddCommand(cmdSubnet)
RootCmd.AddCommand(depositNotaryCmd)
depositNotaryCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
depositNotaryCmd.Flags().String(storageWalletFlag, "", "path to storage node wallet")
depositNotaryCmd.Flags().String(walletAccountFlag, "", "wallet account address")
depositNotaryCmd.Flags().String(refillGasAmountFlag, "", "amount of GAS to deposit")
depositNotaryCmd.Flags().String(notaryDepositTillFlag, "", "notary deposit duration in blocks")
} }