diff --git a/cmd/frostfs-adm/internal/modules/morph/config.go b/cmd/frostfs-adm/internal/modules/morph/config.go index 2d0f12afe..2cff322d1 100644 --- a/cmd/frostfs-adm/internal/modules/morph/config.go +++ b/cmd/frostfs-adm/internal/modules/morph/config.go @@ -85,7 +85,7 @@ func setConfigCmd(cmd *cobra.Command, args []string) error { return errors.New("empty config pairs") } - wCtx, err := NewInitializeContext(cmd, viper.GetViper()) + wCtx, err := util.NewInitializeContext(cmd, viper.GetViper()) if err != nil { return fmt.Errorf("can't initialize context: %w", err) } diff --git a/cmd/frostfs-adm/internal/modules/morph/container.go b/cmd/frostfs-adm/internal/modules/morph/container.go index ab32dcabb..b18fa79ed 100644 --- a/cmd/frostfs-adm/internal/modules/morph/container.go +++ b/cmd/frostfs-adm/internal/modules/morph/container.go @@ -193,7 +193,7 @@ func restoreContainers(cmd *cobra.Command, _ []string) error { return fmt.Errorf("invalid filename: %w", err) } - wCtx, err := NewInitializeContext(cmd, viper.GetViper()) + wCtx, err := morphUtil.NewInitializeContext(cmd, viper.GetViper()) if err != nil { return err } @@ -222,7 +222,7 @@ func restoreContainers(cmd *cobra.Command, _ []string) error { return wCtx.AwaitTx() } -func restoreOrPutContainers(containers []Container, isOK func([]byte) bool, cmd *cobra.Command, wCtx *InitializeContext, ch util.Uint160) error { +func restoreOrPutContainers(containers []Container, isOK func([]byte) bool, cmd *cobra.Command, wCtx *morphUtil.InitializeContext, ch util.Uint160) error { bw := io.NewBufBinWriter() for _, cnt := range containers { hv := hash.Sha256(cnt.Value) @@ -262,7 +262,7 @@ func putContainer(bw *io.BufBinWriter, ch util.Uint160, cnt Container) { } } -func isContainerRestored(cmd *cobra.Command, wCtx *InitializeContext, containerHash util.Uint160, bw *io.BufBinWriter, hashValue util.Uint256) (bool, error) { +func isContainerRestored(cmd *cobra.Command, wCtx *morphUtil.InitializeContext, containerHash util.Uint160, bw *io.BufBinWriter, hashValue util.Uint256) (bool, error) { emit.AppCall(bw.BinWriter, containerHash, "get", callflag.All, hashValue.BytesBE()) res, err := wCtx.Client.InvokeScript(bw.Bytes(), nil) if err != nil { @@ -300,7 +300,7 @@ func parseContainers(filename string) ([]Container, error) { return containers, nil } -func fetchContainerContractHash(wCtx *InitializeContext) (util.Uint160, error) { +func fetchContainerContractHash(wCtx *morphUtil.InitializeContext) (util.Uint160, error) { r := management.NewReader(wCtx.ReadOnlyInvoker) nnsCs, err := r.GetContractByID(1) if err != nil { diff --git a/cmd/frostfs-adm/internal/modules/morph/deploy.go b/cmd/frostfs-adm/internal/modules/morph/deploy.go index 7f11362e0..e6fdbbcf7 100644 --- a/cmd/frostfs-adm/internal/modules/morph/deploy.go +++ b/cmd/frostfs-adm/internal/modules/morph/deploy.go @@ -60,7 +60,7 @@ func init() { func deployContractCmd(cmd *cobra.Command, args []string) error { v := viper.GetViper() - c, err := NewInitializeContext(cmd, v) + c, err := util.NewInitializeContext(cmd, v) if err != nil { return fmt.Errorf("initialization error: %w", err) } @@ -72,7 +72,7 @@ func deployContractCmd(cmd *cobra.Command, args []string) error { return err } - cs, err := readContract(ctrPath, ctrName) + cs, err := util.ReadContract(ctrPath, ctrName) if err != nil { return err } @@ -129,7 +129,7 @@ func deployContractCmd(cmd *cobra.Command, args []string) error { return c.AwaitTx() } -func registerNNS(nnsCs *state.Contract, c *InitializeContext, zone string, domain string, cs *util.ContractState, writer *io.BufBinWriter) error { +func registerNNS(nnsCs *state.Contract, c *util.InitializeContext, zone string, domain string, cs *util.ContractState, writer *io.BufBinWriter) error { bw := io.NewBufBinWriter() emit.Instruction(bw.BinWriter, opcode.INITSSLOT, []byte{1}) emit.AppCall(bw.BinWriter, nnsCs.Hash, "getPrice", callflag.All) @@ -147,12 +147,12 @@ func registerNNS(nnsCs *state.Contract, c *InitializeContext, zone string, domai emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All, zone, c.CommitteeAcc.Contract.ScriptHash(), - frostfsOpsEmail, int64(3600), int64(600), int64(defaultExpirationTime), int64(3600)) + util.FrostfsOpsEmail, int64(3600), int64(600), int64(util.DefaultExpirationTime), int64(3600)) emit.Opcodes(bw.BinWriter, opcode.ASSERT) emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All, domain, c.CommitteeAcc.Contract.ScriptHash(), - frostfsOpsEmail, int64(3600), int64(600), int64(defaultExpirationTime), int64(3600)) + util.FrostfsOpsEmail, int64(3600), int64(600), int64(util.DefaultExpirationTime), int64(3600)) emit.Opcodes(bw.BinWriter, opcode.ASSERT) } else { s, ok, err := c.NNSRegisterDomainScript(nnsCs.Hash, cs.Hash, domain) diff --git a/cmd/frostfs-adm/internal/modules/morph/dump_hashes.go b/cmd/frostfs-adm/internal/modules/morph/dump_hashes.go index 79b82ee47..d33311a8b 100644 --- a/cmd/frostfs-adm/internal/modules/morph/dump_hashes.go +++ b/cmd/frostfs-adm/internal/modules/morph/dump_hashes.go @@ -86,7 +86,7 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error { } } - for _, ctrName := range contractList { + for _, ctrName := range morphUtil.ContractList { bw.Reset() emit.AppCall(bw.BinWriter, cs.Hash, "resolve", callflag.ReadOnly, morphUtil.DomainOf(ctrName), int64(nns.TXT)) diff --git a/cmd/frostfs-adm/internal/modules/morph/epoch.go b/cmd/frostfs-adm/internal/modules/morph/epoch.go index 0fc25d01d..851f757b0 100644 --- a/cmd/frostfs-adm/internal/modules/morph/epoch.go +++ b/cmd/frostfs-adm/internal/modules/morph/epoch.go @@ -17,7 +17,7 @@ import ( ) func forceNewEpochCmd(cmd *cobra.Command, _ []string) error { - wCtx, err := NewInitializeContext(cmd, viper.GetViper()) + wCtx, err := util2.NewInitializeContext(cmd, viper.GetViper()) if err != nil { return fmt.Errorf("can't to initialize context: %w", err) } @@ -48,7 +48,7 @@ func forceNewEpochCmd(cmd *cobra.Command, _ []string) error { return err } -func emitNewEpochCall(bw *io.BufBinWriter, wCtx *InitializeContext, nmHash util.Uint160) error { +func emitNewEpochCall(bw *io.BufBinWriter, wCtx *util2.InitializeContext, nmHash util.Uint160) error { curr, err := unwrap.Int64(wCtx.ReadOnlyInvoker.Call(nmHash, "epoch")) if err != nil { return errors.New("can't fetch current epoch from the netmap contract") diff --git a/cmd/frostfs-adm/internal/modules/morph/frostfsid.go b/cmd/frostfs-adm/internal/modules/morph/frostfsid.go index 4adde815d..3e39c3019 100644 --- a/cmd/frostfs-adm/internal/modules/morph/frostfsid.go +++ b/cmd/frostfs-adm/internal/modules/morph/frostfsid.go @@ -416,11 +416,11 @@ type frostfsidClient struct { bw *io.BufBinWriter contractHash util.Uint160 roCli *frostfsidclient.Client // client can be used only for waiting tx, parsing and forming method params - wCtx *InitializeContext + wCtx *morphUtil.InitializeContext } func newFrostfsIDClient(cmd *cobra.Command) (*frostfsidClient, error) { - wCtx, err := NewInitializeContext(cmd, viper.GetViper()) + wCtx, err := morphUtil.NewInitializeContext(cmd, viper.GetViper()) if err != nil { return nil, fmt.Errorf("can't to initialize context: %w", err) } diff --git a/cmd/frostfs-adm/internal/modules/morph/generate.go b/cmd/frostfs-adm/internal/modules/morph/generate.go index f8e155f5b..2258eb832 100644 --- a/cmd/frostfs-adm/internal/modules/morph/generate.go +++ b/cmd/frostfs-adm/internal/modules/morph/generate.go @@ -45,7 +45,7 @@ func generateAlphabetCreds(cmd *cobra.Command, _ []string) error { return err } - _, err = initializeContractWallet(v, walletDir) + _, err = morphUtil.InitializeContractWallet(v, walletDir) if err != nil { return err } @@ -195,7 +195,7 @@ func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error return err } - wCtx, err := NewInitializeContext(cmd, viper.GetViper()) + wCtx, err := morphUtil.NewInitializeContext(cmd, viper.GetViper()) if err != nil { return err } diff --git a/cmd/frostfs-adm/internal/modules/morph/generate_test.go b/cmd/frostfs-adm/internal/modules/morph/generate_test.go index 207366ec7..e9e942d74 100644 --- a/cmd/frostfs-adm/internal/modules/morph/generate_test.go +++ b/cmd/frostfs-adm/internal/modules/morph/generate_test.go @@ -101,7 +101,7 @@ func TestGenerateAlphabet(t *testing.T) { wg.Wait() t.Run("check contract group wallet", func(t *testing.T) { - p := filepath.Join(walletDir, contractWalletFilename) + p := filepath.Join(walletDir, util.ContractWalletFilename) w, err := wallet.NewWalletFromFile(p) require.NoError(t, err, "contract wallet doesn't exist") require.Equal(t, 1, len(w.Accounts), "contract wallet must have 1 accout") diff --git a/cmd/frostfs-adm/internal/modules/morph/group.go b/cmd/frostfs-adm/internal/modules/morph/group.go index 0e6c511da..655b8b2c3 100644 --- a/cmd/frostfs-adm/internal/modules/morph/group.go +++ b/cmd/frostfs-adm/internal/modules/morph/group.go @@ -2,80 +2,13 @@ package morph import ( "encoding/json" - "fmt" - "os" - "path/filepath" - "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config" util2 "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/util" - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/wallet" - "github.com/spf13/cobra" - "github.com/spf13/viper" ) -const ( - contractWalletFilename = "contract.json" - contractWalletPasswordKey = "contract" -) - -func initializeContractWallet(v *viper.Viper, walletDir string) (*wallet.Wallet, error) { - password, err := config.GetPassword(v, contractWalletPasswordKey) - if err != nil { - return nil, err - } - - w, err := wallet.NewWallet(filepath.Join(walletDir, contractWalletFilename)) - if err != nil { - return nil, err - } - - acc, err := wallet.NewAccount() - if err != nil { - return nil, err - } - - err = acc.Encrypt(password, keys.NEP2ScryptParams()) - if err != nil { - return nil, err - } - - w.AddAccount(acc) - if err := w.SavePretty(); err != nil { - return nil, err - } - - return w, nil -} - -func openContractWallet(v *viper.Viper, cmd *cobra.Command, walletDir string) (*wallet.Wallet, error) { - p := filepath.Join(walletDir, contractWalletFilename) - w, err := wallet.NewWalletFromFile(p) - if err != nil { - if !os.IsNotExist(err) { - return nil, fmt.Errorf("can't open wallet: %w", err) - } - - cmd.Printf("Contract group wallet is missing, initialize at %s\n", p) - return initializeContractWallet(v, walletDir) - } - - password, err := config.GetPassword(v, contractWalletPasswordKey) - if err != nil { - return nil, err - } - - for i := range w.Accounts { - if err := w.Accounts[i].Decrypt(password, keys.NEP2ScryptParams()); err != nil { - return nil, fmt.Errorf("can't unlock wallet: %w", err) - } - } - - return w, nil -} - func addManifestGroup(cw *wallet.Wallet, h util.Uint160, cs *util2.ContractState) error { priv := cw.Accounts[0].PrivateKey() pub := priv.PublicKey() diff --git a/cmd/frostfs-adm/internal/modules/morph/initialize.go b/cmd/frostfs-adm/internal/modules/morph/initialize.go index 2057b4a24..d4219294c 100644 --- a/cmd/frostfs-adm/internal/modules/morph/initialize.go +++ b/cmd/frostfs-adm/internal/modules/morph/initialize.go @@ -1,50 +1,15 @@ package morph import ( - "errors" "fmt" - "os" - "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config" morphUtil "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/util" - morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client" - "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/keys" - "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" - "github.com/nspcc-dev/neo-go/pkg/rpcclient/management" - "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/spf13/cobra" "github.com/spf13/viper" ) -type Cache struct { - NNSCs *state.Contract - GroupKey *keys.PublicKey -} - -type InitializeContext struct { - morphUtil.ClientContext - Cache - // CommitteeAcc is used for retrieving the committee address and the verification script. - CommitteeAcc *wallet.Account - // ConsensusAcc is used for retrieving the committee address and the verification script. - ConsensusAcc *wallet.Account - Wallets []*wallet.Wallet - // ContractWallet is a wallet for providing the contract group signature. - ContractWallet *wallet.Wallet - // Accounts contains simple signature accounts in the same order as in Wallets. - Accounts []*wallet.Account - Contracts map[string]*morphUtil.ContractState - Command *cobra.Command - ContractPath string - ContractURL string -} - func initializeSideChainCmd(cmd *cobra.Command, _ []string) error { - initCtx, err := NewInitializeContext(cmd, viper.GetViper()) + initCtx, err := morphUtil.NewInitializeContext(cmd, viper.GetViper()) if err != nil { return fmt.Errorf("initialization error: %w", err) } @@ -91,289 +56,3 @@ func initializeSideChainCmd(cmd *cobra.Command, _ []string) error { cmd.Println("Stage 7: set addresses in NNS.") return setNNS(initCtx) } - -func (c *InitializeContext) Close() { - if local, ok := c.Client.(*morphUtil.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(morphUtil.AlphabetWalletsFlag)) - wallets, err := morphUtil.GetAlphabetWallets(v, walletDir) - if err != nil { - return nil, err - } - - needContracts := cmd.Name() == "update-contracts" || cmd.Name() == "init" - - var w *wallet.Wallet - w, err = getWallet(cmd, v, needContracts, walletDir) - if err != nil { - return nil, err - } - - c, err := createClient(cmd, v, wallets) - if err != nil { - return nil, err - } - - committeeAcc, err := morphUtil.GetWalletAccount(wallets[0], morphUtil.CommitteeAccountName) - if err != nil { - return nil, fmt.Errorf("can't find committee account: %w", err) - } - - consensusAcc, err := morphUtil.GetWalletAccount(wallets[0], morphUtil.ConsensusAccountName) - if err != nil { - return nil, fmt.Errorf("can't find consensus account: %w", err) - } - - if err := validateInit(cmd); err != nil { - return nil, err - } - - ctrPath, err := getContractsPath(cmd, needContracts) - if err != nil { - return nil, err - } - - var ctrURL string - if needContracts { - ctrURL, _ = cmd.Flags().GetString(contractsURLFlag) - } - - if err := checkNotaryEnabled(c); err != nil { - return nil, err - } - - accounts, err := createWalletAccounts(wallets) - if err != nil { - return nil, err - } - - cliCtx, err := morphUtil.DefaultClientContext(c, committeeAcc) - if err != nil { - return nil, fmt.Errorf("client context: %w", err) - } - - initCtx := &InitializeContext{ - ClientContext: *cliCtx, - ConsensusAcc: consensusAcc, - CommitteeAcc: committeeAcc, - ContractWallet: w, - Wallets: wallets, - Accounts: accounts, - Command: cmd, - Contracts: make(map[string]*morphUtil.ContractState), - ContractPath: ctrPath, - ContractURL: ctrURL, - } - - if needContracts { - err := initCtx.readContracts(fullContractList) - if err != nil { - return nil, err - } - } - - return initCtx, nil -} - -func validateInit(cmd *cobra.Command) error { - if cmd.Name() != "init" { - return nil - } - if viper.GetInt64(epochDurationInitFlag) <= 0 { - return fmt.Errorf("epoch duration must be positive") - } - - if viper.GetInt64(maxObjectSizeInitFlag) <= 0 { - return fmt.Errorf("max object size must be positive") - } - - return nil -} - -func createClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet) (morphUtil.Client, error) { - var c morphUtil.Client - var err error - if ldf := cmd.Flags().Lookup(localDumpFlag); ldf != nil && ldf.Changed { - if cmd.Flags().Changed(morphUtil.EndpointFlag) { - return nil, fmt.Errorf("`%s` and `%s` flags are mutually exclusive", morphUtil.EndpointFlag, localDumpFlag) - } - c, err = morphUtil.NewLocalClient(cmd, v, wallets, ldf.Value.String()) - } else { - c, err = morphUtil.GetN3Client(v) - } - if err != nil { - return nil, fmt.Errorf("can't create N3 client: %w", err) - } - return c, nil -} - -func getWallet(cmd *cobra.Command, v *viper.Viper, needContracts bool, walletDir string) (*wallet.Wallet, error) { - if !needContracts { - return nil, nil - } - return openContractWallet(v, cmd, walletDir) -} - -func getContractsPath(cmd *cobra.Command, needContracts bool) (string, error) { - if !needContracts { - return "", nil - } - - ctrPath, err := cmd.Flags().GetString(contractsInitFlag) - if err != nil { - return "", fmt.Errorf("invalid contracts path: %w", err) - } - return ctrPath, nil -} - -func createWalletAccounts(wallets []*wallet.Wallet) ([]*wallet.Account, error) { - accounts := make([]*wallet.Account, len(wallets)) - for i, w := range wallets { - acc, err := morphUtil.GetWalletAccount(w, morphUtil.SingleAccountName) - if err != nil { - return nil, fmt.Errorf("wallet %s is invalid (no single account): %w", w.Path(), err) - } - accounts[i] = acc - } - return accounts, nil -} - -func (c *InitializeContext) AwaitTx() error { - return c.ClientContext.AwaitTx(c.Command) -} - -func (c *InitializeContext) NNSContractState() (*state.Contract, error) { - if c.NNSCs != nil { - return c.NNSCs, nil - } - - r := management.NewReader(c.ReadOnlyInvoker) - cs, err := r.GetContractByID(1) - if err != nil { - return nil, err - } - - c.NNSCs = cs - return cs, nil -} - -func (c *InitializeContext) GetSigner(tryGroup bool, acc *wallet.Account) transaction.Signer { - if tryGroup && c.GroupKey != nil { - return transaction.Signer{ - Account: acc.Contract.ScriptHash(), - Scopes: transaction.CustomGroups, - AllowedGroups: keys.PublicKeys{c.GroupKey}, - } - } - - signer := transaction.Signer{ - Account: acc.Contract.ScriptHash(), - Scopes: transaction.Global, // Scope is important, as we have nested call to container contract. - } - - if !tryGroup { - return signer - } - - nnsCs, err := c.NNSContractState() - if err != nil { - return signer - } - - groupKey, err := morphUtil.NNSResolveKey(c.ReadOnlyInvoker, nnsCs.Hash, morphClient.NNSGroupKeyName) - if err == nil { - c.GroupKey = groupKey - - signer.Scopes = transaction.CustomGroups - signer.AllowedGroups = keys.PublicKeys{groupKey} - } - return signer -} - -// SendCommitteeTx creates transaction from script, signs it by committee nodes and sends it to RPC. -// If tryGroup is false, global scope is used for the signer (useful when -// working with native contracts). -func (c *InitializeContext) SendCommitteeTx(script []byte, tryGroup bool) error { - return c.sendMultiTx(script, tryGroup, false) -} - -// SendConsensusTx creates transaction from script, signs it by alphabet nodes and sends it to RPC. -// Not that because this is used only after the contracts were initialized and deployed, -// we always try to have a group scope. -func (c *InitializeContext) SendConsensusTx(script []byte) error { - return c.sendMultiTx(script, true, true) -} - -func (c *InitializeContext) sendMultiTx(script []byte, tryGroup bool, withConsensus bool) error { - var act *actor.Actor - var err error - - withConsensus = withConsensus && !c.ConsensusAcc.Contract.ScriptHash().Equals(c.CommitteeAcc.ScriptHash()) - if tryGroup { - // Even for consensus signatures we need the committee to pay. - signers := make([]actor.SignerAccount, 1, 2) - signers[0] = actor.SignerAccount{ - Signer: c.GetSigner(tryGroup, c.CommitteeAcc), - Account: c.CommitteeAcc, - } - if withConsensus { - signers = append(signers, actor.SignerAccount{ - Signer: c.GetSigner(tryGroup, c.ConsensusAcc), - Account: c.ConsensusAcc, - }) - } - act, err = actor.New(c.Client, signers) - } else { - if withConsensus { - panic("BUG: should never happen") - } - act, err = c.CommitteeAct, nil - } - if err != nil { - return fmt.Errorf("could not create actor: %w", err) - } - - tx, err := act.MakeUnsignedRun(script, []transaction.Attribute{{Type: transaction.HighPriority}}) - if err != nil { - return fmt.Errorf("could not perform test invocation: %w", err) - } - - if err := c.MultiSign(tx, morphUtil.CommitteeAccountName); err != nil { - return err - } - if withConsensus { - if err := c.MultiSign(tx, morphUtil.ConsensusAccountName); err != nil { - return err - } - } - - return c.SendTx(tx, c.Command, false) -} - -func checkNotaryEnabled(c morphUtil.Client) error { - ns, err := c.GetNativeContracts() - if err != nil { - return 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 = true - } - nativeHashes[ns[i].Manifest.Name] = ns[i].Hash - } - if !notaryEnabled { - return errors.New("notary contract must be enabled") - } - return nil -} diff --git a/cmd/frostfs-adm/internal/modules/morph/initialize_deploy.go b/cmd/frostfs-adm/internal/modules/morph/initialize_deploy.go index fd27eda6c..322033abc 100644 --- a/cmd/frostfs-adm/internal/modules/morph/initialize_deploy.go +++ b/cmd/frostfs-adm/internal/modules/morph/initialize_deploy.go @@ -1,20 +1,14 @@ package morph import ( - "archive/tar" - "compress/gzip" "encoding/hex" "errors" "fmt" - "io" - "os" - "path/filepath" "strings" "git.frostfs.info/TrueCloudLab/frostfs-contract/common" "git.frostfs.info/TrueCloudLab/frostfs-contract/nns" morphUtil "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/util" - "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring" morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap" "github.com/nspcc-dev/neo-go/pkg/core/state" @@ -35,41 +29,23 @@ import ( const frostfsIDAdminConfigKey = "frostfsid.admin" -var ( - contractList = []string{ - morphUtil.BalanceContract, - morphUtil.ContainerContract, - morphUtil.FrostfsIDContract, - morphUtil.NetmapContract, - morphUtil.PolicyContract, - morphUtil.ProxyContract, - } - - fullContractList = append([]string{ - morphUtil.FrostfsContract, - morphUtil.ProcessingContract, - morphUtil.NNSContract, - morphUtil.AlphabetContract, - }, contractList...) - - netmapConfigKeys = []string{ - netmap.EpochDurationConfig, - netmap.MaxObjectSizeConfig, - netmap.ContainerFeeConfig, - netmap.ContainerAliasFeeConfig, - netmap.IrCandidateFeeConfig, - netmap.WithdrawFeeConfig, - netmap.HomomorphicHashingDisabledKey, - netmap.MaintenanceModeAllowedConfig, - } -) +var netmapConfigKeys = []string{ + netmap.EpochDurationConfig, + netmap.MaxObjectSizeConfig, + netmap.ContainerFeeConfig, + netmap.ContainerAliasFeeConfig, + netmap.IrCandidateFeeConfig, + netmap.WithdrawFeeConfig, + netmap.HomomorphicHashingDisabledKey, + netmap.MaintenanceModeAllowedConfig, +} const ( updateMethodName = "update" deployMethodName = "deploy" ) -func deployNNS(c *InitializeContext, method string) error { +func deployNNS(c *morphUtil.InitializeContext, method string) error { cs := c.GetContract(morphUtil.NNSContract) h := cs.Hash @@ -113,7 +89,7 @@ func deployNNS(c *InitializeContext, method string) error { return c.AwaitTx() } -func updateContractsInternal(c *InitializeContext) error { +func updateContractsInternal(c *morphUtil.InitializeContext) error { alphaCs := c.GetContract(morphUtil.AlphabetContract) nnsCs, err := c.NNSContractState() @@ -166,13 +142,13 @@ func updateContractsInternal(c *InitializeContext) error { return c.AwaitTx() } -func deployOrUpdateContracts(c *InitializeContext, w *io2.BufBinWriter, nnsHash util.Uint160, keysParam []any) error { +func deployOrUpdateContracts(c *morphUtil.InitializeContext, w *io2.BufBinWriter, nnsHash util.Uint160, keysParam []any) error { emit.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1}) emit.AppCall(w.BinWriter, nnsHash, "getPrice", callflag.All) emit.Opcodes(w.BinWriter, opcode.STSFLD0) emit.AppCall(w.BinWriter, nnsHash, "setPrice", callflag.All, 1) - for _, ctrName := range contractList { + for _, ctrName := range morphUtil.ContractList { cs := c.GetContract(ctrName) method := updateMethodName @@ -233,7 +209,7 @@ func deployOrUpdateContracts(c *InitializeContext, w *io2.BufBinWriter, nnsHash return nil } -func deployAlphabetAccounts(c *InitializeContext, nnsHash util.Uint160, w *io2.BufBinWriter, alphaCs *morphUtil.ContractState) ([]any, error) { +func deployAlphabetAccounts(c *morphUtil.InitializeContext, nnsHash util.Uint160, w *io2.BufBinWriter, alphaCs *morphUtil.ContractState) ([]any, error) { var keysParam []any baseGroups := alphaCs.Manifest.Groups @@ -272,7 +248,7 @@ func deployAlphabetAccounts(c *InitializeContext, nnsHash util.Uint160, w *io2.B return keysParam, nil } -func deployContracts(c *InitializeContext) error { +func deployContracts(c *morphUtil.InitializeContext) error { alphaCs := c.GetContract(morphUtil.AlphabetContract) var keysParam []any @@ -309,7 +285,7 @@ func deployContracts(c *InitializeContext) error { c.SentTxs = append(c.SentTxs, morphUtil.HashVUBPair{Hash: txHash, Vub: vub}) } - for _, ctrName := range contractList { + for _, ctrName := range morphUtil.ContractList { cs := c.GetContract(ctrName) ctrHash := cs.Hash @@ -341,147 +317,11 @@ func deployContracts(c *InitializeContext) error { return c.AwaitTx() } -func (c *InitializeContext) IsUpdated(ctrHash util.Uint160, cs *morphUtil.ContractState) bool { - r := management.NewReader(c.ReadOnlyInvoker) - realCs, err := r.GetContract(ctrHash) - return err == nil && realCs != nil && realCs.NEF.Checksum == cs.NEF.Checksum -} - -func (c *InitializeContext) GetContract(ctrName string) *morphUtil.ContractState { - return c.Contracts[ctrName] -} - -func (c *InitializeContext) readContracts(names []string) error { - var ( - fi os.FileInfo - err error - ) - if c.ContractPath != "" { - fi, err = os.Stat(c.ContractPath) - if err != nil { - return fmt.Errorf("invalid contracts path: %w", err) - } - } - - if c.ContractPath != "" && fi.IsDir() { - for _, ctrName := range names { - cs, err := readContract(filepath.Join(c.ContractPath, ctrName), ctrName) - if err != nil { - return err - } - c.Contracts[ctrName] = cs - } - } else { - var r io.ReadCloser - if c.ContractPath != "" { - r, err = os.Open(c.ContractPath) - } else if c.ContractURL != "" { - r, err = downloadContracts(c.Command, c.ContractURL) - } else { - r, err = downloadContractsFromRepository(c.Command) - } - - if err != nil { - return fmt.Errorf("can't open contracts archive: %w", err) - } - defer r.Close() - - m, err := readContractsFromArchive(r, names) - if err != nil { - return err - } - for _, name := range names { - if err := m[name].Parse(); err != nil { - return err - } - c.Contracts[name] = m[name] - } - } - - for _, ctrName := range names { - if ctrName != morphUtil.AlphabetContract { - cs := c.Contracts[ctrName] - cs.Hash = state.CreateContractHash(c.CommitteeAcc.Contract.ScriptHash(), - cs.NEF.Checksum, cs.Manifest.Name) - } - } - return nil -} - -func readContract(ctrPath, ctrName string) (*morphUtil.ContractState, error) { - rawNef, err := os.ReadFile(filepath.Join(ctrPath, ctrName+"_contract.nef")) - if err != nil { - return nil, fmt.Errorf("can't read NEF file for %s contract: %w", ctrName, err) - } - rawManif, err := os.ReadFile(filepath.Join(ctrPath, "config.json")) - if err != nil { - return nil, fmt.Errorf("can't read manifest file for %s contract: %w", ctrName, err) - } - - cs := &morphUtil.ContractState{ - RawNEF: rawNef, - RawManifest: rawManif, - } - - return cs, cs.Parse() -} - -func readContractsFromArchive(file io.Reader, names []string) (map[string]*morphUtil.ContractState, error) { - m := make(map[string]*morphUtil.ContractState, len(names)) - for i := range names { - m[names[i]] = new(morphUtil.ContractState) - } - - gr, err := gzip.NewReader(file) - if err != nil { - return nil, fmt.Errorf("contracts file must be tar.gz archive: %w", err) - } - - r := tar.NewReader(gr) - for h, err := r.Next(); ; h, err = r.Next() { - if err != nil { - break - } - - dir, _ := filepath.Split(h.Name) - ctrName := filepath.Base(dir) - - cs, ok := m[ctrName] - if !ok { - continue - } - - switch { - case strings.HasSuffix(h.Name, filepath.Join(ctrName, ctrName+"_contract.nef")): - cs.RawNEF, err = io.ReadAll(r) - if err != nil { - return nil, fmt.Errorf("can't read NEF file for %s contract: %w", ctrName, err) - } - case strings.HasSuffix(h.Name, "config.json"): - cs.RawManifest, err = io.ReadAll(r) - if err != nil { - return nil, fmt.Errorf("can't read manifest file for %s contract: %w", ctrName, err) - } - } - m[ctrName] = cs - } - - for ctrName, cs := range m { - if cs.RawNEF == nil { - return nil, fmt.Errorf("NEF for %s contract wasn't found", ctrName) - } - if cs.RawManifest == nil { - return nil, fmt.Errorf("manifest for %s contract wasn't found", ctrName) - } - } - return m, nil -} - func getContractDeployParameters(cs *morphUtil.ContractState, deployData []any) []any { return []any{cs.RawNEF, cs.RawManifest, deployData} } -func getContractDeployData(c *InitializeContext, ctrName string, keysParam []any, method string) ([]any, error) { +func getContractDeployData(c *morphUtil.InitializeContext, ctrName string, keysParam []any, method string) ([]any, error) { items := make([]any, 0, 6) switch ctrName { @@ -625,13 +465,3 @@ func mergeNetmapConfig(roInvoker *invoker.Invoker, md map[string]any) error { } return nil } - -func (c *InitializeContext) GetAlphabetDeployItems(i, n int) []any { - items := make([]any, 5) - items[0] = c.Contracts[morphUtil.NetmapContract].Hash - items[1] = c.Contracts[morphUtil.ProxyContract].Hash - items[2] = innerring.GlagoliticLetter(i).String() - items[3] = int64(i) - items[4] = int64(n) - return items -} diff --git a/cmd/frostfs-adm/internal/modules/morph/initialize_nns.go b/cmd/frostfs-adm/internal/modules/morph/initialize_nns.go index 2a80077c1..8e5223098 100644 --- a/cmd/frostfs-adm/internal/modules/morph/initialize_nns.go +++ b/cmd/frostfs-adm/internal/modules/morph/initialize_nns.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "strconv" - "time" "git.frostfs.info/TrueCloudLab/frostfs-contract/nns" morphUtil "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/util" @@ -19,14 +18,9 @@ import ( "github.com/nspcc-dev/neo-go/pkg/util" "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/vmstate" ) -const defaultExpirationTime = 10 * 365 * 24 * time.Hour / time.Second - -const frostfsOpsEmail = "ops@frostfs.info" - -func setNNS(c *InitializeContext) error { +func setNNS(c *morphUtil.InitializeContext) error { r := management.NewReader(c.ReadOnlyInvoker) nnsCs, err := r.GetContractByID(1) if err != nil { @@ -40,7 +34,7 @@ func setNNS(c *InitializeContext) error { bw := io.NewBufBinWriter() emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All, "frostfs", c.CommitteeAcc.Contract.ScriptHash(), - frostfsOpsEmail, int64(3600), int64(600), int64(defaultExpirationTime), int64(3600)) + morphUtil.FrostfsOpsEmail, int64(3600), int64(600), int64(morphUtil.DefaultExpirationTime), int64(3600)) emit.Opcodes(bw.BinWriter, opcode.ASSERT) if err := c.SendCommitteeTx(bw.Bytes(), true); err != nil { return fmt.Errorf("can't add domain root to NNS: %w", err) @@ -61,7 +55,7 @@ func setNNS(c *InitializeContext) error { c.Command.Printf("NNS: Set %s -> %s\n", domain, alphaCs.Hash.StringLE()) } - for _, ctrName := range contractList { + for _, ctrName := range morphUtil.ContractList { cs := c.GetContract(ctrName) domain := ctrName + ".frostfs" @@ -81,7 +75,7 @@ func setNNS(c *InitializeContext) error { return c.AwaitTx() } -func updateNNSGroup(c *InitializeContext, nnsHash util.Uint160, pub *keys.PublicKey) error { +func updateNNSGroup(c *morphUtil.InitializeContext, nnsHash util.Uint160, pub *keys.PublicKey) error { bw := io.NewBufBinWriter() keyAlreadyAdded, domainRegCodeEmitted, err := c.EmitUpdateNNSGroupScript(bw, nnsHash, pub) if keyAlreadyAdded || err != nil { @@ -99,40 +93,6 @@ func updateNNSGroup(c *InitializeContext, nnsHash util.Uint160, pub *keys.Public return c.SendCommitteeTx(script, true) } -// EmitUpdateNNSGroupScript emits script for updating group key stored in NNS. -// First return value is true iff the key is already there and nothing should be done. -// Second return value is true iff a domain registration code was emitted. -func (c *InitializeContext) EmitUpdateNNSGroupScript(bw *io.BufBinWriter, nnsHash util.Uint160, pub *keys.PublicKey) (bool, bool, error) { - isAvail, err := morphUtil.NNSIsAvailable(c.Client, nnsHash, morphClient.NNSGroupKeyName) - if err != nil { - return false, false, err - } - - if !isAvail { - currentPub, err := morphUtil.NNSResolveKey(c.ReadOnlyInvoker, nnsHash, morphClient.NNSGroupKeyName) - if err != nil { - return false, false, err - } - - if pub.Equal(currentPub) { - return true, false, nil - } - } - - if isAvail { - emit.AppCall(bw.BinWriter, nnsHash, "register", callflag.All, - morphClient.NNSGroupKeyName, c.CommitteeAcc.Contract.ScriptHash(), - frostfsOpsEmail, int64(3600), int64(600), int64(defaultExpirationTime), int64(3600)) - emit.Opcodes(bw.BinWriter, opcode.ASSERT) - } - - emit.AppCall(bw.BinWriter, nnsHash, "deleteRecords", callflag.All, "group.frostfs", int64(nns.TXT)) - emit.AppCall(bw.BinWriter, nnsHash, "addRecord", callflag.All, - "group.frostfs", int64(nns.TXT), hex.EncodeToString(pub.Bytes())) - - return false, isAvail, nil -} - func getAlphabetNNSDomain(i int) string { return morphUtil.AlphabetContract + strconv.FormatUint(uint64(i), 10) + ".frostfs" } @@ -160,33 +120,7 @@ func wrapRegisterScriptWithPrice(w *io.BufBinWriter, nnsHash util.Uint160, s []b } } -func (c *InitializeContext) NNSRegisterDomainScript(nnsHash, expectedHash util.Uint160, domain string) ([]byte, bool, error) { - ok, err := morphUtil.NNSIsAvailable(c.Client, nnsHash, domain) - if err != nil { - return nil, false, err - } - - if ok { - bw := io.NewBufBinWriter() - emit.AppCall(bw.BinWriter, nnsHash, "register", callflag.All, - domain, c.CommitteeAcc.Contract.ScriptHash(), - frostfsOpsEmail, int64(3600), int64(600), int64(defaultExpirationTime), int64(3600)) - emit.Opcodes(bw.BinWriter, opcode.ASSERT) - - if bw.Err != nil { - panic(bw.Err) - } - return bw.Bytes(), false, nil - } - - s, err := morphUtil.NNSResolveHash(c.ReadOnlyInvoker, nnsHash, domain) - if err != nil { - return nil, false, err - } - return nil, s == expectedHash, nil -} - -func nnsRegisterDomain(c *InitializeContext, nnsHash, expectedHash util.Uint160, domain string) error { +func nnsRegisterDomain(c *morphUtil.InitializeContext, nnsHash, expectedHash util.Uint160, domain string) error { script, ok, err := c.NNSRegisterDomainScript(nnsHash, expectedHash, domain) if ok || err != nil { return err @@ -204,13 +138,4 @@ func nnsRegisterDomain(c *InitializeContext, nnsHash, expectedHash util.Uint160, return c.SendCommitteeTx(w.Bytes(), true) } -func (c *InitializeContext) NNSRootRegistered(nnsHash util.Uint160, zone string) (bool, error) { - res, err := c.CommitteeAct.Call(nnsHash, "isAvailable", "name."+zone) - if err != nil { - return false, err - } - - return res.State == vmstate.Halt.String(), nil -} - var errMissingNNSRecord = errors.New("missing NNS record") diff --git a/cmd/frostfs-adm/internal/modules/morph/initialize_register.go b/cmd/frostfs-adm/internal/modules/morph/initialize_register.go index 9d3bcff09..d6cbd2d56 100644 --- a/cmd/frostfs-adm/internal/modules/morph/initialize_register.go +++ b/cmd/frostfs-adm/internal/modules/morph/initialize_register.go @@ -28,7 +28,7 @@ const ( registerBatchSize = transaction.MaxAttributes - 1 ) -func registerCandidateRange(c *InitializeContext, start, end int) error { +func registerCandidateRange(c *morphUtil.InitializeContext, start, end int) error { regPrice, err := getCandidateRegisterPrice(c) if err != nil { return fmt.Errorf("can't fetch registration price: %w", err) @@ -82,7 +82,7 @@ func registerCandidateRange(c *InitializeContext, start, end int) error { return c.SendTx(tx, c.Command, true) } -func registerCandidates(c *InitializeContext) error { +func registerCandidates(c *morphUtil.InitializeContext) error { cc, err := unwrap.Array(c.ReadOnlyInvoker.Call(neo.Hash, "getCandidates")) if err != nil { return fmt.Errorf("`getCandidates`: %w", err) @@ -115,7 +115,7 @@ func registerCandidates(c *InitializeContext) error { return nil } -func transferNEOToAlphabetContracts(c *InitializeContext) error { +func transferNEOToAlphabetContracts(c *morphUtil.InitializeContext) error { neoHash := neo.Hash ok, err := transferNEOFinished(c, neoHash) @@ -141,7 +141,7 @@ func transferNEOToAlphabetContracts(c *InitializeContext) error { return c.AwaitTx() } -func transferNEOFinished(c *InitializeContext, neoHash util.Uint160) (bool, error) { +func transferNEOFinished(c *morphUtil.InitializeContext, neoHash util.Uint160) (bool, error) { r := nep17.NewReader(c.ReadOnlyInvoker, neoHash) bal, err := r.BalanceOf(c.CommitteeAcc.Contract.ScriptHash()) return bal.Cmp(big.NewInt(native.NEOTotalSupply)) == -1, err @@ -149,7 +149,7 @@ func transferNEOFinished(c *InitializeContext, neoHash util.Uint160) (bool, erro var errGetPriceInvalid = errors.New("`getRegisterPrice`: invalid response") -func getCandidateRegisterPrice(c *InitializeContext) (int64, error) { +func getCandidateRegisterPrice(c *morphUtil.InitializeContext) (int64, error) { switch c.Client.(type) { case *rpcclient.Client: inv := invoker.New(c.Client, nil) diff --git a/cmd/frostfs-adm/internal/modules/morph/initialize_roles.go b/cmd/frostfs-adm/internal/modules/morph/initialize_roles.go index 35750d602..9e885d5f8 100644 --- a/cmd/frostfs-adm/internal/modules/morph/initialize_roles.go +++ b/cmd/frostfs-adm/internal/modules/morph/initialize_roles.go @@ -9,7 +9,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/vm/emit" ) -func setNotaryAndAlphabetNodes(c *InitializeContext) error { +func setNotaryAndAlphabetNodes(c *util.InitializeContext) error { if ok, err := setRolesFinished(c); ok || err != nil { if err == nil { c.Command.Println("Stage 2: already performed.") @@ -35,7 +35,7 @@ func setNotaryAndAlphabetNodes(c *InitializeContext) error { return c.AwaitTx() } -func setRolesFinished(c *InitializeContext) (bool, error) { +func setRolesFinished(c *util.InitializeContext) (bool, error) { height, err := c.Client.GetBlockCount() if err != nil { return false, err diff --git a/cmd/frostfs-adm/internal/modules/morph/initialize_test.go b/cmd/frostfs-adm/internal/modules/morph/initialize_test.go index d5d50eca8..63e9887c0 100644 --- a/cmd/frostfs-adm/internal/modules/morph/initialize_test.go +++ b/cmd/frostfs-adm/internal/modules/morph/initialize_test.go @@ -58,27 +58,27 @@ func testInitialize(t *testing.T, committeeSize int) { v.Set(util.ProtoConfigPath, filepath.Join(testdataDir, protoFileName)) // Set to the path or remove the next statement to download from the network. - require.NoError(t, initCmd.Flags().Set(contractsInitFlag, contractsPath)) + require.NoError(t, initCmd.Flags().Set(util.ContractsInitFlag, contractsPath)) dumpPath := filepath.Join(testdataDir, "out") - require.NoError(t, initCmd.Flags().Set(localDumpFlag, dumpPath)) + require.NoError(t, initCmd.Flags().Set(util.LocalDumpFlag, dumpPath)) v.Set(util.AlphabetWalletsFlag, testdataDir) - v.Set(epochDurationInitFlag, 1) - v.Set(maxObjectSizeInitFlag, 1024) + v.Set(util.EpochDurationInitFlag, 1) + v.Set(util.MaxObjectSizeInitFlag, 1024) setTestCredentials(v, committeeSize) require.NoError(t, initializeSideChainCmd(initCmd, nil)) t.Run("force-new-epoch", func(t *testing.T) { - require.NoError(t, forceNewEpoch.Flags().Set(localDumpFlag, dumpPath)) + require.NoError(t, forceNewEpoch.Flags().Set(util.LocalDumpFlag, dumpPath)) require.NoError(t, forceNewEpochCmd(forceNewEpoch, nil)) }) t.Run("set-config", func(t *testing.T) { - require.NoError(t, setConfig.Flags().Set(localDumpFlag, dumpPath)) + require.NoError(t, setConfig.Flags().Set(util.LocalDumpFlag, dumpPath)) require.NoError(t, setConfigCmd(setConfig, []string{"MaintenanceModeAllowed=true"})) }) t.Run("set-policy", func(t *testing.T) { - require.NoError(t, setPolicy.Flags().Set(localDumpFlag, dumpPath)) + require.NoError(t, setPolicy.Flags().Set(util.LocalDumpFlag, dumpPath)) require.NoError(t, setPolicyCmd(setPolicy, []string{"ExecFeeFactor=1"})) }) t.Run("remove-node", func(t *testing.T) { @@ -86,7 +86,7 @@ func testInitialize(t *testing.T, committeeSize int) { require.NoError(t, err) pub := hex.EncodeToString(pk.PublicKey().Bytes()) - require.NoError(t, removeNodes.Flags().Set(localDumpFlag, dumpPath)) + require.NoError(t, removeNodes.Flags().Set(util.LocalDumpFlag, dumpPath)) require.NoError(t, removeNodesCmd(removeNodes, []string{pub})) }) } diff --git a/cmd/frostfs-adm/internal/modules/morph/initialize_transfer.go b/cmd/frostfs-adm/internal/modules/morph/initialize_transfer.go index 634b47892..ab4acefee 100644 --- a/cmd/frostfs-adm/internal/modules/morph/initialize_transfer.go +++ b/cmd/frostfs-adm/internal/modules/morph/initialize_transfer.go @@ -13,7 +13,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/rpcclient/neo" "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" - scContext "github.com/nspcc-dev/neo-go/pkg/smartcontract/context" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" @@ -28,7 +27,7 @@ const ( initialProxyGASAmount = 50_000 * native.GASFactor ) -func transferFunds(c *InitializeContext) error { +func transferFunds(c *morphUtil.InitializeContext) error { ok, err := transferFundsFinished(c) if ok || err != nil { if err == nil { @@ -75,7 +74,7 @@ func transferFunds(c *InitializeContext) error { return c.AwaitTx() } -func transferFundsFinished(c *InitializeContext) (bool, error) { +func transferFundsFinished(c *morphUtil.InitializeContext) (bool, error) { acc := c.Accounts[0] r := nep17.NewReader(c.ReadOnlyInvoker, gas.Hash) @@ -83,67 +82,7 @@ func transferFundsFinished(c *InitializeContext) (bool, error) { return res.Cmp(big.NewInt(initialAlphabetGASAmount/2)) == 1, err } -func (c *InitializeContext) MultiSignAndSend(tx *transaction.Transaction, accType string) error { - if err := c.MultiSign(tx, accType); err != nil { - return err - } - - return c.SendTx(tx, c.Command, false) -} - -func (c *InitializeContext) MultiSign(tx *transaction.Transaction, accType string) error { - version, err := c.Client.GetVersion() - if err != nil { - // error appears only if client - // has not been initialized - panic(err) - } - network := version.Protocol.Network - - // Use parameter context to avoid dealing with signature order. - pc := scContext.NewParameterContext("", network, tx) - h := c.CommitteeAcc.Contract.ScriptHash() - if accType == morphUtil.ConsensusAccountName { - h = c.ConsensusAcc.Contract.ScriptHash() - } - for _, w := range c.Wallets { - acc, err := morphUtil.GetWalletAccount(w, accType) - if err != nil { - return fmt.Errorf("can't find %s wallet account: %w", accType, err) - } - - priv := acc.PrivateKey() - sign := priv.SignHashable(uint32(network), tx) - if err := pc.AddSignature(h, acc.Contract, priv.PublicKey(), sign); err != nil { - return fmt.Errorf("can't add signature: %w", err) - } - if len(pc.Items[h].Signatures) == len(acc.Contract.Parameters) { - break - } - } - - w, err := pc.GetWitness(h) - if err != nil { - return fmt.Errorf("incomplete signature: %w", err) - } - - for i := range tx.Signers { - if tx.Signers[i].Account == h { - if i < len(tx.Scripts) { - tx.Scripts[i] = *w - } else if i == len(tx.Scripts) { - tx.Scripts = append(tx.Scripts, *w) - } else { - panic("BUG: invalid signing order") - } - return nil - } - } - - return fmt.Errorf("%s account was not found among transaction signers", accType) -} - -func transferGASToProxy(c *InitializeContext) error { +func transferGASToProxy(c *morphUtil.InitializeContext) error { proxyCs := c.GetContract(morphUtil.ProxyContract) r := nep17.NewReader(c.ReadOnlyInvoker, gas.Hash) diff --git a/cmd/frostfs-adm/internal/modules/morph/netmap_util.go b/cmd/frostfs-adm/internal/modules/morph/netmap_util.go index fa7aa0af3..3cf853cca 100644 --- a/cmd/frostfs-adm/internal/modules/morph/netmap_util.go +++ b/cmd/frostfs-adm/internal/modules/morph/netmap_util.go @@ -3,6 +3,7 @@ package morph import ( "errors" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/util" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/spf13/viper" @@ -10,8 +11,8 @@ import ( func getDefaultNetmapContractConfigMap() map[string]any { m := make(map[string]any) - m[netmap.EpochDurationConfig] = viper.GetInt64(epochDurationInitFlag) - m[netmap.MaxObjectSizeConfig] = viper.GetInt64(maxObjectSizeInitFlag) + m[netmap.EpochDurationConfig] = viper.GetInt64(util.EpochDurationInitFlag) + m[netmap.MaxObjectSizeConfig] = viper.GetInt64(util.MaxObjectSizeInitFlag) m[netmap.ContainerFeeConfig] = viper.GetInt64(containerFeeInitFlag) m[netmap.ContainerAliasFeeConfig] = viper.GetInt64(containerAliasFeeInitFlag) m[netmap.IrCandidateFeeConfig] = viper.GetInt64(candidateFeeInitFlag) diff --git a/cmd/frostfs-adm/internal/modules/morph/notary.go b/cmd/frostfs-adm/internal/modules/morph/notary.go index 3e7afdbf2..d05307705 100644 --- a/cmd/frostfs-adm/internal/modules/morph/notary.go +++ b/cmd/frostfs-adm/internal/modules/morph/notary.go @@ -85,7 +85,7 @@ func transferGas(cmd *cobra.Command, acc *wallet.Account, accHash util.Uint160, return err } - if err := checkNotaryEnabled(c); err != nil { + if err := morphUtil.CheckNotaryEnabled(c); err != nil { return err } diff --git a/cmd/frostfs-adm/internal/modules/morph/policy.go b/cmd/frostfs-adm/internal/modules/morph/policy.go index 97cc76ab7..d01c5e1a5 100644 --- a/cmd/frostfs-adm/internal/modules/morph/policy.go +++ b/cmd/frostfs-adm/internal/modules/morph/policy.go @@ -25,7 +25,7 @@ const ( ) func setPolicyCmd(cmd *cobra.Command, args []string) error { - wCtx, err := NewInitializeContext(cmd, viper.GetViper()) + wCtx, err := util.NewInitializeContext(cmd, viper.GetViper()) if err != nil { return fmt.Errorf("can't to initialize context: %w", err) } diff --git a/cmd/frostfs-adm/internal/modules/morph/proxy.go b/cmd/frostfs-adm/internal/modules/morph/proxy.go index 4bd461b97..aa98fb70d 100644 --- a/cmd/frostfs-adm/internal/modules/morph/proxy.go +++ b/cmd/frostfs-adm/internal/modules/morph/proxy.go @@ -36,7 +36,7 @@ func removeProxyAccount(cmd *cobra.Command, _ []string) { } func processAccount(cmd *cobra.Command, addr util.Uint160, method string) error { - wCtx, err := NewInitializeContext(cmd, viper.GetViper()) + wCtx, err := util2.NewInitializeContext(cmd, viper.GetViper()) if err != nil { return fmt.Errorf("can't to initialize context: %w", err) } diff --git a/cmd/frostfs-adm/internal/modules/morph/remove_node.go b/cmd/frostfs-adm/internal/modules/morph/remove_node.go index 42464e731..e63a5dee9 100644 --- a/cmd/frostfs-adm/internal/modules/morph/remove_node.go +++ b/cmd/frostfs-adm/internal/modules/morph/remove_node.go @@ -29,7 +29,7 @@ func removeNodesCmd(cmd *cobra.Command, args []string) error { } } - wCtx, err := NewInitializeContext(cmd, viper.GetViper()) + wCtx, err := util.NewInitializeContext(cmd, viper.GetViper()) if err != nil { return fmt.Errorf("can't initialize context: %w", err) } diff --git a/cmd/frostfs-adm/internal/modules/morph/root.go b/cmd/frostfs-adm/internal/modules/morph/root.go index 328877967..7585f5845 100644 --- a/cmd/frostfs-adm/internal/modules/morph/root.go +++ b/cmd/frostfs-adm/internal/modules/morph/root.go @@ -8,18 +8,14 @@ import ( ) const ( - alphabetSizeFlag = "size" - storageWalletFlag = "storage-wallet" - storageWalletLabelFlag = "label" - storageGasCLIFlag = "initial-gas" - storageGasConfigFlag = "storage.initial_gas" - contractsInitFlag = "contracts" - contractsInitFlagDesc = "Path to archive with compiled FrostFS contracts (the default is to fetch the latest release from the official repository)" - contractsURLFlag = "contracts-url" - contractsURLFlagDesc = "URL to archive with compiled FrostFS contracts" - maxObjectSizeInitFlag = "network.max_object_size" - maxObjectSizeCLIFlag = "max-object-size" - epochDurationInitFlag = "network.epoch_duration" + alphabetSizeFlag = "size" + storageWalletFlag = "storage-wallet" + storageWalletLabelFlag = "label" + storageGasCLIFlag = "initial-gas" + storageGasConfigFlag = "storage.initial_gas" + + maxObjectSizeCLIFlag = "max-object-size" + epochDurationCLIFlag = "epoch-duration" containerFeeInitFlag = "network.fee.container" containerAliasFeeInitFlag = "network.fee.container_alias" @@ -38,7 +34,6 @@ const ( refillGasAmountFlag = "gas" walletAccountFlag = "account" notaryDepositTillFlag = "till" - localDumpFlag = "local-dump" walletAddressFlag = "wallet-address" ) @@ -66,8 +61,8 @@ var ( PreRun: func(cmd *cobra.Command, _ []string) { _ = viper.BindPFlag(util.AlphabetWalletsFlag, cmd.Flags().Lookup(util.AlphabetWalletsFlag)) _ = viper.BindPFlag(util.EndpointFlag, cmd.Flags().Lookup(util.EndpointFlag)) - _ = viper.BindPFlag(epochDurationInitFlag, cmd.Flags().Lookup(epochDurationCLIFlag)) - _ = viper.BindPFlag(maxObjectSizeInitFlag, cmd.Flags().Lookup(maxObjectSizeCLIFlag)) + _ = viper.BindPFlag(util.EpochDurationInitFlag, cmd.Flags().Lookup(epochDurationCLIFlag)) + _ = viper.BindPFlag(util.MaxObjectSizeInitFlag, cmd.Flags().Lookup(maxObjectSizeCLIFlag)) _ = viper.BindPFlag(homomorphicHashDisabledInitFlag, cmd.Flags().Lookup(homomorphicHashDisabledCLIFlag)) _ = viper.BindPFlag(candidateFeeInitFlag, cmd.Flags().Lookup(candidateFeeCLIFlag)) _ = viper.BindPFlag(containerFeeInitFlag, cmd.Flags().Lookup(containerFeeCLIFlag)) @@ -365,9 +360,9 @@ func initUpdateContractsCmd() { RootCmd.AddCommand(updateContractsCmd) updateContractsCmd.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc) updateContractsCmd.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc) - updateContractsCmd.Flags().String(contractsInitFlag, "", contractsInitFlagDesc) - updateContractsCmd.Flags().String(contractsURLFlag, "", contractsURLFlagDesc) - updateContractsCmd.MarkFlagsMutuallyExclusive(contractsInitFlag, contractsURLFlag) + updateContractsCmd.Flags().String(util.ContractsInitFlag, "", util.ContractsInitFlagDesc) + updateContractsCmd.Flags().String(util.ContractsURLFlag, "", util.ContractsURLFlagDesc) + updateContractsCmd.MarkFlagsMutuallyExclusive(util.ContractsInitFlag, util.ContractsURLFlag) } func initDumpBalancesCmd() { @@ -384,7 +379,7 @@ func initSetConfigCmd() { setConfig.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc) setConfig.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc) setConfig.Flags().Bool(forceConfigSet, false, "Force setting not well-known configuration key") - setConfig.Flags().String(localDumpFlag, "", "Path to the blocks dump file") + setConfig.Flags().String(util.LocalDumpFlag, "", "Path to the blocks dump file") } func initDumpNetworkConfigCmd() { @@ -402,7 +397,7 @@ func initSetPolicyCmd() { RootCmd.AddCommand(setPolicy) setPolicy.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc) setPolicy.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc) - setPolicy.Flags().String(localDumpFlag, "", "Path to the blocks dump file") + setPolicy.Flags().String(util.LocalDumpFlag, "", "Path to the blocks dump file") } func initDumpPolicyCmd() { @@ -414,14 +409,14 @@ func initRemoveNodesCmd() { RootCmd.AddCommand(removeNodes) removeNodes.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc) removeNodes.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc) - removeNodes.Flags().String(localDumpFlag, "", "Path to the blocks dump file") + removeNodes.Flags().String(util.LocalDumpFlag, "", "Path to the blocks dump file") } func initForceNewEpochCmd() { RootCmd.AddCommand(forceNewEpoch) forceNewEpoch.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc) forceNewEpoch.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc) - forceNewEpoch.Flags().String(localDumpFlag, "", "Path to the blocks dump file") + forceNewEpoch.Flags().String(util.LocalDumpFlag, "", "Path to the blocks dump file") } func initGenerateStorageCmd() { @@ -437,8 +432,8 @@ func initInitCmd() { RootCmd.AddCommand(initCmd) initCmd.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc) initCmd.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc) - initCmd.Flags().String(contractsInitFlag, "", contractsInitFlagDesc) - initCmd.Flags().String(contractsURLFlag, "", contractsURLFlagDesc) + initCmd.Flags().String(util.ContractsInitFlag, "", util.ContractsInitFlagDesc) + initCmd.Flags().String(util.ContractsURLFlag, "", util.ContractsURLFlagDesc) initCmd.Flags().Uint(epochDurationCLIFlag, 240, "Amount of side chain blocks in one FrostFS epoch") initCmd.Flags().Uint(maxObjectSizeCLIFlag, 67108864, "Max single object size in bytes") initCmd.Flags().Bool(homomorphicHashDisabledCLIFlag, false, "Disable object homomorphic hashing") @@ -446,8 +441,8 @@ func initInitCmd() { initCmd.Flags().Uint64(containerFeeCLIFlag, 1000, "Container registration fee") initCmd.Flags().Uint64(containerAliasFeeCLIFlag, 500, "Container alias fee") initCmd.Flags().String(util.ProtoConfigPath, "", "Path to the consensus node configuration") - initCmd.Flags().String(localDumpFlag, "", "Path to the blocks dump file") - initCmd.MarkFlagsMutuallyExclusive(contractsInitFlag, contractsURLFlag) + initCmd.Flags().String(util.LocalDumpFlag, "", "Path to the blocks dump file") + initCmd.MarkFlagsMutuallyExclusive(util.ContractsInitFlag, util.ContractsURLFlag) } func initGenerateAlphabetCmd() { diff --git a/cmd/frostfs-adm/internal/modules/morph/update.go b/cmd/frostfs-adm/internal/modules/morph/update.go index b6fcbb5b7..2d5b24712 100644 --- a/cmd/frostfs-adm/internal/modules/morph/update.go +++ b/cmd/frostfs-adm/internal/modules/morph/update.go @@ -3,12 +3,13 @@ package morph import ( "fmt" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/util" "github.com/spf13/cobra" "github.com/spf13/viper" ) func updateContracts(cmd *cobra.Command, _ []string) error { - wCtx, err := NewInitializeContext(cmd, viper.GetViper()) + wCtx, err := util.NewInitializeContext(cmd, viper.GetViper()) if err != nil { return fmt.Errorf("initialization error: %w", err) } diff --git a/cmd/frostfs-adm/internal/modules/morph/util/const.go b/cmd/frostfs-adm/internal/modules/morph/util/const.go index bf71b067f..f100ebc61 100644 --- a/cmd/frostfs-adm/internal/modules/morph/util/const.go +++ b/cmd/frostfs-adm/internal/modules/morph/util/const.go @@ -1,5 +1,7 @@ package util +import "time" + const ( ConsensusAccountName = "consensus" ProtoConfigPath = "protocol" @@ -14,6 +16,13 @@ const ( EndpointFlagShort = "r" AlphabetWalletsFlag = "alphabet-wallets" AlphabetWalletsFlagDesc = "Path to alphabet wallets dir" + LocalDumpFlag = "local-dump" + ContractsInitFlag = "contracts" + ContractsInitFlagDesc = "Path to archive with compiled FrostFS contracts (the default is to fetch the latest release from the official repository)" + ContractsURLFlag = "contracts-url" + ContractsURLFlagDesc = "URL to archive with compiled FrostFS contracts" + EpochDurationInitFlag = "network.epoch_duration" + MaxObjectSizeInitFlag = "network.max_object_size" SingleAccountName = "single" CommitteeAccountName = "committee" @@ -28,4 +37,29 @@ const ( NetmapContract = "netmap" PolicyContract = "policy" ProxyContract = "proxy" + + ContractWalletFilename = "contract.json" + ContractWalletPasswordKey = "contract" + + FrostfsOpsEmail = "ops@frostfs.info" + + DefaultExpirationTime = 10 * 365 * 24 * time.Hour / time.Second +) + +var ( + ContractList = []string{ + BalanceContract, + ContainerContract, + FrostfsIDContract, + NetmapContract, + PolicyContract, + ProxyContract, + } + + FullContractList = append([]string{ + FrostfsContract, + ProcessingContract, + NNSContract, + AlphabetContract, + }, ContractList...) ) diff --git a/cmd/frostfs-adm/internal/modules/morph/download.go b/cmd/frostfs-adm/internal/modules/morph/util/download.go similarity index 99% rename from cmd/frostfs-adm/internal/modules/morph/download.go rename to cmd/frostfs-adm/internal/modules/morph/util/download.go index 5bd2d98bd..ff97a7f4a 100644 --- a/cmd/frostfs-adm/internal/modules/morph/download.go +++ b/cmd/frostfs-adm/internal/modules/morph/util/download.go @@ -1,4 +1,4 @@ -package morph +package util import ( "context" diff --git a/cmd/frostfs-adm/internal/modules/morph/util/initialize.go b/cmd/frostfs-adm/internal/modules/morph/util/initialize.go index 725181ec7..0e4c22644 100644 --- a/cmd/frostfs-adm/internal/modules/morph/util/initialize.go +++ b/cmd/frostfs-adm/internal/modules/morph/util/initialize.go @@ -7,6 +7,7 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-contract/nns" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "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/rpcclient" @@ -187,3 +188,23 @@ func NNSIsAvailable(c Client, nnsHash util.Uint160, name string) (bool, error) { return b, nil } } + +func CheckNotaryEnabled(c Client) error { + ns, err := c.GetNativeContracts() + if err != nil { + return 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 = true + } + nativeHashes[ns[i].Manifest.Name] = ns[i].Hash + } + if !notaryEnabled { + return errors.New("notary contract must be enabled") + } + return nil +} diff --git a/cmd/frostfs-adm/internal/modules/morph/util/initialize_ctx.go b/cmd/frostfs-adm/internal/modules/morph/util/initialize_ctx.go index be5dcf2db..e7ced2ae6 100644 --- a/cmd/frostfs-adm/internal/modules/morph/util/initialize_ctx.go +++ b/cmd/frostfs-adm/internal/modules/morph/util/initialize_ctx.go @@ -1,14 +1,67 @@ package util import ( + "encoding/hex" "encoding/json" "fmt" + io2 "io" + "os" + "path/filepath" + "git.frostfs.info/TrueCloudLab/frostfs-contract/nns" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client" + "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/io" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/management" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/context" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/util" + "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/vmstate" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "github.com/spf13/cobra" + "github.com/spf13/viper" ) +type ContractState struct { + NEF *nef.File + RawNEF []byte + Manifest *manifest.Manifest + RawManifest []byte + Hash util.Uint160 +} + +type Cache struct { + NNSCs *state.Contract + GroupKey *keys.PublicKey +} + +type InitializeContext struct { + ClientContext + Cache + // CommitteeAcc is used for retrieving the committee address and the verification script. + CommitteeAcc *wallet.Account + // ConsensusAcc is used for retrieving the committee address and the verification script. + ConsensusAcc *wallet.Account + Wallets []*wallet.Wallet + // ContractWallet is a wallet for providing the contract group signature. + ContractWallet *wallet.Wallet + // Accounts contains simple signature accounts in the same order as in Wallets. + Accounts []*wallet.Account + Contracts map[string]*ContractState + Command *cobra.Command + ContractPath string + ContractURL string +} + func (cs *ContractState) Parse() error { nf, err := nef.FileFromBytes(cs.RawNEF) if err != nil { @@ -25,10 +78,466 @@ func (cs *ContractState) Parse() error { return nil } -type ContractState struct { - NEF *nef.File - RawNEF []byte - Manifest *manifest.Manifest - RawManifest []byte - Hash util.Uint160 +func NewInitializeContext(cmd *cobra.Command, v *viper.Viper) (*InitializeContext, error) { + walletDir := config.ResolveHomePath(viper.GetString(AlphabetWalletsFlag)) + wallets, err := GetAlphabetWallets(v, walletDir) + if err != nil { + return nil, err + } + + needContracts := cmd.Name() == "update-contracts" || cmd.Name() == "init" + + var w *wallet.Wallet + w, err = GetWallet(cmd, v, needContracts, walletDir) + if err != nil { + return nil, err + } + + c, err := createClient(cmd, v, wallets) + if err != nil { + return nil, err + } + + committeeAcc, err := GetWalletAccount(wallets[0], CommitteeAccountName) + if err != nil { + return nil, fmt.Errorf("can't find committee account: %w", err) + } + + consensusAcc, err := GetWalletAccount(wallets[0], ConsensusAccountName) + if err != nil { + return nil, fmt.Errorf("can't find consensus account: %w", err) + } + + if err := validateInit(cmd); err != nil { + return nil, err + } + + ctrPath, err := getContractsPath(cmd, needContracts) + if err != nil { + return nil, err + } + + var ctrURL string + if needContracts { + ctrURL, _ = cmd.Flags().GetString(ContractsURLFlag) + } + + if err := CheckNotaryEnabled(c); err != nil { + return nil, err + } + + accounts, err := createWalletAccounts(wallets) + if err != nil { + return nil, err + } + + cliCtx, err := DefaultClientContext(c, committeeAcc) + if err != nil { + return nil, fmt.Errorf("client context: %w", err) + } + + initCtx := &InitializeContext{ + ClientContext: *cliCtx, + ConsensusAcc: consensusAcc, + CommitteeAcc: committeeAcc, + ContractWallet: w, + Wallets: wallets, + Accounts: accounts, + Command: cmd, + Contracts: make(map[string]*ContractState), + ContractPath: ctrPath, + ContractURL: ctrURL, + } + + if needContracts { + err := readContracts(initCtx, FullContractList) + if err != nil { + return nil, err + } + } + + return initCtx, nil +} + +func validateInit(cmd *cobra.Command) error { + if cmd.Name() != "init" { + return nil + } + if viper.GetInt64(EpochDurationInitFlag) <= 0 { + return fmt.Errorf("epoch duration must be positive") + } + + if viper.GetInt64(MaxObjectSizeInitFlag) <= 0 { + return fmt.Errorf("max object size must be positive") + } + + return nil +} + +func createClient(cmd *cobra.Command, v *viper.Viper, wallets []*wallet.Wallet) (Client, error) { + var c Client + var err error + if ldf := cmd.Flags().Lookup(LocalDumpFlag); ldf != nil && ldf.Changed { + if cmd.Flags().Changed(EndpointFlag) { + return nil, fmt.Errorf("`%s` and `%s` flags are mutually exclusive", EndpointFlag, LocalDumpFlag) + } + c, err = NewLocalClient(cmd, v, wallets, ldf.Value.String()) + } else { + c, err = GetN3Client(v) + } + if err != nil { + return nil, fmt.Errorf("can't create N3 client: %w", err) + } + return c, nil +} + +func getContractsPath(cmd *cobra.Command, needContracts bool) (string, error) { + if !needContracts { + return "", nil + } + + ctrPath, err := cmd.Flags().GetString(ContractsInitFlag) + if err != nil { + return "", fmt.Errorf("invalid contracts path: %w", err) + } + return ctrPath, nil +} + +func createWalletAccounts(wallets []*wallet.Wallet) ([]*wallet.Account, error) { + accounts := make([]*wallet.Account, len(wallets)) + for i, w := range wallets { + acc, err := GetWalletAccount(w, SingleAccountName) + if err != nil { + return nil, fmt.Errorf("wallet %s is invalid (no single account): %w", w.Path(), err) + } + accounts[i] = acc + } + return accounts, nil +} + +func readContracts(c *InitializeContext, names []string) error { + var ( + fi os.FileInfo + err error + ) + if c.ContractPath != "" { + fi, err = os.Stat(c.ContractPath) + if err != nil { + return fmt.Errorf("invalid contracts path: %w", err) + } + } + + if c.ContractPath != "" && fi.IsDir() { + for _, ctrName := range names { + cs, err := ReadContract(filepath.Join(c.ContractPath, ctrName), ctrName) + if err != nil { + return err + } + c.Contracts[ctrName] = cs + } + } else { + var r io2.ReadCloser + if c.ContractPath != "" { + r, err = os.Open(c.ContractPath) + } else if c.ContractURL != "" { + r, err = downloadContracts(c.Command, c.ContractURL) + } else { + r, err = downloadContractsFromRepository(c.Command) + } + if err != nil { + return fmt.Errorf("can't open contracts archive: %w", err) + } + defer r.Close() + + m, err := ReadContractsFromArchive(r, names) + if err != nil { + return err + } + for _, name := range names { + if err := m[name].Parse(); err != nil { + return err + } + c.Contracts[name] = m[name] + } + } + + for _, ctrName := range names { + if ctrName != AlphabetContract { + cs := c.Contracts[ctrName] + cs.Hash = state.CreateContractHash(c.CommitteeAcc.Contract.ScriptHash(), + cs.NEF.Checksum, cs.Manifest.Name) + } + } + 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 (c *InitializeContext) AwaitTx() error { + return c.ClientContext.AwaitTx(c.Command) +} + +func (c *InitializeContext) NNSContractState() (*state.Contract, error) { + if c.NNSCs != nil { + return c.NNSCs, nil + } + + r := management.NewReader(c.ReadOnlyInvoker) + cs, err := r.GetContractByID(1) + if err != nil { + return nil, err + } + + c.NNSCs = cs + return cs, nil +} + +func (c *InitializeContext) GetSigner(tryGroup bool, acc *wallet.Account) transaction.Signer { + if tryGroup && c.GroupKey != nil { + return transaction.Signer{ + Account: acc.Contract.ScriptHash(), + Scopes: transaction.CustomGroups, + AllowedGroups: keys.PublicKeys{c.GroupKey}, + } + } + + signer := transaction.Signer{ + Account: acc.Contract.ScriptHash(), + Scopes: transaction.Global, // Scope is important, as we have nested call to container contract. + } + + if !tryGroup { + return signer + } + + nnsCs, err := c.NNSContractState() + if err != nil { + return signer + } + + groupKey, err := NNSResolveKey(c.ReadOnlyInvoker, nnsCs.Hash, client.NNSGroupKeyName) + if err == nil { + c.GroupKey = groupKey + + signer.Scopes = transaction.CustomGroups + signer.AllowedGroups = keys.PublicKeys{groupKey} + } + return signer +} + +// SendCommitteeTx creates transaction from script, signs it by committee nodes and sends it to RPC. +// If tryGroup is false, global scope is used for the signer (useful when +// working with native contracts). +func (c *InitializeContext) SendCommitteeTx(script []byte, tryGroup bool) error { + return c.sendMultiTx(script, tryGroup, false) +} + +// SendConsensusTx creates transaction from script, signs it by alphabet nodes and sends it to RPC. +// Not that because this is used only after the contracts were initialized and deployed, +// we always try to have a group scope. +func (c *InitializeContext) SendConsensusTx(script []byte) error { + return c.sendMultiTx(script, true, true) +} + +func (c *InitializeContext) sendMultiTx(script []byte, tryGroup bool, withConsensus bool) error { + var act *actor.Actor + var err error + + withConsensus = withConsensus && !c.ConsensusAcc.Contract.ScriptHash().Equals(c.CommitteeAcc.ScriptHash()) + if tryGroup { + // Even for consensus signatures we need the committee to pay. + signers := make([]actor.SignerAccount, 1, 2) + signers[0] = actor.SignerAccount{ + Signer: c.GetSigner(tryGroup, c.CommitteeAcc), + Account: c.CommitteeAcc, + } + if withConsensus { + signers = append(signers, actor.SignerAccount{ + Signer: c.GetSigner(tryGroup, c.ConsensusAcc), + Account: c.ConsensusAcc, + }) + } + act, err = actor.New(c.Client, signers) + } else { + if withConsensus { + panic("BUG: should never happen") + } + act, err = c.CommitteeAct, nil + } + if err != nil { + return fmt.Errorf("could not create actor: %w", err) + } + + tx, err := act.MakeUnsignedRun(script, []transaction.Attribute{{Type: transaction.HighPriority}}) + if err != nil { + return fmt.Errorf("could not perform test invocation: %w", err) + } + + if err := c.MultiSign(tx, CommitteeAccountName); err != nil { + return err + } + if withConsensus { + if err := c.MultiSign(tx, ConsensusAccountName); err != nil { + return err + } + } + + return c.SendTx(tx, c.Command, false) +} + +func (c *InitializeContext) MultiSignAndSend(tx *transaction.Transaction, accType string) error { + if err := c.MultiSign(tx, accType); err != nil { + return err + } + + return c.SendTx(tx, c.Command, false) +} + +func (c *InitializeContext) MultiSign(tx *transaction.Transaction, accType string) error { + version, err := c.Client.GetVersion() + if err != nil { + // error appears only if client + // has not been initialized + panic(err) + } + network := version.Protocol.Network + + // Use parameter context to avoid dealing with signature order. + pc := context.NewParameterContext("", network, tx) + h := c.CommitteeAcc.Contract.ScriptHash() + if accType == ConsensusAccountName { + h = c.ConsensusAcc.Contract.ScriptHash() + } + for _, w := range c.Wallets { + acc, err := GetWalletAccount(w, accType) + if err != nil { + return fmt.Errorf("can't find %s wallet account: %w", accType, err) + } + + priv := acc.PrivateKey() + sign := priv.SignHashable(uint32(network), tx) + if err := pc.AddSignature(h, acc.Contract, priv.PublicKey(), sign); err != nil { + return fmt.Errorf("can't add signature: %w", err) + } + if len(pc.Items[h].Signatures) == len(acc.Contract.Parameters) { + break + } + } + + w, err := pc.GetWitness(h) + if err != nil { + return fmt.Errorf("incomplete signature: %w", err) + } + + for i := range tx.Signers { + if tx.Signers[i].Account == h { + if i < len(tx.Scripts) { + tx.Scripts[i] = *w + } else if i == len(tx.Scripts) { + tx.Scripts = append(tx.Scripts, *w) + } else { + panic("BUG: invalid signing order") + } + return nil + } + } + + return fmt.Errorf("%s account was not found among transaction signers", accType) +} + +// EmitUpdateNNSGroupScript emits script for updating group key stored in NNS. +// First return value is true iff the key is already there and nothing should be done. +// Second return value is true iff a domain registration code was emitted. +func (c *InitializeContext) EmitUpdateNNSGroupScript(bw *io.BufBinWriter, nnsHash util.Uint160, pub *keys.PublicKey) (bool, bool, error) { + isAvail, err := NNSIsAvailable(c.Client, nnsHash, client.NNSGroupKeyName) + if err != nil { + return false, false, err + } + + if !isAvail { + currentPub, err := NNSResolveKey(c.ReadOnlyInvoker, nnsHash, client.NNSGroupKeyName) + if err != nil { + return false, false, err + } + + if pub.Equal(currentPub) { + return true, false, nil + } + } + + if isAvail { + emit.AppCall(bw.BinWriter, nnsHash, "register", callflag.All, + client.NNSGroupKeyName, c.CommitteeAcc.Contract.ScriptHash(), + FrostfsOpsEmail, int64(3600), int64(600), int64(DefaultExpirationTime), int64(3600)) + emit.Opcodes(bw.BinWriter, opcode.ASSERT) + } + + emit.AppCall(bw.BinWriter, nnsHash, "deleteRecords", callflag.All, "group.frostfs", int64(nns.TXT)) + emit.AppCall(bw.BinWriter, nnsHash, "addRecord", callflag.All, + "group.frostfs", int64(nns.TXT), hex.EncodeToString(pub.Bytes())) + + return false, isAvail, nil +} + +func (c *InitializeContext) NNSRegisterDomainScript(nnsHash, expectedHash util.Uint160, domain string) ([]byte, bool, error) { + ok, err := NNSIsAvailable(c.Client, nnsHash, domain) + if err != nil { + return nil, false, err + } + + if ok { + bw := io.NewBufBinWriter() + emit.AppCall(bw.BinWriter, nnsHash, "register", callflag.All, + domain, c.CommitteeAcc.Contract.ScriptHash(), + FrostfsOpsEmail, int64(3600), int64(600), int64(DefaultExpirationTime), int64(3600)) + emit.Opcodes(bw.BinWriter, opcode.ASSERT) + + if bw.Err != nil { + panic(bw.Err) + } + return bw.Bytes(), false, nil + } + + s, err := NNSResolveHash(c.ReadOnlyInvoker, nnsHash, domain) + if err != nil { + return nil, false, err + } + return nil, s == expectedHash, nil +} + +func (c *InitializeContext) NNSRootRegistered(nnsHash util.Uint160, zone string) (bool, error) { + res, err := c.CommitteeAct.Call(nnsHash, "isAvailable", "name."+zone) + if err != nil { + return false, err + } + + return res.State == vmstate.Halt.String(), nil +} + +func (c *InitializeContext) IsUpdated(ctrHash util.Uint160, cs *ContractState) bool { + r := management.NewReader(c.ReadOnlyInvoker) + realCs, err := r.GetContract(ctrHash) + return err == nil && realCs != nil && realCs.NEF.Checksum == cs.NEF.Checksum +} + +func (c *InitializeContext) GetContract(ctrName string) *ContractState { + return c.Contracts[ctrName] +} + +func (c *InitializeContext) GetAlphabetDeployItems(i, n int) []any { + items := make([]any, 5) + items[0] = c.Contracts[NetmapContract].Hash + items[1] = c.Contracts[ProxyContract].Hash + items[2] = innerring.GlagoliticLetter(i).String() + items[3] = int64(i) + items[4] = int64(n) + return items } diff --git a/cmd/frostfs-adm/internal/modules/morph/util/util.go b/cmd/frostfs-adm/internal/modules/morph/util/util.go index bbe635a40..abd72b04d 100644 --- a/cmd/frostfs-adm/internal/modules/morph/util/util.go +++ b/cmd/frostfs-adm/internal/modules/morph/util/util.go @@ -1,10 +1,14 @@ package util import ( + "archive/tar" + "compress/gzip" "errors" "fmt" + "io" "os" "path/filepath" + "strings" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring" @@ -84,3 +88,72 @@ func NewActor(c actor.RPCActor, committeeAcc *wallet.Account) (*actor.Actor, err Account: committeeAcc, }}) } + +func ReadContract(ctrPath, ctrName string) (*ContractState, error) { + rawNef, err := os.ReadFile(filepath.Join(ctrPath, ctrName+"_contract.nef")) + if err != nil { + return nil, fmt.Errorf("can't read NEF file for %s contract: %w", ctrName, err) + } + rawManif, err := os.ReadFile(filepath.Join(ctrPath, "config.json")) + if err != nil { + return nil, fmt.Errorf("can't read manifest file for %s contract: %w", ctrName, err) + } + + cs := &ContractState{ + RawNEF: rawNef, + RawManifest: rawManif, + } + + return cs, cs.Parse() +} + +func ReadContractsFromArchive(file io.Reader, names []string) (map[string]*ContractState, error) { + m := make(map[string]*ContractState, len(names)) + for i := range names { + m[names[i]] = new(ContractState) + } + + gr, err := gzip.NewReader(file) + if err != nil { + return nil, fmt.Errorf("contracts file must be tar.gz archive: %w", err) + } + + r := tar.NewReader(gr) + for h, err := r.Next(); ; h, err = r.Next() { + if err != nil { + break + } + + dir, _ := filepath.Split(h.Name) + ctrName := filepath.Base(dir) + + cs, ok := m[ctrName] + if !ok { + continue + } + + switch { + case strings.HasSuffix(h.Name, filepath.Join(ctrName, ctrName+"_contract.nef")): + cs.RawNEF, err = io.ReadAll(r) + if err != nil { + return nil, fmt.Errorf("can't read NEF file for %s contract: %w", ctrName, err) + } + case strings.HasSuffix(h.Name, "config.json"): + cs.RawManifest, err = io.ReadAll(r) + if err != nil { + return nil, fmt.Errorf("can't read manifest file for %s contract: %w", ctrName, err) + } + } + m[ctrName] = cs + } + + for ctrName, cs := range m { + if cs.RawNEF == nil { + return nil, fmt.Errorf("NEF for %s contract wasn't found", ctrName) + } + if cs.RawManifest == nil { + return nil, fmt.Errorf("manifest for %s contract wasn't found", ctrName) + } + } + return m, nil +} diff --git a/cmd/frostfs-adm/internal/modules/morph/util/wallet.go b/cmd/frostfs-adm/internal/modules/morph/util/wallet.go new file mode 100644 index 000000000..2110adb10 --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/util/wallet.go @@ -0,0 +1,75 @@ +package util + +import ( + "fmt" + "os" + "path/filepath" + + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func InitializeContractWallet(v *viper.Viper, walletDir string) (*wallet.Wallet, error) { + password, err := config.GetPassword(v, ContractWalletPasswordKey) + if err != nil { + return nil, err + } + + w, err := wallet.NewWallet(filepath.Join(walletDir, ContractWalletFilename)) + if err != nil { + return nil, err + } + + acc, err := wallet.NewAccount() + if err != nil { + return nil, err + } + + err = acc.Encrypt(password, keys.NEP2ScryptParams()) + if err != nil { + return nil, err + } + + w.AddAccount(acc) + if err := w.SavePretty(); err != nil { + return nil, err + } + + return w, nil +} + +func OpenContractWallet(v *viper.Viper, cmd *cobra.Command, walletDir string) (*wallet.Wallet, error) { + p := filepath.Join(walletDir, ContractWalletFilename) + w, err := wallet.NewWalletFromFile(p) + if err != nil { + if !os.IsNotExist(err) { + return nil, fmt.Errorf("can't open wallet: %w", err) + } + + cmd.Printf("Contract group wallet is missing, initialize at %s\n", p) + return InitializeContractWallet(v, walletDir) + } + + password, err := config.GetPassword(v, ContractWalletPasswordKey) + if err != nil { + return nil, err + } + + for i := range w.Accounts { + if err := w.Accounts[i].Decrypt(password, keys.NEP2ScryptParams()); err != nil { + return nil, fmt.Errorf("can't unlock wallet: %w", err) + } + } + + return w, nil +} + +func GetWallet(cmd *cobra.Command, v *viper.Viper, needContracts bool, walletDir string) (*wallet.Wallet, error) { + if !needContracts { + return nil, nil + } + return OpenContractWallet(v, cmd, walletDir) +}