diff --git a/cmd/neofs-adm/internal/modules/morph/balance.go b/cmd/neofs-adm/internal/modules/morph/balance.go index dcb06c94..a113758b 100644 --- a/cmd/neofs-adm/internal/modules/morph/balance.go +++ b/cmd/neofs-adm/internal/modules/morph/balance.go @@ -13,7 +13,6 @@ import ( "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/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" @@ -57,11 +56,19 @@ func dumpBalances(cmd *cobra.Command, _ []string) error { } ns, err := getNativeHashes(c) - if err != nil || ns[nativenames.Gas].Equals(util.Uint160{}) { - return errors.New("can't fetch hash of the GAS contract") + if err != nil { + return fmt.Errorf("can't fetch the list of native contracts: %w", err) } - gasHash := ns[nativenames.Gas] + gasHash, ok := ns[nativenames.Gas] + if !ok { + return fmt.Errorf("can't find the %s native contract hash", nativenames.Gas) + } + + desigHash, ok := ns[nativenames.Designation] + if !ok { + return fmt.Errorf("can't find the %s native contract hash", nativenames.Designation) + } if !notaryEnabled || dumpStorage || dumpAlphabet || dumpProxy { nnsCs, err = c.GetContractStateByID(1) @@ -75,7 +82,7 @@ func dumpBalances(cmd *cobra.Command, _ []string) error { } } - irList, err := fetchIRNodes(c, nmHash) + irList, err := fetchIRNodes(c, nmHash, desigHash) if err != nil { return err } @@ -86,7 +93,7 @@ func dumpBalances(cmd *cobra.Command, _ []string) error { printBalances(cmd, "Inner ring nodes balances:", irList) if dumpStorage { - res, err := c.InvokeFunction(nmHash, "netmap", []smartcontract.Parameter{}, nil) + res, err := invokeFunction(c, nmHash, "netmap", []interface{}{}, nil) if err != nil || res.State != vm.HaltState.String() || len(res.Stack) == 0 { return errors.New("can't fetch the list of storage nodes") } @@ -170,7 +177,7 @@ func dumpBalances(cmd *cobra.Command, _ []string) error { return nil } -func fetchIRNodes(c Client, nmHash util.Uint160) ([]accBalancePair, error) { +func fetchIRNodes(c Client, nmHash, desigHash util.Uint160) ([]accBalancePair, error) { var irList []accBalancePair if notaryEnabled { @@ -179,7 +186,7 @@ func fetchIRNodes(c Client, nmHash util.Uint160) ([]accBalancePair, error) { return nil, fmt.Errorf("can't get block height: %w", err) } - arr, err := c.GetDesignatedByRole(noderoles.NeoFSAlphabet, height) + arr, err := getDesignatedByRole(c, desigHash, noderoles.NeoFSAlphabet, height) if err != nil { return nil, errors.New("can't fetch list of IR nodes from the netmap contract") } @@ -189,7 +196,7 @@ func fetchIRNodes(c Client, nmHash util.Uint160) ([]accBalancePair, error) { irList[i].scriptHash = arr[i].GetScriptHash() } } else { - res, err := c.InvokeFunction(nmHash, "innerRingList", []smartcontract.Parameter{}, nil) + res, err := invokeFunction(c, nmHash, "innerRingList", []interface{}{}, 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") } diff --git a/cmd/neofs-adm/internal/modules/morph/container.go b/cmd/neofs-adm/internal/modules/morph/container.go index 04581d6d..33673673 100644 --- a/cmd/neofs-adm/internal/modules/morph/container.go +++ b/cmd/neofs-adm/internal/modules/morph/container.go @@ -9,7 +9,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/io" - "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/emit" @@ -49,8 +48,7 @@ func dumpContainers(cmd *cobra.Command, _ []string) error { } } - res, err := c.InvokeFunction(ch, "list", - []smartcontract.Parameter{{Type: smartcontract.StringType, Value: ""}}, nil) + res, err := invokeFunction(c, ch, "list", []interface{}{""}, nil) if err != nil { return fmt.Errorf("%w: %v", errInvalidContainerResponse, err) } diff --git a/cmd/neofs-adm/internal/modules/morph/dump.go b/cmd/neofs-adm/internal/modules/morph/dump.go index 92837635..2f8a4403 100644 --- a/cmd/neofs-adm/internal/modules/morph/dump.go +++ b/cmd/neofs-adm/internal/modules/morph/dump.go @@ -8,9 +8,7 @@ import ( "fmt" "text/tabwriter" - "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/io" - "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" @@ -160,8 +158,7 @@ func dumpNetworkConfig(cmd *cobra.Command, _ []string) error { return fmt.Errorf("can't get netmap contract hash: %w", err) } - res, err := c.InvokeFunction(nmHash, "listConfig", - []smartcontract.Parameter{}, []transaction.Signer{{}}) + res, err := invokeFunction(c, nmHash, "listConfig", nil, nil) if err != nil || res.State != vm.HaltState.String() || len(res.Stack) == 0 { return errors.New("can't fetch list of network config keys from the netmap contract") } diff --git a/cmd/neofs-adm/internal/modules/morph/epoch.go b/cmd/neofs-adm/internal/modules/morph/epoch.go index f8f3d194..f29cd4ea 100644 --- a/cmd/neofs-adm/internal/modules/morph/epoch.go +++ b/cmd/neofs-adm/internal/modules/morph/epoch.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/nspcc-dev/neo-go/pkg/io" - "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/emit" @@ -29,7 +28,7 @@ func forceNewEpochCmd(cmd *cobra.Command, args []string) error { return fmt.Errorf("can't get netmap contract hash: %w", err) } - res, err := wCtx.Client.InvokeFunction(nmHash, "epoch", []smartcontract.Parameter{}, nil) + res, err := invokeFunction(wCtx.Client, nmHash, "epoch", nil, nil) if err != nil || res.State != vm.HaltState.String() || len(res.Stack) == 0 { return errors.New("can't fetch current epoch from the netmap contract") } diff --git a/cmd/neofs-adm/internal/modules/morph/initialize.go b/cmd/neofs-adm/internal/modules/morph/initialize.go index 344fe514..6fed2df9 100644 --- a/cmd/neofs-adm/internal/modules/morph/initialize.go +++ b/cmd/neofs-adm/internal/modules/morph/initialize.go @@ -51,6 +51,7 @@ func initializeSideChainCmd(cmd *cobra.Command, args []string) error { if err != nil { return fmt.Errorf("initialization error: %w", err) } + defer initCtx.close() // 1. Transfer funds to committee accounts. cmd.Println("Stage 1: transfer GAS to alphabet nodes.") @@ -98,6 +99,16 @@ func initializeSideChainCmd(cmd *cobra.Command, args []string) error { return nil } +func (c *initializeContext) close() { + if local, ok := c.Client.(*localClient); ok { + err := local.dump() + if err != nil { + c.Command.PrintErrf("Can't write dump: %v\n", err) + os.Exit(1) + } + } +} + func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContext, error) { walletDir := config.ResolveHomePath(viper.GetString(alphabetWalletsFlag)) wallets, err := openAlphabetWallets(walletDir) @@ -110,7 +121,18 @@ func newInitializeContext(cmd *cobra.Command, v *viper.Viper) (*initializeContex return nil, err } - c, err := getN3Client(v) + var c Client + if v.GetString(localDumpFlag) != "" { + if cmd.Name() != "init" { + return nil, errors.New("dump creation is only supported for `init` command") + } + if v.GetString(endpointFlag) != "" { + return nil, fmt.Errorf("`%s` and `%s` flags are mutually exclusive", endpointFlag, localDumpFlag) + } + c, err = newLocalClient(v, wallets) + } else { + c, err = getN3Client(v) + } if err != nil { return nil, fmt.Errorf("can't create N3 client: %w", err) } @@ -287,6 +309,12 @@ func (c *clientContext) awaitTx(cmd *cobra.Command) error { return nil } + if local, ok := c.Client.(*localClient); ok { + if err := local.putTransactions(); err != nil { + return fmt.Errorf("can't persist transactions: %w", err) + } + } + cmd.Println("Waiting for transactions to persist...") tick := time.NewTicker(c.PollInterval) @@ -304,8 +332,8 @@ loop: res, err := c.Client.GetApplicationLog(c.Hashes[i], &at) if err == nil { if retErr == nil && len(res.Executions) > 0 && res.Executions[0].VMState != vm.HaltState { - retErr = fmt.Errorf("tx persisted in %s state: %s", - res.Executions[0].VMState, res.Executions[0].FaultException) + retErr = fmt.Errorf("tx %d persisted in %s state: %s", + i, res.Executions[0].VMState, res.Executions[0].FaultException) } continue loop } @@ -315,8 +343,8 @@ loop: res, err := c.Client.GetApplicationLog(c.Hashes[i], &at) if err == nil { if retErr == nil && len(res.Executions) > 0 && res.Executions[0].VMState != vm.HaltState { - retErr = fmt.Errorf("tx persisted in %s state: %s", - res.Executions[0].VMState, res.Executions[0].FaultException) + retErr = fmt.Errorf("tx %d persisted in %s state: %s", + i, res.Executions[0].VMState, res.Executions[0].FaultException) } continue loop } diff --git a/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go b/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go index a41029b6..78e5d8a7 100644 --- a/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go +++ b/cmd/neofs-adm/internal/modules/morph/initialize_deploy.go @@ -125,7 +125,7 @@ func (c *initializeContext) deployNNS(method string) error { mgmtHash = nnsCs.Hash } - res, err := c.Client.InvokeFunction(mgmtHash, method, params, []transaction.Signer{signer}) + res, err := invokeFunction(c.Client, mgmtHash, method, params, []transaction.Signer{signer}) if err != nil { return fmt.Errorf("can't deploy NNS contract: %w", err) } @@ -161,7 +161,7 @@ func (c *initializeContext) updateContracts() error { totalGasCost := int64(0) w := io2.NewBufBinWriter() - var keysParam []smartcontract.Parameter + var keysParam []interface{} // Update script size for a single-node committee is close to the maximum allowed size of 65535. // Because of this we want to reuse alphabet contract NEF and manifest for different updates. @@ -184,10 +184,7 @@ func (c *initializeContext) updateContracts() error { return fmt.Errorf("can't resolve hash for contract update: %w", err) } - keysParam = append(keysParam, smartcontract.Parameter{ - Type: smartcontract.PublicKeyType, - Value: acc.PrivateKey().PublicKey().Bytes(), - }) + keysParam = append(keysParam, acc.PrivateKey().PublicKey().Bytes()) params := c.getAlphabetDeployItems(i, len(c.Wallets)) emit.Array(w.BinWriter, params...) @@ -243,7 +240,7 @@ func (c *initializeContext) updateContracts() error { Scopes: transaction.Global, } - res, err := c.Client.InvokeFunction(invokeHash, method, params, []transaction.Signer{signer}) + res, err := invokeFunction(c.Client, invokeHash, method, params, []transaction.Signer{signer}) if err != nil { return fmt.Errorf("can't deploy %s contract: %w", ctrName, err) } @@ -287,7 +284,7 @@ func (c *initializeContext) deployContracts() error { mgmtHash := c.nativeHash(nativenames.Management) alphaCs := c.getContract(alphabetContract) - var keysParam []smartcontract.Parameter + var keysParam []interface{} // alphabet contracts should be deployed by individual nodes to get different hashes. for i, acc := range c.Accounts { @@ -298,15 +295,12 @@ func (c *initializeContext) deployContracts() error { } invokeHash := mgmtHash - keysParam = append(keysParam, smartcontract.Parameter{ - Type: smartcontract.PublicKeyType, - Value: acc.PrivateKey().PublicKey().Bytes(), - }) + keysParam = append(keysParam, acc.PrivateKey().PublicKey().Bytes()) params := getContractDeployParameters(alphaCs.RawNEF, alphaCs.RawManifest, - c.getAlphabetDeployParameters(i, len(c.Wallets))) + c.getAlphabetDeployItems(i, len(c.Wallets))) - res, err := c.Client.InvokeFunction(invokeHash, deployMethodName, params, []transaction.Signer{{ + res, err := invokeFunction(c.Client, invokeHash, deployMethodName, params, []transaction.Signer{{ Account: acc.Contract.ScriptHash(), Scopes: transaction.CalledByEntry, }}) @@ -344,7 +338,7 @@ func (c *initializeContext) deployContracts() error { Scopes: transaction.Global, } - res, err := c.Client.InvokeFunction(invokeHash, deployMethodName, params, []transaction.Signer{signer}) + res, err := invokeFunction(c.Client, invokeHash, deployMethodName, params, []transaction.Signer{signer}) if err != nil { return fmt.Errorf("can't deploy %s contract: %w", ctrName, err) } @@ -497,43 +491,29 @@ func readContractsFromArchive(file io.Reader, names []string) (map[string]*contr return m, nil } -func getContractDeployParameters(rawNef, rawManif []byte, deployData []smartcontract.Parameter) []smartcontract.Parameter { - return []smartcontract.Parameter{ - { - Type: smartcontract.ByteArrayType, - Value: rawNef, - }, - { - Type: smartcontract.ByteArrayType, - Value: rawManif, - }, - { - Type: smartcontract.ArrayType, - Value: deployData, - }, - } +func getContractDeployParameters(rawNef, rawManif []byte, deployData []interface{}) []interface{} { + return []interface{}{rawNef, rawManif, deployData} } -func (c *initializeContext) getContractDeployData(ctrName string, keysParam []smartcontract.Parameter) []smartcontract.Parameter { - items := make([]smartcontract.Parameter, 1, 6) - items[0] = newContractParameter(smartcontract.BoolType, false) // notaryDisabled is false +func (c *initializeContext) getContractDeployData(ctrName string, keysParam []interface{}) []interface{} { + items := make([]interface{}, 1, 6) + items[0] = false // notaryDisabled is false switch ctrName { case neofsContract: items = append(items, - newContractParameter(smartcontract.Hash160Type, c.Contracts[processingContract].Hash), - newContractParameter(smartcontract.ArrayType, keysParam), - newContractParameter(smartcontract.ArrayType, smartcontract.Parameter{})) + c.Contracts[processingContract].Hash, + keysParam, + smartcontract.Parameter{}) case processingContract: - items = append(items, newContractParameter(smartcontract.Hash160Type, c.Contracts[neofsContract].Hash)) + items = append(items, c.Contracts[neofsContract].Hash) return items[1:] // no notary info case auditContract: - items = append(items, - newContractParameter(smartcontract.Hash160Type, c.Contracts[netmapContract].Hash)) + items = append(items, c.Contracts[netmapContract].Hash) case balanceContract: items = append(items, - newContractParameter(smartcontract.Hash160Type, c.Contracts[netmapContract].Hash), - newContractParameter(smartcontract.Hash160Type, c.Contracts[containerContract].Hash)) + c.Contracts[netmapContract].Hash, + c.Contracts[containerContract].Hash) case containerContract: // In case if NNS is updated multiple times, we can't calculate // it's actual hash based on local data, thus query chain. @@ -542,43 +522,33 @@ func (c *initializeContext) getContractDeployData(ctrName string, keysParam []sm panic("NNS is not yet deployed") } items = append(items, - newContractParameter(smartcontract.Hash160Type, c.Contracts[netmapContract].Hash), - newContractParameter(smartcontract.Hash160Type, c.Contracts[balanceContract].Hash), - newContractParameter(smartcontract.Hash160Type, c.Contracts[neofsIDContract].Hash), - newContractParameter(smartcontract.Hash160Type, nnsCs.Hash), - newContractParameter(smartcontract.StringType, "container")) + c.Contracts[netmapContract].Hash, + c.Contracts[balanceContract].Hash, + c.Contracts[neofsIDContract].Hash, + nnsCs.Hash, + "container") case neofsIDContract: items = append(items, - newContractParameter(smartcontract.Hash160Type, c.Contracts[netmapContract].Hash), - newContractParameter(smartcontract.Hash160Type, c.Contracts[containerContract].Hash)) + c.Contracts[netmapContract].Hash, + c.Contracts[containerContract].Hash) case netmapContract: - configParam := []smartcontract.Parameter{ - {Type: smartcontract.StringType, Value: netmapEpochKey}, - {Type: smartcontract.IntegerType, Value: viper.GetInt64(epochDurationInitFlag)}, - {Type: smartcontract.StringType, Value: netmapMaxObjectSizeKey}, - {Type: smartcontract.IntegerType, Value: viper.GetInt64(maxObjectSizeInitFlag)}, - {Type: smartcontract.StringType, Value: netmapAuditFeeKey}, - {Type: smartcontract.IntegerType, Value: viper.GetInt64(auditFeeInitFlag)}, - {Type: smartcontract.StringType, Value: netmapContainerFeeKey}, - {Type: smartcontract.IntegerType, Value: viper.GetInt64(containerFeeInitFlag)}, - {Type: smartcontract.StringType, Value: netmapContainerAliasFeeKey}, - {Type: smartcontract.IntegerType, Value: viper.GetInt64(containerAliasFeeInitFlag)}, - {Type: smartcontract.StringType, Value: netmapEigenTrustIterationsKey}, - {Type: smartcontract.IntegerType, Value: int64(defaultEigenTrustIterations)}, - {Type: smartcontract.StringType, Value: netmapEigenTrustAlphaKey}, - {Type: smartcontract.StringType, Value: defaultEigenTrustAlpha}, - {Type: smartcontract.StringType, Value: netmapBasicIncomeRateKey}, - {Type: smartcontract.IntegerType, Value: viper.GetInt64(incomeRateInitFlag)}, - {Type: smartcontract.StringType, Value: netmapInnerRingCandidateFeeKey}, - {Type: smartcontract.IntegerType, Value: viper.GetInt64(candidateFeeInitFlag)}, - {Type: smartcontract.StringType, Value: netmapWithdrawFeeKey}, - {Type: smartcontract.IntegerType, Value: viper.GetInt64(withdrawFeeInitFlag)}, + configParam := []interface{}{ + netmapEpochKey, viper.GetInt64(epochDurationInitFlag), + netmapMaxObjectSizeKey, viper.GetInt64(maxObjectSizeInitFlag), + netmapAuditFeeKey, viper.GetInt64(auditFeeInitFlag), + netmapContainerFeeKey, viper.GetInt64(containerFeeInitFlag), + netmapContainerAliasFeeKey, viper.GetInt64(containerAliasFeeInitFlag), + netmapEigenTrustIterationsKey, int64(defaultEigenTrustIterations), + netmapEigenTrustAlphaKey, defaultEigenTrustAlpha, + netmapBasicIncomeRateKey, viper.GetInt64(incomeRateInitFlag), + netmapInnerRingCandidateFeeKey, viper.GetInt64(candidateFeeInitFlag), + netmapWithdrawFeeKey, viper.GetInt64(withdrawFeeInitFlag), } items = append(items, - newContractParameter(smartcontract.Hash160Type, c.Contracts[balanceContract].Hash), - newContractParameter(smartcontract.Hash160Type, c.Contracts[containerContract].Hash), - newContractParameter(smartcontract.ArrayType, keysParam), - newContractParameter(smartcontract.ArrayType, configParam)) + c.Contracts[balanceContract].Hash, + c.Contracts[containerContract].Hash, + keysParam, + configParam) case proxyContract: items = nil case reputationContract: @@ -589,18 +559,6 @@ func (c *initializeContext) getContractDeployData(ctrName string, keysParam []sm return items } -func (c *initializeContext) getAlphabetDeployParameters(i, n int) []smartcontract.Parameter { - items := c.getAlphabetDeployItems(i, n) - return []smartcontract.Parameter{ - newContractParameter(smartcontract.BoolType, items[0]), - newContractParameter(smartcontract.Hash160Type, items[1]), - newContractParameter(smartcontract.Hash160Type, items[2]), - newContractParameter(smartcontract.StringType, items[3]), - newContractParameter(smartcontract.IntegerType, items[4]), - newContractParameter(smartcontract.IntegerType, items[5]), - } -} - func (c *initializeContext) getAlphabetDeployItems(i, n int) []interface{} { items := make([]interface{}, 6) items[0] = false diff --git a/cmd/neofs-adm/internal/modules/morph/initialize_nns.go b/cmd/neofs-adm/internal/modules/morph/initialize_nns.go index 39ffb4cc..d59a0307 100644 --- a/cmd/neofs-adm/internal/modules/morph/initialize_nns.go +++ b/cmd/neofs-adm/internal/modules/morph/initialize_nns.go @@ -12,7 +12,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "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" @@ -165,8 +164,8 @@ func (c *initializeContext) nnsRegisterDomain(nnsHash, expectedHash util.Uint160 } func (c *initializeContext) nnsRootRegistered(nnsHash util.Uint160) (bool, error) { - params := []smartcontract.Parameter{{Type: smartcontract.StringType, Value: "name.neofs"}} - res, err := c.Client.InvokeFunction(nnsHash, "isAvailable", params, nil) + params := []interface{}{"name.neofs"} + res, err := invokeFunction(c.Client, nnsHash, "isAvailable", params, nil) if err != nil { return false, err } @@ -185,16 +184,7 @@ func nnsResolveHash(c Client, nnsHash util.Uint160, domain string) (util.Uint160 } func nnsResolve(c Client, nnsHash util.Uint160, domain string) (stackitem.Item, error) { - result, err := c.InvokeFunction(nnsHash, "resolve", []smartcontract.Parameter{ - { - Type: smartcontract.StringType, - Value: domain, - }, - { - Type: smartcontract.IntegerType, - Value: int64(nns.TXT), - }, - }, nil) + result, err := invokeFunction(c, nnsHash, "resolve", []interface{}{domain, int64(nns.TXT)}, nil) if err != nil { return nil, fmt.Errorf("`resolve`: %w", err) } @@ -244,11 +234,24 @@ func parseNNSResolveResult(res stackitem.Item) (util.Uint160, error) { return util.Uint160DecodeStringLE(string(bs)) } +var errNNSIsAvailableInvalid = errors.New("`isAvailable`: invalid response") + func nnsIsAvailable(c Client, nnsHash util.Uint160, name string) (bool, error) { switch ct := c.(type) { case *client.Client: return ct.NNSIsAvailable(nnsHash, name) default: - panic("unimplemented") + res, err := invokeFunction(c, nnsHash, "isAvailable", []interface{}{name}, nil) + if err != nil { + return false, err + } + if len(res.Stack) == 0 { + return false, errNNSIsAvailableInvalid + } + b, err := res.Stack[0].TryBool() + if err != nil { + return b, errNNSIsAvailableInvalid + } + return b, nil } } diff --git a/cmd/neofs-adm/internal/modules/morph/initialize_register.go b/cmd/neofs-adm/internal/modules/morph/initialize_register.go index 29d93756..2480978f 100644 --- a/cmd/neofs-adm/internal/modules/morph/initialize_register.go +++ b/cmd/neofs-adm/internal/modules/morph/initialize_register.go @@ -1,6 +1,7 @@ package morph import ( + "errors" "fmt" "github.com/nspcc-dev/neo-go/pkg/core/native" @@ -8,7 +9,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/state" "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" @@ -23,7 +23,7 @@ const initialAlphabetNEOAmount = native.NEOTotalSupply func (c *initializeContext) registerCandidates() error { neoHash := c.nativeHash(nativenames.Neo) - res, err := c.Client.InvokeFunction(neoHash, "getCandidates", []smartcontract.Parameter{}, nil) + res, err := invokeFunction(c.Client, neoHash, "getCandidates", nil, nil) if err != nil { return err } @@ -34,7 +34,7 @@ func (c *initializeContext) registerCandidates() error { } } - regPrice, err := getCandidateRegisterPrice(c.Client) + regPrice, err := c.getCandidateRegisterPrice() if err != nil { return fmt.Errorf("can't fetch registration price: %w", err) } @@ -84,11 +84,25 @@ func (c *initializeContext) transferNEOFinished(neoHash util.Uint160) (bool, err return bal < native.NEOTotalSupply, err } -func getCandidateRegisterPrice(c Client) (int64, error) { - switch ct := c.(type) { +var errGetPriceInvalid = errors.New("`getRegisterPrice`: invalid response") + +func (c *initializeContext) getCandidateRegisterPrice() (int64, error) { + switch ct := c.Client.(type) { case *client.Client: return ct.GetCandidateRegisterPrice() default: - panic("unimplemented") + neoHash := c.nativeHash(nativenames.Neo) + res, err := invokeFunction(c.Client, neoHash, "getRegisterPrice", nil, nil) + if err != nil { + return 0, err + } + if len(res.Stack) == 0 { + return 0, errGetPriceInvalid + } + bi, err := res.Stack[0].TryInteger() + if err != nil || !bi.IsInt64() { + return 0, errGetPriceInvalid + } + return bi.Int64(), nil } } diff --git a/cmd/neofs-adm/internal/modules/morph/initialize_roles.go b/cmd/neofs-adm/internal/modules/morph/initialize_roles.go index 080bb173..2103b9f8 100644 --- a/cmd/neofs-adm/internal/modules/morph/initialize_roles.go +++ b/cmd/neofs-adm/internal/modules/morph/initialize_roles.go @@ -42,6 +42,7 @@ func (c *initializeContext) setRolesFinished() (bool, error) { return false, err } - pubs, err := c.Client.GetDesignatedByRole(noderoles.NeoFSAlphabet, height) + h := c.nativeHash(nativenames.Designation) + pubs, err := getDesignatedByRole(c.Client, h, noderoles.NeoFSAlphabet, height) return len(pubs) == len(c.Wallets), err } diff --git a/cmd/neofs-adm/internal/modules/morph/local_client.go b/cmd/neofs-adm/internal/modules/morph/local_client.go new file mode 100644 index 00000000..7b862d83 --- /dev/null +++ b/cmd/neofs-adm/internal/modules/morph/local_client.go @@ -0,0 +1,357 @@ +package morph + +import ( + "bytes" + "crypto/elliptic" + "errors" + "fmt" + "os" + "sort" + + "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/config/netmode" + "github.com/nspcc-dev/neo-go/pkg/core" + "github.com/nspcc-dev/neo-go/pkg/core/block" + "github.com/nspcc-dev/neo-go/pkg/core/chaindump" + "github.com/nspcc-dev/neo-go/pkg/core/fee" + "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/core/storage" + "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/io" + "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/smartcontract/callflag" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" + "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/opcode" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "github.com/spf13/viper" + "go.uber.org/zap" +) + +type localClient struct { + bc *core.Blockchain + transactions []*transaction.Transaction + dumpPath string + accounts []*wallet.Account +} + +func newLocalClient(v *viper.Viper, wallets []*wallet.Wallet) (*localClient, error) { + cfg, err := config.LoadFile(v.GetString(protoConfigPath)) + if err != nil { + return nil, err + } + + bc, err := core.NewBlockchain(storage.NewMemoryStore(), cfg.ProtocolConfiguration, zap.NewNop()) + if err != nil { + return nil, err + } + defer func() { go bc.Run() }() + + m := smartcontract.GetDefaultHonestNodeCount(cfg.ProtocolConfiguration.ValidatorsCount) + accounts := make([]*wallet.Account, len(wallets)) + for i := range accounts { + accounts[i], err = getWalletAccount(wallets[i], consensusAccountName) + if err != nil { + return nil, err + } + } + + indexMap := make(map[string]int) + for i, pub := range cfg.ProtocolConfiguration.StandbyCommittee { + indexMap[pub] = i + } + + sort.Slice(accounts, func(i, j int) bool { + pi := accounts[i].PrivateKey().PublicKey().Bytes() + pj := accounts[j].PrivateKey().PublicKey().Bytes() + return indexMap[string(pi)] < indexMap[string(pj)] + }) + sort.Slice(accounts[:cfg.ProtocolConfiguration.ValidatorsCount], func(i, j int) bool { + pi := accounts[i].PrivateKey().PublicKey().Bytes() + pj := accounts[j].PrivateKey().PublicKey().Bytes() + return bytes.Compare(pi, pj) == -1 + }) + + return &localClient{ + bc: bc, + dumpPath: v.GetString(localDumpFlag), + accounts: accounts[:m], + }, nil +} + +func (l *localClient) GetBlockCount() (uint32, error) { + return l.bc.BlockHeight(), nil +} + +func (l *localClient) GetContractStateByID(id int32) (*state.Contract, error) { + h, err := l.bc.GetContractScriptHash(id) + if err != nil { + return nil, err + } + return l.GetContractStateByHash(h) +} + +func (l *localClient) GetContractStateByHash(h util.Uint160) (*state.Contract, error) { + if cs := l.bc.GetContractState(h); cs != nil { + return cs, nil + } + return nil, storage.ErrKeyNotFound +} + +func (l *localClient) GetNativeContracts() ([]state.NativeContract, error) { + return l.bc.GetNatives(), nil +} + +func (l *localClient) GetNetwork() (netmode.Magic, error) { + return l.bc.GetConfig().Magic, nil +} + +func (l *localClient) GetApplicationLog(h util.Uint256, t *trigger.Type) (*result.ApplicationLog, error) { + aer, err := l.bc.GetAppExecResults(h, *t) + if err != nil { + return nil, err + } + + a := result.NewApplicationLog(h, aer, *t) + return &a, nil +} + +func (l *localClient) CreateTxFromScript(script []byte, acc *wallet.Account, sysFee int64, netFee int64, cosigners []client.SignerAccount) (*transaction.Transaction, error) { + signers, accounts, err := getSigners(acc, cosigners) + if err != nil { + return nil, fmt.Errorf("failed to construct tx signers: %w", err) + } + if sysFee < 0 { + res, err := l.InvokeScript(script, signers) + if err != nil { + return nil, fmt.Errorf("can't add system fee to transaction: %w", err) + } + if res.State != "HALT" { + return nil, fmt.Errorf("can't add system fee to transaction: bad vm state: %s due to an error: %s", res.State, res.FaultException) + } + sysFee = res.GasConsumed + } + + tx := transaction.New(script, sysFee) + tx.Signers = signers + tx.ValidUntilBlock = l.bc.BlockHeight() + 2 + + err = l.addNetworkFee(tx, netFee, accounts...) + if err != nil { + return nil, fmt.Errorf("failed to add network fee: %w", err) + } + + return tx, nil +} + +// addNetworkFee adds network fee for each witness script and optional extra +// network fee to transaction. `accs` is an array signer's accounts. +// 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 +func (l *localClient) addNetworkFee(tx *transaction.Transaction, extraFee int64, accs ...*wallet.Account) error { + if len(tx.Signers) != len(accs) { + return errors.New("number of signers must match number of scripts") + } + + size := io.GetVarSize(tx) + ef := l.bc.GetBaseExecFee() + for i := range tx.Signers { + netFee, sizeDelta := fee.Calculate(ef, accs[i].Contract.Script) + tx.NetworkFee += netFee + size += sizeDelta + } + + tx.NetworkFee += int64(size)*l.bc.FeePerByte() + extraFee + return nil +} + +// getSigners returns an array of transaction signers and corresponding accounts from +// given sender and cosigners. If cosigners list already contains sender, the sender +// will be placed at the start of the list. +// Copied from neo-go with minor corrections: +// https://github.com/nspcc-dev/neo-go/blob/6ff11baa1b9e4c71ef0d1de43b92a8c541ca732c/pkg/rpc/client/rpc.go#L735 +func getSigners(sender *wallet.Account, cosigners []client.SignerAccount) ([]transaction.Signer, []*wallet.Account, error) { + var ( + signers []transaction.Signer + accounts []*wallet.Account + ) + + from := sender.Contract.ScriptHash() + s := transaction.Signer{ + Account: from, + Scopes: transaction.None, + } + for _, c := range cosigners { + if c.Signer.Account == from { + s = c.Signer + continue + } + signers = append(signers, c.Signer) + accounts = append(accounts, c.Account) + } + signers = append([]transaction.Signer{s}, signers...) + accounts = append([]*wallet.Account{sender}, accounts...) + return signers, accounts, nil +} + +func (l *localClient) NEP17BalanceOf(h util.Uint160, acc util.Uint160) (int64, error) { + res, err := invokeFunction(l, h, "balanceOf", []interface{}{acc}, nil) + if err != nil { + return 0, err + } + if res.State != vm.HaltState.String() || len(res.Stack) == 0 { + return 0, fmt.Errorf("`balance`: invalid response (empty: %t): %s", + len(res.Stack) == 0, res.FaultException) + } + bi, err := res.Stack[0].TryInteger() + if err != nil || !bi.IsInt64() { + return 0, fmt.Errorf("`balance`: invalid response") + } + return bi.Int64(), nil +} + +func (l *localClient) InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error) { + lastBlock, err := l.bc.GetBlock(l.bc.CurrentBlockHash()) + if err != nil { + return nil, err + } + + tx := transaction.New(script, 0) + tx.Signers = signers + tx.ValidUntilBlock = l.bc.BlockHeight() + 2 + + ic := l.bc.GetTestVM(trigger.Application, tx, &block.Block{ + Header: block.Header{ + Index: lastBlock.Index + 1, + Timestamp: lastBlock.Timestamp + 1, + }, + }) + + ic.VM.GasLimit = 100_0000_0000 + ic.VM.LoadScriptWithFlags(script, callflag.All) + + var errStr string + if err := ic.VM.Run(); err != nil { + errStr = err.Error() + } + return &result.Invoke{ + State: ic.VM.State().String(), + GasConsumed: ic.VM.GasConsumed(), + Script: script, + Stack: ic.VM.Estack().ToArray(), + FaultException: errStr, + }, nil +} + +func (l *localClient) SendRawTransaction(tx *transaction.Transaction) (util.Uint256, error) { + l.transactions = append(l.transactions, tx) + return tx.Hash(), nil +} + +func (l *localClient) putTransactions() error { + // 1. Prepare new block. + lastBlock, err := l.bc.GetBlock(l.bc.CurrentBlockHash()) + if err != nil { + panic(err) + } + defer func() { l.transactions = l.transactions[:0] }() + + b := &block.Block{ + Header: block.Header{ + NextConsensus: l.accounts[0].Contract.ScriptHash(), + Script: transaction.Witness{ + VerificationScript: l.accounts[0].Contract.Script, + }, + Timestamp: lastBlock.Timestamp + 1, + }, + Transactions: l.transactions, + } + + if l.bc.GetConfig().StateRootInHeader { + b.StateRootEnabled = true + b.PrevStateRoot = l.bc.GetStateModule().CurrentLocalStateRoot() + } + b.PrevHash = lastBlock.Hash() + b.Index = lastBlock.Index + 1 + b.RebuildMerkleRoot() + + // 2. Sign prepared block. + var invocationScript []byte + + magic := l.bc.GetConfig().Magic + for _, acc := range l.accounts { + sign := acc.PrivateKey().SignHashable(uint32(magic), b) + invocationScript = append(invocationScript, byte(opcode.PUSHDATA1), 64) + invocationScript = append(invocationScript, sign...) + } + b.Script.InvocationScript = invocationScript + + // 3. Persist block. + return l.bc.AddBlock(b) +} + +func invokeFunction(c Client, h util.Uint160, method string, parameters []interface{}, signers []transaction.Signer) (*result.Invoke, error) { + w := io.NewBufBinWriter() + emit.Array(w.BinWriter, parameters...) + emit.AppCallNoArgs(w.BinWriter, h, method, callflag.All) + if w.Err != nil { + panic(fmt.Sprintf("BUG: invalid parameters for '%s': %v", method, w.Err)) + } + return c.InvokeScript(w.Bytes(), signers) +} + +var errGetDesignatedByRoleResponse = errors.New("`getDesignatedByRole`: invalid response") + +func getDesignatedByRole(c Client, h util.Uint160, role noderoles.Role, u uint32) (keys.PublicKeys, error) { + res, err := invokeFunction(c, h, "getDesignatedByRole", []interface{}{int64(role), int64(u)}, nil) + if err != nil { + return nil, err + } + if res.State != vm.HaltState.String() || len(res.Stack) == 0 { + return nil, errGetDesignatedByRoleResponse + } + arr, ok := res.Stack[0].Value().([]stackitem.Item) + if !ok { + return nil, errGetDesignatedByRoleResponse + } + + pubs := make(keys.PublicKeys, len(arr)) + for i := range arr { + bs, err := arr[i].TryBytes() + if err != nil { + return nil, errGetDesignatedByRoleResponse + } + pubs[i], err = keys.NewPublicKeyFromBytes(bs, elliptic.P256()) + if err != nil { + return nil, errGetDesignatedByRoleResponse + } + } + + return pubs, nil +} + +func (l *localClient) dump() (err error) { + defer l.bc.Close() + + f, err := os.Create(l.dumpPath) + if err != nil { + return err + } + defer func() { + closeErr := f.Close() + if err == nil && closeErr != nil { + err = closeErr + } + }() + + w := io.NewBinWriterFromIO(f) + err = chaindump.Dump(l.bc, w, 0, l.bc.BlockHeight()) + return +} diff --git a/cmd/neofs-adm/internal/modules/morph/n3client.go b/cmd/neofs-adm/internal/modules/morph/n3client.go index c3f5688e..82f1ef61 100644 --- a/cmd/neofs-adm/internal/modules/morph/n3client.go +++ b/cmd/neofs-adm/internal/modules/morph/n3client.go @@ -7,13 +7,10 @@ import ( "time" "github.com/nspcc-dev/neo-go/pkg/config/netmode" - "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/core/transaction" - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "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/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/wallet" @@ -25,7 +22,6 @@ import ( // and sending signed transactions to chain. type Client interface { GetBlockCount() (uint32, error) - GetDesignatedByRole(noderoles.Role, uint32) (keys.PublicKeys, error) GetContractStateByID(int32) (*state.Contract, error) GetContractStateByHash(util.Uint160) (*state.Contract, error) GetNativeContracts() ([]state.NativeContract, error) @@ -33,7 +29,6 @@ type Client interface { GetApplicationLog(util.Uint256, *trigger.Type) (*result.ApplicationLog, error) CreateTxFromScript([]byte, *wallet.Account, int64, int64, []client.SignerAccount) (*transaction.Transaction, error) NEP17BalanceOf(util.Uint160, util.Uint160) (int64, error) - InvokeFunction(util.Uint160, string, []smartcontract.Parameter, []transaction.Signer) (*result.Invoke, error) InvokeScript([]byte, []transaction.Signer) (*result.Invoke, error) SendRawTransaction(*transaction.Transaction) (util.Uint256, error) } diff --git a/cmd/neofs-adm/internal/modules/morph/root.go b/cmd/neofs-adm/internal/modules/morph/root.go index a166b6ec..34ec928b 100644 --- a/cmd/neofs-adm/internal/modules/morph/root.go +++ b/cmd/neofs-adm/internal/modules/morph/root.go @@ -35,6 +35,8 @@ const ( refillGasAmountFlag = "gas" walletAccountFlag = "account" notaryDepositTillFlag = "till" + localDumpFlag = "local-dump" + protoConfigPath = "protocol" ) var ( @@ -68,6 +70,8 @@ var ( _ = viper.BindPFlag(containerFeeInitFlag, cmd.Flags().Lookup(containerFeeCLIFlag)) _ = viper.BindPFlag(containerAliasFeeInitFlag, cmd.Flags().Lookup(containerAliasFeeCLIFlag)) _ = viper.BindPFlag(withdrawFeeInitFlag, cmd.Flags().Lookup(withdrawFeeCLIFlag)) + _ = viper.BindPFlag(protoConfigPath, cmd.Flags().Lookup(protoConfigPath)) + _ = viper.BindPFlag(localDumpFlag, cmd.Flags().Lookup(localDumpFlag)) }, RunE: initializeSideChainCmd, } @@ -197,6 +201,8 @@ func init() { // Defaults are taken from neo-preodolenie. initCmd.Flags().Uint64(containerFeeCLIFlag, 1000, "container registration fee") initCmd.Flags().Uint64(containerAliasFeeCLIFlag, 500, "container alias fee") + initCmd.Flags().String(protoConfigPath, "", "path to the consensus node configuration") + initCmd.Flags().String(localDumpFlag, "", "path to the blocks dump file") RootCmd.AddCommand(generateStorageCmd) generateStorageCmd.Flags().String(alphabetWalletsFlag, "", "path to alphabet wallets dir") diff --git a/go.mod b/go.mod index d9cc422a..57f4ca9e 100644 --- a/go.mod +++ b/go.mod @@ -49,6 +49,7 @@ require ( github.com/google/go-querystring v1.1.0 // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/holiman/uint256 v1.2.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/ipfs/go-cid v0.0.7 // indirect github.com/magiconair/properties v1.8.5 // indirect diff --git a/go.sum b/go.sum index 2ae3cce2..1a3023bd 100644 --- a/go.sum +++ b/go.sum @@ -263,6 +263,7 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -628,6 +629,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=