forked from TrueCloudLab/frostfs-node
[#1466] adm: Do not use morph client in adm
Using morph client leads to requiring WebSocket endpoints. Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
This commit is contained in:
parent
60789dd475
commit
815e02f185
3 changed files with 409 additions and 170 deletions
|
@ -19,7 +19,9 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"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/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
@ -143,7 +145,7 @@ func (l *localClient) CreateTxFromScript(script []byte, acc *wallet.Account, sys
|
||||||
tx.Signers = signers
|
tx.Signers = signers
|
||||||
tx.ValidUntilBlock = l.bc.BlockHeight() + 2
|
tx.ValidUntilBlock = l.bc.BlockHeight() + 2
|
||||||
|
|
||||||
err = l.addNetworkFee(tx, netFee, accounts...)
|
err = l.AddNetworkFee(tx, netFee, accounts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to add network fee: %w", err)
|
return nil, fmt.Errorf("failed to add network fee: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -151,11 +153,36 @@ func (l *localClient) CreateTxFromScript(script []byte, acc *wallet.Account, sys
|
||||||
return tx, nil
|
return tx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// addNetworkFee adds network fee for each witness script and optional extra
|
func (l *localClient) GetCommittee() (keys.PublicKeys, error) {
|
||||||
|
// not used by `morph init` command
|
||||||
|
panic("unexpected call")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *localClient) InvokeFunction(_ util.Uint160, _ string, _ []smartcontract.Parameter, _ []transaction.Signer) (*result.Invoke, error) {
|
||||||
|
// not used by `morph init` command
|
||||||
|
panic("unexpected call")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *localClient) CalculateNotaryFee(_ uint8) (int64, error) {
|
||||||
|
// not used by `morph init` command
|
||||||
|
panic("unexpected call")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *localClient) SignAndPushP2PNotaryRequest(_ *transaction.Transaction, _ []byte, _ int64, _ int64, _ uint32, _ *wallet.Account) (*payload.P2PNotaryRequest, error) {
|
||||||
|
// not used by `morph init` command
|
||||||
|
panic("unexpected call")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *localClient) SignAndPushInvocationTx(_ []byte, _ *wallet.Account, _ int64, _ fixedn.Fixed8, _ []client.SignerAccount) (util.Uint256, error) {
|
||||||
|
// not used by `morph init` command
|
||||||
|
panic("unexpected call")
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddNetworkFee adds network fee for each witness script and optional extra
|
||||||
// network fee to transaction. `accs` is an array signer's accounts.
|
// network fee to transaction. `accs` is an array signer's accounts.
|
||||||
// Copied from neo-go with minor corrections (no need to support contract signers):
|
// Copied from neo-go with minor corrections (no need to support contract signers):
|
||||||
// https://github.com/nspcc-dev/neo-go/blob/6ff11baa1b9e4c71ef0d1de43b92a8c541ca732c/pkg/rpc/client/rpc.go#L960
|
// https://github.com/nspcc-dev/neo-go/blob/6ff11baa1b9e4c71ef0d1de43b92a8c541ca732c/pkg/rpc/client/rpc.go#L960
|
||||||
func (l *localClient) addNetworkFee(tx *transaction.Transaction, extraFee int64, accs ...*wallet.Account) error {
|
func (l *localClient) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs ...*wallet.Account) error {
|
||||||
if len(tx.Signers) != len(accs) {
|
if len(tx.Signers) != len(accs) {
|
||||||
return errors.New("number of signers must match number of scripts")
|
return errors.New("number of signers must match number of scripts")
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,9 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"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/fixedn"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
|
@ -31,6 +34,11 @@ type Client interface {
|
||||||
NEP17BalanceOf(util.Uint160, util.Uint160) (int64, error)
|
NEP17BalanceOf(util.Uint160, util.Uint160) (int64, error)
|
||||||
InvokeScript([]byte, []transaction.Signer) (*result.Invoke, error)
|
InvokeScript([]byte, []transaction.Signer) (*result.Invoke, error)
|
||||||
SendRawTransaction(*transaction.Transaction) (util.Uint256, error)
|
SendRawTransaction(*transaction.Transaction) (util.Uint256, error)
|
||||||
|
GetCommittee() (keys.PublicKeys, error)
|
||||||
|
CalculateNotaryFee(uint8) (int64, error)
|
||||||
|
AddNetworkFee(*transaction.Transaction, int64, ...*wallet.Account) error
|
||||||
|
SignAndPushInvocationTx([]byte, *wallet.Account, int64, fixedn.Fixed8, []client.SignerAccount) (util.Uint256, error)
|
||||||
|
SignAndPushP2PNotaryRequest(*transaction.Transaction, []byte, int64, int64, uint32, *wallet.Account) (*payload.P2PNotaryRequest, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type clientContext struct {
|
type clientContext struct {
|
||||||
|
|
|
@ -7,12 +7,20 @@ import (
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/cli/flags"
|
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||||
"github.com/nspcc-dev/neo-go/cli/input"
|
"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/state"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"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/crypto/keys"
|
||||||
|
neogocli "github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-adm/internal/modules/morph/internal"
|
"github.com/nspcc-dev/neofs-node/cmd/neofs-adm/internal/modules/morph/internal"
|
||||||
"github.com/nspcc-dev/neofs-node/pkg/morph/client"
|
"github.com/nspcc-dev/neofs-node/pkg/morph/client"
|
||||||
morphsubnet "github.com/nspcc-dev/neofs-node/pkg/morph/client/subnet"
|
|
||||||
"github.com/nspcc-dev/neofs-node/pkg/util/rand"
|
"github.com/nspcc-dev/neofs-node/pkg/util/rand"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/subnet"
|
"github.com/nspcc-dev/neofs-sdk-go/subnet"
|
||||||
subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id"
|
subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id"
|
||||||
|
@ -21,12 +29,6 @@ import (
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
// cmdSubnet flags.
|
|
||||||
const (
|
|
||||||
// Neo RPC endpoint
|
|
||||||
flagSubnetEndpoint = endpointFlag
|
|
||||||
)
|
|
||||||
|
|
||||||
func viperBindFlags(cmd *cobra.Command, flags ...string) {
|
func viperBindFlags(cmd *cobra.Command, flags ...string) {
|
||||||
for i := range flags {
|
for i := range flags {
|
||||||
_ = viper.BindPFlag(flags[i], cmd.Flags().Lookup(flags[i]))
|
_ = viper.BindPFlag(flags[i], cmd.Flags().Lookup(flags[i]))
|
||||||
|
@ -39,7 +41,7 @@ var cmdSubnet = &cobra.Command{
|
||||||
Short: "NeoFS subnet management.",
|
Short: "NeoFS subnet management.",
|
||||||
PreRun: func(cmd *cobra.Command, _ []string) {
|
PreRun: func(cmd *cobra.Command, _ []string) {
|
||||||
viperBindFlags(cmd,
|
viperBindFlags(cmd,
|
||||||
flagSubnetEndpoint,
|
endpointFlag,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -110,71 +112,11 @@ func readSubnetKey(key *keys.PrivateKey) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// calls initSubnetClientCheckNotary with unset checkNotary.
|
|
||||||
func initSubnetClient(c *morphsubnet.Client, key *keys.PrivateKey) error {
|
|
||||||
return initSubnetClientCheckNotary(c, key, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// enable notary
|
// enable notary
|
||||||
flagSubnetNotary = "notary"
|
flagSubnetNotary = "notary"
|
||||||
)
|
)
|
||||||
|
|
||||||
// initializes morph subnet client with the specified private key.
|
|
||||||
//
|
|
||||||
// Parameters are read from:
|
|
||||||
// * contract address: flagSubnetContract;
|
|
||||||
// * endpoint: flagSubnetEndpoint.
|
|
||||||
//
|
|
||||||
// If checkNotary is set, notary mode is read from flagSubnetNotary.
|
|
||||||
func initSubnetClientCheckNotary(c *morphsubnet.Client, key *keys.PrivateKey, checkNotary bool) error {
|
|
||||||
// read endpoint
|
|
||||||
endpoint := viper.GetString(flagSubnetEndpoint)
|
|
||||||
if endpoint == "" {
|
|
||||||
return errors.New("missing endpoint")
|
|
||||||
}
|
|
||||||
|
|
||||||
// create base morph client
|
|
||||||
cMorph, err := client.New(key, endpoint)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error means group was not set in NNS, continue with Global scope in this case.
|
|
||||||
_ = cMorph.SetGroupSignerScope()
|
|
||||||
|
|
||||||
// read contract address
|
|
||||||
contractAddr, err := cMorph.NNSContractAddress(client.NNSSubnetworkContractName)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("read subnet contract address in NNS: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// calc client mode
|
|
||||||
cMode := morphsubnet.NonNotary
|
|
||||||
|
|
||||||
if checkNotary && viper.GetBool(flagSubnetNotary) {
|
|
||||||
err = cMorph.EnableNotarySupport()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("enable notary support: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cMode = morphsubnet.NotaryNonAlphabet
|
|
||||||
}
|
|
||||||
|
|
||||||
// init subnet morph client
|
|
||||||
var prmInit morphsubnet.InitPrm
|
|
||||||
|
|
||||||
prmInit.SetBaseClient(cMorph)
|
|
||||||
prmInit.SetContractAddress(contractAddr)
|
|
||||||
prmInit.SetMode(cMode)
|
|
||||||
|
|
||||||
if err := c.Init(prmInit); err != nil {
|
|
||||||
return fmt.Errorf("init call: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// create subnet command.
|
// create subnet command.
|
||||||
var cmdSubnetCreate = &cobra.Command{
|
var cmdSubnetCreate = &cobra.Command{
|
||||||
Use: "create",
|
Use: "create",
|
||||||
|
@ -231,24 +173,9 @@ var cmdSubnetCreate = &cobra.Command{
|
||||||
return fmt.Errorf("marshal subnet info: %w", err)
|
return fmt.Errorf("marshal subnet info: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize morph subnet client
|
err = invokeMethod(key, true, "put", binID, key.PublicKey().Bytes(), binInfo)
|
||||||
var cSubnet morphsubnet.Client
|
|
||||||
|
|
||||||
err = initSubnetClientCheckNotary(&cSubnet, &key, true)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("init subnet client: %w", err)
|
return fmt.Errorf("morph invocation: %w", err)
|
||||||
}
|
|
||||||
|
|
||||||
// prepare call parameters and create subnet
|
|
||||||
var prm morphsubnet.PutPrm
|
|
||||||
|
|
||||||
prm.SetID(binID)
|
|
||||||
prm.SetOwner(key.PublicKey().Bytes())
|
|
||||||
prm.SetInfo(binInfo)
|
|
||||||
|
|
||||||
_, err = cSubnet.Put(prm)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("morph call: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Printf("Create subnet request sent successfully. ID: %s.\n", &id)
|
cmd.Printf("Create subnet request sent successfully. ID: %s.\n", &id)
|
||||||
|
@ -303,22 +230,9 @@ var cmdSubnetRemove = &cobra.Command{
|
||||||
return fmt.Errorf("marshal subnet ID: %w", err)
|
return fmt.Errorf("marshal subnet ID: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize morph subnet client
|
err = invokeMethod(key, false, "delete", binID)
|
||||||
var cSubnet morphsubnet.Client
|
|
||||||
|
|
||||||
err = initSubnetClient(&cSubnet, &key)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("init subnet client: %w", err)
|
return fmt.Errorf("morph invocation: %w", err)
|
||||||
}
|
|
||||||
|
|
||||||
// prepare call parameters and remove subnet
|
|
||||||
var prm morphsubnet.DeletePrm
|
|
||||||
|
|
||||||
prm.SetID(binID)
|
|
||||||
|
|
||||||
_, err = cSubnet.Delete(prm)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("morph call: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Println("Remove subnet request sent successfully.")
|
cmd.Println("Remove subnet request sent successfully.")
|
||||||
|
@ -360,9 +274,6 @@ var cmdSubnetGet = &cobra.Command{
|
||||||
return fmt.Errorf("marshal subnet ID: %w", err)
|
return fmt.Errorf("marshal subnet ID: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize morph subnet client
|
|
||||||
var cSubnet morphsubnet.Client
|
|
||||||
|
|
||||||
// use random key to fetch the data
|
// use random key to fetch the data
|
||||||
// we could use raw neo-go client to perform testInvoke
|
// we could use raw neo-go client to perform testInvoke
|
||||||
// without keys, as it is done in other commands
|
// without keys, as it is done in other commands
|
||||||
|
@ -371,25 +282,23 @@ var cmdSubnetGet = &cobra.Command{
|
||||||
return fmt.Errorf("init subnet client: %w", err)
|
return fmt.Errorf("init subnet client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = initSubnetClient(&cSubnet, key)
|
res, err := testInvokeMethod(*key, "get", binID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("init subnet client: %w", err)
|
return fmt.Errorf("morph invocation: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare call parameters and read subnet
|
if len(res) == 0 {
|
||||||
var prm morphsubnet.GetPrm
|
return errors.New("subnet does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
prm.SetID(binID)
|
data, err := client.BytesFromStackItem(res[0])
|
||||||
|
|
||||||
res, err := cSubnet.Get(prm)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("morph call: %w", err)
|
return fmt.Errorf("decoding contract response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode info
|
// decode info
|
||||||
var info subnet.Info
|
var info subnet.Info
|
||||||
|
if err = info.Unmarshal(data); err != nil {
|
||||||
if err = info.Unmarshal(res.Info()); err != nil {
|
|
||||||
return fmt.Errorf("decode subnet info: %w", err)
|
return fmt.Errorf("decode subnet info: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -473,7 +382,10 @@ func manageSubnetAdmins(cmd *cobra.Command, rm bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare call parameters
|
// prepare call parameters
|
||||||
var prm morphsubnet.ManageAdminsPrm
|
prm := make([]interface{}, 0, 3)
|
||||||
|
prm = append(prm, binID)
|
||||||
|
|
||||||
|
var method string
|
||||||
|
|
||||||
if viper.GetBool(flagSubnetAdminClient) {
|
if viper.GetBool(flagSubnetAdminClient) {
|
||||||
// read group ID and encode it
|
// read group ID and encode it
|
||||||
|
@ -489,28 +401,26 @@ func manageSubnetAdmins(cmd *cobra.Command, rm bool) error {
|
||||||
return fmt.Errorf("marshal group ID: %w", err)
|
return fmt.Errorf("marshal group ID: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
prm.SetClient()
|
if rm {
|
||||||
prm.SetGroup(binGroupID)
|
method = "removeClientAdmin"
|
||||||
|
} else {
|
||||||
|
method = "addClientAdmin"
|
||||||
|
}
|
||||||
|
|
||||||
|
prm = append(prm, binGroupID)
|
||||||
|
} else {
|
||||||
|
if rm {
|
||||||
|
method = "removeNodeAdmin"
|
||||||
|
} else {
|
||||||
|
method = "addNodeAdmin"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
prm.SetSubnet(binID)
|
prm = append(prm, binAdminKey)
|
||||||
prm.SetAdmin(binAdminKey)
|
|
||||||
|
|
||||||
if rm {
|
err = invokeMethod(key, false, method, prm...)
|
||||||
prm.SetRemove()
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize morph subnet client
|
|
||||||
var cSubnet morphsubnet.Client
|
|
||||||
|
|
||||||
err = initSubnetClient(&cSubnet, &key)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("init subnet client: %w", err)
|
return fmt.Errorf("morph invocation: %w", err)
|
||||||
}
|
|
||||||
|
|
||||||
_, err = cSubnet.ManageAdmins(prm)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("morph call: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var op string
|
var op string
|
||||||
|
@ -628,27 +538,16 @@ func manageSubnetClients(cmd *cobra.Command, rm bool) error {
|
||||||
return fmt.Errorf("marshal group ID: %w", err)
|
return fmt.Errorf("marshal group ID: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var prm morphsubnet.ManageClientsPrm
|
var method string
|
||||||
|
|
||||||
prm.SetGroup(binGroupID)
|
|
||||||
prm.SetSubnet(binID)
|
|
||||||
prm.SetClient(clientID.WalletBytes())
|
|
||||||
|
|
||||||
if rm {
|
if rm {
|
||||||
prm.SetRemove()
|
method = "removeUser"
|
||||||
|
} else {
|
||||||
|
method = "addUser"
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize morph subnet client
|
err = invokeMethod(key, false, method, binID, binGroupID, clientID.WalletBytes())
|
||||||
var cSubnet morphsubnet.Client
|
|
||||||
|
|
||||||
err = initSubnetClient(&cSubnet, &key)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("init subnet client: %w", err)
|
return fmt.Errorf("morph invocation: %w", err)
|
||||||
}
|
|
||||||
|
|
||||||
_, err = cSubnet.ManageClients(prm)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("morph call: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var op string
|
var op string
|
||||||
|
@ -728,26 +627,16 @@ func manageSubnetNodes(cmd *cobra.Command, rm bool) error {
|
||||||
return fmt.Errorf("node ID format: %w", err)
|
return fmt.Errorf("node ID format: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var prm morphsubnet.ManageNodesPrm
|
var method string
|
||||||
|
|
||||||
prm.SetSubnet(binID)
|
|
||||||
prm.SetNode(binNodeID)
|
|
||||||
|
|
||||||
if rm {
|
if rm {
|
||||||
prm.SetRemove()
|
method = "removeNode"
|
||||||
|
} else {
|
||||||
|
method = "addNode"
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize morph subnet client
|
err = invokeMethod(key, false, method, binID, binNodeID)
|
||||||
var cSubnet morphsubnet.Client
|
|
||||||
|
|
||||||
err = initSubnetClient(&cSubnet, &key)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("init subnet client: %w", err)
|
return fmt.Errorf("morph invocation: %w", err)
|
||||||
}
|
|
||||||
|
|
||||||
_, err = cSubnet.ManageNodes(prm)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("morph call: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var op string
|
var op string
|
||||||
|
@ -897,8 +786,8 @@ func init() {
|
||||||
|
|
||||||
// subnet global flags
|
// subnet global flags
|
||||||
cmdSubnetFlags := cmdSubnet.PersistentFlags()
|
cmdSubnetFlags := cmdSubnet.PersistentFlags()
|
||||||
cmdSubnetFlags.StringP(flagSubnetEndpoint, "r", "", "N3 RPC node endpoint")
|
cmdSubnetFlags.StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
|
||||||
_ = cmdSubnet.MarkFlagRequired(flagSubnetEndpoint)
|
_ = cmdSubnet.MarkFlagRequired(endpointFlag)
|
||||||
|
|
||||||
// add all subnet commands to corresponding command section
|
// add all subnet commands to corresponding command section
|
||||||
addCommandInheritPreRun(cmdSubnet,
|
addCommandInheritPreRun(cmdSubnet,
|
||||||
|
@ -910,3 +799,318 @@ func init() {
|
||||||
cmdSubnetNode,
|
cmdSubnetNode,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testInvokeMethod(key keys.PrivateKey, method string, args ...interface{}) ([]stackitem.Item, error) {
|
||||||
|
c, err := getN3Client(viper.GetViper())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("morph client creation: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nnsCs, err := c.GetContractStateByID(1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("NNS contract resolving: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
subnetHash, err := nnsResolveHash(c, nnsCs.Hash, subnetContract+".neofs")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("subnet hash resolving: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cosigner := []transaction.Signer{
|
||||||
|
{
|
||||||
|
Account: key.PublicKey().GetScriptHash(),
|
||||||
|
Scopes: transaction.Global,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := invokeFunction(c, subnetHash, method, args, cosigner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invocation parameters prepararion: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = checkInvocationResults(res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.Stack, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokeMethod(key keys.PrivateKey, tryNotary bool, method string, args ...interface{}) error {
|
||||||
|
c, err := getN3Client(viper.GetViper())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("morph client creation: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tryNotary {
|
||||||
|
cc, err := c.GetNativeContracts()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("native hashes: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var notary bool
|
||||||
|
var notaryHash util.Uint160
|
||||||
|
for _, c := range cc {
|
||||||
|
if c.Manifest.Name == nativenames.Notary {
|
||||||
|
notary = len(c.UpdateHistory) > 0
|
||||||
|
notaryHash = c.Hash
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if notary {
|
||||||
|
err = invokeNotary(c, key, method, notaryHash, args...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("notary invocation: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = invokeNonNotary(c, key, method, args...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("non-notary invocation: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokeNonNotary(c Client, key keys.PrivateKey, method string, args ...interface{}) error {
|
||||||
|
nnsCs, err := c.GetContractStateByID(1)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("NNS contract resolving: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
subnetHash, err := nnsResolveHash(c, nnsCs.Hash, subnetContract+".neofs")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("subnet hash resolving: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
acc := wallet.NewAccountFromPrivateKey(&key)
|
||||||
|
|
||||||
|
cosigner := []transaction.Signer{
|
||||||
|
{
|
||||||
|
Account: key.PublicKey().GetScriptHash(),
|
||||||
|
Scopes: transaction.Global,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cosignerAcc := []neogocli.SignerAccount{
|
||||||
|
{
|
||||||
|
Signer: cosigner[0],
|
||||||
|
Account: acc,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
test, err := invokeFunction(c, subnetHash, method, args, cosigner)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("test invocation: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = checkInvocationResults(test)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = c.SignAndPushInvocationTx(test.Script, acc, test.GasConsumed, 0, cosignerAcc)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("sending transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokeNotary(c Client, key keys.PrivateKey, method string, notaryHash util.Uint160, args ...interface{}) error {
|
||||||
|
nnsCs, err := c.GetContractStateByID(1)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("NNS contract resolving: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
subnetHash, err := nnsResolveHash(c, nnsCs.Hash, subnetContract+".neofs")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("subnet hash resolving: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
alphabet, err := c.GetCommittee()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("alphabet list: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
multisigScript, err := smartcontract.CreateDefaultMultiSigRedeemScript(alphabet)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("alphabet multi-signature script: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cosigners, err := notaryCosigners(c, notaryHash, nnsCs, key, hash.Hash160(multisigScript))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cosigners collecting: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make test invocation of the method
|
||||||
|
test, err := invokeFunction(c, subnetHash, method, args, cosigners)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("test invocation: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = checkInvocationResults(test)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
multisigAccount := &wallet.Account{
|
||||||
|
Contract: &wallet.Contract{
|
||||||
|
Script: multisigScript,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
bc, err := c.GetBlockCount()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("blockchain height: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signersNumber := uint8(smartcontract.GetDefaultHonestNodeCount(len(alphabet)) + 1) // alphabet multisig + key signature
|
||||||
|
|
||||||
|
// notaryRequestValidity is number of blocks during
|
||||||
|
// witch notary request is considered valid
|
||||||
|
const notaryRequestValidity = 100
|
||||||
|
|
||||||
|
mainTx := &transaction.Transaction{
|
||||||
|
Nonce: rand.Uint32(),
|
||||||
|
SystemFee: test.GasConsumed,
|
||||||
|
ValidUntilBlock: bc + notaryRequestValidity,
|
||||||
|
Script: test.Script,
|
||||||
|
Attributes: []transaction.Attribute{
|
||||||
|
{
|
||||||
|
Type: transaction.NotaryAssistedT,
|
||||||
|
Value: &transaction.NotaryAssisted{NKeys: signersNumber},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Signers: cosigners,
|
||||||
|
}
|
||||||
|
|
||||||
|
notaryFee, err := c.CalculateNotaryFee(signersNumber)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
acc := wallet.NewAccountFromPrivateKey(&key)
|
||||||
|
aa := notaryAccounts(multisigAccount, acc)
|
||||||
|
|
||||||
|
err = c.AddNetworkFee(mainTx, notaryFee, aa...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("notary network fee adding: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mainTx.Scripts = notaryWitnesses(c, multisigAccount, acc, mainTx)
|
||||||
|
|
||||||
|
_, err = c.SignAndPushP2PNotaryRequest(mainTx,
|
||||||
|
[]byte{byte(opcode.RET)},
|
||||||
|
-1,
|
||||||
|
0,
|
||||||
|
40,
|
||||||
|
acc)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("sending notary request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func notaryCosigners(c Client, notaryHash util.Uint160, nnsCs *state.Contract,
|
||||||
|
key keys.PrivateKey, alphabetAccount util.Uint160) ([]transaction.Signer, error) {
|
||||||
|
proxyHash, err := nnsResolveHash(c, nnsCs.Hash, proxyContract+".neofs")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("proxy hash resolving: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []transaction.Signer{
|
||||||
|
{
|
||||||
|
Account: proxyHash,
|
||||||
|
Scopes: transaction.None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Account: alphabetAccount,
|
||||||
|
Scopes: transaction.Global,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Account: hash.Hash160(key.PublicKey().GetVerificationScript()),
|
||||||
|
Scopes: transaction.Global,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Account: notaryHash,
|
||||||
|
Scopes: transaction.None,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func notaryAccounts(alphabet, acc *wallet.Account) []*wallet.Account {
|
||||||
|
return []*wallet.Account{
|
||||||
|
// proxy
|
||||||
|
{
|
||||||
|
Contract: &wallet.Contract{
|
||||||
|
Deployed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
alphabet,
|
||||||
|
// caller's account
|
||||||
|
acc,
|
||||||
|
// last one is a placeholder for notary contract account
|
||||||
|
{
|
||||||
|
Contract: &wallet.Contract{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func notaryWitnesses(c Client, alphabet, acc *wallet.Account, tx *transaction.Transaction) []transaction.Witness {
|
||||||
|
ww := make([]transaction.Witness, 0, 4)
|
||||||
|
|
||||||
|
// empty proxy contract witness
|
||||||
|
ww = append(ww, transaction.Witness{
|
||||||
|
InvocationScript: []byte{},
|
||||||
|
VerificationScript: []byte{},
|
||||||
|
})
|
||||||
|
|
||||||
|
// alphabet multi-address witness
|
||||||
|
ww = append(ww, transaction.Witness{
|
||||||
|
InvocationScript: append(
|
||||||
|
[]byte{byte(opcode.PUSHDATA1), 64},
|
||||||
|
make([]byte, 64)...,
|
||||||
|
),
|
||||||
|
VerificationScript: alphabet.GetVerificationScript(),
|
||||||
|
})
|
||||||
|
|
||||||
|
magicNumber, _ := c.GetNetwork()
|
||||||
|
|
||||||
|
// caller's witness
|
||||||
|
ww = append(ww, transaction.Witness{
|
||||||
|
InvocationScript: append(
|
||||||
|
[]byte{byte(opcode.PUSHDATA1), 64},
|
||||||
|
acc.PrivateKey().SignHashable(uint32(magicNumber), tx)...),
|
||||||
|
VerificationScript: acc.GetVerificationScript(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// notary contract witness
|
||||||
|
ww = append(ww, transaction.Witness{
|
||||||
|
InvocationScript: append(
|
||||||
|
[]byte{byte(opcode.PUSHDATA1), 64},
|
||||||
|
make([]byte, 64)...,
|
||||||
|
),
|
||||||
|
VerificationScript: []byte{},
|
||||||
|
})
|
||||||
|
|
||||||
|
return ww
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkInvocationResults(res *result.Invoke) error {
|
||||||
|
if res.State != "HALT" {
|
||||||
|
return fmt.Errorf("test invocation state: %s, exception %s: ", res.State, res.FaultException)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res.Script) == 0 {
|
||||||
|
return errors.New("empty invocation script")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue