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 app hash on v2 #3879

Merged
merged 12 commits into from
Sep 23, 2024
198 changes: 125 additions & 73 deletions app/test/consistent_apphash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@ import (
"github.com/celestiaorg/celestia-app/v3/app"
"github.com/celestiaorg/celestia-app/v3/app/encoding"
"github.com/celestiaorg/celestia-app/v3/pkg/appconsts"
v1 "github.com/celestiaorg/celestia-app/v3/pkg/appconsts/v1"
v2 "github.com/celestiaorg/celestia-app/v3/pkg/appconsts/v2"
"github.com/celestiaorg/celestia-app/v3/pkg/user"
testutil "github.com/celestiaorg/celestia-app/v3/test/util"
"github.com/celestiaorg/celestia-app/v3/test/util/blobfactory"
"github.com/celestiaorg/celestia-app/v3/test/util/testfactory"
blobtypes "github.com/celestiaorg/celestia-app/v3/x/blob/types"
signal "github.com/celestiaorg/celestia-app/v3/x/signal/types"
"github.com/celestiaorg/go-square/v2/share"
"github.com/celestiaorg/go-square/v2/tx"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
Expand All @@ -35,66 +39,119 @@ import (
"github.com/tendermint/tendermint/proto/tendermint/version"
)

type BlobTx struct {
type blobTx struct {
author string
blobs []*share.Blob
txOptions []user.TxOption
}

// TestConsistentAppHash executes all state machine messages, generates an app hash,
type (
encodedSdkMessages func(*testing.T, []sdk.AccAddress, []stakingtypes.Validator, *app.App, *user.Signer, *user.Signer) ([][]byte, [][]byte, [][]byte)
encodedBlobTxs func(*testing.T, *user.Signer, []sdk.AccAddress) []byte
)

type appHashTest struct {
name string
version uint64
encodedSdkMessages encodedSdkMessages
encodedBlobTxs encodedBlobTxs
expectedDataRoot []byte
expectedAppHash []byte
}

// TestConsistentAppHash executes all state machine messages on all app versions, 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) {
// Expected app hash produced by v1.x - https://github.com/celestiaorg/celestia-app/blob/v1.x/app/consistent_apphash_test.go
expectedAppHash := []byte{84, 216, 210, 48, 113, 204, 234, 21, 150, 236, 97, 87, 242, 184, 45, 248, 116, 127, 49, 88, 134, 197, 202, 125, 44, 210, 67, 144, 107, 51, 145, 65}
expectedDataRoot := []byte{100, 59, 112, 241, 238, 49, 50, 64, 105, 90, 209, 211, 49, 254, 211, 83, 133, 88, 5, 89, 221, 116, 141, 72, 33, 110, 16, 78, 5, 48, 118, 72}

// Initialize testApp
testApp := testutil.NewTestApp()
enc := encoding.MakeConfig(app.ModuleEncodingRegisters...)

// Create deterministic keys
kr, pubKeys := deterministicKeyRing(enc.Codec)

// Apply genesis state to the app.
valKeyRing, _, err := testutil.SetupDeterministicGenesisState(testApp, pubKeys, 20_000_000_000, app.DefaultInitialConsensusParams())
require.NoError(t, err)
tc := []appHashTest{
{
name: "execute sdk messages and blob tx on v1",
version: v1.Version,
encodedSdkMessages: encodedSdkMessagesV1,
encodedBlobTxs: createEncodedBlobTx,
expectedDataRoot: []byte{100, 59, 112, 241, 238, 49, 50, 64, 105, 90, 209, 211, 49, 254, 211, 83, 133, 88, 5, 89, 221, 116, 141, 72, 33, 110, 16, 78, 5, 48, 118, 72},
// Expected app hash produced by v1.x - https://github.com/celestiaorg/celestia-app/blob/v1.x/app/consistent_apphash_test.go
expectedAppHash: []byte{84, 216, 210, 48, 113, 204, 234, 21, 150, 236, 97, 87, 242, 184, 45, 248, 116, 127, 49, 88, 134, 197, 202, 125, 44, 210, 67, 144, 107, 51, 145, 65},
},
{
name: "execute sdk messages and blob tx on v2",
version: v2.Version,
encodedSdkMessages: func(t *testing.T, accountAddresses []sdk.AccAddress, genValidators []stakingtypes.Validator, testApp *app.App, signer *user.Signer, valSigner *user.Signer) ([][]byte, [][]byte, [][]byte) {
firstBlockEncodedTxs, secondBlockEncodedTxs, thirdBlockEncodedTxs := encodedSdkMessagesV1(t, accountAddresses, genValidators, testApp, signer, valSigner)
Copy link
Collaborator

Choose a reason for hiding this comment

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

v2 removed x/blobstream so I think the blobstream transactions need to be removed the test case on v2. Alternatively if they aren't removed, maybe this test case should verify that x/blobstream messages are no-ops on v2.

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 think those messages are supposed to be rejected on v2. I'll investigate.

Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like the blobstream messages were never part of the v1 test suite anyway so I think doing it like this is fine

Copy link
Collaborator

Choose a reason for hiding this comment

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

If the v1 test doesn't have blobstream messages, is it missing any other messages? Do we have a GH issue to make this test exhaustive of all message types supported by each app version?

Copy link
Member Author

Choose a reason for hiding this comment

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

it covers most of it now. the only message we don't execute is MsgSubmitEvidence and IBC messages that require a different setup. Initially, we wanted to cover as much as possible and we're now extending to cover IBC messages too.

encodedMessagesV2 := encodedSdkMessagesV2(t, genValidators, valSigner)
thirdBlockEncodedTxs = append(thirdBlockEncodedTxs, encodedMessagesV2...)

return firstBlockEncodedTxs, secondBlockEncodedTxs, thirdBlockEncodedTxs
},
encodedBlobTxs: createEncodedBlobTx,
expectedDataRoot: []byte{200, 61, 245, 166, 119, 211, 170, 2, 73, 239, 253, 97, 243, 112, 116, 196, 70, 41, 201, 172, 123, 28, 15, 182, 52, 222, 122, 243, 95, 97, 66, 233},
// Expected app hash produced on v2.x - https://github.com/celestiaorg/celestia-app/blob/v2.x/app/test/consistent_apphash_test.go
expectedAppHash: []byte{16, 144, 102, 79, 23, 207, 200, 139, 77, 245, 250, 101, 217, 227, 255, 245, 172, 1, 44, 70, 188, 140, 215, 103, 178, 4, 80, 179, 11, 150, 31, 134},
},
}

// ------------ Genesis User Accounts ------------
for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
testApp := testutil.NewTestApp()
enc := encoding.MakeConfig(app.ModuleEncodingRegisters...)
// Create deterministic keys
kr, pubKeys := deterministicKeyRing(enc.Codec)
consensusParams := app.DefaultConsensusParams()
consensusParams.Version.AppVersion = tt.version
// Apply genesis state to the app.
valKeyRing, _, err := testutil.SetupDeterministicGenesisState(testApp, pubKeys, 20_000_000_000, consensusParams)
require.NoError(t, err)

// Get account names and addresses from the keyring and create signer
signer, accountAddresses := getAccountsAndCreateSigner(t, kr, enc.TxConfig, testutil.ChainID, app.DefaultInitialVersion, testApp)
// Validators from genesis state
genValidators := testApp.StakingKeeper.GetAllValidators(testApp.NewContext(false, tmproto.Header{}))
valSigner, _ := getAccountsAndCreateSigner(t, valKeyRing, enc.TxConfig, testutil.ChainID, app.DefaultInitialVersion, testApp)

// Convert validators to ABCI validators
abciValidators, err := convertToABCIValidators(genValidators)
require.NoError(t, err)

firstBlockTxs, secondBlockTxs, thirdBlockTxs := tt.encodedSdkMessages(t, accountAddresses, genValidators, testApp, signer, valSigner)
encodedBlobTx := tt.encodedBlobTxs(t, signer, accountAddresses)

// Execute the first block
_, firstBlockAppHash, err := executeTxs(testApp, []byte{}, firstBlockTxs, abciValidators, testApp.LastCommitID().Hash, tt.version)
require.NoError(t, err)
// Execute the second block
_, secondBlockAppHash, err := executeTxs(testApp, encodedBlobTx, secondBlockTxs, abciValidators, firstBlockAppHash, tt.version)
require.NoError(t, err)
// Execute the final block and get the data root alongside the final app hash
finalDataRoot, finalAppHash, err := executeTxs(testApp, []byte{}, thirdBlockTxs, abciValidators, secondBlockAppHash, tt.version)
require.NoError(t, err)
fmt.Println(finalDataRoot, finalAppHash, tt.version)

// Require that the app hash is equal to the app hash produced on a different commit
require.Equal(t, tt.expectedAppHash, finalAppHash)
// Require that the data root is equal to the data root produced on a different commit
require.Equal(t, tt.expectedDataRoot, finalDataRoot)
})
}
}

// getAccountsAndCreateSigner returns a signer with accounts
func getAccountsAndCreateSigner(t *testing.T, kr keyring.Keyring, enc client.TxConfig, chainID string, initialVersion uint64, testApp *app.App) (*user.Signer, []sdk.AccAddress) {
// Get account names and addresses from the keyring
accountNames := testfactory.GetAccountNames(kr)
accountAddresses := testfactory.GetAddresses(kr)

// Query keyring account infos
accountInfos := queryAccountInfo(testApp, accountNames, kr)

// Create accounts for the signer
accounts := createAccounts(accountInfos, accountNames)

// Create a signer with accounts
signer, err := user.NewSigner(kr, enc.TxConfig, testutil.ChainID, app.DefaultInitialVersion, accounts...)
require.NoError(t, err)

// ------------ Genesis Validator Accounts ------------

// Validators from genesis state
genValidators := testApp.StakingKeeper.GetAllValidators(testApp.NewContext(false, tmproto.Header{}))

// Get validator account names from the validator keyring
valAccountNames := testfactory.GetAccountNames(valKeyRing)

// Query validator account infos
valAccountInfos := queryAccountInfo(testApp, valAccountNames, valKeyRing)

// Create accounts for the validators' signer
valAccounts := createAccounts(valAccountInfos, valAccountNames)

// Create a signer with validator accounts
valSigner, err := user.NewSigner(valKeyRing, enc.TxConfig, testutil.ChainID, app.DefaultInitialVersion, valAccounts...)
signer, err := user.NewSigner(kr, enc, chainID, initialVersion, accounts...)
require.NoError(t, err)
return signer, accountAddresses
}

// ----------- Create SDK Messages ------------
// encodedSdkMessagesV1 returns encoded SDK messages for v1
func encodedSdkMessagesV1(t *testing.T, accountAddresses []sdk.AccAddress, genValidators []stakingtypes.Validator, testApp *app.App, signer *user.Signer, valSigner *user.Signer) ([][]byte, [][]byte, [][]byte) {
// ----------- Create v1 SDK Messages ------------

amount := sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewIntFromUint64(1_000)))
// Minimum deposit required for a gov proposal to become active
Expand Down Expand Up @@ -255,51 +312,46 @@ func TestConsistentAppHash(t *testing.T) {
msgUnjail := slashingtypes.NewMsgUnjail(genValidators[3].GetOperator())
thirdBlockSdkMsgs = append(thirdBlockSdkMsgs, msgUnjail)

// ------------ Construct Txs ------------

// Create SDK transactions from the list of messages
// and separate them into 3 different blocks
firstBlockEncodedTxs, err := processSdkMessages(signer, firstBlockSdkMsgs)
firstBlockTxs, err := processSdkMessages(signer, firstBlockSdkMsgs)
require.NoError(t, err)

secondBlockEncodedTxs, err := processSdkMessages(signer, secondBlockSdkMsgs)
secondBlockTxs, err := processSdkMessages(signer, secondBlockSdkMsgs)
require.NoError(t, err)
thirdBlockTxs, err := processSdkMessages(valSigner, thirdBlockSdkMsgs)
require.NoError(t, err)

return firstBlockTxs, secondBlockTxs, thirdBlockTxs
}

// encodedSdkMessagesV2 returns encoded SDK messages introduced in v2
func encodedSdkMessagesV2(t *testing.T, genValidators []stakingtypes.Validator, valSigner *user.Signer) [][]byte {
var v2Messages []sdk.Msg
msgTryUpgrade := signal.NewMsgTryUpgrade(sdk.AccAddress(genValidators[0].GetOperator()))
v2Messages = append(v2Messages, msgTryUpgrade)

msgSignalVersion := signal.NewMsgSignalVersion(genValidators[0].GetOperator(), 2)
v2Messages = append(v2Messages, msgSignalVersion)

thirdBlockEncodedTxs, err := processSdkMessages(valSigner, thirdBlockSdkMsgs)
encodedTxs, err := processSdkMessages(valSigner, v2Messages)
require.NoError(t, err)

return encodedTxs
}

// createEncodedBlobTx creates, signs and returns an encoded blob transaction
func createEncodedBlobTx(t *testing.T, signer *user.Signer, accountAddresses []sdk.AccAddress) []byte {
senderAcc := signer.AccountByAddress(accountAddresses[1])
blob, err := share.NewBlob(fixedNamespace(), []byte{1}, appconsts.DefaultShareVersion, nil)
require.NoError(t, err)

// Create a Blob Tx
blobTx := BlobTx{
author: accountNames[1],
blobTx := blobTx{
author: senderAcc.Name(),
blobs: []*share.Blob{blob},
txOptions: blobfactory.DefaultTxOpts(),
}
encodedBlobTx, _, err := signer.CreatePayForBlobs(blobTx.author, blobTx.blobs, blobTx.txOptions...)
require.NoError(t, err)

// Convert validators to ABCI validators
abciValidators, err := convertToABCIValidators(genValidators)
require.NoError(t, err)

// Execute the first block
_, firstBlockAppHash, err := executeTxs(testApp, []byte{}, firstBlockEncodedTxs, abciValidators, testApp.LastCommitID().Hash)
require.NoError(t, err)

// Execute the second block
_, secondBlockAppHash, err := executeTxs(testApp, encodedBlobTx, secondBlockEncodedTxs, abciValidators, firstBlockAppHash)
require.NoError(t, err)

// Execute the final block and get the data root alongside the final app hash
finalDataRoot, finalAppHash, err := executeTxs(testApp, []byte{}, thirdBlockEncodedTxs, abciValidators, secondBlockAppHash)
require.NoError(t, err)

// Require that the app hash is equal to the app hash produced on a different commit
require.Equal(t, expectedAppHash, finalAppHash)
// Require that the data root is equal to the data root produced on a different commit
require.Equal(t, expectedDataRoot, finalDataRoot)
return encodedBlobTx
}

// fixedNamespace returns a hardcoded namespace
Expand Down Expand Up @@ -365,7 +417,7 @@ func processSdkMessages(signer *user.Signer, sdkMessages []sdk.Msg) ([][]byte, e
}

// executeTxs executes a set of transactions and returns the data hash and app hash
func executeTxs(testApp *app.App, encodedBlobTx []byte, encodedSdkTxs [][]byte, validators []abci.Validator, lastCommitHash []byte) ([]byte, []byte, error) {
func executeTxs(testApp *app.App, encodedBlobTx []byte, encodedSdkTxs [][]byte, validators []abci.Validator, lastCommitHash []byte, appVersion uint64) ([]byte, []byte, error) {
height := testApp.LastBlockHeight() + 1
chainID := testApp.GetChainID()

Expand All @@ -388,7 +440,7 @@ func executeTxs(testApp *app.App, encodedBlobTx []byte, encodedSdkTxs [][]byte,
dataHash := resPrepareProposal.BlockData.Hash

header := tmproto.Header{
Version: version.Consensus{App: 1},
Version: version.Consensus{App: appVersion},
ninabarbakadze marked this conversation as resolved.
Show resolved Hide resolved
DataHash: resPrepareProposal.BlockData.Hash,
ChainID: chainID,
Time: genesisTime.Add(time.Duration(height) * time.Minute),
Expand Down
Loading