From af9ecdfe3d06978416e3227109db72d806818371 Mon Sep 17 00:00:00 2001 From: Nina Barbakadze Date: Fri, 7 Jun 2024 17:14:02 +0200 Subject: [PATCH 1/4] 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) +} From a1262daa6bcdc60552bca36d18492b88295a854f Mon Sep 17 00:00:00 2001 From: Nina Barbakadze Date: Fri, 7 Jun 2024 17:51:41 +0200 Subject: [PATCH 2/4] refactor: update after removing bp and refactor non determinism test; --- app/apphash_test.go | 212 +++++++++++++++++++++++++++++ test/util/blobfactory/test_util.go | 13 ++ test/util/genesis/accounts.go | 42 +++--- test/util/genesis/document.go | 69 ++++++---- test/util/genesis/files.go | 2 +- test/util/genesis/genesis.go | 130 +++++++++++------- test/util/genesis/modifier.go | 2 +- test/util/genesis/util.go | 2 +- test/util/test_app.go | 116 +++++++++++++++- 9 files changed, 490 insertions(+), 98 deletions(-) create mode 100644 app/apphash_test.go diff --git a/app/apphash_test.go b/app/apphash_test.go new file mode 100644 index 0000000000..fdd10ebddd --- /dev/null +++ b/app/apphash_test.go @@ -0,0 +1,212 @@ +package app_test + +import ( + "fmt" + "testing" + + "github.com/celestiaorg/celestia-app/app" + "github.com/celestiaorg/celestia-app/app/encoding" + "github.com/celestiaorg/celestia-app/pkg/appconsts" + testutil "github.com/celestiaorg/celestia-app/test/util" + "github.com/celestiaorg/celestia-app/test/util/blobfactory" + + // "github.com/celestiaorg/celestia-app/v2/test/util/blobfactory" + // "github.com/celestiaorg/celestia-app/v2/test/util/testfactory" + // "github.com/celestiaorg/go-square/blob" + appns "github.com/celestiaorg/celestia-app/pkg/namespace" + "github.com/cosmos/cosmos-sdk/codec" + hd "github.com/cosmos/cosmos-sdk/crypto/hd" + keyring "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/stretchr/testify/require" + + "github.com/celestiaorg/celestia-app/pkg/user" + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + abci "github.com/tendermint/tendermint/abci/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "github.com/tendermint/tendermint/proto/tendermint/version" + coretypes "github.com/tendermint/tendermint/types" +) + +type sdkTxStruct struct { + sdkMsgs []sdk.Msg + txOptions []user.TxOption +} + +type blobTxStruct struct { + author string + blobs []*tmproto.Blob + txOptions []user.TxOption +} + +// TestConsistentAppHash executes a set of txs, generating an app hash, +// and compares it against a previously generated hash from the same set of transactions. +// App hashes across different commits should be consistent. +func TestConsistentAppHash(t *testing.T) { + // App hash produced on a different commit + originalAppHash := []byte{9, 208, 117, 101, 108, 61, 146, 58, 26, 190, 199, 124, 76, 178, 84, 74, 54, 159, 76, 187, 2, 169, 128, 87, 70, 78, 8, 192, 28, 144, 116, 117} + + // Initialize testApp + testApp := testutil.NewTestApp() + + enc := encoding.MakeConfig(app.ModuleEncodingRegisters...) + // Create deterministic keys + kr, pubKeys := DeterministicKeyRing(enc.Codec) + + recs, err := kr.List() + require.NoError(t, err) + accountNames := make([]string, 0, len(recs)) + + // Get the name of the records + for _, rec := range recs { + accountNames = append(accountNames, rec.Name) + } + + // Apply genesis state to the app. + _, _, err = testutil.ApplyGenesisState(testApp, pubKeys, 1_000_000_000, app.DefaultInitialConsensusParams()) + require.NoError(t, err) + + // Query keyring account infos + accountInfos := queryAccountInfo(testApp, accountNames, kr) + + // Create accounts for the signer + var accounts []*user.Account + for i, accountInfo := range accountInfos { + account := user.NewAccount(accountNames[i], accountInfo.AccountNum, accountInfo.Sequence) + accounts = append(accounts, account) + } + + // Create a signer with keyring accounts + signer, err := user.NewTxSigner(kr, enc.TxConfig, testutil.ChainID, appconsts.LatestVersion, accounts...) + require.NoError(t, err) + + amount := sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewIntFromUint64(1000))) + + // Create an SDK Tx + sdkTx := sdkTxStruct{ + sdkMsgs: []sdk.Msg{ + banktypes.NewMsgSend(signer.Account(accountNames[0]).Address(), + signer.Account(accountNames[1]).Address(), + amount)}, + txOptions: blobfactory.DefaultTxOpts(), + } + + // Create a Blob Tx + blobTx := blobTxStruct{ + author: accountNames[2], + blobs: []*tmproto.Blob{New(Namespace(), []byte{1}, appconsts.DefaultShareVersion)}, + txOptions: blobfactory.DefaultTxOpts(), + } + + // Create SDK Tx + rawSdkTx, err := signer.CreateTx(sdkTx.sdkMsgs, sdkTx.txOptions...) + require.NoError(t, err) + + // Create Blob Tx + rawBlobTx, _, err := signer.CreatePayForBlobs(blobTx.author, blobTx.blobs, blobTx.txOptions...) + require.NoError(t, err) + + // BeginBlock + header := tmproto.Header{ + Version: version.Consensus{App: 1}, + Height: testApp.LastBlockHeight() + 1, + } + testApp.BeginBlock(abci.RequestBeginBlock{Header: header}) + + // Deliver SDK Tx + resp := testApp.DeliverTx(abci.RequestDeliverTx{Tx: rawSdkTx}) + require.EqualValues(t, 0, resp.Code, resp.Log) + + // Deliver Blob Tx + blob, isBlobTx := coretypes.UnmarshalBlobTx(rawBlobTx) + require.True(t, isBlobTx) + resp = testApp.DeliverTx(abci.RequestDeliverTx{Tx: blob.Tx}) + require.EqualValues(t, 0, resp.Code, resp.Log) + + // EndBlock + testApp.EndBlock(abci.RequestEndBlock{Height: header.Height}) + + // Commit the state + testApp.Commit() + + // Get the app hash + currentAppHash := testApp.LastCommitID().Hash + + // Require that the current app hash is equal to the one produced on a different commit + require.Equal(t, originalAppHash, currentAppHash) +} + +// DeterministicNamespace returns a deterministic namespace +func Namespace() appns.Namespace { + return appns.Namespace{ + Version: 0, + ID: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 67, 154, 200, 228, 130, 74, 147, 162, 11}, + } +} + +// DeterministicKeyRing returns a deterministic keyring and a list of deterministic public keys +func DeterministicKeyRing(cdc codec.Codec) (keyring.Keyring, []types.PubKey) { + mnemonics := []string{ + "great myself congress genuine scale muscle view uncover pipe miracle sausage broccoli lonely swap table foam brand turtle comic gorilla firm mad grunt hazard", + "cheap job month trigger flush cactus chest juice dolphin people limit crunch curious secret object beach shield snake hunt group sketch cousin puppy fox", + "oil suffer bamboo one better attack exist dolphin relief enforce cat asset raccoon lava regret found love certain plunge grocery accuse goat together kiss", + "giraffe busy subject doll jump drama sea daring again club spend toe mind organ real liar permit refuse change opinion donkey job cricket speed", + "fee vapor thing fish fan memory negative raven cram win quantum ozone job mirror shoot sting quiz black apart funny sort cancel friend curtain", + "skin beef review pilot tooth act any alarm there only kick uniform ticket material cereal radar ethics list unlock method coral smooth street frequent", + "ecology scout core guard load oil school effort near alcohol fancy save cereal owner enforce impact sand husband trophy solve amount fish festival sell", + "used describe angle twin amateur pyramid bitter pool fluid wing erode rival wife federal curious drink battle put elbow mandate another token reveal tone", + "reason fork target chimney lift typical fine divorce mixture web robot kiwi traffic stove miss crane welcome camp bless fuel october riot pluck ordinary", + "undo logic mobile modify master force donor rose crumble forget plate job canal waste turn damp sure point deposit hazard quantum car annual churn", + "charge subway treat loop donate place loan want grief leg message siren joy road exclude match empty enforce vote meadow enlist vintage wool involve", + } + kb := keyring.NewInMemory(cdc) + pubKeys := make([]types.PubKey, len(mnemonics)) + for idx, mnemonic := range mnemonics { + rec, err := kb.NewAccount(fmt.Sprintf("account-%d", idx), mnemonic, "", "", hd.Secp256k1) + if err != nil { + panic(err) + } + pubKey, err := rec.GetPubKey() + if err != nil { + panic(err) + } + pubKeys[idx] = pubKey + } + return kb, pubKeys +} + +func getAddress(account string, kr keyring.Keyring) sdk.AccAddress { + rec, err := kr.Key(account) + if err != nil { + panic(err) + } + addr, err := rec.GetAddress() + if err != nil { + panic(err) + } + return addr +} + +func queryAccountInfo(capp *app.App, accs []string, kr keyring.Keyring) []blobfactory.AccountInfo { + infos := make([]blobfactory.AccountInfo, len(accs)) + for i, acc := range accs { + addr := getAddress(acc, kr) + accI := testutil.DirectQueryAccount(capp, addr) + infos[i] = blobfactory.AccountInfo{ + AccountNum: accI.GetAccountNumber(), + Sequence: accI.GetSequence(), + } + } + return infos +} + +// New creates a new tmproto.Blob from the provided data +func New(ns appns.Namespace, blob []byte, shareVersion uint8) *tmproto.Blob { + return &tmproto.Blob{ + NamespaceId: ns.ID, + Data: blob, + ShareVersion: uint32(shareVersion), + NamespaceVersion: uint32(ns.Version), + } +} diff --git a/test/util/blobfactory/test_util.go b/test/util/blobfactory/test_util.go index 8406e51907..99f4f79fed 100644 --- a/test/util/blobfactory/test_util.go +++ b/test/util/blobfactory/test_util.go @@ -2,6 +2,7 @@ package blobfactory import ( "github.com/celestiaorg/celestia-app/pkg/appconsts" + "github.com/celestiaorg/celestia-app/pkg/user" "github.com/celestiaorg/celestia-app/test/util/testfactory" blobtypes "github.com/celestiaorg/celestia-app/x/blob/types" "github.com/cosmos/cosmos-sdk/client" @@ -11,6 +12,18 @@ import ( coretypes "github.com/tendermint/tendermint/types" ) +func DefaultTxOpts() []user.TxOption { + return FeeTxOpts(10_000_000) +} + +func FeeTxOpts(gas uint64) []user.TxOption { + fee := uint64(float64(gas)*appconsts.DefaultMinGasPrice) + 1 + return []user.TxOption{ + user.SetFee(fee), + user.SetGasLimit(gas), + } +} + func GenerateManyRawSendTxs(txConfig client.TxConfig, count int) []coretypes.Tx { const acc = "signer" kr := testfactory.GenerateKeyring(acc) diff --git a/test/util/genesis/accounts.go b/test/util/genesis/accounts.go index 6605949443..7e10cc573d 100644 --- a/test/util/genesis/accounts.go +++ b/test/util/genesis/accounts.go @@ -1,6 +1,7 @@ package genesis import ( + "errors" "fmt" mrand "math/rand" "time" @@ -15,15 +16,22 @@ import ( "github.com/tendermint/tendermint/crypto" ) -type Account struct { +const ( + DefaultInitialBalance = 1e15 // 1 billion TIA +) + +// KeyringAccount represents a user account on the Celestia network. +// Either the name, if using the genesis keyring, or an address +// needs to be provided +type KeyringAccount struct { Name string InitialTokens int64 } -func NewAccounts(initBal int64, names ...string) []Account { - accounts := make([]Account, len(names)) +func NewKeyringAccounts(initBal int64, names ...string) []KeyringAccount { + accounts := make([]KeyringAccount, len(names)) for i, name := range names { - accounts[i] = Account{ + accounts[i] = KeyringAccount{ Name: name, InitialTokens: initBal, } @@ -31,18 +39,18 @@ func NewAccounts(initBal int64, names ...string) []Account { return accounts } -func (ga *Account) ValidateBasic() error { +func (ga *KeyringAccount) ValidateBasic() error { if ga.Name == "" { - return fmt.Errorf("name cannot be empty") + return errors.New("name cannot be empty") } if ga.InitialTokens <= 0 { - return fmt.Errorf("initial tokens must be positive") + return errors.New("initial tokens must be positive") } return nil } type Validator struct { - Account + KeyringAccount Stake int64 // ConsensusKey is the key used by the validator to sign votes. @@ -53,11 +61,11 @@ type Validator struct { func NewDefaultValidator(name string) Validator { r := mrand.New(mrand.NewSource(time.Now().UnixNano())) return Validator{ - Account: Account{ + KeyringAccount: KeyringAccount{ Name: name, - InitialTokens: 999_999_999_999_999_999, + InitialTokens: DefaultInitialBalance, }, - Stake: 99_999_999_999_999_999, // save some tokens for fees + Stake: DefaultInitialBalance / 2, // save some tokens for fees ConsensusKey: GenerateEd25519(NewSeed(r)), NetworkKey: GenerateEd25519(NewSeed(r)), } @@ -65,17 +73,17 @@ func NewDefaultValidator(name string) Validator { // ValidateBasic performs stateless validation on the validitor func (v *Validator) ValidateBasic() error { - if err := v.Account.ValidateBasic(); err != nil { + if err := v.KeyringAccount.ValidateBasic(); err != nil { return err } if v.Stake <= 0 { - return fmt.Errorf("stake must be positive") + return errors.New("stake must be positive") } if v.ConsensusKey == nil { - return fmt.Errorf("consensus key cannot be empty") + return errors.New("consensus key cannot be empty") } if v.Stake > v.InitialTokens { - return fmt.Errorf("stake cannot be greater than initial tokens") + return errors.New("stake cannot be greater than initial tokens") } return nil } @@ -115,7 +123,7 @@ func (v *Validator) GenTx(ecfg encoding.Config, kr keyring.Keyring, chainID stri return nil, err } - fee := sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewInt(1))) + fee := sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewInt(20000))) txBuilder := ecfg.TxConfig.NewTxBuilder() err = txBuilder.SetMsgs(createValMsg) if err != nil { @@ -136,4 +144,4 @@ func (v *Validator) GenTx(ecfg encoding.Config, kr keyring.Keyring, chainID stri } return txBuilder.GetTx(), nil -} +} \ No newline at end of file diff --git a/test/util/genesis/document.go b/test/util/genesis/document.go index 4c18840dc1..4f455346c8 100644 --- a/test/util/genesis/document.go +++ b/test/util/genesis/document.go @@ -23,26 +23,25 @@ func Document( params *tmproto.ConsensusParams, chainID string, gentxs []json.RawMessage, - addrs []string, - pubkeys []cryptotypes.PubKey, + accounts []Account, mods ...Modifier, ) (*coretypes.GenesisDoc, error) { genutilGenState := genutiltypes.DefaultGenesisState() genutilGenState.GenTxs = gentxs - genBals, genAccs, err := accountsToSDKTypes(addrs, pubkeys) + genBals, genAccs, err := accountsToSDKTypes(accounts) if err != nil { - return nil, err + return nil, fmt.Errorf("converting accounts into sdk types: %w", err) } - accounts, err := authtypes.PackAccounts(genAccs) + sdkAccounts, err := authtypes.PackAccounts(genAccs) if err != nil { - return nil, err + return nil, fmt.Errorf("packing accounts: %w", err) } authGenState := authtypes.DefaultGenesisState() bankGenState := banktypes.DefaultGenesisState() - authGenState.Accounts = append(authGenState.Accounts, accounts...) + authGenState.Accounts = append(authGenState.Accounts, sdkAccounts...) bankGenState.Balances = append(bankGenState.Balances, genBals...) bankGenState.Balances = banktypes.SanitizeGenesisBalances(bankGenState.Balances) @@ -58,17 +57,18 @@ func Document( } 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) + for _, modifier := range mods { + state = modifier(state) } stateBz, err := json.MarshalIndent(state, "", " ") if err != nil { - return nil, err + return nil, fmt.Errorf("marshalling genesis state: %w", err) } // Create the genesis doc @@ -83,33 +83,42 @@ func Document( } // 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] { +func accountsToSDKTypes(accounts []Account) ([]banktypes.Balance, []authtypes.GenesisAccount, error) { + genBals := make([]banktypes.Balance, len(accounts)) + genAccs := make([]authtypes.GenesisAccount, len(accounts)) + hasMap := make(map[string]struct{}) + for i, account := range accounts { + if err := account.ValidateBasic(); err != nil { + return nil, nil, fmt.Errorf("invalid account %d: %v", i, err) + } + addr := sdk.AccAddress(account.PubKey.Address()) + if _, ok := hasMap[addr.String()]; ok { return nil, nil, fmt.Errorf("duplicate account address %s", addr) } - hasMap[addr] = true - - pubKey := pubkeys[i] + hasMap[addr.String()] = struct{}{} balances := sdk.NewCoins( - sdk.NewCoin(appconsts.BondDenom, sdk.NewInt(999_999_999_999_999_999)), + sdk.NewCoin(appconsts.BondDenom, sdk.NewInt(account.Balance)), ) - genBals[i] = banktypes.Balance{Address: addr, Coins: balances.Sort()} + genBals[i] = banktypes.Balance{Address: addr.String(), Coins: balances.Sort()} - parsedAddress, err := sdk.AccAddressFromBech32(addr) - if err != nil { - return nil, nil, err - } - - genAccs[i] = authtypes.NewBaseAccount(parsedAddress, pubKey, uint64(i), 0) + genAccs[i] = authtypes.NewBaseAccount(addr, account.PubKey, uint64(i), 0) } return genBals, genAccs, nil } + +type Account struct { + PubKey cryptotypes.PubKey + Balance int64 +} + +func (ga Account) ValidateBasic() error { + if ga.PubKey == nil { + return fmt.Errorf("pubkey cannot be empty") + } + if ga.Balance <= 0 { + return fmt.Errorf("balance must be greater than 0") + } + return nil +} \ No newline at end of file diff --git a/test/util/genesis/files.go b/test/util/genesis/files.go index 3272b46537..e87fb8195a 100644 --- a/test/util/genesis/files.go +++ b/test/util/genesis/files.go @@ -66,4 +66,4 @@ func InitFiles( } return basePath, nil -} +} \ No newline at end of file diff --git a/test/util/genesis/genesis.go b/test/util/genesis/genesis.go index 560d20291d..01484c4358 100644 --- a/test/util/genesis/genesis.go +++ b/test/util/genesis/genesis.go @@ -1,16 +1,15 @@ package genesis import ( + "bytes" "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" @@ -50,7 +49,7 @@ func NewDefaultGenesis() *Genesis { ecfg := encoding.MakeConfig(app.ModuleBasics) g := &Genesis{ ecfg: ecfg, - ConsensusParams: DefaultConsensusParams(), + ConsensusParams: app.DefaultConsensusParams(), ChainID: tmrand.Str(6), GenesisTime: time.Now(), kr: keyring.NewInMemory(ecfg.Codec), @@ -81,7 +80,7 @@ func (g *Genesis) WithGenesisTime(genesisTime time.Time) *Genesis { func (g *Genesis) WithValidators(vals ...Validator) *Genesis { for _, val := range vals { - err := g.AddValidator(val) + err := g.NewValidator(val) if err != nil { panic(err) } @@ -89,9 +88,11 @@ func (g *Genesis) WithValidators(vals ...Validator) *Genesis { return g } -func (g *Genesis) WithAccounts(accs ...Account) *Genesis { +// WithKeyringAccounts adds the given keyring accounts to the genesis. If an +// account with the same name already exists, it panics. +func (g *Genesis) WithKeyringAccounts(accs ...KeyringAccount) *Genesis { for _, acc := range accs { - err := g.AddAccount(acc) + err := g.NewAccount(acc) if err != nil { panic(err) } @@ -99,29 +100,52 @@ func (g *Genesis) WithAccounts(accs ...Account) *Genesis { 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) +func (g *Genesis) AddAccount(account Account) error { + for _, acc := range g.accounts { + if bytes.Equal(acc.PubKey.Bytes(), account.PubKey.Bytes()) { + return fmt.Errorf("account with pubkey %s already exists", account.PubKey.String()) + } } + g.accounts = append(g.accounts, account) + return nil +} + +func (g *Genesis) NewAccount(acc KeyringAccount) error { if err := acc.ValidateBasic(); err != nil { return err } - _, _, err = g.kr.NewMnemonic(acc.Name, keyring.English, "", "", hd.Secp256k1) + // check that the account does not already exist + if _, err := g.kr.Key(acc.Name); err == nil { + return fmt.Errorf("account with name %s already exists", acc.Name) + } + + // generate the keys and add it to the genesis keyring + record, _, err := g.kr.NewMnemonic(acc.Name, keyring.English, "", "", hd.Secp256k1) + if err != nil { + return err + } + + pubKey, err := record.GetPubKey() if err != nil { return err } - g.accounts = append(g.accounts, acc) + + account := Account{ + PubKey: pubKey, + Balance: acc.InitialTokens, + } + + g.accounts = append(g.accounts, account) return nil } -func (g *Genesis) AddValidator(val Validator) error { +func (g *Genesis) NewValidator(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 { + if err := g.NewAccount(val.KeyringAccount); err != nil { return err } @@ -137,36 +161,56 @@ func (g *Genesis) AddValidator(val Validator) error { 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)) +// TODO improve this function to imply that we're just adding one validator to make it deterministic +func (g *Genesis) AddValidator(val Validator) error { + mnemo := "body world north giggle crop reduce height copper damp next verify orphan lens loan adjust inform utility theory now ranch motion opinion crowd fun" + rec, err := g.kr.NewAccount("validator1", mnemo, "", "", hd.Secp256k1) + if err != nil { + return err + } + validatorPubKey, err := rec.GetPubKey() + if err != nil { + return err + } - for _, acc := range g.Accounts() { - rec, err := g.kr.Key(acc.Name) - if err != nil { - return nil, err - } + if err := val.ValidateBasic(); err != nil { + return err + } - addr, err := rec.GetAddress() - if err != nil { - return nil, err - } + // make account from keyring account + account := Account{ + PubKey: validatorPubKey, + Balance: val.KeyringAccount.InitialTokens, + } - addrs = append(addrs, addr.String()) + if err := g.AddAccount(account); err != nil { + return err + } - pubK, err := rec.GetPubKey() - if err != nil { - return nil, err - } + // TODO decide on this + // add validator to genesis keyring + // if _, err := g.kr.Key(val.Name); err == nil { + // return fmt.Errorf("validator with name %s already exists", val.Name) + // } - pubKeys = append(pubKeys, pubK) + // // 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) { + gentxs := make([]json.RawMessage, 0, len(g.genTxs)) for _, genTx := range g.genTxs { bz, err := g.ecfg.TxConfig.TxJSONEncoder()(genTx) if err != nil { @@ -181,8 +225,7 @@ func (g *Genesis) Export() (*coretypes.GenesisDoc, error) { g.ConsensusParams, g.ChainID, gentxs, - addrs, - pubKeys, + g.accounts, g.genOps..., ) } @@ -202,11 +245,4 @@ func (g *Genesis) Validator(i int) (Validator, bool) { 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 -} +} \ No newline at end of file diff --git a/test/util/genesis/modifier.go b/test/util/genesis/modifier.go index 9ffe274d11..2cb7e83c67 100644 --- a/test/util/genesis/modifier.go +++ b/test/util/genesis/modifier.go @@ -88,4 +88,4 @@ func FundAccounts(codec codec.Codec, addresses []sdk.AccAddress, balance sdk.Coi state[banktypes.ModuleName] = codec.MustMarshalJSON(&bankGenState) return state } -} +} \ No newline at end of file diff --git a/test/util/genesis/util.go b/test/util/genesis/util.go index 42d11288d9..6fb5f0429a 100644 --- a/test/util/genesis/util.go +++ b/test/util/genesis/util.go @@ -20,4 +20,4 @@ func NewSeed(r *mrand.Rand) []byte { func GenerateEd25519(seed []byte) crypto.PrivKey { return ed25519.GenPrivKeyFromSecret(seed) -} +} \ No newline at end of file diff --git a/test/util/test_app.go b/test/util/test_app.go index efa8e478fe..e206b93876 100644 --- a/test/util/test_app.go +++ b/test/util/test_app.go @@ -28,11 +28,15 @@ import ( tmtypes "github.com/tendermint/tendermint/types" dbm "github.com/tendermint/tm-db" + "github.com/celestiaorg/celestia-app/test/util/genesis" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/tendermint/tendermint/crypto/ed25519" + tmversion "github.com/tendermint/tendermint/proto/tendermint/version" ) const ( - ChainID = "testapp" + ChainID = "test-app" ) // Get flags every time the simulator is run @@ -40,6 +44,116 @@ func init() { simapp.GetSimulatorFlags() } +type EmptyAppOptions struct{} + +// Get implements AppOptions +func (ao EmptyAppOptions) Get(_ string) interface{} { + return nil +} + +// NewTestApp initializes a new app with a no-op logger and in-memory database. +func NewTestApp() *app.App { + // EmptyAppOptions is a stub implementing AppOptions + emptyOpts := EmptyAppOptions{} + // var anteOpt = func(bapp *baseapp.BaseApp) { bapp.SetAnteHandler(nil) } + db := dbm.NewMemDB() + + encCfg := encoding.MakeConfig(app.ModuleEncodingRegisters...) + + return app.New( + log.NewNopLogger(), + db, + nil, + true, + nil, + "", + cast.ToUint(emptyOpts.Get(server.FlagInvCheckPeriod)), + encCfg, + emptyOpts, + ) +} + +// ApplyGenesisState applies genesis with a specified state on initialized testApp. +func ApplyGenesisState(testApp *app.App, pubKeys []cryptotypes.PubKey, balance int64, cparams *tmproto.ConsensusParams) (keyring.Keyring, []genesis.Account, error) { + // Create genesis + gen := genesis.NewDefaultGenesis().WithChainID(ChainID).WithConsensusParams(cparams).WithGenesisTime(time.Date(2023, 1, 1, 1, 1, 1, 1, time.UTC).UTC()) + + // Add accounts to the genesis state + for _, pk := range pubKeys { + err := gen.AddAccount(genesis.Account{ + PubKey: pk, + Balance: balance, + }) + if err != nil { + return nil, nil, err + } + } + + // Hardcoding keys in order to make validator creation deterministic + consensusKey := ed25519.PrivKey(ed25519.GenPrivKeyFromSecret([]byte("12345678901234567890123456389012"))) + networkKey := ed25519.PrivKey(ed25519.GenPrivKeyFromSecret([]byte("12345678901234567890123456786012"))) + + // Add validator to genesis + err := gen.AddValidator(genesis.Validator{ + KeyringAccount: genesis.KeyringAccount{ + Name: "validator1", + InitialTokens: 1_000_000_000, + }, + Stake: 1_000_000, + ConsensusKey: consensusKey, + NetworkKey: networkKey, + }) + if err != nil { + return nil, nil, err + } + + genDoc, err := gen.Export() + if err != nil { + return nil, nil, err + } + + testApp.Info(abci.RequestInfo{}) + + abciParams := &abci.ConsensusParams{ + Block: &abci.BlockParams{ + // choose some value large enough to not bottleneck the max square + // size + MaxBytes: int64(appconsts.DefaultSquareSizeUpperBound*appconsts.DefaultSquareSizeUpperBound) * appconsts.ContinuationSparseShareContentSize, + MaxGas: cparams.Block.MaxGas, + }, + Evidence: &cparams.Evidence, + Validator: &cparams.Validator, + Version: &cparams.Version, + } + + // Init chain will set the validator set and initialize the genesis accounts + // TODO: Understand why genDoc.GenesisTime is getting reset + testApp.InitChain( + abci.RequestInitChain{ + Time: gen.GenesisTime, + Validators: []abci.ValidatorUpdate{}, + ConsensusParams: abciParams, + AppStateBytes: genDoc.AppState, + ChainId: genDoc.ChainID, + }, + ) + + // Commit genesis changes + testApp.Commit() + testApp.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{ + ChainID: ChainID, + Height: testApp.LastBlockHeight() + 1, + AppHash: testApp.LastCommitID().Hash, + ValidatorsHash: genDoc.ValidatorHash(), + NextValidatorsHash: genDoc.ValidatorHash(), + Version: tmversion.Consensus{ + App: cparams.Version.AppVersion, + }, + }}) + + return gen.Keyring(), gen.Accounts(), nil +} + // SetupTestAppWithGenesisValSet initializes a new app with a validator set and // genesis accounts that also act as delegators. For simplicity, each validator // is bonded with a delegation of one consensus engine unit in the default token From 563ab6a4db2b4f5a9835b72056d6793f468208c6 Mon Sep 17 00:00:00 2001 From: Nina Barbakadze Date: Fri, 7 Jun 2024 18:35:38 +0200 Subject: [PATCH 3/4] style: cleanup and lint --- ...ash_test.go => consistent_apphash_test.go} | 20 ++++++++----------- test/util/genesis/accounts.go | 2 +- test/util/genesis/document.go | 2 +- test/util/genesis/files.go | 2 +- test/util/genesis/genesis.go | 2 +- test/util/genesis/modifier.go | 2 +- test/util/genesis/util.go | 2 +- test/util/test_app.go | 4 +--- 8 files changed, 15 insertions(+), 21 deletions(-) rename app/{apphash_test.go => consistent_apphash_test.go} (95%) diff --git a/app/apphash_test.go b/app/consistent_apphash_test.go similarity index 95% rename from app/apphash_test.go rename to app/consistent_apphash_test.go index fdd10ebddd..0e1efd92f6 100644 --- a/app/apphash_test.go +++ b/app/consistent_apphash_test.go @@ -7,22 +7,17 @@ import ( "github.com/celestiaorg/celestia-app/app" "github.com/celestiaorg/celestia-app/app/encoding" "github.com/celestiaorg/celestia-app/pkg/appconsts" + appns "github.com/celestiaorg/celestia-app/pkg/namespace" + "github.com/celestiaorg/celestia-app/pkg/user" testutil "github.com/celestiaorg/celestia-app/test/util" "github.com/celestiaorg/celestia-app/test/util/blobfactory" - - // "github.com/celestiaorg/celestia-app/v2/test/util/blobfactory" - // "github.com/celestiaorg/celestia-app/v2/test/util/testfactory" - // "github.com/celestiaorg/go-square/blob" - appns "github.com/celestiaorg/celestia-app/pkg/namespace" "github.com/cosmos/cosmos-sdk/codec" - hd "github.com/cosmos/cosmos-sdk/crypto/hd" - keyring "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/stretchr/testify/require" - - "github.com/celestiaorg/celestia-app/pkg/user" sdk "github.com/cosmos/cosmos-sdk/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/proto/tendermint/version" @@ -71,7 +66,7 @@ func TestConsistentAppHash(t *testing.T) { accountInfos := queryAccountInfo(testApp, accountNames, kr) // Create accounts for the signer - var accounts []*user.Account + accounts := make([]*user.Account, 0, len(accountInfos)) for i, accountInfo := range accountInfos { account := user.NewAccount(accountNames[i], accountInfo.AccountNum, accountInfo.Sequence) accounts = append(accounts, account) @@ -88,7 +83,8 @@ func TestConsistentAppHash(t *testing.T) { sdkMsgs: []sdk.Msg{ banktypes.NewMsgSend(signer.Account(accountNames[0]).Address(), signer.Account(accountNames[1]).Address(), - amount)}, + amount), + }, txOptions: blobfactory.DefaultTxOpts(), } diff --git a/test/util/genesis/accounts.go b/test/util/genesis/accounts.go index 7e10cc573d..a62b009f43 100644 --- a/test/util/genesis/accounts.go +++ b/test/util/genesis/accounts.go @@ -144,4 +144,4 @@ func (v *Validator) GenTx(ecfg encoding.Config, kr keyring.Keyring, chainID stri } return txBuilder.GetTx(), nil -} \ No newline at end of file +} diff --git a/test/util/genesis/document.go b/test/util/genesis/document.go index 4f455346c8..6973025ea5 100644 --- a/test/util/genesis/document.go +++ b/test/util/genesis/document.go @@ -121,4 +121,4 @@ func (ga Account) ValidateBasic() error { return fmt.Errorf("balance must be greater than 0") } return nil -} \ No newline at end of file +} diff --git a/test/util/genesis/files.go b/test/util/genesis/files.go index e87fb8195a..3272b46537 100644 --- a/test/util/genesis/files.go +++ b/test/util/genesis/files.go @@ -66,4 +66,4 @@ func InitFiles( } return basePath, nil -} \ No newline at end of file +} diff --git a/test/util/genesis/genesis.go b/test/util/genesis/genesis.go index 01484c4358..7c4562008f 100644 --- a/test/util/genesis/genesis.go +++ b/test/util/genesis/genesis.go @@ -245,4 +245,4 @@ func (g *Genesis) Validator(i int) (Validator, bool) { return g.validators[i], true } return Validator{}, false -} \ No newline at end of file +} diff --git a/test/util/genesis/modifier.go b/test/util/genesis/modifier.go index 2cb7e83c67..9ffe274d11 100644 --- a/test/util/genesis/modifier.go +++ b/test/util/genesis/modifier.go @@ -88,4 +88,4 @@ func FundAccounts(codec codec.Codec, addresses []sdk.AccAddress, balance sdk.Coi state[banktypes.ModuleName] = codec.MustMarshalJSON(&bankGenState) return state } -} \ No newline at end of file +} diff --git a/test/util/genesis/util.go b/test/util/genesis/util.go index 6fb5f0429a..42d11288d9 100644 --- a/test/util/genesis/util.go +++ b/test/util/genesis/util.go @@ -20,4 +20,4 @@ func NewSeed(r *mrand.Rand) []byte { func GenerateEd25519(seed []byte) crypto.PrivKey { return ed25519.GenPrivKeyFromSecret(seed) -} \ No newline at end of file +} diff --git a/test/util/test_app.go b/test/util/test_app.go index e206b93876..c9db1eb710 100644 --- a/test/util/test_app.go +++ b/test/util/test_app.go @@ -55,7 +55,6 @@ func (ao EmptyAppOptions) Get(_ string) interface{} { func NewTestApp() *app.App { // EmptyAppOptions is a stub implementing AppOptions emptyOpts := EmptyAppOptions{} - // var anteOpt = func(bapp *baseapp.BaseApp) { bapp.SetAnteHandler(nil) } db := dbm.NewMemDB() encCfg := encoding.MakeConfig(app.ModuleEncodingRegisters...) @@ -75,7 +74,7 @@ func NewTestApp() *app.App { // ApplyGenesisState applies genesis with a specified state on initialized testApp. func ApplyGenesisState(testApp *app.App, pubKeys []cryptotypes.PubKey, balance int64, cparams *tmproto.ConsensusParams) (keyring.Keyring, []genesis.Account, error) { - // Create genesis + // Create default genesis state gen := genesis.NewDefaultGenesis().WithChainID(ChainID).WithConsensusParams(cparams).WithGenesisTime(time.Date(2023, 1, 1, 1, 1, 1, 1, time.UTC).UTC()) // Add accounts to the genesis state @@ -127,7 +126,6 @@ func ApplyGenesisState(testApp *app.App, pubKeys []cryptotypes.PubKey, balance i } // Init chain will set the validator set and initialize the genesis accounts - // TODO: Understand why genDoc.GenesisTime is getting reset testApp.InitChain( abci.RequestInitChain{ Time: gen.GenesisTime, From b5afa0875ed53f9fba4fa44b8b7c59434ede484e Mon Sep 17 00:00:00 2001 From: Nina Barbakadze Date: Thu, 20 Jun 2024 13:06:54 +0200 Subject: [PATCH 4/4] refactor: apply the changes i made on main --- app/consistent_apphash_test.go | 34 ++++++------- test/util/genesis/files.go | 2 +- test/util/genesis/genesis.go | 87 ++++++++++++---------------------- test/util/test_app.go | 75 +++++++++++++++++++++-------- 4 files changed, 103 insertions(+), 95 deletions(-) diff --git a/app/consistent_apphash_test.go b/app/consistent_apphash_test.go index 0e1efd92f6..44c4272964 100644 --- a/app/consistent_apphash_test.go +++ b/app/consistent_apphash_test.go @@ -24,30 +24,30 @@ import ( coretypes "github.com/tendermint/tendermint/types" ) -type sdkTxStruct struct { +type SdkTx struct { sdkMsgs []sdk.Msg txOptions []user.TxOption } -type blobTxStruct struct { +type BlobTx struct { author string blobs []*tmproto.Blob txOptions []user.TxOption } -// TestConsistentAppHash executes a set of txs, generating an app hash, +// TestConsistentAppHash executes a set of txs, generates an app hash, // and compares it against a previously generated hash from the same set of transactions. // App hashes across different commits should be consistent. func TestConsistentAppHash(t *testing.T) { - // App hash produced on a different commit - originalAppHash := []byte{9, 208, 117, 101, 108, 61, 146, 58, 26, 190, 199, 124, 76, 178, 84, 74, 54, 159, 76, 187, 2, 169, 128, 87, 70, 78, 8, 192, 28, 144, 116, 117} + // App hash produced from executing txs on this branch + expectedAppHash := []byte{9, 208, 117, 101, 108, 61, 146, 58, 26, 190, 199, 124, 76, 178, 84, 74, 54, 159, 76, 187, 2, 169, 128, 87, 70, 78, 8, 192, 28, 144, 116, 117} // Initialize testApp testApp := testutil.NewTestApp() enc := encoding.MakeConfig(app.ModuleEncodingRegisters...) // Create deterministic keys - kr, pubKeys := DeterministicKeyRing(enc.Codec) + kr, pubKeys := deterministicKeyRing(enc.Codec) recs, err := kr.List() require.NoError(t, err) @@ -59,7 +59,7 @@ func TestConsistentAppHash(t *testing.T) { } // Apply genesis state to the app. - _, _, err = testutil.ApplyGenesisState(testApp, pubKeys, 1_000_000_000, app.DefaultInitialConsensusParams()) + _, _, err = testutil.SetupDeterministicGenesisState(testApp, pubKeys, 1_000_000_000, app.DefaultInitialConsensusParams()) require.NoError(t, err) // Query keyring account infos @@ -79,7 +79,7 @@ func TestConsistentAppHash(t *testing.T) { amount := sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewIntFromUint64(1000))) // Create an SDK Tx - sdkTx := sdkTxStruct{ + sdkTx := SdkTx{ sdkMsgs: []sdk.Msg{ banktypes.NewMsgSend(signer.Account(accountNames[0]).Address(), signer.Account(accountNames[1]).Address(), @@ -89,9 +89,9 @@ func TestConsistentAppHash(t *testing.T) { } // Create a Blob Tx - blobTx := blobTxStruct{ + blobTx := BlobTx{ author: accountNames[2], - blobs: []*tmproto.Blob{New(Namespace(), []byte{1}, appconsts.DefaultShareVersion)}, + blobs: []*tmproto.Blob{New(fixedNamespace(), []byte{1}, appconsts.DefaultShareVersion)}, txOptions: blobfactory.DefaultTxOpts(), } @@ -127,22 +127,22 @@ func TestConsistentAppHash(t *testing.T) { testApp.Commit() // Get the app hash - currentAppHash := testApp.LastCommitID().Hash + appHash := testApp.LastCommitID().Hash - // Require that the current app hash is equal to the one produced on a different commit - require.Equal(t, originalAppHash, currentAppHash) + // Require that the app hash is equal to the app hash produced on a different commit + require.Equal(t, expectedAppHash, appHash) } -// DeterministicNamespace returns a deterministic namespace -func Namespace() appns.Namespace { +// fixedNamespace returns a hardcoded namespace +func fixedNamespace() appns.Namespace { return appns.Namespace{ Version: 0, ID: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 67, 154, 200, 228, 130, 74, 147, 162, 11}, } } -// DeterministicKeyRing returns a deterministic keyring and a list of deterministic public keys -func DeterministicKeyRing(cdc codec.Codec) (keyring.Keyring, []types.PubKey) { +// deterministicKeyRing returns a deterministic keyring and a list of deterministic public keys +func deterministicKeyRing(cdc codec.Codec) (keyring.Keyring, []types.PubKey) { mnemonics := []string{ "great myself congress genuine scale muscle view uncover pipe miracle sausage broccoli lonely swap table foam brand turtle comic gorilla firm mad grunt hazard", "cheap job month trigger flush cactus chest juice dolphin people limit crunch curious secret object beach shield snake hunt group sketch cousin puppy fox", diff --git a/test/util/genesis/files.go b/test/util/genesis/files.go index 3272b46537..c8de7eec03 100644 --- a/test/util/genesis/files.go +++ b/test/util/genesis/files.go @@ -36,7 +36,7 @@ func InitFiles( } gDoc, err := g.Export() if err != nil { - return "", err + return "", fmt.Errorf("exporting genesis: %w", err) } err = gDoc.SaveAs(tmCfg.GenesisFile()) if err != nil { diff --git a/test/util/genesis/genesis.go b/test/util/genesis/genesis.go index 7c4562008f..a886e58195 100644 --- a/test/util/genesis/genesis.go +++ b/test/util/genesis/genesis.go @@ -44,6 +44,21 @@ type Genesis struct { genOps []Modifier } +// Accounts getter +func (g *Genesis) Accounts() []Account { + return g.accounts +} + +// Keyring getter +func (g *Genesis) Keyring() keyring.Keyring { + return g.kr +} + +// Validators getter +func (g *Genesis) Validators() []Validator { + return g.validators +} + // NewDefaultGenesis creates a new default genesis with no accounts or validators. func NewDefaultGenesis() *Genesis { ecfg := encoding.MakeConfig(app.ModuleBasics) @@ -58,26 +73,31 @@ func NewDefaultGenesis() *Genesis { return g } +// WithModifier adds a genesis modifier to the genesis. func (g *Genesis) WithModifiers(ops ...Modifier) *Genesis { g.genOps = append(g.genOps, ops...) return g } +// WithConsensusParams sets the consensus parameters of the genesis. func (g *Genesis) WithConsensusParams(params *tmproto.ConsensusParams) *Genesis { g.ConsensusParams = params return g } +// WithChainID sets the chain ID of the genesis. func (g *Genesis) WithChainID(chainID string) *Genesis { g.ChainID = chainID return g } +// WithGenesisTime sets the genesis time of the genesis. func (g *Genesis) WithGenesisTime(genesisTime time.Time) *Genesis { g.GenesisTime = genesisTime return g } +// WithAccounts adds the given validators to the genesis. func (g *Genesis) WithValidators(vals ...Validator) *Genesis { for _, val := range vals { err := g.NewValidator(val) @@ -100,6 +120,7 @@ func (g *Genesis) WithKeyringAccounts(accs ...KeyringAccount) *Genesis { return g } +// AddAccount adds an existing account to the genesis. func (g *Genesis) AddAccount(account Account) error { for _, acc := range g.accounts { if bytes.Equal(acc.PubKey.Bytes(), account.PubKey.Bytes()) { @@ -110,6 +131,7 @@ func (g *Genesis) AddAccount(account Account) error { return nil } +// NewAccount creates a new account and adds it to the genesis. func (g *Genesis) NewAccount(acc KeyringAccount) error { if err := acc.ValidateBasic(); err != nil { return err @@ -139,16 +161,12 @@ func (g *Genesis) NewAccount(acc KeyringAccount) error { return nil } -func (g *Genesis) NewValidator(val Validator) error { +// AddValidator verifies and adds a given validator to the genesis. +func (g *Genesis) AddValidator(val Validator) error { if err := val.ValidateBasic(); err != nil { return err } - // Add the validator's genesis account - if err := g.NewAccount(val.KeyringAccount); err != nil { - return err - } - // Add the validator's genesis transaction gentx, err := val.GenTx(g.ecfg, g.kr, g.ChainID) if err != nil { @@ -161,54 +179,17 @@ func (g *Genesis) NewValidator(val Validator) error { return nil } -// TODO improve this function to imply that we're just adding one validator to make it deterministic -func (g *Genesis) AddValidator(val Validator) error { - mnemo := "body world north giggle crop reduce height copper damp next verify orphan lens loan adjust inform utility theory now ranch motion opinion crowd fun" - rec, err := g.kr.NewAccount("validator1", mnemo, "", "", hd.Secp256k1) - if err != nil { - return err - } - validatorPubKey, err := rec.GetPubKey() - if err != nil { - return err - } - - if err := val.ValidateBasic(); err != nil { - return err - } - - // make account from keyring account - account := Account{ - PubKey: validatorPubKey, - Balance: val.KeyringAccount.InitialTokens, - } - - if err := g.AddAccount(account); err != nil { - return err - } - - // TODO decide on this - // add validator to genesis keyring - // if _, err := g.kr.Key(val.Name); err == nil { - // return fmt.Errorf("validator with name %s already exists", val.Name) - // } - - // // Add the validator's genesis transaction - gentx, err := val.GenTx(g.ecfg, g.kr, g.ChainID) - if err != nil { +// Creates a new validator account and adds it to the genesis. +func (g *Genesis) NewValidator(val Validator) error { + // Add the validator's genesis account + if err := g.NewAccount(val.KeyringAccount); 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 + return g.AddValidator(val) } +// Export returns the genesis document of the network. func (g *Genesis) Export() (*coretypes.GenesisDoc, error) { gentxs := make([]json.RawMessage, 0, len(g.genTxs)) for _, genTx := range g.genTxs { @@ -230,14 +211,6 @@ func (g *Genesis) Export() (*coretypes.GenesisDoc, error) { ) } -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) { diff --git a/test/util/test_app.go b/test/util/test_app.go index c9db1eb710..7371d43e5e 100644 --- a/test/util/test_app.go +++ b/test/util/test_app.go @@ -29,6 +29,7 @@ import ( dbm "github.com/tendermint/tm-db" "github.com/celestiaorg/celestia-app/test/util/genesis" + "github.com/cosmos/cosmos-sdk/crypto/hd" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/tendermint/tendermint/crypto/ed25519" @@ -72,12 +73,15 @@ func NewTestApp() *app.App { ) } -// ApplyGenesisState applies genesis with a specified state on initialized testApp. -func ApplyGenesisState(testApp *app.App, pubKeys []cryptotypes.PubKey, balance int64, cparams *tmproto.ConsensusParams) (keyring.Keyring, []genesis.Account, error) { - // Create default genesis state - gen := genesis.NewDefaultGenesis().WithChainID(ChainID).WithConsensusParams(cparams).WithGenesisTime(time.Date(2023, 1, 1, 1, 1, 1, 1, time.UTC).UTC()) +// SetupDeterministicGenesisState sets genesis on initialized testApp with the provided arguments. +func SetupDeterministicGenesisState(testApp *app.App, pubKeys []cryptotypes.PubKey, balance int64, cparams *tmproto.ConsensusParams) (keyring.Keyring, []genesis.Account, error) { + // Create genesis + gen := genesis.NewDefaultGenesis(). + WithChainID(ChainID). + WithConsensusParams(cparams). + WithGenesisTime(time.Date(2023, 1, 1, 1, 1, 1, 1, time.UTC).UTC()) - // Add accounts to the genesis state + // Add accounts to genesis for _, pk := range pubKeys { err := gen.AddAccount(genesis.Account{ PubKey: pk, @@ -88,29 +92,18 @@ func ApplyGenesisState(testApp *app.App, pubKeys []cryptotypes.PubKey, balance i } } - // Hardcoding keys in order to make validator creation deterministic - consensusKey := ed25519.PrivKey(ed25519.GenPrivKeyFromSecret([]byte("12345678901234567890123456389012"))) - networkKey := ed25519.PrivKey(ed25519.GenPrivKeyFromSecret([]byte("12345678901234567890123456786012"))) - // Add validator to genesis - err := gen.AddValidator(genesis.Validator{ - KeyringAccount: genesis.KeyringAccount{ - Name: "validator1", - InitialTokens: 1_000_000_000, - }, - Stake: 1_000_000, - ConsensusKey: consensusKey, - NetworkKey: networkKey, - }) + err := AddDeterministicValidatorToGenesis(gen) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("failed to add validator: %w", err) } genDoc, err := gen.Export() if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("failed to export genesis doc: %w", err) } + // Initialise test app against genesis testApp.Info(abci.RequestInfo{}) abciParams := &abci.ConsensusParams{ @@ -217,6 +210,48 @@ func SetupTestAppWithGenesisValSet(cparams *tmproto.ConsensusParams, genAccounts return testApp, kr } +// AddDeterministicValidatorToGenesis adds a single deterministic validator to the genesis. +func AddDeterministicValidatorToGenesis(g *genesis.Genesis) error { + // Hardcoded keys for deterministic account creation + mnemo := "body world north giggle crop reduce height copper damp next verify orphan lens loan adjust inform utility theory now ranch motion opinion crowd fun" + consensusKey := ed25519.GenPrivKeyFromSecret([]byte("12345678901234567890123456389012")) + networkKey := ed25519.GenPrivKeyFromSecret([]byte("12345678901234567890123456786012")) + + val := genesis.Validator{ + KeyringAccount: genesis.KeyringAccount{ + Name: "validator1", + InitialTokens: 1_000_000_000, + }, + Stake: 1_000_000, + ConsensusKey: consensusKey, + NetworkKey: networkKey, + } + + // Initialize the validator's genesis account in the keyring + rec, err := g.Keyring().NewAccount(val.Name, mnemo, "", "", hd.Secp256k1) + if err != nil { + return fmt.Errorf("failed to create account: %w", err) + } + + validatorPubKey, err := rec.GetPubKey() + if err != nil { + return fmt.Errorf("failed to get pubkey: %w", err) + } + + // Make account from keyring account + account := genesis.Account{ + PubKey: validatorPubKey, + Balance: val.KeyringAccount.InitialTokens, + } + + // Add the validator's account to the genesis + if err := g.AddAccount(account); err != nil { + return fmt.Errorf("failed to add account: %w", err) + } + + return g.AddValidator(val) +} + // AddGenesisAccount mimics the cli addGenesisAccount command, providing an // account with an allocation of to "token" and "tia" tokens in the genesis // state