diff --git a/app/consistent_apphash_test.go b/app/consistent_apphash_test.go new file mode 100644 index 0000000000..44c4272964 --- /dev/null +++ b/app/consistent_apphash_test.go @@ -0,0 +1,208 @@ +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" + 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/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/crypto/types" + 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" + coretypes "github.com/tendermint/tendermint/types" +) + +type SdkTx struct { + sdkMsgs []sdk.Msg + txOptions []user.TxOption +} + +type BlobTx struct { + author string + blobs []*tmproto.Blob + txOptions []user.TxOption +} + +// 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 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) + + 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.SetupDeterministicGenesisState(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 + 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) + } + + // 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 := SdkTx{ + sdkMsgs: []sdk.Msg{ + banktypes.NewMsgSend(signer.Account(accountNames[0]).Address(), + signer.Account(accountNames[1]).Address(), + amount), + }, + txOptions: blobfactory.DefaultTxOpts(), + } + + // Create a Blob Tx + blobTx := BlobTx{ + author: accountNames[2], + blobs: []*tmproto.Blob{New(fixedNamespace(), []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 + appHash := testApp.LastCommitID().Hash + + // Require that the app hash is equal to the app hash produced on a different commit + require.Equal(t, expectedAppHash, appHash) +} + +// 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) { + 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..a62b009f43 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 { diff --git a/test/util/genesis/document.go b/test/util/genesis/document.go index 4c18840dc1..6973025ea5 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 +} 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 560d20291d..a886e58195 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" @@ -45,12 +44,27 @@ 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) g := &Genesis{ ecfg: ecfg, - ConsensusParams: DefaultConsensusParams(), + ConsensusParams: app.DefaultConsensusParams(), ChainID: tmrand.Str(6), GenesisTime: time.Now(), kr: keyring.NewInMemory(ecfg.Codec), @@ -59,29 +73,34 @@ 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.AddValidator(val) + err := g.NewValidator(val) if err != nil { panic(err) } @@ -89,9 +108,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,32 +120,53 @@ 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) +// 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()) { + return fmt.Errorf("account with pubkey %s already exists", account.PubKey.String()) + } } + g.accounts = append(g.accounts, account) + 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 } - _, _, 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 } +// 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.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 { @@ -137,36 +179,19 @@ func (g *Genesis) AddValidator(val Validator) error { return nil } -func (g *Genesis) Accounts() []Account { - return g.accounts +// 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 + } + + return g.AddValidator(val) } +// Export returns the genesis document of the network. 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 { @@ -181,20 +206,11 @@ func (g *Genesis) Export() (*coretypes.GenesisDoc, error) { g.ConsensusParams, g.ChainID, gentxs, - addrs, - pubKeys, + g.accounts, 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) { @@ -203,10 +219,3 @@ func (g *Genesis) Validator(i int) (Validator, bool) { } 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/test_app.go b/test/util/test_app.go index efa8e478fe..7371d43e5e 100644 --- a/test/util/test_app.go +++ b/test/util/test_app.go @@ -28,11 +28,16 @@ import ( tmtypes "github.com/tendermint/tendermint/types" 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" + tmversion "github.com/tendermint/tendermint/proto/tendermint/version" ) const ( - ChainID = "testapp" + ChainID = "test-app" ) // Get flags every time the simulator is run @@ -40,6 +45,106 @@ 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{} + 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, + ) +} + +// 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 genesis + for _, pk := range pubKeys { + err := gen.AddAccount(genesis.Account{ + PubKey: pk, + Balance: balance, + }) + if err != nil { + return nil, nil, err + } + } + + // Add validator to genesis + err := AddDeterministicValidatorToGenesis(gen) + if err != nil { + return nil, nil, fmt.Errorf("failed to add validator: %w", err) + } + + genDoc, err := gen.Export() + if err != nil { + return nil, nil, fmt.Errorf("failed to export genesis doc: %w", err) + } + + // Initialise test app against genesis + 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 + 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 @@ -105,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