diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f829b12c3..debfddb4ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ### Bug fixes +* [#2175](https://github.com/crypto-org-chain/cronos/pull/2175) fix(rpc): guard `TxsResults` length before indexing by block tx position. * [#2176](https://github.com/crypto-org-chain/cronos/pull/2176) fix(app): retry block list decryption instead of caching the blob before it is applied. * [#2155](https://github.com/crypto-org-chain/cronos/pull/2155) fix(mempool): size tx-cache-size and max-tx-bytes from mempool config directly. * [#2169](https://github.com/crypto-org-chain/cronos/pull/2169) fix(cronos): add safe multiply int check during voucher conversion to EVM coins. diff --git a/x/cronos/rpc/api.go b/x/cronos/rpc/api.go index e25f9990cd..8b546b0f61 100644 --- a/x/cronos/rpc/api.go +++ b/x/cronos/rpc/api.go @@ -86,6 +86,16 @@ func NewCronosAPI( } } +func checkTxsResultsLength(block *coretypes.ResultBlock, blockResults *coretypes.ResultBlockResults) error { + if len(blockResults.TxsResults) != len(block.Block.Txs) { + return fmt.Errorf( + "mismatched tx results length at height %d: block has %d txs, but got %d tx results", + block.Block.Height, len(block.Block.Txs), len(blockResults.TxsResults), + ) + } + return nil +} + func (api *CronosAPI) getBlockDetail(blockNrOrHash rpctypes.BlockNumberOrHash) ( resBlock *coretypes.ResultBlock, blockNumber int64, @@ -94,22 +104,24 @@ func (api *CronosAPI) getBlockDetail(blockNrOrHash rpctypes.BlockNumberOrHash) ( baseFee *big.Int, err error, ) { - var blockNum rpctypes.BlockNumber resBlock, err = api.getBlock(blockNrOrHash) if err != nil { api.logger.Debug("block not found", "height", blockNrOrHash, "error", err.Error()) - return resBlock, blockNumber, blockHash, blockRes, baseFee, err + return nil, 0, "", nil, nil, err } blockNumber = resBlock.Block.Height blockHash = common.BytesToHash(resBlock.Block.Header.Hash()).Hex() blockRes, err = api.backend.TendermintBlockResultByNumber(&blockNumber) if err != nil { - api.logger.Debug("failed to retrieve block results", "height", blockNum, "error", err.Error()) - return resBlock, blockNumber, blockHash, blockRes, baseFee, err + api.logger.Debug("failed to retrieve block results", "height", blockNumber, "error", err.Error()) + return nil, 0, "", nil, nil, err + } + if err = checkTxsResultsLength(resBlock, blockRes); err != nil { + return nil, 0, "", nil, nil, err } baseFee, err = api.backend.BaseFee(blockRes) if err != nil { - return resBlock, blockNumber, blockHash, blockRes, baseFee, err + return nil, 0, "", nil, nil, err } return resBlock, blockNumber, blockHash, blockRes, baseFee, err } diff --git a/x/cronos/rpc/api_test.go b/x/cronos/rpc/api_test.go new file mode 100644 index 0000000000..e1c9861a18 --- /dev/null +++ b/x/cronos/rpc/api_test.go @@ -0,0 +1,81 @@ +package rpc + +import ( + "testing" + + abci "github.com/cometbft/cometbft/abci/types" + coretypes "github.com/cometbft/cometbft/rpc/core/types" + cmttypes "github.com/cometbft/cometbft/types" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +func newResBlock(t *testing.T, numTxs int) *coretypes.ResultBlock { + t.Helper() + txs := make(cmttypes.Txs, numTxs) + for i := range txs { + txs[i] = cmttypes.Tx{byte(i)} + } + return &coretypes.ResultBlock{ + Block: &cmttypes.Block{ + Header: cmttypes.Header{Height: 100}, + Data: cmttypes.Data{Txs: txs}, + }, + } +} + +func newBlockRes(t *testing.T, numResults int) *coretypes.ResultBlockResults { + t.Helper() + results := make([]*abci.ExecTxResult, numResults) + for i := range results { + results[i] = &abci.ExecTxResult{} + } + return &coretypes.ResultBlockResults{TxsResults: results} +} + +type CheckTxsResultsLengthTestSuite struct { + suite.Suite +} + +func TestCheckTxsResultsLengthTestSuite(t *testing.T) { + suite.Run(t, new(CheckTxsResultsLengthTestSuite)) +} + +func (s *CheckTxsResultsLengthTestSuite) TestCheckTxsResultsLength() { + testCases := []struct { + name string + numTxs int + numRes int + wantErr string + }{ + { + name: "matching lengths returns no error", + numTxs: 3, + numRes: 3, + }, + { + name: "fewer tx results than txs returns error instead of panicking", + numTxs: 3, + numRes: 2, + wantErr: "mismatched tx results length at height 100: block has 3 txs, but got 2 tx results", + }, + { + name: "more tx results than txs returns error", + numTxs: 2, + numRes: 3, + wantErr: "mismatched tx results length at height 100: block has 2 txs, but got 3 tx results", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + t := s.T() + err := checkTxsResultsLength(newResBlock(t, tc.numTxs), newBlockRes(t, tc.numRes)) + if tc.wantErr == "" { + require.NoError(t, err) + return + } + require.EqualError(t, err, tc.wantErr) + }) + } +}