Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add test/util/genesis on v1.x #3520

Merged
merged 1 commit into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions test/util/genesis/accounts.go
Original file line number Diff line number Diff line change
@@ -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
}
115 changes: 115 additions & 0 deletions test/util/genesis/document.go
Original file line number Diff line number Diff line change
@@ -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
}
69 changes: 69 additions & 0 deletions test/util/genesis/files.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading
Loading