Skip to content

Commit

Permalink
chore: add test for tx priority (#2394)
Browse files Browse the repository at this point in the history
## Overview

adds a simple integration test for tx priority in the mempool

closes #513

## Checklist

- [x] New and updated code has appropriate documentation
- [x] New and updated code has new and/or updated testing
- [x] Required CI checks are passing
- [x] Visual proof for any user facing features like CLI or
documentation updates
- [x] Linked issues closed with keywords

---------

Co-authored-by: Rootul Patel <rootulp@gmail.com>
(cherry picked from commit 58c1e4e)

# Conflicts:
#	Makefile
  • Loading branch information
evan-forbes authored and mergify[bot] committed Aug 31, 2023
1 parent e3322bc commit f5db4f2
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 0 deletions.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,11 @@ test-short:
## test-race: Run unit tests in race mode.
test-race:
@echo "--> Running tests in race mode"
<<<<<<< HEAD
@VERSION=$(VERSION) go test -race -short ./...
=======
@go test ./... -v -race -skip "TestPrepareProposalConsistency|TestIntegrationTestSuite|TestQGBRPCQueries|TestSquareSizeIntegrationTest|TestStandardSDKIntegrationTestSuite|TestTxsimCommandFlags|TestTxsimCommandEnvVar|TestMintIntegrationTestSuite|TestQGBCLI|TestUpgrade|TestMaliciousTestNode|TestMaxTotalBlobSizeSuite|TestQGBIntegrationSuite|TestSignerTestSuite|TestPriorityTestSuite"
>>>>>>> 58c1e4e (chore: add test for tx priority (#2394))
.PHONY: test-race

## test-bench: Run unit tests in bench mode.
Expand Down
154 changes: 154 additions & 0 deletions app/test/priority_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package app_test

import (
"encoding/hex"
"sort"
"testing"
"time"

"github.com/celestiaorg/celestia-app/app"
"github.com/celestiaorg/celestia-app/app/encoding"
"github.com/celestiaorg/celestia-app/pkg/namespace"
"github.com/celestiaorg/celestia-app/pkg/user"
"github.com/celestiaorg/celestia-app/test/util/blobfactory"
"github.com/celestiaorg/celestia-app/test/util/testfactory"
"github.com/celestiaorg/celestia-app/test/util/testnode"
blobtypes "github.com/celestiaorg/celestia-app/x/blob/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
tmrand "github.com/tendermint/tendermint/libs/rand"
rpctypes "github.com/tendermint/tendermint/rpc/core/types"
)

func TestPriorityTestSuite(t *testing.T) {
if testing.Short() {
t.Skip("skipping app/test/priority_test in short mode.")
}
suite.Run(t, &PriorityTestSuite{})
}

type PriorityTestSuite struct {
suite.Suite

ecfg encoding.Config
signers []*user.Signer
cctx testnode.Context

rand *tmrand.Rand
}

func (s *PriorityTestSuite) SetupSuite() {
t := s.T()

cfg := testnode.DefaultConfig().
WithAccounts(testfactory.GenerateAccounts(10)).
// use a long block time to guarantee that some transactions are included in the same block
WithTimeoutCommit(time.Second)

cctx, _, _ := testnode.NewNetwork(t, cfg)
s.cctx = cctx
s.ecfg = encoding.MakeConfig(app.ModuleEncodingRegisters...)
s.rand = tmrand.NewRand()

require.NoError(t, cctx.WaitForNextBlock())

for _, acc := range cfg.Accounts {
addr := testfactory.GetAddress(s.cctx.Keyring, acc)
signer, err := user.SetupSigner(s.cctx.GoContext(), s.cctx.Keyring, s.cctx.GRPCClient, addr, s.ecfg)
signer.SetPollTime(time.Millisecond * 300)
require.NoError(t, err)
s.signers = append(s.signers, signer)
}
}

// TestPriorityByGasPrice tests that transactions are sorted by gas price when
// they are included in a block. It does this by submitting blobs with random
// gas prices, and then compares the ordering of the transactions after they are
// committed.
func (s *PriorityTestSuite) TestPriorityByGasPrice() {
t := s.T()

// quickly submit blobs with a random fee
hashes := make([]string, 0, len(s.signers))
for _, signer := range s.signers {
blobSize := uint32(100)
gasLimit := blobtypes.DefaultEstimateGas([]uint32{blobSize})
gasPrice := s.rand.Float64()
btx, err := signer.CreatePayForBlob(
blobfactory.ManyBlobs(
t,
s.rand,
[]namespace.Namespace{namespace.RandomBlobNamespace()},
[]int{100}),
user.SetGasLimitAndFee(gasLimit, gasPrice),
)
require.NoError(t, err)
resp, err := signer.BroadcastTx(s.cctx.GoContext(), btx)
require.NoError(t, err)
require.Equal(t, abci.CodeTypeOK, resp.Code)
hashes = append(hashes, resp.TxHash)
}

err := s.cctx.WaitForNextBlock()
require.NoError(t, err)

// get the responses for each tx for analysis and sort by height
// note: use rpc types because they contain the tx index
heightMap := make(map[int64][]*rpctypes.ResultTx)
for _, hash := range hashes {
resp, err := s.signers[0].ConfirmTx(s.cctx.GoContext(), hash)
require.NoError(t, err)
require.NotNil(t, resp)
require.Equal(t, abci.CodeTypeOK, resp.Code)
// use the core rpc type because it contains the tx index
hash, err := hex.DecodeString(hash)
require.NoError(t, err)
coreRes, err := s.cctx.Client.Tx(s.cctx.GoContext(), hash, false)
require.NoError(t, err)
heightMap[resp.Height] = append(heightMap[resp.Height], coreRes)
}
require.GreaterOrEqual(t, len(heightMap), 1)

// check that the transactions in each height are sorted by fee after
// sorting by index
highestNumOfTxsPerBlock := 0
for _, responses := range heightMap {
responses = sortByIndex(responses)
require.True(t, isSortedByFee(t, s.ecfg, responses))
if len(responses) > highestNumOfTxsPerBlock {
highestNumOfTxsPerBlock = len(responses)
}
}

// check that there was at least one block with more than three transactions
// in it. This is more of a sanity check than a test.
require.True(t, highestNumOfTxsPerBlock > 3)
}

func sortByIndex(txs []*rpctypes.ResultTx) []*rpctypes.ResultTx {
sort.Slice(txs, func(i, j int) bool {
return txs[i].Index < txs[j].Index
})
return txs
}

func isSortedByFee(t *testing.T, ecfg encoding.Config, responses []*rpctypes.ResultTx) bool {
for i := 0; i < len(responses)-1; i++ {
if gasPrice(t, ecfg, responses[i]) <= gasPrice(t, ecfg, responses[i+1]) {
return false
}
}
return true
}

func gasPrice(t *testing.T, ecfg encoding.Config, resp *rpctypes.ResultTx) float64 {
sdkTx, err := ecfg.TxConfig.TxDecoder()(resp.Tx)
require.NoError(t, err)
feeTx := sdkTx.(sdk.FeeTx)
fee := feeTx.GetFee().AmountOf(app.BondDenom).Uint64()
gas := feeTx.GetGas()
price := float64(fee) / float64(gas)
return price
}
15 changes: 15 additions & 0 deletions pkg/user/tx_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,21 @@ func SetFeeGranter(feeGranter sdk.AccAddress) TxOption {
}
}

// SetGasLimitAndFee sets the gas limit and fee using the provided gas price and
// gas limit. Note that this could overwrite or be overwritten by other
// conflicting TxOptions.
func SetGasLimitAndFee(gasLimit uint64, gasPrice float64) TxOption {
return func(builder sdkclient.TxBuilder) sdkclient.TxBuilder {
builder.SetGasLimit(gasLimit)
builder.SetFeeAmount(
sdk.NewCoins(
sdk.NewCoin(appconsts.BondDenom, sdk.NewInt(int64(gasPrice*float64(gasLimit)))),
),
)
return builder
}
}

// InheritTxConfig sets all of the accessible configurations from a given tx
// into a given client.TxBuilder
func InheritTxConfig(builder sdkclient.TxBuilder, tx authsigning.Tx) sdkclient.TxBuilder {
Expand Down

0 comments on commit f5db4f2

Please sign in to comment.