diff --git a/app/estimate_square_size_test.go b/app/estimate_square_size_test.go index ddf4925859..c86467c87b 100644 --- a/app/estimate_square_size_test.go +++ b/app/estimate_square_size_test.go @@ -97,6 +97,7 @@ func Test_estimateSquareSize_MultiBlob(t *testing.T) { enc.TxConfig.TxEncoder(), signer, tt.getBlobSizes(), + 0, 0, ) normalTxs, blobTxs := separateTxs(enc.TxConfig, shares.TxsToBytes(txs)) resSquareSize, resStart := estimateSquareSize(normalTxs, blobTxs) diff --git a/app/parse_txs.go b/app/parse_txs.go index 578b2b749d..30599556fa 100644 --- a/app/parse_txs.go +++ b/app/parse_txs.go @@ -2,7 +2,10 @@ package app import ( "github.com/cosmos/cosmos-sdk/client" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/tendermint/tendermint/libs/log" core "github.com/tendermint/tendermint/proto/tendermint/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" coretypes "github.com/tendermint/tendermint/types" ) @@ -20,3 +23,66 @@ func separateTxs(txcfg client.TxConfig, rawTxs [][]byte) ([][]byte, []core.BlobT } return normalTxs, blobTxs } + +// filterStdTxs applies the provided antehandler to each transaction and removes +// transactions that return an error. Panics are caught by the checkTxValidity +// function used to apply the ante handler. +func filterStdTxs(logger log.Logger, dec sdk.TxDecoder, ctx sdk.Context, handler sdk.AnteHandler, txs [][]byte) ([][]byte, sdk.Context) { + n := 0 + var err error + for _, tx := range txs { + ctx, err = checkTxValidity(logger, dec, ctx, handler, tx) + // either the transaction is invalid (ie incorrect nonce) and we + // simply want to remove this tx, or we're catching a panic from one + // of the anteHanders which is logged. + if err != nil { + continue + } + txs[n] = tx + n++ + + } + + return txs[:n], ctx +} + +// filterBlobTxs applies the provided antehandler to each transaction +// and removes transactions that return an error. Panics are caught by the checkTxValidity +// function used to apply the ante handler. +func filterBlobTxs(logger log.Logger, dec sdk.TxDecoder, ctx sdk.Context, handler sdk.AnteHandler, txs []tmproto.BlobTx) ([]tmproto.BlobTx, sdk.Context) { + n := 0 + var err error + for _, tx := range txs { + ctx, err = checkTxValidity(logger, dec, ctx, handler, tx.Tx) + // either the transaction is invalid (ie incorrect nonce) and we + // simply want to remove this tx, or we're catching a panic from one + // of the anteHanders which is logged. + if err != nil { + continue + } + txs[n] = tx + n++ + + } + + return txs[:n], ctx +} + +func checkTxValidity(logger log.Logger, dec sdk.TxDecoder, ctx sdk.Context, handler sdk.AnteHandler, tx []byte) (sdk.Context, error) { + // catch panics from anteHandlers + defer func() { + if r := recover(); r != nil { + err := recoverHandler(r) + if err != nil { + logger.Error(err.Error()) + } + } + }() + + sdkTx, err := dec(tx) + if err != nil { + return ctx, err + } + + return handler(ctx, sdkTx, false) +} diff --git a/app/prepare_proposal.go b/app/prepare_proposal.go index 52b066161d..aaeb43d62f 100644 --- a/app/prepare_proposal.go +++ b/app/prepare_proposal.go @@ -25,6 +25,22 @@ func (app *App) PrepareProposal(req abci.RequestPrepareProposal) abci.ResponsePr // the txs is maintained. normalTxs, blobTxs := separateTxs(app.txConfig, req.BlockData.Txs) + sdkCtx, err := app.NewProcessProposalQueryContext() + if err != nil { + panic(err) + } + + // increment the sequences of the standard cosmos-sdk transactions. Panics + // from the anteHandler are caught and logged. + seqHandler := incrementSequenceAnteHandler(&app.AccountKeeper) + normalTxs, sdkCtx = filterStdTxs(app.Logger(), app.txConfig.TxDecoder(), sdkCtx, seqHandler, normalTxs) + + // check the signatures and increment the sequences of the blob txs, + // and filter out any that fail. Panics from the anteHandler are caught and + // logged. + svHandler := sigVerifyAnteHandler(&app.AccountKeeper, app.txConfig) + blobTxs, _ = filterBlobTxs(app.Logger(), app.txConfig.TxDecoder(), sdkCtx, svHandler, blobTxs) + // estimate the square size. This estimation errs on the side of larger // squares but can only return values within the min and max square size. squareSize, nonreservedStart := estimateSquareSize(normalTxs, blobTxs) diff --git a/app/process_proposal.go b/app/process_proposal.go index 66a971f1dc..27ef3966cd 100644 --- a/app/process_proposal.go +++ b/app/process_proposal.go @@ -86,7 +86,22 @@ func (app *App) ProcessProposal(req abci.RequestProcessProposal) abci.ResponsePr } } - // iterate over all of the MsgPayForBlobs transactions and ensure that their + // create the anteHanders that are used to check the validity of + // transactions. We verify the signatures of PFB containing txs using the + // sigVerifyAnterHandler, and simply increase the nonce of all other + // transactions. + svHander := sigVerifyAnteHandler(&app.AccountKeeper, app.txConfig) + seqHandler := incrementSequenceAnteHandler(&app.AccountKeeper) + + sdkCtx, err := app.NewProcessProposalQueryContext() + if err != nil { + logInvalidPropBlockError(app.Logger(), req.Header, "failure to load query context", err) + return abci.ResponseProcessProposal{ + Result: abci.ResponseProcessProposal_REJECT, + } + } + + // iterate over all of the MsgPayForBlob transactions and ensure that their // commitments are subtree roots of the data root. for _, rawTx := range req.BlockData.Txs { tx := rawTx @@ -105,6 +120,17 @@ func (app *App) ProcessProposal(req abci.RequestProcessProposal) abci.ResponsePr pfb, has := hasPFB(sdkTx.GetMsgs()) if !has { + // we need to increment the sequence for every transaction so that + // the signature check below is accurate. this error only gets hit + // if the account in question doens't exist. + sdkCtx, err = seqHandler(sdkCtx, sdkTx, false) + if err != nil { + logInvalidPropBlockError(app.Logger(), req.Header, "failure to incrememnt sequence", err) + return abci.ResponseProcessProposal{ + Result: abci.ResponseProcessProposal_REJECT, + } + } + // we do not need to perform further checks on this transaction, // since it has no PFB continue @@ -149,6 +175,14 @@ func (app *App) ProcessProposal(req abci.RequestProcessProposal) abci.ResponsePr } } } + + sdkCtx, err = svHander(sdkCtx, sdkTx, true) + if err != nil { + logInvalidPropBlockError(app.Logger(), req.Header, "invalid PFB signature", err) + return abci.ResponseProcessProposal{ + Result: abci.ResponseProcessProposal_REJECT, + } + } } return abci.ResponseProcessProposal{ diff --git a/app/test/fuzz_abci_test.go b/app/test/fuzz_abci_test.go index 5e56767938..1e8dfa9ac1 100644 --- a/app/test/fuzz_abci_test.go +++ b/app/test/fuzz_abci_test.go @@ -7,9 +7,9 @@ import ( "github.com/celestiaorg/celestia-app/app" "github.com/celestiaorg/celestia-app/app/encoding" "github.com/celestiaorg/celestia-app/testutil" - "github.com/celestiaorg/celestia-app/testutil/blobfactory" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" + tmrand "github.com/tendermint/tendermint/libs/rand" core "github.com/tendermint/tendermint/proto/tendermint/types" coretypes "github.com/tendermint/tendermint/types" ) @@ -25,54 +25,68 @@ func TestPrepareProposalConsistency(t *testing.T) { t.Skip("skipping TestPrepareProposalConsistency in short mode.") } encConf := encoding.MakeConfig(app.ModuleEncodingRegisters...) - testApp, _ := testutil.SetupTestAppWithGenesisValSet() + accounts := make([]string, 1100) // 1000 for creating blob txs, 100 for creating send txs + for i := range accounts { + accounts[i] = tmrand.Str(20) + } + + testApp, kr := testutil.SetupTestAppWithGenesisValSet(accounts...) type test struct { name string count, blobCount, size int } tests := []test{ - {"many small single share single blob transactions", 10000, 1, 400}, + {"many small single share single blob transactions", 1000, 1, 400}, {"one hundred normal sized single blob transactions", 100, 1, 400000}, {"many single share multi-blob transactions", 1000, 100, 400}, {"one hundred normal sized multi-blob transactions", 100, 4, 400000}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - timer := time.After(time.Second * 30) + timer := time.After(time.Second * 20) for { select { case <-timer: return default: - ProcessRandomProposal(t, tt.count, tt.size, tt.blobCount, encConf, testApp) + txs := testutil.RandBlobTxsWithAccounts( + t, + testApp, + encConf.TxConfig.TxEncoder(), + kr, + tt.size, + tt.count, + true, + "", + accounts[:tt.count], + ) + // create 100 send transactions + sendTxs := testutil.SendTxsWithAccounts( + t, + testApp, + encConf.TxConfig.TxEncoder(), + kr, + 1000, + accounts[0], + accounts[len(accounts)-100:], + "", + ) + txs = append(txs, sendTxs...) + resp := testApp.PrepareProposal(abci.RequestPrepareProposal{ + BlockData: &core.Data{ + Txs: coretypes.Txs(txs).ToSliceOfBytes(), + }, + }) + res := testApp.ProcessProposal(abci.RequestProcessProposal{ + BlockData: resp.BlockData, + Header: core.Header{ + DataHash: resp.BlockData.Hash, + }, + }) + require.Equal(t, abci.ResponseProcessProposal_ACCEPT, res.Result) } } }) } } - -func ProcessRandomProposal( - t *testing.T, - count, - maxSize int, - maxBlobCount int, - cfg encoding.Config, - capp *app.App, -) { - txs := blobfactory.RandBlobTxsRandomlySized(cfg.TxConfig.TxEncoder(), count, maxSize, maxBlobCount) - sendTxs := blobfactory.GenerateManyRawSendTxs(cfg.TxConfig, count) - txs = append(txs, sendTxs...) - resp := capp.PrepareProposal(abci.RequestPrepareProposal{ - BlockData: &core.Data{ - Txs: coretypes.Txs(txs).ToSliceOfBytes(), - }, - }) - res := capp.ProcessProposal(abci.RequestProcessProposal{ - BlockData: resp.BlockData, - Header: core.Header{ - DataHash: resp.BlockData.Hash, - }, - }) - require.Equal(t, abci.ResponseProcessProposal_ACCEPT, res.Result) -} diff --git a/app/test/integration_test.go b/app/test/integration_test.go index bdcf2772ac..f68762cda6 100644 --- a/app/test/integration_test.go +++ b/app/test/integration_test.go @@ -60,7 +60,7 @@ func (s *IntegrationTestSuite) SetupSuite() { } s.T().Log("setting up integration test suite") - numAccounts := 100 + numAccounts := 120 s.accounts = make([]string, numAccounts) for i := 0; i < numAccounts; i++ { s.accounts[i] = tmrand.Str(20) @@ -115,7 +115,7 @@ func (s *IntegrationTestSuite) TestMaxBlockSize() { 3, false, s.cfg.ChainID, - s.accounts[:20], + s.accounts[20:40], ) } @@ -132,7 +132,7 @@ func (s *IntegrationTestSuite) TestMaxBlockSize() { 8, true, s.cfg.ChainID, - s.accounts[:80], + s.accounts[40:120], ) } diff --git a/app/test/prepare_proposal_test.go b/app/test/prepare_proposal_test.go index dda307c956..fa1a959ff7 100644 --- a/app/test/prepare_proposal_test.go +++ b/app/test/prepare_proposal_test.go @@ -3,10 +3,12 @@ package app_test import ( "testing" - authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" + tmrand "github.com/tendermint/tendermint/libs/rand" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" coretypes "github.com/tendermint/tendermint/types" @@ -15,16 +17,16 @@ import ( "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/celestiaorg/celestia-app/testutil" "github.com/celestiaorg/celestia-app/testutil/blobfactory" + "github.com/celestiaorg/celestia-app/testutil/namespace" "github.com/celestiaorg/celestia-app/testutil/testfactory" - "github.com/celestiaorg/celestia-app/x/blob/types" + blobtypes "github.com/celestiaorg/celestia-app/x/blob/types" ) func TestPrepareProposalBlobSorting(t *testing.T) { - signer := types.GenerateKeyringSigner(t, types.TestAccName) - encCfg := encoding.MakeConfig(app.ModuleEncodingRegisters...) - - testApp, _ := testutil.SetupTestAppWithGenesisValSet() + accnts := testfactory.GenerateAccounts(6) + testApp, kr := testutil.SetupTestAppWithGenesisValSet(accnts...) + infos := queryAccountInfo(testApp, accnts, kr) type test struct { input abci.RequestPrepareProposal @@ -32,16 +34,35 @@ func TestPrepareProposalBlobSorting(t *testing.T) { expectedTxs int } - blobTxs := blobfactory.RandBlobTxsWithNamespacesAndSigner( + blobTxs := blobfactory.ManyMultiBlobTx( + t, encCfg.TxConfig.TxEncoder(), - signer, - [][]byte{ - {1, 1, 1, 1, 1, 1, 1, 1}, - {3, 3, 3, 3, 3, 3, 3, 3}, - {2, 2, 2, 2, 2, 2, 2, 2}, + kr, + testutil.ChainID, + accnts[:3], + infos[:3], + [][]*tmproto.Blob{ + { + { + NamespaceId: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + Data: tmrand.Bytes(100), + }, + }, + { + { + NamespaceId: []byte{3, 3, 3, 3, 3, 3, 3, 3}, + Data: tmrand.Bytes(1000), + }, + }, + { + { + NamespaceId: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + Data: tmrand.Bytes(420), + }, + }, }, - []int{100, 1000, 420}, ) + decodedBlobTxs := make([]tmproto.BlobTx, 0, len(blobTxs)) for _, rawBtx := range blobTxs { btx, isbtx := coretypes.UnmarshalBlobTx(rawBtx) @@ -55,7 +76,7 @@ func TestPrepareProposalBlobSorting(t *testing.T) { { input: abci.RequestPrepareProposal{ BlockData: &tmproto.Data{ - Txs: coretypes.Txs(blobTxs).ToSliceOfBytes(), + Txs: blobTxs, }, }, expectedBlobs: []tmproto.Blob{ @@ -80,42 +101,14 @@ func TestPrepareProposalBlobSorting(t *testing.T) { res := testApp.PrepareProposal(tt.input) assert.Equal(t, tt.expectedBlobs, res.BlockData.Blobs) assert.Equal(t, tt.expectedTxs, len(res.BlockData.Txs)) - - // verify the signatures of the prepared txs - sdata, err := signer.GetSignerData() - require.NoError(t, err) - - dec := encoding.IndexWrapperDecoder(encCfg.TxConfig.TxDecoder()) - for _, tx := range res.BlockData.Txs { - sTx, err := dec(tx) - require.NoError(t, err) - - sigTx, ok := sTx.(authsigning.SigVerifiableTx) - require.True(t, ok) - - sigs, err := sigTx.GetSignaturesV2() - require.NoError(t, err) - require.Equal(t, 1, len(sigs)) - sig := sigs[0] - - err = authsigning.VerifySignature( - sdata.PubKey, - sdata, - sig.Data, - encCfg.TxConfig.SignModeHandler(), - sTx, - ) - assert.NoError(t, err) - } } } func TestPrepareProposalOverflow(t *testing.T) { - signer := types.GenerateKeyringSigner(t, types.TestAccName) - + acc := "test" encCfg := encoding.MakeConfig(app.ModuleEncodingRegisters...) - - testApp, _ := testutil.SetupTestAppWithGenesisValSet() + testApp, kr := testutil.SetupTestAppWithGenesisValSet(acc) + signer := blobtypes.NewKeyringSigner(kr, acc, testutil.ChainID) type test struct { name string @@ -148,15 +141,17 @@ func TestPrepareProposalOverflow(t *testing.T) { } for _, tt := range tests { - blobTxs := blobfactory.RandBlobTxsWithNamespacesAndSigner( + btxs := blobfactory.ManyMultiBlobTxSameSigner( + t, encCfg.TxConfig.TxEncoder(), signer, - testfactory.Repeat([]byte{1, 2, 3, 4, 5, 6, 7, 8}, tt.singleSharePFBs), - testfactory.Repeat(1, tt.singleSharePFBs), + testfactory.Repeat([]int{1}, tt.singleSharePFBs), + 0, + 1, // use the account number 1 since the first account is taken by the validator ) req := abci.RequestPrepareProposal{ BlockData: &tmproto.Data{ - Txs: coretypes.Txs(blobTxs).ToSliceOfBytes(), + Txs: coretypes.Txs(btxs).ToSliceOfBytes(), }, } res := testApp.PrepareProposal(req) @@ -168,15 +163,42 @@ func TestPrepareProposalOverflow(t *testing.T) { func TestPrepareProposalPutsPFBsAtEnd(t *testing.T) { numBlobTxs, numNormalTxs := 3, 3 + accnts := testfactory.GenerateAccounts(numBlobTxs + numNormalTxs) + testApp, kr := testutil.SetupTestAppWithGenesisValSet(accnts...) encCfg := encoding.MakeConfig(app.ModuleEncodingRegisters...) - blobTxs := blobfactory.RandBlobTxs(encCfg.TxConfig.TxEncoder(), numBlobTxs, 100) - normalTxs := blobfactory.GenerateManyRawSendTxs(encCfg.TxConfig, numNormalTxs) - txs := append(blobTxs, normalTxs...) - testApp, _ := testutil.SetupTestAppWithGenesisValSet() + infos := queryAccountInfo(testApp, accnts, kr) + + blobTxs := blobfactory.ManyMultiBlobTx( + t, + encCfg.TxConfig.TxEncoder(), + kr, + testutil.ChainID, + accnts[:numBlobTxs], + infos[:numBlobTxs], + testfactory.Repeat([]*tmproto.Blob{ + { + NamespaceId: namespace.RandomBlobNamespace(), + Data: []byte{1}, + ShareVersion: uint32(appconsts.DefaultShareVersion), + }, + }, numBlobTxs), + ) + + normalTxs := testutil.SendTxsWithAccounts( + t, + testApp, + encCfg.TxConfig.TxEncoder(), + kr, + 1000, + accnts[0], + accnts[numBlobTxs:], + "", + ) + txs := append(blobTxs, coretypes.Txs(normalTxs).ToSliceOfBytes()...) resp := testApp.PrepareProposal(abci.RequestPrepareProposal{ BlockData: &tmproto.Data{ - Txs: coretypes.Txs(txs).ToSliceOfBytes(), + Txs: txs, }, }) require.Len(t, resp.BlockData.Txs, numBlobTxs+numNormalTxs) @@ -189,3 +211,142 @@ func TestPrepareProposalPutsPFBsAtEnd(t *testing.T) { } } } + +func TestPrepareProposalFiltering(t *testing.T) { + encConf := encoding.MakeConfig(app.ModuleEncodingRegisters...) + accounts := testfactory.GenerateAccounts(6) + testApp, kr := testutil.SetupTestAppWithGenesisValSet(accounts...) + infos := queryAccountInfo(testApp, accounts, kr) + + // create 3 single blob blobTxs that are signed with valid account numbers + // and sequences + blobTxs := blobfactory.ManyMultiBlobTx( + t, + encConf.TxConfig.TxEncoder(), + kr, + testutil.ChainID, + accounts[:3], + infos[:3], + blobfactory.NestedBlobs( + t, + [][]byte{ + namespace.RandomBlobNamespace(), + namespace.RandomBlobNamespace(), + namespace.RandomBlobNamespace(), + }, + [][]int{{100}, {1000}, {420}}, + ), + ) + + // create 3 MsgSend transactions that are signed with valid account numbers + // and sequences + sendTxs := coretypes.Txs(testutil.SendTxsWithAccounts( + t, + testApp, + encConf.TxConfig.TxEncoder(), + kr, + 1000, + accounts[0], + accounts[len(accounts)-3:], + "", + )).ToSliceOfBytes() + + validTxs := func() [][]byte { + txs := make([][]byte, 0, len(sendTxs)+len(blobTxs)) + txs = append(txs, blobTxs...) + txs = append(txs, sendTxs...) + return txs + } + + // create 3 MsgSend transactions that are using the same sequence as the + // first three blob transactions above + duplicateSeqSendTxs := coretypes.Txs(testutil.SendTxsWithAccounts( + t, + testApp, + encConf.TxConfig.TxEncoder(), + kr, + 1000, + accounts[0], + accounts[:3], + "", + )).ToSliceOfBytes() + + // create a transaction with an account that doesn't exist. This will cause the increment nonce + nilAccount := "carmon san diego" + _, _, err := kr.NewMnemonic(nilAccount, keyring.English, "", "", hd.Secp256k1) + require.NoError(t, err) + noAccountTx := []byte(testutil.SendTxWithManualSequence(t, encConf.TxConfig.TxEncoder(), kr, nilAccount, accounts[0], 1000, "", 0, 6)) + + type test struct { + name string + txs func() [][]byte + prunedTxs [][]byte + } + + tests := []test{ + { + name: "all valid txs, none are pruned", + txs: func() [][]byte { return validTxs() }, + prunedTxs: [][]byte{}, + }, + { + // even though duplicateSeqSendTxs are getting appended to the end of the + // block, and we do not check the signatures of the standard txs, + // the blob txs still get pruned because we are separating the + // normal and blob txs, and checking/executing the normal txs first. + name: "duplicate sequence appended to the end of the block", + txs: func() [][]byte { + return append(validTxs(), duplicateSeqSendTxs...) + }, + prunedTxs: blobTxs, + }, + { + name: "duplicate sequence txs", + txs: func() [][]byte { + txs := make([][]byte, 0, len(sendTxs)+len(blobTxs)+len(duplicateSeqSendTxs)) + // these should increment the nonce of the accounts that are + // signing the blobtxs, which should make those signatures + // invalid. + txs = append(txs, duplicateSeqSendTxs...) + txs = append(txs, blobTxs...) + txs = append(txs, sendTxs...) + return txs + }, + prunedTxs: blobTxs, + }, + { + name: "nil account panic catch", + txs: func() [][]byte { + return [][]byte{noAccountTx} + }, + prunedTxs: [][]byte{noAccountTx}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp := testApp.PrepareProposal(abci.RequestPrepareProposal{ + BlockData: &tmproto.Data{Txs: tt.txs()}, + }) + // check that we have the expected number of transactions + require.Equal(t, len(tt.txs())-len(tt.prunedTxs), len(resp.BlockData.Txs)) + // check the the expected txs were removed + for _, ptx := range tt.prunedTxs { + assert.NotContains(t, resp.BlockData.Txs, ptx) + } + }) + } +} + +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 +} diff --git a/app/test/process_proposal_test.go b/app/test/process_proposal_test.go index f4a1ed6808..a2cb58396a 100644 --- a/app/test/process_proposal_test.go +++ b/app/test/process_proposal_test.go @@ -1,6 +1,8 @@ package app_test import ( + "bytes" + "sort" "testing" "github.com/stretchr/testify/assert" @@ -18,22 +20,69 @@ import ( "github.com/celestiaorg/celestia-app/pkg/shares" "github.com/celestiaorg/celestia-app/testutil" "github.com/celestiaorg/celestia-app/testutil/blobfactory" + "github.com/celestiaorg/celestia-app/testutil/namespace" + "github.com/celestiaorg/celestia-app/testutil/testfactory" ) func TestProcessProposal(t *testing.T) { - testApp, _ := testutil.SetupTestAppWithGenesisValSet() encConf := encoding.MakeConfig(app.ModuleEncodingRegisters...) + accounts := testfactory.GenerateAccounts(6) + testApp, kr := testutil.SetupTestAppWithGenesisValSet(accounts...) + infos := queryAccountInfo(testApp, accounts, kr) + + // create 3 single blob blobTxs that are signed with valid account numbers + // and sequences + blobTxs := blobfactory.ManyMultiBlobTx( + t, + encConf.TxConfig.TxEncoder(), + kr, + testutil.ChainID, + accounts[:3], + infos[:3], + blobfactory.NestedBlobs( + t, + [][]byte{ + namespace.RandomBlobNamespace(), + namespace.RandomBlobNamespace(), + namespace.RandomBlobNamespace(), + }, + [][]int{{100}, {1000}, {420}}, + ), + ) + + // create 3 MsgSend transactions that are signed with valid account numbers + // and sequences + sendTxs := testutil.SendTxsWithAccounts( + t, + testApp, + encConf.TxConfig.TxEncoder(), + kr, + 1000, + accounts[0], + accounts[len(accounts)-3:], + "", + ) // block with all blobs included - validData := func() *core.Data { - return &core.Data{ - Txs: coretypes.Txs(blobfactory.RandBlobTxs(encConf.TxConfig.TxEncoder(), 4, 1000)).ToSliceOfBytes(), + validData := func() *tmproto.Data { + return &tmproto.Data{ + Txs: blobTxs, } } // create block data with a PFB that is not indexed and has no blob unindexedData := validData() - blobtx := blobfactory.RandBlobTxs(encConf.TxConfig.TxEncoder(), 1, 1000)[0] + blobtx := testutil.RandBlobTxsWithAccounts( + t, + testApp, + encConf.TxConfig.TxEncoder(), + kr, + 1000, + 2, + false, + "", + accounts[:1], + )[0] btx, _ := coretypes.UnmarshalBlobTx(blobtx) unindexedData.Txs = append(unindexedData.Txs, btx.Tx) @@ -43,8 +92,23 @@ func TestProcessProposal(t *testing.T) { undecodableData.Txs = append(unindexedData.Txs, tmrand.Bytes(300)) mixedData := validData() - normalTxs := blobfactory.GenerateManyRawSendTxs(encConf.TxConfig, 4) - mixedData.Txs = append(mixedData.Txs, coretypes.Txs(normalTxs).ToSliceOfBytes()...) + mixedData.Txs = append(mixedData.Txs, coretypes.Txs(sendTxs).ToSliceOfBytes()...) + + // create an invalid block by adding an otherwise valid PFB, but an invalid + // signature since there's no account + badSigPFBData := validData() + badSigBlobTx := testutil.RandBlobTxsWithManualSequence( + t, + encConf.TxConfig.TxEncoder(), + kr, + 1000, + 1, + false, + "", + accounts[:1], + 420, 42, + )[0] + badSigPFBData.Txs = append(badSigPFBData.Txs, badSigBlobTx) type test struct { name string @@ -135,6 +199,58 @@ func TestProcessProposal(t *testing.T) { }, expectedResult: abci.ResponseProcessProposal_REJECT, }, + { + // while this test passes and the block gets rejected, it is getting + // rejected because the data root is different. We need to refactor + // prepare proposal to abstract functionality into a different + // function or be able to skip the filtering checks. TODO: perform + // the mentioned refactor and make it easier to create invalid + // blocks for testing. + name: "included pfb with bad signature", + input: validData(), + mutator: func(d *core.Data) { + btx, _ := coretypes.UnmarshalBlobTx(badSigBlobTx) + d.Txs = append(d.Txs, btx.Tx) + d.Blobs = append(d.Blobs, deref(btx.Blobs)...) + sort.SliceStable(d.Blobs, func(i, j int) bool { + return bytes.Compare(d.Blobs[i].NamespaceId, d.Blobs[j].NamespaceId) < 0 + }) + // todo: replace the data root with an updated hash + }, + expectedResult: abci.ResponseProcessProposal_REJECT, + }, + { + name: "blob with parity namespace", + input: validData(), + mutator: func(d *tmproto.Data) { + d.Blobs[len(d.Blobs)-1].NamespaceId = appconsts.ParitySharesNamespaceID + // todo: replace the data root with an updated hash + }, + expectedResult: abci.ResponseProcessProposal_REJECT, + }, + { + name: "tampered sequence start", + input: &tmproto.Data{ + Txs: coretypes.Txs(sendTxs).ToSliceOfBytes(), + }, + mutator: func(d *tmproto.Data) { + bd, err := coretypes.DataFromProto(d) + require.NoError(t, err) + + dataSquare, err := shares.Split(bd, true) + require.NoError(t, err) + dataSquare[1] = flipSequenceStart(dataSquare[1]) + + eds, err := da.ExtendShares(d.SquareSize, shares.ToBytes(dataSquare)) + require.NoError(t, err) + + dah := da.NewDataAvailabilityHeader(eds) + // replace the hash of the prepare proposal response with the hash of a data + // square with a tampered sequence start indicator + d.Hash = dah.Hash() + }, + expectedResult: abci.ResponseProcessProposal_REJECT, + }, } for _, tt := range tests { @@ -154,59 +270,6 @@ func TestProcessProposal(t *testing.T) { } } -func TestProcessProposalWithParityShareNamespace(t *testing.T) { - testApp, _ := testutil.SetupTestAppWithGenesisValSet() - encConf := encoding.MakeConfig(app.ModuleEncodingRegisters...) - - txs := coretypes.Txs(blobfactory.RandBlobTxs(encConf.TxConfig.TxEncoder(), 4, 1000)).ToSliceOfBytes() - req := abci.RequestPrepareProposal{ - BlockData: &tmproto.Data{ - Txs: txs, - }, - } - - resp := testApp.PrepareProposal(req) - - resp.BlockData.Blobs[0].NamespaceId = appconsts.ParitySharesNamespaceID - - input := abci.RequestProcessProposal{ - BlockData: resp.BlockData, - } - res := testApp.ProcessProposal(input) - require.Equal(t, abci.ResponseProcessProposal_REJECT, res.Result) -} - -func TestProcessProposalWithTamperedSequenceStart(t *testing.T) { - testApp, _ := testutil.SetupTestAppWithGenesisValSet() - encConf := encoding.MakeConfig(app.ModuleEncodingRegisters...) - - txs := coretypes.Txs(blobfactory.GenerateManyRawSendTxs(encConf.TxConfig, 10)).ToSliceOfBytes() - req := abci.RequestPrepareProposal{ - BlockData: &tmproto.Data{ - Txs: txs, - }, - } - resp := testApp.PrepareProposal(req) - - coreData, err := coretypes.DataFromProto(resp.BlockData) - assert.NoError(t, err) - dataSquare, err := shares.Split(coreData, true) - assert.NoError(t, err) - dataSquare[1] = flipSequenceStart(dataSquare[1]) - eds, err := da.ExtendShares(resp.BlockData.SquareSize, shares.ToBytes(dataSquare)) - assert.NoError(t, err) - dah := da.NewDataAvailabilityHeader(eds) - // replace the hash of the prepare proposal response with the hash of a data - // square with a tampered sequence start indicator - resp.BlockData.Hash = dah.Hash() - input := abci.RequestProcessProposal{ - BlockData: resp.BlockData, - } - - res := testApp.ProcessProposal(input) - require.Equal(t, abci.ResponseProcessProposal_REJECT, res.Result) -} - // flipSequenceStart flips the sequence start indicator of the share provided func flipSequenceStart(share shares.Share) shares.Share { // the info byte is immediately after the namespace @@ -216,3 +279,11 @@ func flipSequenceStart(share shares.Share) shares.Share { share[infoByteIndex] = share[infoByteIndex] ^ 0x01 return share } + +func deref[T any](s []*T) []T { + t := make([]T, len(s)) + for i, ss := range s { + t[i] = *ss + } + return t +} diff --git a/app/testutil_test.go b/app/testutil_test.go index 2f86ad0e47..ea15ccfe85 100644 --- a/app/testutil_test.go +++ b/app/testutil_test.go @@ -24,7 +24,9 @@ func generateMixedTxs(normalTxCount, pfbCount, pfbSize int) ([][]byte, []tmproto } // generateBlobTxsWithNIDs will generate len(nids) BlobTxs with -// len(blobSizes[i]) number of blobs per BlobTx. +// len(blobSizes[i]) number of blobs per BlobTx. Note: not suitable for using in +// prepare or process proposal, as the signatures will be invalid since this +// does not query for relevant account numbers or sequences. func generateBlobTxsWithNIDs(t *testing.T, nids [][]byte, blobSizes [][]int) []tmproto.BlobTx { encCfg := encoding.MakeConfig(ModuleEncodingRegisters...) const acc = "signer" @@ -35,6 +37,7 @@ func generateBlobTxsWithNIDs(t *testing.T, nids [][]byte, blobSizes [][]int) []t kr, "chainid", blobfactory.Repeat(acc, len(blobSizes)), + blobfactory.Repeat(blobfactory.AccountInfo{}, len(blobSizes)), blobfactory.NestedBlobs(t, nids, blobSizes), ) _, blobTxs := separateTxs(encCfg.TxConfig, txs) diff --git a/app/verify_txs.go b/app/verify_txs.go new file mode 100644 index 0000000000..b6de6c3f95 --- /dev/null +++ b/app/verify_txs.go @@ -0,0 +1,41 @@ +package app + +import ( + "fmt" + "runtime/debug" + + "github.com/cosmos/cosmos-sdk/client" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" +) + +// sigVerifyAnteHandler creates an AnteHandler with the SetupContext, SetPubKey, +// SigVerification, and IncremementSequence ante decorators to check that +// sequences have be incremented. +func sigVerifyAnteHandler(accKeeper *authkeeper.AccountKeeper, txConfig client.TxConfig) sdk.AnteHandler { + setupd := ante.NewSetUpContextDecorator() + setPubKd := ante.NewSetPubKeyDecorator(accKeeper) + svd := ante.NewSigVerificationDecorator(accKeeper, txConfig.SignModeHandler()) + isd := ante.NewIncrementSequenceDecorator(accKeeper) + return sdk.ChainAnteDecorators(setupd, setPubKd, svd, isd) +} + +// incrementSequenceAnteHandler creates an AnteHandler that only incrememts the +// sequence. +func incrementSequenceAnteHandler(accKeeper *authkeeper.AccountKeeper) sdk.AnteHandler { + setupd := ante.NewSetUpContextDecorator() + isd := ante.NewIncrementSequenceDecorator(accKeeper) + return sdk.ChainAnteDecorators(setupd, isd) +} + +// recoverHandler will simply wrap the caught panic in an error containing the +// stack trace. +func recoverHandler(recoveryObj interface{}) error { + return sdkerrors.Wrap( + sdkerrors.ErrPanic, fmt.Sprintf( + "recovered: %v\nstack:\n%v", recoveryObj, string(debug.Stack()), + ), + ) +} diff --git a/go.mod b/go.mod index 3f183a400e..bfd04dd90b 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/celestiaorg/celestia-app go 1.18 require ( - github.com/celestiaorg/nmt v0.12.0 + github.com/celestiaorg/nmt v0.13.0 github.com/celestiaorg/quantum-gravity-bridge v1.3.0 github.com/ethereum/go-ethereum v1.10.26 github.com/gogo/protobuf v1.3.3 @@ -184,7 +184,8 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.6.0-sdk-v0.46.7 + github.com/celestiaorg/nmt v0.13.0 => github.com/celestiaorg/nmt v0.12.0 + github.com/cosmos/cosmos-sdk => github.com/celestiaorg/cosmos-sdk v1.7.0-sdk-v0.46.7 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.14.0-tm-v0.34.23 ) diff --git a/go.sum b/go.sum index 5044ad3102..f1d1b3b235 100644 --- a/go.sum +++ b/go.sum @@ -188,8 +188,8 @@ github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOC github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/celestiaorg/celestia-core v1.14.0-tm-v0.34.23 h1:8zE523TUe5W33/nheJ9umHF2d1q6iHQlqJfMXMTPe3k= github.com/celestiaorg/celestia-core v1.14.0-tm-v0.34.23/go.mod h1:fGDSg7aw2OH/Uze1zymop0x0y1kAPEO9OII2A2cb99Q= -github.com/celestiaorg/cosmos-sdk v1.6.0-sdk-v0.46.7 h1:wP4RRK9zbVBlQXqRqJXji09e9UUFpNpwL2zorHwbJhQ= -github.com/celestiaorg/cosmos-sdk v1.6.0-sdk-v0.46.7/go.mod h1:MkOIxmKiICA4d8yPWo/590S4fqpaLXfocz0xn1fxBp4= +github.com/celestiaorg/cosmos-sdk v1.7.0-sdk-v0.46.7 h1:jy24W2wTCnDY843A/5hy79QP/4GNhGPtADN55zs9o6g= +github.com/celestiaorg/cosmos-sdk v1.7.0-sdk-v0.46.7/go.mod h1:qH1Q0s96i4Op0WSh6qC72yWkoWdZNs0CY4HPUrWaK44= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc= github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4/go.mod h1:fzuHnhzj1pUygGz+1ZkB3uQbEUL4htqCGJ4Qs2LwMZA= github.com/celestiaorg/nmt v0.12.0 h1:6CmaMPri9FdSiytZP7yCrEq3ewebFiIEjlJhasrS6oQ= diff --git a/testutil/blobfactory/account_info.go b/testutil/blobfactory/account_info.go new file mode 100644 index 0000000000..68f10f6661 --- /dev/null +++ b/testutil/blobfactory/account_info.go @@ -0,0 +1,17 @@ +package blobfactory + +import ( + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +type AccountInfo struct { + AccountNum, Sequence uint64 +} + +func ExtractAccountInfo(accs []authtypes.AccountI) []AccountInfo { + infos := make([]AccountInfo, len(accs)) + for i, acc := range accs { + infos[i] = AccountInfo{Sequence: acc.GetSequence(), AccountNum: acc.GetAccountNumber()} + } + return infos +} diff --git a/testutil/blobfactory/payforblob_factory.go b/testutil/blobfactory/payforblob_factory.go index 6ceb15c4a0..6d07dc36e9 100644 --- a/testutil/blobfactory/payforblob_factory.go +++ b/testutil/blobfactory/payforblob_factory.go @@ -259,17 +259,19 @@ func RandBlobTxsWithNamespaces(enc sdk.TxEncoder, nIds [][]byte, sizes []int) [] return RandBlobTxsWithNamespacesAndSigner(enc, signer, nIds, sizes) } -// ManyMultiBlobTxSameSigner generates and returns many blob transactions with the -// possibility to add more than one blob. +// ManyMultiBlobTxSameSigner generates and returns many blob transactions with +// the possibility to add more than one blob. The sequence and account number +// are manually set, and the sequence is manually incremented when doing so. func ManyMultiBlobTxSameSigner( t *testing.T, enc sdk.TxEncoder, signer *blobtypes.KeyringSigner, blobSizes [][]int, + sequence, accountNum uint64, ) []coretypes.Tx { txs := make([]coretypes.Tx, len(blobSizes)) for i := 0; i < len(blobSizes); i++ { - txs[i] = MultiBlobTx(t, enc, signer, ManyRandBlobs(t, blobSizes[i]...)...) + txs[i] = MultiBlobTx(t, enc, signer, sequence+uint64(i), accountNum, ManyRandBlobs(t, blobSizes[i]...)...) } return txs } @@ -324,12 +326,13 @@ func ManyMultiBlobTx( kr keyring.Keyring, chainid string, accounts []string, + accInfos []AccountInfo, blobs [][]*tmproto.Blob, ) [][]byte { txs := make([][]byte, len(accounts)) for i, acc := range accounts { signer := blobtypes.NewKeyringSigner(kr, acc, chainid) - txs[i] = MultiBlobTx(t, enc, signer, blobs[i]...) + txs[i] = MultiBlobTx(t, enc, signer, accInfos[i].Sequence, accInfos[i].AccountNum, blobs[i]...) } return txs } @@ -338,6 +341,7 @@ func MultiBlobTx( t *testing.T, enc sdk.TxEncoder, signer *blobtypes.KeyringSigner, + sequence, accountNum uint64, blobs ...*tmproto.Blob, ) coretypes.Tx { addr, err := signer.GetSignerInfo().GetAddress() @@ -354,6 +358,9 @@ func MultiBlobTx( msg, err := blobtypes.NewMsgPayForBlobs(addr.String(), blobs...) require.NoError(t, err) + signer.SetAccountNumber(accountNum) + signer.SetSequence(sequence) + builder := signer.NewTxBuilder(opts...) stx, err := signer.BuildSignedTx(builder, msg) require.NoError(t, err) diff --git a/testutil/blobfactory/test_util.go b/testutil/blobfactory/test_util.go index c43e2fdba9..b69634e84c 100644 --- a/testutil/blobfactory/test_util.go +++ b/testutil/blobfactory/test_util.go @@ -21,16 +21,16 @@ func GenerateManyRawSendTxs(txConfig client.TxConfig, count int) []coretypes.Tx signer := blobtypes.NewKeyringSigner(kr, acc, "chainid") txs := make([]coretypes.Tx, count) for i := 0; i < count; i++ { - txs[i] = generateRawSendTx(txConfig, signer, 100) + txs[i] = GenerateRawSendTx(txConfig, signer, 100) } return txs } -// generateRawSendTx creates send transactions meant to help test encoding/prepare/process +// GenerateRawSendTx creates send transactions meant to help test encoding/prepare/process // proposal, they are not meant to actually be executed by the state machine. If // we want that, we have to update nonce, and send funds to someone other than // the same account signing the transaction. -func generateRawSendTx(txConfig client.TxConfig, signer *blobtypes.KeyringSigner, amount int64) (rawTx []byte) { +func GenerateRawSendTx(txConfig client.TxConfig, signer *blobtypes.KeyringSigner, amount int64) (rawTx []byte) { feeCoin := sdk.Coin{ Denom: bondDenom, Amount: sdk.NewInt(1), @@ -53,10 +53,10 @@ func generateRawSendTx(txConfig client.TxConfig, signer *blobtypes.KeyringSigner msg := banktypes.NewMsgSend(addr, addr, sdk.NewCoins(amountCoin)) - return genrateRawTx(txConfig, msg, signer, opts...) + return CreateRawTx(txConfig, msg, signer, opts...) } -func genrateRawTx(txConfig client.TxConfig, msg sdk.Msg, signer *blobtypes.KeyringSigner, opts ...blobtypes.TxBuilderOption) []byte { +func CreateRawTx(txConfig client.TxConfig, msg sdk.Msg, signer *blobtypes.KeyringSigner, opts ...blobtypes.TxBuilderOption) []byte { builder := signer.NewTxBuilder(opts...) tx, err := signer.BuildSignedTx(builder, msg) if err != nil { diff --git a/testutil/direct_tx_gen.go b/testutil/direct_tx_gen.go new file mode 100644 index 0000000000..8e54cd7736 --- /dev/null +++ b/testutil/direct_tx_gen.go @@ -0,0 +1,272 @@ +package testutil + +import ( + "math/rand" + "testing" + + "github.com/celestiaorg/celestia-app/app" + "github.com/celestiaorg/celestia-app/testutil/blobfactory" + blobtypes "github.com/celestiaorg/celestia-app/x/blob/types" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/stretchr/testify/require" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + coretypes "github.com/tendermint/tendermint/types" +) + +// RandBlobTxsWithAccounts will create random blob transactions using the +// provided configuration. The account info is queried directly from the +// application. One blob transaction is generated per account provided. +func RandBlobTxsWithAccounts( + t *testing.T, + capp *app.App, + enc sdk.TxEncoder, + kr keyring.Keyring, + size int, + blobCount int, + randSize bool, + chainid string, + accounts []string, + extraOpts ...blobtypes.TxBuilderOption, +) []coretypes.Tx { + coin := sdk.Coin{ + Denom: app.BondDenom, + Amount: sdk.NewInt(10), + } + + opts := []blobtypes.TxBuilderOption{ + blobtypes.SetFeeAmount(sdk.NewCoins(coin)), + blobtypes.SetGasLimit(100000000000000), + } + opts = append(opts, extraOpts...) + + txs := make([]coretypes.Tx, len(accounts)) + for i := 0; i < len(accounts); i++ { + signer := blobtypes.NewKeyringSigner(kr, accounts[i], chainid) + + addr, err := signer.GetSignerInfo().GetAddress() + if err != nil { + panic(err) + } + + // update the account info in the signer so the signature is valid + acc := DirectQueryAccount(capp, addr) + signer.SetAccountNumber(0) + signer.SetSequence(acc.GetSequence()) + + if size <= 0 { + panic("size should be positive") + } + randomizedSize := size + if randSize { + randomizedSize = rand.Intn(size) + if randomizedSize == 0 { + randomizedSize = 1 + } + } + if blobCount <= 0 { + panic("blobCount should be strictly positive") + } + randomizedBlobCount := blobCount + if randSize { + randomizedBlobCount = rand.Intn(blobCount) + if randomizedBlobCount == 0 { + randomizedBlobCount = 1 + } + } + + msg, blobs := blobfactory.RandMsgPayForBlobsWithSigner(addr.String(), randomizedSize, randomizedBlobCount) + builder := signer.NewTxBuilder(opts...) + stx, err := signer.BuildSignedTx(builder, msg) + if err != nil { + panic(err) + } + + rawTx, err := signer.EncodeTx(stx) + if err != nil { + panic(err) + } + cTx, err := coretypes.MarshalBlobTx(rawTx, blobs...) + if err != nil { + panic(err) + } + + txs[i] = cTx + } + + return txs +} + +func DirectQueryAccount(app *app.App, addr sdk.AccAddress) authtypes.AccountI { + ctx := app.NewContext(true, tmproto.Header{}) + return app.AccountKeeper.GetAccount(ctx, addr) +} + +// RandBlobTxsWithManualSequence will create random blob transactions using the +// provided configuration. One blob transaction is generated per account +// provided. The sequence and account numbers are set manually using the provided values. +func RandBlobTxsWithManualSequence( + t *testing.T, + enc sdk.TxEncoder, + kr keyring.Keyring, + size int, + blobCount int, + randSize bool, + chainid string, + accounts []string, + sequence, accountNum uint64, +) []coretypes.Tx { + coin := sdk.Coin{ + Denom: app.BondDenom, + Amount: sdk.NewInt(10), + } + + opts := []blobtypes.TxBuilderOption{ + blobtypes.SetFeeAmount(sdk.NewCoins(coin)), + blobtypes.SetGasLimit(100000000000000), + } + + txs := make([]coretypes.Tx, len(accounts)) + for i := 0; i < len(accounts); i++ { + signer := blobtypes.NewKeyringSigner(kr, accounts[i], chainid) + + addr, err := signer.GetSignerInfo().GetAddress() + if err != nil { + panic(err) + } + + signer.SetAccountNumber(accountNum) + signer.SetSequence(sequence) + + if size <= 0 { + panic("size should be positive") + } + randomizedSize := size + if randSize { + randomizedSize = rand.Intn(size) + if randomizedSize == 0 { + randomizedSize = 1 + } + } + if blobCount <= 0 { + panic("blobCount should be strictly positive") + } + randomizedBlobCount := blobCount + if randSize { + randomizedBlobCount = rand.Intn(blobCount) + if randomizedBlobCount == 0 { + randomizedBlobCount = 1 + } + } + msg, blobs := blobfactory.RandMsgPayForBlobsWithSigner(addr.String(), randomizedSize, randomizedBlobCount) + builder := signer.NewTxBuilder(opts...) + stx, err := signer.BuildSignedTx(builder, msg) + if err != nil { + panic(err) + } + rawTx, err := signer.EncodeTx(stx) + if err != nil { + panic(err) + } + cTx, err := coretypes.MarshalBlobTx(rawTx, blobs...) + if err != nil { + panic(err) + } + txs[i] = cTx + } + + return txs +} + +// SendTxsWithAccounts will create a send transaction per account provided, and +// send all funds to the "toAccount". The account info is queried directly from +// the application. +func SendTxsWithAccounts( + t *testing.T, + capp *app.App, + enc sdk.TxEncoder, + kr keyring.Keyring, + amount uint64, + toAccount string, + accounts []string, + chainid string, + extraOpts ...blobtypes.TxBuilderOption, +) []coretypes.Tx { + coin := sdk.Coin{ + Denom: app.BondDenom, + Amount: sdk.NewInt(10), + } + + opts := []blobtypes.TxBuilderOption{ + blobtypes.SetFeeAmount(sdk.NewCoins(coin)), + blobtypes.SetGasLimit(1000000), + } + opts = append(opts, extraOpts...) + + txs := make([]coretypes.Tx, len(accounts)) + for i := 0; i < len(accounts); i++ { + signingAddr := getAddress(accounts[i], kr) + + // update the account info in the signer so the signature is valid + acc := DirectQueryAccount(capp, signingAddr) + + txs[i] = SendTxWithManualSequence( + t, + enc, + kr, + accounts[i], + toAccount, + amount, + chainid, + acc.GetSequence(), + acc.GetAccountNumber(), + opts..., + ) + } + + return txs +} + +// SendTxsWithAccounts will create a send transaction per account provided. The +// account info must be provided. +func SendTxWithManualSequence( + t *testing.T, + enc sdk.TxEncoder, + kr keyring.Keyring, + fromAcc, toAcc string, + amount uint64, + chainid string, + sequence, accountNum uint64, + opts ...blobtypes.TxBuilderOption, +) coretypes.Tx { + signer := blobtypes.NewKeyringSigner(kr, fromAcc, chainid) + + signer.SetAccountNumber(accountNum) + signer.SetSequence(sequence) + + fromAddr, toAddr := getAddress(fromAcc, kr), getAddress(toAcc, kr) + + msg := banktypes.NewMsgSend(fromAddr, toAddr, sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewIntFromUint64(amount)))) + + stx, err := signer.BuildSignedTx(signer.NewTxBuilder(opts...), msg) + require.NoError(t, err) + + rawTx, err := signer.EncodeTx(stx) + require.NoError(t, err) + + return rawTx +} + +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 +} diff --git a/testutil/testfactory/utils.go b/testutil/testfactory/utils.go index 1fb2634921..8c9b7ba1ab 100644 --- a/testutil/testfactory/utils.go +++ b/testutil/testfactory/utils.go @@ -95,3 +95,11 @@ func FundKeyringAccounts(cdc codec.Codec, accounts ...string) (keyring.Keyring, } return kr, genBalances, genAccounts } + +func GenerateAccounts(count int) []string { + accs := make([]string, count) + for i := 0; i < count; i++ { + accs[i] = tmrand.Str(20) + } + return accs +} diff --git a/x/blob/types/test/blob_tx_test.go b/x/blob/types/test/blob_tx_test.go index e6367ce9e7..5088dccad2 100644 --- a/x/blob/types/test/blob_tx_test.go +++ b/x/blob/types/test/blob_tx_test.go @@ -161,6 +161,7 @@ func TestValidateBlobTx(t *testing.T) { t, encCfg.TxConfig.TxEncoder(), signer, + 0, 0, blobfactory.RandBlobsWithNamespace( [][]byte{namespace.RandomBlobNamespace(), namespace.RandomBlobNamespace()}, []int{100, 100})..., @@ -178,6 +179,7 @@ func TestValidateBlobTx(t *testing.T) { t, encCfg.TxConfig.TxEncoder(), signer, + 0, 0, blobfactory.RandBlobsWithNamespace( [][]byte{namespace.RandomBlobNamespace(), namespace.RandomBlobNamespace()}, []int{100000, 1000000})..., @@ -196,6 +198,7 @@ func TestValidateBlobTx(t *testing.T) { t, encCfg.TxConfig.TxEncoder(), signer, + 0, 0, blobfactory.RandBlobsWithNamespace( [][]byte{ns, ns}, []int{100, 100})..., @@ -221,6 +224,7 @@ func TestValidateBlobTx(t *testing.T) { t, encCfg.TxConfig.TxEncoder(), signer, + 0, 0, blobfactory.RandBlobsWithNamespace( namespaces, sizes,