* updating mev lane with cleaner impl * nit * lint * updating anteverifytx to verify tx * nit * ignoring first height * tidy
179 lines
6.5 KiB
Go
179 lines
6.5 KiB
Go
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(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 := h.mempool.Registry()
|
|
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
|
|
// fail to prepare the partial proposal, the lane that failed will be skipped and the next
|
|
// lane in the chain will be called to prepare the proposal.
|
|
func ChainPrepareLanes(chain []block.Lane) block.PrepareLanesHandler {
|
|
if len(chain) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Handle non-terminated decorators chain
|
|
if (chain[len(chain)-1] != terminator.Terminator{}) {
|
|
chain = append(chain, terminator.Terminator{})
|
|
}
|
|
|
|
return func(ctx sdk.Context, partialProposal proposals.Proposal) (finalProposal proposals.Proposal, err error) {
|
|
lane := chain[0]
|
|
|
|
// Cache the context in the case where any of the lanes fail to prepare the proposal.
|
|
cacheCtx, write := ctx.CacheContext()
|
|
|
|
// We utilize a recover to handle any panics or errors that occur during the preparation
|
|
// of a lane's transactions. This defer will first check if there was a panic or error
|
|
// thrown from the lane's preparation logic. If there was, we log the error, skip the lane,
|
|
// and call the next lane in the chain to the prepare the proposal.
|
|
defer func() {
|
|
if rec := recover(); rec != nil || err != nil {
|
|
if len(chain) <= 2 {
|
|
// If there are only two lanes remaining, then the first lane in the chain
|
|
// is the lane that failed to prepare the partial proposal and the second lane in the
|
|
// chain is the terminator lane. We return the proposal as is.
|
|
finalProposal, err = partialProposal, nil
|
|
} else {
|
|
// If there are more than two lanes remaining, then the first lane in the chain
|
|
// is the lane that failed to prepare the proposal but the second lane in the
|
|
// chain is not the terminator lane so there could potentially be more transactions
|
|
// added to the proposal
|
|
finalProposal, err = ChainPrepareLanes(chain[1:])(ctx, partialProposal)
|
|
}
|
|
} else {
|
|
// Write the cache to the context since we know that the lane successfully prepared
|
|
// the partial proposal. State is written to in a backwards, cascading fashion. This means
|
|
// that the final context will only be updated after all other lanes have successfully
|
|
// prepared the partial proposal.
|
|
write()
|
|
}
|
|
}()
|
|
|
|
return lane.PrepareLane(
|
|
cacheCtx,
|
|
partialProposal,
|
|
ChainPrepareLanes(chain[1:]),
|
|
)
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
if len(chain) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// 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) {
|
|
lane := chain[0]
|
|
partialProposal := partialProposals[0]
|
|
return lane.ProcessLane(ctx, proposal, partialProposal, ChainProcessLanes(partialProposals[1:], chain[1:]))
|
|
}
|
|
}
|