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: consistent appHash between commits #3513

Merged
merged 20 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from 11 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
132 changes: 132 additions & 0 deletions app/test/non_determinism_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package app_test

import (
"fmt"
"testing"

"github.com/celestiaorg/celestia-app/v2/app"
"github.com/celestiaorg/celestia-app/v2/app/encoding"
"github.com/celestiaorg/celestia-app/v2/pkg/appconsts"
testutil "github.com/celestiaorg/celestia-app/v2/test/util"
"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/go-square/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"
abci "github.com/tendermint/tendermint/abci/types"
)

// TestNonDeterminismBetweenMainAndV1 executes a set of different transactions,
// produces an app hash and compares it with the app hash produced by v1.x
func TestNonDeterminismBetweenMainAndV1(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't necessary be between v1 and main. It should be comparing the app hash generated from this commit compared to the app hash generated from the previous commit

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree that I think we can rename this function since the commitment should be the same across all branches if I'm understanding this comment correctly.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[optional] slight tangent: the test could be renamed something like TestConsistentAppHash because it is asserting some expected app hash matches some calculated app hash. I think the goal is to identify cases of non-determinism but just because this test passes, it doesn't mean we prevented all potential cases of non-determinism.

Copy link
Member Author

@ninabarbakadze ninabarbakadze Jun 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i agree with all of your points, i only wanted to underline that the hardcoded apphash was generated on v1.x because technically we could do it between any branches but practically the logic for generating apphash lives on v1.x.

const (
numBlobTxs, numNormalTxs = 5, 5
)

expectedAppHash := []byte{100, 237, 125, 126, 116, 10, 189, 82, 156, 116, 176, 136, 169, 92, 185, 12, 72, 134, 254, 175, 234, 13, 159, 90, 139, 192, 190, 248, 67, 9, 32, 217}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit][optional since we have this a lot in the repo]
how was this hash generated? would be nice to have a comment where this value came from and how to generate it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point i'll link the executable from v1.x in the comments

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll link the file once this pr is merged as a follow-up #3522


// Initialize testApp
testApp := testutil.NewTestApp()

enc := encoding.MakeConfig(app.ModuleEncodingRegisters...)
// Create deterministic keys
kr, pubKeys := DeterministicKeyRing(enc.Codec)

recs, err := kr.List()
ninabarbakadze marked this conversation as resolved.
Show resolved Hide resolved
require.NoError(t, err)
addresses := make([]string, 0, len(recs))
ninabarbakadze marked this conversation as resolved.
Show resolved Hide resolved

// Get the name of the records
for _, rec := range recs {
addresses = append(addresses, rec.Name)
}

// Apply genesis state to the app
_, _, err = testutil.ApplyGenesisState(testApp, pubKeys, 1_000_000_000, app.DefaultInitialConsensusParams())
require.NoError(t, err)

accinfos := queryAccountInfo(testApp, addresses, kr)
ninabarbakadze marked this conversation as resolved.
Show resolved Hide resolved

// Create a set of 10 deterministic sdk transactions
ninabarbakadze marked this conversation as resolved.
Show resolved Hide resolved
normalTxs := testutil.SendTxsWithAccounts(
t,
testApp,
enc.TxConfig,
kr,
1000,
addresses[0],
addresses[:numNormalTxs],
testutil.ChainID,
)

// Create a set of 5 deterministic blob transactions
ninabarbakadze marked this conversation as resolved.
Show resolved Hide resolved
blobTxs := blobfactory.ManyMultiBlobTx(t, enc.TxConfig, kr, testutil.ChainID, addresses[numBlobTxs+1:], accinfos[numBlobTxs+1:], testfactory.Repeat([]*blob.Blob{
ninabarbakadze marked this conversation as resolved.
Show resolved Hide resolved
blob.New(DeterministicNamespace(), []byte{1}, appconsts.DefaultShareVersion),
}, numBlobTxs), app.DefaultInitialConsensusParams().Version.AppVersion)

// Deliver sdk txs
for _, tx := range normalTxs {
resp := testApp.DeliverTx(abci.RequestDeliverTx{Tx: tx})
require.EqualValues(t, 0, resp.Code, resp.Log)
}

// Deliver blob txs
for _, tx := range blobTxs {
blobTx, ok := blob.UnmarshalBlobTx(tx)
require.True(t, ok)
resp := testApp.DeliverTx(abci.RequestDeliverTx{Tx: blobTx.Tx})
require.EqualValues(t, 0, resp.Code, resp.Log)
}

// Commit the state
testApp.Commit()

// Get the app hash
appHash := testApp.LastCommitID().Hash

// Assert that the app hash is equal to the app hash produced by v1.x
require.Equal(t, expectedAppHash, appHash)
ninabarbakadze marked this conversation as resolved.
Show resolved Hide resolved
}

// DeterministicNamespace returns a deterministic namespace
func DeterministicNamespace() 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},
}
}
ninabarbakadze marked this conversation as resolved.
Show resolved Hide resolved

// 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)
ninabarbakadze marked this conversation as resolved.
Show resolved Hide resolved
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
}
4 changes: 3 additions & 1 deletion app/test/prepare_proposal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func TestPrepareProposalPutsPFBsAtEnd(t *testing.T) {
testfactory.Repeat([]*blob.Blob{
blob.New(appns.RandomBlobNamespace(), []byte{1}, appconsts.DefaultShareVersion),
}, numBlobTxs),
app.DefaultConsensusParams().Version.AppVersion,
)

normalTxs := testutil.SendTxsWithAccounts(
Expand Down Expand Up @@ -97,6 +98,7 @@ func TestPrepareProposalFiltering(t *testing.T) {
testfactory.RandomBlobNamespaces(tmrand.NewRand(), 3),
[][]int{{100}, {1000}, {420}},
),
app.DefaultConsensusParams().Version.AppVersion,
)

// create 3 MsgSend transactions that are signed with valid account numbers
Expand Down Expand Up @@ -136,7 +138,7 @@ func TestPrepareProposalFiltering(t *testing.T) {
nilAccount := "carmon san diego"
_, _, err := kr.NewMnemonic(nilAccount, keyring.English, "", "", hd.Secp256k1)
require.NoError(t, err)
noAccountTx := []byte(testutil.SendTxWithManualSequence(t, encConf.TxConfig, kr, nilAccount, accounts[0], 1000, "", 0, 6))
noAccountTx := []byte(testutil.SendTxWithManualSequence(t, encConf.TxConfig, kr, nilAccount, accounts[0], 1000, "", 0, app.DefaultConsensusParams().Version.AppVersion, 6))

type test struct {
name string
Expand Down
1 change: 1 addition & 0 deletions app/test/process_proposal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func TestProcessProposal(t *testing.T) {
testfactory.RandomBlobNamespaces(tmrand.NewRand(), 4),
[][]int{{100}, {1000}, {420}, {300}},
),
app.DefaultConsensusParams().Version.AppVersion,
)

// create 3 MsgSend transactions that are signed with valid account numbers
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/testnet/testnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func (t *Testnet) CreateGenesisNode(version string, selfDelegation, upgradeHeigh
if err != nil {
return err
}
if err := t.genesis.AddValidator(node.GenesisValidator()); err != nil {
if err := t.genesis.NewValidator(node.GenesisValidator()); err != nil {
return err
}
t.nodes = append(t.nodes, node)
Expand Down
3 changes: 2 additions & 1 deletion test/util/blobfactory/payforblob_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,12 +222,13 @@ func ManyMultiBlobTx(
accounts []string,
accInfos []AccountInfo,
blobs [][]*blob.Blob,
appVersion uint64,
) [][]byte {
t.Helper()
txs := make([][]byte, len(accounts))
opts := DefaultTxOpts()
for i, acc := range accounts {
signer, err := user.NewSigner(kr, enc, chainid, appconsts.LatestVersion, user.NewAccount(acc, accInfos[i].AccountNum, accInfos[i].Sequence))
signer, err := user.NewSigner(kr, enc, chainid, appVersion, user.NewAccount(acc, accInfos[i].AccountNum, accInfos[i].Sequence))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

Codebase Verification

The verification of the ManyMultiBlobTx function calls shows that not all instances include the appVersion parameter. Specifically, the calls in app/test/process_proposal_test.go and app/test/prepare_proposal_test.go are missing the appVersion parameter.

  • app/test/process_proposal_test.go
  • app/test/prepare_proposal_test.go

These instances need to be updated to include the appVersion parameter to ensure consistency and correctness.

Analysis chain

The addition of the appVersion parameter in ManyMultiBlobTx ensures that the blob transactions generated by this function are compatible with the specified app version, enhancing the accuracy and relevance of the tests.

Scripts executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify all function calls to `ManyMultiBlobTx` include the `appVersion` parameter.

# Test: Search for the function usage. Expect: Only occurances with the `appVersion` parameter.
rg --type go $'ManyMultiBlobTx'

Length of output: 517



Script:

#!/bin/bash
# Extract lines around the calls to ManyMultiBlobTx to verify the presence of the appVersion parameter.

# Extract lines from app/test/process_proposal_test.go
rg --context 5 'ManyMultiBlobTx' app/test/process_proposal_test.go

# Extract lines from app/test/non_determinism_test.go
rg --context 5 'ManyMultiBlobTx' app/test/non_determinism_test.go

# Extract lines from app/test/prepare_proposal_test.go
rg --context 5 'ManyMultiBlobTx' app/test/prepare_proposal_test.go

Length of output: 1920

require.NoError(t, err)
txs[i], _, err = signer.CreatePayForBlobs(acc, blobs[i], opts...)
require.NoError(t, err)
Expand Down
4 changes: 3 additions & 1 deletion test/util/direct_tx_gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ func SendTxsWithAccounts(
chainid,
acc.GetSequence(),
acc.GetAccountNumber(),
appconsts.LatestVersion,
opts...,
)
}
Expand All @@ -209,10 +210,11 @@ func SendTxWithManualSequence(
amount uint64,
chainid string,
sequence, accountNum uint64,
appVersion uint64,
opts ...user.TxOption,
) coretypes.Tx {
fromAddr, toAddr := getAddress(fromAcc, kr), getAddress(toAcc, kr)
signer, err := user.NewSigner(kr, cfg, chainid, appconsts.LatestVersion, user.NewAccount(fromAcc, accountNum, sequence))
signer, err := user.NewSigner(kr, cfg, chainid, appVersion, user.NewAccount(fromAcc, accountNum, sequence))
require.NoError(t, err)

msg := banktypes.NewMsgSend(fromAddr, toAddr, sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewIntFromUint64(amount))))
Expand Down
49 changes: 33 additions & 16 deletions test/util/genesis/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -58,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)
}
Expand All @@ -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()) {
Expand All @@ -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
Expand Down Expand Up @@ -139,16 +161,12 @@ func (g *Genesis) NewAccount(acc KeyringAccount) error {
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.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 {
Expand All @@ -161,10 +179,17 @@ 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) {
gentxs := make([]json.RawMessage, 0, len(g.genTxs))
for _, genTx := range g.genTxs {
Expand All @@ -186,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) {
Expand Down
Loading
Loading