From 27632ddc804fbf7655ef5ba941eb4f346c539be9 Mon Sep 17 00:00:00 2001 From: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Date: Thu, 15 Sep 2022 20:29:28 -0500 Subject: [PATCH] test: add test to build random blocks and check if they're valid (#715) * test: add test to build random blocks and check if they're valid * fix: test util func docs Co-authored-by: Rootul P Co-authored-by: Rootul P --- app/test/fuzz_abci_test.go | 53 +++++++++++++ testutil/payment/testutil.go | 141 +++++++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 app/test/fuzz_abci_test.go create mode 100644 testutil/payment/testutil.go diff --git a/app/test/fuzz_abci_test.go b/app/test/fuzz_abci_test.go new file mode 100644 index 0000000000..9c8587107c --- /dev/null +++ b/app/test/fuzz_abci_test.go @@ -0,0 +1,53 @@ +package app_test + +import ( + "testing" + "time" + + "github.com/celestiaorg/celestia-app/app" + "github.com/celestiaorg/celestia-app/app/encoding" + "github.com/celestiaorg/celestia-app/testutil" + paytestutil "github.com/celestiaorg/celestia-app/testutil/payment" + "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" +) + +// TestFuzzPrepareProcessProposal produces blocks with random data using +// PrepareProposal and then tests those blocks by calling ProcessProposal. All +// blocks produced by PrepareProposal should be accepted by ProcessProposal. It +// doesn't use the standard go tools for fuzzing as those tools only support +// fuzzing limited types, which forces us to create random block data ourselves +// anyway. We also want to run this test alongside the other tests and not just +// when fuzzing. +func TestFuzzPrepareProcessProposal(t *testing.T) { + encConf := encoding.MakeConfig(app.ModuleEncodingRegisters...) + signer := testutil.GenerateKeyringSigner(t, testAccName) + testApp := testutil.SetupTestAppWithGenesisValSet(t) + timer := time.After(time.Second * 30) + for { + select { + case <-timer: + return + default: + t.Run("randomized inputs to Prepare and Process Proposal", func(t *testing.T) { + pfdTxs := paytestutil.GenerateManyRawWirePFD(t, encConf.TxConfig, signer, tmrand.Intn(100), -1) + txs := paytestutil.GenerateManyRawSendTxs(t, encConf.TxConfig, signer, tmrand.Intn(20)) + txs = append(txs, pfdTxs...) + resp := testApp.PrepareProposal(abci.RequestPrepareProposal{ + BlockData: &core.Data{ + Txs: txs, + }, + }) + res := testApp.ProcessProposal(abci.RequestProcessProposal{ + BlockData: resp.BlockData, + Header: core.Header{ + DataHash: resp.BlockData.Hash, + }, + }) + require.Equal(t, abci.ResponseProcessProposal_ACCEPT, res.Result) + }) + } + } +} diff --git a/testutil/payment/testutil.go b/testutil/payment/testutil.go new file mode 100644 index 0000000000..55b3af4da5 --- /dev/null +++ b/testutil/payment/testutil.go @@ -0,0 +1,141 @@ +package paytestutil + +import ( + "bytes" + "testing" + + "github.com/celestiaorg/celestia-app/app" + "github.com/celestiaorg/celestia-app/pkg/appconsts" + "github.com/celestiaorg/celestia-app/x/payment/types" + "github.com/celestiaorg/nmt/namespace" + "github.com/cosmos/cosmos-sdk/client" + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/stretchr/testify/require" + tmrand "github.com/tendermint/tendermint/libs/rand" +) + +// GenerateManyRawWirePFD creates many raw WirePayForData transactions. Using +// negative numbers for count and size will randomize those values. count is +// capped at 5000 and size is capped at 3MB. Going over these caps will result +// in randomized values. +func GenerateManyRawWirePFD(t *testing.T, txConfig client.TxConfig, signer *types.KeyringSigner, count, size int) [][]byte { + // hardcode a maximum of 5000 transactions so that we can use this for fuzzing + if count > 5000 || count < 0 { + count = tmrand.Intn(5000) + } + txs := make([][]byte, count) + + coin := sdk.Coin{ + Denom: app.BondDenom, + Amount: sdk.NewInt(10), + } + + opts := []types.TxBuilderOption{ + types.SetFeeAmount(sdk.NewCoins(coin)), + types.SetGasLimit(10000000), + } + + for i := 0; i < count; i++ { + if size < 0 || size > 3000000 { + size = tmrand.Intn(1000000) + } + wpfdTx := generateRawWirePFDTx( + t, + txConfig, + randomValidNamespace(), + tmrand.Bytes(size), + signer, + opts..., + ) + txs[i] = wpfdTx + } + return txs +} + +func GenerateManyRawSendTxs(t *testing.T, txConfig client.TxConfig, signer *types.KeyringSigner, count int) [][]byte { + txs := make([][]byte, count) + for i := 0; i < count; i++ { + txs[i] = generateRawSendTx(t, txConfig, signer, 100) + } + return txs +} + +// this 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(t *testing.T, txConfig client.TxConfig, signer *types.KeyringSigner, amount int64) (rawTx []byte) { + feeCoin := sdk.Coin{ + Denom: app.BondDenom, + Amount: sdk.NewInt(1), + } + + opts := []types.TxBuilderOption{ + types.SetFeeAmount(sdk.NewCoins(feeCoin)), + types.SetGasLimit(1000000000), + } + + amountCoin := sdk.Coin{ + Denom: app.BondDenom, + Amount: sdk.NewInt(amount), + } + + addr, err := signer.GetSignerInfo().GetAddress() + require.NoError(t, err) + + builder := signer.NewTxBuilder(opts...) + + msg := banktypes.NewMsgSend(addr, addr, sdk.NewCoins(amountCoin)) + + tx, err := signer.BuildSignedTx(builder, msg) + require.NoError(t, err) + + rawTx, err = txConfig.TxEncoder()(tx) + require.NoError(t, err) + + return rawTx +} + +// generateRawWirePFDTx creates a tx with a single MsgWirePayForData message using the provided namespace and message +func generateRawWirePFDTx(t *testing.T, txConfig client.TxConfig, ns, message []byte, signer *types.KeyringSigner, opts ...types.TxBuilderOption) (rawTx []byte) { + // create a msg + msg := generateSignedWirePayForData(t, ns, message, signer, opts, types.AllSquareSizes(len(message))...) + + builder := signer.NewTxBuilder(opts...) + tx, err := signer.BuildSignedTx(builder, msg) + require.NoError(t, err) + + // encode the tx + rawTx, err = txConfig.TxEncoder()(tx) + require.NoError(t, err) + + return rawTx +} + +func generateSignedWirePayForData(t *testing.T, ns, message []byte, signer *types.KeyringSigner, options []types.TxBuilderOption, ks ...uint64) *types.MsgWirePayForData { + msg, err := types.NewWirePayForData(ns, message, ks...) + if err != nil { + t.Error(err) + } + + err = msg.SignShareCommitments(signer, options...) + if err != nil { + t.Error(err) + } + + return msg +} + +const ( + TestAccountName = "test-account" +) + +func randomValidNamespace() namespace.ID { + for { + s := tmrand.Bytes(8) + if bytes.Compare(s, appconsts.MaxReservedNamespace) > 0 { + return s + } + } +}