[#932] adm: Move InitializeContext to util package

Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
This commit is contained in:
Anton Nikiforov 2024-02-01 15:21:51 +03:00
parent b68f7be0b6
commit 77694a2f3b
29 changed files with 809 additions and 794 deletions

View file

@ -85,7 +85,7 @@ func setConfigCmd(cmd *cobra.Command, args []string) error {
return errors.New("empty config pairs") return errors.New("empty config pairs")
} }
wCtx, err := NewInitializeContext(cmd, viper.GetViper()) wCtx, err := util.NewInitializeContext(cmd, viper.GetViper())
if err != nil { if err != nil {
return fmt.Errorf("can't initialize context: %w", err) return fmt.Errorf("can't initialize context: %w", err)
} }

View file

@ -193,7 +193,7 @@ func restoreContainers(cmd *cobra.Command, _ []string) error {
return fmt.Errorf("invalid filename: %w", err) return fmt.Errorf("invalid filename: %w", err)
} }
wCtx, err := NewInitializeContext(cmd, viper.GetViper()) wCtx, err := morphUtil.NewInitializeContext(cmd, viper.GetViper())
if err != nil { if err != nil {
return err return err
} }
@ -222,7 +222,7 @@ func restoreContainers(cmd *cobra.Command, _ []string) error {
return wCtx.AwaitTx() 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() bw := io.NewBufBinWriter()
for _, cnt := range containers { for _, cnt := range containers {
hv := hash.Sha256(cnt.Value) 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()) emit.AppCall(bw.BinWriter, containerHash, "get", callflag.All, hashValue.BytesBE())
res, err := wCtx.Client.InvokeScript(bw.Bytes(), nil) res, err := wCtx.Client.InvokeScript(bw.Bytes(), nil)
if err != nil { if err != nil {
@ -300,7 +300,7 @@ func parseContainers(filename string) ([]Container, error) {
return containers, nil return containers, nil
} }
func fetchContainerContractHash(wCtx *InitializeContext) (util.Uint160, error) { func fetchContainerContractHash(wCtx *morphUtil.InitializeContext) (util.Uint160, error) {
r := management.NewReader(wCtx.ReadOnlyInvoker) r := management.NewReader(wCtx.ReadOnlyInvoker)
nnsCs, err := r.GetContractByID(1) nnsCs, err := r.GetContractByID(1)
if err != nil { if err != nil {

View file

@ -60,7 +60,7 @@ func init() {
func deployContractCmd(cmd *cobra.Command, args []string) error { func deployContractCmd(cmd *cobra.Command, args []string) error {
v := viper.GetViper() v := viper.GetViper()
c, err := NewInitializeContext(cmd, v) c, err := util.NewInitializeContext(cmd, v)
if err != nil { if err != nil {
return fmt.Errorf("initialization error: %w", err) return fmt.Errorf("initialization error: %w", err)
} }
@ -72,7 +72,7 @@ func deployContractCmd(cmd *cobra.Command, args []string) error {
return err return err
} }
cs, err := readContract(ctrPath, ctrName) cs, err := util.ReadContract(ctrPath, ctrName)
if err != nil { if err != nil {
return err return err
} }
@ -129,7 +129,7 @@ func deployContractCmd(cmd *cobra.Command, args []string) error {
return c.AwaitTx() 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() bw := io.NewBufBinWriter()
emit.Instruction(bw.BinWriter, opcode.INITSSLOT, []byte{1}) emit.Instruction(bw.BinWriter, opcode.INITSSLOT, []byte{1})
emit.AppCall(bw.BinWriter, nnsCs.Hash, "getPrice", callflag.All) 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, emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
zone, c.CommitteeAcc.Contract.ScriptHash(), 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.Opcodes(bw.BinWriter, opcode.ASSERT)
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All, emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
domain, c.CommitteeAcc.Contract.ScriptHash(), 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) emit.Opcodes(bw.BinWriter, opcode.ASSERT)
} else { } else {
s, ok, err := c.NNSRegisterDomainScript(nnsCs.Hash, cs.Hash, domain) s, ok, err := c.NNSRegisterDomainScript(nnsCs.Hash, cs.Hash, domain)

View file

@ -86,7 +86,7 @@ func dumpContractHashes(cmd *cobra.Command, _ []string) error {
} }
} }
for _, ctrName := range contractList { for _, ctrName := range morphUtil.ContractList {
bw.Reset() bw.Reset()
emit.AppCall(bw.BinWriter, cs.Hash, "resolve", callflag.ReadOnly, emit.AppCall(bw.BinWriter, cs.Hash, "resolve", callflag.ReadOnly,
morphUtil.DomainOf(ctrName), int64(nns.TXT)) morphUtil.DomainOf(ctrName), int64(nns.TXT))

View file

@ -17,7 +17,7 @@ import (
) )
func forceNewEpochCmd(cmd *cobra.Command, _ []string) error { func forceNewEpochCmd(cmd *cobra.Command, _ []string) error {
wCtx, err := NewInitializeContext(cmd, viper.GetViper()) wCtx, err := util2.NewInitializeContext(cmd, viper.GetViper())
if err != nil { if err != nil {
return fmt.Errorf("can't to initialize context: %w", err) return fmt.Errorf("can't to initialize context: %w", err)
} }
@ -48,7 +48,7 @@ func forceNewEpochCmd(cmd *cobra.Command, _ []string) error {
return err 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")) curr, err := unwrap.Int64(wCtx.ReadOnlyInvoker.Call(nmHash, "epoch"))
if err != nil { if err != nil {
return errors.New("can't fetch current epoch from the netmap contract") return errors.New("can't fetch current epoch from the netmap contract")

View file

@ -416,11 +416,11 @@ type frostfsidClient struct {
bw *io.BufBinWriter bw *io.BufBinWriter
contractHash util.Uint160 contractHash util.Uint160
roCli *frostfsidclient.Client // client can be used only for waiting tx, parsing and forming method params 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) { func newFrostfsIDClient(cmd *cobra.Command) (*frostfsidClient, error) {
wCtx, err := NewInitializeContext(cmd, viper.GetViper()) wCtx, err := morphUtil.NewInitializeContext(cmd, viper.GetViper())
if err != nil { if err != nil {
return nil, fmt.Errorf("can't to initialize context: %w", err) return nil, fmt.Errorf("can't to initialize context: %w", err)
} }

View file

@ -45,7 +45,7 @@ func generateAlphabetCreds(cmd *cobra.Command, _ []string) error {
return err return err
} }
_, err = initializeContractWallet(v, walletDir) _, err = morphUtil.InitializeContractWallet(v, walletDir)
if err != nil { if err != nil {
return err return err
} }
@ -195,7 +195,7 @@ func refillGas(cmd *cobra.Command, gasFlag string, createWallet bool) (err error
return err return err
} }
wCtx, err := NewInitializeContext(cmd, viper.GetViper()) wCtx, err := morphUtil.NewInitializeContext(cmd, viper.GetViper())
if err != nil { if err != nil {
return err return err
} }

View file

@ -101,7 +101,7 @@ func TestGenerateAlphabet(t *testing.T) {
wg.Wait() wg.Wait()
t.Run("check contract group wallet", func(t *testing.T) { 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) w, err := wallet.NewWalletFromFile(p)
require.NoError(t, err, "contract wallet doesn't exist") require.NoError(t, err, "contract wallet doesn't exist")
require.Equal(t, 1, len(w.Accounts), "contract wallet must have 1 accout") require.Equal(t, 1, len(w.Accounts), "contract wallet must have 1 accout")

View file

@ -2,80 +2,13 @@ package morph
import ( import (
"encoding/json" "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" 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/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet" "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 { func addManifestGroup(cw *wallet.Wallet, h util.Uint160, cs *util2.ContractState) error {
priv := cw.Accounts[0].PrivateKey() priv := cw.Accounts[0].PrivateKey()
pub := priv.PublicKey() pub := priv.PublicKey()

View file

@ -1,50 +1,15 @@
package morph package morph
import ( import (
"errors"
"fmt" "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" 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/cobra"
"github.com/spf13/viper" "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 { func initializeSideChainCmd(cmd *cobra.Command, _ []string) error {
initCtx, err := NewInitializeContext(cmd, viper.GetViper()) initCtx, err := morphUtil.NewInitializeContext(cmd, viper.GetViper())
if err != nil { if err != nil {
return fmt.Errorf("initialization error: %w", err) 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.") cmd.Println("Stage 7: set addresses in NNS.")
return setNNS(initCtx) 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
}

View file

@ -1,20 +1,14 @@
package morph package morph
import ( import (
"archive/tar"
"compress/gzip"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"io"
"os"
"path/filepath"
"strings" "strings"
"git.frostfs.info/TrueCloudLab/frostfs-contract/common" "git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns" "git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
morphUtil "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/util" 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" morphClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
@ -35,41 +29,23 @@ import (
const frostfsIDAdminConfigKey = "frostfsid.admin" const frostfsIDAdminConfigKey = "frostfsid.admin"
var ( var netmapConfigKeys = []string{
contractList = []string{ netmap.EpochDurationConfig,
morphUtil.BalanceContract, netmap.MaxObjectSizeConfig,
morphUtil.ContainerContract, netmap.ContainerFeeConfig,
morphUtil.FrostfsIDContract, netmap.ContainerAliasFeeConfig,
morphUtil.NetmapContract, netmap.IrCandidateFeeConfig,
morphUtil.PolicyContract, netmap.WithdrawFeeConfig,
morphUtil.ProxyContract, netmap.HomomorphicHashingDisabledKey,
} netmap.MaintenanceModeAllowedConfig,
}
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,
}
)
const ( const (
updateMethodName = "update" updateMethodName = "update"
deployMethodName = "deploy" deployMethodName = "deploy"
) )
func deployNNS(c *InitializeContext, method string) error { func deployNNS(c *morphUtil.InitializeContext, method string) error {
cs := c.GetContract(morphUtil.NNSContract) cs := c.GetContract(morphUtil.NNSContract)
h := cs.Hash h := cs.Hash
@ -113,7 +89,7 @@ func deployNNS(c *InitializeContext, method string) error {
return c.AwaitTx() return c.AwaitTx()
} }
func updateContractsInternal(c *InitializeContext) error { func updateContractsInternal(c *morphUtil.InitializeContext) error {
alphaCs := c.GetContract(morphUtil.AlphabetContract) alphaCs := c.GetContract(morphUtil.AlphabetContract)
nnsCs, err := c.NNSContractState() nnsCs, err := c.NNSContractState()
@ -166,13 +142,13 @@ func updateContractsInternal(c *InitializeContext) error {
return c.AwaitTx() 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.Instruction(w.BinWriter, opcode.INITSSLOT, []byte{1})
emit.AppCall(w.BinWriter, nnsHash, "getPrice", callflag.All) emit.AppCall(w.BinWriter, nnsHash, "getPrice", callflag.All)
emit.Opcodes(w.BinWriter, opcode.STSFLD0) emit.Opcodes(w.BinWriter, opcode.STSFLD0)
emit.AppCall(w.BinWriter, nnsHash, "setPrice", callflag.All, 1) emit.AppCall(w.BinWriter, nnsHash, "setPrice", callflag.All, 1)
for _, ctrName := range contractList { for _, ctrName := range morphUtil.ContractList {
cs := c.GetContract(ctrName) cs := c.GetContract(ctrName)
method := updateMethodName method := updateMethodName
@ -233,7 +209,7 @@ func deployOrUpdateContracts(c *InitializeContext, w *io2.BufBinWriter, nnsHash
return nil 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 var keysParam []any
baseGroups := alphaCs.Manifest.Groups baseGroups := alphaCs.Manifest.Groups
@ -272,7 +248,7 @@ func deployAlphabetAccounts(c *InitializeContext, nnsHash util.Uint160, w *io2.B
return keysParam, nil return keysParam, nil
} }
func deployContracts(c *InitializeContext) error { func deployContracts(c *morphUtil.InitializeContext) error {
alphaCs := c.GetContract(morphUtil.AlphabetContract) alphaCs := c.GetContract(morphUtil.AlphabetContract)
var keysParam []any var keysParam []any
@ -309,7 +285,7 @@ func deployContracts(c *InitializeContext) error {
c.SentTxs = append(c.SentTxs, morphUtil.HashVUBPair{Hash: txHash, Vub: vub}) c.SentTxs = append(c.SentTxs, morphUtil.HashVUBPair{Hash: txHash, Vub: vub})
} }
for _, ctrName := range contractList { for _, ctrName := range morphUtil.ContractList {
cs := c.GetContract(ctrName) cs := c.GetContract(ctrName)
ctrHash := cs.Hash ctrHash := cs.Hash
@ -341,147 +317,11 @@ func deployContracts(c *InitializeContext) error {
return c.AwaitTx() 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 { func getContractDeployParameters(cs *morphUtil.ContractState, deployData []any) []any {
return []any{cs.RawNEF, cs.RawManifest, deployData} 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) items := make([]any, 0, 6)
switch ctrName { switch ctrName {
@ -625,13 +465,3 @@ func mergeNetmapConfig(roInvoker *invoker.Invoker, md map[string]any) error {
} }
return nil 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
}

View file

@ -5,7 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"strconv" "strconv"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns" "git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
morphUtil "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/util" 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/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit" "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/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
) )
const defaultExpirationTime = 10 * 365 * 24 * time.Hour / time.Second func setNNS(c *morphUtil.InitializeContext) error {
const frostfsOpsEmail = "ops@frostfs.info"
func setNNS(c *InitializeContext) error {
r := management.NewReader(c.ReadOnlyInvoker) r := management.NewReader(c.ReadOnlyInvoker)
nnsCs, err := r.GetContractByID(1) nnsCs, err := r.GetContractByID(1)
if err != nil { if err != nil {
@ -40,7 +34,7 @@ func setNNS(c *InitializeContext) error {
bw := io.NewBufBinWriter() bw := io.NewBufBinWriter()
emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All, emit.AppCall(bw.BinWriter, nnsCs.Hash, "register", callflag.All,
"frostfs", c.CommitteeAcc.Contract.ScriptHash(), "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) emit.Opcodes(bw.BinWriter, opcode.ASSERT)
if err := c.SendCommitteeTx(bw.Bytes(), true); err != nil { if err := c.SendCommitteeTx(bw.Bytes(), true); err != nil {
return fmt.Errorf("can't add domain root to NNS: %w", err) 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()) 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) cs := c.GetContract(ctrName)
domain := ctrName + ".frostfs" domain := ctrName + ".frostfs"
@ -81,7 +75,7 @@ func setNNS(c *InitializeContext) error {
return c.AwaitTx() 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() bw := io.NewBufBinWriter()
keyAlreadyAdded, domainRegCodeEmitted, err := c.EmitUpdateNNSGroupScript(bw, nnsHash, pub) keyAlreadyAdded, domainRegCodeEmitted, err := c.EmitUpdateNNSGroupScript(bw, nnsHash, pub)
if keyAlreadyAdded || err != nil { if keyAlreadyAdded || err != nil {
@ -99,40 +93,6 @@ func updateNNSGroup(c *InitializeContext, nnsHash util.Uint160, pub *keys.Public
return c.SendCommitteeTx(script, true) 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 { func getAlphabetNNSDomain(i int) string {
return morphUtil.AlphabetContract + strconv.FormatUint(uint64(i), 10) + ".frostfs" 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) { func nnsRegisterDomain(c *morphUtil.InitializeContext, nnsHash, expectedHash util.Uint160, domain string) 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 {
script, ok, err := c.NNSRegisterDomainScript(nnsHash, expectedHash, domain) script, ok, err := c.NNSRegisterDomainScript(nnsHash, expectedHash, domain)
if ok || err != nil { if ok || err != nil {
return err return err
@ -204,13 +138,4 @@ func nnsRegisterDomain(c *InitializeContext, nnsHash, expectedHash util.Uint160,
return c.SendCommitteeTx(w.Bytes(), true) 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") var errMissingNNSRecord = errors.New("missing NNS record")

View file

@ -28,7 +28,7 @@ const (
registerBatchSize = transaction.MaxAttributes - 1 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) regPrice, err := getCandidateRegisterPrice(c)
if err != nil { if err != nil {
return fmt.Errorf("can't fetch registration price: %w", err) 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) 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")) cc, err := unwrap.Array(c.ReadOnlyInvoker.Call(neo.Hash, "getCandidates"))
if err != nil { if err != nil {
return fmt.Errorf("`getCandidates`: %w", err) return fmt.Errorf("`getCandidates`: %w", err)
@ -115,7 +115,7 @@ func registerCandidates(c *InitializeContext) error {
return nil return nil
} }
func transferNEOToAlphabetContracts(c *InitializeContext) error { func transferNEOToAlphabetContracts(c *morphUtil.InitializeContext) error {
neoHash := neo.Hash neoHash := neo.Hash
ok, err := transferNEOFinished(c, neoHash) ok, err := transferNEOFinished(c, neoHash)
@ -141,7 +141,7 @@ func transferNEOToAlphabetContracts(c *InitializeContext) error {
return c.AwaitTx() 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) r := nep17.NewReader(c.ReadOnlyInvoker, neoHash)
bal, err := r.BalanceOf(c.CommitteeAcc.Contract.ScriptHash()) bal, err := r.BalanceOf(c.CommitteeAcc.Contract.ScriptHash())
return bal.Cmp(big.NewInt(native.NEOTotalSupply)) == -1, err 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") var errGetPriceInvalid = errors.New("`getRegisterPrice`: invalid response")
func getCandidateRegisterPrice(c *InitializeContext) (int64, error) { func getCandidateRegisterPrice(c *morphUtil.InitializeContext) (int64, error) {
switch c.Client.(type) { switch c.Client.(type) {
case *rpcclient.Client: case *rpcclient.Client:
inv := invoker.New(c.Client, nil) inv := invoker.New(c.Client, nil)

View file

@ -9,7 +9,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/vm/emit" "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 ok, err := setRolesFinished(c); ok || err != nil {
if err == nil { if err == nil {
c.Command.Println("Stage 2: already performed.") c.Command.Println("Stage 2: already performed.")
@ -35,7 +35,7 @@ func setNotaryAndAlphabetNodes(c *InitializeContext) error {
return c.AwaitTx() return c.AwaitTx()
} }
func setRolesFinished(c *InitializeContext) (bool, error) { func setRolesFinished(c *util.InitializeContext) (bool, error) {
height, err := c.Client.GetBlockCount() height, err := c.Client.GetBlockCount()
if err != nil { if err != nil {
return false, err return false, err

View file

@ -58,27 +58,27 @@ func testInitialize(t *testing.T, committeeSize int) {
v.Set(util.ProtoConfigPath, filepath.Join(testdataDir, protoFileName)) v.Set(util.ProtoConfigPath, filepath.Join(testdataDir, protoFileName))
// Set to the path or remove the next statement to download from the network. // 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") 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(util.AlphabetWalletsFlag, testdataDir)
v.Set(epochDurationInitFlag, 1) v.Set(util.EpochDurationInitFlag, 1)
v.Set(maxObjectSizeInitFlag, 1024) v.Set(util.MaxObjectSizeInitFlag, 1024)
setTestCredentials(v, committeeSize) setTestCredentials(v, committeeSize)
require.NoError(t, initializeSideChainCmd(initCmd, nil)) require.NoError(t, initializeSideChainCmd(initCmd, nil))
t.Run("force-new-epoch", func(t *testing.T) { 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)) require.NoError(t, forceNewEpochCmd(forceNewEpoch, nil))
}) })
t.Run("set-config", func(t *testing.T) { 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"})) require.NoError(t, setConfigCmd(setConfig, []string{"MaintenanceModeAllowed=true"}))
}) })
t.Run("set-policy", func(t *testing.T) { 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"})) require.NoError(t, setPolicyCmd(setPolicy, []string{"ExecFeeFactor=1"}))
}) })
t.Run("remove-node", func(t *testing.T) { t.Run("remove-node", func(t *testing.T) {
@ -86,7 +86,7 @@ func testInitialize(t *testing.T, committeeSize int) {
require.NoError(t, err) require.NoError(t, err)
pub := hex.EncodeToString(pk.PublicKey().Bytes()) 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})) require.NoError(t, removeNodesCmd(removeNodes, []string{pub}))
}) })
} }

View file

@ -13,7 +13,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo" "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/rpcclient/nep17"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "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/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit" "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/opcode"
@ -28,7 +27,7 @@ const (
initialProxyGASAmount = 50_000 * native.GASFactor initialProxyGASAmount = 50_000 * native.GASFactor
) )
func transferFunds(c *InitializeContext) error { func transferFunds(c *morphUtil.InitializeContext) error {
ok, err := transferFundsFinished(c) ok, err := transferFundsFinished(c)
if ok || err != nil { if ok || err != nil {
if err == nil { if err == nil {
@ -75,7 +74,7 @@ func transferFunds(c *InitializeContext) error {
return c.AwaitTx() return c.AwaitTx()
} }
func transferFundsFinished(c *InitializeContext) (bool, error) { func transferFundsFinished(c *morphUtil.InitializeContext) (bool, error) {
acc := c.Accounts[0] acc := c.Accounts[0]
r := nep17.NewReader(c.ReadOnlyInvoker, gas.Hash) 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 return res.Cmp(big.NewInt(initialAlphabetGASAmount/2)) == 1, err
} }
func (c *InitializeContext) MultiSignAndSend(tx *transaction.Transaction, accType string) error { func transferGASToProxy(c *morphUtil.InitializeContext) 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 {
proxyCs := c.GetContract(morphUtil.ProxyContract) proxyCs := c.GetContract(morphUtil.ProxyContract)
r := nep17.NewReader(c.ReadOnlyInvoker, gas.Hash) r := nep17.NewReader(c.ReadOnlyInvoker, gas.Hash)

View file

@ -3,6 +3,7 @@ package morph
import ( import (
"errors" "errors"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/util"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -10,8 +11,8 @@ import (
func getDefaultNetmapContractConfigMap() map[string]any { func getDefaultNetmapContractConfigMap() map[string]any {
m := make(map[string]any) m := make(map[string]any)
m[netmap.EpochDurationConfig] = viper.GetInt64(epochDurationInitFlag) m[netmap.EpochDurationConfig] = viper.GetInt64(util.EpochDurationInitFlag)
m[netmap.MaxObjectSizeConfig] = viper.GetInt64(maxObjectSizeInitFlag) m[netmap.MaxObjectSizeConfig] = viper.GetInt64(util.MaxObjectSizeInitFlag)
m[netmap.ContainerFeeConfig] = viper.GetInt64(containerFeeInitFlag) m[netmap.ContainerFeeConfig] = viper.GetInt64(containerFeeInitFlag)
m[netmap.ContainerAliasFeeConfig] = viper.GetInt64(containerAliasFeeInitFlag) m[netmap.ContainerAliasFeeConfig] = viper.GetInt64(containerAliasFeeInitFlag)
m[netmap.IrCandidateFeeConfig] = viper.GetInt64(candidateFeeInitFlag) m[netmap.IrCandidateFeeConfig] = viper.GetInt64(candidateFeeInitFlag)

View file

@ -85,7 +85,7 @@ func transferGas(cmd *cobra.Command, acc *wallet.Account, accHash util.Uint160,
return err return err
} }
if err := checkNotaryEnabled(c); err != nil { if err := morphUtil.CheckNotaryEnabled(c); err != nil {
return err return err
} }

View file

@ -25,7 +25,7 @@ const (
) )
func setPolicyCmd(cmd *cobra.Command, args []string) error { func setPolicyCmd(cmd *cobra.Command, args []string) error {
wCtx, err := NewInitializeContext(cmd, viper.GetViper()) wCtx, err := util.NewInitializeContext(cmd, viper.GetViper())
if err != nil { if err != nil {
return fmt.Errorf("can't to initialize context: %w", err) return fmt.Errorf("can't to initialize context: %w", err)
} }

View file

@ -36,7 +36,7 @@ func removeProxyAccount(cmd *cobra.Command, _ []string) {
} }
func processAccount(cmd *cobra.Command, addr util.Uint160, method string) error { 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 { if err != nil {
return fmt.Errorf("can't to initialize context: %w", err) return fmt.Errorf("can't to initialize context: %w", err)
} }

View file

@ -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 { if err != nil {
return fmt.Errorf("can't initialize context: %w", err) return fmt.Errorf("can't initialize context: %w", err)
} }

View file

@ -8,18 +8,14 @@ import (
) )
const ( const (
alphabetSizeFlag = "size" alphabetSizeFlag = "size"
storageWalletFlag = "storage-wallet" storageWalletFlag = "storage-wallet"
storageWalletLabelFlag = "label" storageWalletLabelFlag = "label"
storageGasCLIFlag = "initial-gas" storageGasCLIFlag = "initial-gas"
storageGasConfigFlag = "storage.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)" maxObjectSizeCLIFlag = "max-object-size"
contractsURLFlag = "contracts-url"
contractsURLFlagDesc = "URL to archive with compiled FrostFS contracts"
maxObjectSizeInitFlag = "network.max_object_size"
maxObjectSizeCLIFlag = "max-object-size"
epochDurationInitFlag = "network.epoch_duration"
epochDurationCLIFlag = "epoch-duration" epochDurationCLIFlag = "epoch-duration"
containerFeeInitFlag = "network.fee.container" containerFeeInitFlag = "network.fee.container"
containerAliasFeeInitFlag = "network.fee.container_alias" containerAliasFeeInitFlag = "network.fee.container_alias"
@ -38,7 +34,6 @@ const (
refillGasAmountFlag = "gas" refillGasAmountFlag = "gas"
walletAccountFlag = "account" walletAccountFlag = "account"
notaryDepositTillFlag = "till" notaryDepositTillFlag = "till"
localDumpFlag = "local-dump"
walletAddressFlag = "wallet-address" walletAddressFlag = "wallet-address"
) )
@ -66,8 +61,8 @@ var (
PreRun: func(cmd *cobra.Command, _ []string) { PreRun: func(cmd *cobra.Command, _ []string) {
_ = viper.BindPFlag(util.AlphabetWalletsFlag, cmd.Flags().Lookup(util.AlphabetWalletsFlag)) _ = viper.BindPFlag(util.AlphabetWalletsFlag, cmd.Flags().Lookup(util.AlphabetWalletsFlag))
_ = viper.BindPFlag(util.EndpointFlag, cmd.Flags().Lookup(util.EndpointFlag)) _ = viper.BindPFlag(util.EndpointFlag, cmd.Flags().Lookup(util.EndpointFlag))
_ = viper.BindPFlag(epochDurationInitFlag, cmd.Flags().Lookup(epochDurationCLIFlag)) _ = viper.BindPFlag(util.EpochDurationInitFlag, cmd.Flags().Lookup(epochDurationCLIFlag))
_ = viper.BindPFlag(maxObjectSizeInitFlag, cmd.Flags().Lookup(maxObjectSizeCLIFlag)) _ = viper.BindPFlag(util.MaxObjectSizeInitFlag, cmd.Flags().Lookup(maxObjectSizeCLIFlag))
_ = viper.BindPFlag(homomorphicHashDisabledInitFlag, cmd.Flags().Lookup(homomorphicHashDisabledCLIFlag)) _ = viper.BindPFlag(homomorphicHashDisabledInitFlag, cmd.Flags().Lookup(homomorphicHashDisabledCLIFlag))
_ = viper.BindPFlag(candidateFeeInitFlag, cmd.Flags().Lookup(candidateFeeCLIFlag)) _ = viper.BindPFlag(candidateFeeInitFlag, cmd.Flags().Lookup(candidateFeeCLIFlag))
_ = viper.BindPFlag(containerFeeInitFlag, cmd.Flags().Lookup(containerFeeCLIFlag)) _ = viper.BindPFlag(containerFeeInitFlag, cmd.Flags().Lookup(containerFeeCLIFlag))
@ -365,9 +360,9 @@ func initUpdateContractsCmd() {
RootCmd.AddCommand(updateContractsCmd) RootCmd.AddCommand(updateContractsCmd)
updateContractsCmd.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc) updateContractsCmd.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc)
updateContractsCmd.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc) updateContractsCmd.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc)
updateContractsCmd.Flags().String(contractsInitFlag, "", contractsInitFlagDesc) updateContractsCmd.Flags().String(util.ContractsInitFlag, "", util.ContractsInitFlagDesc)
updateContractsCmd.Flags().String(contractsURLFlag, "", contractsURLFlagDesc) updateContractsCmd.Flags().String(util.ContractsURLFlag, "", util.ContractsURLFlagDesc)
updateContractsCmd.MarkFlagsMutuallyExclusive(contractsInitFlag, contractsURLFlag) updateContractsCmd.MarkFlagsMutuallyExclusive(util.ContractsInitFlag, util.ContractsURLFlag)
} }
func initDumpBalancesCmd() { func initDumpBalancesCmd() {
@ -384,7 +379,7 @@ func initSetConfigCmd() {
setConfig.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc) setConfig.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc)
setConfig.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc) setConfig.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc)
setConfig.Flags().Bool(forceConfigSet, false, "Force setting not well-known configuration key") 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() { func initDumpNetworkConfigCmd() {
@ -402,7 +397,7 @@ func initSetPolicyCmd() {
RootCmd.AddCommand(setPolicy) RootCmd.AddCommand(setPolicy)
setPolicy.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc) setPolicy.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc)
setPolicy.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc) 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() { func initDumpPolicyCmd() {
@ -414,14 +409,14 @@ func initRemoveNodesCmd() {
RootCmd.AddCommand(removeNodes) RootCmd.AddCommand(removeNodes)
removeNodes.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc) removeNodes.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc)
removeNodes.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc) 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() { func initForceNewEpochCmd() {
RootCmd.AddCommand(forceNewEpoch) RootCmd.AddCommand(forceNewEpoch)
forceNewEpoch.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc) forceNewEpoch.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc)
forceNewEpoch.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc) 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() { func initGenerateStorageCmd() {
@ -437,8 +432,8 @@ func initInitCmd() {
RootCmd.AddCommand(initCmd) RootCmd.AddCommand(initCmd)
initCmd.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc) initCmd.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc)
initCmd.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc) initCmd.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc)
initCmd.Flags().String(contractsInitFlag, "", contractsInitFlagDesc) initCmd.Flags().String(util.ContractsInitFlag, "", util.ContractsInitFlagDesc)
initCmd.Flags().String(contractsURLFlag, "", contractsURLFlagDesc) initCmd.Flags().String(util.ContractsURLFlag, "", util.ContractsURLFlagDesc)
initCmd.Flags().Uint(epochDurationCLIFlag, 240, "Amount of side chain blocks in one FrostFS epoch") 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().Uint(maxObjectSizeCLIFlag, 67108864, "Max single object size in bytes")
initCmd.Flags().Bool(homomorphicHashDisabledCLIFlag, false, "Disable object homomorphic hashing") 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(containerFeeCLIFlag, 1000, "Container registration fee")
initCmd.Flags().Uint64(containerAliasFeeCLIFlag, 500, "Container alias fee") initCmd.Flags().Uint64(containerAliasFeeCLIFlag, 500, "Container alias fee")
initCmd.Flags().String(util.ProtoConfigPath, "", "Path to the consensus node configuration") initCmd.Flags().String(util.ProtoConfigPath, "", "Path to the consensus node configuration")
initCmd.Flags().String(localDumpFlag, "", "Path to the blocks dump file") initCmd.Flags().String(util.LocalDumpFlag, "", "Path to the blocks dump file")
initCmd.MarkFlagsMutuallyExclusive(contractsInitFlag, contractsURLFlag) initCmd.MarkFlagsMutuallyExclusive(util.ContractsInitFlag, util.ContractsURLFlag)
} }
func initGenerateAlphabetCmd() { func initGenerateAlphabetCmd() {

View file

@ -3,12 +3,13 @@ package morph
import ( import (
"fmt" "fmt"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/util"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
func updateContracts(cmd *cobra.Command, _ []string) error { func updateContracts(cmd *cobra.Command, _ []string) error {
wCtx, err := NewInitializeContext(cmd, viper.GetViper()) wCtx, err := util.NewInitializeContext(cmd, viper.GetViper())
if err != nil { if err != nil {
return fmt.Errorf("initialization error: %w", err) return fmt.Errorf("initialization error: %w", err)
} }

View file

@ -1,5 +1,7 @@
package util package util
import "time"
const ( const (
ConsensusAccountName = "consensus" ConsensusAccountName = "consensus"
ProtoConfigPath = "protocol" ProtoConfigPath = "protocol"
@ -14,6 +16,13 @@ const (
EndpointFlagShort = "r" EndpointFlagShort = "r"
AlphabetWalletsFlag = "alphabet-wallets" AlphabetWalletsFlag = "alphabet-wallets"
AlphabetWalletsFlagDesc = "Path to alphabet wallets dir" 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" SingleAccountName = "single"
CommitteeAccountName = "committee" CommitteeAccountName = "committee"
@ -28,4 +37,29 @@ const (
NetmapContract = "netmap" NetmapContract = "netmap"
PolicyContract = "policy" PolicyContract = "policy"
ProxyContract = "proxy" 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...)
) )

View file

@ -1,4 +1,4 @@
package morph package util
import ( import (
"context" "context"

View file

@ -7,6 +7,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns" "git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags" "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/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/rpcclient" "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 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
}

View file

@ -1,14 +1,67 @@
package util package util
import ( import (
"encoding/hex"
"encoding/json" "encoding/json"
"fmt" "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/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
"github.com/nspcc-dev/neo-go/pkg/util" "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 { func (cs *ContractState) Parse() error {
nf, err := nef.FileFromBytes(cs.RawNEF) nf, err := nef.FileFromBytes(cs.RawNEF)
if err != nil { if err != nil {
@ -25,10 +78,466 @@ func (cs *ContractState) Parse() error {
return nil return nil
} }
type ContractState struct { func NewInitializeContext(cmd *cobra.Command, v *viper.Viper) (*InitializeContext, error) {
NEF *nef.File walletDir := config.ResolveHomePath(viper.GetString(AlphabetWalletsFlag))
RawNEF []byte wallets, err := GetAlphabetWallets(v, walletDir)
Manifest *manifest.Manifest if err != nil {
RawManifest []byte return nil, err
Hash util.Uint160 }
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
} }

View file

@ -1,10 +1,14 @@
package util package util
import ( import (
"archive/tar"
"compress/gzip"
"errors" "errors"
"fmt" "fmt"
"io"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config" "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/innerring"
@ -84,3 +88,72 @@ func NewActor(c actor.RPCActor, committeeAcc *wallet.Account) (*actor.Actor, err
Account: committeeAcc, 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
}

View file

@ -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)
}