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:
parent
f607439637
commit
f7dfbda2b1
@ -7,6 +7,7 @@
|
||||
"MD033": false,
|
||||
"MD034": false,
|
||||
"MD014": false,
|
||||
"MD013": false,
|
||||
"no-hard-tabs": false,
|
||||
"whitespace": false
|
||||
}
|
||||
|
||||
49
abci/abci.go
49
abci/abci.go
@ -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
108
abci/utils.go
108
abci/utils.go
@ -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:]))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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{
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ type Lane interface {
|
||||
ProcessLane(
|
||||
ctx sdk.Context,
|
||||
proposal proposals.Proposal,
|
||||
partialProposal [][]byte,
|
||||
txs []sdk.Tx,
|
||||
next ProcessLanesHandler,
|
||||
) (proposals.Proposal, error)
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
})
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -64,7 +64,7 @@ var (
|
||||
}
|
||||
|
||||
consensusParams = ictestutil.Toml{
|
||||
"timeout_commit": "3500ms",
|
||||
"timeout_commit": "5000ms",
|
||||
}
|
||||
|
||||
// interchain specification
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -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))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user