Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

test(v1.x): consistent appHash between commits #3522

Merged
merged 5 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 208 additions & 0 deletions app/consistent_apphash_test.go
Original file line number Diff line number Diff line change
@@ -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 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
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 := 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),
}
}
13 changes: 13 additions & 0 deletions test/util/blobfactory/test_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
Expand Down
147 changes: 147 additions & 0 deletions test/util/genesis/accounts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package genesis

import (
"errors"
"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"
)

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
ninabarbakadze marked this conversation as resolved.
Show resolved Hide resolved
type KeyringAccount struct {
Name string
InitialTokens int64
}

func NewKeyringAccounts(initBal int64, names ...string) []KeyringAccount {
accounts := make([]KeyringAccount, len(names))
for i, name := range names {
accounts[i] = KeyringAccount{
Name: name,
InitialTokens: initBal,
}
}
return accounts
}

func (ga *KeyringAccount) ValidateBasic() error {
if ga.Name == "" {
return errors.New("name cannot be empty")
}
if ga.InitialTokens <= 0 {
return errors.New("initial tokens must be positive")
}
return nil
}

type Validator struct {
KeyringAccount
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{
KeyringAccount: KeyringAccount{
Name: name,
InitialTokens: DefaultInitialBalance,
},
Stake: DefaultInitialBalance / 2, // 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.KeyringAccount.ValidateBasic(); err != nil {
return err
}
if v.Stake <= 0 {
return errors.New("stake must be positive")
}
if v.ConsensusKey == nil {
return errors.New("consensus key cannot be empty")
}
if v.Stake > v.InitialTokens {
return errors.New("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(20000)))
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
}
Loading
Loading