feat: Greedy Algorithm for Lane Verification (#236)

* greedy approach to lane verification

* docs

* base lane testing

* mev lane testing nits

* abci top level testing done

* network spamming in E2E

* removing logs from testing

* nit
This commit is contained in:
David Terpay 2023-11-27 15:43:09 -06:00 committed by GitHub
parent f607439637
commit f7dfbda2b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1213 additions and 935 deletions

View File

@ -7,6 +7,7 @@
"MD033": false,
"MD034": false,
"MD014": false,
"MD013": false,
"no-hard-tabs": false,
"whitespace": false
}

View File

@ -9,11 +9,7 @@ import (
"github.com/skip-mev/block-sdk/block"
"github.com/skip-mev/block-sdk/block/proposals"
)
const (
// ProposalInfoIndex is the index of the proposal metadata in the proposal.
ProposalInfoIndex = 0
"github.com/skip-mev/block-sdk/block/utils"
)
type (
@ -78,25 +74,17 @@ func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler {
return &abci.ResponsePrepareProposal{Txs: make([][]byte, 0)}, err
}
prepareLanesHandler := ChainPrepareLanes(registry)
// Fill the proposal with transactions from each lane.
prepareLanesHandler := ChainPrepareLanes(registry)
finalProposal, err := 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
}
// Retrieve the proposal with metadata and transactions.
txs, err := finalProposal.GetProposalWithInfo()
if err != nil {
h.logger.Error("failed to get proposal with metadata", "err", err)
return &abci.ResponsePrepareProposal{Txs: make([][]byte, 0)}, err
}
h.logger.Info(
"prepared proposal",
"num_txs", len(txs),
"num_txs", len(finalProposal.Txs),
"total_tx_bytes", finalProposal.Info.BlockSize,
"max_tx_bytes", finalProposal.Info.MaxBlockSize,
"total_gas_limit", finalProposal.Info.GasLimit,
@ -111,7 +99,7 @@ func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler {
)
return &abci.ResponsePrepareProposal{
Txs: txs,
Txs: finalProposal.Txs,
}, nil
}
}
@ -119,9 +107,9 @@ func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler {
// ProcessProposalHandler processes the proposal by verifying all transactions in the proposal
// according to each lane's verification logic. Proposals are verified similar to how they are
// constructed. After a proposal is processed, it should amount to the same proposal that was prepared.
// Each proposal will first be broken down by the lanes that prepared each partial proposal. Then, each
// lane will iteratively verify the transactions that it belong to it. If any lane fails to verify the
// transactions, then the proposal is rejected.
// The proposal is verified in a greedy fashion, respecting the ordering of lanes. A lane will
// verify all transactions in the proposal that belong to the lane and pass any remaining transactions
// to the next lane in the chain.
func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler {
return func(ctx sdk.Context, req *abci.RequestProcessProposal) (resp *abci.ResponseProcessProposal, err error) {
if req.Height <= 1 {
@ -138,10 +126,10 @@ func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler {
}
}()
// Extract all of the lanes and their corresponding transactions from the proposal.
proposalInfo, partialProposals, err := h.ExtractLanes(ctx, req.Txs)
// Decode the transactions in the proposal. These will be verified by each lane in a greedy fashion.
decodedTxs, err := utils.GetDecodedTxs(h.txDecoder, req.Txs)
if err != nil {
h.logger.Error("failed to validate proposal", "err", err)
h.logger.Error("failed to decode txs", "err", err)
return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, err
}
@ -152,22 +140,21 @@ func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler {
return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, err
}
processLanesHandler := ChainProcessLanes(partialProposals, registry)
finalProposal, err := processLanesHandler(ctx, proposals.NewProposalWithContext(h.logger, ctx, h.txEncoder))
// Verify the proposal.
processLanesHandler := ChainProcessLanes(registry)
finalProposal, err := processLanesHandler(
ctx,
proposals.NewProposalWithContext(h.logger, ctx, h.txEncoder),
decodedTxs,
)
if err != nil {
h.logger.Error("failed to validate the proposal", "err", err)
return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, err
}
// Ensure block size and gas limit are correct.
if err := h.ValidateBlockLimits(finalProposal, proposalInfo); err != nil {
h.logger.Error("failed to validate the proposal", "err", err)
return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, err
}
h.logger.Info(
"processed proposal",
"num_txs", len(req.Txs),
"num_txs", len(finalProposal.Txs),
"total_tx_bytes", finalProposal.Info.BlockSize,
"max_tx_bytes", finalProposal.Info.MaxBlockSize,
"total_gas_limit", finalProposal.Info.GasLimit,

File diff suppressed because it is too large Load Diff

View File

@ -1,108 +1,13 @@
package abci
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/skip-mev/block-sdk/block"
"github.com/skip-mev/block-sdk/block/proposals"
"github.com/skip-mev/block-sdk/block/proposals/types"
"github.com/skip-mev/block-sdk/lanes/terminator"
)
// ExtractLanes validates the proposal against the basic invariants that are required
// for the proposal to be valid. This includes:
// 1. The proposal must contain the proposal information and must be valid.
// 2. The proposal must contain the correct number of transactions for each lane.
func (h *ProposalHandler) ExtractLanes(ctx sdk.Context, proposal [][]byte) (types.ProposalInfo, [][][]byte, error) {
// If the proposal is empty, then the metadata was not included.
if len(proposal) == 0 {
return types.ProposalInfo{}, nil, fmt.Errorf("proposal does not contain proposal metadata")
}
metaDataBz, txs := proposal[ProposalInfoIndex], proposal[ProposalInfoIndex+1:]
// Retrieve the metadata from the proposal.
var metaData types.ProposalInfo
if err := metaData.Unmarshal(metaDataBz); err != nil {
return types.ProposalInfo{}, nil, fmt.Errorf("failed to unmarshal proposal metadata: %w", err)
}
lanes, err := h.mempool.Registry(ctx)
if err != nil {
return types.ProposalInfo{}, nil, fmt.Errorf("failed to get mempool registry: %w", err)
}
partialProposals := make([][][]byte, len(lanes))
if metaData.TxsByLane == nil {
if len(txs) > 0 {
return types.ProposalInfo{}, nil, fmt.Errorf("proposal contains invalid number of transactions")
}
return types.ProposalInfo{}, partialProposals, nil
}
h.logger.Info(
"received proposal with metadata",
"max_block_size", metaData.MaxBlockSize,
"max_gas_limit", metaData.MaxGasLimit,
"gas_limit", metaData.GasLimit,
"block_size", metaData.BlockSize,
"lanes_with_txs", metaData.TxsByLane,
)
// Iterate through all of the lanes and match the corresponding transactions to the lane.
for index, lane := range lanes {
numTxs := metaData.TxsByLane[lane.Name()]
if numTxs > uint64(len(txs)) {
return types.ProposalInfo{}, nil, fmt.Errorf(
"proposal metadata contains invalid number of transactions for lane %s; got %d, expected %d",
lane.Name(),
len(txs),
numTxs,
)
}
partialProposals[index] = txs[:numTxs]
txs = txs[numTxs:]
}
// If there are any transactions remaining in the proposal, then the proposal is invalid.
if len(txs) > 0 {
return types.ProposalInfo{}, nil, fmt.Errorf("proposal contains invalid number of transactions")
}
return metaData, partialProposals, nil
}
// ValidateBlockLimits validates the block limits of the proposal against the block limits
// of the chain.
func (h *ProposalHandler) ValidateBlockLimits(finalProposal proposals.Proposal, proposalInfo types.ProposalInfo) error {
// Conduct final checks on block size and gas limit.
if finalProposal.Info.BlockSize != proposalInfo.BlockSize {
h.logger.Error(
"proposal block size does not match",
"expected", proposalInfo.BlockSize,
"got", finalProposal.Info.BlockSize,
)
return fmt.Errorf("proposal block size does not match")
}
if finalProposal.Info.GasLimit != proposalInfo.GasLimit {
h.logger.Error(
"proposal gas limit does not match",
"expected", proposalInfo.GasLimit,
"got", finalProposal.Info.GasLimit,
)
return fmt.Errorf("proposal gas limit does not match")
}
return nil
}
// ChainPrepareLanes chains together the proposal preparation logic from each lane into a
// single function. The first lane in the chain is the first lane to be prepared and the
// last lane in the chain is the last lane to be prepared. In the case where any of the lanes
@ -162,8 +67,11 @@ func ChainPrepareLanes(chain []block.Lane) block.PrepareLanesHandler {
// ChainProcessLanes chains together the proposal verification logic from each lane
// into a single function. The first lane in the chain is the first lane to be verified and
// the last lane in the chain is the last lane to be verified. Each lane will validate
// the transactions that it selected in the prepare phase.
func ChainProcessLanes(partialProposals [][][]byte, chain []block.Lane) block.ProcessLanesHandler {
// the transactions that belong to the lane and pass any remaining transactions to the next
// lane in the chain. If any of the lanes fail to verify the transactions, the proposal will
// be rejected. If there are any remaining transactions after all lanes have been processed,
// the proposal will be rejected.
func ChainProcessLanes(chain []block.Lane) block.ProcessLanesHandler {
if len(chain) == 0 {
return nil
}
@ -171,12 +79,10 @@ func ChainProcessLanes(partialProposals [][][]byte, chain []block.Lane) block.Pr
// Handle non-terminated decorators chain
if (chain[len(chain)-1] != terminator.Terminator{}) {
chain = append(chain, terminator.Terminator{})
partialProposals = append(partialProposals, nil)
}
return func(ctx sdk.Context, proposal proposals.Proposal) (proposals.Proposal, error) {
return func(ctx sdk.Context, proposal proposals.Proposal, txs []sdk.Tx) (proposals.Proposal, error) {
lane := chain[0]
partialProposal := partialProposals[0]
return lane.ProcessLane(ctx, proposal, partialProposal, ChainProcessLanes(partialProposals[1:], chain[1:]))
return lane.ProcessLane(ctx, proposal, txs, ChainProcessLanes(chain[1:]))
}
}

View File

@ -17,9 +17,6 @@ import (
signeradaptors "github.com/skip-mev/block-sdk/adapters/signer_extraction_adapter"
"github.com/skip-mev/block-sdk/block"
"github.com/skip-mev/block-sdk/block/base"
"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/lanes/free"
"github.com/skip-mev/block-sdk/lanes/mev"
@ -58,6 +55,29 @@ func (s *ProposalsTestSuite) setUpAnteHandler(expectedExecution map[sdk.Tx]bool)
return anteHandler
}
func (s *ProposalsTestSuite) setUpCustomMatchHandlerLane(maxBlockSpace math.LegacyDec, expectedExecution map[sdk.Tx]bool, mh base.MatchHandler, name string) block.Lane {
cfg := base.LaneConfig{
Logger: log.NewNopLogger(),
TxEncoder: s.encodingConfig.TxConfig.TxEncoder(),
TxDecoder: s.encodingConfig.TxConfig.TxDecoder(),
AnteHandler: s.setUpAnteHandler(expectedExecution),
MaxBlockSpace: maxBlockSpace,
SignerExtractor: signeradaptors.NewDefaultAdapter(),
}
lane := base.NewBaseLane(
cfg,
name,
base.NewMempool[string](base.DefaultTxPriority(), cfg.TxEncoder, cfg.SignerExtractor, 0),
mh,
)
lane.SetPrepareLaneHandler(lane.DefaultPrepareLaneHandler())
lane.SetProcessLaneHandler(lane.DefaultProcessLaneHandler())
return lane
}
func (s *ProposalsTestSuite) setUpStandardLane(maxBlockSpace math.LegacyDec, expectedExecution map[sdk.Tx]bool) *defaultlane.DefaultLane {
cfg := base.LaneConfig{
Logger: log.NewNopLogger(),
@ -153,51 +173,8 @@ func (s *ProposalsTestSuite) setUpProposalHandlers(lanes []block.Lane) *abci.Pro
)
}
func (s *ProposalsTestSuite) createProposal(distribution map[string]uint64, txs ...sdk.Tx) [][]byte {
maxSize, maxGasLimit := proposals.GetBlockLimits(s.ctx)
size, limit := s.getTxInfos(txs...)
info := s.createProposalInfoBytes(
maxGasLimit,
limit,
maxSize,
size,
distribution,
)
proposal := s.getTxBytes(txs...)
return append([][]byte{info}, proposal...)
}
func (s *ProposalsTestSuite) getProposalInfo(bz []byte) types.ProposalInfo {
var info types.ProposalInfo
s.Require().NoError(info.Unmarshal(bz))
return info
}
func (s *ProposalsTestSuite) createProposalInfo(
maxGasLimit, gasLimit uint64,
maxBlockSize, blockSize int64,
txsByLane map[string]uint64,
) types.ProposalInfo {
return types.ProposalInfo{
MaxGasLimit: maxGasLimit,
GasLimit: gasLimit,
MaxBlockSize: maxBlockSize,
BlockSize: blockSize,
TxsByLane: txsByLane,
}
}
func (s *ProposalsTestSuite) createProposalInfoBytes(
maxGasLimit, gasLimit uint64,
maxBlockSize, blockSize int64,
txsByLane map[string]uint64,
) []byte {
info := s.createProposalInfo(maxGasLimit, gasLimit, maxBlockSize, blockSize, txsByLane)
bz, err := info.Marshal()
s.Require().NoError(err)
return bz
func (s *ProposalsTestSuite) createProposal(txs ...sdk.Tx) [][]byte {
return s.getTxBytes(txs...)
}
func (s *ProposalsTestSuite) getTxBytes(txs ...sdk.Tx) [][]byte {
@ -211,21 +188,6 @@ func (s *ProposalsTestSuite) getTxBytes(txs ...sdk.Tx) [][]byte {
return txBytes
}
func (s *ProposalsTestSuite) getTxInfos(txs ...sdk.Tx) (int64, uint64) {
totalSize := int64(0)
totalGasLimit := uint64(0)
for _, tx := range txs {
info, err := utils.GetTxInfo(s.encodingConfig.TxConfig.TxEncoder(), tx)
s.Require().NoError(err)
totalSize += info.Size
totalGasLimit += info.GasLimit
}
return totalSize, totalGasLimit
}
func (s *ProposalsTestSuite) setBlockParams(maxGasLimit, maxBlockSize int64) {
s.ctx = s.ctx.WithConsensusParams(
tmprototypes.ConsensusParams{

View File

@ -76,42 +76,39 @@ func (l *BaseLane) PrepareLane(
func (l *BaseLane) ProcessLane(
ctx sdk.Context,
proposal proposals.Proposal,
txs [][]byte,
txs []sdk.Tx,
next block.ProcessLanesHandler,
) (proposals.Proposal, error) {
l.Logger().Info("processing lane", "lane", l.Name(), "num_txs_to_verify", len(txs))
l.Logger().Info(
"processing lane",
"lane", l.Name(),
"num_txs_to_verify", len(txs),
)
// Assume that this lane is processing sdk.Tx's and decode the transactions.
decodedTxs, err := utils.GetDecodedTxs(l.TxDecoder(), txs)
if err != nil {
l.Logger().Error(
"failed to decode transactions",
"lane", l.Name(),
"err", err,
)
return proposal, err
if len(txs) == 0 {
return next(ctx, proposal, txs)
}
// Verify the transactions that belong to this lane according to the verification logic of the lane.
if err := l.processLaneHandler(ctx, decodedTxs); err != nil {
// Verify the transactions that belong to the lane and return any transactions that must be
// validated by the next lane in the chain.
txsFromLane, remainingTxs, err := l.processLaneHandler(ctx, txs)
if err != nil {
l.Logger().Error(
"failed to process lane",
"lane", l.Name(),
"err", err,
"num_txs_to_verify", len(decodedTxs),
)
return proposal, err
}
// Optimistically update the proposal with the partial proposal.
if err := proposal.UpdateProposal(l, decodedTxs); err != nil {
if err := proposal.UpdateProposal(l, txsFromLane); err != nil {
l.Logger().Error(
"failed to update proposal",
"lane", l.Name(),
"num_txs_verified", len(txsFromLane),
"err", err,
"num_txs_to_verify", len(decodedTxs),
)
return proposal, err
@ -120,10 +117,12 @@ func (l *BaseLane) ProcessLane(
l.Logger().Info(
"lane processed",
"lane", l.Name(),
"num_txs_verified", len(decodedTxs),
"num_txs_verified", len(txsFromLane),
"num_txs_remaining", len(remainingTxs),
)
return next(ctx, proposal)
// Validate the remaining transactions with the next lane in the chain.
return next(ctx, proposal, remainingTxs)
}
// VerifyTx verifies that the transaction is valid respecting the ante verification logic of

View File

@ -111,35 +111,59 @@ func (l *BaseLane) DefaultPrepareLaneHandler() PrepareLaneHandler {
// DefaultProcessLaneHandler returns a default implementation of the ProcessLaneHandler. It verifies
// the following invariants:
// 1. All transactions belong to this lane.
// 2. All transactions respect the priority defined by the mempool.
// 3. All transactions are valid respecting the verification logic of the lane.
// 1. Transactions belonging to the lane must be contiguous from the beginning of the partial proposal.
// 2. Transactions that do not belong to the lane must be contiguous from the end of the partial proposal.
// 3. Transactions must be ordered respecting the priority defined by the lane (e.g. gas price).
// 4. Transactions must be valid according to the verification logic of the lane.
func (l *BaseLane) DefaultProcessLaneHandler() ProcessLaneHandler {
return func(ctx sdk.Context, partialProposal []sdk.Tx) error {
// Process all transactions that match the lane's matcher.
return func(ctx sdk.Context, partialProposal []sdk.Tx) ([]sdk.Tx, []sdk.Tx, error) {
if len(partialProposal) == 0 {
return nil, nil, nil
}
for index, tx := range partialProposal {
if !l.Match(ctx, tx) {
return fmt.Errorf("the %s lane contains a transaction that belongs to another lane", l.Name())
// If the transaction does not belong to this lane, we return the remaining transactions
// iff there are no matches in the remaining transactions after this index.
if index+1 < len(partialProposal) {
if err := l.VerifyNoMatches(ctx, partialProposal[index+1:]); err != nil {
return nil, nil, fmt.Errorf("failed to verify no matches: %w", err)
}
}
return partialProposal[:index], partialProposal[index:], nil
}
// If the transactions do not respect the priority defined by the mempool, we consider the proposal
// to be invalid
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)
return nil, nil, fmt.Errorf("transaction at index %d has a higher priority than %d", index, index-1)
}
}
if err := l.VerifyTx(ctx, tx, false); err != nil {
return fmt.Errorf("failed to verify tx: %w", err)
return nil, nil, fmt.Errorf("failed to verify tx: %w", err)
}
}
// This means we have processed all transactions in the partial proposal.
return nil
// This means we have processed all transactions in the partial proposal i.e.
// all of the transactions belong to this lane. There are no remaining transactions.
return partialProposal, nil, nil
}
}
// VerifyNoMatches returns an error if any of the transactions match the lane.
func (l *BaseLane) VerifyNoMatches(ctx sdk.Context, txs []sdk.Tx) error {
for _, tx := range txs {
if l.Match(ctx, tx) {
return fmt.Errorf("transaction belongs to lane when it should not")
}
}
return nil
}
// DefaultMatchHandler returns a default implementation of the MatchHandler. It matches all
// transactions.
func DefaultMatchHandler() MatchHandler {

View File

@ -21,9 +21,13 @@ type (
) (txsToInclude []sdk.Tx, txsToRemove []sdk.Tx, err error)
// ProcessLaneHandler is responsible for processing transactions that are included in a block and
// belong to a given lane. This handler must return an error if the transactions are not correctly
// ordered, do not belong to this lane, or any other relevant error.
ProcessLaneHandler func(ctx sdk.Context, partialProposal []sdk.Tx) error
// belong to a given lane. The handler must return the transactions that were successfully processed
// and the transactions that it cannot process because they belong to a different lane.
ProcessLaneHandler func(ctx sdk.Context, partialProposal []sdk.Tx) (
txsFromLane []sdk.Tx,
remainingTxs []sdk.Tx,
err error,
)
)
// NoOpPrepareLaneHandler returns a no-op prepare lane handler.
@ -45,15 +49,15 @@ func PanicPrepareLaneHandler() PrepareLaneHandler {
// NoOpProcessLaneHandler returns a no-op process lane handler.
// This should only be used for testing.
func NoOpProcessLaneHandler() ProcessLaneHandler {
return func(sdk.Context, []sdk.Tx) error {
return nil
return func(sdk.Context, []sdk.Tx) ([]sdk.Tx, []sdk.Tx, error) {
return nil, nil, nil
}
}
// PanicProcessLanesHandler returns a process lanes handler that panics.
// This should only be used for testing.
func PanicProcessLaneHandler() ProcessLaneHandler {
return func(sdk.Context, []sdk.Tx) error {
return func(sdk.Context, []sdk.Tx) ([]sdk.Tx, []sdk.Tx, error) {
panic("panic process lanes handler")
}
}

View File

@ -50,7 +50,7 @@ type Lane interface {
ProcessLane(
ctx sdk.Context,
proposal proposals.Proposal,
partialProposal [][]byte,
txs []sdk.Tx,
next ProcessLanesHandler,
) (proposals.Proposal, error)

View File

@ -171,23 +171,23 @@ func (_m *Lane) PrepareLane(ctx types.Context, proposal proposals.Proposal, next
return r0, r1
}
// ProcessLane provides a mock function with given fields: ctx, proposal, partialProposal, next
func (_m *Lane) ProcessLane(ctx types.Context, proposal proposals.Proposal, partialProposal [][]byte, next block.ProcessLanesHandler) (proposals.Proposal, error) {
ret := _m.Called(ctx, proposal, partialProposal, next)
// 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)
var r0 proposals.Proposal
var r1 error
if rf, ok := ret.Get(0).(func(types.Context, proposals.Proposal, [][]byte, block.ProcessLanesHandler) (proposals.Proposal, error)); ok {
return rf(ctx, proposal, partialProposal, next)
if rf, ok := ret.Get(0).(func(types.Context, proposals.Proposal, []types.Tx, block.ProcessLanesHandler) (proposals.Proposal, error)); ok {
return rf(ctx, proposal, txs, next)
}
if rf, ok := ret.Get(0).(func(types.Context, proposals.Proposal, [][]byte, block.ProcessLanesHandler) proposals.Proposal); ok {
r0 = rf(ctx, proposal, partialProposal, next)
if rf, ok := ret.Get(0).(func(types.Context, proposals.Proposal, []types.Tx, block.ProcessLanesHandler) proposals.Proposal); ok {
r0 = rf(ctx, proposal, txs, next)
} else {
r0 = ret.Get(0).(proposals.Proposal)
}
if rf, ok := ret.Get(1).(func(types.Context, proposals.Proposal, [][]byte, block.ProcessLanesHandler) error); ok {
r1 = rf(ctx, proposal, partialProposal, next)
if rf, ok := ret.Get(1).(func(types.Context, proposals.Proposal, []types.Tx, block.ProcessLanesHandler) error); ok {
r1 = rf(ctx, proposal, txs, next)
} else {
r1 = ret.Error(1)
}

View File

@ -48,6 +48,9 @@ func NewProposal(logger log.Logger, txEncoder sdk.TxEncoder, maxBlockSize int64,
// GetProposalWithInfo returns all of the transactions in the proposal along with information
// about the lanes that built the proposal.
//
// NOTE: This is currently not used in production but likely will be once
// ABCI 3.0 is released.
func (p *Proposal) GetProposalWithInfo() ([][]byte, error) {
// Marshall the proposal info into the first slot of the proposal.
infoBz, err := p.Info.Marshal()

View File

@ -1,7 +1,6 @@
package proposals
import (
"encoding/base64"
"fmt"
"cosmossdk.io/math"
@ -48,15 +47,13 @@ func (p *Proposal) UpdateProposal(lane Lane, partialProposal []sdk.Tx) error {
return fmt.Errorf("err retrieving transaction info: %s", err)
}
p.Logger.Debug(
p.Logger.Info(
"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.

View File

@ -15,7 +15,7 @@ type (
// ProcessLanesHandler wraps all of the lanes' ProcessLane functions into a single chained
// function. You can think of it like an AnteHandler, but for processing proposals in the
// context of lanes instead of modules.
ProcessLanesHandler func(ctx sdk.Context, proposal proposals.Proposal) (proposals.Proposal, error)
ProcessLanesHandler func(ctx sdk.Context, proposal proposals.Proposal, txs []sdk.Tx) (proposals.Proposal, error)
)
// NoOpPrepareLanesHandler returns a no-op prepare lanes handler.
@ -29,7 +29,7 @@ func NoOpPrepareLanesHandler() PrepareLanesHandler {
// NoOpProcessLanesHandler returns a no-op process lanes handler.
// This should only be used for testing.
func NoOpProcessLanesHandler() ProcessLanesHandler {
return func(_ sdk.Context, p proposals.Proposal) (proposals.Proposal, error) {
return func(_ sdk.Context, p proposals.Proposal, _ []sdk.Tx) (proposals.Proposal, error) {
return p, nil
}
}

View File

@ -1,9 +1,11 @@
package utils
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"strings"
comettypes "github.com/cometbft/cometbft/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool"
@ -31,8 +33,7 @@ func GetTxInfo(txEncoder sdk.TxEncoder, tx sdk.Tx) (TxInfo, error) {
return TxInfo{}, fmt.Errorf("failed to encode transaction: %w", err)
}
txHash := sha256.Sum256(txBz)
txHashStr := hex.EncodeToString(txHash[:])
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)

View File

@ -10,6 +10,7 @@ import (
"cosmossdk.io/log"
"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/mock"
signer_extraction "github.com/skip-mev/block-sdk/adapters/signer_extraction_adapter"
"github.com/skip-mev/block-sdk/block"
@ -521,6 +522,51 @@ func (s *BaseTestSuite) TestPrepareLane() {
s.Require().Equal(uint64(2), finalProposal.Info.GasLimit)
s.Require().Equal([][]byte{txBz}, finalProposal.Txs)
})
s.Run("should not attempt to include transaction that matches to a different lane", func() {
// Create a basic transaction that should not in the proposal
tx, err := testutils.CreateRandomTx(
s.encodingConfig.TxConfig,
s.accounts[0],
0,
1,
0,
2,
sdk.NewCoin(s.gasTokenDenom, math.NewInt(1)),
)
s.Require().NoError(err)
// Create a lane with a max block space of 1 but a proposal that is smaller than the tx
expectedExecution := map[sdk.Tx]bool{
tx: true,
}
lane := s.initLane(math.LegacyOneDec(), expectedExecution)
// Insert the transaction into the lane
s.Require().NoError(lane.Insert(s.ctx, tx))
mockLane := mocks.NewLane(s.T())
mockLane.On(
"Match",
mock.Anything,
tx,
).Return(true, nil)
lane.SetIgnoreList([]block.Lane{mockLane})
txBz, err := s.encodingConfig.TxConfig.TxEncoder()(tx)
s.Require().NoError(err)
emptyProposal := proposals.NewProposal(
log.NewNopLogger(),
s.encodingConfig.TxConfig.TxEncoder(),
int64(len(txBz))*10,
1000000,
)
finalProposal, err := lane.PrepareLane(s.ctx, emptyProposal, block.NoOpPrepareLanesHandler())
s.Require().NoError(err)
s.Require().Len(finalProposal.Txs, 0)
})
}
func (s *BaseTestSuite) TestProcessLane() {
@ -560,8 +606,10 @@ func (s *BaseTestSuite) TestProcessLane() {
},
)
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal)
s.Require().NoError(err)
s.Require().Len(txsFromLane, 2)
s.Require().Len(remainingTxs, 0)
emptyProposal := proposals.NewProposal(
log.NewNopLogger(),
@ -570,8 +618,13 @@ func (s *BaseTestSuite) TestProcessLane() {
100000,
)
_, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
finalProposal, err := lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler())
s.Require().Len(finalProposal.Txs, 2)
s.Require().NoError(err)
encodedTxs, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
s.Require().NoError(err)
s.Require().Equal(encodedTxs, finalProposal.Txs)
})
s.Run("should accept a proposal where transaction fees are not in order bc of sequence numbers with other txs", func() {
@ -623,8 +676,11 @@ func (s *BaseTestSuite) TestProcessLane() {
},
)
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
//
txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal)
s.Require().NoError(err)
s.Require().Len(txsFromLane, 3)
s.Require().Len(remainingTxs, 0)
emptyProposal := proposals.NewProposal(
log.NewNopLogger(),
@ -633,8 +689,13 @@ func (s *BaseTestSuite) TestProcessLane() {
100000,
)
_, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
finalProposal, err := lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler())
s.Require().Len(finalProposal.Txs, 3)
s.Require().NoError(err)
encodedTxs, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
s.Require().NoError(err)
s.Require().Equal(encodedTxs, finalProposal.Txs)
})
s.Run("accepts proposal with multiple senders and seq nums", func() {
@ -699,8 +760,10 @@ func (s *BaseTestSuite) TestProcessLane() {
},
)
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal)
s.Require().NoError(err)
s.Require().Len(txsFromLane, 4)
s.Require().Len(remainingTxs, 0)
emptyProposal := proposals.NewProposal(
log.NewNopLogger(),
@ -709,11 +772,16 @@ func (s *BaseTestSuite) TestProcessLane() {
100000,
)
_, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
finalProposal, err := lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler())
s.Require().Len(finalProposal.Txs, 4)
s.Require().NoError(err)
encodedTxs, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
s.Require().NoError(err)
s.Require().Equal(encodedTxs, finalProposal.Txs)
})
s.Run("should accept a proposal with valid transactions", func() {
s.Run("should accept a proposal with a single valid transaction", func() {
tx1, err := testutils.CreateRandomTx(
s.encodingConfig.TxConfig,
s.accounts[0],
@ -735,8 +803,10 @@ func (s *BaseTestSuite) TestProcessLane() {
},
)
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal)
s.Require().NoError(err)
s.Require().Len(txsFromLane, 1)
s.Require().Len(remainingTxs, 0)
emptyProposal := proposals.NewProposal(
log.NewNopLogger(),
@ -745,8 +815,13 @@ func (s *BaseTestSuite) TestProcessLane() {
100000,
)
_, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
finalProposal, err := lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler())
s.Require().Len(finalProposal.Txs, 1)
s.Require().NoError(err)
encodedTxs, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
s.Require().NoError(err)
s.Require().Equal(encodedTxs, finalProposal.Txs)
})
s.Run("should not accept a proposal with invalid transactions", func() {
@ -771,8 +846,10 @@ func (s *BaseTestSuite) TestProcessLane() {
},
)
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
s.Require().NoError(err)
txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal)
s.Require().Error(err)
s.Require().Len(txsFromLane, 0)
s.Require().Len(remainingTxs, 0)
emptyProposal := proposals.NewProposal(
log.NewNopLogger(),
@ -781,7 +858,7 @@ func (s *BaseTestSuite) TestProcessLane() {
100000,
)
_, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
_, err = lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler())
s.Require().Error(err)
})
@ -828,10 +905,13 @@ func (s *BaseTestSuite) TestProcessLane() {
tx1: true,
tx2: false,
tx3: true,
})
},
)
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
s.Require().NoError(err)
txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal)
s.Require().Error(err)
s.Require().Len(txsFromLane, 0)
s.Require().Len(remainingTxs, 0)
emptyProposal := proposals.NewProposal(
log.NewNopLogger(),
@ -840,11 +920,11 @@ func (s *BaseTestSuite) TestProcessLane() {
100000,
)
_, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
_, err = lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler())
s.Require().Error(err)
})
s.Run("should accept proposal with transactions in correct order", func() {
s.Run("should accept proposal with transactions in correct order with same fees", func() {
tx1, err := testutils.CreateRandomTx(
s.encodingConfig.TxConfig,
s.accounts[0],
@ -863,7 +943,7 @@ func (s *BaseTestSuite) TestProcessLane() {
1,
0,
1,
sdk.NewCoin(s.gasTokenDenom, math.NewInt(1)),
sdk.NewCoin(s.gasTokenDenom, math.NewInt(2)),
)
s.Require().NoError(err)
@ -877,10 +957,13 @@ func (s *BaseTestSuite) TestProcessLane() {
map[sdk.Tx]bool{
tx1: true,
tx2: true,
})
},
)
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal)
s.Require().NoError(err)
s.Require().Len(txsFromLane, 2)
s.Require().Len(remainingTxs, 0)
emptyProposal := proposals.NewProposal(
log.NewNopLogger(),
@ -889,11 +972,16 @@ func (s *BaseTestSuite) TestProcessLane() {
100000,
)
_, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
finalProposal, err := lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler())
s.Require().Len(finalProposal.Txs, 2)
s.Require().NoError(err)
encodedTxs, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
s.Require().NoError(err)
s.Require().Equal(encodedTxs, finalProposal.Txs)
})
s.Run("should not accept a proposal with transactions that are not in the correct order", func() {
s.Run("should not accept a proposal with transactions that are not in the correct order fee wise", func() {
tx1, err := testutils.CreateRandomTx(
s.encodingConfig.TxConfig,
s.accounts[0],
@ -926,10 +1014,13 @@ func (s *BaseTestSuite) TestProcessLane() {
map[sdk.Tx]bool{
tx1: true,
tx2: true,
})
},
)
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
s.Require().NoError(err)
txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal)
s.Require().Error(err)
s.Require().Len(txsFromLane, 0)
s.Require().Len(remainingTxs, 0)
emptyProposal := proposals.NewProposal(
log.NewNopLogger(),
@ -938,11 +1029,11 @@ func (s *BaseTestSuite) TestProcessLane() {
100000,
)
_, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
_, err = lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler())
s.Require().Error(err)
})
s.Run("should not accept a proposal where transactions are out of order relative to other lanes", func() {
s.Run("should not accept proposal where transactions from lane are not contiguous from the start", func() {
tx1, err := testutils.CreateRandomTx(
s.encodingConfig.TxConfig,
s.accounts[0],
@ -965,14 +1056,27 @@ func (s *BaseTestSuite) TestProcessLane() {
)
s.Require().NoError(err)
otherLane := s.initLane(math.LegacyOneDec(), nil)
// First lane matches this lane the other does not.
otherLane := mocks.NewLane(s.T())
otherLane.On(
"Match",
mock.Anything,
tx1,
).Return(true, nil)
otherLane.On(
"Match",
mock.Anything,
tx2,
).Return(false, nil)
lane := s.initLane(
math.LegacyOneDec(),
map[sdk.Tx]bool{
tx1: true,
tx2: false,
})
tx2: true,
},
)
lane.SetIgnoreList([]block.Lane{otherLane})
proposal := []sdk.Tx{
@ -980,8 +1084,10 @@ func (s *BaseTestSuite) TestProcessLane() {
tx2,
}
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
s.Require().NoError(err)
txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal)
s.Require().Error(err)
s.Require().Len(txsFromLane, 0)
s.Require().Len(remainingTxs, 0)
emptyProposal := proposals.NewProposal(
log.NewNopLogger(),
@ -990,7 +1096,7 @@ func (s *BaseTestSuite) TestProcessLane() {
100000,
)
_, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
_, err = lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler())
s.Require().Error(err)
})
@ -1013,12 +1119,16 @@ func (s *BaseTestSuite) TestProcessLane() {
math.LegacyOneDec(),
map[sdk.Tx]bool{
tx1: true,
})
},
)
maxSize := s.getTxSize(tx1) - 1
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal)
s.Require().NoError(err)
s.Require().Len(txsFromLane, 1)
s.Require().Len(remainingTxs, 0)
// Set the size to be 1 less than the size of the transaction
maxSize := s.getTxSize(tx1) - 1
emptyProposal := proposals.NewProposal(
log.NewNopLogger(),
s.encodingConfig.TxConfig.TxEncoder(),
@ -1026,7 +1136,7 @@ func (s *BaseTestSuite) TestProcessLane() {
1000000,
)
_, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
_, err = lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler())
s.Require().Error(err)
})
@ -1052,10 +1162,12 @@ func (s *BaseTestSuite) TestProcessLane() {
},
)
maxSize := s.getTxSize(tx1)
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal)
s.Require().NoError(err)
s.Require().Len(txsFromLane, 1)
s.Require().Len(remainingTxs, 0)
maxSize := s.getTxSize(tx1)
emptyProposal := proposals.NewProposal(
log.NewNopLogger(),
s.encodingConfig.TxConfig.TxEncoder(),
@ -1063,7 +1175,7 @@ func (s *BaseTestSuite) TestProcessLane() {
9,
)
_, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
_, err = lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler())
s.Require().Error(err)
})
@ -1098,12 +1210,15 @@ func (s *BaseTestSuite) TestProcessLane() {
map[sdk.Tx]bool{
tx1: true,
tx2: true,
})
},
)
txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal)
s.Require().NoError(err)
s.Require().Len(txsFromLane, 2)
s.Require().Len(remainingTxs, 0)
maxSize := s.getTxSize(tx1) + s.getTxSize(tx2)
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
s.Require().NoError(err)
emptyProposal := proposals.NewProposal(
log.NewNopLogger(),
s.encodingConfig.TxConfig.TxEncoder(),
@ -1111,7 +1226,7 @@ func (s *BaseTestSuite) TestProcessLane() {
19,
)
_, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
_, err = lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler())
s.Require().Error(err)
})
@ -1149,10 +1264,12 @@ func (s *BaseTestSuite) TestProcessLane() {
},
)
maxSize := s.getTxSize(tx1) + s.getTxSize(tx2) - 1
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal)
s.Require().NoError(err)
s.Require().Len(txsFromLane, 2)
s.Require().Len(remainingTxs, 0)
maxSize := s.getTxSize(tx1) + s.getTxSize(tx2) - 1
emptyProposal := proposals.NewProposal(
log.NewNopLogger(),
s.encodingConfig.TxConfig.TxEncoder(),
@ -1160,7 +1277,308 @@ func (s *BaseTestSuite) TestProcessLane() {
20,
)
_, err = lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
_, err = lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler())
s.Require().Error(err)
})
s.Run("contiguous set of transactions should be accepted with other transactions that do not match", func() {
tx1, err := testutils.CreateRandomTx(
s.encodingConfig.TxConfig,
s.accounts[0],
1,
1,
0,
1,
)
s.Require().NoError(err)
tx2, err := testutils.CreateRandomTx(
s.encodingConfig.TxConfig,
s.accounts[1],
2,
1,
0,
1,
)
s.Require().NoError(err)
tx3, err := testutils.CreateRandomTx(
s.encodingConfig.TxConfig,
s.accounts[2],
3,
1,
0,
1,
)
s.Require().NoError(err)
tx4, err := testutils.CreateRandomTx(
s.encodingConfig.TxConfig,
s.accounts[3],
4,
1,
0,
1,
)
s.Require().NoError(err)
proposal := []sdk.Tx{
tx1,
tx2,
tx3,
tx4,
}
otherLane := mocks.NewLane(s.T())
otherLane.On(
"Match",
mock.Anything,
tx1,
).Return(false, nil)
otherLane.On(
"Match",
mock.Anything,
tx2,
).Return(false, nil)
otherLane.On(
"Match",
mock.Anything,
tx3,
).Return(true, nil)
otherLane.On(
"Match",
mock.Anything,
tx4,
).Return(true, nil)
lane := s.initLane(
math.LegacyOneDec(),
map[sdk.Tx]bool{
tx1: true,
tx2: true,
},
)
lane.SetIgnoreList([]block.Lane{otherLane})
txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal)
s.Require().NoError(err)
s.Require().Len(txsFromLane, 2)
s.Require().Len(remainingTxs, 2)
s.Require().Equal([]sdk.Tx{tx3, tx4}, remainingTxs)
emptyProposal := proposals.NewProposal(
log.NewNopLogger(),
s.encodingConfig.TxConfig.TxEncoder(),
1000,
1000,
)
finalProposal, err := lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler())
s.Require().NoError(err)
s.Require().Len(finalProposal.Txs, 2)
encodedTxs, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), []sdk.Tx{tx1, tx2})
s.Require().NoError(err)
s.Require().Equal(encodedTxs, finalProposal.Txs)
})
s.Run("returns no error if transactions belong to a different lane", func() {
tx1, err := testutils.CreateRandomTx(
s.encodingConfig.TxConfig,
s.accounts[0],
1,
1,
0,
1,
)
s.Require().NoError(err)
tx2, err := testutils.CreateRandomTx(
s.encodingConfig.TxConfig,
s.accounts[1],
2,
1,
0,
1,
)
s.Require().NoError(err)
tx3, err := testutils.CreateRandomTx(
s.encodingConfig.TxConfig,
s.accounts[2],
3,
1,
0,
1,
)
s.Require().NoError(err)
tx4, err := testutils.CreateRandomTx(
s.encodingConfig.TxConfig,
s.accounts[3],
4,
1,
0,
1,
)
s.Require().NoError(err)
proposal := []sdk.Tx{
tx1,
tx2,
tx3,
tx4,
}
otherLane := mocks.NewLane(s.T())
otherLane.On(
"Match",
mock.Anything,
tx1,
).Return(true, nil)
otherLane.On(
"Match",
mock.Anything,
tx2,
).Return(true, nil)
otherLane.On(
"Match",
mock.Anything,
tx3,
).Return(true, nil)
otherLane.On(
"Match",
mock.Anything,
tx4,
).Return(true, nil)
lane := s.initLane(
math.LegacyOneDec(),
map[sdk.Tx]bool{},
)
lane.SetIgnoreList([]block.Lane{otherLane})
txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal)
s.Require().NoError(err)
s.Require().Len(txsFromLane, 0)
s.Require().Len(remainingTxs, 4)
s.Require().Equal(proposal, remainingTxs)
emptyProposal := proposals.NewProposal(
log.NewNopLogger(),
s.encodingConfig.TxConfig.TxEncoder(),
1000,
1000,
)
finalProposal, err := lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler())
s.Require().NoError(err)
s.Require().Len(finalProposal.Txs, 0)
})
s.Run("returns an error if transactions are interleaved with other lanes", func() {
tx1, err := testutils.CreateRandomTx(
s.encodingConfig.TxConfig,
s.accounts[0],
1,
1,
0,
1,
)
s.Require().NoError(err)
tx2, err := testutils.CreateRandomTx(
s.encodingConfig.TxConfig,
s.accounts[1],
2,
1,
0,
1,
)
s.Require().NoError(err)
tx3, err := testutils.CreateRandomTx(
s.encodingConfig.TxConfig,
s.accounts[2],
3,
1,
0,
1,
)
s.Require().NoError(err)
tx4, err := testutils.CreateRandomTx(
s.encodingConfig.TxConfig,
s.accounts[3],
4,
1,
0,
1,
)
s.Require().NoError(err)
proposal := []sdk.Tx{
tx1,
tx2,
tx3,
tx4,
}
otherLane := mocks.NewLane(s.T())
otherLane.On(
"Match",
mock.Anything,
tx1,
).Return(false, nil)
otherLane.On(
"Match",
mock.Anything,
tx2,
).Return(true, nil)
otherLane.On(
"Match",
mock.Anything,
tx3,
).Return(false, nil).Maybe()
otherLane.On(
"Match",
mock.Anything,
tx4,
).Return(true, nil).Maybe()
lane := s.initLane(
math.LegacyOneDec(),
map[sdk.Tx]bool{
tx1: true,
tx2: true,
tx3: true,
tx4: true,
},
)
lane.SetIgnoreList([]block.Lane{otherLane})
txsFromLane, remainingTxs, err := lane.DefaultProcessLaneHandler()(s.ctx, proposal)
s.Require().Error(err)
s.Require().Len(txsFromLane, 0)
s.Require().Len(remainingTxs, 0)
emptyProposal := proposals.NewProposal(
log.NewNopLogger(),
s.encodingConfig.TxConfig.TxEncoder(),
1000,
1000,
)
_, err = lane.ProcessLane(s.ctx, emptyProposal, proposal, block.NoOpProcessLanesHandler())
s.Require().Error(err)
})
}
@ -1213,7 +1631,7 @@ func (s *BaseTestSuite) TestPrepareProcessParity() {
)
proposal, err := lane.PrepareLane(s.ctx, emptyProposal, block.NoOpPrepareLanesHandler())
s.Require().NoError(err)
s.Require().Equal(len(txsToInsert), len(proposal.Txs))
s.Require().Equal(len(retrievedTxs), len(proposal.Txs))
// Ensure that the transactions are in the same order
for i := 0; i < len(retrievedTxs); i++ {
@ -1222,6 +1640,9 @@ func (s *BaseTestSuite) TestPrepareProcessParity() {
s.Require().Equal(bz, proposal.Txs[i])
}
decodedTxs, err := utils.GetDecodedTxs(s.encodingConfig.TxConfig.TxDecoder(), proposal.Txs)
s.Require().NoError(err)
// Verify the same proposal with the process lanes handler
emptyProposal = proposals.NewProposal(
log.NewNopLogger(),
@ -1229,7 +1650,7 @@ func (s *BaseTestSuite) TestPrepareProcessParity() {
1000000000000000,
1000000000000000,
)
proposal, err = lane.ProcessLane(s.ctx, emptyProposal, proposal.Txs, block.NoOpProcessLanesHandler())
proposal, err = lane.ProcessLane(s.ctx, emptyProposal, decodedTxs, block.NoOpProcessLanesHandler())
s.Require().NoError(err)
s.Require().Equal(len(txsToInsert), len(proposal.Txs))
s.T().Logf("proposal num txs: %d", len(proposal.Txs))
@ -1282,9 +1703,6 @@ func (s *BaseTestSuite) TestIterateMempoolAndProcessProposalParity() {
s.Require().Equal(len(txsToInsert), len(retrievedTxs))
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), retrievedTxs)
s.Require().NoError(err)
emptyProposal := proposals.NewProposal(
log.NewNopLogger(),
s.encodingConfig.TxConfig.TxEncoder(),
@ -1292,9 +1710,9 @@ func (s *BaseTestSuite) TestIterateMempoolAndProcessProposalParity() {
1000000000000000,
)
proposal, err := lane.ProcessLane(s.ctx, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
proposal, err := lane.ProcessLane(s.ctx, emptyProposal, retrievedTxs, block.NoOpProcessLanesHandler())
s.Require().NoError(err)
s.Require().Equal(len(txsToInsert), len(proposal.Txs))
s.Require().Equal(len(retrievedTxs), len(proposal.Txs))
s.T().Logf("proposal num txs: %d", len(proposal.Txs))
// Ensure that the transactions are in the same order

View File

@ -77,73 +77,84 @@ func (l *MEVLane) PrepareLaneHandler() base.PrepareLaneHandler {
// ProcessLaneHandler will ensure that block proposals that include transactions from
// the mev lane are valid. In particular, the invariant checks that we perform are:
// 1. The first transaction in the partial block proposal must be a bid transaction.
// 1. If the first transaction does not match the lane, no other MEV transactions
// should be included in the proposal.
// 2. The bid transaction must be valid.
// 3. The bundled transactions must be valid.
// 4. The bundled transactions must match the transactions in the block proposal in the
// same order they were defined in the bid transaction.
// 5. The bundled transactions must not be bid transactions.
func (l *MEVLane) ProcessLaneHandler() base.ProcessLaneHandler {
return func(ctx sdk.Context, partialProposal []sdk.Tx) error {
return func(ctx sdk.Context, partialProposal []sdk.Tx) ([]sdk.Tx, []sdk.Tx, error) {
if len(partialProposal) == 0 {
return nil
return nil, nil, nil
}
// If the first transaction does not match the lane, then we return an error.
bidTx := partialProposal[0]
if !l.Match(ctx, bidTx) {
return fmt.Errorf("expected first transaction in lane %s to be a bid transaction", l.Name())
// If the transaction does not belong to this lane, we return the remaining transactions
// iff there are no matches in the remaining transactions after this index.
if len(partialProposal) > 1 {
if err := l.VerifyNoMatches(ctx, partialProposal[1:]); err != nil {
return nil, nil, fmt.Errorf("failed to verify no matches: %w", err)
}
}
return nil, partialProposal, nil
}
bidInfo, err := l.GetAuctionBidInfo(bidTx)
if err != nil {
return fmt.Errorf("failed to get bid info from auction bid tx for lane %s: %w", l.Name(), err)
return nil, nil, fmt.Errorf("failed to get bid info from auction bid tx for lane %s: %w", l.Name(), err)
}
if bidInfo == nil {
return fmt.Errorf("bid info is nil")
return nil, nil, fmt.Errorf("bid info is nil")
}
// Check that all bundled transactions were included.
if len(bidInfo.Transactions)+1 != len(partialProposal) {
return fmt.Errorf(
bundleSize := len(bidInfo.Transactions) + 1
if bundleSize > len(partialProposal) {
return nil, nil, fmt.Errorf(
"expected %d transactions in lane %s but got %d",
len(bidInfo.Transactions)+1,
bundleSize,
l.Name(),
len(partialProposal),
)
}
// Ensure the transactions in the proposal match the bundled transactions in the bid transaction.
bundle := partialProposal[1:]
bundle := partialProposal[1:bundleSize]
for index, bundledTxBz := range bidInfo.Transactions {
bundledTx, err := l.WrapBundleTransaction(bundledTxBz)
if err != nil {
return fmt.Errorf("invalid bid tx; failed to decode bundled tx: %w", err)
return nil, nil, fmt.Errorf("invalid bid tx; failed to decode bundled tx: %w", err)
}
expectedTxBz, err := l.TxEncoder()(bundledTx)
if err != nil {
return fmt.Errorf("invalid bid tx; failed to encode bundled tx: %w", err)
return nil, nil, fmt.Errorf("invalid bid tx; failed to encode bundled tx: %w", err)
}
actualTxBz, err := l.TxEncoder()(bundle[index])
if err != nil {
return fmt.Errorf("invalid bid tx; failed to encode tx: %w", err)
return nil, nil, fmt.Errorf("invalid bid tx; failed to encode tx: %w", err)
}
// Verify that the bundled transaction matches the transaction in the block proposal.
if !bytes.Equal(actualTxBz, expectedTxBz) {
return fmt.Errorf("invalid bid tx; bundled tx does not match tx in block proposal")
return nil, nil, fmt.Errorf("invalid bid tx; bundled tx does not match tx in block proposal")
}
}
// Verify the top-level bid transaction.
//
// TODO: There is duplicate work being done in VerifyBidTx and here.
if err := l.VerifyBidTx(ctx, bidTx, bundle); err != nil {
return fmt.Errorf("invalid bid tx; failed to verify bid tx: %w", err)
return nil, nil, fmt.Errorf("invalid bid tx; failed to verify bid tx: %w", err)
}
return nil
return partialProposal[:bundleSize], partialProposal[bundleSize:], nil
}
}

View File

@ -230,23 +230,31 @@ func (s *MEVTestSuite) TestProcessLane() {
lane := s.initLane(math.LegacyOneDec(), nil)
proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200, 100)
proposal, err := lane.ProcessLane(s.ctx, proposal, nil, block.NoOpProcessLanesHandler())
txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, nil)
s.Require().NoError(err)
s.Require().Equal(0, len(txsFromLane))
s.Require().Equal(0, len(remainingTxs))
proposal, err = lane.ProcessLane(s.ctx, proposal, nil, block.NoOpProcessLanesHandler())
s.Require().NoError(err)
s.Require().Equal(0, len(proposal.Txs))
s.Require().Equal(0, len(proposal.Info.TxsByLane))
s.Require().Equal(int64(0), proposal.Info.BlockSize)
s.Require().Equal(uint64(0), proposal.Info.GasLimit)
})
s.Run("can process a proposal with tx that does not belong to this lane", func() {
txBz, err := testutils.CreateRandomTxBz(s.encCfg.TxConfig, s.accounts[0], 0, 1, 0, 100)
tx, err := testutils.CreateRandomTx(s.encCfg.TxConfig, s.accounts[0], 0, 1, 0, 100)
s.Require().NoError(err)
lane := s.initLane(math.LegacyOneDec(), nil)
proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200, 100)
_, err = lane.ProcessLane(s.ctx, proposal, [][]byte{txBz}, block.NoOpProcessLanesHandler())
s.Require().Error(err)
txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, []sdk.Tx{tx})
s.Require().NoError(err)
s.Require().Equal(0, len(txsFromLane))
s.Require().Equal(1, len(remainingTxs))
finalProposal, err := lane.ProcessLane(s.ctx, proposal, []sdk.Tx{tx}, block.NoOpProcessLanesHandler())
s.Require().NoError(err)
s.Require().Equal(0, len(finalProposal.Txs))
})
s.Run("can process a proposal with bad bid tx", func() {
@ -261,12 +269,16 @@ func (s *MEVTestSuite) TestProcessLane() {
)
s.Require().NoError(err)
partialProposal, err := utils.GetEncodedTxs(s.encCfg.TxConfig.TxEncoder(), []sdk.Tx{bidTx})
s.Require().NoError(err)
partialProposal := []sdk.Tx{bidTx}
lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: false})
proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, partialProposal)
s.Require().Error(err)
s.Require().Equal(0, len(txsFromLane))
s.Require().Equal(0, len(remainingTxs))
proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
_, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler())
s.Require().Error(err)
})
@ -283,12 +295,16 @@ func (s *MEVTestSuite) TestProcessLane() {
)
s.Require().NoError(err)
partialProposal, err := utils.GetEncodedTxs(s.encCfg.TxConfig.TxEncoder(), []sdk.Tx{bidTx, bundle[0], bundle[1]})
s.Require().NoError(err)
partialProposal := []sdk.Tx{bidTx, bundle[0], bundle[1]}
lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: false})
proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, partialProposal)
s.Require().Error(err)
s.Require().Equal(0, len(txsFromLane))
s.Require().Equal(0, len(remainingTxs))
proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
_, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler())
s.Require().Error(err)
})
@ -305,12 +321,16 @@ func (s *MEVTestSuite) TestProcessLane() {
)
s.Require().NoError(err)
partialProposal, err := utils.GetEncodedTxs(s.encCfg.TxConfig.TxEncoder(), []sdk.Tx{bidTx, bundle[1], bundle[0]})
s.Require().NoError(err)
partialProposal := []sdk.Tx{bidTx, bundle[1], bundle[0]}
lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: true})
proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, partialProposal)
s.Require().Error(err)
s.Require().Equal(0, len(txsFromLane))
s.Require().Equal(0, len(remainingTxs))
proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
_, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler())
s.Require().Error(err)
})
@ -327,12 +347,16 @@ func (s *MEVTestSuite) TestProcessLane() {
)
s.Require().NoError(err)
partialProposal, err := utils.GetEncodedTxs(s.encCfg.TxConfig.TxEncoder(), []sdk.Tx{bidTx, bundle[0]})
s.Require().NoError(err)
partialProposal := []sdk.Tx{bidTx, bundle[0]}
lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true})
proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, partialProposal)
s.Require().Error(err)
s.Require().Equal(0, len(txsFromLane))
s.Require().Equal(0, len(remainingTxs))
proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
_, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler())
s.Require().Error(err)
})
@ -349,12 +373,16 @@ func (s *MEVTestSuite) TestProcessLane() {
)
s.Require().NoError(err)
partialProposal, err := utils.GetEncodedTxs(s.encCfg.TxConfig.TxEncoder(), []sdk.Tx{bidTx, bundle[0], bundle[1]})
s.Require().NoError(err)
partialProposal := []sdk.Tx{bidTx, bundle[0], bundle[1]}
lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: true})
proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, partialProposal)
s.Require().NoError(err)
s.Require().Equal(3, len(txsFromLane))
s.Require().Equal(0, len(remainingTxs))
proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
_, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler())
s.Require().NoError(err)
})
@ -371,12 +399,16 @@ func (s *MEVTestSuite) TestProcessLane() {
)
s.Require().NoError(err)
partialProposal, err := utils.GetEncodedTxs(s.encCfg.TxConfig.TxEncoder(), []sdk.Tx{bidTx})
s.Require().NoError(err)
partialProposal := []sdk.Tx{bidTx}
lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true})
proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, partialProposal)
s.Require().NoError(err)
s.Require().Equal(1, len(txsFromLane))
s.Require().Equal(0, len(remainingTxs))
proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
_, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler())
s.Require().NoError(err)
})
@ -393,12 +425,16 @@ func (s *MEVTestSuite) TestProcessLane() {
)
s.Require().NoError(err)
partialProposal, err := utils.GetEncodedTxs(s.encCfg.TxConfig.TxEncoder(), []sdk.Tx{bidTx, bundle[0], bundle[1]})
s.Require().NoError(err)
partialProposal := []sdk.Tx{bidTx, bundle[0], bundle[1]}
lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: true})
proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 20000, 99)
txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, partialProposal)
s.Require().NoError(err)
s.Require().Equal(3, len(txsFromLane))
s.Require().Equal(0, len(remainingTxs))
proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 20000, 99)
_, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler())
s.Require().Error(err)
})
@ -415,12 +451,80 @@ func (s *MEVTestSuite) TestProcessLane() {
)
s.Require().NoError(err)
partialProposal, err := utils.GetEncodedTxs(s.encCfg.TxConfig.TxEncoder(), []sdk.Tx{bidTx, bundle[0], bundle[1]})
s.Require().NoError(err)
partialProposal := []sdk.Tx{bidTx, bundle[0], bundle[1]}
lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: true})
proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200, 100)
txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, partialProposal)
s.Require().NoError(err)
s.Require().Equal(3, len(txsFromLane))
s.Require().Equal(0, len(remainingTxs))
proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200, 100)
_, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler())
s.Require().Error(err)
})
s.Run("can accept a block proposal with bid and other txs", func() {
bidTx, bundle, err := testutils.CreateAuctionTx(
s.encCfg.TxConfig,
s.accounts[0],
sdk.NewCoin("stake", math.NewInt(100)),
0,
0,
s.accounts[0:2],
100,
)
s.Require().NoError(err)
otherTx, err := testutils.CreateRandomTx(s.encCfg.TxConfig, s.accounts[0], 0, 1, 0, 100)
s.Require().NoError(err)
partialProposal := []sdk.Tx{bidTx, bundle[0], bundle[1], otherTx}
lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: true})
txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, partialProposal)
s.Require().NoError(err)
s.Require().Equal(3, len(txsFromLane))
s.Require().Equal(1, len(remainingTxs))
s.Require().Equal(otherTx, remainingTxs[0])
proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
proposal, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler())
s.Require().NoError(err)
s.Require().Len(proposal.Txs, 3)
encodedTxs, err := utils.GetEncodedTxs(s.encCfg.TxConfig.TxEncoder(), []sdk.Tx{bidTx, bundle[0], bundle[1]})
s.Require().NoError(err)
s.Require().Equal(encodedTxs, proposal.Txs)
})
s.Run("rejects a block where the bid tx is not the first tx", func() {
bidTx, bundle, err := testutils.CreateAuctionTx(
s.encCfg.TxConfig,
s.accounts[0],
sdk.NewCoin("stake", math.NewInt(100)),
0,
0,
s.accounts[0:2],
100,
)
s.Require().NoError(err)
otherTx, err := testutils.CreateRandomTx(s.encCfg.TxConfig, s.accounts[0], 0, 1, 0, 100)
s.Require().NoError(err)
partialProposal := []sdk.Tx{otherTx, bidTx, bundle[0], bundle[1]}
lane := s.initLane(math.LegacyOneDec(), map[sdk.Tx]bool{bidTx: true, bundle[0]: true, bundle[1]: true})
txsFromLane, remainingTxs, err := lane.ProcessLaneHandler()(s.ctx, partialProposal)
s.Require().Error(err)
s.Require().Equal(0, len(txsFromLane))
s.Require().Equal(0, len(remainingTxs))
proposal := proposals.NewProposal(log.NewNopLogger(), s.encCfg.TxConfig.TxEncoder(), 200000, 1000000)
_, err = lane.ProcessLane(s.ctx, proposal, partialProposal, block.NoOpProcessLanesHandler())
s.Require().Error(err)
})

View File

@ -2,6 +2,7 @@ package terminator
import (
"context"
"fmt"
"cosmossdk.io/log"
"cosmossdk.io/math"
@ -46,7 +47,11 @@ func (t Terminator) PrepareLane(_ sdk.Context, proposal proposals.Proposal, _ bl
}
// ProcessLane is a no-op
func (t Terminator) ProcessLane(_ sdk.Context, p proposals.Proposal, _ [][]byte, _ block.ProcessLanesHandler) (proposals.Proposal, error) {
func (t Terminator) ProcessLane(_ sdk.Context, p proposals.Proposal, txs []sdk.Tx, _ block.ProcessLanesHandler) (proposals.Proposal, error) {
if len(txs) > 0 {
return p, fmt.Errorf("terminator lane should not have any transactions")
}
return p, nil
}

View File

@ -64,7 +64,7 @@ var (
}
consensusParams = ictestutil.Toml{
"timeout_commit": "3500ms",
"timeout_commit": "5000ms",
}
// interchain specification

View File

@ -2,6 +2,8 @@ package integration
import (
"context"
"math/rand"
"time"
"cosmossdk.io/math"
rpctypes "github.com/cometbft/cometbft/rpc/core/types"
@ -39,6 +41,8 @@ type IntegrationTestSuite struct {
user1, user2, user3 ibc.Wallet
// denom
denom string
// fuzzusers
fuzzusers []ibc.Wallet
// overrides for key-ring configuration of the broadcaster
broadcasterOverrides *KeyringOverride
@ -86,6 +90,10 @@ func (s *IntegrationTestSuite) SetupSuite() {
s.user2 = interchaintest.GetAndFundTestUsers(s.T(), ctx, s.T().Name(), initBalance, s.chain)[0]
s.user3 = interchaintest.GetAndFundTestUsers(s.T(), ctx, s.T().Name(), initBalance, s.chain)[0]
for i := 0; i < 10; i++ {
s.fuzzusers = append(s.fuzzusers, interchaintest.GetAndFundTestUsers(s.T(), ctx, s.T().Name(), initBalance, s.chain)[0])
}
// create the broadcaster
s.T().Log("creating broadcaster")
s.setupBroadcaster()
@ -1258,3 +1266,161 @@ func (s *IntegrationTestSuite) TestLanes() {
require.Equal(s.T(), user2BalanceBefore, user2BalanceAfter+delegation.Amount.Int64())
})
}
func (s *IntegrationTestSuite) TestNetwork() {
amountToTest := time.NewTicker(time.Second * 45)
defer amountToTest.Stop()
numTxs := 10
sendAmount := sdk.NewCoins(sdk.NewCoin(s.denom, math.NewInt(100)))
delegation := sdk.NewCoin(s.denom, math.NewInt(100))
validators := QueryValidators(s.T(), s.chain.(*cosmos.CosmosChain))
s.Run("can produce blocks with only default transactions", func() {
for {
select {
case <-amountToTest.C:
return
default:
height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background())
s.NoError(err)
WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1)
s.T().Logf("height: %d", height+1)
for i := 0; i < numTxs; i++ {
for _, user := range s.fuzzusers {
fee := rand.Int63n(100000)
sequenceOffset := uint64(i)
normalTx := s.CreateDummyNormalTx(user, s.user1, sendAmount, sequenceOffset, fee)
s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{normalTx})
}
}
}
}
})
s.Run("can produce blocks with only free transactions", func() {
for {
select {
case <-amountToTest.C:
return
default:
height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background())
s.NoError(err)
WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1)
s.T().Logf("height: %d", height+1)
for i := 0; i < numTxs; i++ {
for _, user := range s.fuzzusers {
sequenceOffset := uint64(i)
freeTx := s.CreateDummyFreeTx(user, validators[0], delegation, sequenceOffset)
s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{freeTx})
}
}
}
}
})
s.Run("can produce blocks with only MEV transactions", func() {
for {
select {
case <-amountToTest.C:
return
default:
height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background())
s.NoError(err)
WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1)
s.T().Logf("height: %d", height+1)
for i := 0; i < numTxs; i++ {
for _, user := range s.fuzzusers {
bid := rand.Int63n(1000000)
bidAmount := sdk.NewCoin(s.denom, math.NewInt(bid))
mevTx := s.CreateDummyAuctionBidTx(
height+2,
user,
bidAmount,
)
s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), []Tx{mevTx})
}
}
}
}
})
amountToTest.Reset(5 * time.Minute)
s.Run("can produce blocks with all types of transactions", func() {
for {
select {
case <-amountToTest.C:
return
default:
height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background())
s.NoError(err)
WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1)
s.T().Logf("height: %d", height+1)
txs := []Tx{}
for i := 0; i < numTxs; i++ {
for _, user := range s.fuzzusers[0:3] {
bid := rand.Int63n(1000000)
bidAmount := sdk.NewCoin(s.denom, math.NewInt(bid))
bidTx := s.CreateDummyAuctionBidTx(
height+2,
user,
bidAmount,
)
txs = append(txs, bidTx)
}
}
for i := 0; i < numTxs; i++ {
for _, user := range s.fuzzusers[3:6] {
sequenceOffset := uint64(i)
freeTx := s.CreateDummyFreeTx(user, validators[0], delegation, sequenceOffset)
txs = append(txs, freeTx)
}
}
for i := 0; i < numTxs; i++ {
for _, user := range s.fuzzusers[6:10] {
fee := rand.Int63n(100000)
sequenceOffset := uint64(i)
normalTx := s.CreateDummyNormalTx(user, s.user1, sendAmount, sequenceOffset, fee)
txs = append(txs, normalTx)
}
}
s.BroadcastTxs(context.Background(), s.chain.(*cosmos.CosmosChain), txs)
}
}
})
// Wait for 1 minute for the network to stabilize
amountToTest.Reset(1 * time.Minute)
s.Run("can produce empty blocks", func() {
for {
select {
case <-amountToTest.C:
return
default:
height, err := s.chain.(*cosmos.CosmosChain).Height(context.Background())
s.NoError(err)
WaitForHeight(s.T(), s.chain.(*cosmos.CosmosChain), height+1)
s.T().Logf("height: %d", height+1)
}
}
})
}

View File

@ -24,6 +24,7 @@ import (
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
interchaintest "github.com/strangelove-ventures/interchaintest/v8"
"github.com/strangelove-ventures/interchaintest/v8/chain/cosmos"
@ -124,6 +125,71 @@ func (s *IntegrationTestSuite) CreateTx(ctx context.Context, chain *cosmos.Cosmo
return bz
}
func (s *IntegrationTestSuite) CreateDummyAuctionBidTx(
height uint64,
searcher ibc.Wallet,
bid sdk.Coin,
) Tx {
msgAuctionBid := auctiontypes.NewMsgAuctionBid(
searcher.Address(),
bid,
nil,
)
return Tx{
User: searcher,
Msgs: []sdk.Msg{msgAuctionBid},
GasPrice: 1000,
Height: height + 1,
SkipInclusionCheck: true,
IgnoreChecks: true,
}
}
func (s *IntegrationTestSuite) CreateDummyNormalTx(
from, to ibc.Wallet,
coins sdk.Coins,
sequenceOffset uint64,
gasPrice int64,
) Tx {
msgSend := banktypes.NewMsgSend(
sdk.AccAddress(from.Address()),
sdk.AccAddress(to.Address()),
coins,
)
return Tx{
User: from,
Msgs: []sdk.Msg{msgSend},
GasPrice: gasPrice,
SequenceIncrement: sequenceOffset,
SkipInclusionCheck: true,
IgnoreChecks: true,
}
}
func (s *IntegrationTestSuite) CreateDummyFreeTx(
user ibc.Wallet,
validator sdk.ValAddress,
delegation sdk.Coin,
sequenceOffset uint64,
) Tx {
delegateMsg := stakingtypes.NewMsgDelegate(
sdk.AccAddress(user.Address()).String(),
sdk.ValAddress(validator).String(),
delegation,
)
return Tx{
User: user,
Msgs: []sdk.Msg{delegateMsg},
GasPrice: 1000,
SequenceIncrement: sequenceOffset,
SkipInclusionCheck: true,
IgnoreChecks: true,
}
}
// SimulateTx simulates the provided messages, and checks whether the provided failure condition is met
func (s *IntegrationTestSuite) SimulateTx(ctx context.Context, chain *cosmos.CosmosChain, user cosmos.User, height uint64, expectFail bool, msgs ...sdk.Msg) {
// create tx factory + Client Context
@ -154,6 +220,7 @@ type Tx struct {
Height uint64
SkipInclusionCheck bool
ExpectFail bool
IgnoreChecks bool
}
// CreateAuctionBidMsg creates a new AuctionBid tx signed by the given user, the order of txs in the MsgAuctionBid will be determined by the contents + order of the MessageForUsers
@ -203,13 +270,13 @@ func (s *IntegrationTestSuite) BroadcastTxsWithCallback(
s.Require().True(len(chain.Nodes()) > 0)
client := chain.Nodes()[0].Client
statusResp, err := client.Status(context.Background())
s.Require().NoError(err)
s.T().Logf("broadcasting transactions at latest height of %d", statusResp.SyncInfo.LatestBlockHeight)
for i, tx := range rawTxs {
// broadcast tx
if txs[i].IgnoreChecks {
client.BroadcastTxAsync(ctx, tx)
continue
}
resp, err := client.BroadcastTxSync(ctx, tx)
// check execution was successful
@ -228,7 +295,7 @@ func (s *IntegrationTestSuite) BroadcastTxsWithCallback(
eg := errgroup.Group{}
for i, tx := range rawTxs {
// if we don't expect this tx to be included.. skip it
if txs[i].SkipInclusionCheck || txs[i].ExpectFail {
if txs[i].SkipInclusionCheck || txs[i].ExpectFail || txs[i].IgnoreChecks {
continue
}
@ -356,25 +423,11 @@ func WaitForHeight(t *testing.T, chain *cosmos.CosmosChain, height uint64) {
require.NoError(t, err)
}
// VerifyBlock takes a Block and verifies that it contains the given bid at the 0-th index, and the bundled txs immediately after
func VerifyBlock(t *testing.T, block *rpctypes.ResultBlock, offset int, bidTxHash string, txs [][]byte) {
// verify the block
if bidTxHash != "" {
require.Equal(t, bidTxHash, TxHash(block.Block.Data.Txs[offset+1]))
offset += 1
}
// verify the txs in sequence
for i, tx := range txs {
require.Equal(t, TxHash(tx), TxHash(block.Block.Data.Txs[i+offset+1]))
}
}
// VerifyBlockWithExpectedBlock takes in a list of raw tx bytes and compares each tx hash to the tx hashes in the block.
// The expected block is the block that should be returned by the chain at the given height.
func VerifyBlockWithExpectedBlock(t *testing.T, chain *cosmos.CosmosChain, height uint64, txs [][]byte) {
block := Block(t, chain, int64(height))
blockTxs := block.Block.Data.Txs[1:]
blockTxs := block.Block.Data.Txs
t.Logf("verifying block %d", height)
require.Equal(t, len(txs), len(blockTxs))