feat: Lane's with custom tx adapters (#239)

* greedy approach to lane verification

* docs

* base lane testing

* mev lane testing nits

* abci top level testing done

* network spamming in E2E

* string rep of escrow address

* nit

* nit

* nit v1.0.1

* removing logs from testing

* query test

* logging with tx info

* nits

* nit

* nit

* testing nit

---------

Co-authored-by: Alex Johnson <alex@skip.money>
This commit is contained in:
David Terpay 2023-11-28 13:51:42 -05:00 committed by GitHub
parent 01a84b1cc5
commit be4465ae95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 444 additions and 98 deletions

View File

@ -12,6 +12,19 @@ type SignerData struct {
Sequence uint64
}
// NewSignerData returns a new SignerData instance.
func NewSignerData(signer sdk.AccAddress, sequence uint64) SignerData {
return SignerData{
Signer: signer,
Sequence: sequence,
}
}
// String implements the fmt.Stringer interface.
func (s SignerData) String() string {
return fmt.Sprintf("SignerData{Signer: %s, Sequence: %d}", s.Signer, s.Sequence)
}
// SignerExtractionAdapter is an interface used to determine how the signers of a transaction should be extracted
// from the transaction.
type Adapter interface {

View File

@ -42,9 +42,26 @@ func (l *BaseLane) PrepareLane(
)
}
// Get the transaction info for each transaction that was selected.
txsWithInfo := make([]utils.TxWithInfo, len(txsToInclude))
for i, tx := range txsToInclude {
txInfo, err := l.GetTxInfo(ctx, tx)
if err != nil {
l.Logger().Error(
"failed to get tx info",
"lane", l.Name(),
"err", err,
)
return proposal, err
}
txsWithInfo[i] = txInfo
}
// Update the proposal with the selected transactions. This fails if the lane attempted to add
// more transactions than the allocated max block space for the lane.
if err := proposal.UpdateProposal(l, txsToInclude); err != nil {
if err := proposal.UpdateProposal(l, txsWithInfo); err != nil {
l.Logger().Error(
"failed to update proposal",
"lane", l.Name(),
@ -102,8 +119,25 @@ func (l *BaseLane) ProcessLane(
return proposal, err
}
// Retrieve the transaction info for each transaction that belongs to the lane.
txsWithInfo := make([]utils.TxWithInfo, len(txsFromLane))
for i, tx := range txsFromLane {
txInfo, err := l.GetTxInfo(ctx, tx)
if err != nil {
l.Logger().Error(
"failed to get tx info",
"lane", l.Name(),
"err", err,
)
return proposal, err
}
txsWithInfo[i] = txInfo
}
// Optimistically update the proposal with the partial proposal.
if err := proposal.UpdateProposal(l, txsFromLane); err != nil {
if err := proposal.UpdateProposal(l, txsWithInfo); err != nil {
l.Logger().Error(
"failed to update proposal",
"lane", l.Name(),

View File

@ -6,7 +6,6 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/skip-mev/block-sdk/block/proposals"
"github.com/skip-mev/block-sdk/block/utils"
)
// DefaultPrepareLaneHandler returns a default implementation of the PrepareLaneHandler. It
@ -27,7 +26,7 @@ func (l *BaseLane) DefaultPrepareLaneHandler() PrepareLaneHandler {
for iterator := l.Select(ctx, nil); iterator != nil; iterator = iterator.Next() {
tx := iterator.Tx()
txInfo, err := utils.GetTxInfo(l.TxEncoder(), tx)
txInfo, err := l.GetTxInfo(ctx, tx)
if err != nil {
l.Logger().Info("failed to get hash of tx", "err", err)

View File

@ -102,19 +102,24 @@ func NewMempool[C comparable](txPriority TxPriority[C], txEncoder sdk.TxEncoder,
}
}
// Priority returns the priority of the transaction.
func (cm *Mempool[C]) Priority(ctx sdk.Context, tx sdk.Tx) any {
return cm.txPriority.GetTxPriority(ctx, tx)
}
// Insert inserts a transaction into the mempool.
func (cm *Mempool[C]) Insert(ctx context.Context, tx sdk.Tx) error {
if err := cm.index.Insert(ctx, tx); err != nil {
return fmt.Errorf("failed to insert tx into auction index: %w", err)
}
txInfo, err := utils.GetTxInfo(cm.txEncoder, tx)
hash, err := utils.GetTxHash(cm.txEncoder, tx)
if err != nil {
cm.Remove(tx)
return err
}
cm.txCache[txInfo.Hash] = struct{}{}
cm.txCache[hash] = struct{}{}
return nil
}
@ -125,12 +130,12 @@ func (cm *Mempool[C]) Remove(tx sdk.Tx) error {
return fmt.Errorf("failed to remove transaction from the mempool: %w", err)
}
txInfo, err := utils.GetTxInfo(cm.txEncoder, tx)
hash, err := utils.GetTxHash(cm.txEncoder, tx)
if err != nil {
return fmt.Errorf("failed to get tx hash string: %w", err)
}
delete(cm.txCache, txInfo.Hash)
delete(cm.txCache, hash)
return nil
}
@ -150,12 +155,12 @@ func (cm *Mempool[C]) CountTx() int {
// Contains returns true if the transaction is contained in the mempool.
func (cm *Mempool[C]) Contains(tx sdk.Tx) bool {
txInfo, err := utils.GetTxInfo(cm.txEncoder, tx)
hash, err := utils.GetTxHash(cm.txEncoder, tx)
if err != nil {
return false
}
_, ok := cm.txCache[txInfo.Hash]
_, ok := cm.txCache[hash]
return ok
}

41
block/base/tx_info.go Normal file
View File

@ -0,0 +1,41 @@
package base
import (
"encoding/hex"
"fmt"
"strings"
comettypes "github.com/cometbft/cometbft/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/skip-mev/block-sdk/block/utils"
)
// GetTxInfo returns various information about the transaction that
// belongs to the lane including its priority, signer's, sequence number,
// size and more.
func (l *BaseLane) GetTxInfo(ctx sdk.Context, tx sdk.Tx) (utils.TxWithInfo, error) {
txBytes, err := l.cfg.TxEncoder(tx)
if err != nil {
return utils.TxWithInfo{}, fmt.Errorf("failed to encode transaction: %w", err)
}
// TODO: Add an adapter to lanes so that this can be flexible to support EVM, etc.
gasTx, ok := tx.(sdk.FeeTx)
if !ok {
return utils.TxWithInfo{}, fmt.Errorf("failed to cast transaction to gas tx")
}
signers, err := l.cfg.SignerExtractor.GetSigners(tx)
if err != nil {
return utils.TxWithInfo{}, err
}
return utils.TxWithInfo{
Hash: strings.ToUpper(hex.EncodeToString(comettypes.Tx(txBytes).Hash())),
Size: int64(len(txBytes)),
GasLimit: gasTx.GetGas(),
TxBytes: txBytes,
Priority: l.LaneMempool.Priority(ctx, tx),
Signers: signers,
}, nil
}

View File

@ -6,6 +6,7 @@ import (
sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool"
"github.com/skip-mev/block-sdk/block/proposals"
"github.com/skip-mev/block-sdk/block/utils"
)
// LaneMempool defines the interface a lane's mempool should implement. The basic API
@ -23,6 +24,9 @@ type LaneMempool interface {
// Contains returns true if the transaction is contained in the mempool.
Contains(tx sdk.Tx) bool
// Priority returns the priority of a transaction that belongs to this lane.
Priority(ctx sdk.Context, tx sdk.Tx) any
}
// Lane defines an interface used for matching transactions to lanes, storing transactions,
@ -74,6 +78,11 @@ type Lane interface {
// Match determines if a transaction belongs to this lane.
Match(ctx sdk.Context, tx sdk.Tx) bool
// GetTxInfo returns various information about the transaction that
// belongs to the lane including its priority, signer's, sequence number,
// size and more.
GetTxInfo(ctx sdk.Context, tx sdk.Tx) (utils.TxWithInfo, error)
}
// FindLane finds a Lanes from in an array of Lanes and returns it and its index if found.

View File

@ -16,6 +16,8 @@ import (
proposals "github.com/skip-mev/block-sdk/block/proposals"
types "github.com/cosmos/cosmos-sdk/types"
utils "github.com/skip-mev/block-sdk/block/utils"
)
// Lane is an autogenerated mock type for the Lane type
@ -105,6 +107,30 @@ func (_m *Lane) GetMaxBlockSpace() math.LegacyDec {
return r0
}
// GetTxInfo provides a mock function with given fields: ctx, tx
func (_m *Lane) GetTxInfo(ctx types.Context, tx types.Tx) (utils.TxWithInfo, error) {
ret := _m.Called(ctx, tx)
var r0 utils.TxWithInfo
var r1 error
if rf, ok := ret.Get(0).(func(types.Context, types.Tx) (utils.TxWithInfo, error)); ok {
return rf(ctx, tx)
}
if rf, ok := ret.Get(0).(func(types.Context, types.Tx) utils.TxWithInfo); ok {
r0 = rf(ctx, tx)
} else {
r0 = ret.Get(0).(utils.TxWithInfo)
}
if rf, ok := ret.Get(1).(func(types.Context, types.Tx) error); ok {
r1 = rf(ctx, tx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Insert provides a mock function with given fields: _a0, _a1
func (_m *Lane) Insert(_a0 context.Context, _a1 types.Tx) error {
ret := _m.Called(_a0, _a1)
@ -171,6 +197,22 @@ func (_m *Lane) PrepareLane(ctx types.Context, proposal proposals.Proposal, next
return r0, r1
}
// Priority provides a mock function with given fields: ctx, tx
func (_m *Lane) Priority(ctx types.Context, tx types.Tx) interface{} {
ret := _m.Called(ctx, tx)
var r0 interface{}
if rf, ok := ret.Get(0).(func(types.Context, types.Tx) interface{}); ok {
r0 = rf(ctx, tx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(interface{})
}
}
return r0
}
// ProcessLane provides a mock function with given fields: ctx, proposal, txs, next
func (_m *Lane) ProcessLane(ctx types.Context, proposal proposals.Proposal, txs []types.Tx, next block.ProcessLanesHandler) (proposals.Proposal, error) {
ret := _m.Called(ctx, proposal, txs, next)

View File

@ -82,6 +82,22 @@ func (_m *LaneMempool) Insert(_a0 context.Context, _a1 types.Tx) error {
return r0
}
// Priority provides a mock function with given fields: ctx, tx
func (_m *LaneMempool) Priority(ctx types.Context, tx types.Tx) interface{} {
ret := _m.Called(ctx, tx)
var r0 interface{}
if rf, ok := ret.Get(0).(func(types.Context, types.Tx) interface{}); ok {
r0 = rf(ctx, tx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(interface{})
}
}
return r0
}
// Remove provides a mock function with given fields: _a0
func (_m *LaneMempool) Remove(_a0 types.Tx) error {
ret := _m.Called(_a0)

View File

@ -9,10 +9,13 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
signerextraction "github.com/skip-mev/block-sdk/adapters/signer_extraction_adapter"
"github.com/skip-mev/block-sdk/block/base"
"github.com/skip-mev/block-sdk/block/mocks"
"github.com/skip-mev/block-sdk/block/proposals"
"github.com/skip-mev/block-sdk/block/proposals/types"
"github.com/skip-mev/block-sdk/block/utils"
defaultlane "github.com/skip-mev/block-sdk/lanes/base"
"github.com/skip-mev/block-sdk/testutils"
)
@ -29,7 +32,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(log.NewNopLogger(), nil, 100, 100)
proposal := proposals.NewProposal(log.NewTestLogger(t), nil, 100, 100)
err := proposal.UpdateProposal(lane, nil)
require.NoError(t, err)
@ -61,9 +64,12 @@ func TestUpdateProposal(t *testing.T) {
size := len(txBzs[0])
gasLimit := 100
proposal := proposals.NewProposal(log.NewNopLogger(), 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})
txsWithInfo, err := getTxsWithInfo([]sdk.Tx{tx})
require.NoError(t, err)
err = proposal.UpdateProposal(lane, txsWithInfo)
require.NoError(t, err)
// Ensure that the proposal is not empty.
@ -107,9 +113,12 @@ func TestUpdateProposal(t *testing.T) {
gasLimit += 100
}
proposal := proposals.NewProposal(log.NewNopLogger(), encodingConfig.TxConfig.TxEncoder(), int64(size), gasLimit)
proposal := proposals.NewProposal(log.NewTestLogger(t), encodingConfig.TxConfig.TxEncoder(), int64(size), gasLimit)
err = proposal.UpdateProposal(lane, txs)
txsWithInfo, err := getTxsWithInfo(txs)
require.NoError(t, err)
err = proposal.UpdateProposal(lane, txsWithInfo)
require.NoError(t, err)
// Ensure that the proposal is not empty.
@ -144,9 +153,12 @@ func TestUpdateProposal(t *testing.T) {
size := int64(len(txBzs[0]))
gasLimit := uint64(100)
proposal := proposals.NewProposal(log.NewNopLogger(), encodingConfig.TxConfig.TxEncoder(), size, gasLimit)
proposal := proposals.NewProposal(log.NewTestLogger(t), encodingConfig.TxConfig.TxEncoder(), size, gasLimit)
err = proposal.UpdateProposal(lane, []sdk.Tx{tx})
txsWithInfo, err := getTxsWithInfo([]sdk.Tx{tx})
require.NoError(t, err)
err = proposal.UpdateProposal(lane, txsWithInfo)
require.NoError(t, err)
// Ensure that the proposal is empty.
@ -161,8 +173,11 @@ func TestUpdateProposal(t *testing.T) {
otherlane.On("Name").Return("test").Maybe()
otherlane.On("GetMaxBlockSpace").Return(math.LegacyNewDec(1)).Maybe()
txsWithInfo, err = getTxsWithInfo([]sdk.Tx{tx})
require.NoError(t, err)
// Attempt to add the same transaction again.
err = proposal.UpdateProposal(otherlane, []sdk.Tx{tx})
err = proposal.UpdateProposal(otherlane, txsWithInfo)
require.Error(t, err)
require.Equal(t, 1, len(proposal.Txs))
@ -204,12 +219,18 @@ func TestUpdateProposal(t *testing.T) {
size := len(txBzs[0]) + len(txBzs[1])
gasLimit := 200
proposal := proposals.NewProposal(log.NewNopLogger(), 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})
txsWithInfo, err := getTxsWithInfo([]sdk.Tx{tx})
require.NoError(t, err)
err = proposal.UpdateProposal(lane, []sdk.Tx{tx2})
err = proposal.UpdateProposal(lane, txsWithInfo)
require.NoError(t, err)
txsWithInfo, err = getTxsWithInfo([]sdk.Tx{tx2})
require.NoError(t, err)
err = proposal.UpdateProposal(lane, txsWithInfo)
require.Error(t, err)
// Ensure that the proposal is not empty.
@ -242,14 +263,17 @@ func TestUpdateProposal(t *testing.T) {
size := len(txBzs[0])
gasLimit := 100
proposal := proposals.NewProposal(log.NewNopLogger(), encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit))
proposal := proposals.NewProposal(log.NewTestLogger(t), encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit))
lane := mocks.NewLane(t)
lane.On("Name").Return("test").Maybe()
lane.On("GetMaxBlockSpace").Return(math.LegacyMustNewDecFromStr("0.5")).Maybe()
err = proposal.UpdateProposal(lane, []sdk.Tx{tx})
txsWithInfo, err := getTxsWithInfo([]sdk.Tx{tx})
require.NoError(t, err)
err = proposal.UpdateProposal(lane, txsWithInfo)
require.Error(t, err)
// Ensure that the proposal is empty.
@ -280,14 +304,17 @@ func TestUpdateProposal(t *testing.T) {
size := len(txBzs[0])
gasLimit := 100
proposal := proposals.NewProposal(log.NewNopLogger(), encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit))
proposal := proposals.NewProposal(log.NewTestLogger(t), encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit))
lane := mocks.NewLane(t)
lane.On("Name").Return("test").Maybe()
lane.On("GetMaxBlockSpace").Return(math.LegacyMustNewDecFromStr("0.5")).Maybe()
err = proposal.UpdateProposal(lane, []sdk.Tx{tx})
txsWithInfo, err := getTxsWithInfo([]sdk.Tx{tx})
require.NoError(t, err)
err = proposal.UpdateProposal(lane, txsWithInfo)
require.Error(t, err)
// Ensure that the proposal is empty.
@ -318,9 +345,12 @@ func TestUpdateProposal(t *testing.T) {
size := len(txBzs[0])
gasLimit := 100
proposal := proposals.NewProposal(log.NewNopLogger(), 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})
txsWithInfo, err := getTxsWithInfo([]sdk.Tx{tx})
require.NoError(t, err)
err = proposal.UpdateProposal(lane, txsWithInfo)
require.Error(t, err)
// Ensure that the proposal is empty.
@ -351,9 +381,12 @@ func TestUpdateProposal(t *testing.T) {
size := len(txBzs[0])
gasLimit := 100
proposal := proposals.NewProposal(log.NewNopLogger(), 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})
txsWithInfo, err := getTxsWithInfo([]sdk.Tx{tx})
require.NoError(t, err)
err = proposal.UpdateProposal(lane, txsWithInfo)
require.Error(t, err)
// Ensure that the proposal is empty.
@ -392,16 +425,22 @@ func TestUpdateProposal(t *testing.T) {
txBzs, err := utils.GetEncodedTxs(encodingConfig.TxConfig.TxEncoder(), []sdk.Tx{tx, tx2})
require.NoError(t, err)
proposal := proposals.NewProposal(log.NewNopLogger(), encodingConfig.TxConfig.TxEncoder(), 10000, 10000)
proposal := proposals.NewProposal(log.NewTestLogger(t), encodingConfig.TxConfig.TxEncoder(), 10000, 10000)
err = proposal.UpdateProposal(lane, []sdk.Tx{tx})
txsWithInfo, err := getTxsWithInfo([]sdk.Tx{tx})
require.NoError(t, err)
err = proposal.UpdateProposal(lane, txsWithInfo)
require.NoError(t, err)
otherlane := mocks.NewLane(t)
otherlane.On("Name").Return("test2")
otherlane.On("GetMaxBlockSpace").Return(math.LegacyMustNewDecFromStr("1.0"))
err = proposal.UpdateProposal(otherlane, []sdk.Tx{tx2})
txsWithInfo, err = getTxsWithInfo([]sdk.Tx{tx2})
require.NoError(t, err)
err = proposal.UpdateProposal(otherlane, txsWithInfo)
require.NoError(t, err)
size := len(txBzs[0]) + len(txBzs[1])
@ -540,3 +579,29 @@ func TestGetLaneLimits(t *testing.T) {
})
}
}
func getTxsWithInfo(txs []sdk.Tx) ([]utils.TxWithInfo, error) {
encoding := testutils.CreateTestEncodingConfig()
cfg := base.NewLaneConfig(
log.NewNopLogger(),
encoding.TxConfig.TxEncoder(),
encoding.TxConfig.TxDecoder(),
nil,
signerextraction.NewDefaultAdapter(),
math.LegacyNewDec(1),
)
lane := defaultlane.NewDefaultLane(cfg)
txsWithInfo := make([]utils.TxWithInfo, len(txs))
for i, tx := range txs {
txInfo, err := lane.GetTxInfo(sdk.Context{}, tx)
if err != nil {
return nil, err
}
txsWithInfo[i] = txInfo
}
return txsWithInfo, nil
}

View File

@ -4,15 +4,14 @@ import (
"fmt"
"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/skip-mev/block-sdk/block/utils"
)
// Lane defines the contract interface for a lane.
type Lane interface {
GetMaxBlockSpace() math.LegacyDec
Name() string
GetMaxBlockSpace() math.LegacyDec
}
// UpdateProposal updates the proposal with the given transactions and lane limits. There are a
@ -25,7 +24,7 @@ type Lane interface {
// the lane.
// 5. The lane must not have already prepared a partial proposal.
// 6. The transaction must not already be in the proposal.
func (p *Proposal) UpdateProposal(lane Lane, partialProposal []sdk.Tx) error {
func (p *Proposal) UpdateProposal(lane Lane, partialProposal []utils.TxWithInfo) error {
if len(partialProposal) == 0 {
return nil
}
@ -42,29 +41,26 @@ func (p *Proposal) UpdateProposal(lane Lane, partialProposal []sdk.Tx) error {
partialProposalGasLimit := uint64(0)
for index, tx := range partialProposal {
txInfo, err := utils.GetTxInfo(p.TxEncoder, tx)
if err != nil {
return fmt.Errorf("err retrieving transaction info: %s", err)
}
p.Logger.Info(
"updating proposal with tx",
"index", index,
"index", index+len(p.Txs),
"lane", lane.Name(),
"tx_hash", txInfo.Hash,
"tx_size", txInfo.Size,
"tx_gas_limit", txInfo.GasLimit,
"hash", tx.Hash,
"size", tx.Size,
"gas_limit", tx.GasLimit,
"signers", tx.Signers,
"priority", tx.Priority,
)
// 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)
if _, ok := p.Cache[tx.Hash]; ok {
return fmt.Errorf("transaction %s is already in the proposal", tx.Hash)
}
hashes[txInfo.Hash] = struct{}{}
partialProposalSize += txInfo.Size
partialProposalGasLimit += txInfo.GasLimit
txs[index] = txInfo.TxBytes
hashes[tx.Hash] = struct{}{}
partialProposalSize += tx.Size
partialProposalGasLimit += tx.GasLimit
txs[index] = tx.TxBytes
}
// invariant check: Ensure that the partial proposal is not too large.

View File

@ -6,47 +6,62 @@ import (
"strings"
comettypes "github.com/cometbft/cometbft/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool"
signerextraction "github.com/skip-mev/block-sdk/adapters/signer_extraction_adapter"
)
type (
// TxInfo contains the information required for a transaction to be
// included in a proposal.
TxInfo struct {
// Hash is the hex-encoded hash of the transaction.
Hash string
// Size is the size of the transaction in bytes.
Size int64
// GasLimit is the gas limit of the transaction.
GasLimit uint64
// TxBytes is the bytes of the transaction.
TxBytes []byte
// TxWithInfo contains the information required for a transaction to be
// included in a proposal.
type TxWithInfo struct {
// Hash is the hex-encoded hash of the transaction.
Hash string
// Size is the size of the transaction in bytes.
Size int64
// GasLimit is the gas limit of the transaction.
GasLimit uint64
// TxBytes is the bytes of the transaction.
TxBytes []byte
// Priority defines the priority of the transaction.
Priority any
// Signers defines the signers of a transaction.
Signers []signerextraction.SignerData
}
// NewTxInfo returns a new TxInfo instance.
func NewTxInfo(
hash string,
size int64,
gasLimit uint64,
txBytes []byte,
priority any,
signers []signerextraction.SignerData,
) TxWithInfo {
return TxWithInfo{
Hash: hash,
Size: size,
GasLimit: gasLimit,
TxBytes: txBytes,
Priority: priority,
Signers: signers,
}
)
}
// GetTxHashStr returns the TxInfo of a given transaction.
func GetTxInfo(txEncoder sdk.TxEncoder, tx sdk.Tx) (TxInfo, error) {
txBz, err := txEncoder(tx)
// String implements the fmt.Stringer interface.
func (t TxWithInfo) String() string {
return fmt.Sprintf("TxWithInfo{Hash: %s, Size: %d, GasLimit: %d, Priority: %s, Signers: %v}",
t.Hash, t.Size, t.GasLimit, t.Priority, t.Signers)
}
// GetTxHash returns the string hash representation of a transaction.
func GetTxHash(encoder sdk.TxEncoder, tx sdk.Tx) (string, error) {
txBz, err := encoder(tx)
if err != nil {
return TxInfo{}, fmt.Errorf("failed to encode transaction: %w", err)
return "", fmt.Errorf("failed to encode transaction: %w", err)
}
txHashStr := strings.ToUpper(hex.EncodeToString(comettypes.Tx(txBz).Hash()))
// TODO: Add an adapter to lanes so that this can be flexible to support EVM, etc.
gasTx, ok := tx.(sdk.FeeTx)
if !ok {
return TxInfo{}, fmt.Errorf("failed to cast transaction to GasTx")
}
return TxInfo{
Hash: txHashStr,
Size: int64(len(txBz)),
GasLimit: gasTx.GetGas(),
TxBytes: txBz,
}, nil
return txHashStr, nil
}
// GetDecodedTxs returns the decoded transactions from the given bytes.

View File

@ -510,7 +510,10 @@ func (s *BaseTestSuite) TestPrepareLane() {
mockLane.On("Name").Return("test")
mockLane.On("GetMaxBlockSpace").Return(math.LegacyOneDec())
err = emptyProposal.UpdateProposal(mockLane, []sdk.Tx{tx})
txWithInfo, err := lane.GetTxInfo(s.ctx, tx)
s.Require().NoError(err)
err = emptyProposal.UpdateProposal(mockLane, []utils.TxWithInfo{txWithInfo})
s.Require().NoError(err)
finalProposal, err := lane.PrepareLane(s.ctx, emptyProposal, block.NoOpPrepareLanesHandler())

View File

@ -0,0 +1,94 @@
package base_test
import (
"math/rand"
"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/skip-mev/block-sdk/testutils"
)
func (s *BaseTestSuite) TestGetTxInfo() {
accounts := testutils.RandomAccounts(rand.New(rand.NewSource(1)), 3)
lane := s.initLane(math.LegacyOneDec(), nil)
s.Run("can retrieve information for a default tx", func() {
signer := accounts[0]
nonce := uint64(1)
fee := sdk.NewCoins(sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)))
gasLimit := uint64(100)
tx, err := testutils.CreateRandomTx(
s.encodingConfig.TxConfig,
signer,
nonce,
1,
0,
gasLimit,
fee...,
)
s.Require().NoError(err)
txInfo, err := lane.GetTxInfo(s.ctx, tx)
s.Require().NoError(err)
s.Require().NotEmpty(txInfo.Hash)
// Verify the signers
s.Require().Len(txInfo.Signers, 1)
s.Require().Equal(signer.Address.String(), txInfo.Signers[0].Signer.String())
s.Require().Equal(nonce, txInfo.Signers[0].Sequence)
// Verify the priority
actualfee, err := sdk.ParseCoinsNormalized(txInfo.Priority.(string))
s.Require().NoError(err)
s.Require().Equal(fee, actualfee)
// Verify the gas limit
s.Require().Equal(gasLimit, txInfo.GasLimit)
// Verify the bytes
txBz, err := s.encodingConfig.TxConfig.TxEncoder()(tx)
s.Require().NoError(err)
s.Require().Equal(txBz, txInfo.TxBytes)
// Verify the size
s.Require().Equal(int64(len(txBz)), txInfo.Size)
})
s.Run("can retrieve information with different fees", func() {
signer := accounts[1]
nonce := uint64(10)
fee := sdk.NewCoins(sdk.NewCoin(s.gasTokenDenom, math.NewInt(20000)))
gasLimit := uint64(10000000)
tx, err := testutils.CreateRandomTx(
s.encodingConfig.TxConfig,
signer,
nonce,
10,
0,
gasLimit,
fee...,
)
s.Require().NoError(err)
txInfo, err := lane.GetTxInfo(s.ctx, tx)
s.Require().NoError(err)
s.Require().NotEmpty(txInfo.Hash)
// Verify the signers
s.Require().Len(txInfo.Signers, 1)
s.Require().Equal(signer.Address.String(), txInfo.Signers[0].Signer.String())
s.Require().Equal(nonce, txInfo.Signers[0].Sequence)
// Verify the priority
actualfee, err := sdk.ParseCoinsNormalized(txInfo.Priority.(string))
s.Require().NoError(err)
s.Require().Equal(fee, actualfee)
// Verify the bytes
txBz, err := s.encodingConfig.TxConfig.TxEncoder()(tx)
s.Require().NoError(err)
s.Require().Equal(txBz, txInfo.TxBytes)
})
}

View File

@ -8,7 +8,6 @@ import (
"github.com/skip-mev/block-sdk/block/base"
"github.com/skip-mev/block-sdk/block/proposals"
"github.com/skip-mev/block-sdk/block/utils"
)
// PrepareLaneHandler will attempt to select the highest bid transaction that is valid
@ -35,7 +34,9 @@ func (l *MEVLane) PrepareLaneHandler() base.PrepareLaneHandler {
continue
}
bundle, err := l.VerifyBidBasic(bidTx, proposal, limit)
cacheCtx, write := ctx.CacheContext()
bundle, err := l.VerifyBidBasic(cacheCtx, bidTx, proposal, limit)
if err != nil {
l.Logger().Info(
"failed to select auction bid tx for lane; tx is invalid",
@ -46,7 +47,6 @@ func (l *MEVLane) PrepareLaneHandler() base.PrepareLaneHandler {
continue
}
cacheCtx, write := ctx.CacheContext()
if err := l.VerifyBidTx(cacheCtx, bidTx, bundle); err != nil {
l.Logger().Info(
"failed to select auction bid tx for lane; tx is invalid",
@ -161,6 +161,7 @@ func (l *MEVLane) ProcessLaneHandler() base.ProcessLaneHandler {
// VerifyBidBasic will verify that the bid transaction and all of its bundled
// transactions respect the basic invariants of the lane (e.g. size, gas limit).
func (l *MEVLane) VerifyBidBasic(
ctx sdk.Context,
bidTx sdk.Tx,
proposal proposals.Proposal,
limit proposals.LaneLimits,
@ -175,7 +176,7 @@ func (l *MEVLane) VerifyBidBasic(
return nil, fmt.Errorf("bid info is nil")
}
txInfo, err := utils.GetTxInfo(l.TxEncoder(), bidTx)
txInfo, err := l.GetTxInfo(ctx, bidTx)
if err != nil {
return nil, fmt.Errorf("err retrieving transaction info: %s", err)
}
@ -196,7 +197,7 @@ func (l *MEVLane) VerifyBidBasic(
return nil, fmt.Errorf("invalid bid tx; failed to decode bundled tx: %w", err)
}
bundledTxInfo, err := utils.GetTxInfo(l.TxEncoder(), bundledTx)
bundledTxInfo, err := l.GetTxInfo(ctx, bundledTx)
if err != nil {
return nil, fmt.Errorf("err retrieving transaction info: %s", err)
}

View File

@ -547,7 +547,7 @@ func (s *MEVTestSuite) TestVerifyBidBasic() {
)
s.Require().NoError(err)
bundle, err := lane.VerifyBidBasic(bidTx, proposal, limits)
bundle, err := lane.VerifyBidBasic(s.ctx, bidTx, proposal, limits)
s.Require().NoError(err)
s.compare(bundle, expectedBundle)
})
@ -563,7 +563,7 @@ func (s *MEVTestSuite) TestVerifyBidBasic() {
)
s.Require().NoError(err)
_, err = lane.VerifyBidBasic(tx, proposal, limits)
_, err = lane.VerifyBidBasic(s.ctx, tx, proposal, limits)
s.Require().Error(err)
})
@ -579,7 +579,7 @@ func (s *MEVTestSuite) TestVerifyBidBasic() {
)
s.Require().NoError(err)
_, err = lane.VerifyBidBasic(bidTx, proposal, limits)
_, err = lane.VerifyBidBasic(s.ctx, bidTx, proposal, limits)
s.Require().Error(err)
})
@ -599,7 +599,7 @@ func (s *MEVTestSuite) TestVerifyBidBasic() {
proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), size-1, 100)
limits := proposal.GetLaneLimits(lane.GetMaxBlockSpace())
_, err = lane.VerifyBidBasic(bidTx, proposal, limits)
_, err = lane.VerifyBidBasic(s.ctx, bidTx, proposal, limits)
s.Require().Error(err)
})
@ -624,7 +624,7 @@ func (s *MEVTestSuite) TestVerifyBidBasic() {
)
s.Require().NoError(err)
_, err = lane.VerifyBidBasic(bidTx, proposal, limits)
_, err = lane.VerifyBidBasic(s.ctx, bidTx, proposal, limits)
s.Require().Error(err)
})
}

View File

@ -14,8 +14,8 @@ func TxPriority(config Factory) base.TxPriority[string] {
return base.TxPriority[string]{
GetTxPriority: func(goCtx context.Context, tx sdk.Tx) string {
bidInfo, err := config.GetAuctionBidInfo(tx)
if err != nil {
panic(err)
if err != nil || bidInfo == nil || bidInfo.Bid.IsNil() {
return ""
}
return bidInfo.Bid.String()

View File

@ -11,6 +11,7 @@ import (
"github.com/skip-mev/block-sdk/block"
"github.com/skip-mev/block-sdk/block/proposals"
"github.com/skip-mev/block-sdk/block/utils"
)
const (
@ -73,6 +74,11 @@ func (t Terminator) Name() string {
return LaneName
}
// GetTxInfo is a no-op
func (t Terminator) GetTxInfo(_ sdk.Context, _ sdk.Tx) (utils.TxWithInfo, error) {
return utils.TxWithInfo{}, fmt.Errorf("terminator lane should not have any transactions")
}
// SetAnteHandler is a no-op
func (t Terminator) SetAnteHandler(sdk.AnteHandler) {}
@ -114,7 +120,12 @@ func (t Terminator) Select(context.Context, [][]byte) sdkmempool.Iterator {
return nil
}
// HasHigherPriority is a no-op
// Compare is a no-op
func (t Terminator) Compare(sdk.Context, sdk.Tx, sdk.Tx) (int, error) {
return 0, nil
}
// Priority is a no-op
func (t Terminator) Priority(sdk.Context, sdk.Tx) any {
return 0
}

View File

@ -109,7 +109,8 @@ func (s *IntegrationTestSuite) SetupSubTest() {
// query height
height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background())
require.NoError(s.T(), err)
WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1)
WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+3)
s.T().Logf("reached height %d", height+2)
}
func (s *IntegrationTestSuite) TestQueryParams() {
@ -1354,7 +1355,7 @@ func (s *IntegrationTestSuite) TestNetwork() {
}
})
amountToTest.Reset(5 * time.Minute)
amountToTest.Reset(3 * time.Minute)
s.Run("can produce blocks with all types of transactions", func() {
for {
select {

View File

@ -7,6 +7,7 @@ import (
"encoding/hex"
"encoding/json"
"io"
"math/rand"
"os"
"path"
"strings"
@ -183,7 +184,7 @@ func (s *IntegrationTestSuite) CreateDummyFreeTx(
return Tx{
User: user,
Msgs: []sdk.Msg{delegateMsg},
GasPrice: 1000,
GasPrice: rand.Int63n(150000),
SequenceIncrement: sequenceOffset,
SkipInclusionCheck: true,
IgnoreChecks: true,