forked from TrueCloudLab/frostfs-node
[#995] neofs-adm: implement deposit-notary
command
Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
parent
fba8890224
commit
40b51b3586
5 changed files with 201 additions and 32 deletions
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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,19 +143,15 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex
|
||||||
}
|
}
|
||||||
|
|
||||||
initCtx := &initializeContext{
|
initCtx := &initializeContext{
|
||||||
clientContext: clientContext{
|
clientContext: *defaultClientContext(c),
|
||||||
Client: c,
|
ConsensusAcc: consensusAcc,
|
||||||
WaitDuration: time.Second * 30,
|
CommitteeAcc: committeeAcc,
|
||||||
PollInterval: time.Second,
|
Wallets: wallets,
|
||||||
},
|
Accounts: accounts,
|
||||||
ConsensusAcc: consensusAcc,
|
Command: cmd,
|
||||||
CommitteeAcc: committeeAcc,
|
Contracts: make(map[string]*contractState),
|
||||||
Wallets: wallets,
|
ContractPath: ctrPath,
|
||||||
Accounts: accounts,
|
Natives: nativeHashes,
|
||||||
Command: cmd,
|
|
||||||
Contracts: make(map[string]*contractState),
|
|
||||||
ContractPath: ctrPath,
|
|
||||||
Natives: nativeHashes,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if needContracts {
|
if needContracts {
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue