fix(compare): Adding Sequence Number check on Compare Priority (#159)
* adding seq num check on compare * nit * adding debug logging
This commit is contained in:
parent
339b927323
commit
aff0e228a3
@ -23,6 +23,8 @@
|
||||
|
||||
### 🤔 What is the Block SDK?
|
||||
|
||||
> **Note**: The Block SDK is midway through an audit. Please use at your own risk. Timeline for audit completion is early November.
|
||||
|
||||
**🌐 The Block SDK is a toolkit for building customized blocks**. The Block SDK is a set of Cosmos SDK and ABCI++ primitives that allow chains to fully customize blocks to specific use cases. It turns your chain's blocks into a **`highway`** consisting of individual **`lanes`** with their own special functionality.
|
||||
|
||||
|
||||
|
||||
@ -75,7 +75,7 @@ func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler {
|
||||
)
|
||||
|
||||
// Fill the proposal with transactions from each lane.
|
||||
finalProposal, err := h.prepareLanesHandler(ctx, proposals.NewProposalWithContext(ctx, h.txEncoder))
|
||||
finalProposal, err := h.prepareLanesHandler(ctx, proposals.NewProposalWithContext(h.logger, ctx, h.txEncoder))
|
||||
if err != nil {
|
||||
h.logger.Error("failed to prepare proposal", "err", err)
|
||||
return &abci.ResponsePrepareProposal{Txs: make([][]byte, 0)}, err
|
||||
@ -141,7 +141,7 @@ func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler {
|
||||
|
||||
// Build handler that will verify the partial proposals according to each lane's verification logic.
|
||||
processLanesHandler := ChainProcessLanes(partialProposals, h.mempool.Registry())
|
||||
finalProposal, err := processLanesHandler(ctx, proposals.NewProposalWithContext(ctx, h.txEncoder))
|
||||
finalProposal, err := processLanesHandler(ctx, proposals.NewProposalWithContext(h.logger, ctx, h.txEncoder))
|
||||
if err != nil {
|
||||
h.logger.Error("failed to validate the proposal", "err", err)
|
||||
return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, err
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package abci_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
@ -862,6 +863,54 @@ func (s *ProposalsTestSuite) TestProcessProposal() {
|
||||
s.Require().Equal(&cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_ACCEPT}, resp)
|
||||
})
|
||||
|
||||
s.Run("can accept proposal where txs are broadcasted with different sequence numbers", func() {
|
||||
tx1, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(1)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
tx2, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(2)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
defaultLane := s.setUpStandardLane(math.LegacyMustNewDecFromStr("0.0"), map[sdk.Tx]bool{
|
||||
tx1: true,
|
||||
tx2: true,
|
||||
})
|
||||
|
||||
defaultLane.Insert(sdk.Context{}, tx1)
|
||||
defaultLane.Insert(sdk.Context{}, tx2)
|
||||
|
||||
txs := [][]sdk.Tx{}
|
||||
|
||||
for iterator := defaultLane.Select(context.Background(), nil); iterator != nil; iterator = iterator.Next() {
|
||||
txs = append(txs, []sdk.Tx{iterator.Tx()})
|
||||
}
|
||||
|
||||
s.Require().Equal(2, len(txs))
|
||||
|
||||
proposal := s.createProposal(map[string]uint64{defaultLane.Name(): 2}, tx1, tx2)
|
||||
|
||||
proposalHandler := s.setUpProposalHandlers([]block.Lane{defaultLane}).ProcessProposalHandler()
|
||||
resp, err := proposalHandler(s.ctx, &cometabci.RequestProcessProposal{Txs: proposal, Height: 2})
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(resp)
|
||||
s.Require().Equal(&cometabci.ResponseProcessProposal{Status: cometabci.ResponseProcessProposal_ACCEPT}, resp)
|
||||
})
|
||||
|
||||
s.Run("can process a valid proposal with a single tx", func() {
|
||||
// Create a random transaction that will be inserted into the default lane
|
||||
tx, err := testutils.CreateRandomTx(
|
||||
@ -1271,6 +1320,227 @@ func (s *ProposalsTestSuite) TestProcessProposal() {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *ProposalsTestSuite) TestPrepareProcessParity() {
|
||||
// Define a large enough block size and gas limit to ensure that the proposal is accepted
|
||||
s.setBlockParams(1000000000000, 1000000000000)
|
||||
|
||||
// Create a random transaction that will be inserted into the default/free lane
|
||||
numTxsPerAccount := uint64(25)
|
||||
numAccounts := 25
|
||||
accounts := testutils.RandomAccounts(s.random, numAccounts)
|
||||
|
||||
// Create a bunch of transactions to insert into the default lane
|
||||
txsToInsert := []sdk.Tx{}
|
||||
validationMap := make(map[sdk.Tx]bool)
|
||||
for _, account := range accounts {
|
||||
for nonce := uint64(0); nonce < numTxsPerAccount; nonce++ {
|
||||
// create a random fee amount
|
||||
feeAmount := math.NewInt(int64(rand.Intn(100000)))
|
||||
tx, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
account,
|
||||
nonce,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
sdk.NewCoin(s.gasTokenDenom, feeAmount),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
txsToInsert = append(txsToInsert, tx)
|
||||
validationMap[tx] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Set up the default lane with the transactions
|
||||
defaultLane := s.setUpStandardLane(math.LegacyMustNewDecFromStr("0.0"), validationMap)
|
||||
for _, tx := range txsToInsert {
|
||||
s.Require().NoError(defaultLane.Insert(s.ctx, tx))
|
||||
}
|
||||
|
||||
// Create a bunch of transactions to insert into the free lane
|
||||
freeTxsToInsert := []sdk.Tx{}
|
||||
freeValidationMap := make(map[sdk.Tx]bool)
|
||||
for _, account := range accounts {
|
||||
for nonce := uint64(0); nonce < numTxsPerAccount; nonce++ {
|
||||
// create a random fee amount
|
||||
feeAmount := math.NewInt(int64(rand.Intn(100000)))
|
||||
tx, err := testutils.CreateFreeTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
account,
|
||||
nonce,
|
||||
1,
|
||||
"test",
|
||||
sdk.NewCoin(s.gasTokenDenom, feeAmount),
|
||||
sdk.NewCoin(s.gasTokenDenom, feeAmount),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
freeTxsToInsert = append(freeTxsToInsert, tx)
|
||||
freeValidationMap[tx] = true
|
||||
}
|
||||
}
|
||||
|
||||
freelane := s.setUpFreeLane(math.LegacyMustNewDecFromStr("0.0"), freeValidationMap)
|
||||
for _, tx := range freeTxsToInsert {
|
||||
s.Require().NoError(freelane.Insert(s.ctx, tx))
|
||||
}
|
||||
|
||||
// Retrieve the transactions from the default lane in the same way the prepare function would.
|
||||
retrievedTxs := []sdk.Tx{}
|
||||
for iterator := defaultLane.Select(context.Background(), nil); iterator != nil; iterator = iterator.Next() {
|
||||
retrievedTxs = append(retrievedTxs, iterator.Tx())
|
||||
}
|
||||
s.Require().Equal(len(txsToInsert), len(retrievedTxs))
|
||||
|
||||
// Retrieve the transactions from the free lane in the same way the prepare function would.
|
||||
freeRetrievedTxs := []sdk.Tx{}
|
||||
for iterator := freelane.Select(context.Background(), nil); iterator != nil; iterator = iterator.Next() {
|
||||
freeRetrievedTxs = append(freeRetrievedTxs, iterator.Tx())
|
||||
}
|
||||
s.Require().Equal(len(freeTxsToInsert), len(freeRetrievedTxs))
|
||||
|
||||
numTxsPerLane := numTxsPerAccount * uint64(numAccounts)
|
||||
s.Require().Equal(numTxsPerLane, uint64(len(retrievedTxs)))
|
||||
s.Require().Equal(numTxsPerLane, uint64(len(freeRetrievedTxs)))
|
||||
|
||||
// Create a proposal with the retrieved transactions
|
||||
// Set up the default lane with no transactions
|
||||
proposalHandler := s.setUpProposalHandlers([]block.Lane{freelane, defaultLane}).PrepareProposalHandler()
|
||||
resp, err := proposalHandler(s.ctx, &cometabci.RequestPrepareProposal{Height: 2})
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(resp)
|
||||
|
||||
info := s.getProposalInfo(resp.Txs[0])
|
||||
s.Require().NotNil(info)
|
||||
s.Require().Equal(2, len(info.TxsByLane))
|
||||
s.Require().Equal(numTxsPerLane, info.TxsByLane[defaultLane.Name()])
|
||||
s.Require().Equal(numTxsPerLane, info.TxsByLane[freelane.Name()])
|
||||
s.Require().Equal(numTxsPerLane*2, uint64(len(resp.Txs)-1))
|
||||
|
||||
// Ensure the transactions are in the correct order for the free lane
|
||||
for i := 0; i < int(numTxsPerLane); i++ {
|
||||
bz, err := s.encodingConfig.TxConfig.TxEncoder()(freeRetrievedTxs[i])
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(bz, resp.Txs[i+1])
|
||||
}
|
||||
|
||||
// Ensure the transactions are in the correct order for the default lane
|
||||
for i := 0; i < int(numTxsPerLane); i++ {
|
||||
bz, err := s.encodingConfig.TxConfig.TxEncoder()(retrievedTxs[i])
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(bz, resp.Txs[i+1+int(numTxsPerLane)])
|
||||
}
|
||||
|
||||
proposal := s.createProposal(
|
||||
map[string]uint64{defaultLane.Name(): numTxsPerLane, freelane.Name(): numTxsPerLane},
|
||||
append(freeRetrievedTxs, retrievedTxs...)...,
|
||||
)
|
||||
|
||||
// Validate the proposal
|
||||
processHandler := s.setUpProposalHandlers([]block.Lane{freelane, defaultLane}).ProcessProposalHandler()
|
||||
processResp, err := processHandler(s.ctx, &cometabci.RequestProcessProposal{Txs: proposal, Height: 2})
|
||||
s.Require().NotNil(processResp)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
func (s *ProposalsTestSuite) TestIterateMempoolAndProcessProposalParity() {
|
||||
// Define a large enough block size and gas limit to ensure that the proposal is accepted
|
||||
s.setBlockParams(1000000000000, 1000000000000)
|
||||
|
||||
// Create a random transaction that will be inserted into the default/free lane
|
||||
numTxsPerAccount := uint64(25)
|
||||
numAccounts := 25
|
||||
accounts := testutils.RandomAccounts(s.random, numAccounts)
|
||||
|
||||
// Create a bunch of transactions to insert into the default lane
|
||||
txsToInsert := []sdk.Tx{}
|
||||
validationMap := make(map[sdk.Tx]bool)
|
||||
for _, account := range accounts {
|
||||
for nonce := uint64(0); nonce < numTxsPerAccount; nonce++ {
|
||||
// create a random fee amount
|
||||
feeAmount := math.NewInt(int64(rand.Intn(100000)))
|
||||
tx, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
account,
|
||||
nonce,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
sdk.NewCoin(s.gasTokenDenom, feeAmount),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
txsToInsert = append(txsToInsert, tx)
|
||||
validationMap[tx] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Set up the default lane with the transactions
|
||||
defaultLane := s.setUpStandardLane(math.LegacyMustNewDecFromStr("0.0"), validationMap)
|
||||
for _, tx := range txsToInsert {
|
||||
s.Require().NoError(defaultLane.Insert(s.ctx, tx))
|
||||
}
|
||||
|
||||
// Create a bunch of transactions to insert into the free lane
|
||||
freeTxsToInsert := []sdk.Tx{}
|
||||
freeValidationMap := make(map[sdk.Tx]bool)
|
||||
for _, account := range accounts {
|
||||
for nonce := uint64(0); nonce < numTxsPerAccount; nonce++ {
|
||||
// create a random fee amount
|
||||
feeAmount := math.NewInt(int64(rand.Intn(100000)))
|
||||
tx, err := testutils.CreateFreeTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
account,
|
||||
nonce,
|
||||
1,
|
||||
"test",
|
||||
sdk.NewCoin(s.gasTokenDenom, feeAmount),
|
||||
sdk.NewCoin(s.gasTokenDenom, feeAmount),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
freeTxsToInsert = append(freeTxsToInsert, tx)
|
||||
freeValidationMap[tx] = true
|
||||
}
|
||||
}
|
||||
|
||||
freelane := s.setUpFreeLane(math.LegacyMustNewDecFromStr("0.0"), freeValidationMap)
|
||||
for _, tx := range freeTxsToInsert {
|
||||
s.Require().NoError(freelane.Insert(s.ctx, tx))
|
||||
}
|
||||
|
||||
// Retrieve the transactions from the default lane in the same way the prepare function would.
|
||||
retrievedTxs := []sdk.Tx{}
|
||||
for iterator := defaultLane.Select(context.Background(), nil); iterator != nil; iterator = iterator.Next() {
|
||||
retrievedTxs = append(retrievedTxs, iterator.Tx())
|
||||
}
|
||||
s.Require().Equal(len(txsToInsert), len(retrievedTxs))
|
||||
|
||||
// Retrieve the transactions from the free lane in the same way the prepare function would.
|
||||
freeRetrievedTxs := []sdk.Tx{}
|
||||
for iterator := freelane.Select(context.Background(), nil); iterator != nil; iterator = iterator.Next() {
|
||||
freeRetrievedTxs = append(freeRetrievedTxs, iterator.Tx())
|
||||
}
|
||||
s.Require().Equal(len(freeTxsToInsert), len(freeRetrievedTxs))
|
||||
|
||||
// Create a proposal with the retrieved transactions
|
||||
numTxsPerLane := numTxsPerAccount * uint64(numAccounts)
|
||||
s.Require().Equal(numTxsPerLane, uint64(len(retrievedTxs)))
|
||||
s.Require().Equal(numTxsPerLane, uint64(len(freeRetrievedTxs)))
|
||||
|
||||
proposal := s.createProposal(
|
||||
map[string]uint64{defaultLane.Name(): numTxsPerLane, freelane.Name(): numTxsPerLane},
|
||||
append(freeRetrievedTxs, retrievedTxs...)...,
|
||||
)
|
||||
|
||||
// Validate the proposal
|
||||
proposalHandler := s.setUpProposalHandlers([]block.Lane{freelane, defaultLane}).ProcessProposalHandler()
|
||||
resp, err := proposalHandler(s.ctx, &cometabci.RequestProcessProposal{Txs: proposal, Height: 2})
|
||||
s.Require().NotNil(resp)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
func (s *ProposalsTestSuite) TestValidateBasic() {
|
||||
// Set up the default lane with no transactions
|
||||
mevlane := s.setUpTOBLane(math.LegacyMustNewDecFromStr("0.25"), nil)
|
||||
|
||||
@ -124,8 +124,10 @@ func (l *BaseLane) DefaultProcessLaneHandler() ProcessLaneHandler {
|
||||
|
||||
// If the transactions do not respect the priority defined by the mempool, we consider the proposal
|
||||
// to be invalid
|
||||
if index > 0 && l.Compare(ctx, partialProposal[index-1], tx) == -1 {
|
||||
return fmt.Errorf("transaction at index %d has a higher priority than %d", index, index-1)
|
||||
if index > 0 {
|
||||
if v, err := l.Compare(ctx, partialProposal[index-1], tx); v == -1 || err != nil {
|
||||
return fmt.Errorf("transaction at index %d has a higher priority than %d", index, index-1)
|
||||
}
|
||||
}
|
||||
|
||||
if err := l.VerifyTx(ctx, tx, false); err != nil {
|
||||
|
||||
@ -22,6 +22,10 @@ type (
|
||||
// index defines an index of transactions.
|
||||
index sdkmempool.Mempool
|
||||
|
||||
// signerExtractor defines the signer extraction adapter that allows us to
|
||||
// extract the signer from a transaction.
|
||||
extractor signer_extraction.Adapter
|
||||
|
||||
// txPriority defines the transaction priority function. It is used to
|
||||
// retrieve the priority of a given transaction and to compare the priority
|
||||
// of two transactions. The index utilizes this struct to order transactions
|
||||
@ -91,6 +95,7 @@ func NewMempool[C comparable](txPriority TxPriority[C], txEncoder sdk.TxEncoder,
|
||||
},
|
||||
extractor,
|
||||
),
|
||||
extractor: extractor,
|
||||
txPriority: txPriority,
|
||||
txEncoder: txEncoder,
|
||||
txCache: make(map[string]struct{}),
|
||||
@ -155,8 +160,48 @@ func (cm *Mempool[C]) Contains(tx sdk.Tx) bool {
|
||||
}
|
||||
|
||||
// Compare determines the relative priority of two transactions belonging in the same lane.
|
||||
func (cm *Mempool[C]) Compare(ctx sdk.Context, this sdk.Tx, other sdk.Tx) int {
|
||||
// There are two cases to consider:
|
||||
// 1. The transactions have the same signer. In this case, we compare the sequence numbers.
|
||||
// 2. The transactions have different signers. In this case, we compare the priorities of the
|
||||
// transactions.
|
||||
//
|
||||
// Compare will return -1 if this transaction has a lower priority than the other transaction, 0 if
|
||||
// they have the same priority, and 1 if this transaction has a higher priority than the other transaction.
|
||||
func (cm *Mempool[C]) Compare(ctx sdk.Context, this sdk.Tx, other sdk.Tx) (int, error) {
|
||||
signers, err := cm.extractor.GetSigners(this)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(signers) == 0 {
|
||||
return 0, fmt.Errorf("expected one signer for the first transaction")
|
||||
}
|
||||
// The priority nonce mempool uses the first tx signer so this is a safe operation.
|
||||
thisSignerInfo := signers[0]
|
||||
|
||||
signers, err = cm.extractor.GetSigners(other)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(signers) == 0 {
|
||||
return 0, fmt.Errorf("expected one signer for the second transaction")
|
||||
}
|
||||
otherSignerInfo := signers[0]
|
||||
|
||||
// If the signers are the same, we compare the sequence numbers.
|
||||
if thisSignerInfo.Signer.Equals(otherSignerInfo.Signer) {
|
||||
switch {
|
||||
case thisSignerInfo.Sequence < otherSignerInfo.Sequence:
|
||||
return 1, nil
|
||||
case thisSignerInfo.Sequence > otherSignerInfo.Sequence:
|
||||
return -1, nil
|
||||
default:
|
||||
// This case should never happen but we add in the case for completeness.
|
||||
return 0, fmt.Errorf("the two transactions have the same sequence number")
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the priority and compare the priorities.
|
||||
firstPriority := cm.txPriority.GetTxPriority(ctx, this)
|
||||
secondPriority := cm.txPriority.GetTxPriority(ctx, other)
|
||||
return cm.txPriority.Compare(firstPriority, secondPriority)
|
||||
return cm.txPriority.Compare(firstPriority, secondPriority), nil
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ type LaneMempool interface {
|
||||
// Compare determines the relative priority of two transactions belonging in the same lane. Compare
|
||||
// will return -1 if this transaction has a lower priority than the other transaction, 0 if they have
|
||||
// the same priority, and 1 if this transaction has a higher priority than the other transaction.
|
||||
Compare(ctx sdk.Context, this, other sdk.Tx) int
|
||||
Compare(ctx sdk.Context, this, other sdk.Tx) (int, error)
|
||||
|
||||
// Contains returns true if the transaction is contained in the mempool.
|
||||
Contains(tx sdk.Tx) bool
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package proposals
|
||||
|
||||
import (
|
||||
"cosmossdk.io/log"
|
||||
"cosmossdk.io/math"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/skip-mev/block-sdk/block/proposals/types"
|
||||
@ -9,6 +10,8 @@ import (
|
||||
type (
|
||||
// Proposal defines a block proposal type.
|
||||
Proposal struct {
|
||||
Logger log.Logger
|
||||
|
||||
// Txs is the list of transactions in the proposal.
|
||||
Txs [][]byte
|
||||
// Cache is a cache of the selected transactions in the proposal.
|
||||
@ -21,15 +24,16 @@ type (
|
||||
)
|
||||
|
||||
// NewProposalWithContext returns a new empty proposal.
|
||||
func NewProposalWithContext(ctx sdk.Context, txEncoder sdk.TxEncoder) Proposal {
|
||||
func NewProposalWithContext(logger log.Logger, ctx sdk.Context, txEncoder sdk.TxEncoder) Proposal {
|
||||
maxBlockSize, maxGasLimit := GetBlockLimits(ctx)
|
||||
return NewProposal(txEncoder, maxBlockSize, maxGasLimit)
|
||||
return NewProposal(logger, txEncoder, maxBlockSize, maxGasLimit)
|
||||
}
|
||||
|
||||
// NewProposal returns a new empty proposal. Any transactions added to the proposal
|
||||
// will be subject to the given max block size and max gas limit.
|
||||
func NewProposal(txEncoder sdk.TxEncoder, maxBlockSize int64, maxGasLimit uint64) Proposal {
|
||||
func NewProposal(logger log.Logger, txEncoder sdk.TxEncoder, maxBlockSize int64, maxGasLimit uint64) Proposal {
|
||||
return Proposal{
|
||||
Logger: logger,
|
||||
TxEncoder: txEncoder,
|
||||
Txs: make([][]byte, 0),
|
||||
Cache: make(map[string]struct{}),
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"cosmossdk.io/log"
|
||||
"cosmossdk.io/math"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/skip-mev/block-sdk/block/mocks"
|
||||
@ -27,7 +28,7 @@ func TestUpdateProposal(t *testing.T) {
|
||||
lane.On("GetMaxBlockSpace").Return(math.LegacyNewDec(1)).Maybe()
|
||||
|
||||
t.Run("can update with no transactions", func(t *testing.T) {
|
||||
proposal := proposals.NewProposal(nil, 100, 100)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(t), nil, 100, 100)
|
||||
|
||||
err := proposal.UpdateProposal(lane, nil)
|
||||
require.NoError(t, err)
|
||||
@ -59,7 +60,7 @@ func TestUpdateProposal(t *testing.T) {
|
||||
|
||||
size := len(txBzs[0])
|
||||
gasLimit := 100
|
||||
proposal := proposals.NewProposal(encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit))
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(t), encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit))
|
||||
|
||||
err = proposal.UpdateProposal(lane, []sdk.Tx{tx})
|
||||
require.NoError(t, err)
|
||||
@ -105,7 +106,7 @@ func TestUpdateProposal(t *testing.T) {
|
||||
gasLimit += 100
|
||||
}
|
||||
|
||||
proposal := proposals.NewProposal(encodingConfig.TxConfig.TxEncoder(), int64(size), gasLimit)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(t), encodingConfig.TxConfig.TxEncoder(), int64(size), gasLimit)
|
||||
|
||||
err = proposal.UpdateProposal(lane, txs)
|
||||
require.NoError(t, err)
|
||||
@ -142,7 +143,7 @@ func TestUpdateProposal(t *testing.T) {
|
||||
|
||||
size := int64(len(txBzs[0]))
|
||||
gasLimit := uint64(100)
|
||||
proposal := proposals.NewProposal(encodingConfig.TxConfig.TxEncoder(), size, gasLimit)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(t), encodingConfig.TxConfig.TxEncoder(), size, gasLimit)
|
||||
|
||||
err = proposal.UpdateProposal(lane, []sdk.Tx{tx})
|
||||
require.NoError(t, err)
|
||||
@ -202,7 +203,7 @@ func TestUpdateProposal(t *testing.T) {
|
||||
|
||||
size := len(txBzs[0]) + len(txBzs[1])
|
||||
gasLimit := 200
|
||||
proposal := proposals.NewProposal(encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit))
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(t), encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit))
|
||||
|
||||
err = proposal.UpdateProposal(lane, []sdk.Tx{tx})
|
||||
require.NoError(t, err)
|
||||
@ -240,7 +241,7 @@ func TestUpdateProposal(t *testing.T) {
|
||||
|
||||
size := len(txBzs[0])
|
||||
gasLimit := 100
|
||||
proposal := proposals.NewProposal(encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit))
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(t), encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit))
|
||||
|
||||
lane := mocks.NewLane(t)
|
||||
|
||||
@ -278,7 +279,7 @@ func TestUpdateProposal(t *testing.T) {
|
||||
|
||||
size := len(txBzs[0])
|
||||
gasLimit := 100
|
||||
proposal := proposals.NewProposal(encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit))
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(t), encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit))
|
||||
|
||||
lane := mocks.NewLane(t)
|
||||
|
||||
@ -316,7 +317,7 @@ func TestUpdateProposal(t *testing.T) {
|
||||
|
||||
size := len(txBzs[0])
|
||||
gasLimit := 100
|
||||
proposal := proposals.NewProposal(encodingConfig.TxConfig.TxEncoder(), int64(size)-1, uint64(gasLimit))
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(t), encodingConfig.TxConfig.TxEncoder(), int64(size)-1, uint64(gasLimit))
|
||||
|
||||
err = proposal.UpdateProposal(lane, []sdk.Tx{tx})
|
||||
require.Error(t, err)
|
||||
@ -349,7 +350,7 @@ func TestUpdateProposal(t *testing.T) {
|
||||
|
||||
size := len(txBzs[0])
|
||||
gasLimit := 100
|
||||
proposal := proposals.NewProposal(encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit)-1)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(t), encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit)-1)
|
||||
|
||||
err = proposal.UpdateProposal(lane, []sdk.Tx{tx})
|
||||
require.Error(t, err)
|
||||
@ -390,7 +391,7 @@ func TestUpdateProposal(t *testing.T) {
|
||||
txBzs, err := utils.GetEncodedTxs(encodingConfig.TxConfig.TxEncoder(), []sdk.Tx{tx, tx2})
|
||||
require.NoError(t, err)
|
||||
|
||||
proposal := proposals.NewProposal(encodingConfig.TxConfig.TxEncoder(), 10000, 10000)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(t), encodingConfig.TxConfig.TxEncoder(), 10000, 10000)
|
||||
|
||||
err = proposal.UpdateProposal(lane, []sdk.Tx{tx})
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package proposals
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
@ -46,6 +47,17 @@ func (p *Proposal) UpdateProposal(lane Lane, partialProposal []sdk.Tx) error {
|
||||
return fmt.Errorf("err retrieving transaction info: %s", err)
|
||||
}
|
||||
|
||||
p.Logger.Debug(
|
||||
"updating proposal with tx",
|
||||
"index", index,
|
||||
"lane", lane.Name(),
|
||||
"tx_hash", txInfo.Hash,
|
||||
"tx_size", txInfo.Size,
|
||||
"tx_gas_limit", txInfo.GasLimit,
|
||||
"tx_bytes", txInfo.TxBytes,
|
||||
"raw_tx", base64.StdEncoding.EncodeToString(txInfo.TxBytes),
|
||||
)
|
||||
|
||||
// invariant check: Ensure that the transaction is not already in the proposal.
|
||||
if _, ok := p.Cache[txInfo.Hash]; ok {
|
||||
return fmt.Errorf("transaction %s is already in the proposal", txInfo.Hash)
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
package base_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
|
||||
"cosmossdk.io/log"
|
||||
"cosmossdk.io/math"
|
||||
@ -43,6 +45,7 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
int64(len(txBz)),
|
||||
1,
|
||||
@ -86,6 +89,7 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
MaxGasLimit: 10,
|
||||
}
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
limit.MaxTxBytes,
|
||||
limit.MaxGasLimit,
|
||||
@ -130,6 +134,7 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
MaxGasLimit: 10,
|
||||
}
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
limit.MaxTxBytes,
|
||||
limit.MaxGasLimit,
|
||||
@ -173,6 +178,7 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
MaxGasLimit: 10,
|
||||
}
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
limit.MaxTxBytes,
|
||||
limit.MaxGasLimit,
|
||||
@ -213,6 +219,7 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
int64(len(txBz)),
|
||||
10,
|
||||
@ -272,6 +279,7 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
size := int64(len(txBz1)) + int64(len(txBz2))
|
||||
gasLimit := uint64(20)
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
size,
|
||||
gasLimit,
|
||||
@ -328,6 +336,7 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
size := int64(len(txBz1)) + int64(len(txBz2))
|
||||
gasLimit := uint64(2)
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
size,
|
||||
gasLimit,
|
||||
@ -387,6 +396,7 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
size := int64(len(txBz1)) + int64(len(txBz2)) - 1
|
||||
gasLimit := uint64(3)
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
size,
|
||||
gasLimit,
|
||||
@ -446,6 +456,7 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
size := int64(len(txBz1)) + int64(len(txBz2)) - 1
|
||||
gasLimit := uint64(1)
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
size,
|
||||
gasLimit,
|
||||
@ -487,6 +498,7 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
int64(len(txBz))*10,
|
||||
1000000,
|
||||
@ -512,6 +524,195 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
}
|
||||
|
||||
func (s *BaseTestSuite) TestProcessLane() {
|
||||
s.Run("should accept a proposal where transaction fees are not in order bc of sequence numbers", func() {
|
||||
tx1, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(1)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
tx2, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(2)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
proposal := []sdk.Tx{
|
||||
tx1,
|
||||
tx2, // This transaction has a higher sequence number and higher fees
|
||||
}
|
||||
|
||||
lane := s.initLane(
|
||||
math.LegacyOneDec(),
|
||||
map[sdk.Tx]bool{
|
||||
tx1: true,
|
||||
tx2: true,
|
||||
},
|
||||
)
|
||||
|
||||
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
100000,
|
||||
100000,
|
||||
)
|
||||
|
||||
_, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
})
|
||||
|
||||
s.Run("should accept a proposal where transaction fees are not in order bc of sequence numbers with other txs", func() {
|
||||
tx1, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(10)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
tx2, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(20)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
tx3, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[1],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(3)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
proposal := []sdk.Tx{
|
||||
tx1,
|
||||
tx2, // This transaction has a higher sequence number and higher fees
|
||||
tx3,
|
||||
}
|
||||
|
||||
lane := s.initLane(
|
||||
math.LegacyOneDec(),
|
||||
map[sdk.Tx]bool{
|
||||
tx1: true,
|
||||
tx2: true,
|
||||
tx3: true,
|
||||
},
|
||||
)
|
||||
|
||||
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
100000,
|
||||
100000,
|
||||
)
|
||||
|
||||
_, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
})
|
||||
|
||||
s.Run("accepts proposal with multiple senders and seq nums", func() {
|
||||
tx1, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(10)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
tx2, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(20)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
tx3, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[1],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(9)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
tx4, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[1],
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(11)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
proposal := []sdk.Tx{
|
||||
tx1,
|
||||
tx2,
|
||||
tx3,
|
||||
tx4,
|
||||
}
|
||||
|
||||
lane := s.initLane(
|
||||
math.LegacyOneDec(),
|
||||
map[sdk.Tx]bool{
|
||||
tx1: true,
|
||||
tx2: true,
|
||||
tx3: true,
|
||||
tx4: true,
|
||||
},
|
||||
)
|
||||
|
||||
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
100000,
|
||||
100000,
|
||||
)
|
||||
|
||||
_, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
})
|
||||
|
||||
s.Run("should accept a proposal with valid transactions", func() {
|
||||
tx1, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
@ -538,6 +739,7 @@ func (s *BaseTestSuite) TestProcessLane() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
100000,
|
||||
100000,
|
||||
@ -573,6 +775,7 @@ func (s *BaseTestSuite) TestProcessLane() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
100000,
|
||||
100000,
|
||||
@ -631,6 +834,7 @@ func (s *BaseTestSuite) TestProcessLane() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
100000,
|
||||
100000,
|
||||
@ -679,6 +883,7 @@ func (s *BaseTestSuite) TestProcessLane() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
100000,
|
||||
100000,
|
||||
@ -727,6 +932,7 @@ func (s *BaseTestSuite) TestProcessLane() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
100000,
|
||||
100000,
|
||||
@ -778,6 +984,7 @@ func (s *BaseTestSuite) TestProcessLane() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
100000,
|
||||
100000,
|
||||
@ -813,6 +1020,7 @@ func (s *BaseTestSuite) TestProcessLane() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
maxSize,
|
||||
1000000,
|
||||
@ -849,6 +1057,7 @@ func (s *BaseTestSuite) TestProcessLane() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
maxSize,
|
||||
9,
|
||||
@ -896,6 +1105,7 @@ func (s *BaseTestSuite) TestProcessLane() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
maxSize,
|
||||
19,
|
||||
@ -944,6 +1154,7 @@ func (s *BaseTestSuite) TestProcessLane() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
maxSize,
|
||||
20,
|
||||
@ -954,6 +1165,146 @@ func (s *BaseTestSuite) TestProcessLane() {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *BaseTestSuite) TestPrepareProcessParity() {
|
||||
txsToInsert := []sdk.Tx{}
|
||||
validationMap := make(map[sdk.Tx]bool)
|
||||
numTxsPerAccount := uint64(50)
|
||||
accounts := testutils.RandomAccounts(s.random, 50)
|
||||
|
||||
for _, account := range accounts {
|
||||
for nonce := uint64(0); nonce < numTxsPerAccount; nonce++ {
|
||||
// create a random fee amount
|
||||
feeAmount := math.NewInt(int64(rand.Intn(100000)))
|
||||
tx, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
account,
|
||||
nonce,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
sdk.NewCoin(s.gasTokenDenom, feeAmount),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
txsToInsert = append(txsToInsert, tx)
|
||||
validationMap[tx] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Add the transactions to the lane
|
||||
lane := s.initLane(math.LegacyOneDec(), validationMap)
|
||||
for _, tx := range txsToInsert {
|
||||
s.Require().NoError(lane.Insert(s.ctx, tx))
|
||||
}
|
||||
|
||||
// Retrieve the transactions from the lane in the same way the prepare function would.
|
||||
retrievedTxs := []sdk.Tx{}
|
||||
for iterator := lane.Select(context.Background(), nil); iterator != nil; iterator = iterator.Next() {
|
||||
retrievedTxs = append(retrievedTxs, iterator.Tx())
|
||||
}
|
||||
s.Require().Equal(len(txsToInsert), len(retrievedTxs))
|
||||
|
||||
// Construct a block proposal with the transactions in the mempool
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
1000000000000000,
|
||||
1000000000000000,
|
||||
)
|
||||
proposal, err := lane.PrepareLane(s.ctx, emptyProposal, block.NoOpPrepareLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(len(txsToInsert), len(proposal.Txs))
|
||||
|
||||
// Ensure that the transactions are in the same order
|
||||
for i := 0; i < len(retrievedTxs); i++ {
|
||||
bz, err := s.encodingConfig.TxConfig.TxEncoder()(retrievedTxs[i])
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(bz, proposal.Txs[i])
|
||||
}
|
||||
|
||||
// Verify the same proposal with the process lanes handler
|
||||
emptyProposal = proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
1000000000000000,
|
||||
1000000000000000,
|
||||
)
|
||||
proposal, err = lane.ProcessLane(s.ctx, emptyProposal, proposal.Txs, block.NoOpProcessLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(len(txsToInsert), len(proposal.Txs))
|
||||
s.T().Logf("proposal num txs: %d", len(proposal.Txs))
|
||||
|
||||
// Ensure that the transactions are in the same order
|
||||
for i := 0; i < len(retrievedTxs); i++ {
|
||||
bz, err := s.encodingConfig.TxConfig.TxEncoder()(retrievedTxs[i])
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(bz, proposal.Txs[i])
|
||||
}
|
||||
}
|
||||
|
||||
func (s *BaseTestSuite) TestIterateMempoolAndProcessProposalParity() {
|
||||
txsToInsert := []sdk.Tx{}
|
||||
validationMap := make(map[sdk.Tx]bool)
|
||||
numTxsPerAccount := uint64(200)
|
||||
accounts := testutils.RandomAccounts(s.random, 50)
|
||||
|
||||
for _, account := range accounts {
|
||||
for nonce := uint64(0); nonce < numTxsPerAccount; nonce++ {
|
||||
// create a random fee amount
|
||||
feeAmount := math.NewInt(int64(rand.Intn(100000)))
|
||||
tx, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
account,
|
||||
nonce,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
sdk.NewCoin(s.gasTokenDenom, feeAmount),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
txsToInsert = append(txsToInsert, tx)
|
||||
validationMap[tx] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Add the transactions to the lane
|
||||
lane := s.initLane(math.LegacyOneDec(), validationMap)
|
||||
for _, tx := range txsToInsert {
|
||||
s.Require().NoError(lane.Insert(s.ctx, tx))
|
||||
}
|
||||
|
||||
// Retrieve the transactions from the lane in the same way the prepare function would.
|
||||
retrievedTxs := []sdk.Tx{}
|
||||
for iterator := lane.Select(context.Background(), nil); iterator != nil; iterator = iterator.Next() {
|
||||
retrievedTxs = append(retrievedTxs, iterator.Tx())
|
||||
}
|
||||
|
||||
s.Require().Equal(len(txsToInsert), len(retrievedTxs))
|
||||
|
||||
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), retrievedTxs)
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
1000000000000000,
|
||||
1000000000000000,
|
||||
)
|
||||
|
||||
proposal, err := lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(len(txsToInsert), len(proposal.Txs))
|
||||
s.T().Logf("proposal num txs: %d", len(proposal.Txs))
|
||||
|
||||
// Ensure that the transactions are in the same order
|
||||
for i := 0; i < len(retrievedTxs); i++ {
|
||||
bz, err := s.encodingConfig.TxConfig.TxEncoder()(retrievedTxs[i])
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(bz, proposal.Txs[i])
|
||||
}
|
||||
}
|
||||
|
||||
func (s *BaseTestSuite) initLane(
|
||||
maxBlockSpace math.LegacyDec,
|
||||
expectedExecution map[sdk.Tx]bool,
|
||||
|
||||
@ -86,6 +86,119 @@ func (s *BaseTestSuite) TestCompareTxPriority() {
|
||||
b := sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)).String()
|
||||
s.Require().Equal(0, txPriority.Compare(a, b))
|
||||
})
|
||||
|
||||
lane := s.initLane(math.LegacyOneDec(), nil)
|
||||
|
||||
s.Run("should return -1 when signers are the same but the first tx has a higher sequence", func() {
|
||||
tx1, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
tx2, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
cmp, err := lane.Compare(sdk.Context{}, tx1, tx2)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(-1, cmp)
|
||||
})
|
||||
|
||||
s.Run("should return 1 when signers are the same but the second tx has a higher sequence", func() {
|
||||
tx1, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
tx2, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
cmp, err := lane.Compare(sdk.Context{}, tx1, tx2)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(1, cmp)
|
||||
})
|
||||
|
||||
s.Run("should return 0 when signers are the same and the sequence is the same", func() {
|
||||
tx1, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
tx2, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
_, err = lane.Compare(sdk.Context{}, tx1, tx2)
|
||||
s.Require().Error(err)
|
||||
})
|
||||
|
||||
s.Run("should return 1 when the first tx has a higher priority", func() {
|
||||
tx1, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(200)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
tx2, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[1],
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
cmp, err := lane.Compare(sdk.Context{}, tx1, tx2)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(1, cmp)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *BaseTestSuite) TestInsert() {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package mev_test
|
||||
|
||||
import (
|
||||
log "cosmossdk.io/log"
|
||||
"cosmossdk.io/math"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/skip-mev/block-sdk/block"
|
||||
@ -14,7 +15,7 @@ func (s *MEVTestSuite) TestPrepareLane() {
|
||||
|
||||
s.Run("can prepare a lane with no txs in mempool", func() {
|
||||
lane := s.initLane(math.LegacyOneDec(), nil)
|
||||
proposal := proposals.NewProposal(s.encCfg.TxConfig.TxEncoder(), 200, 100)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(s.T()), s.encCfg.TxConfig.TxEncoder(), 200, 100)
|
||||
|
||||
proposal, err := lane.PrepareLane(s.ctx, proposal, block.NoOpPrepareLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
@ -40,7 +41,7 @@ func (s *MEVTestSuite) TestPrepareLane() {
|
||||
lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true})
|
||||
s.Require().NoError(lane.Insert(s.ctx, bidTx))
|
||||
|
||||
proposal := proposals.NewProposal(s.encCfg.TxConfig.TxEncoder(), 200, 100)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(s.T()), s.encCfg.TxConfig.TxEncoder(), 200, 100)
|
||||
|
||||
proposal, err = lane.PrepareLane(s.ctx, proposal, block.NoOpPrepareLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
@ -82,7 +83,7 @@ func (s *MEVTestSuite) TestPrepareLane() {
|
||||
s.Require().NoError(lane.Insert(s.ctx, bidTx1))
|
||||
s.Require().NoError(lane.Insert(s.ctx, bidTx2))
|
||||
|
||||
proposal := proposals.NewProposal(s.encCfg.TxConfig.TxEncoder(), 20000, 100000)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(s.T()), s.encCfg.TxConfig.TxEncoder(), 20000, 100000)
|
||||
|
||||
proposal, err = lane.PrepareLane(s.ctx, proposal, block.NoOpPrepareLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
@ -124,7 +125,7 @@ func (s *MEVTestSuite) TestPrepareLane() {
|
||||
s.Require().NoError(lane.Insert(s.ctx, bidTx1))
|
||||
s.Require().NoError(lane.Insert(s.ctx, bidTx2))
|
||||
|
||||
proposal := proposals.NewProposal(s.encCfg.TxConfig.TxEncoder(), 20000, 100000)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(s.T()), s.encCfg.TxConfig.TxEncoder(), 20000, 100000)
|
||||
|
||||
proposal, err = lane.PrepareLane(s.ctx, proposal, block.NoOpPrepareLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
@ -154,7 +155,7 @@ func (s *MEVTestSuite) TestPrepareLane() {
|
||||
lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: true})
|
||||
s.Require().NoError(lane.Insert(s.ctx, bidTx))
|
||||
|
||||
proposal := proposals.NewProposal(s.encCfg.TxConfig.TxEncoder(), 20000, 100000)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(s.T()), s.encCfg.TxConfig.TxEncoder(), 20000, 100000)
|
||||
|
||||
proposal, err = lane.PrepareLane(s.ctx, proposal, block.NoOpPrepareLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
@ -185,7 +186,7 @@ func (s *MEVTestSuite) TestPrepareLane() {
|
||||
lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: true})
|
||||
s.Require().NoError(lane.Insert(s.ctx, bidTx))
|
||||
|
||||
proposal := proposals.NewProposal(s.encCfg.TxConfig.TxEncoder(), s.getTxSize(bidTx), 100000)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(s.T()), s.encCfg.TxConfig.TxEncoder(), s.getTxSize(bidTx), 100000)
|
||||
|
||||
proposal, err = lane.PrepareLane(s.ctx, proposal, block.NoOpPrepareLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
@ -210,7 +211,7 @@ func (s *MEVTestSuite) TestPrepareLane() {
|
||||
lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true})
|
||||
s.Require().NoError(lane.Insert(s.ctx, bidTx))
|
||||
|
||||
proposal := proposals.NewProposal(s.encCfg.TxConfig.TxEncoder(), s.getTxSize(bidTx), 99)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(s.T()), s.encCfg.TxConfig.TxEncoder(), s.getTxSize(bidTx), 99)
|
||||
|
||||
proposal, err = lane.PrepareLane(s.ctx, proposal, block.NoOpPrepareLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
@ -226,7 +227,7 @@ func (s *MEVTestSuite) TestProcessLane() {
|
||||
|
||||
s.Run("can process an empty proposal", func() {
|
||||
lane := s.initLane(math.LegacyOneDec(), nil)
|
||||
proposal := proposals.NewProposal(s.encCfg.TxConfig.TxEncoder(), 200, 100)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(s.T()), s.encCfg.TxConfig.TxEncoder(), 200, 100)
|
||||
|
||||
proposal, err := lane.ProcessLane(s.ctx, proposal, nil, block.NoOpProcessLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
@ -241,7 +242,7 @@ func (s *MEVTestSuite) TestProcessLane() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
lane := s.initLane(math.LegacyOneDec(), nil)
|
||||
proposal := proposals.NewProposal(s.encCfg.TxConfig.TxEncoder(), 200, 100)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(s.T()), s.encCfg.TxConfig.TxEncoder(), 200, 100)
|
||||
|
||||
_, err = lane.ProcessLane(s.ctx, proposal, [][]byte{txBz}, block.NoOpProcessLanesHandler())
|
||||
s.Require().Error(err)
|
||||
@ -263,7 +264,7 @@ func (s *MEVTestSuite) TestProcessLane() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: false})
|
||||
proposal := proposals.NewProposal(s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(s.T()), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
|
||||
|
||||
_, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler())
|
||||
s.Require().Error(err)
|
||||
@ -285,7 +286,7 @@ func (s *MEVTestSuite) TestProcessLane() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: false})
|
||||
proposal := proposals.NewProposal(s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(s.T()), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
|
||||
|
||||
_, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler())
|
||||
s.Require().Error(err)
|
||||
@ -307,7 +308,7 @@ func (s *MEVTestSuite) TestProcessLane() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: true})
|
||||
proposal := proposals.NewProposal(s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(s.T()), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
|
||||
|
||||
_, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler())
|
||||
s.Require().Error(err)
|
||||
@ -329,7 +330,7 @@ func (s *MEVTestSuite) TestProcessLane() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true})
|
||||
proposal := proposals.NewProposal(s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(s.T()), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
|
||||
|
||||
_, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler())
|
||||
s.Require().Error(err)
|
||||
@ -351,7 +352,7 @@ func (s *MEVTestSuite) TestProcessLane() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: true})
|
||||
proposal := proposals.NewProposal(s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(s.T()), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
|
||||
|
||||
_, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
@ -373,7 +374,7 @@ func (s *MEVTestSuite) TestProcessLane() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true})
|
||||
proposal := proposals.NewProposal(s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(s.T()), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
|
||||
|
||||
_, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
@ -395,7 +396,7 @@ func (s *MEVTestSuite) TestProcessLane() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: true})
|
||||
proposal := proposals.NewProposal(s.encCfg.TxConfig.TxEncoder(), 20000, 99)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(s.T()), s.encCfg.TxConfig.TxEncoder(), 20000, 99)
|
||||
|
||||
_, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler())
|
||||
s.Require().Error(err)
|
||||
@ -417,7 +418,7 @@ func (s *MEVTestSuite) TestProcessLane() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: true})
|
||||
proposal := proposals.NewProposal(s.encCfg.TxConfig.TxEncoder(), 200, 100)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(s.T()), s.encCfg.TxConfig.TxEncoder(), 200, 100)
|
||||
|
||||
_, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler())
|
||||
s.Require().Error(err)
|
||||
@ -426,7 +427,7 @@ func (s *MEVTestSuite) TestProcessLane() {
|
||||
|
||||
func (s *MEVTestSuite) TestVerifyBidBasic() {
|
||||
lane := s.initLane(math.LegacyOneDec(), nil)
|
||||
proposal := proposals.NewProposal(s.encCfg.TxConfig.TxEncoder(), 200, 100)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(s.T()), s.encCfg.TxConfig.TxEncoder(), 200, 100)
|
||||
limits := proposal.GetLaneLimits(lane.GetMaxBlockSpace())
|
||||
|
||||
s.Run("can verify a bid with no bundled txs", func() {
|
||||
@ -490,7 +491,7 @@ func (s *MEVTestSuite) TestVerifyBidBasic() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
size := s.getTxSize(bidTx)
|
||||
proposal := proposals.NewProposal(s.encCfg.TxConfig.TxEncoder(), size-1, 100)
|
||||
proposal := proposals.NewProposal(log.NewTestLogger(s.T()), s.encCfg.TxConfig.TxEncoder(), size-1, 100)
|
||||
limits := proposal.GetLaneLimits(lane.GetMaxBlockSpace())
|
||||
|
||||
_, err = lane.VerifyBidBasic(bidTx, proposal, limits)
|
||||
|
||||
@ -102,6 +102,6 @@ func (t Terminator) Select(context.Context, [][]byte) sdkmempool.Iterator {
|
||||
}
|
||||
|
||||
// HasHigherPriority is a no-op
|
||||
func (t Terminator) Compare(sdk.Context, sdk.Tx, sdk.Tx) int {
|
||||
return 0
|
||||
func (t Terminator) Compare(sdk.Context, sdk.Tx, sdk.Tx) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user