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:
co-authored by
Alex Johnson
parent
01a84b1cc5
commit
be4465ae95
+36
-2
@@ -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(),
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
+11
-6
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+14
-18
@@ -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.
|
||||
|
||||
+46
-31
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user