diff --git a/app/test/consistent_apphash_test.go b/app/test/consistent_apphash_test.go index c3133ba98c..b31cc168be 100644 --- a/app/test/consistent_apphash_test.go +++ b/app/test/consistent_apphash_test.go @@ -3,6 +3,7 @@ package app_test import ( "fmt" "testing" + "time" "github.com/celestiaorg/celestia-app/v2/app" "github.com/celestiaorg/celestia-app/v2/app/encoding" @@ -10,127 +11,378 @@ import ( "github.com/celestiaorg/celestia-app/v2/pkg/user" 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" + blobtypes "github.com/celestiaorg/celestia-app/v2/x/blob/types" + blobstreamtypes "github.com/celestiaorg/celestia-app/v2/x/blobstream/types" + signal "github.com/celestiaorg/celestia-app/v2/x/signal/types" "github.com/celestiaorg/go-square/blob" appns "github.com/celestiaorg/go-square/namespace" + + // "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" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" + vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + "github.com/cosmos/cosmos-sdk/x/authz" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + crisisTypes "github.com/cosmos/cosmos-sdk/x/crisis/types" + distribution "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/cosmos/cosmos-sdk/x/feegrant" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + gethcommon "github.com/ethereum/go-ethereum/common" "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" ) -type SdkTx struct { - sdkMsgs []sdk.Msg - txOptions []user.TxOption -} - -type BlobTx struct { +type blobTx struct { author string blobs []*blob.Blob txOptions []user.TxOption } -// TestConsistentAppHash executes a set of txs, 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 - TODO: link to the test producing the hash - 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} + tc := []appHashTest{ + { + name: "execute sdk messages and blob tx on v1", + version: 1, + 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: 2, + 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) + encodedMessagesV2 := encodedSdkMessagesV2(t, genValidators, valSigner) + thirdBlockEncodedTxs = append(thirdBlockEncodedTxs, encodedMessagesV2...) - // Initialize testApp - testApp := testutil.NewTestApp() + 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}, + }, + } - enc := encoding.MakeConfig(app.ModuleEncodingRegisters...) - // Create deterministic keys - kr, pubKeys := deterministicKeyRing(enc.Codec) + 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) - recs, err := kr.List() - require.NoError(t, err) - accountNames := make([]string, 0, len(recs)) + // Get account names and addresses from the keyring and create signer + signer, accountAddresses := getAccountsAndCreateSigner(t, kr, enc.TxConfig, testutil.ChainID, tt.version, testApp) + // Validators from genesis state + genValidators := testApp.StakingKeeper.GetAllValidators(testApp.NewContext(false, tmproto.Header{})) + valSigner, _ := getAccountsAndCreateSigner(t, valKeyRing, enc.TxConfig, testutil.ChainID, tt.version, testApp) - // Get the name of the records - for _, rec := range recs { - accountNames = append(accountNames, rec.Name) - } + // Convert validators to ABCI validators + abciValidators, err := convertToABCIValidators(genValidators) + require.NoError(t, err) - // Apply genesis state to the app. - _, _, err = testutil.SetupDeterministicGenesisState(testApp, pubKeys, 1_000_000_000, app.DefaultInitialConsensusParams()) - 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) + require.NoError(t, err) + // Execute the second block + _, secondBlockAppHash, err := executeTxs(testApp, encodedBlobTx, secondBlockTxs, 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{}, thirdBlockTxs, abciValidators, secondBlockAppHash) + require.NoError(t, err) + + fmt.Println(finalDataRoot, "finalDataRoot") + fmt.Println(finalAppHash, "finalAppHash") + // 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, appVersion 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 := make([]*user.Account, 0, len(accountInfos)) - for i, accountInfo := range accountInfos { - account := user.NewAccount(accountNames[i], accountInfo.AccountNum, accountInfo.Sequence) - accounts = append(accounts, account) + accounts := createAccounts(accountInfos, accountNames) + // Create a signer with accounts + signer, err := user.NewSigner(kr, enc, chainID, appVersion, accounts...) + require.NoError(t, err) + return signer, accountAddresses +} + +// 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 + depositAmount := sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewIntFromUint64(10000000000))) + twoInt := sdk.NewInt(2) + + // ---------------- First Block ------------ + var firstBlockSdkMsgs []sdk.Msg + + // NewMsgSend - sends funds from account-0 to account-1 + sendFundsMsg := banktypes.NewMsgSend(accountAddresses[0], accountAddresses[1], amount) + firstBlockSdkMsgs = append(firstBlockSdkMsgs, sendFundsMsg) + + // MultiSend - creates a multi-send transaction from account-0 to account-1 + multiSendFundsMsg := banktypes.NewMsgMultiSend([]banktypes.Input{ + banktypes.NewInput( + accountAddresses[0], + amount, + ), + }, + []banktypes.Output{ + banktypes.NewOutput( + accountAddresses[1], + amount, + ), + }) + firstBlockSdkMsgs = append(firstBlockSdkMsgs, multiSendFundsMsg) + + // NewMsgGrant - grants authorization to account-1 + grantExpiration := time.Date(2026, time.January, 1, 0, 0, 0, 0, time.UTC) + authorization := authz.NewGenericAuthorization(blobtypes.URLMsgPayForBlobs) + msgGrant, err := authz.NewMsgGrant( + accountAddresses[0], + accountAddresses[1], + authorization, + &grantExpiration, + ) + require.NoError(t, err) + firstBlockSdkMsgs = append(firstBlockSdkMsgs, msgGrant) + + // MsgVerifyInvariant - verifies the nonnegative-outstanding invariant within the bank module for the account-0 + msgVerifyInvariant := crisisTypes.NewMsgVerifyInvariant(accountAddresses[0], banktypes.ModuleName, "nonnegative-outstanding") + firstBlockSdkMsgs = append(firstBlockSdkMsgs, msgVerifyInvariant) + + // MsgGrantAllowance - creates a grant allowance for account-1 + basicAllowance := feegrant.BasicAllowance{ + SpendLimit: sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewIntFromUint64(1000))), } + feegrantMsg, err := feegrant.NewMsgGrantAllowance(&basicAllowance, accountAddresses[0], accountAddresses[1]) + require.NoError(t, err) + firstBlockSdkMsgs = append(firstBlockSdkMsgs, feegrantMsg) + + // NewMsgSubmitProposal - submits a proposal to send funds from the governance account to account-1 + govAccount := testApp.GovKeeper.GetGovernanceAccount(testApp.NewContext(false, tmproto.Header{})).GetAddress() + msgSend := banktypes.MsgSend{ + FromAddress: govAccount.String(), + ToAddress: accountAddresses[1].String(), + Amount: amount, + } + proposal, err := govtypes.NewMsgSubmitProposal([]sdk.Msg{&msgSend}, amount, accountAddresses[0].String(), "") + require.NoError(t, err) + firstBlockSdkMsgs = append(firstBlockSdkMsgs, proposal) + + // NewMsgDeposit - deposits funds to a governance proposal + msgDeposit := govtypes.NewMsgDeposit(accountAddresses[0], 1, depositAmount) + firstBlockSdkMsgs = append(firstBlockSdkMsgs, msgDeposit) - // Create a signer with keyring accounts - signer, err := user.NewSigner(kr, enc.TxConfig, testutil.ChainID, app.DefaultInitialVersion, accounts...) + // NewMsgCreateValidator - creates a new validator + msgCreateValidator, err := stakingtypes.NewMsgCreateValidator(sdk.ValAddress(accountAddresses[6]), + ed25519.GenPrivKeyFromSecret([]byte("validator")).PubKey(), + amount[0], + stakingtypes.NewDescription("taco tuesday", "my keybase", "www.celestia.org", "ping @celestiaorg on twitter", "fake validator"), + stakingtypes.NewCommissionRates(sdk.NewDecWithPrec(6, 0o2), sdk.NewDecWithPrec(12, 0o2), sdk.NewDecWithPrec(1, 0o2)), + sdk.OneInt()) require.NoError(t, err) + firstBlockSdkMsgs = append(firstBlockSdkMsgs, msgCreateValidator) + + // NewMsgDelegate - delegates funds to validator-0 + msgDelegate := stakingtypes.NewMsgDelegate(accountAddresses[0], genValidators[0].GetOperator(), amount[0]) + firstBlockSdkMsgs = append(firstBlockSdkMsgs, msgDelegate) + + // NewMsgBeginRedelegate - re-delegates funds from validator-0 to validator-1 + msgBeginRedelegate := stakingtypes.NewMsgBeginRedelegate(accountAddresses[0], genValidators[0].GetOperator(), genValidators[1].GetOperator(), amount[0]) + firstBlockSdkMsgs = append(firstBlockSdkMsgs, msgBeginRedelegate) + + // ------------ Second Block ------------ + + var secondBlockSdkMsgs []sdk.Msg + + // NewMsgVote - votes yes on a governance proposal + msgVote := govtypes.NewMsgVote(accountAddresses[0], 1, govtypes.VoteOption_VOTE_OPTION_YES, "") + secondBlockSdkMsgs = append(secondBlockSdkMsgs, msgVote) + + // NewMsgRevoke - revokes authorization from account-1 + msgRevoke := authz.NewMsgRevoke( + accountAddresses[0], + accountAddresses[1], + blobtypes.URLMsgPayForBlobs, + ) + + // NewMsgExec - executes the revoke authorization message + msgExec := authz.NewMsgExec(accountAddresses[0], []sdk.Msg{&msgRevoke}) + secondBlockSdkMsgs = append(secondBlockSdkMsgs, &msgExec) + + // NewMsgVoteWeighted - votes with a weighted vote + msgVoteWeighted := govtypes.NewMsgVoteWeighted( + accountAddresses[0], + 1, + govtypes.WeightedVoteOptions([]*govtypes.WeightedVoteOption{{Option: govtypes.OptionYes, Weight: "1.0"}}), // Cast the slice to the expected type + "", + ) + secondBlockSdkMsgs = append(secondBlockSdkMsgs, msgVoteWeighted) + + // NewMsgEditValidator - edits the newly created validator's description + msgEditValidator := stakingtypes.NewMsgEditValidator(sdk.ValAddress(accountAddresses[6]), stakingtypes.NewDescription("add", "new", "val", "desc", "."), nil, &twoInt) + secondBlockSdkMsgs = append(secondBlockSdkMsgs, msgEditValidator) + + // NewMsgUndelegate - undelegates funds from validator-1 + msgUndelegate := stakingtypes.NewMsgUndelegate(accountAddresses[0], genValidators[1].GetOperator(), amount[0]) + secondBlockSdkMsgs = append(secondBlockSdkMsgs, msgUndelegate) + + // NewMsgDelegate - delegates funds to validator-0 + msgDelegate = stakingtypes.NewMsgDelegate(accountAddresses[0], genValidators[0].GetOperator(), amount[0]) + secondBlockSdkMsgs = append(secondBlockSdkMsgs, msgDelegate) + + // Block 2 height + blockHeight := testApp.LastBlockHeight() + 2 + // NewMsgCancelUnbondingDelegation - cancels unbonding delegation from validator-1 + msgCancelUnbondingDelegation := stakingtypes.NewMsgCancelUnbondingDelegation(accountAddresses[0], genValidators[1].GetOperator(), blockHeight, amount[0]) + secondBlockSdkMsgs = append(secondBlockSdkMsgs, msgCancelUnbondingDelegation) - amount := sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewIntFromUint64(1000))) + // NewMsgSetWithdrawAddress - sets the withdraw address for account-0 + msgSetWithdrawAddress := distribution.NewMsgSetWithdrawAddress(accountAddresses[0], accountAddresses[1]) + secondBlockSdkMsgs = append(secondBlockSdkMsgs, msgSetWithdrawAddress) - // Create an SDK Tx - sdkTx := SdkTx{ - sdkMsgs: []sdk.Msg{ - banktypes.NewMsgSend(signer.Account(accountNames[0]).Address(), - signer.Account(accountNames[1]).Address(), - amount), + // NewMsgRevokeAllowance - revokes the allowance granted to account-1 + msgRevokeAllowance := feegrant.NewMsgRevokeAllowance(accountAddresses[0], accountAddresses[1]) + secondBlockSdkMsgs = append(secondBlockSdkMsgs, &msgRevokeAllowance) + + // NewMsgFundCommunityPool - funds the community pool + msgFundCommunityPool := distribution.NewMsgFundCommunityPool(amount, accountAddresses[0]) + secondBlockSdkMsgs = append(secondBlockSdkMsgs, msgFundCommunityPool) + + // NewMsgWithdrawDelegatorReward - withdraws delegator rewards + msgWithdrawDelegatorReward := distribution.NewMsgWithdrawDelegatorReward(accountAddresses[0], genValidators[0].GetOperator()) + secondBlockSdkMsgs = append(secondBlockSdkMsgs, msgWithdrawDelegatorReward) + + // NewMsgCreatePeriodicVestingAccount - creates a periodic vesting account + newAddress := sdk.AccAddress(ed25519.GenPrivKeyFromSecret([]byte("anotherAddress")).PubKey().Address()) + vestingPeriod := []vestingtypes.Period{ + { + Length: 3600, + Amount: amount, }, - txOptions: blobfactory.DefaultTxOpts(), } + msgCreatePeriodicVestingAccount := vestingtypes.NewMsgCreatePeriodicVestingAccount(accountAddresses[3], newAddress, 2, vestingPeriod) + secondBlockSdkMsgs = append(secondBlockSdkMsgs, msgCreatePeriodicVestingAccount) - // Create a Blob Tx - blobTx := BlobTx{ - author: accountNames[2], - blobs: []*blob.Blob{blob.New(fixedNamespace(), []byte{1}, appconsts.DefaultShareVersion)}, - txOptions: blobfactory.DefaultTxOpts(), + // NewMsgCreatePermanentLockedAccount - creates a permanent locked account + newAddress = sdk.AccAddress(ed25519.GenPrivKeyFromSecret([]byte("anotherAddress2")).PubKey().Address()) + msgCreatePermamentLockedAccount := vestingtypes.NewMsgCreatePermanentLockedAccount(accountAddresses[3], newAddress, amount) + secondBlockSdkMsgs = append(secondBlockSdkMsgs, msgCreatePermamentLockedAccount) + + // NewMsgCreateVestingAccount - creates a vesting account + newAddress = sdk.AccAddress(ed25519.GenPrivKeyFromSecret([]byte("anotherAddress3")).PubKey().Address()) + msgCreateVestingAccount := vestingtypes.NewMsgCreateVestingAccount(accountAddresses[3], newAddress, amount, 1, 2, false) + secondBlockSdkMsgs = append(secondBlockSdkMsgs, msgCreateVestingAccount) + + // ------------ Third Block ------------ + + // Txs within the third block are signed by the validator's signer + var thirdBlockSdkMsgs []sdk.Msg + + // NewMsgWithdrawValidatorCommission - withdraws validator-0's commission + msgWithdrawValidatorCommission := distribution.NewMsgWithdrawValidatorCommission(genValidators[0].GetOperator()) + thirdBlockSdkMsgs = append(thirdBlockSdkMsgs, msgWithdrawValidatorCommission) + + // NewMsgUnjail - unjails validator-3 + msgUnjail := slashingtypes.NewMsgUnjail(genValidators[3].GetOperator()) + thirdBlockSdkMsgs = append(thirdBlockSdkMsgs, msgUnjail) + + // NewMsgRegisterEVMAddress - registers an EVM address + // This message is only available on v1 + if testApp.AppVersion() == 1 { + msgRegisterEVMAddress := blobstreamtypes.NewMsgRegisterEVMAddress(genValidators[1].GetOperator(), gethcommon.HexToAddress("hi")) + thirdBlockSdkMsgs = append(thirdBlockSdkMsgs, msgRegisterEVMAddress) } - // Create SDK Tx - rawSdkTx, err := signer.CreateTx(sdkTx.sdkMsgs, sdkTx.txOptions...) + firstBlockTxs, err := processSdkMessages(signer, firstBlockSdkMsgs) require.NoError(t, err) - - // Create Blob Tx - rawBlobTx, _, err := signer.CreatePayForBlobs(blobTx.author, blobTx.blobs, blobTx.txOptions...) + secondBlockTxs, err := processSdkMessages(signer, secondBlockSdkMsgs) + require.NoError(t, err) + thirdBlockTxs, err := processSdkMessages(valSigner, thirdBlockSdkMsgs) require.NoError(t, err) - // BeginBlock - header := tmproto.Header{ - Version: version.Consensus{App: 1}, - Height: testApp.LastBlockHeight() + 1, - } - testApp.BeginBlock(abci.RequestBeginBlock{Header: header}) + return firstBlockTxs, secondBlockTxs, thirdBlockTxs +} - // Deliver SDK Tx - resp := testApp.DeliverTx(abci.RequestDeliverTx{Tx: rawSdkTx}) - require.EqualValues(t, 0, resp.Code, resp.Log) +// 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) - // Deliver Blob Tx - blob, isBlobTx := blob.UnmarshalBlobTx(rawBlobTx) - require.True(t, isBlobTx) - resp = testApp.DeliverTx(abci.RequestDeliverTx{Tx: blob.Tx}) - require.EqualValues(t, 0, resp.Code, resp.Log) + msgSignalVersion := signal.NewMsgSignalVersion(genValidators[0].GetOperator(), 2) + v2Messages = append(v2Messages, msgSignalVersion) - // EndBlock - testApp.EndBlock(abci.RequestEndBlock{Height: header.Height}) + encodedTxs, err := processSdkMessages(valSigner, v2Messages) + require.NoError(t, err) - // Commit the state - testApp.Commit() + return encodedTxs +} - // Get the app hash - appHash := testApp.LastCommitID().Hash +// 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]) + blobTxx := blob.New(fixedNamespace(), []byte{1}, appconsts.DefaultShareVersion) - // Require that the app hash is equal to the app hash produced on a different commit - require.Equal(t, expectedAppHash, appHash) + // Create a Blob Tx + blobTx := blobTx{ + author: senderAcc.Name(), + blobs: []*blob.Blob{blobTxx}, + txOptions: blobfactory.DefaultTxOpts(), + } + encodedBlobTx, _, err := signer.CreatePayForBlobs(blobTx.author, blobTx.blobs, blobTx.txOptions...) + require.NoError(t, err) + return encodedBlobTx } // fixedNamespace returns a hardcoded namespace @@ -171,3 +423,147 @@ func deterministicKeyRing(cdc codec.Codec) (keyring.Keyring, []types.PubKey) { } return kb, pubKeys } + +// processSdkMessages takes a list of sdk messages, forms transactions, signs them +// and returns a list of encoded transactions +func processSdkMessages(signer *user.Signer, sdkMessages []sdk.Msg) ([][]byte, error) { + encodedTxs := make([][]byte, 0, len(sdkMessages)) + for _, msg := range sdkMessages { + encodedTx, err := signer.CreateTx([]sdk.Msg{msg}, blobfactory.DefaultTxOpts()...) + if err != nil { + return nil, err + } + + signerAddress := msg.GetSigners()[0] + signerAccount := signer.AccountByAddress(signerAddress) + err = signer.SetSequence(signerAccount.Name(), signerAccount.Sequence()+1) + if err != nil { + return nil, err + } + + encodedTxs = append(encodedTxs, encodedTx) + } + return encodedTxs, nil +} + +// 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) { + height := testApp.LastBlockHeight() + 1 + chainID := testApp.GetChainID() + + genesisTime := testutil.GenesisTime + + // Prepare Proposal + resPrepareProposal := testApp.PrepareProposal(abci.RequestPrepareProposal{ + BlockData: &tmproto.Data{ + Txs: encodedSdkTxs, + }, + ChainId: chainID, + Height: height, + // Dynamically increase time so the validator can be unjailed (1m duration) + Time: genesisTime.Add(time.Duration(height) * time.Minute), + }) + if len(resPrepareProposal.BlockData.Txs) != len(encodedSdkTxs) { + return nil, nil, fmt.Errorf("PrepareProposal removed transactions. Was %d, now %d", len(encodedSdkTxs), len(resPrepareProposal.BlockData.Txs)) + } + + dataHash := resPrepareProposal.BlockData.Hash + + header := tmproto.Header{ + Version: version.Consensus{App: testApp.AppVersion()}, + DataHash: resPrepareProposal.BlockData.Hash, + ChainID: chainID, + Time: genesisTime.Add(time.Duration(height) * time.Minute), + Height: height, + LastCommitHash: lastCommitHash, + } + + // Process Proposal + resProcessProposal := testApp.ProcessProposal(abci.RequestProcessProposal{ + BlockData: resPrepareProposal.BlockData, + Header: header, + }, + ) + if abci.ResponseProcessProposal_ACCEPT != resProcessProposal.Result { + return nil, nil, fmt.Errorf("ProcessProposal failed: %v", resProcessProposal.Result) + } + + // Begin block + validator3Signed := height == 2 // Validator 3 signs only the first block + testApp.BeginBlock(abci.RequestBeginBlock{ + Header: header, + LastCommitInfo: abci.LastCommitInfo{ + Votes: []abci.VoteInfo{ + // In order to withdraw commission for this validator + { + Validator: validators[0], + SignedLastBlock: true, + }, + // In order to jail this validator + { + Validator: validators[3], + SignedLastBlock: validator3Signed, + }, + }, + }, + }) + + // Deliver SDK Txs + for i, tx := range encodedSdkTxs { + resp := testApp.DeliverTx(abci.RequestDeliverTx{Tx: tx}) + if resp.Code != abci.CodeTypeOK { + return nil, nil, fmt.Errorf("DeliverTx failed for the message at index %d: %s", i, resp.Log) + } + } + + // Deliver Blob Txs + if len(encodedBlobTx) != 0 { + // Deliver Blob Tx + blob, isBlobTx := blob.UnmarshalBlobTx(encodedBlobTx) + if !isBlobTx { + return nil, nil, fmt.Errorf("Not a valid BlobTx") + } + + respDeliverTx := testApp.DeliverTx(abci.RequestDeliverTx{Tx: blob.Tx}) + if respDeliverTx.Code != uint32(0) { + return nil, nil, fmt.Errorf("DeliverTx failed for the BlobTx: %s", respDeliverTx.Log) + } + } + + // EndBlock + testApp.EndBlock(abci.RequestEndBlock{Height: header.Height}) + + // Commit the state + testApp.Commit() + + // Get the app hash + appHash := testApp.LastCommitID().Hash + + return dataHash, appHash, nil +} + +// createAccounts creates a list of user.Accounts from a list of accountInfos +func createAccounts(accountInfos []blobfactory.AccountInfo, accountNames []string) []*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) + } + return accounts +} + +// convertToABCIValidators converts a list of staking.Validator to a list of abci.Validator +func convertToABCIValidators(genValidators []stakingtypes.Validator) ([]abci.Validator, error) { + abciValidators := make([]abci.Validator, 0, len(genValidators)) + for _, val := range genValidators { + consAddr, err := val.GetConsAddr() + if err != nil { + return nil, err + } + abciValidators = append(abciValidators, abci.Validator{ + Address: consAddr, + Power: 100, + }) + } + return abciValidators, nil +} diff --git a/test/util/common.go b/test/util/common.go index b969400c2c..e4fa050661 100644 --- a/test/util/common.go +++ b/test/util/common.go @@ -40,6 +40,7 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" + tmed "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/libs/log" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" tmversion "github.com/tendermint/tendermint/proto/tendermint/version" @@ -59,6 +60,33 @@ var ( MinCommissionRate: sdk.NewDecWithPrec(0, 0), } + // HardcodedConsensusPrivKeys + FixedConsensusPrivKeys = []tmed.PrivKey{ + tmed.GenPrivKeyFromSecret([]byte("12345678901234567890123456389012")), + tmed.GenPrivKeyFromSecret([]byte("12345678901234567890123456389013")), + tmed.GenPrivKeyFromSecret([]byte("12345678901234567890123456389014")), + tmed.GenPrivKeyFromSecret([]byte("12345678901234567890123456389015")), + tmed.GenPrivKeyFromSecret([]byte("12345678901234567890123456389016")), + } + + FixedNetworkPrivKeys = []tmed.PrivKey{ + tmed.GenPrivKeyFromSecret([]byte("12345678901234567890123456786012")), + tmed.GenPrivKeyFromSecret([]byte("12345678901234567890123456786013")), + tmed.GenPrivKeyFromSecret([]byte("12345678901234567890123456786014")), + tmed.GenPrivKeyFromSecret([]byte("12345678901234567890123456786015")), + tmed.GenPrivKeyFromSecret([]byte("12345678901234567890123456786016")), + } + + // FixedMnemonics is a set of fixed mnemonics for testing. + // Account names are: validator1, validator2, validator3, validator4, validator5 + FixedMnemonics = []string{ + "body world north giggle crop reduce height copper damp next verify orphan lens loan adjust inform utility theory now ranch motion opinion crowd fun", + "body champion street fat bone above office guess waste vivid gift around approve elevator depth fiber alarm usual skirt like organ space antique silk", + "cheap alpha render punch clap prize duty drive steel situate person radar smooth elegant over chronic wait danger thumb soft letter spatial acquire rough", + "outdoor ramp suspect office disagree world attend vanish small wish capable fall wall soon damp session emotion chest toss viable meat host clerk truth", + "ability evidence casino cram weasel chest brush bridge sister blur onion found glad own mansion amateur expect force fun dragon famous alien appear open", + } + // ConsPrivKeys generate ed25519 ConsPrivKeys to be used for validator operator keys ConsPrivKeys = []ccrypto.PrivKey{ ed25519.GenPrivKey(), diff --git a/test/util/genesis/modifier.go b/test/util/genesis/modifier.go index 20c62fb768..b7a419b848 100644 --- a/test/util/genesis/modifier.go +++ b/test/util/genesis/modifier.go @@ -13,6 +13,7 @@ import ( 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" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" ) // Modifier allows for arbitrary changes to be made on the genesis state @@ -30,6 +31,16 @@ func SetBlobParams(codec codec.Codec, params blobtypes.Params) Modifier { } } +// SetSlashingParams will set the provided slashing params as genesis state. +func SetSlashingParams(codec codec.Codec, parans slashingtypes.Params) Modifier { + return func(state map[string]json.RawMessage) map[string]json.RawMessage { + slashingGenState := slashingtypes.DefaultGenesisState() + slashingGenState.Params = parans + state[slashingtypes.ModuleName] = codec.MustMarshalJSON(slashingGenState) + return state + } +} + // ImmediateProposals sets the thresholds for getting a gov proposal to very low // levels. func ImmediateProposals(codec codec.Codec) Modifier { diff --git a/test/util/test_app.go b/test/util/test_app.go index e1d50722e2..a4ca8e6208 100644 --- a/test/util/test_app.go +++ b/test/util/test_app.go @@ -36,11 +36,14 @@ import ( tmtypes "github.com/tendermint/tendermint/types" dbm "github.com/tendermint/tm-db" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) const ChainID = testfactory.ChainID +var GenesisTime = time.Date(2023, 1, 1, 1, 1, 1, 1, time.UTC).UTC() + // Get flags every time the simulator is run func init() { simapp.GetSimulatorFlags() @@ -96,13 +99,16 @@ func NewTestApp() *app.App { // 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 + slashingParams := slashingtypes.NewParams(2, sdk.OneDec(), time.Minute, sdk.OneDec(), sdk.OneDec()) + + // Create genesis gen := genesis.NewDefaultGenesis(). WithChainID(ChainID). WithConsensusParams(cparams). - WithGenesisTime(time.Date(2023, 1, 1, 1, 1, 1, 1, time.UTC).UTC()) + WithModifiers(genesis.SetSlashingParams(testApp.AppCodec(), slashingParams)). + WithGenesisTime(GenesisTime) - // add accounts to genesis + // Add accounts to genesis for i, pk := range pubKeys { err := gen.AddAccount(genesis.Account{ PubKey: pk, @@ -114,8 +120,8 @@ func SetupDeterministicGenesisState(testApp *app.App, pubKeys []cryptotypes.PubK } } - // add validator to genesis - err := AddDeterministicValidatorToGenesis(gen) + // Add validators to genesis + err := AddDeterministicValidatorsToGenesis(gen) if err != nil { return nil, nil, fmt.Errorf("failed to add validator: %w", err) } @@ -125,12 +131,12 @@ func SetupDeterministicGenesisState(testApp *app.App, pubKeys []cryptotypes.PubK return nil, nil, fmt.Errorf("failed to export genesis doc: %w", err) } - // initialise test app against genesis + // 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 + // Choose some value large enough to not bottleneck the max square // size MaxBytes: int64(appconsts.DefaultSquareSizeUpperBound*appconsts.DefaultSquareSizeUpperBound) * appconsts.ContinuationSparseShareContentSize, MaxGas: cparams.Block.MaxGas, @@ -140,7 +146,7 @@ func SetupDeterministicGenesisState(testApp *app.App, pubKeys []cryptotypes.PubK Version: &cparams.Version, } - // init chain will set the validator set and initialize the genesis accounts + // Init chain will set the validator set and initialize the genesis accounts testApp.InitChain( abci.RequestInitChain{ Time: gen.GenesisTime, @@ -151,7 +157,7 @@ func SetupDeterministicGenesisState(testApp *app.App, pubKeys []cryptotypes.PubK }, ) - // commit genesis changes + // Commit genesis changes testApp.Commit() testApp.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{ ChainID: ChainID, @@ -189,14 +195,12 @@ func NewTestAppWithGenesisSet(cparams *tmproto.ConsensusParams, genAccounts ...s Version: &cparams.Version, } - genesisTime := time.Date(2023, 1, 1, 1, 1, 1, 1, time.UTC).UTC() - testApp.Info(abci.RequestInfo{}) // init chain will set the validator set and initialize the genesis accounts testApp.InitChain( abci.RequestInitChain{ - Time: genesisTime, + Time: GenesisTime, Validators: []abci.ValidatorUpdate{}, ConsensusParams: abciParams, AppStateBytes: stateBytes, @@ -206,47 +210,47 @@ func NewTestAppWithGenesisSet(cparams *tmproto.ConsensusParams, genAccounts ...s return testApp, valSet, 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, - } +// AddDeterministicValidatorToGenesis adds a set of five validators to the genesis. +func AddDeterministicValidatorsToGenesis(g *genesis.Genesis) error { + for i := range FixedMnemonics { + val := genesis.Validator{ + KeyringAccount: genesis.KeyringAccount{ + Name: "validator" + fmt.Sprint(i), + InitialTokens: 5_000_000_000, + }, + Stake: 1_000_000_000, + ConsensusKey: FixedConsensusPrivKeys[i], + NetworkKey: FixedNetworkPrivKeys[i], + } - // 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) - } + // Initialize the validator's genesis account in the keyring + rec, err := g.Keyring().NewAccount(val.Name, FixedMnemonics[i], "", "", 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) - } + 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, - Name: val.Name, - } + // Construct account from keyring account + account := genesis.Account{ + PubKey: validatorPubKey, + Balance: val.KeyringAccount.InitialTokens, + Name: val.Name, + } - // add the validator's account to the genesis - if err := g.AddAccount(account); err != nil { - return fmt.Errorf("failed to add account: %w", err) + // Add the validator's account to the genesis + if err := g.AddAccount(account); err != nil { + return fmt.Errorf("failed to add account: %w", err) + } + if err := g.AddValidator(val); err != nil { + return fmt.Errorf("failed to add validator: %w", err) + } } - return g.AddValidator(val) + return nil } // AddAccount mimics the cli addAccount command, providing an diff --git a/test/util/testfactory/common.go b/test/util/testfactory/common.go index 8edb003aa8..a25a9002b8 100644 --- a/test/util/testfactory/common.go +++ b/test/util/testfactory/common.go @@ -71,15 +71,28 @@ func GetAddresses(keys keyring.Keyring) []sdk.AccAddress { panic(err) } addresses := make([]sdk.AccAddress, 0, len(recs)) - for idx, rec := range recs { - addresses[idx], err = rec.GetAddress() + for _, rec := range recs { + address, err := rec.GetAddress() if err != nil { panic(err) } + addresses = append(addresses, address) } return addresses } +func GetAccountNames(keys keyring.Keyring) []string { + recs, err := keys.List() + if err != nil { + panic(err) + } + names := make([]string, 0, len(recs)) + for _, rec := range recs { + names = append(names, rec.Name) + } + return names +} + func GetAddress(keys keyring.Keyring, account string) sdk.AccAddress { rec, err := keys.Key(account) if err != nil {