From af9ecdfe3d06978416e3227109db72d806818371 Mon Sep 17 00:00:00 2001 From: Nina Barbakadze Date: Fri, 7 Jun 2024 17:14:02 +0200 Subject: [PATCH] chore: revert backport and manually add genesis --- test/util/genesis/accounts.go | 139 ++++++++++++++++++++++ test/util/genesis/document.go | 115 ++++++++++++++++++ test/util/genesis/files.go | 69 +++++++++++ test/util/genesis/genesis.go | 212 ++++++++++++++++++++++++++++++++++ test/util/genesis/modifier.go | 91 +++++++++++++++ test/util/genesis/util.go | 23 ++++ 6 files changed, 649 insertions(+) create mode 100644 test/util/genesis/accounts.go create mode 100644 test/util/genesis/document.go create mode 100644 test/util/genesis/files.go create mode 100644 test/util/genesis/genesis.go create mode 100644 test/util/genesis/modifier.go create mode 100644 test/util/genesis/util.go diff --git a/test/util/genesis/accounts.go b/test/util/genesis/accounts.go new file mode 100644 index 0000000000..6605949443 --- /dev/null +++ b/test/util/genesis/accounts.go @@ -0,0 +1,139 @@ +package genesis + +import ( + "fmt" + mrand "math/rand" + "time" + + "github.com/celestiaorg/celestia-app/app" + "github.com/celestiaorg/celestia-app/app/encoding" + "github.com/cosmos/cosmos-sdk/client/tx" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/tendermint/tendermint/crypto" +) + +type Account struct { + Name string + InitialTokens int64 +} + +func NewAccounts(initBal int64, names ...string) []Account { + accounts := make([]Account, len(names)) + for i, name := range names { + accounts[i] = Account{ + Name: name, + InitialTokens: initBal, + } + } + return accounts +} + +func (ga *Account) ValidateBasic() error { + if ga.Name == "" { + return fmt.Errorf("name cannot be empty") + } + if ga.InitialTokens <= 0 { + return fmt.Errorf("initial tokens must be positive") + } + return nil +} + +type Validator struct { + Account + Stake int64 + + // ConsensusKey is the key used by the validator to sign votes. + ConsensusKey crypto.PrivKey + NetworkKey crypto.PrivKey +} + +func NewDefaultValidator(name string) Validator { + r := mrand.New(mrand.NewSource(time.Now().UnixNano())) + return Validator{ + Account: Account{ + Name: name, + InitialTokens: 999_999_999_999_999_999, + }, + Stake: 99_999_999_999_999_999, // save some tokens for fees + ConsensusKey: GenerateEd25519(NewSeed(r)), + NetworkKey: GenerateEd25519(NewSeed(r)), + } +} + +// ValidateBasic performs stateless validation on the validitor +func (v *Validator) ValidateBasic() error { + if err := v.Account.ValidateBasic(); err != nil { + return err + } + if v.Stake <= 0 { + return fmt.Errorf("stake must be positive") + } + if v.ConsensusKey == nil { + return fmt.Errorf("consensus key cannot be empty") + } + if v.Stake > v.InitialTokens { + return fmt.Errorf("stake cannot be greater than initial tokens") + } + return nil +} + +// GenTx generates a genesis transaction to create a validator as configured by +// the validator struct. It assumes the validator's genesis account has already +// been added to the keyring and that the sequence for that account is 0. +func (v *Validator) GenTx(ecfg encoding.Config, kr keyring.Keyring, chainID string) (sdk.Tx, error) { + rec, err := kr.Key(v.Name) + if err != nil { + return nil, err + } + addr, err := rec.GetAddress() + if err != nil { + return nil, err + } + + commission, err := sdk.NewDecFromStr("0.5") + if err != nil { + return nil, err + } + + pk, err := cryptocodec.FromTmPubKeyInterface(v.ConsensusKey.PubKey()) + if err != nil { + return nil, fmt.Errorf("converting public key for node %s: %w", v.Name, err) + } + + createValMsg, err := stakingtypes.NewMsgCreateValidator( + sdk.ValAddress(addr), + pk, + sdk.NewCoin(app.BondDenom, sdk.NewInt(v.Stake)), + stakingtypes.NewDescription(v.Name, "", "", "", ""), + stakingtypes.NewCommissionRates(commission, sdk.OneDec(), sdk.OneDec()), + sdk.NewInt(v.Stake/2), + ) + if err != nil { + return nil, err + } + + fee := sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewInt(1))) + txBuilder := ecfg.TxConfig.NewTxBuilder() + err = txBuilder.SetMsgs(createValMsg) + if err != nil { + return nil, err + } + txBuilder.SetFeeAmount(fee) // Arbitrary fee + txBuilder.SetGasLimit(1000000) // Need at least 100386 + + txFactory := tx.Factory{} + txFactory = txFactory. + WithChainID(chainID). + WithKeybase(kr). + WithTxConfig(ecfg.TxConfig) + + err = tx.Sign(txFactory, v.Name, txBuilder, true) + if err != nil { + return nil, err + } + + return txBuilder.GetTx(), nil +} diff --git a/test/util/genesis/document.go b/test/util/genesis/document.go new file mode 100644 index 0000000000..4c18840dc1 --- /dev/null +++ b/test/util/genesis/document.go @@ -0,0 +1,115 @@ +package genesis + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/celestiaorg/celestia-app/app" + "github.com/celestiaorg/celestia-app/app/encoding" + "github.com/celestiaorg/celestia-app/pkg/appconsts" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + coretypes "github.com/tendermint/tendermint/types" +) + +// Document will create a valid genesis doc with funded addresses. +func Document( + ecfg encoding.Config, + params *tmproto.ConsensusParams, + chainID string, + gentxs []json.RawMessage, + addrs []string, + pubkeys []cryptotypes.PubKey, + mods ...Modifier, +) (*coretypes.GenesisDoc, error) { + genutilGenState := genutiltypes.DefaultGenesisState() + genutilGenState.GenTxs = gentxs + + genBals, genAccs, err := accountsToSDKTypes(addrs, pubkeys) + if err != nil { + return nil, err + } + + accounts, err := authtypes.PackAccounts(genAccs) + if err != nil { + return nil, err + } + + authGenState := authtypes.DefaultGenesisState() + bankGenState := banktypes.DefaultGenesisState() + authGenState.Accounts = append(authGenState.Accounts, accounts...) + bankGenState.Balances = append(bankGenState.Balances, genBals...) + bankGenState.Balances = banktypes.SanitizeGenesisBalances(bankGenState.Balances) + + // perform some basic validation of the genesis state + if err := authtypes.ValidateGenesis(*authGenState); err != nil { + return nil, err + } + if err := bankGenState.Validate(); err != nil { + return nil, err + } + if err := genutiltypes.ValidateGenesis(genutilGenState, ecfg.TxConfig.TxJSONDecoder()); err != nil { + return nil, err + } + + state := app.ModuleBasics.DefaultGenesis(ecfg.Codec) + state[authtypes.ModuleName] = ecfg.Codec.MustMarshalJSON(authGenState) + state[banktypes.ModuleName] = ecfg.Codec.MustMarshalJSON(bankGenState) + state[genutiltypes.ModuleName] = ecfg.Codec.MustMarshalJSON(genutilGenState) + + for _, modifer := range mods { + state = modifer(state) + } + + stateBz, err := json.MarshalIndent(state, "", " ") + if err != nil { + return nil, err + } + + // Create the genesis doc + genesisDoc := &coretypes.GenesisDoc{ + ChainID: chainID, + GenesisTime: time.Now(), + ConsensusParams: params, + AppState: stateBz, + } + + return genesisDoc, nil +} + +// accountsToSDKTypes converts the genesis accounts to native SDK types. +func accountsToSDKTypes(addrs []string, pubkeys []cryptotypes.PubKey) ([]banktypes.Balance, []authtypes.GenesisAccount, error) { + if len(addrs) != len(pubkeys) { + return nil, nil, fmt.Errorf("length of addresses and public keys are not equal") + } + genBals := make([]banktypes.Balance, len(addrs)) + genAccs := make([]authtypes.GenesisAccount, len(addrs)) + hasMap := make(map[string]bool) + for i, addr := range addrs { + if hasMap[addr] { + return nil, nil, fmt.Errorf("duplicate account address %s", addr) + } + hasMap[addr] = true + + pubKey := pubkeys[i] + + balances := sdk.NewCoins( + sdk.NewCoin(appconsts.BondDenom, sdk.NewInt(999_999_999_999_999_999)), + ) + + genBals[i] = banktypes.Balance{Address: addr, Coins: balances.Sort()} + + parsedAddress, err := sdk.AccAddressFromBech32(addr) + if err != nil { + return nil, nil, err + } + + genAccs[i] = authtypes.NewBaseAccount(parsedAddress, pubKey, uint64(i), 0) + } + return genBals, genAccs, nil +} diff --git a/test/util/genesis/files.go b/test/util/genesis/files.go new file mode 100644 index 0000000000..3272b46537 --- /dev/null +++ b/test/util/genesis/files.go @@ -0,0 +1,69 @@ +package genesis + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/tendermint/tendermint/config" + tmos "github.com/tendermint/tendermint/libs/os" + "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/privval" +) + +// InitFiles initializes the files for a new tendermint node with the provided +// genesis. It will use the validatorIndex to save the validator's consensus +// key. +func InitFiles( + dir string, + tmCfg *config.Config, + g *Genesis, + validatorIndex int, +) (string, error) { + val, has := g.Validator(validatorIndex) + if !has { + return "", fmt.Errorf("validator %d not found", validatorIndex) + } + + basePath := filepath.Join(dir, ".celestia-app") + tmCfg.SetRoot(basePath) + + // save the genesis file + configPath := filepath.Join(basePath, "config") + err := os.MkdirAll(configPath, os.ModePerm) + if err != nil { + return "", err + } + gDoc, err := g.Export() + if err != nil { + return "", err + } + err = gDoc.SaveAs(tmCfg.GenesisFile()) + if err != nil { + return "", err + } + + pvStateFile := tmCfg.PrivValidatorStateFile() + if err := tmos.EnsureDir(filepath.Dir(pvStateFile), 0o777); err != nil { + return "", err + } + pvKeyFile := tmCfg.PrivValidatorKeyFile() + if err := tmos.EnsureDir(filepath.Dir(pvKeyFile), 0o777); err != nil { + return "", err + } + filePV := privval.NewFilePV(val.ConsensusKey, pvKeyFile, pvStateFile) + filePV.Save() + + nodeKeyFile := tmCfg.NodeKeyFile() + if err := tmos.EnsureDir(filepath.Dir(nodeKeyFile), 0o777); err != nil { + return "", err + } + nodeKey := &p2p.NodeKey{ + PrivKey: val.NetworkKey, + } + if err := nodeKey.SaveAs(nodeKeyFile); err != nil { + return "", err + } + + return basePath, nil +} diff --git a/test/util/genesis/genesis.go b/test/util/genesis/genesis.go new file mode 100644 index 0000000000..560d20291d --- /dev/null +++ b/test/util/genesis/genesis.go @@ -0,0 +1,212 @@ +package genesis + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/celestiaorg/celestia-app/app" + "github.com/celestiaorg/celestia-app/app/encoding" + "github.com/celestiaorg/celestia-app/pkg/appconsts" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + tmrand "github.com/tendermint/tendermint/libs/rand" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + coretypes "github.com/tendermint/tendermint/types" +) + +// Genesis manages the creation of the genesis state of a network. It is meant +// to be used as the first step to any test that requires a network. +type Genesis struct { + ecfg encoding.Config + // ConsensusParams are the consensus parameters of the network. + ConsensusParams *tmproto.ConsensusParams + // ChainID is the chain ID of the network. + ChainID string + // GenesisTime is the genesis time of the network. + GenesisTime time.Time + + // kr is the keyring used to generate the genesis accounts and validators. + // Transaction keys for all genesis accounts are stored in this keyring and + // are indexed by account name. Public keys and addresses can be derived + // from those keys using the existing keyring API. + kr keyring.Keyring + + // accounts are the genesis accounts that will be included in the genesis. + accounts []Account + // validators are the validators of the network. Note that each validator + // also has a genesis account. + validators []Validator + // genTxs are the genesis transactions that will be included in the genesis. + // Transactions are generated upon adding a validator to the genesis. + genTxs []sdk.Tx + genOps []Modifier +} + +// NewDefaultGenesis creates a new default genesis with no accounts or validators. +func NewDefaultGenesis() *Genesis { + ecfg := encoding.MakeConfig(app.ModuleBasics) + g := &Genesis{ + ecfg: ecfg, + ConsensusParams: DefaultConsensusParams(), + ChainID: tmrand.Str(6), + GenesisTime: time.Now(), + kr: keyring.NewInMemory(ecfg.Codec), + genOps: []Modifier{}, + } + return g +} + +func (g *Genesis) WithModifiers(ops ...Modifier) *Genesis { + g.genOps = append(g.genOps, ops...) + return g +} + +func (g *Genesis) WithConsensusParams(params *tmproto.ConsensusParams) *Genesis { + g.ConsensusParams = params + return g +} + +func (g *Genesis) WithChainID(chainID string) *Genesis { + g.ChainID = chainID + return g +} + +func (g *Genesis) WithGenesisTime(genesisTime time.Time) *Genesis { + g.GenesisTime = genesisTime + return g +} + +func (g *Genesis) WithValidators(vals ...Validator) *Genesis { + for _, val := range vals { + err := g.AddValidator(val) + if err != nil { + panic(err) + } + } + return g +} + +func (g *Genesis) WithAccounts(accs ...Account) *Genesis { + for _, acc := range accs { + err := g.AddAccount(acc) + if err != nil { + panic(err) + } + } + return g +} + +func (g *Genesis) AddAccount(acc Account) error { + _, err := g.kr.Key(acc.Name) + if err == nil { + return fmt.Errorf("account with name %s already exists", acc.Name) + } + if err := acc.ValidateBasic(); err != nil { + return err + } + _, _, err = g.kr.NewMnemonic(acc.Name, keyring.English, "", "", hd.Secp256k1) + if err != nil { + return err + } + g.accounts = append(g.accounts, acc) + return nil +} + +func (g *Genesis) AddValidator(val Validator) error { + if err := val.ValidateBasic(); err != nil { + return err + } + + // Add the validator's genesis account + if err := g.AddAccount(val.Account); err != nil { + return err + } + + // Add the validator's genesis transaction + gentx, err := val.GenTx(g.ecfg, g.kr, g.ChainID) + if err != nil { + return err + } + + // install the validator + g.genTxs = append(g.genTxs, gentx) + g.validators = append(g.validators, val) + return nil +} + +func (g *Genesis) Accounts() []Account { + return g.accounts +} + +func (g *Genesis) Export() (*coretypes.GenesisDoc, error) { + addrs := make([]string, 0, len(g.accounts)) + pubKeys := make([]cryptotypes.PubKey, 0, len(g.accounts)) + gentxs := make([]json.RawMessage, 0, len(g.genTxs)) + + for _, acc := range g.Accounts() { + rec, err := g.kr.Key(acc.Name) + if err != nil { + return nil, err + } + + addr, err := rec.GetAddress() + if err != nil { + return nil, err + } + + addrs = append(addrs, addr.String()) + + pubK, err := rec.GetPubKey() + if err != nil { + return nil, err + } + + pubKeys = append(pubKeys, pubK) + } + + for _, genTx := range g.genTxs { + bz, err := g.ecfg.TxConfig.TxJSONEncoder()(genTx) + if err != nil { + return nil, err + } + + gentxs = append(gentxs, json.RawMessage(bz)) + } + + return Document( + g.ecfg, + g.ConsensusParams, + g.ChainID, + gentxs, + addrs, + pubKeys, + g.genOps..., + ) +} + +func (g *Genesis) Keyring() keyring.Keyring { + return g.kr +} + +func (g *Genesis) Validators() []Validator { + return g.validators +} + +// Validator returns the validator at the given index. False is returned if the +// index is out of bounds. +func (g *Genesis) Validator(i int) (Validator, bool) { + if i < len(g.validators) { + return g.validators[i], true + } + return Validator{}, false +} + +func DefaultConsensusParams() *tmproto.ConsensusParams { + cparams := coretypes.DefaultConsensusParams() + cparams.Block.TimeIotaMs = 1 + cparams.Block.MaxBytes = appconsts.DefaultMaxBytes + return cparams +} diff --git a/test/util/genesis/modifier.go b/test/util/genesis/modifier.go new file mode 100644 index 0000000000..9ffe274d11 --- /dev/null +++ b/test/util/genesis/modifier.go @@ -0,0 +1,91 @@ +package genesis + +import ( + "encoding/json" + "time" + + "github.com/celestiaorg/celestia-app/app" + blobtypes "github.com/celestiaorg/celestia-app/x/blob/types" + qgbtypes "github.com/celestiaorg/celestia-app/x/qgb/types" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" +) + +// Modifier allows for arbitrary changes to be made on the genesis state +// after initial accounts have been added. It accepts the genesis state as input +// and is expected to return the modified genesis as output. +type Modifier func(state map[string]json.RawMessage) map[string]json.RawMessage + +// SetBlobParams will set the provided blob params as genesis state. +func SetBlobParams(codec codec.Codec, params blobtypes.Params) Modifier { + return func(state map[string]json.RawMessage) map[string]json.RawMessage { + blobGenState := blobtypes.DefaultGenesis() + blobGenState.Params = params + state[blobtypes.ModuleName] = codec.MustMarshalJSON(blobGenState) + return state + } +} + +// ImmediateProposals sets the thresholds for getting a gov proposal to very low +// levels. +func ImmediateProposals(codec codec.Codec) Modifier { + return func(state map[string]json.RawMessage) map[string]json.RawMessage { + gs := v1.DefaultGenesisState() + gs.DepositParams.MinDeposit = sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewInt(1))) + gs.TallyParams.Quorum = "0.000001" + gs.TallyParams.Threshold = "0.000001" + vp := time.Second * 5 + gs.VotingParams.VotingPeriod = &vp + state[govtypes.ModuleName] = codec.MustMarshalJSON(gs) + return state + } +} + +// SetDataCommitmentWindow will set the provided data commitment window in the +// qgb module's genesis state. +func SetDataCommitmentWindow(codec codec.Codec, window uint64) Modifier { + return func(state map[string]json.RawMessage) map[string]json.RawMessage { + qgbGenState := qgbtypes.DefaultGenesis() + qgbGenState.Params.DataCommitmentWindow = window + state[qgbtypes.ModuleName] = codec.MustMarshalJSON(qgbGenState) + return state + } +} + +// FundAccounts adds a set of accounts to the genesis and then sets their balance as provided. +// This is good in the case where you have a separate keyring you want to test against and not +// use the one generated by the testnet infra. +func FundAccounts(codec codec.Codec, addresses []sdk.AccAddress, balance sdk.Coin) Modifier { + return func(state map[string]json.RawMessage) map[string]json.RawMessage { + // set the accounts in the genesis state + var authGenState authtypes.GenesisState + codec.MustUnmarshalJSON(state[authtypes.ModuleName], &authGenState) + + genAccounts := make([]authtypes.GenesisAccount, len(addresses)) + genBalances := make([]banktypes.Balance, len(addresses)) + for idx, addr := range addresses { + genAccounts[idx] = authtypes.NewBaseAccount(addr, nil, uint64(idx+len(authGenState.Accounts)), 0) + genBalances[idx] = banktypes.Balance{Address: addr.String(), Coins: sdk.NewCoins(balance)} + } + + accounts, err := authtypes.PackAccounts(genAccounts) + if err != nil { + panic(err) + } + + authGenState.Accounts = append(authGenState.Accounts, accounts...) + state[authtypes.ModuleName] = codec.MustMarshalJSON(&authGenState) + + // set the balances in the genesis state + var bankGenState banktypes.GenesisState + codec.MustUnmarshalJSON(state[banktypes.ModuleName], &bankGenState) + + bankGenState.Balances = append(bankGenState.Balances, genBalances...) + state[banktypes.ModuleName] = codec.MustMarshalJSON(&bankGenState) + return state + } +} diff --git a/test/util/genesis/util.go b/test/util/genesis/util.go new file mode 100644 index 0000000000..42d11288d9 --- /dev/null +++ b/test/util/genesis/util.go @@ -0,0 +1,23 @@ +package genesis + +import ( + "io" + mrand "math/rand" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" +) + +func NewSeed(r *mrand.Rand) []byte { + seed := make([]byte, ed25519.SeedSize) + + _, err := io.ReadFull(r, seed) + if err != nil { + panic(err) // this shouldn't happen + } + return seed +} + +func GenerateEd25519(seed []byte) crypto.PrivKey { + return ed25519.GenPrivKeyFromSecret(seed) +}