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:
@@ -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 {
|
||||
|
||||
+47
-2
@@ -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
|
||||
}
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user