feat(ABCI): New Proposal Struct with Associated Metadata (#126)
* new proto types for proposal info * new proposal type * nits * lane input * lint * feat(ABCI): Deprecating `CheckOrderHandler` with new Proposal MetaData (#127) * refactor without checkorder * nits * more nits * lint * nits * feat(ABCI): Updating MEV lane to have no `CheckOrder` handler + testing (#128) * updating mev lane * nits * preventing adding multiple bid txs in prepare * update
This commit is contained in:
parent
3abfde4f34
commit
b9d6761776
4
Makefile
4
Makefile
@ -97,6 +97,10 @@ use-integration:
|
||||
go work edit -dropuse .
|
||||
go work edit -use ./tests/integration
|
||||
|
||||
tidy:
|
||||
go mod tidy
|
||||
gofmt -s -w ./
|
||||
|
||||
.PHONY: docker-build docker-build-integration
|
||||
###############################################################################
|
||||
## Docker ##
|
||||
|
||||
271
abci/abci.go
271
abci/abci.go
@ -8,8 +8,13 @@ import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/skip-mev/block-sdk/block"
|
||||
"github.com/skip-mev/block-sdk/block/utils"
|
||||
"github.com/skip-mev/block-sdk/lanes/terminator"
|
||||
"github.com/skip-mev/block-sdk/block/proposals"
|
||||
"github.com/skip-mev/block-sdk/block/proposals/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// ProposalInfoIndex is the index of the proposal metadata in the proposal.
|
||||
ProposalInfoIndex = 0
|
||||
)
|
||||
|
||||
type (
|
||||
@ -18,66 +23,92 @@ type (
|
||||
ProposalHandler struct {
|
||||
logger log.Logger
|
||||
txDecoder sdk.TxDecoder
|
||||
txEncoder sdk.TxEncoder
|
||||
prepareLanesHandler block.PrepareLanesHandler
|
||||
processLanesHandler block.ProcessLanesHandler
|
||||
mempool block.Mempool
|
||||
}
|
||||
)
|
||||
|
||||
// NewProposalHandler returns a new abci++ proposal handler. This proposal handler will
|
||||
// NewProposalHandler returns a new ABCI++ proposal handler. This proposal handler will
|
||||
// iteratively call each of the lanes in the chain to prepare and process the proposal.
|
||||
func NewProposalHandler(logger log.Logger, txDecoder sdk.TxDecoder, mempool block.Mempool) *ProposalHandler {
|
||||
func NewProposalHandler(
|
||||
logger log.Logger,
|
||||
txDecoder sdk.TxDecoder,
|
||||
txEncoder sdk.TxEncoder,
|
||||
mempool block.Mempool,
|
||||
) *ProposalHandler {
|
||||
return &ProposalHandler{
|
||||
logger: logger,
|
||||
txDecoder: txDecoder,
|
||||
prepareLanesHandler: ChainPrepareLanes(mempool.Registry()...),
|
||||
processLanesHandler: ChainProcessLanes(mempool.Registry()...),
|
||||
txEncoder: txEncoder,
|
||||
prepareLanesHandler: ChainPrepareLanes(mempool.Registry()),
|
||||
mempool: mempool,
|
||||
}
|
||||
}
|
||||
|
||||
// PrepareProposalHandler prepares the proposal by selecting transactions from each lane
|
||||
// according to each lane's selection logic. We select transactions in a greedy fashion. Note that
|
||||
// each lane has an boundary on the number of bytes that can be included in the proposal. By default,
|
||||
// the default lane will not have a boundary on the number of bytes that can be included in the proposal and
|
||||
// will include all valid transactions in the proposal (up to MaxTxBytes).
|
||||
// according to each lane's selection logic. We select transactions in the order in which the
|
||||
// lanes are configured on the chain. Note that each lane has an boundary on the number of
|
||||
// bytes/gas that can be included in the proposal. By default, the default lane will not have
|
||||
// a boundary on the number of bytes that can be included in the proposal and will include all
|
||||
// valid transactions in the proposal (up to MaxBlockSize, MaxGasLimit).
|
||||
func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler {
|
||||
return func(ctx sdk.Context, req *abci.RequestPrepareProposal) (resp *abci.ResponsePrepareProposal, err error) {
|
||||
// In the case where there is a panic, we recover here and return an empty proposal.
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
if rec := recover(); rec != nil {
|
||||
h.logger.Error("failed to prepare proposal", "err", err)
|
||||
|
||||
// TODO: Should we attempt to return a empty proposal here with empty proposal info?
|
||||
resp = &abci.ResponsePrepareProposal{Txs: make([][]byte, 0)}
|
||||
err = fmt.Errorf("failed to prepare proposal: %v", rec)
|
||||
}
|
||||
}()
|
||||
|
||||
h.logger.Info("mempool distribution before proposal creation", "distribution", h.mempool.GetTxDistribution())
|
||||
|
||||
proposal, err := h.prepareLanesHandler(ctx, block.NewProposal(req.MaxTxBytes))
|
||||
// Build an empty placeholder proposal with the maximum block size and gas limit.
|
||||
maxBlockSize, maxGasLimit := proposals.GetBlockLimits(ctx)
|
||||
emptyProposal := proposals.NewProposal(h.txEncoder, maxBlockSize, maxGasLimit)
|
||||
|
||||
// Fill the proposal with transactions from each lane.
|
||||
finalProposal, err := h.prepareLanesHandler(ctx, emptyProposal)
|
||||
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", proposal.GetNumTxs(),
|
||||
"total_tx_bytes", proposal.GetTotalTxBytes(),
|
||||
"num_txs", len(txs),
|
||||
"total_tx_bytes", finalProposal.Info.BlockSize,
|
||||
"max_tx_bytes", maxBlockSize,
|
||||
"total_gas_limit", finalProposal.Info.GasLimit,
|
||||
"max_gas_limit", maxGasLimit,
|
||||
"height", req.Height,
|
||||
)
|
||||
|
||||
h.logger.Info("mempool distribution after proposal creation", "distribution", h.mempool.GetTxDistribution())
|
||||
|
||||
return &abci.ResponsePrepareProposal{
|
||||
Txs: proposal.GetProposal(),
|
||||
Txs: txs,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessProposalHandler processes the proposal by verifying all transactions in the proposal
|
||||
// according to each lane's verification logic. We verify proposals in a greedy fashion.
|
||||
// If a lane's portion of the proposal is invalid, we reject the proposal. After a lane's portion
|
||||
// of the proposal is verified, we pass the remaining transactions to the next lane in the chain.
|
||||
// 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.
|
||||
func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler {
|
||||
return func(ctx sdk.Context, req *abci.RequestProcessProposal) (resp *abci.ResponseProcessProposal, err error) {
|
||||
// In the case where any of the lanes panic, we recover here and return a reject status.
|
||||
@ -90,126 +121,132 @@ func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler {
|
||||
}
|
||||
}()
|
||||
|
||||
txs := req.Txs
|
||||
if len(txs) == 0 {
|
||||
h.logger.Info("accepted empty proposal")
|
||||
return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil
|
||||
}
|
||||
|
||||
// Decode the transactions from the proposal.
|
||||
decodedTxs, err := utils.GetDecodedTxs(h.txDecoder, txs)
|
||||
// Extract all of the lanes and their corresponding transactions from the proposal.
|
||||
proposalInfo, partialProposals, err := h.ExtractLanes(req.Txs)
|
||||
if err != nil {
|
||||
h.logger.Error("failed to decode transactions", "err", err)
|
||||
h.logger.Error("failed to validate proposal", "err", err)
|
||||
return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, err
|
||||
}
|
||||
|
||||
// Verify the proposal using the verification logic from each lane.
|
||||
if _, err := h.processLanesHandler(ctx, decodedTxs); err != nil {
|
||||
// Build handler that will verify the partial proposals according to each lane's verification logic.
|
||||
processLanesHandler := ChainProcessLanes(partialProposals, h.mempool.Registry())
|
||||
|
||||
// Build an empty placeholder proposal.
|
||||
maxBlockSize, maxGasLimit := proposals.GetBlockLimits(ctx)
|
||||
emptyProposal := proposals.NewProposal(h.txEncoder, maxBlockSize, maxGasLimit)
|
||||
|
||||
// Verify the proposal according to the verification logic from each lane.
|
||||
finalProposal, err := processLanesHandler(ctx, emptyProposal)
|
||||
if err != nil {
|
||||
h.logger.Error("failed to validate the proposal", "err", err)
|
||||
return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, err
|
||||
}
|
||||
|
||||
h.logger.Info("validated proposal", "num_txs", len(txs))
|
||||
// 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),
|
||||
"total_tx_bytes", finalProposal.Info.BlockSize,
|
||||
"max_tx_bytes", maxBlockSize,
|
||||
"total_gas_limit", finalProposal.Info.GasLimit,
|
||||
"max_gas_limit", maxGasLimit,
|
||||
"height", req.Height,
|
||||
)
|
||||
|
||||
return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, 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
|
||||
// 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")
|
||||
}
|
||||
|
||||
// Handle non-terminated decorators chain
|
||||
if (chain[len(chain)-1] != terminator.Terminator{}) {
|
||||
chain = append(chain, terminator.Terminator{})
|
||||
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)
|
||||
}
|
||||
|
||||
return func(ctx sdk.Context, partialProposal block.BlockProposal) (finalProposal block.BlockProposal, err error) {
|
||||
lane := chain[0]
|
||||
lane.Logger().Info("preparing lane", "lane", lane.Name())
|
||||
lanes := h.mempool.Registry()
|
||||
partialProposals := make([][][]byte, len(lanes))
|
||||
|
||||
// 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 {
|
||||
lane.Logger().Error("failed to prepare lane", "lane", lane.Name(), "err", err, "recover_error", rec)
|
||||
lane.Logger().Info("skipping lane", "lane", lane.Name())
|
||||
|
||||
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()
|
||||
}
|
||||
}()
|
||||
|
||||
// Get the maximum number of bytes that can be included in the proposal for this lane.
|
||||
maxTxBytesForLane := utils.GetMaxTxBytesForLane(
|
||||
partialProposal.GetMaxTxBytes(),
|
||||
partialProposal.GetTotalTxBytes(),
|
||||
lane.GetMaxBlockSpace(),
|
||||
)
|
||||
|
||||
return lane.PrepareLane(
|
||||
cacheCtx,
|
||||
partialProposal,
|
||||
maxTxBytesForLane,
|
||||
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.
|
||||
func ChainProcessLanes(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{})
|
||||
}
|
||||
|
||||
return func(ctx sdk.Context, proposalTxs []sdk.Tx) (sdk.Context, error) {
|
||||
// Short circuit if there are no transactions to process.
|
||||
if len(proposalTxs) == 0 {
|
||||
return ctx, nil
|
||||
if metaData.TxsByLane == nil {
|
||||
if len(txs) > 0 {
|
||||
return types.ProposalInfo{}, nil, fmt.Errorf("proposal contains invalid number of transactions")
|
||||
}
|
||||
|
||||
chain[0].Logger().Info("processing lane", "lane", chain[0].Name())
|
||||
return types.ProposalInfo{}, partialProposals, nil
|
||||
}
|
||||
|
||||
if err := chain[0].CheckOrder(ctx, proposalTxs); err != nil {
|
||||
chain[0].Logger().Error("failed to process lane", "lane", chain[0].Name(), "err", err)
|
||||
return ctx, err
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
return chain[0].ProcessLane(ctx, proposalTxs, ChainProcessLanes(chain[1:]...))
|
||||
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
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
93
abci/utils.go
Normal file
93
abci/utils.go
Normal file
@ -0,0 +1,93 @@
|
||||
package abci
|
||||
|
||||
import (
|
||||
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/lanes/terminator"
|
||||
)
|
||||
|
||||
// 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]
|
||||
lane.Logger().Info("preparing lane", "lane", lane.Name())
|
||||
|
||||
// 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 {
|
||||
lane.Logger().Error("failed to prepare lane", "lane", lane.Name(), "err", err, "recover_error", rec)
|
||||
lane.Logger().Info("skipping lane", "lane", lane.Name())
|
||||
|
||||
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]
|
||||
|
||||
lane.Logger().Info("processing lane", "lane", chain[0].Name())
|
||||
|
||||
return lane.ProcessLane(ctx, proposal, partialProposal, ChainProcessLanes(partialProposals[1:], chain[1:]))
|
||||
}
|
||||
}
|
||||
212
abci/utils_test.go
Normal file
212
abci/utils_test.go
Normal file
@ -0,0 +1,212 @@
|
||||
package abci_test
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"cosmossdk.io/log"
|
||||
"cosmossdk.io/math"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
tmprototypes "github.com/cometbft/cometbft/proto/tendermint/types"
|
||||
"github.com/skip-mev/block-sdk/abci"
|
||||
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"
|
||||
)
|
||||
|
||||
func (s *ProposalsTestSuite) setUpAnteHandler(expectedExecution map[sdk.Tx]bool) sdk.AnteHandler {
|
||||
txCache := make(map[string]bool)
|
||||
for tx, pass := range expectedExecution {
|
||||
bz, err := s.encodingConfig.TxConfig.TxEncoder()(tx)
|
||||
s.Require().NoError(err)
|
||||
|
||||
hash := sha256.Sum256(bz)
|
||||
hashStr := hex.EncodeToString(hash[:])
|
||||
txCache[hashStr] = pass
|
||||
}
|
||||
|
||||
anteHandler := func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) {
|
||||
bz, err := s.encodingConfig.TxConfig.TxEncoder()(tx)
|
||||
s.Require().NoError(err)
|
||||
|
||||
hash := sha256.Sum256(bz)
|
||||
hashStr := hex.EncodeToString(hash[:])
|
||||
|
||||
pass, found := txCache[hashStr]
|
||||
if !found {
|
||||
return ctx, fmt.Errorf("tx not found")
|
||||
}
|
||||
|
||||
if pass {
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
return ctx, fmt.Errorf("tx failed")
|
||||
}
|
||||
|
||||
return anteHandler
|
||||
}
|
||||
|
||||
func (s *ProposalsTestSuite) setUpStandardLane(maxBlockSpace math.LegacyDec, expectedExecution map[sdk.Tx]bool) *defaultlane.DefaultLane {
|
||||
cfg := base.LaneConfig{
|
||||
Logger: log.NewTestLogger(s.T()),
|
||||
TxEncoder: s.encodingConfig.TxConfig.TxEncoder(),
|
||||
TxDecoder: s.encodingConfig.TxConfig.TxDecoder(),
|
||||
AnteHandler: s.setUpAnteHandler(expectedExecution),
|
||||
MaxBlockSpace: maxBlockSpace,
|
||||
SignerExtractor: signeradaptors.NewDefaultAdapter(),
|
||||
}
|
||||
|
||||
return defaultlane.NewDefaultLane(cfg)
|
||||
}
|
||||
|
||||
func (s *ProposalsTestSuite) setUpTOBLane(maxBlockSpace math.LegacyDec, expectedExecution map[sdk.Tx]bool) *mev.MEVLane {
|
||||
cfg := base.LaneConfig{
|
||||
Logger: log.NewTestLogger(s.T()),
|
||||
TxEncoder: s.encodingConfig.TxConfig.TxEncoder(),
|
||||
TxDecoder: s.encodingConfig.TxConfig.TxDecoder(),
|
||||
AnteHandler: s.setUpAnteHandler(expectedExecution),
|
||||
MaxBlockSpace: maxBlockSpace,
|
||||
SignerExtractor: signeradaptors.NewDefaultAdapter(),
|
||||
}
|
||||
|
||||
return mev.NewMEVLane(cfg, mev.NewDefaultAuctionFactory(cfg.TxDecoder, signeradaptors.NewDefaultAdapter()))
|
||||
}
|
||||
|
||||
func (s *ProposalsTestSuite) setUpFreeLane(maxBlockSpace math.LegacyDec, expectedExecution map[sdk.Tx]bool) *free.FreeLane {
|
||||
cfg := base.LaneConfig{
|
||||
Logger: log.NewTestLogger(s.T()),
|
||||
TxEncoder: s.encodingConfig.TxConfig.TxEncoder(),
|
||||
TxDecoder: s.encodingConfig.TxConfig.TxDecoder(),
|
||||
AnteHandler: s.setUpAnteHandler(expectedExecution),
|
||||
MaxBlockSpace: maxBlockSpace,
|
||||
SignerExtractor: signeradaptors.NewDefaultAdapter(),
|
||||
}
|
||||
|
||||
return free.NewFreeLane(cfg, base.DefaultTxPriority(), free.DefaultMatchHandler())
|
||||
}
|
||||
|
||||
func (s *ProposalsTestSuite) setUpPanicLane(maxBlockSpace math.LegacyDec) *base.BaseLane {
|
||||
cfg := base.LaneConfig{
|
||||
Logger: log.NewTestLogger(s.T()),
|
||||
TxEncoder: s.encodingConfig.TxConfig.TxEncoder(),
|
||||
TxDecoder: s.encodingConfig.TxConfig.TxDecoder(),
|
||||
MaxBlockSpace: maxBlockSpace,
|
||||
SignerExtractor: signeradaptors.NewDefaultAdapter(),
|
||||
}
|
||||
|
||||
lane := base.NewBaseLane(
|
||||
cfg,
|
||||
"panic",
|
||||
base.NewMempool[string](base.DefaultTxPriority(), cfg.TxEncoder, cfg.SignerExtractor, 0),
|
||||
base.DefaultMatchHandler(),
|
||||
)
|
||||
|
||||
lane.SetPrepareLaneHandler(base.PanicPrepareLaneHandler())
|
||||
lane.SetProcessLaneHandler(base.PanicProcessLaneHandler())
|
||||
|
||||
return lane
|
||||
}
|
||||
|
||||
func (s *ProposalsTestSuite) setUpProposalHandlers(lanes []block.Lane) *abci.ProposalHandler {
|
||||
mempool := block.NewLanedMempool(log.NewTestLogger(s.T()), true, lanes...)
|
||||
|
||||
return abci.NewProposalHandler(
|
||||
log.NewTestLogger(s.T()),
|
||||
s.encodingConfig.TxConfig.TxDecoder(),
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
mempool,
|
||||
)
|
||||
}
|
||||
|
||||
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) getTxBytes(txs ...sdk.Tx) [][]byte {
|
||||
txBytes := make([][]byte, len(txs))
|
||||
for i, tx := range txs {
|
||||
bz, err := s.encodingConfig.TxConfig.TxEncoder()(tx)
|
||||
s.Require().NoError(err)
|
||||
|
||||
txBytes[i] = bz
|
||||
}
|
||||
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{
|
||||
Block: &tmprototypes.BlockParams{
|
||||
MaxBytes: maxBlockSize,
|
||||
MaxGas: maxGasLimit,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -1,16 +1,10 @@
|
||||
package utils
|
||||
package block
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
type (
|
||||
// Lane defines the required API dependencies for the IgnoreDecorator. The ignore decorator
|
||||
// will check if a transaction belongs to a lane by calling the Match function.
|
||||
Lane interface {
|
||||
Match(ctx sdk.Context, tx sdk.Tx) bool
|
||||
}
|
||||
|
||||
// IgnoreDecorator is an AnteDecorator that wraps an existing AnteDecorator. It allows
|
||||
// for the AnteDecorator to be ignored for specified lanes.
|
||||
IgnoreDecorator struct {
|
||||
@ -4,20 +4,24 @@ import (
|
||||
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/utils"
|
||||
)
|
||||
|
||||
// PrepareLane will prepare a partial proposal for the lane. It will select transactions from the
|
||||
// lane respecting the selection logic of the prepareLaneHandler. It will then update the partial
|
||||
// proposal with the selected transactions. If the proposal is unable to be updated, we return an
|
||||
// error. The proposal will only be modified if it passes all of the invarient checks.
|
||||
// error. The proposal will only be modified if it passes all of the invariant checks.
|
||||
func (l *BaseLane) PrepareLane(
|
||||
ctx sdk.Context,
|
||||
proposal block.BlockProposal,
|
||||
maxTxBytes int64,
|
||||
proposal proposals.Proposal,
|
||||
next block.PrepareLanesHandler,
|
||||
) (block.BlockProposal, error) {
|
||||
txs, txsToRemove, err := l.prepareLaneHandler(ctx, proposal, maxTxBytes)
|
||||
) (proposals.Proposal, error) {
|
||||
limit := proposal.GetLaneLimits(l.cfg.MaxBlockSpace)
|
||||
|
||||
// Select transactions from the lane respecting the selection logic of the lane and the
|
||||
// max block space for the lane.
|
||||
txsToInclude, txsToRemove, err := l.prepareLaneHandler(ctx, proposal, limit)
|
||||
if err != nil {
|
||||
return proposal, err
|
||||
}
|
||||
@ -31,31 +35,75 @@ func (l *BaseLane) PrepareLane(
|
||||
)
|
||||
}
|
||||
|
||||
// Update the proposal with the selected transactions.
|
||||
if err := proposal.UpdateProposal(l, txs); err != nil {
|
||||
// Update the proposal with the selected transactions. This fails if the lane attempted to add
|
||||
// more transactions than the allocated max block space for the lane.
|
||||
if err := proposal.UpdateProposal(l, txsToInclude); err != nil {
|
||||
l.Logger().Error(
|
||||
"failed to update proposal",
|
||||
"lane", l.Name(),
|
||||
"err", err,
|
||||
"num_txs_to_add", len(txsToInclude),
|
||||
"num_txs_to_remove", len(txsToRemove),
|
||||
)
|
||||
|
||||
return proposal, err
|
||||
}
|
||||
|
||||
l.Logger().Info(
|
||||
"lane prepared",
|
||||
"lane", l.Name(),
|
||||
"num_txs_added", len(txsToInclude),
|
||||
"num_txs_removed", len(txsToRemove),
|
||||
)
|
||||
|
||||
return next(ctx, proposal)
|
||||
}
|
||||
|
||||
// CheckOrder checks that the ordering logic of the lane is respected given the set of transactions
|
||||
// in the block proposal. If the ordering logic is not respected, we return an error.
|
||||
func (l *BaseLane) CheckOrder(ctx sdk.Context, txs []sdk.Tx) error {
|
||||
return l.checkOrderHandler(ctx, txs)
|
||||
}
|
||||
|
||||
// ProcessLane verifies that the transactions included in the block proposal are valid respecting
|
||||
// the verification logic of the lane (processLaneHandler). If the transactions are valid, we
|
||||
// return the transactions that do not belong to this lane to the next lane. If the transactions
|
||||
// are invalid, we return an error.
|
||||
func (l *BaseLane) ProcessLane(ctx sdk.Context, txs []sdk.Tx, next block.ProcessLanesHandler) (sdk.Context, error) {
|
||||
remainingTxs, err := l.processLaneHandler(ctx, txs)
|
||||
// the verification logic of the lane (processLaneHandler). If any of the transactions are invalid,
|
||||
// we return an error. If all of the transactions are valid, we return the updated proposal.
|
||||
func (l *BaseLane) ProcessLane(
|
||||
ctx sdk.Context,
|
||||
proposal proposals.Proposal,
|
||||
txs [][]byte,
|
||||
next block.ProcessLanesHandler,
|
||||
) (proposals.Proposal, error) {
|
||||
// Assume that this lane is processing sdk.Tx's and decode the transactions.
|
||||
decodedTxs, err := utils.GetDecodedTxs(l.TxDecoder(), txs)
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
l.Logger().Error(
|
||||
"failed to decode transactions",
|
||||
"lane", l.Name(),
|
||||
"err", err,
|
||||
)
|
||||
|
||||
return proposal, err
|
||||
}
|
||||
|
||||
return next(ctx, remainingTxs)
|
||||
// Verify the transactions that belong to this lane according to the verification logic of the lane.
|
||||
if err := l.processLaneHandler(ctx, decodedTxs); err != nil {
|
||||
return proposal, err
|
||||
}
|
||||
|
||||
// Optimistically update the proposal with the partial proposal.
|
||||
if err := proposal.UpdateProposal(l, decodedTxs); err != nil {
|
||||
l.Logger().Error(
|
||||
"failed to update proposal",
|
||||
"lane", l.Name(),
|
||||
"err", err,
|
||||
"num_txs_to_verify", len(decodedTxs),
|
||||
)
|
||||
|
||||
return proposal, err
|
||||
}
|
||||
|
||||
l.Logger().Info(
|
||||
"lane processed",
|
||||
"lane", l.Name(),
|
||||
"num_txs_verified", len(decodedTxs),
|
||||
)
|
||||
|
||||
return next(ctx, proposal)
|
||||
}
|
||||
|
||||
// AnteVerifyTx verifies that the transaction is valid respecting the ante verification logic of
|
||||
|
||||
@ -5,20 +5,21 @@ import (
|
||||
|
||||
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/utils"
|
||||
)
|
||||
|
||||
// DefaultPrepareLaneHandler returns a default implementation of the PrepareLaneHandler. It
|
||||
// selects all transactions in the mempool that are valid and not already in the partial
|
||||
// proposal. It will continue to reap transactions until the maximum block space for this
|
||||
// proposal. It will continue to reap transactions until the maximum blockspace/gas for this
|
||||
// lane has been reached. Additionally, any transactions that are invalid will be returned.
|
||||
func (l *BaseLane) DefaultPrepareLaneHandler() PrepareLaneHandler {
|
||||
return func(ctx sdk.Context, proposal block.BlockProposal, maxTxBytes int64) ([][]byte, []sdk.Tx, error) {
|
||||
return func(ctx sdk.Context, proposal proposals.Proposal, limit proposals.LaneLimits) ([]sdk.Tx, []sdk.Tx, error) {
|
||||
var (
|
||||
totalSize int64
|
||||
txs [][]byte
|
||||
txsToRemove []sdk.Tx
|
||||
totalSize int64
|
||||
totalGas uint64
|
||||
txsToInclude []sdk.Tx
|
||||
txsToRemove []sdk.Tx
|
||||
)
|
||||
|
||||
// Select all transactions in the mempool that are valid and not already in the
|
||||
@ -26,7 +27,7 @@ func (l *BaseLane) DefaultPrepareLaneHandler() PrepareLaneHandler {
|
||||
for iterator := l.Select(ctx, nil); iterator != nil; iterator = iterator.Next() {
|
||||
tx := iterator.Tx()
|
||||
|
||||
txBytes, hash, err := utils.GetTxHashStr(l.TxEncoder(), tx)
|
||||
txInfo, err := utils.GetTxInfo(l.TxEncoder(), tx)
|
||||
if err != nil {
|
||||
l.Logger().Info("failed to get hash of tx", "err", err)
|
||||
|
||||
@ -38,7 +39,7 @@ func (l *BaseLane) DefaultPrepareLaneHandler() PrepareLaneHandler {
|
||||
if !l.Match(ctx, tx) {
|
||||
l.Logger().Info(
|
||||
"failed to select tx for lane; tx does not belong to lane",
|
||||
"tx_hash", hash,
|
||||
"tx_hash", txInfo.Hash,
|
||||
"lane", l.Name(),
|
||||
)
|
||||
|
||||
@ -47,10 +48,10 @@ func (l *BaseLane) DefaultPrepareLaneHandler() PrepareLaneHandler {
|
||||
}
|
||||
|
||||
// if the transaction is already in the (partial) block proposal, we skip it.
|
||||
if proposal.Contains(txBytes) {
|
||||
if proposal.Contains(txInfo.Hash) {
|
||||
l.Logger().Info(
|
||||
"failed to select tx for lane; tx is already in proposal",
|
||||
"tx_hash", hash,
|
||||
"tx_hash", txInfo.Hash,
|
||||
"lane", l.Name(),
|
||||
)
|
||||
|
||||
@ -58,25 +59,40 @@ func (l *BaseLane) DefaultPrepareLaneHandler() PrepareLaneHandler {
|
||||
}
|
||||
|
||||
// If the transaction is too large, we break and do not attempt to include more txs.
|
||||
txSize := int64(len(txBytes))
|
||||
if updatedSize := totalSize + txSize; updatedSize > maxTxBytes {
|
||||
if updatedSize := totalSize + txInfo.Size; updatedSize > limit.MaxTxBytes {
|
||||
l.Logger().Info(
|
||||
"tx bytes above the maximum allowed",
|
||||
"failed to select tx for lane; tx bytes above the maximum allowed",
|
||||
"lane", l.Name(),
|
||||
"tx_size", txSize,
|
||||
"tx_size", txInfo.Size,
|
||||
"total_size", totalSize,
|
||||
"max_tx_bytes", maxTxBytes,
|
||||
"tx_hash", hash,
|
||||
"max_tx_bytes", limit.MaxTxBytes,
|
||||
"tx_hash", txInfo.Hash,
|
||||
)
|
||||
|
||||
break
|
||||
// TODO: Determine if there is any trade off with breaking or continuing here.
|
||||
continue
|
||||
}
|
||||
|
||||
// If the gas limit of the transaction is too large, we break and do not attempt to include more txs.
|
||||
if updatedGas := totalGas + txInfo.GasLimit; updatedGas > limit.MaxGasLimit {
|
||||
l.Logger().Info(
|
||||
"failed to select tx for lane; gas limit above the maximum allowed",
|
||||
"lane", l.Name(),
|
||||
"tx_gas", txInfo.GasLimit,
|
||||
"total_gas", totalGas,
|
||||
"max_gas", limit.MaxGasLimit,
|
||||
"tx_hash", txInfo.Hash,
|
||||
)
|
||||
|
||||
// TODO: Determine if there is any trade off with breaking or continuing here.
|
||||
continue
|
||||
}
|
||||
|
||||
// Verify the transaction.
|
||||
if ctx, err = l.AnteVerifyTx(ctx, tx, false); err != nil {
|
||||
l.Logger().Info(
|
||||
"failed to verify tx",
|
||||
"tx_hash", hash,
|
||||
"tx_hash", txInfo.Hash,
|
||||
"err", err,
|
||||
)
|
||||
|
||||
@ -84,66 +100,40 @@ func (l *BaseLane) DefaultPrepareLaneHandler() PrepareLaneHandler {
|
||||
continue
|
||||
}
|
||||
|
||||
totalSize += txSize
|
||||
txs = append(txs, txBytes)
|
||||
totalSize += txInfo.Size
|
||||
totalGas += txInfo.GasLimit
|
||||
txsToInclude = append(txsToInclude, tx)
|
||||
}
|
||||
|
||||
return txs, txsToRemove, nil
|
||||
return txsToInclude, txsToRemove, nil
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultProcessLaneHandler returns a default implementation of the ProcessLaneHandler. It
|
||||
// verifies all transactions in the lane that matches to the lane. If any transaction
|
||||
// fails to verify, the entire proposal is rejected. If the handler comes across a transaction
|
||||
// that does not match the lane's matcher, it will return the remaining transactions in the
|
||||
// proposal.
|
||||
// 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.
|
||||
func (l *BaseLane) DefaultProcessLaneHandler() ProcessLaneHandler {
|
||||
return func(ctx sdk.Context, txs []sdk.Tx) ([]sdk.Tx, error) {
|
||||
var err error
|
||||
|
||||
return func(ctx sdk.Context, partialProposal []sdk.Tx) error {
|
||||
// Process all transactions that match the lane's matcher.
|
||||
for index, tx := range txs {
|
||||
if l.Match(ctx, tx) {
|
||||
if ctx, err = l.AnteVerifyTx(ctx, tx, false); err != nil {
|
||||
return nil, fmt.Errorf("failed to verify tx: %w", err)
|
||||
}
|
||||
} else {
|
||||
return txs[index:], nil
|
||||
}
|
||||
}
|
||||
|
||||
// This means we have processed all transactions in the proposal.
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultCheckOrderHandler returns a default implementation of the CheckOrderHandler. It
|
||||
// ensures the following invariants:
|
||||
//
|
||||
// 1. All transactions that belong to this lane respect the ordering logic defined by the
|
||||
// lane.
|
||||
// 2. Transactions that belong to other lanes cannot be interleaved with transactions that
|
||||
// belong to this lane.
|
||||
func (l *BaseLane) DefaultCheckOrderHandler() CheckOrderHandler {
|
||||
return func(ctx sdk.Context, txs []sdk.Tx) error {
|
||||
seenOtherLaneTx := false
|
||||
|
||||
for index, tx := range txs {
|
||||
if l.Match(ctx, tx) {
|
||||
if seenOtherLaneTx {
|
||||
return fmt.Errorf("the %s lane contains a transaction that belongs to another lane", l.Name())
|
||||
}
|
||||
|
||||
// If the transactions do not respect the priority defined by the mempool, we consider the proposal
|
||||
// to be invalid
|
||||
if index > 0 && l.Compare(ctx, txs[index-1], tx) == -1 {
|
||||
return fmt.Errorf("transaction at index %d has a higher priority than %d", index, index-1)
|
||||
}
|
||||
} else {
|
||||
seenOtherLaneTx = true
|
||||
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 transactions do not respect the priority defined by the mempool, we consider the proposal
|
||||
// to be invalid
|
||||
if index > 0 && l.Compare(ctx, partialProposal[index-1], tx) == -1 {
|
||||
return fmt.Errorf("transaction at index %d has a higher priority than %d", index, index-1)
|
||||
}
|
||||
|
||||
if _, err := l.AnteVerifyTx(ctx, tx, false); err != nil {
|
||||
return fmt.Errorf("failed to verify tx: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// This means we have processed all transactions in the partial proposal.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,11 +38,6 @@ type BaseLane struct { //nolint
|
||||
// requested and the lane needs to submit transactions it wants included in the block.
|
||||
prepareLaneHandler PrepareLaneHandler
|
||||
|
||||
// checkOrderHandler is the function that is called when a new proposal is being
|
||||
// verified and the lane needs to verify that the transactions included in the proposal
|
||||
// respect the ordering rules of the lane and does not interleave transactions from other lanes.
|
||||
checkOrderHandler CheckOrderHandler
|
||||
|
||||
// processLaneHandler is the function that is called when a new proposal is being
|
||||
// verified and the lane needs to verify that the transactions included in the proposal
|
||||
// are valid respecting the verification logic of the lane.
|
||||
@ -95,10 +90,6 @@ func (l *BaseLane) ValidateBasic() error {
|
||||
l.processLaneHandler = l.DefaultProcessLaneHandler()
|
||||
}
|
||||
|
||||
if l.checkOrderHandler == nil {
|
||||
l.checkOrderHandler = l.DefaultCheckOrderHandler()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -125,18 +116,6 @@ func (l *BaseLane) SetProcessLaneHandler(processLaneHandler ProcessLaneHandler)
|
||||
l.processLaneHandler = processLaneHandler
|
||||
}
|
||||
|
||||
// SetCheckOrderHandler sets the check order handler for the lane. This handler
|
||||
// is called when a new proposal is being verified and the lane needs to verify
|
||||
// that the transactions included in the proposal respect the ordering rules of
|
||||
// the lane and does not include transactions from other lanes.
|
||||
func (l *BaseLane) SetCheckOrderHandler(checkOrderHandler CheckOrderHandler) {
|
||||
if checkOrderHandler == nil {
|
||||
panic("check order handler cannot be nil")
|
||||
}
|
||||
|
||||
l.checkOrderHandler = checkOrderHandler
|
||||
}
|
||||
|
||||
// Match returns true if the transaction should be processed by this lane. This
|
||||
// function first determines if the transaction matches the lane and then checks
|
||||
// if the transaction is on the ignore list. If the transaction is on the ignore
|
||||
|
||||
@ -103,13 +103,13 @@ func (cm *Mempool[C]) Insert(ctx context.Context, tx sdk.Tx) error {
|
||||
return fmt.Errorf("failed to insert tx into auction index: %w", err)
|
||||
}
|
||||
|
||||
_, txHashStr, err := utils.GetTxHashStr(cm.txEncoder, tx)
|
||||
txInfo, err := utils.GetTxInfo(cm.txEncoder, tx)
|
||||
if err != nil {
|
||||
cm.Remove(tx)
|
||||
return err
|
||||
}
|
||||
|
||||
cm.txCache[txHashStr] = struct{}{}
|
||||
cm.txCache[txInfo.Hash] = struct{}{}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -120,12 +120,12 @@ func (cm *Mempool[C]) Remove(tx sdk.Tx) error {
|
||||
return fmt.Errorf("failed to remove transaction from the mempool: %w", err)
|
||||
}
|
||||
|
||||
_, txHashStr, err := utils.GetTxHashStr(cm.txEncoder, tx)
|
||||
txInfo, err := utils.GetTxInfo(cm.txEncoder, tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get tx hash string: %w", err)
|
||||
}
|
||||
|
||||
delete(cm.txCache, txHashStr)
|
||||
delete(cm.txCache, txInfo.Hash)
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -145,12 +145,12 @@ func (cm *Mempool[C]) CountTx() int {
|
||||
|
||||
// Contains returns true if the transaction is contained in the mempool.
|
||||
func (cm *Mempool[C]) Contains(tx sdk.Tx) bool {
|
||||
_, txHashStr, err := utils.GetTxHashStr(cm.txEncoder, tx)
|
||||
txInfo, err := utils.GetTxInfo(cm.txEncoder, tx)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, ok := cm.txCache[txHashStr]
|
||||
_, ok := cm.txCache[txInfo.Hash]
|
||||
return ok
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ package base
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/skip-mev/block-sdk/block"
|
||||
"github.com/skip-mev/block-sdk/block/proposals"
|
||||
)
|
||||
|
||||
type (
|
||||
@ -16,28 +16,20 @@ type (
|
||||
// the transactions that must be removed from the lane, and an error if one occurred.
|
||||
PrepareLaneHandler func(
|
||||
ctx sdk.Context,
|
||||
proposal block.BlockProposal,
|
||||
maxTxBytes int64,
|
||||
) (txsToInclude [][]byte, txsToRemove []sdk.Tx, err error)
|
||||
proposal proposals.Proposal,
|
||||
limit proposals.LaneLimits,
|
||||
) (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. ProcessLaneHandler is executed after CheckOrderHandler so the transactions
|
||||
// passed into this function SHOULD already be in order respecting the ordering rules of the lane and
|
||||
// respecting the ordering rules of mempool relative to the lanes it has.
|
||||
ProcessLaneHandler func(ctx sdk.Context, txs []sdk.Tx) ([]sdk.Tx, error)
|
||||
|
||||
// CheckOrderHandler is responsible for checking the order of transactions that belong to a given
|
||||
// lane. This handler should be used to verify that the ordering of transactions passed into the
|
||||
// function respect the ordering logic of the lane (if any transactions from the lane are included).
|
||||
// This function should also ensure that transactions that belong to this lane are contiguous and do
|
||||
// not have any transactions from other lanes in between them.
|
||||
CheckOrderHandler func(ctx sdk.Context, txs []sdk.Tx) error
|
||||
// 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
|
||||
)
|
||||
|
||||
// NoOpPrepareLaneHandler returns a no-op prepare lane handler.
|
||||
// This should only be used for testing.
|
||||
func NoOpPrepareLaneHandler() PrepareLaneHandler {
|
||||
return func(ctx sdk.Context, proposal block.BlockProposal, maxTxBytes int64) (txsToInclude [][]byte, txsToRemove []sdk.Tx, err error) {
|
||||
return func(sdk.Context, proposals.Proposal, proposals.LaneLimits) ([]sdk.Tx, []sdk.Tx, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
}
|
||||
@ -45,7 +37,7 @@ func NoOpPrepareLaneHandler() PrepareLaneHandler {
|
||||
// PanicPrepareLaneHandler returns a prepare lane handler that panics.
|
||||
// This should only be used for testing.
|
||||
func PanicPrepareLaneHandler() PrepareLaneHandler {
|
||||
return func(sdk.Context, block.BlockProposal, int64) (txsToInclude [][]byte, txsToRemove []sdk.Tx, err error) {
|
||||
return func(sdk.Context, proposals.Proposal, proposals.LaneLimits) ([]sdk.Tx, []sdk.Tx, error) {
|
||||
panic("panic prepare lanes handler")
|
||||
}
|
||||
}
|
||||
@ -53,15 +45,15 @@ func PanicPrepareLaneHandler() PrepareLaneHandler {
|
||||
// NoOpProcessLaneHandler returns a no-op process lane handler.
|
||||
// This should only be used for testing.
|
||||
func NoOpProcessLaneHandler() ProcessLaneHandler {
|
||||
return func(ctx sdk.Context, txs []sdk.Tx) ([]sdk.Tx, error) {
|
||||
return txs, nil
|
||||
return func(sdk.Context, []sdk.Tx) error {
|
||||
return 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) ([]sdk.Tx, error) {
|
||||
return func(sdk.Context, []sdk.Tx) error {
|
||||
panic("panic process lanes handler")
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,13 +5,14 @@ import (
|
||||
"cosmossdk.io/math"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool"
|
||||
"github.com/skip-mev/block-sdk/block/proposals"
|
||||
)
|
||||
|
||||
// LaneMempool defines the interface a lane's mempool should implement. The basic API
|
||||
// is the same as the sdk.Mempool, but it also includes a Compare function that is used
|
||||
// to determine the relative priority of two transactions belonging in the same lane.
|
||||
//
|
||||
//go:generate mockery --name LaneMempool --output ./utils/mocks --outpkg mocks --case underscore
|
||||
//go:generate mockery --name LaneMempool --output ./mocks --outpkg mocks --case underscore
|
||||
type LaneMempool interface {
|
||||
sdkmempool.Mempool
|
||||
|
||||
@ -27,29 +28,31 @@ type LaneMempool interface {
|
||||
// Lane defines an interface used for matching transactions to lanes, storing transactions,
|
||||
// and constructing partial blocks.
|
||||
//
|
||||
//go:generate mockery --name Lane --output ./utils/mocks --outpkg mocks --case underscore
|
||||
//go:generate mockery --name Lane --output ./mocks --outpkg mocks --case underscore
|
||||
type Lane interface {
|
||||
LaneMempool
|
||||
|
||||
// PrepareLane builds a portion of the block. It inputs the maxTxBytes that can be
|
||||
// included in the proposal for the given lane, the partial proposal, and a function
|
||||
// to call the next lane in the chain. The next lane in the chain will be called with
|
||||
// the updated proposal and context.
|
||||
// PrepareLane builds a portion of the block. It inputs the current context, proposal, and a
|
||||
// function to call the next lane in the chain. This handler should update the context as needed
|
||||
// and add transactions to the proposal. Note, the lane should only add transactions up to the
|
||||
// max block space for the lane.
|
||||
PrepareLane(
|
||||
ctx sdk.Context,
|
||||
proposal BlockProposal,
|
||||
maxTxBytes int64,
|
||||
proposal proposals.Proposal,
|
||||
next PrepareLanesHandler,
|
||||
) (BlockProposal, error)
|
||||
) (proposals.Proposal, error)
|
||||
|
||||
// CheckOrder validates that transactions belonging to this lane are not misplaced
|
||||
// in the block proposal and respect the ordering rules of the lane.
|
||||
CheckOrder(ctx sdk.Context, txs []sdk.Tx) error
|
||||
|
||||
// ProcessLane verifies this lane's portion of a proposed block. It inputs the transactions
|
||||
// that may belong to this lane and a function to call the next lane in the chain. The next
|
||||
// lane in the chain will be called with the updated context and filtered down transactions.
|
||||
ProcessLane(ctx sdk.Context, proposalTxs []sdk.Tx, next ProcessLanesHandler) (sdk.Context, error)
|
||||
// ProcessLane verifies this lane's portion of a proposed block. It inputs the current context,
|
||||
// proposal, transactions that belong to this lane, and a function to call the next lane in the
|
||||
// chain. This handler should update the context as needed and add transactions to the proposal.
|
||||
// The entire process lane chain should end up constructing the same proposal as the prepare lane
|
||||
// chain.
|
||||
ProcessLane(
|
||||
ctx sdk.Context,
|
||||
proposal proposals.Proposal,
|
||||
partialProposal [][]byte,
|
||||
next ProcessLanesHandler,
|
||||
) (proposals.Proposal, error)
|
||||
|
||||
// GetMaxBlockSpace returns the max block space for the lane as a relative percentage.
|
||||
GetMaxBlockSpace() math.LegacyDec
|
||||
|
||||
@ -15,6 +15,8 @@ import (
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
proposals "github.com/skip-mev/block-sdk/block/proposals"
|
||||
|
||||
types "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
@ -23,20 +25,6 @@ type Lane struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// CheckOrder provides a mock function with given fields: ctx, txs
|
||||
func (_m *Lane) CheckOrder(ctx types.Context, txs []types.Tx) error {
|
||||
ret := _m.Called(ctx, txs)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(types.Context, []types.Tx) error); ok {
|
||||
r0 = rf(ctx, txs)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Compare provides a mock function with given fields: ctx, this, other
|
||||
func (_m *Lane) Compare(ctx types.Context, this types.Tx, other types.Tx) int {
|
||||
ret := _m.Called(ctx, this, other)
|
||||
@ -151,25 +139,23 @@ func (_m *Lane) Name() string {
|
||||
return r0
|
||||
}
|
||||
|
||||
// PrepareLane provides a mock function with given fields: ctx, proposal, maxTxBytes, next
|
||||
func (_m *Lane) PrepareLane(ctx types.Context, proposal block.BlockProposal, maxTxBytes int64, next block.PrepareLanesHandler) (block.BlockProposal, error) {
|
||||
ret := _m.Called(ctx, proposal, maxTxBytes, next)
|
||||
// PrepareLane provides a mock function with given fields: ctx, proposal, next
|
||||
func (_m *Lane) PrepareLane(ctx types.Context, proposal proposals.Proposal, next block.PrepareLanesHandler) (proposals.Proposal, error) {
|
||||
ret := _m.Called(ctx, proposal, next)
|
||||
|
||||
var r0 block.BlockProposal
|
||||
var r0 proposals.Proposal
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(types.Context, block.BlockProposal, int64, block.PrepareLanesHandler) (block.BlockProposal, error)); ok {
|
||||
return rf(ctx, proposal, maxTxBytes, next)
|
||||
if rf, ok := ret.Get(0).(func(types.Context, proposals.Proposal, block.PrepareLanesHandler) (proposals.Proposal, error)); ok {
|
||||
return rf(ctx, proposal, next)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(types.Context, block.BlockProposal, int64, block.PrepareLanesHandler) block.BlockProposal); ok {
|
||||
r0 = rf(ctx, proposal, maxTxBytes, next)
|
||||
if rf, ok := ret.Get(0).(func(types.Context, proposals.Proposal, block.PrepareLanesHandler) proposals.Proposal); ok {
|
||||
r0 = rf(ctx, proposal, next)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(block.BlockProposal)
|
||||
}
|
||||
r0 = ret.Get(0).(proposals.Proposal)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(types.Context, block.BlockProposal, int64, block.PrepareLanesHandler) error); ok {
|
||||
r1 = rf(ctx, proposal, maxTxBytes, next)
|
||||
if rf, ok := ret.Get(1).(func(types.Context, proposals.Proposal, block.PrepareLanesHandler) error); ok {
|
||||
r1 = rf(ctx, proposal, next)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
@ -177,23 +163,23 @@ func (_m *Lane) PrepareLane(ctx types.Context, proposal block.BlockProposal, max
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// ProcessLane provides a mock function with given fields: ctx, proposalTxs, next
|
||||
func (_m *Lane) ProcessLane(ctx types.Context, proposalTxs []types.Tx, next block.ProcessLanesHandler) (types.Context, error) {
|
||||
ret := _m.Called(ctx, proposalTxs, next)
|
||||
// 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)
|
||||
|
||||
var r0 types.Context
|
||||
var r0 proposals.Proposal
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(types.Context, []types.Tx, block.ProcessLanesHandler) (types.Context, error)); ok {
|
||||
return rf(ctx, proposalTxs, next)
|
||||
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, []types.Tx, block.ProcessLanesHandler) types.Context); ok {
|
||||
r0 = rf(ctx, proposalTxs, next)
|
||||
if rf, ok := ret.Get(0).(func(types.Context, proposals.Proposal, [][]byte, block.ProcessLanesHandler) proposals.Proposal); ok {
|
||||
r0 = rf(ctx, proposal, partialProposal, next)
|
||||
} else {
|
||||
r0 = ret.Get(0).(types.Context)
|
||||
r0 = ret.Get(0).(proposals.Proposal)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(types.Context, []types.Tx, block.ProcessLanesHandler) error); ok {
|
||||
r1 = rf(ctx, proposalTxs, next)
|
||||
if rf, ok := ret.Get(1).(func(types.Context, proposals.Proposal, [][]byte, block.ProcessLanesHandler) error); ok {
|
||||
r1 = rf(ctx, proposal, partialProposal, next)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
@ -246,8 +232,7 @@ func (_m *Lane) SetIgnoreList(ignoreList []block.Lane) {
|
||||
func NewLane(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
},
|
||||
) *Lane {
|
||||
}) *Lane {
|
||||
mock := &Lane{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
@ -107,8 +107,7 @@ func (_m *LaneMempool) Select(_a0 context.Context, _a1 [][]byte) mempool.Iterato
|
||||
func NewLaneMempool(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
},
|
||||
) *LaneMempool {
|
||||
}) *LaneMempool {
|
||||
mock := &LaneMempool{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
@ -1,205 +0,0 @@
|
||||
package block
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"cosmossdk.io/log"
|
||||
"cosmossdk.io/math"
|
||||
|
||||
"github.com/skip-mev/block-sdk/block/utils"
|
||||
)
|
||||
|
||||
var _ BlockProposal = (*Proposal)(nil)
|
||||
|
||||
type (
|
||||
// LaneProposal defines the interface/APIs that are required for the proposal to interact
|
||||
// with a lane.
|
||||
LaneProposal interface {
|
||||
// Logger returns the lane's logger.
|
||||
Logger() log.Logger
|
||||
|
||||
// GetMaxBlockSpace returns the maximum block space for the lane as a relative percentage.
|
||||
GetMaxBlockSpace() math.LegacyDec
|
||||
|
||||
// Name returns the name of the lane.
|
||||
Name() string
|
||||
}
|
||||
|
||||
// BlockProposal is the interface/APIs that are required for proposal creation + interacting with
|
||||
// and updating proposals. BlockProposals are iteratively updated as each lane prepares its
|
||||
// partial proposal. Each lane must call UpdateProposal with its partial proposal in PrepareLane. BlockProposals
|
||||
// can also include vote extensions, which are included at the top of the proposal.
|
||||
BlockProposal interface { //nolint
|
||||
// UpdateProposal updates the proposal with the given transactions. There are a
|
||||
// few invarients that are checked:
|
||||
// 1. The total size of the proposal must be less than the maximum number of bytes allowed.
|
||||
// 2. The total size of the partial proposal must be less than the maximum number of bytes allowed for
|
||||
// the lane.
|
||||
UpdateProposal(lane LaneProposal, partialProposalTxs [][]byte) error
|
||||
|
||||
// GetMaxTxBytes returns the maximum number of bytes that can be included in the proposal.
|
||||
GetMaxTxBytes() int64
|
||||
|
||||
// GetTotalTxBytes returns the total number of bytes currently included in the proposal.
|
||||
GetTotalTxBytes() int64
|
||||
|
||||
// GetTxs returns the transactions in the proposal.
|
||||
GetTxs() [][]byte
|
||||
|
||||
// GetNumTxs returns the number of transactions in the proposal.
|
||||
GetNumTxs() int
|
||||
|
||||
// Contains returns true if the proposal contains the given transaction.
|
||||
Contains(tx []byte) bool
|
||||
|
||||
// AddVoteExtension adds a vote extension to the proposal.
|
||||
AddVoteExtension(voteExtension []byte)
|
||||
|
||||
// GetVoteExtensions returns the vote extensions in the proposal.
|
||||
GetVoteExtensions() [][]byte
|
||||
|
||||
// GetProposal returns all of the transactions in the proposal along with the vote extensions
|
||||
// at the top of the proposal.
|
||||
GetProposal() [][]byte
|
||||
}
|
||||
|
||||
// Proposal defines a block proposal type.
|
||||
Proposal struct {
|
||||
// txs is the list of transactions in the proposal.
|
||||
txs [][]byte
|
||||
|
||||
// voteExtensions is the list of vote extensions in the proposal.
|
||||
voteExtensions [][]byte
|
||||
|
||||
// cache is a cache of the selected transactions in the proposal.
|
||||
cache map[string]struct{}
|
||||
|
||||
// totalTxBytes is the total number of bytes currently included in the proposal.
|
||||
totalTxBytes int64
|
||||
|
||||
// maxTxBytes is the maximum number of bytes that can be included in the proposal.
|
||||
maxTxBytes int64
|
||||
}
|
||||
)
|
||||
|
||||
// NewProposal returns a new empty proposal.
|
||||
func NewProposal(maxTxBytes int64) *Proposal {
|
||||
return &Proposal{
|
||||
txs: make([][]byte, 0),
|
||||
voteExtensions: make([][]byte, 0),
|
||||
cache: make(map[string]struct{}),
|
||||
maxTxBytes: maxTxBytes,
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateProposal updates the proposal with the given transactions and total size. There are a
|
||||
// few invarients that are checked:
|
||||
// 1. The total size of the proposal must be less than the maximum number of bytes allowed.
|
||||
// 2. The total size of the partial proposal must be less than the maximum number of bytes allowed for
|
||||
// the lane.
|
||||
func (p *Proposal) UpdateProposal(lane LaneProposal, partialProposalTxs [][]byte) error {
|
||||
if len(partialProposalTxs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
partialProposalSize := int64(0)
|
||||
for _, tx := range partialProposalTxs {
|
||||
partialProposalSize += int64(len(tx))
|
||||
}
|
||||
|
||||
// Invarient check: Ensure that the lane did not prepare a partial proposal that is too large.
|
||||
maxTxBytesForLane := utils.GetMaxTxBytesForLane(p.GetMaxTxBytes(), p.GetTotalTxBytes(), lane.GetMaxBlockSpace())
|
||||
if partialProposalSize > maxTxBytesForLane {
|
||||
return fmt.Errorf(
|
||||
"%s lane prepared a partial proposal that is too large: %d > %d",
|
||||
lane.Name(),
|
||||
partialProposalSize,
|
||||
maxTxBytesForLane,
|
||||
)
|
||||
}
|
||||
|
||||
// Invarient check: Ensure that the lane did not prepare a block proposal that is too large.
|
||||
updatedSize := p.totalTxBytes + partialProposalSize
|
||||
if updatedSize > p.maxTxBytes {
|
||||
return fmt.Errorf(
|
||||
"lane %s prepared a block proposal that is too large: %d > %d",
|
||||
lane.Name(),
|
||||
p.totalTxBytes,
|
||||
p.maxTxBytes,
|
||||
)
|
||||
}
|
||||
p.totalTxBytes = updatedSize
|
||||
|
||||
p.txs = append(p.txs, partialProposalTxs...)
|
||||
|
||||
for _, tx := range partialProposalTxs {
|
||||
txHash := sha256.Sum256(tx)
|
||||
txHashStr := hex.EncodeToString(txHash[:])
|
||||
|
||||
p.cache[txHashStr] = struct{}{}
|
||||
|
||||
lane.Logger().Info(
|
||||
"adding transaction to proposal",
|
||||
"lane", lane.Name(),
|
||||
"tx_hash", txHashStr,
|
||||
"tx_bytes", len(tx),
|
||||
)
|
||||
}
|
||||
|
||||
lane.Logger().Info(
|
||||
"lane successfully updated proposal",
|
||||
"lane", lane.Name(),
|
||||
"num_txs", len(partialProposalTxs),
|
||||
"partial_proposal_size", partialProposalSize,
|
||||
"cumulative_proposal_size", updatedSize,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetProposal returns all of the transactions in the proposal along with the vote extensions
|
||||
// at the top of the proposal.
|
||||
func (p *Proposal) GetProposal() [][]byte {
|
||||
return append(p.voteExtensions, p.txs...)
|
||||
}
|
||||
|
||||
// AddVoteExtension adds a vote extension to the proposal.
|
||||
func (p *Proposal) AddVoteExtension(voteExtension []byte) {
|
||||
p.voteExtensions = append(p.voteExtensions, voteExtension)
|
||||
}
|
||||
|
||||
// GetVoteExtensions returns the vote extensions in the proposal.
|
||||
func (p *Proposal) GetVoteExtensions() [][]byte {
|
||||
return p.voteExtensions
|
||||
}
|
||||
|
||||
// GetMaxTxBytes returns the maximum number of bytes that can be included in the proposal.
|
||||
func (p *Proposal) GetMaxTxBytes() int64 {
|
||||
return p.maxTxBytes
|
||||
}
|
||||
|
||||
// GetTotalTxBytes returns the total number of bytes currently included in the proposal.
|
||||
func (p *Proposal) GetTotalTxBytes() int64 {
|
||||
return p.totalTxBytes
|
||||
}
|
||||
|
||||
// GetTxs returns the transactions in the proposal.
|
||||
func (p *Proposal) GetTxs() [][]byte {
|
||||
return p.txs
|
||||
}
|
||||
|
||||
// GetNumTxs returns the number of transactions in the proposal.
|
||||
func (p *Proposal) GetNumTxs() int {
|
||||
return len(p.txs)
|
||||
}
|
||||
|
||||
// Contains returns true if the proposal contains the given transaction.
|
||||
func (p *Proposal) Contains(tx []byte) bool {
|
||||
txHash := sha256.Sum256(tx)
|
||||
txHashStr := hex.EncodeToString(txHash[:])
|
||||
|
||||
_, ok := p.cache[txHashStr]
|
||||
return ok
|
||||
}
|
||||
94
block/proposals/proposals.go
Normal file
94
block/proposals/proposals.go
Normal file
@ -0,0 +1,94 @@
|
||||
package proposals
|
||||
|
||||
import (
|
||||
"cosmossdk.io/math"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/skip-mev/block-sdk/block/proposals/types"
|
||||
)
|
||||
|
||||
type (
|
||||
// Proposal defines a block proposal type.
|
||||
Proposal struct {
|
||||
// Txs is the list of transactions in the proposal.
|
||||
Txs [][]byte
|
||||
// Cache is a cache of the selected transactions in the proposal.
|
||||
Cache map[string]struct{}
|
||||
// TxEncoder is the transaction encoder.
|
||||
TxEncoder sdk.TxEncoder
|
||||
// Info contains information about the state of the proposal.
|
||||
Info types.ProposalInfo
|
||||
}
|
||||
)
|
||||
|
||||
// NewProposal returns a new empty proposal. Any transactions added to the proposal
|
||||
// will be subject to the given max block size and max gas limit.
|
||||
func NewProposal(txEncoder sdk.TxEncoder, maxBlockSize int64, maxGasLimit uint64) Proposal {
|
||||
return Proposal{
|
||||
TxEncoder: txEncoder,
|
||||
Txs: make([][]byte, 0),
|
||||
Cache: make(map[string]struct{}),
|
||||
Info: types.ProposalInfo{
|
||||
TxsByLane: make(map[string]uint64),
|
||||
MaxBlockSize: maxBlockSize,
|
||||
MaxGasLimit: maxGasLimit,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetProposalWithInfo returns all of the transactions in the proposal along with information
|
||||
// about the lanes that built the proposal.
|
||||
func (p *Proposal) GetProposalWithInfo() ([][]byte, error) {
|
||||
// Marshall the proposal info into the first slot of the proposal.
|
||||
infoBz, err := p.Info.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proposal := [][]byte{infoBz}
|
||||
proposal = append(proposal, p.Txs...)
|
||||
|
||||
return proposal, nil
|
||||
}
|
||||
|
||||
// GetLaneLimits returns the maximum number of bytes and gas limit that can be
|
||||
// included/consumed in the proposal for the given block space ratio. Lane's
|
||||
// must first call this function to determine the maximum number of bytes and
|
||||
// gas limit they can include in the proposal before constructing a partial
|
||||
// proposal.
|
||||
func (p *Proposal) GetLaneLimits(ratio math.LegacyDec) LaneLimits {
|
||||
var (
|
||||
txBytes int64
|
||||
gasLimit uint64
|
||||
)
|
||||
|
||||
// In the case where the ratio is zero, we return the max tx bytes remaining.
|
||||
// Note, the only lane that should have a ratio of zero is the default lane.
|
||||
if ratio.IsZero() {
|
||||
txBytes = p.Info.MaxBlockSize - p.Info.BlockSize
|
||||
if txBytes < 0 {
|
||||
txBytes = 0
|
||||
}
|
||||
|
||||
// Unsigned subtraction needs an additional check
|
||||
if p.Info.GasLimit >= p.Info.MaxGasLimit {
|
||||
gasLimit = 0
|
||||
} else {
|
||||
gasLimit = p.Info.MaxGasLimit - p.Info.GasLimit
|
||||
}
|
||||
} else {
|
||||
// Otherwise, we calculate the max tx bytes / gas limit for the lane based on the ratio.
|
||||
txBytes = ratio.MulInt64(p.Info.MaxBlockSize).TruncateInt().Int64()
|
||||
gasLimit = ratio.MulInt(math.NewIntFromUint64(p.Info.MaxGasLimit)).TruncateInt().Uint64()
|
||||
}
|
||||
|
||||
return LaneLimits{
|
||||
MaxTxBytes: txBytes,
|
||||
MaxGasLimit: gasLimit,
|
||||
}
|
||||
}
|
||||
|
||||
// Contains returns true if the proposal contains the given transaction.
|
||||
func (p *Proposal) Contains(txHash string) bool {
|
||||
_, ok := p.Cache[txHash]
|
||||
return ok
|
||||
}
|
||||
540
block/proposals/proposals_test.go
Normal file
540
block/proposals/proposals_test.go
Normal file
@ -0,0 +1,540 @@
|
||||
package proposals_test
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/skip-mev/block-sdk/block/mocks"
|
||||
"github.com/skip-mev/block-sdk/block/proposals"
|
||||
"github.com/skip-mev/block-sdk/block/proposals/types"
|
||||
"github.com/skip-mev/block-sdk/block/utils"
|
||||
"github.com/skip-mev/block-sdk/testutils"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUpdateProposal(t *testing.T) {
|
||||
encodingConfig := testutils.CreateTestEncodingConfig()
|
||||
|
||||
// Create a few random accounts
|
||||
random := rand.New(rand.NewSource(1))
|
||||
accounts := testutils.RandomAccounts(random, 5)
|
||||
|
||||
lane := mocks.NewLane(t)
|
||||
|
||||
lane.On("Name").Return("test").Maybe()
|
||||
lane.On("GetMaxBlockSpace").Return(math.LegacyNewDec(1)).Maybe()
|
||||
|
||||
t.Run("can update with no transactions", func(t *testing.T) {
|
||||
proposal := proposals.NewProposal(nil, 100, 100)
|
||||
|
||||
err := proposal.UpdateProposal(lane, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Ensure that the proposal is empty.
|
||||
require.Equal(t, 0, len(proposal.Txs))
|
||||
require.Equal(t, int64(0), proposal.Info.BlockSize)
|
||||
require.Equal(t, uint64(0), proposal.Info.GasLimit)
|
||||
require.Equal(t, 0, len(proposal.Info.TxsByLane))
|
||||
|
||||
block, err := proposal.GetProposalWithInfo()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(block))
|
||||
})
|
||||
|
||||
t.Run("can update with a single transaction", func(t *testing.T) {
|
||||
tx, err := testutils.CreateRandomTx(
|
||||
encodingConfig.TxConfig,
|
||||
accounts[0],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
100,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
txBzs, err := utils.GetEncodedTxs(encodingConfig.TxConfig.TxEncoder(), []sdk.Tx{tx})
|
||||
require.NoError(t, err)
|
||||
|
||||
size := len(txBzs[0])
|
||||
gasLimit := 100
|
||||
proposal := proposals.NewProposal(encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit))
|
||||
|
||||
err = proposal.UpdateProposal(lane, []sdk.Tx{tx})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Ensure that the proposal is not empty.
|
||||
require.Equal(t, 1, len(proposal.Txs))
|
||||
require.Equal(t, int64(size), proposal.Info.BlockSize)
|
||||
require.Equal(t, uint64(gasLimit), proposal.Info.GasLimit)
|
||||
require.Equal(t, 1, len(proposal.Info.TxsByLane))
|
||||
require.Equal(t, uint64(1), proposal.Info.TxsByLane["test"])
|
||||
|
||||
// Ensure that the proposal can be marshalled.
|
||||
block, err := proposal.GetProposalWithInfo()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(block))
|
||||
require.Equal(t, txBzs[0], block[1])
|
||||
})
|
||||
|
||||
t.Run("can update with multiple transactions", func(t *testing.T) {
|
||||
txs := make([]sdk.Tx, 0)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
tx, err := testutils.CreateRandomTx(
|
||||
encodingConfig.TxConfig,
|
||||
accounts[0],
|
||||
0,
|
||||
uint64(i),
|
||||
0,
|
||||
100,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
txs = append(txs, tx)
|
||||
}
|
||||
|
||||
txBzs, err := utils.GetEncodedTxs(encodingConfig.TxConfig.TxEncoder(), txs)
|
||||
require.NoError(t, err)
|
||||
|
||||
size := 0
|
||||
gasLimit := uint64(0)
|
||||
for _, txBz := range txBzs {
|
||||
size += len(txBz)
|
||||
gasLimit += 100
|
||||
}
|
||||
|
||||
proposal := proposals.NewProposal(encodingConfig.TxConfig.TxEncoder(), int64(size), gasLimit)
|
||||
|
||||
err = proposal.UpdateProposal(lane, txs)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Ensure that the proposal is not empty.
|
||||
require.Equal(t, len(txs), len(proposal.Txs))
|
||||
require.Equal(t, int64(size), proposal.Info.BlockSize)
|
||||
require.Equal(t, gasLimit, proposal.Info.GasLimit)
|
||||
require.Equal(t, uint64(10), proposal.Info.TxsByLane["test"])
|
||||
|
||||
// Ensure that the proposal can be marshalled.
|
||||
block, err := proposal.GetProposalWithInfo()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 11, len(block))
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
require.Equal(t, txBzs[i], block[i+1])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("rejects an update with duplicate transactions", func(t *testing.T) {
|
||||
tx, err := testutils.CreateRandomTx(
|
||||
encodingConfig.TxConfig,
|
||||
accounts[0],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
100,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
txBzs, err := utils.GetEncodedTxs(encodingConfig.TxConfig.TxEncoder(), []sdk.Tx{tx})
|
||||
require.NoError(t, err)
|
||||
|
||||
size := int64(len(txBzs[0]))
|
||||
gasLimit := uint64(100)
|
||||
proposal := proposals.NewProposal(encodingConfig.TxConfig.TxEncoder(), size, gasLimit)
|
||||
|
||||
err = proposal.UpdateProposal(lane, []sdk.Tx{tx})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Ensure that the proposal is empty.
|
||||
require.Equal(t, 1, len(proposal.Txs))
|
||||
require.Equal(t, size, proposal.Info.BlockSize)
|
||||
require.Equal(t, gasLimit, proposal.Info.GasLimit)
|
||||
require.Equal(t, 1, len(proposal.Info.TxsByLane))
|
||||
require.Equal(t, uint64(1), proposal.Info.TxsByLane["test"])
|
||||
|
||||
otherlane := mocks.NewLane(t)
|
||||
|
||||
otherlane.On("Name").Return("test").Maybe()
|
||||
otherlane.On("GetMaxBlockSpace").Return(math.LegacyNewDec(1)).Maybe()
|
||||
|
||||
// Attempt to add the same transaction again.
|
||||
err = proposal.UpdateProposal(otherlane, []sdk.Tx{tx})
|
||||
require.Error(t, err)
|
||||
|
||||
require.Equal(t, 1, len(proposal.Txs))
|
||||
require.Equal(t, size, proposal.Info.BlockSize)
|
||||
require.Equal(t, gasLimit, proposal.Info.GasLimit)
|
||||
require.Equal(t, 1, len(proposal.Info.TxsByLane))
|
||||
require.Equal(t, uint64(1), proposal.Info.TxsByLane["test"])
|
||||
|
||||
// Ensure that the proposal can be marshalled.
|
||||
block, err := proposal.GetProposalWithInfo()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(block))
|
||||
require.Equal(t, txBzs[0], block[1])
|
||||
})
|
||||
|
||||
t.Run("rejects an update with duplicate lane updates", func(t *testing.T) {
|
||||
tx, err := testutils.CreateRandomTx(
|
||||
encodingConfig.TxConfig,
|
||||
accounts[0],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
100,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
tx2, err := testutils.CreateRandomTx(
|
||||
encodingConfig.TxConfig,
|
||||
accounts[1],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
100,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
txBzs, err := utils.GetEncodedTxs(encodingConfig.TxConfig.TxEncoder(), []sdk.Tx{tx, tx2})
|
||||
require.NoError(t, err)
|
||||
|
||||
size := len(txBzs[0]) + len(txBzs[1])
|
||||
gasLimit := 200
|
||||
proposal := proposals.NewProposal(encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit))
|
||||
|
||||
err = proposal.UpdateProposal(lane, []sdk.Tx{tx})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = proposal.UpdateProposal(lane, []sdk.Tx{tx2})
|
||||
require.Error(t, err)
|
||||
|
||||
// Ensure that the proposal is not empty.
|
||||
require.Equal(t, 1, len(proposal.Txs))
|
||||
require.Equal(t, int64(len(txBzs[0])), proposal.Info.BlockSize)
|
||||
require.Equal(t, uint64(100), proposal.Info.GasLimit)
|
||||
require.Equal(t, 1, len(proposal.Info.TxsByLane))
|
||||
require.Equal(t, uint64(1), proposal.Info.TxsByLane["test"])
|
||||
|
||||
// Ensure that the proposal can be marshalled.
|
||||
block, err := proposal.GetProposalWithInfo()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(block))
|
||||
require.Equal(t, txBzs[0], block[1])
|
||||
})
|
||||
|
||||
t.Run("rejects an update where lane limit is smaller (block size)", func(t *testing.T) {
|
||||
tx, err := testutils.CreateRandomTx(
|
||||
encodingConfig.TxConfig,
|
||||
accounts[0],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
100,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
txBzs, err := utils.GetEncodedTxs(encodingConfig.TxConfig.TxEncoder(), []sdk.Tx{tx})
|
||||
require.NoError(t, err)
|
||||
|
||||
size := len(txBzs[0])
|
||||
gasLimit := 100
|
||||
proposal := proposals.NewProposal(encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit))
|
||||
|
||||
lane := mocks.NewLane(t)
|
||||
|
||||
lane.On("Name").Return("test").Maybe()
|
||||
lane.On("GetMaxBlockSpace").Return(math.LegacyMustNewDecFromStr("0.5")).Maybe()
|
||||
|
||||
err = proposal.UpdateProposal(lane, []sdk.Tx{tx})
|
||||
require.Error(t, err)
|
||||
|
||||
// Ensure that the proposal is empty.
|
||||
require.Equal(t, 0, len(proposal.Txs))
|
||||
require.Equal(t, int64(0), proposal.Info.BlockSize)
|
||||
require.Equal(t, uint64(0), proposal.Info.GasLimit)
|
||||
require.Equal(t, 0, len(proposal.Info.TxsByLane))
|
||||
|
||||
// Ensure that the proposal can be marshalled.
|
||||
block, err := proposal.GetProposalWithInfo()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(block))
|
||||
})
|
||||
|
||||
t.Run("rejects an update where the lane limit is smaller (gas limit)", func(t *testing.T) {
|
||||
tx, err := testutils.CreateRandomTx(
|
||||
encodingConfig.TxConfig,
|
||||
accounts[0],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
100,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
txBzs, err := utils.GetEncodedTxs(encodingConfig.TxConfig.TxEncoder(), []sdk.Tx{tx})
|
||||
require.NoError(t, err)
|
||||
|
||||
size := len(txBzs[0])
|
||||
gasLimit := 100
|
||||
proposal := proposals.NewProposal(encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit))
|
||||
|
||||
lane := mocks.NewLane(t)
|
||||
|
||||
lane.On("Name").Return("test").Maybe()
|
||||
lane.On("GetMaxBlockSpace").Return(math.LegacyMustNewDecFromStr("0.5")).Maybe()
|
||||
|
||||
err = proposal.UpdateProposal(lane, []sdk.Tx{tx})
|
||||
require.Error(t, err)
|
||||
|
||||
// Ensure that the proposal is empty.
|
||||
require.Equal(t, 0, len(proposal.Txs))
|
||||
require.Equal(t, int64(0), proposal.Info.BlockSize)
|
||||
require.Equal(t, 0, len(proposal.Info.TxsByLane))
|
||||
require.Equal(t, uint64(0), proposal.Info.GasLimit)
|
||||
|
||||
// Ensure that the proposal can be marshalled.
|
||||
block, err := proposal.GetProposalWithInfo()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(block))
|
||||
})
|
||||
|
||||
t.Run("rejects an update where the proposal exceeds max block size", func(t *testing.T) {
|
||||
tx, err := testutils.CreateRandomTx(
|
||||
encodingConfig.TxConfig,
|
||||
accounts[0],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
100,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
txBzs, err := utils.GetEncodedTxs(encodingConfig.TxConfig.TxEncoder(), []sdk.Tx{tx})
|
||||
require.NoError(t, err)
|
||||
|
||||
size := len(txBzs[0])
|
||||
gasLimit := 100
|
||||
proposal := proposals.NewProposal(encodingConfig.TxConfig.TxEncoder(), int64(size)-1, uint64(gasLimit))
|
||||
|
||||
err = proposal.UpdateProposal(lane, []sdk.Tx{tx})
|
||||
require.Error(t, err)
|
||||
|
||||
// Ensure that the proposal is empty.
|
||||
require.Equal(t, 0, len(proposal.Txs))
|
||||
require.Equal(t, int64(0), proposal.Info.BlockSize)
|
||||
require.Equal(t, uint64(0), proposal.Info.GasLimit)
|
||||
require.Equal(t, 0, len(proposal.Info.TxsByLane))
|
||||
|
||||
// Ensure that the proposal can be marshalled.
|
||||
block, err := proposal.GetProposalWithInfo()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(block))
|
||||
})
|
||||
|
||||
t.Run("rejects an update where the proposal exceeds max gas limit", func(t *testing.T) {
|
||||
tx, err := testutils.CreateRandomTx(
|
||||
encodingConfig.TxConfig,
|
||||
accounts[0],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
100,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
txBzs, err := utils.GetEncodedTxs(encodingConfig.TxConfig.TxEncoder(), []sdk.Tx{tx})
|
||||
require.NoError(t, err)
|
||||
|
||||
size := len(txBzs[0])
|
||||
gasLimit := 100
|
||||
proposal := proposals.NewProposal(encodingConfig.TxConfig.TxEncoder(), int64(size), uint64(gasLimit)-1)
|
||||
|
||||
err = proposal.UpdateProposal(lane, []sdk.Tx{tx})
|
||||
require.Error(t, err)
|
||||
|
||||
// Ensure that the proposal is empty.
|
||||
require.Equal(t, 0, len(proposal.Txs))
|
||||
require.Equal(t, int64(0), proposal.Info.BlockSize)
|
||||
require.Equal(t, uint64(0), proposal.Info.GasLimit)
|
||||
require.Equal(t, 0, len(proposal.Info.TxsByLane))
|
||||
|
||||
// Ensure that the proposal can be marshalled.
|
||||
block, err := proposal.GetProposalWithInfo()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(block))
|
||||
})
|
||||
|
||||
t.Run("can add transactions from multiple lanes", func(t *testing.T) {
|
||||
tx, err := testutils.CreateRandomTx(
|
||||
encodingConfig.TxConfig,
|
||||
accounts[0],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
100,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
tx2, err := testutils.CreateRandomTx(
|
||||
encodingConfig.TxConfig,
|
||||
accounts[1],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
100,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
txBzs, err := utils.GetEncodedTxs(encodingConfig.TxConfig.TxEncoder(), []sdk.Tx{tx, tx2})
|
||||
require.NoError(t, err)
|
||||
|
||||
proposal := proposals.NewProposal(encodingConfig.TxConfig.TxEncoder(), 10000, 10000)
|
||||
|
||||
err = proposal.UpdateProposal(lane, []sdk.Tx{tx})
|
||||
require.NoError(t, err)
|
||||
|
||||
otherlane := mocks.NewLane(t)
|
||||
otherlane.On("Name").Return("test2")
|
||||
otherlane.On("GetMaxBlockSpace").Return(math.LegacyMustNewDecFromStr("1.0"))
|
||||
|
||||
err = proposal.UpdateProposal(otherlane, []sdk.Tx{tx2})
|
||||
require.NoError(t, err)
|
||||
|
||||
size := len(txBzs[0]) + len(txBzs[1])
|
||||
gasLimit := 200
|
||||
|
||||
// Ensure that the proposal is not empty.
|
||||
require.Equal(t, 2, len(proposal.Txs))
|
||||
require.Equal(t, int64(size), proposal.Info.BlockSize)
|
||||
require.Equal(t, uint64(gasLimit), proposal.Info.GasLimit)
|
||||
require.Equal(t, 2, len(proposal.Info.TxsByLane))
|
||||
require.Equal(t, uint64(1), proposal.Info.TxsByLane["test"])
|
||||
require.Equal(t, uint64(1), proposal.Info.TxsByLane["test2"])
|
||||
|
||||
// Ensure that the proposal can be marshalled.
|
||||
block, err := proposal.GetProposalWithInfo()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 3, len(block))
|
||||
require.Equal(t, txBzs[0], block[1])
|
||||
require.Equal(t, txBzs[1], block[2])
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetLaneLimits(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
maxTxBytes int64
|
||||
totalTxBytesUsed int64
|
||||
maxGasLimit uint64
|
||||
totalGasLimitUsed uint64
|
||||
ratio math.LegacyDec
|
||||
expectedTxBytes int64
|
||||
expectedGasLimit uint64
|
||||
}{
|
||||
{
|
||||
"ratio is zero",
|
||||
100,
|
||||
50,
|
||||
100,
|
||||
50,
|
||||
math.LegacyZeroDec(),
|
||||
50,
|
||||
50,
|
||||
},
|
||||
{
|
||||
"ratio is zero",
|
||||
100,
|
||||
100,
|
||||
50,
|
||||
25,
|
||||
math.LegacyZeroDec(),
|
||||
0,
|
||||
25,
|
||||
},
|
||||
{
|
||||
"ratio is zero",
|
||||
100,
|
||||
150,
|
||||
100,
|
||||
150,
|
||||
math.LegacyZeroDec(),
|
||||
0,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"ratio is 1",
|
||||
100,
|
||||
0,
|
||||
75,
|
||||
0,
|
||||
math.LegacyOneDec(),
|
||||
100,
|
||||
75,
|
||||
},
|
||||
{
|
||||
"ratio is 10%",
|
||||
100,
|
||||
0,
|
||||
75,
|
||||
0,
|
||||
math.LegacyMustNewDecFromStr("0.1"),
|
||||
10,
|
||||
7,
|
||||
},
|
||||
{
|
||||
"ratio is 25%",
|
||||
100,
|
||||
0,
|
||||
80,
|
||||
0,
|
||||
math.LegacyMustNewDecFromStr("0.25"),
|
||||
25,
|
||||
20,
|
||||
},
|
||||
{
|
||||
"ratio is 50%",
|
||||
101,
|
||||
0,
|
||||
75,
|
||||
0,
|
||||
math.LegacyMustNewDecFromStr("0.5"),
|
||||
50,
|
||||
37,
|
||||
},
|
||||
{
|
||||
"ratio is 33%",
|
||||
100,
|
||||
0,
|
||||
75,
|
||||
0,
|
||||
math.LegacyMustNewDecFromStr("0.33"),
|
||||
33,
|
||||
24,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
proposal := proposals.Proposal{
|
||||
Info: types.ProposalInfo{
|
||||
MaxBlockSize: tc.maxTxBytes,
|
||||
BlockSize: tc.totalTxBytesUsed,
|
||||
MaxGasLimit: tc.maxGasLimit,
|
||||
GasLimit: tc.totalGasLimitUsed,
|
||||
},
|
||||
}
|
||||
|
||||
res := proposal.GetLaneLimits(tc.ratio)
|
||||
|
||||
if res.MaxTxBytes != tc.expectedTxBytes {
|
||||
t.Errorf("expected tx bytes %d, got %d", tc.expectedTxBytes, res.MaxTxBytes)
|
||||
}
|
||||
|
||||
if res.MaxGasLimit != tc.expectedGasLimit {
|
||||
t.Errorf("expected gas limit %d, got %d", tc.expectedGasLimit, res.MaxGasLimit)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
572
block/proposals/types/types.pb.go
Normal file
572
block/proposals/types/types.pb.go
Normal file
@ -0,0 +1,572 @@
|
||||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: sdk/proposals/v1/types.proto
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/cosmos/gogoproto/proto"
|
||||
io "io"
|
||||
math "math"
|
||||
math_bits "math/bits"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
// ProposalInfo contains the metadata about a given proposal that was built by
|
||||
// the block-sdk. This is used to verify and consilidate proposal data across
|
||||
// the network.
|
||||
type ProposalInfo struct {
|
||||
// TxsByLane contains information about how each partial proposal
|
||||
// was constructed by the block-sdk lanes.
|
||||
TxsByLane map[string]uint64 `protobuf:"bytes,1,rep,name=txs_by_lane,json=txsByLane,proto3" json:"txs_by_lane,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
|
||||
// MaxBlockSize corresponds to the upper bound on the size of the
|
||||
// block that was used to construct this block proposal.
|
||||
MaxBlockSize int64 `protobuf:"varint,2,opt,name=max_block_size,json=maxBlockSize,proto3" json:"max_block_size,omitempty"`
|
||||
// MaxGasLimit corresponds to the upper bound on the gas limit of the
|
||||
// block that was used to construct this block proposal.
|
||||
MaxGasLimit uint64 `protobuf:"varint,3,opt,name=max_gas_limit,json=maxGasLimit,proto3" json:"max_gas_limit,omitempty"`
|
||||
// BlockSize corresponds to the size of this block proposal.
|
||||
BlockSize int64 `protobuf:"varint,4,opt,name=block_size,json=blockSize,proto3" json:"block_size,omitempty"`
|
||||
// GasLimit corresponds to the gas limit of this block proposal.
|
||||
GasLimit uint64 `protobuf:"varint,5,opt,name=gas_limit,json=gasLimit,proto3" json:"gas_limit,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ProposalInfo) Reset() { *m = ProposalInfo{} }
|
||||
func (m *ProposalInfo) String() string { return proto.CompactTextString(m) }
|
||||
func (*ProposalInfo) ProtoMessage() {}
|
||||
func (*ProposalInfo) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_b5d6b8540ee6bc1e, []int{0}
|
||||
}
|
||||
func (m *ProposalInfo) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *ProposalInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_ProposalInfo.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (m *ProposalInfo) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ProposalInfo.Merge(m, src)
|
||||
}
|
||||
func (m *ProposalInfo) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *ProposalInfo) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_ProposalInfo.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_ProposalInfo proto.InternalMessageInfo
|
||||
|
||||
func (m *ProposalInfo) GetTxsByLane() map[string]uint64 {
|
||||
if m != nil {
|
||||
return m.TxsByLane
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ProposalInfo) GetMaxBlockSize() int64 {
|
||||
if m != nil {
|
||||
return m.MaxBlockSize
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *ProposalInfo) GetMaxGasLimit() uint64 {
|
||||
if m != nil {
|
||||
return m.MaxGasLimit
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *ProposalInfo) GetBlockSize() int64 {
|
||||
if m != nil {
|
||||
return m.BlockSize
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *ProposalInfo) GetGasLimit() uint64 {
|
||||
if m != nil {
|
||||
return m.GasLimit
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*ProposalInfo)(nil), "sdk.proposals.v1.ProposalInfo")
|
||||
proto.RegisterMapType((map[string]uint64)(nil), "sdk.proposals.v1.ProposalInfo.TxsByLaneEntry")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("sdk/proposals/v1/types.proto", fileDescriptor_b5d6b8540ee6bc1e) }
|
||||
|
||||
var fileDescriptor_b5d6b8540ee6bc1e = []byte{
|
||||
// 325 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x91, 0xcf, 0x4b, 0xc3, 0x30,
|
||||
0x1c, 0xc5, 0x97, 0x75, 0x13, 0x9b, 0xcd, 0x31, 0x82, 0x87, 0xe2, 0x8f, 0x52, 0x86, 0x87, 0x5e,
|
||||
0x96, 0x32, 0x77, 0x11, 0xf1, 0x34, 0x10, 0x11, 0x36, 0x90, 0xea, 0xc9, 0x4b, 0x49, 0xb7, 0x38,
|
||||
0x43, 0x9b, 0xa6, 0x2c, 0x59, 0x69, 0xf7, 0x57, 0xf8, 0x2f, 0xf8, 0xdf, 0x78, 0xdc, 0xd1, 0xa3,
|
||||
0x6c, 0xff, 0x88, 0x34, 0x9b, 0x73, 0x7a, 0xfb, 0xbe, 0x97, 0x7c, 0x1e, 0x0f, 0x1e, 0x3c, 0x93,
|
||||
0x93, 0xc8, 0x4b, 0x67, 0x22, 0x15, 0x92, 0xc4, 0xd2, 0xcb, 0x7a, 0x9e, 0x2a, 0x52, 0x2a, 0x71,
|
||||
0x3a, 0x13, 0x4a, 0xa0, 0xb6, 0x9c, 0x44, 0x78, 0xf7, 0x8a, 0xb3, 0x5e, 0xe7, 0xbd, 0x0a, 0x9b,
|
||||
0x0f, 0x5b, 0xe3, 0x3e, 0x79, 0x11, 0x68, 0x04, 0x1b, 0x2a, 0x97, 0x41, 0x58, 0x04, 0x31, 0x49,
|
||||
0xa8, 0x05, 0x1c, 0xc3, 0x6d, 0x5c, 0x76, 0xf1, 0x7f, 0x10, 0xef, 0x43, 0xf8, 0x29, 0x97, 0x83,
|
||||
0x62, 0x48, 0x12, 0x7a, 0x9b, 0xa8, 0x59, 0xe1, 0x9b, 0xea, 0x47, 0xa3, 0x0b, 0xd8, 0xe2, 0x24,
|
||||
0x0f, 0xc2, 0x58, 0x8c, 0xa3, 0x40, 0xb2, 0x05, 0xb5, 0xaa, 0x0e, 0x70, 0x0d, 0xbf, 0xc9, 0x49,
|
||||
0x3e, 0x28, 0xcd, 0x47, 0xb6, 0xa0, 0xa8, 0x03, 0x8f, 0xca, 0x5f, 0x53, 0x22, 0x83, 0x98, 0x71,
|
||||
0xa6, 0x2c, 0xc3, 0x01, 0x6e, 0xcd, 0x6f, 0x70, 0x92, 0xdf, 0x11, 0x39, 0x2c, 0x2d, 0x74, 0x0e,
|
||||
0xe1, 0x5e, 0x4a, 0x4d, 0xa7, 0x98, 0xe1, 0x2e, 0xe2, 0x14, 0x9a, 0xbf, 0x78, 0x5d, 0xe3, 0x87,
|
||||
0xd3, 0x2d, 0x7b, 0x72, 0x03, 0x5b, 0x7f, 0x2b, 0xa2, 0x36, 0x34, 0x22, 0x5a, 0x58, 0xc0, 0x01,
|
||||
0xae, 0xe9, 0x97, 0x27, 0x3a, 0x86, 0xf5, 0x8c, 0xc4, 0xf3, 0x4d, 0xc1, 0x9a, 0xbf, 0x11, 0xd7,
|
||||
0xd5, 0x2b, 0x30, 0x18, 0x7d, 0xac, 0x6c, 0xb0, 0x5c, 0xd9, 0xe0, 0x6b, 0x65, 0x83, 0xb7, 0xb5,
|
||||
0x5d, 0x59, 0xae, 0xed, 0xca, 0xe7, 0xda, 0xae, 0x3c, 0xf7, 0xa7, 0x4c, 0xbd, 0xce, 0x43, 0x3c,
|
||||
0x16, 0xdc, 0x93, 0x11, 0x4b, 0xbb, 0x9c, 0x66, 0x9e, 0xee, 0xd4, 0x2d, 0x77, 0xd0, 0xd7, 0xde,
|
||||
0x1a, 0x7a, 0x8a, 0xf0, 0x40, 0x6f, 0xd1, 0xff, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x7a, 0xfb, 0x40,
|
||||
0x73, 0xab, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *ProposalInfo) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *ProposalInfo) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *ProposalInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.GasLimit != 0 {
|
||||
i = encodeVarintTypes(dAtA, i, uint64(m.GasLimit))
|
||||
i--
|
||||
dAtA[i] = 0x28
|
||||
}
|
||||
if m.BlockSize != 0 {
|
||||
i = encodeVarintTypes(dAtA, i, uint64(m.BlockSize))
|
||||
i--
|
||||
dAtA[i] = 0x20
|
||||
}
|
||||
if m.MaxGasLimit != 0 {
|
||||
i = encodeVarintTypes(dAtA, i, uint64(m.MaxGasLimit))
|
||||
i--
|
||||
dAtA[i] = 0x18
|
||||
}
|
||||
if m.MaxBlockSize != 0 {
|
||||
i = encodeVarintTypes(dAtA, i, uint64(m.MaxBlockSize))
|
||||
i--
|
||||
dAtA[i] = 0x10
|
||||
}
|
||||
if len(m.TxsByLane) > 0 {
|
||||
for k := range m.TxsByLane {
|
||||
v := m.TxsByLane[k]
|
||||
baseI := i
|
||||
i = encodeVarintTypes(dAtA, i, uint64(v))
|
||||
i--
|
||||
dAtA[i] = 0x10
|
||||
i -= len(k)
|
||||
copy(dAtA[i:], k)
|
||||
i = encodeVarintTypes(dAtA, i, uint64(len(k)))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
i = encodeVarintTypes(dAtA, i, uint64(baseI-i))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
}
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func encodeVarintTypes(dAtA []byte, offset int, v uint64) int {
|
||||
offset -= sovTypes(v)
|
||||
base := offset
|
||||
for v >= 1<<7 {
|
||||
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||
v >>= 7
|
||||
offset++
|
||||
}
|
||||
dAtA[offset] = uint8(v)
|
||||
return base
|
||||
}
|
||||
func (m *ProposalInfo) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.TxsByLane) > 0 {
|
||||
for k, v := range m.TxsByLane {
|
||||
_ = k
|
||||
_ = v
|
||||
mapEntrySize := 1 + len(k) + sovTypes(uint64(len(k))) + 1 + sovTypes(uint64(v))
|
||||
n += mapEntrySize + 1 + sovTypes(uint64(mapEntrySize))
|
||||
}
|
||||
}
|
||||
if m.MaxBlockSize != 0 {
|
||||
n += 1 + sovTypes(uint64(m.MaxBlockSize))
|
||||
}
|
||||
if m.MaxGasLimit != 0 {
|
||||
n += 1 + sovTypes(uint64(m.MaxGasLimit))
|
||||
}
|
||||
if m.BlockSize != 0 {
|
||||
n += 1 + sovTypes(uint64(m.BlockSize))
|
||||
}
|
||||
if m.GasLimit != 0 {
|
||||
n += 1 + sovTypes(uint64(m.GasLimit))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func sovTypes(x uint64) (n int) {
|
||||
return (math_bits.Len64(x|1) + 6) / 7
|
||||
}
|
||||
func sozTypes(x uint64) (n int) {
|
||||
return sovTypes(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
}
|
||||
func (m *ProposalInfo) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTypes
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: ProposalInfo: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: ProposalInfo: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field TxsByLane", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTypes
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if m.TxsByLane == nil {
|
||||
m.TxsByLane = make(map[string]uint64)
|
||||
}
|
||||
var mapkey string
|
||||
var mapvalue uint64
|
||||
for iNdEx < postIndex {
|
||||
entryPreIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTypes
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
if fieldNum == 1 {
|
||||
var stringLenmapkey uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTypes
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLenmapkey |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLenmapkey := int(stringLenmapkey)
|
||||
if intStringLenmapkey < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
postStringIndexmapkey := iNdEx + intStringLenmapkey
|
||||
if postStringIndexmapkey < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if postStringIndexmapkey > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
mapkey = string(dAtA[iNdEx:postStringIndexmapkey])
|
||||
iNdEx = postStringIndexmapkey
|
||||
} else if fieldNum == 2 {
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTypes
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
mapvalue |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
iNdEx = entryPreIndex
|
||||
skippy, err := skipTypes(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > postIndex {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
m.TxsByLane[mapkey] = mapvalue
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field MaxBlockSize", wireType)
|
||||
}
|
||||
m.MaxBlockSize = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTypes
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.MaxBlockSize |= int64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 3:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field MaxGasLimit", wireType)
|
||||
}
|
||||
m.MaxGasLimit = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTypes
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.MaxGasLimit |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 4:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field BlockSize", wireType)
|
||||
}
|
||||
m.BlockSize = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTypes
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.BlockSize |= int64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 5:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field GasLimit", wireType)
|
||||
}
|
||||
m.GasLimit = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowTypes
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.GasLimit |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipTypes(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
return ErrInvalidLengthTypes
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func skipTypes(dAtA []byte) (n int, err error) {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
depth := 0
|
||||
for iNdEx < l {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowTypes
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
wireType := int(wire & 0x7)
|
||||
switch wireType {
|
||||
case 0:
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowTypes
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx++
|
||||
if dAtA[iNdEx-1] < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
iNdEx += 8
|
||||
case 2:
|
||||
var length int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowTypes
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
length |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if length < 0 {
|
||||
return 0, ErrInvalidLengthTypes
|
||||
}
|
||||
iNdEx += length
|
||||
case 3:
|
||||
depth++
|
||||
case 4:
|
||||
if depth == 0 {
|
||||
return 0, ErrUnexpectedEndOfGroupTypes
|
||||
}
|
||||
depth--
|
||||
case 5:
|
||||
iNdEx += 4
|
||||
default:
|
||||
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
|
||||
}
|
||||
if iNdEx < 0 {
|
||||
return 0, ErrInvalidLengthTypes
|
||||
}
|
||||
if depth == 0 {
|
||||
return iNdEx, nil
|
||||
}
|
||||
}
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidLengthTypes = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowTypes = fmt.Errorf("proto: integer overflow")
|
||||
ErrUnexpectedEndOfGroupTypes = fmt.Errorf("proto: unexpected end of group")
|
||||
)
|
||||
113
block/proposals/update.go
Normal file
113
block/proposals/update.go
Normal file
@ -0,0 +1,113 @@
|
||||
package proposals
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/skip-mev/block-sdk/block/utils"
|
||||
)
|
||||
|
||||
// Lane defines the contract interface for a lane.
|
||||
type Lane interface {
|
||||
GetMaxBlockSpace() math.LegacyDec
|
||||
Name() string
|
||||
}
|
||||
|
||||
// UpdateProposal updates the proposal with the given transactions and lane limits. There are a
|
||||
// few invariants that are checked:
|
||||
// 1. The total size of the proposal must be less than the maximum number of bytes allowed.
|
||||
// 2. The total size of the partial proposal must be less than the maximum number of bytes allowed for
|
||||
// the lane.
|
||||
// 3. The total gas limit of the proposal must be less than the maximum gas limit allowed.
|
||||
// 4. The total gas limit of the partial proposal must be less than the maximum gas limit allowed for
|
||||
// the lane.
|
||||
// 5. The lane must not have already prepared a partial proposal.
|
||||
// 6. The transaction must not already be in the proposal.
|
||||
func (p *Proposal) UpdateProposal(lane Lane, partialProposal []sdk.Tx) error {
|
||||
if len(partialProposal) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// invariant check: Ensure we have not already prepared a partial proposal for this lane.
|
||||
if _, ok := p.Info.TxsByLane[lane.Name()]; ok {
|
||||
return fmt.Errorf("lane %s already prepared a partial proposal", lane)
|
||||
}
|
||||
|
||||
// Aggregate info from the transactions.
|
||||
hashes := make(map[string]struct{})
|
||||
txs := make([][]byte, len(partialProposal))
|
||||
partialProposalSize := int64(0)
|
||||
partialProposalGasLimit := uint64(0)
|
||||
|
||||
for index, tx := range partialProposal {
|
||||
txInfo, err := utils.GetTxInfo(p.TxEncoder, tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("err retrieving transaction info: %s", err)
|
||||
}
|
||||
|
||||
// invariant check: Ensure that the transaction is not already in the proposal.
|
||||
if _, ok := p.Cache[txInfo.Hash]; ok {
|
||||
return fmt.Errorf("transaction %s is already in the proposal", txInfo.Hash)
|
||||
}
|
||||
|
||||
hashes[txInfo.Hash] = struct{}{}
|
||||
partialProposalSize += txInfo.Size
|
||||
partialProposalGasLimit += txInfo.GasLimit
|
||||
txs[index] = txInfo.TxBytes
|
||||
}
|
||||
|
||||
// invariant check: Ensure that the partial proposal is not too large.
|
||||
limit := p.GetLaneLimits(lane.GetMaxBlockSpace())
|
||||
if partialProposalSize > limit.MaxTxBytes {
|
||||
return fmt.Errorf(
|
||||
"partial proposal is too large: %d > %d",
|
||||
partialProposalSize,
|
||||
limit.MaxTxBytes,
|
||||
)
|
||||
}
|
||||
|
||||
// invariant check: Ensure that the partial proposal does not consume too much gas.
|
||||
if partialProposalGasLimit > limit.MaxGasLimit {
|
||||
return fmt.Errorf(
|
||||
"partial proposal consumes too much gas: %d > %d",
|
||||
partialProposalGasLimit,
|
||||
limit.MaxGasLimit,
|
||||
)
|
||||
}
|
||||
|
||||
// invariant check: Ensure that the lane did not prepare a block proposal that is too large.
|
||||
updatedSize := p.Info.BlockSize + partialProposalSize
|
||||
if updatedSize > p.Info.MaxBlockSize {
|
||||
return fmt.Errorf(
|
||||
"block proposal is too large: %d > %d",
|
||||
updatedSize,
|
||||
p.Info.MaxBlockSize,
|
||||
)
|
||||
}
|
||||
|
||||
// invariant check: Ensure that the lane did not prepare a block proposal that consumes too much gas.
|
||||
updatedGasLimit := p.Info.GasLimit + partialProposalGasLimit
|
||||
if updatedGasLimit > p.Info.MaxGasLimit {
|
||||
return fmt.Errorf(
|
||||
"block proposal consumes too much gas: %d > %d",
|
||||
updatedGasLimit,
|
||||
p.Info.MaxGasLimit,
|
||||
)
|
||||
}
|
||||
|
||||
// Update the proposal.
|
||||
p.Info.BlockSize = updatedSize
|
||||
p.Info.GasLimit = updatedGasLimit
|
||||
|
||||
// Update the lane info.
|
||||
p.Info.TxsByLane[lane.Name()] = uint64(len(partialProposal))
|
||||
|
||||
// Update the proposal.
|
||||
p.Txs = append(p.Txs, txs...)
|
||||
for hash := range hashes {
|
||||
p.Cache[hash] = struct{}{}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
39
block/proposals/utils.go
Normal file
39
block/proposals/utils.go
Normal file
@ -0,0 +1,39 @@
|
||||
package proposals
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxUint64 is the maximum value of a uint64.
|
||||
MaxUint64 = 1<<64 - 1
|
||||
)
|
||||
|
||||
type (
|
||||
// LaneLimits defines the constraints for a partial proposal. Each lane must only propose
|
||||
// transactions that satisfy these constraints. Otherwise the partial proposal update will
|
||||
// be rejected.
|
||||
LaneLimits struct {
|
||||
// MaxTxBytes is the maximum number of bytes allowed in the partial proposal.
|
||||
MaxTxBytes int64
|
||||
// MaxGasLimit is the maximum gas limit allowed in the partial proposal.
|
||||
MaxGasLimit uint64
|
||||
}
|
||||
)
|
||||
|
||||
// GetBlockLimits retrieves the maximum number of bytes and gas limit allowed in a block.
|
||||
func GetBlockLimits(ctx sdk.Context) (int64, uint64) {
|
||||
blockParams := ctx.ConsensusParams().Block
|
||||
|
||||
// If the max gas is set to 0, then the max gas limit for the block can be infinite.
|
||||
// Otherwise we use the max gas limit casted as a uint64 which is how gas limits are
|
||||
// extracted from sdk.Tx's.
|
||||
var maxGasLimit uint64
|
||||
if maxGas := blockParams.MaxGas; maxGas > 0 {
|
||||
maxGasLimit = uint64(maxGas)
|
||||
} else {
|
||||
maxGasLimit = MaxUint64
|
||||
}
|
||||
|
||||
return blockParams.MaxBytes, maxGasLimit
|
||||
}
|
||||
@ -2,24 +2,25 @@ package block
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/skip-mev/block-sdk/block/proposals"
|
||||
)
|
||||
|
||||
type (
|
||||
// PrepareLanesHandler wraps all of the lanes' PrepareLane function into a single chained
|
||||
// function. You can think of it like an AnteHandler, but for preparing proposals in the
|
||||
// context of lanes instead of modules.
|
||||
PrepareLanesHandler func(ctx sdk.Context, proposal BlockProposal) (BlockProposal, error)
|
||||
PrepareLanesHandler func(ctx sdk.Context, proposal proposals.Proposal) (proposals.Proposal, error)
|
||||
|
||||
// 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, txs []sdk.Tx) (sdk.Context, error)
|
||||
ProcessLanesHandler func(ctx sdk.Context, proposal proposals.Proposal) (proposals.Proposal, error)
|
||||
)
|
||||
|
||||
// NoOpPrepareLanesHandler returns a no-op prepare lanes handler.
|
||||
// This should only be used for testing.
|
||||
func NoOpPrepareLanesHandler() PrepareLanesHandler {
|
||||
return func(ctx sdk.Context, proposal BlockProposal) (BlockProposal, error) {
|
||||
return func(ctx sdk.Context, proposal proposals.Proposal) (proposals.Proposal, error) {
|
||||
return proposal, nil
|
||||
}
|
||||
}
|
||||
@ -27,7 +28,7 @@ func NoOpPrepareLanesHandler() PrepareLanesHandler {
|
||||
// NoOpProcessLanesHandler returns a no-op process lanes handler.
|
||||
// This should only be used for testing.
|
||||
func NoOpProcessLanesHandler() ProcessLanesHandler {
|
||||
return func(ctx sdk.Context, txs []sdk.Tx) (sdk.Context, error) {
|
||||
return ctx, nil
|
||||
return func(_ sdk.Context, p proposals.Proposal) (proposals.Proposal, error) {
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,23 +5,47 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool"
|
||||
)
|
||||
|
||||
// GetTxHashStr returns the hex-encoded hash of the transaction alongside the
|
||||
// transaction bytes.
|
||||
func GetTxHashStr(txEncoder sdk.TxEncoder, tx sdk.Tx) ([]byte, string, error) {
|
||||
type (
|
||||
// TxInfo contains the information required for a transaction to be
|
||||
// included in a proposal.
|
||||
TxInfo struct {
|
||||
// Hash is the hex-encoded hash of the transaction.
|
||||
Hash string
|
||||
// Size is the size of the transaction in bytes.
|
||||
Size int64
|
||||
// GasLimit is the gas limit of the transaction.
|
||||
GasLimit uint64
|
||||
// TxBytes is the bytes of the transaction.
|
||||
TxBytes []byte
|
||||
}
|
||||
)
|
||||
|
||||
// GetTxHashStr returns the TxInfo of a given transaction.
|
||||
func GetTxInfo(txEncoder sdk.TxEncoder, tx sdk.Tx) (TxInfo, error) {
|
||||
txBz, err := txEncoder(tx)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to encode transaction: %w", err)
|
||||
return TxInfo{}, fmt.Errorf("failed to encode transaction: %w", err)
|
||||
}
|
||||
|
||||
txHash := sha256.Sum256(txBz)
|
||||
txHashStr := hex.EncodeToString(txHash[:])
|
||||
|
||||
return txBz, txHashStr, nil
|
||||
// TODO: Add an adapter to lanes so that this can be flexible to support EVM, etc.
|
||||
gasTx, ok := tx.(sdk.FeeTx)
|
||||
if !ok {
|
||||
return TxInfo{}, fmt.Errorf("failed to cast transaction to GasTx")
|
||||
}
|
||||
|
||||
return TxInfo{
|
||||
Hash: txHashStr,
|
||||
Size: int64(len(txBz)),
|
||||
GasLimit: gasTx.GetGas(),
|
||||
TxBytes: txBz,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetDecodedTxs returns the decoded transactions from the given bytes.
|
||||
@ -39,6 +63,21 @@ func GetDecodedTxs(txDecoder sdk.TxDecoder, txs [][]byte) ([]sdk.Tx, error) {
|
||||
return decodedTxs, nil
|
||||
}
|
||||
|
||||
// GetEncodedTxs returns the encoded transactions from the given bytes.
|
||||
func GetEncodedTxs(txEncoder sdk.TxEncoder, txs []sdk.Tx) ([][]byte, error) {
|
||||
var encodedTxs [][]byte
|
||||
for _, tx := range txs {
|
||||
txBz, err := txEncoder(tx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode transaction: %w", err)
|
||||
}
|
||||
|
||||
encodedTxs = append(encodedTxs, txBz)
|
||||
}
|
||||
|
||||
return encodedTxs, nil
|
||||
}
|
||||
|
||||
// RemoveTxsFromLane removes the transactions from the given lane's mempool.
|
||||
func RemoveTxsFromLane(txs []sdk.Tx, mempool sdkmempool.Mempool) error {
|
||||
for _, tx := range txs {
|
||||
@ -49,23 +88,3 @@ func RemoveTxsFromLane(txs []sdk.Tx, mempool sdkmempool.Mempool) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMaxTxBytesForLane returns the maximum number of bytes that can be included in the proposal
|
||||
// for the given lane.
|
||||
func GetMaxTxBytesForLane(maxTxBytes, totalTxBytes int64, ratio math.LegacyDec) int64 {
|
||||
// In the case where the ratio is zero, we return the max tx bytes remaining. Note, the only
|
||||
// lane that should have a ratio of zero is the default lane. This means the default lane
|
||||
// will have no limit on the number of transactions it can include in a block and is only
|
||||
// limited by the maxTxBytes included in the PrepareProposalRequest.
|
||||
if ratio.IsZero() {
|
||||
remainder := maxTxBytes - totalTxBytes
|
||||
if remainder < 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return remainder
|
||||
}
|
||||
|
||||
// Otherwise, we calculate the max tx bytes for the lane based on the ratio.
|
||||
return ratio.MulInt64(maxTxBytes).TruncateInt().Int64()
|
||||
}
|
||||
|
||||
@ -1,78 +0,0 @@
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
|
||||
"github.com/skip-mev/block-sdk/block/utils"
|
||||
)
|
||||
|
||||
func TestGetMaxTxBytesForLane(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
maxTxBytes int64
|
||||
totalTxBytes int64
|
||||
ratio math.LegacyDec
|
||||
expected int64
|
||||
}{
|
||||
{
|
||||
"ratio is zero",
|
||||
100,
|
||||
50,
|
||||
math.LegacyZeroDec(),
|
||||
50,
|
||||
},
|
||||
{
|
||||
"ratio is zero",
|
||||
100,
|
||||
100,
|
||||
math.LegacyZeroDec(),
|
||||
0,
|
||||
},
|
||||
{
|
||||
"ratio is zero",
|
||||
100,
|
||||
150,
|
||||
math.LegacyZeroDec(),
|
||||
0,
|
||||
},
|
||||
{
|
||||
"ratio is 1",
|
||||
100,
|
||||
50,
|
||||
math.LegacyOneDec(),
|
||||
100,
|
||||
},
|
||||
{
|
||||
"ratio is 10%",
|
||||
100,
|
||||
50,
|
||||
math.LegacyMustNewDecFromStr("0.1"),
|
||||
10,
|
||||
},
|
||||
{
|
||||
"ratio is 25%",
|
||||
100,
|
||||
50,
|
||||
math.LegacyMustNewDecFromStr("0.25"),
|
||||
25,
|
||||
},
|
||||
{
|
||||
"ratio is 50%",
|
||||
101,
|
||||
50,
|
||||
math.LegacyMustNewDecFromStr("0.5"),
|
||||
50,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
actual := utils.GetMaxTxBytesForLane(tc.maxTxBytes, tc.totalTxBytes, tc.ratio)
|
||||
if actual != tc.expected {
|
||||
t.Errorf("expected %d, got %d", tc.expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -12,7 +12,9 @@ import (
|
||||
signer_extraction "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/utils/mocks"
|
||||
"github.com/skip-mev/block-sdk/block/mocks"
|
||||
"github.com/skip-mev/block-sdk/block/proposals"
|
||||
"github.com/skip-mev/block-sdk/block/utils"
|
||||
defaultlane "github.com/skip-mev/block-sdk/lanes/base"
|
||||
testutils "github.com/skip-mev/block-sdk/testutils"
|
||||
)
|
||||
@ -26,33 +28,36 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
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.LegacyMustNewDecFromStr("1"), expectedExecution)
|
||||
|
||||
// Insert the transaction into the lane
|
||||
lane := s.initLane(math.LegacyMustNewDecFromStr("0.5"), expectedExecution)
|
||||
s.Require().NoError(lane.Insert(sdk.Context{}, tx))
|
||||
|
||||
txBz, err := s.encodingConfig.TxConfig.TxEncoder()(tx)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a proposal
|
||||
maxTxBytes := int64(len(txBz) - 1)
|
||||
proposal, err := lane.PrepareLane(sdk.Context{}, block.NewProposal(maxTxBytes), maxTxBytes, block.NoOpPrepareLanesHandler())
|
||||
emptyProposal := proposals.NewProposal(
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
int64(len(txBz)),
|
||||
1,
|
||||
)
|
||||
|
||||
finalProposal, err := lane.PrepareLane(sdk.Context{}, emptyProposal, block.NoOpPrepareLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Ensure the proposal is empty
|
||||
s.Require().Equal(0, proposal.GetNumTxs())
|
||||
s.Require().Equal(int64(0), proposal.GetTotalTxBytes())
|
||||
s.Require().Equal(0, len(finalProposal.Txs))
|
||||
s.Require().Equal(int64(0), finalProposal.Info.BlockSize)
|
||||
s.Require().Equal(uint64(0), finalProposal.Info.GasLimit)
|
||||
})
|
||||
|
||||
s.Run("should not build a proposal when box space configured to lane is too small", func() {
|
||||
s.Run("should not build a proposal when gas configured to lane is too small", func() {
|
||||
// Create a basic transaction that should not in the proposal
|
||||
tx, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
@ -60,15 +65,58 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
10,
|
||||
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.LegacyMustNewDecFromStr("0.000001"), expectedExecution)
|
||||
lane := s.initLane(math.LegacyMustNewDecFromStr("0.5"), expectedExecution)
|
||||
|
||||
// Insert the transaction into the lane
|
||||
s.Require().NoError(lane.Insert(sdk.Context{}, tx))
|
||||
|
||||
txBz, err := s.encodingConfig.TxConfig.TxEncoder()(tx)
|
||||
s.Require().NoError(err)
|
||||
|
||||
limit := proposals.LaneLimits{
|
||||
MaxTxBytes: int64(len(txBz)) * 10,
|
||||
MaxGasLimit: 10,
|
||||
}
|
||||
emptyProposal := proposals.NewProposal(
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
limit.MaxTxBytes,
|
||||
limit.MaxGasLimit,
|
||||
)
|
||||
|
||||
finalProposal, err := lane.PrepareLane(sdk.Context{}, emptyProposal, block.NoOpPrepareLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Ensure the proposal is empty
|
||||
s.Require().Equal(0, len(finalProposal.Txs))
|
||||
s.Require().Equal(int64(0), finalProposal.Info.BlockSize)
|
||||
s.Require().Equal(uint64(0), finalProposal.Info.GasLimit)
|
||||
})
|
||||
|
||||
s.Run("should not build a proposal when gas configured to lane is too small p2", func() {
|
||||
// Create a basic transaction that should not in the proposal
|
||||
tx, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
10,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(1)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
expectedExecution := map[sdk.Tx]bool{
|
||||
tx: true,
|
||||
}
|
||||
lane := s.initLane(math.LegacyMustNewDecFromStr("0.5"), expectedExecution)
|
||||
|
||||
// Insert the transaction into the lane
|
||||
s.Require().NoError(lane.Insert(sdk.Context{}, tx))
|
||||
@ -77,48 +125,67 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a proposal
|
||||
maxTxBytes := int64(len(txBz))
|
||||
proposal, err := lane.PrepareLane(sdk.Context{}, block.NewProposal(maxTxBytes), maxTxBytes, block.NoOpPrepareLanesHandler())
|
||||
s.Require().Error(err)
|
||||
limit := proposals.LaneLimits{
|
||||
MaxTxBytes: int64(len(txBz)) * 10, // have enough space for 10 of these txs
|
||||
MaxGasLimit: 10,
|
||||
}
|
||||
emptyProposal := proposals.NewProposal(
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
limit.MaxTxBytes,
|
||||
limit.MaxGasLimit,
|
||||
)
|
||||
|
||||
finalProposal, err := lane.PrepareLane(sdk.Context{}, emptyProposal, block.NoOpPrepareLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Ensure the proposal is empty
|
||||
s.Require().Equal(0, proposal.GetNumTxs())
|
||||
s.Require().Equal(int64(0), proposal.GetTotalTxBytes())
|
||||
s.Require().Equal(0, len(finalProposal.Txs))
|
||||
s.Require().Equal(int64(0), finalProposal.Info.BlockSize)
|
||||
s.Require().Equal(uint64(0), finalProposal.Info.GasLimit)
|
||||
})
|
||||
|
||||
s.Run("should be able to build a proposal with a tx that just fits in", func() {
|
||||
// Create a basic transaction that should not in the proposal
|
||||
// Create a basic transaction that should just fit in with the gas limit
|
||||
// and max size
|
||||
tx, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
10,
|
||||
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.LegacyMustNewDecFromStr("1"), expectedExecution)
|
||||
lane := s.initLane(math.LegacyOneDec(), expectedExecution)
|
||||
|
||||
// Insert the transaction into the lane
|
||||
s.Require().NoError(lane.Insert(sdk.Context{}, tx))
|
||||
|
||||
txBz, err := s.encodingConfig.TxConfig.TxEncoder()(tx)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create a proposal
|
||||
maxTxBytes := int64(len(txBz))
|
||||
proposal, err := lane.PrepareLane(sdk.Context{}, block.NewProposal(maxTxBytes), maxTxBytes, block.NoOpPrepareLanesHandler())
|
||||
limit := proposals.LaneLimits{
|
||||
MaxTxBytes: int64(len(txBz)),
|
||||
MaxGasLimit: 10,
|
||||
}
|
||||
emptyProposal := proposals.NewProposal(
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
limit.MaxTxBytes,
|
||||
limit.MaxGasLimit,
|
||||
)
|
||||
|
||||
finalProposal, err := lane.PrepareLane(sdk.Context{}, emptyProposal, block.NoOpPrepareLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Ensure the proposal is not empty and contains the transaction
|
||||
s.Require().Equal(1, proposal.GetNumTxs())
|
||||
s.Require().Equal(maxTxBytes, proposal.GetTotalTxBytes())
|
||||
s.Require().Equal(txBz, proposal.GetTxs()[0])
|
||||
s.Require().Equal(1, len(finalProposal.Txs))
|
||||
s.Require().Equal(limit.MaxTxBytes, finalProposal.Info.BlockSize)
|
||||
s.Require().Equal(uint64(10), finalProposal.Info.GasLimit)
|
||||
s.Require().Equal(txBz, finalProposal.Txs[0])
|
||||
})
|
||||
|
||||
s.Run("should not build a proposal with a that fails verify tx", func() {
|
||||
@ -129,30 +196,35 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
10,
|
||||
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
|
||||
// We expect the transaction to fail verify tx
|
||||
expectedExecution := map[sdk.Tx]bool{
|
||||
tx: false,
|
||||
}
|
||||
lane := s.initLane(math.LegacyMustNewDecFromStr("1"), expectedExecution)
|
||||
lane := s.initLane(math.LegacyOneDec(), expectedExecution)
|
||||
|
||||
// Insert the transaction into the lane
|
||||
s.Require().NoError(lane.Insert(sdk.Context{}, tx))
|
||||
|
||||
// Create a proposal
|
||||
txBz, err := s.encodingConfig.TxConfig.TxEncoder()(tx)
|
||||
s.Require().NoError(err)
|
||||
|
||||
maxTxBytes := int64(len(txBz))
|
||||
proposal, err := lane.PrepareLane(sdk.Context{}, block.NewProposal(maxTxBytes), maxTxBytes, block.NoOpPrepareLanesHandler())
|
||||
emptyProposal := proposals.NewProposal(
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
int64(len(txBz)),
|
||||
10,
|
||||
)
|
||||
|
||||
finalProposal, err := lane.PrepareLane(sdk.Context{}, emptyProposal, block.NoOpPrepareLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Ensure the proposal is empty
|
||||
s.Require().Equal(0, proposal.GetNumTxs())
|
||||
s.Require().Equal(int64(0), proposal.GetTotalTxBytes())
|
||||
s.Require().Equal(0, len(finalProposal.Txs))
|
||||
s.Require().Equal(int64(0), finalProposal.Info.BlockSize)
|
||||
s.Require().Equal(uint64(0), finalProposal.Info.GasLimit)
|
||||
|
||||
// Ensure the transaction is removed from the lane
|
||||
s.Require().False(lane.Contains(tx))
|
||||
@ -160,13 +232,13 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
})
|
||||
|
||||
s.Run("should order transactions correctly in the proposal", func() {
|
||||
// Create a basic transaction that should not in the proposal
|
||||
tx1, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
10,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(2)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
@ -177,18 +249,17 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
10,
|
||||
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{
|
||||
tx1: true,
|
||||
tx2: true,
|
||||
}
|
||||
lane := s.initLane(math.LegacyMustNewDecFromStr("1"), expectedExecution)
|
||||
lane := s.initLane(math.LegacyOneDec(), expectedExecution)
|
||||
|
||||
// Insert the transaction into the lane
|
||||
s.Require().NoError(lane.Insert(sdk.Context{}, tx1))
|
||||
s.Require().NoError(lane.Insert(sdk.Context{}, tx2))
|
||||
|
||||
@ -198,24 +269,32 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
txBz2, err := s.encodingConfig.TxConfig.TxEncoder()(tx2)
|
||||
s.Require().NoError(err)
|
||||
|
||||
maxTxBytes := int64(len(txBz1)) + int64(len(txBz2))
|
||||
proposal, err := lane.PrepareLane(sdk.Context{}, block.NewProposal(maxTxBytes), maxTxBytes, block.NoOpPrepareLanesHandler())
|
||||
size := int64(len(txBz1)) + int64(len(txBz2))
|
||||
gasLimit := uint64(20)
|
||||
emptyProposal := proposals.NewProposal(
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
size,
|
||||
gasLimit,
|
||||
)
|
||||
|
||||
finalProposal, err := lane.PrepareLane(sdk.Context{}, emptyProposal, block.NoOpPrepareLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Ensure the proposal is ordered correctly
|
||||
s.Require().Equal(2, proposal.GetNumTxs())
|
||||
s.Require().Equal(maxTxBytes, proposal.GetTotalTxBytes())
|
||||
s.Require().Equal([][]byte{txBz1, txBz2}, proposal.GetTxs())
|
||||
s.Require().Equal(2, len(finalProposal.Txs))
|
||||
s.Require().Equal(size, finalProposal.Info.BlockSize)
|
||||
s.Require().Equal(gasLimit, finalProposal.Info.GasLimit)
|
||||
s.Require().Equal([][]byte{txBz1, txBz2}, finalProposal.Txs)
|
||||
})
|
||||
|
||||
s.Run("should order transactions correctly in the proposal (with different insertion)", func() {
|
||||
// Create a basic transaction that should not in the proposal
|
||||
tx1, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(1)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
@ -226,18 +305,17 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(2)),
|
||||
)
|
||||
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{
|
||||
tx1: true,
|
||||
tx2: true,
|
||||
}
|
||||
lane := s.initLane(math.LegacyMustNewDecFromStr("1"), expectedExecution)
|
||||
lane := s.initLane(math.LegacyOneDec(), expectedExecution)
|
||||
|
||||
// Insert the transaction into the lane
|
||||
s.Require().NoError(lane.Insert(sdk.Context{}, tx1))
|
||||
s.Require().NoError(lane.Insert(sdk.Context{}, tx2))
|
||||
|
||||
@ -247,14 +325,22 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
txBz2, err := s.encodingConfig.TxConfig.TxEncoder()(tx2)
|
||||
s.Require().NoError(err)
|
||||
|
||||
maxTxBytes := int64(len(txBz1)) + int64(len(txBz2))
|
||||
proposal, err := lane.PrepareLane(sdk.Context{}, block.NewProposal(maxTxBytes), maxTxBytes, block.NoOpPrepareLanesHandler())
|
||||
size := int64(len(txBz1)) + int64(len(txBz2))
|
||||
gasLimit := uint64(2)
|
||||
emptyProposal := proposals.NewProposal(
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
size,
|
||||
gasLimit,
|
||||
)
|
||||
|
||||
finalProposal, err := lane.PrepareLane(sdk.Context{}, emptyProposal, block.NoOpPrepareLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Ensure the proposal is ordered correctly
|
||||
s.Require().Equal(2, proposal.GetNumTxs())
|
||||
s.Require().Equal(maxTxBytes, proposal.GetTotalTxBytes())
|
||||
s.Require().Equal([][]byte{txBz2, txBz1}, proposal.GetTxs())
|
||||
s.Require().Equal(2, len(finalProposal.Txs))
|
||||
s.Require().Equal(size, finalProposal.Info.BlockSize)
|
||||
s.Require().Equal(gasLimit, finalProposal.Info.GasLimit)
|
||||
s.Require().Equal([][]byte{txBz2, txBz1}, finalProposal.Txs)
|
||||
})
|
||||
|
||||
s.Run("should include tx that fits in proposal when other does not", func() {
|
||||
@ -265,6 +351,7 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(1)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
@ -275,6 +362,7 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
0,
|
||||
10, // This tx is too large to fit in the proposal
|
||||
0,
|
||||
1,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(1)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
@ -284,7 +372,7 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
tx1: true,
|
||||
tx2: true,
|
||||
}
|
||||
lane := s.initLane(math.LegacyMustNewDecFromStr("1"), expectedExecution)
|
||||
lane := s.initLane(math.LegacyOneDec(), expectedExecution)
|
||||
|
||||
// Insert the transaction into the lane
|
||||
s.Require().NoError(lane.Insert(sdk.Context{}.WithPriority(10), tx1))
|
||||
@ -296,14 +384,130 @@ func (s *BaseTestSuite) TestPrepareLane() {
|
||||
txBz2, err := s.encodingConfig.TxConfig.TxEncoder()(tx2)
|
||||
s.Require().NoError(err)
|
||||
|
||||
maxTxBytes := int64(len(txBz1)) + int64(len(txBz2)) - 1
|
||||
proposal, err := lane.PrepareLane(sdk.Context{}, block.NewProposal(maxTxBytes), maxTxBytes, block.NoOpPrepareLanesHandler())
|
||||
size := int64(len(txBz1)) + int64(len(txBz2)) - 1
|
||||
gasLimit := uint64(3)
|
||||
emptyProposal := proposals.NewProposal(
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
size,
|
||||
gasLimit,
|
||||
)
|
||||
|
||||
finalProposal, err := lane.PrepareLane(sdk.Context{}, emptyProposal, block.NoOpPrepareLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Ensure the proposal is ordered correctly
|
||||
s.Require().Equal(1, proposal.GetNumTxs())
|
||||
s.Require().Equal(int64(len(txBz1)), proposal.GetTotalTxBytes())
|
||||
s.Require().Equal([][]byte{txBz1}, proposal.GetTxs())
|
||||
s.Require().Equal(1, len(finalProposal.Txs))
|
||||
s.Require().Equal(int64(len(txBz1)), finalProposal.Info.BlockSize)
|
||||
s.Require().Equal(uint64(2), finalProposal.Info.GasLimit)
|
||||
s.Require().Equal([][]byte{txBz1}, finalProposal.Txs)
|
||||
})
|
||||
|
||||
s.Run("should include tx that consumes all gas in proposal while other cannot", func() {
|
||||
// Create a basic transaction that should not in the proposal
|
||||
tx1, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(1)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
tx2, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[1],
|
||||
0,
|
||||
10, // This tx is too large to fit in the proposal
|
||||
0,
|
||||
1,
|
||||
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{
|
||||
tx1: true,
|
||||
tx2: true,
|
||||
}
|
||||
lane := s.initLane(math.LegacyOneDec(), expectedExecution)
|
||||
|
||||
// Insert the transaction into the lane
|
||||
s.Require().NoError(lane.Insert(sdk.Context{}, tx1))
|
||||
s.Require().NoError(lane.Insert(sdk.Context{}, tx2))
|
||||
|
||||
txBz1, err := s.encodingConfig.TxConfig.TxEncoder()(tx1)
|
||||
s.Require().NoError(err)
|
||||
|
||||
txBz2, err := s.encodingConfig.TxConfig.TxEncoder()(tx2)
|
||||
s.Require().NoError(err)
|
||||
|
||||
size := int64(len(txBz1)) + int64(len(txBz2)) - 1
|
||||
gasLimit := uint64(1)
|
||||
emptyProposal := proposals.NewProposal(
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
size,
|
||||
gasLimit,
|
||||
)
|
||||
|
||||
finalProposal, err := lane.PrepareLane(sdk.Context{}, emptyProposal, block.NoOpPrepareLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Ensure the proposal is ordered correctly
|
||||
s.Require().Equal(1, len(finalProposal.Txs))
|
||||
s.Require().Equal(int64(len(txBz2)), finalProposal.Info.BlockSize)
|
||||
s.Require().Equal(uint64(1), finalProposal.Info.GasLimit)
|
||||
s.Require().Equal([][]byte{txBz2}, finalProposal.Txs)
|
||||
})
|
||||
|
||||
s.Run("should not attempt to include transaction already included in the proposal", 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(sdk.Context{}, tx))
|
||||
|
||||
txBz, err := s.encodingConfig.TxConfig.TxEncoder()(tx)
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
int64(len(txBz))*10,
|
||||
1000000,
|
||||
)
|
||||
|
||||
mockLane := mocks.NewLane(s.T())
|
||||
|
||||
mockLane.On("Name").Return("test")
|
||||
mockLane.On("GetMaxBlockSpace").Return(math.LegacyOneDec())
|
||||
|
||||
err = emptyProposal.UpdateProposal(mockLane, []sdk.Tx{tx})
|
||||
s.Require().NoError(err)
|
||||
|
||||
finalProposal, err := lane.PrepareLane(sdk.Context{}, emptyProposal, block.NoOpPrepareLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Ensure the proposal is ordered correctly
|
||||
s.Require().Equal(1, len(finalProposal.Txs))
|
||||
s.Require().Equal(int64(len(txBz)), finalProposal.Info.BlockSize)
|
||||
s.Require().Equal(uint64(2), finalProposal.Info.GasLimit)
|
||||
s.Require().Equal([][]byte{txBz}, finalProposal.Txs)
|
||||
})
|
||||
}
|
||||
|
||||
@ -315,6 +519,7 @@ func (s *BaseTestSuite) TestProcessLane() {
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
@ -322,11 +527,23 @@ func (s *BaseTestSuite) TestProcessLane() {
|
||||
tx1,
|
||||
}
|
||||
|
||||
lane := s.initLane(math.LegacyMustNewDecFromStr("1"), map[sdk.Tx]bool{
|
||||
tx1: true,
|
||||
})
|
||||
lane := s.initLane(
|
||||
math.LegacyOneDec(),
|
||||
map[sdk.Tx]bool{
|
||||
tx1: true,
|
||||
},
|
||||
)
|
||||
|
||||
_, err = lane.ProcessLane(sdk.Context{}, proposal, block.NoOpProcessLanesHandler())
|
||||
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
100000,
|
||||
100000,
|
||||
)
|
||||
|
||||
_, err = lane.ProcessLane(sdk.Context{}, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
})
|
||||
|
||||
@ -337,6 +554,7 @@ func (s *BaseTestSuite) TestProcessLane() {
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
@ -344,11 +562,23 @@ func (s *BaseTestSuite) TestProcessLane() {
|
||||
tx1,
|
||||
}
|
||||
|
||||
lane := s.initLane(math.LegacyMustNewDecFromStr("1"), map[sdk.Tx]bool{
|
||||
tx1: false,
|
||||
})
|
||||
lane := s.initLane(
|
||||
math.LegacyOneDec(),
|
||||
map[sdk.Tx]bool{
|
||||
tx1: false,
|
||||
},
|
||||
)
|
||||
|
||||
_, err = lane.ProcessLane(sdk.Context{}, proposal, block.NoOpProcessLanesHandler())
|
||||
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
100000,
|
||||
100000,
|
||||
)
|
||||
|
||||
_, err = lane.ProcessLane(sdk.Context{}, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
|
||||
s.Require().Error(err)
|
||||
})
|
||||
|
||||
@ -359,6 +589,7 @@ func (s *BaseTestSuite) TestProcessLane() {
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
@ -368,6 +599,7 @@ func (s *BaseTestSuite) TestProcessLane() {
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
@ -377,6 +609,7 @@ func (s *BaseTestSuite) TestProcessLane() {
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
@ -386,18 +619,27 @@ func (s *BaseTestSuite) TestProcessLane() {
|
||||
tx3,
|
||||
}
|
||||
|
||||
lane := s.initLane(math.LegacyMustNewDecFromStr("1"), map[sdk.Tx]bool{
|
||||
tx1: true,
|
||||
tx2: false,
|
||||
tx3: true,
|
||||
})
|
||||
lane := s.initLane(
|
||||
math.LegacyOneDec(),
|
||||
map[sdk.Tx]bool{
|
||||
tx1: true,
|
||||
tx2: false,
|
||||
tx3: true,
|
||||
})
|
||||
|
||||
_, err = lane.ProcessLane(sdk.Context{}, proposal, block.NoOpProcessLanesHandler())
|
||||
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
100000,
|
||||
100000,
|
||||
)
|
||||
|
||||
_, err = lane.ProcessLane(sdk.Context{}, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
|
||||
s.Require().Error(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *BaseTestSuite) TestCheckOrder() {
|
||||
s.Run("should accept proposal with transactions in correct order", func() {
|
||||
tx1, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
@ -405,6 +647,7 @@ func (s *BaseTestSuite) TestCheckOrder() {
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(2)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
@ -415,6 +658,7 @@ func (s *BaseTestSuite) TestCheckOrder() {
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(1)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
@ -424,11 +668,24 @@ func (s *BaseTestSuite) TestCheckOrder() {
|
||||
tx2,
|
||||
}
|
||||
|
||||
lane := s.initLane(math.LegacyMustNewDecFromStr("1"), map[sdk.Tx]bool{
|
||||
tx1: true,
|
||||
tx2: true,
|
||||
})
|
||||
s.Require().NoError(lane.CheckOrder(sdk.Context{}, proposal))
|
||||
lane := s.initLane(
|
||||
math.LegacyOneDec(),
|
||||
map[sdk.Tx]bool{
|
||||
tx1: true,
|
||||
tx2: true,
|
||||
})
|
||||
|
||||
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
100000,
|
||||
100000,
|
||||
)
|
||||
|
||||
_, err = lane.ProcessLane(sdk.Context{}, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
|
||||
s.Require().NoError(err)
|
||||
})
|
||||
|
||||
s.Run("should not accept a proposal with transactions that are not in the correct order", func() {
|
||||
@ -438,6 +695,7 @@ func (s *BaseTestSuite) TestCheckOrder() {
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(1)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
@ -448,6 +706,7 @@ func (s *BaseTestSuite) TestCheckOrder() {
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(2)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
@ -457,11 +716,24 @@ func (s *BaseTestSuite) TestCheckOrder() {
|
||||
tx2,
|
||||
}
|
||||
|
||||
lane := s.initLane(math.LegacyMustNewDecFromStr("1"), map[sdk.Tx]bool{
|
||||
tx1: true,
|
||||
tx2: true,
|
||||
})
|
||||
s.Require().Error(lane.CheckOrder(sdk.Context{}, proposal))
|
||||
lane := s.initLane(
|
||||
math.LegacyOneDec(),
|
||||
map[sdk.Tx]bool{
|
||||
tx1: true,
|
||||
tx2: true,
|
||||
})
|
||||
|
||||
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
100000,
|
||||
100000,
|
||||
)
|
||||
|
||||
_, err = lane.ProcessLane(sdk.Context{}, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
|
||||
s.Require().Error(err)
|
||||
})
|
||||
|
||||
s.Run("should not accept a proposal where transactions are out of order relative to other lanes", func() {
|
||||
@ -471,6 +743,7 @@ func (s *BaseTestSuite) TestCheckOrder() {
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(1)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
@ -481,23 +754,203 @@ func (s *BaseTestSuite) TestCheckOrder() {
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(2)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
mocklane := mocks.NewLane(s.T())
|
||||
mocklane.On("Match", sdk.Context{}, tx1).Return(true)
|
||||
mocklane.On("Match", sdk.Context{}, tx2).Return(false)
|
||||
otherLane := s.initLane(math.LegacyOneDec(), nil)
|
||||
|
||||
lane := s.initLane(math.LegacyMustNewDecFromStr("1"), nil)
|
||||
lane.SetIgnoreList([]block.Lane{mocklane})
|
||||
lane := s.initLane(
|
||||
math.LegacyOneDec(),
|
||||
map[sdk.Tx]bool{
|
||||
tx1: true,
|
||||
tx2: false,
|
||||
})
|
||||
lane.SetIgnoreList([]block.Lane{otherLane})
|
||||
|
||||
proposal := []sdk.Tx{
|
||||
tx1,
|
||||
tx2,
|
||||
}
|
||||
|
||||
s.Require().Error(lane.CheckOrder(sdk.Context{}, proposal))
|
||||
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
100000,
|
||||
100000,
|
||||
)
|
||||
|
||||
_, err = lane.ProcessLane(sdk.Context{}, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
|
||||
s.Require().Error(err)
|
||||
})
|
||||
|
||||
s.Run("should not accept a proposal that builds too large of a partial block", func() {
|
||||
tx1, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
proposal := []sdk.Tx{
|
||||
tx1,
|
||||
}
|
||||
|
||||
lane := s.initLane(
|
||||
math.LegacyOneDec(),
|
||||
map[sdk.Tx]bool{
|
||||
tx1: true,
|
||||
})
|
||||
|
||||
maxSize := s.getTxSize(tx1) - 1
|
||||
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
maxSize,
|
||||
1000000,
|
||||
)
|
||||
|
||||
_, err = lane.ProcessLane(sdk.Context{}, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
|
||||
s.Require().Error(err)
|
||||
})
|
||||
|
||||
s.Run("should not accept a proposal that builds a partial block that is too gas consumptive", func() {
|
||||
tx1, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
10,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
proposal := []sdk.Tx{
|
||||
tx1,
|
||||
}
|
||||
|
||||
lane := s.initLane(
|
||||
math.LegacyOneDec(),
|
||||
map[sdk.Tx]bool{
|
||||
tx1: true,
|
||||
},
|
||||
)
|
||||
|
||||
maxSize := s.getTxSize(tx1)
|
||||
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
maxSize,
|
||||
9,
|
||||
)
|
||||
|
||||
_, err = lane.ProcessLane(sdk.Context{}, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
|
||||
s.Require().Error(err)
|
||||
})
|
||||
|
||||
s.Run("should not accept a proposal that builds a partial block that is too gas consumptive p2", func() {
|
||||
tx1, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
10,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
tx2, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[1],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
10,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
proposal := []sdk.Tx{
|
||||
tx1,
|
||||
tx2,
|
||||
}
|
||||
|
||||
lane := s.initLane(
|
||||
math.LegacyOneDec(),
|
||||
map[sdk.Tx]bool{
|
||||
tx1: true,
|
||||
tx2: true,
|
||||
})
|
||||
|
||||
maxSize := s.getTxSize(tx1) + s.getTxSize(tx2)
|
||||
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
maxSize,
|
||||
19,
|
||||
)
|
||||
|
||||
_, err = lane.ProcessLane(sdk.Context{}, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
|
||||
s.Require().Error(err)
|
||||
})
|
||||
|
||||
s.Run("should not accept a proposal that builds a partial block that is too large p2", func() {
|
||||
tx1, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[0],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
10,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
tx2, err := testutils.CreateRandomTx(
|
||||
s.encodingConfig.TxConfig,
|
||||
s.accounts[1],
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
10,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
proposal := []sdk.Tx{
|
||||
tx1,
|
||||
tx2,
|
||||
}
|
||||
|
||||
lane := s.initLane(
|
||||
math.LegacyOneDec(),
|
||||
map[sdk.Tx]bool{
|
||||
tx1: true,
|
||||
tx2: true,
|
||||
},
|
||||
)
|
||||
|
||||
maxSize := s.getTxSize(tx1) + s.getTxSize(tx2) - 1
|
||||
partialProposal, err := utils.GetEncodedTxs(s.encodingConfig.TxConfig.TxEncoder(), proposal)
|
||||
s.Require().NoError(err)
|
||||
|
||||
emptyProposal := proposals.NewProposal(
|
||||
s.encodingConfig.TxConfig.TxEncoder(),
|
||||
maxSize,
|
||||
20,
|
||||
)
|
||||
|
||||
_, err = lane.ProcessLane(sdk.Context{}, emptyProposal, partialProposal, block.NoOpProcessLanesHandler())
|
||||
s.Require().Error(err)
|
||||
})
|
||||
}
|
||||
|
||||
@ -549,3 +1002,10 @@ func (s *BaseTestSuite) setUpAnteHandler(expectedExecution map[sdk.Tx]bool) sdk.
|
||||
|
||||
return anteHandler
|
||||
}
|
||||
|
||||
func (s *BaseTestSuite) getTxSize(tx sdk.Tx) int64 {
|
||||
txBz, err := s.encodingConfig.TxConfig.TxEncoder()(tx)
|
||||
s.Require().NoError(err)
|
||||
|
||||
return int64(len(txBz))
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ func (s *BaseTestSuite) TestGetTxPriority() {
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
@ -34,6 +35,7 @@ func (s *BaseTestSuite) TestGetTxPriority() {
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
@ -48,6 +50,7 @@ func (s *BaseTestSuite) TestGetTxPriority() {
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdk.NewCoin("random", math.NewInt(100)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
@ -95,6 +98,7 @@ func (s *BaseTestSuite) TestInsert() {
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
@ -112,6 +116,7 @@ func (s *BaseTestSuite) TestInsert() {
|
||||
uint64(i),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(int64(100*i))),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
@ -127,6 +132,7 @@ func (s *BaseTestSuite) TestInsert() {
|
||||
10,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
@ -147,6 +153,7 @@ func (s *BaseTestSuite) TestRemove() {
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
@ -166,6 +173,7 @@ func (s *BaseTestSuite) TestRemove() {
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
@ -184,6 +192,7 @@ func (s *BaseTestSuite) TestSelect() {
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
@ -194,6 +203,7 @@ func (s *BaseTestSuite) TestSelect() {
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(200)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
@ -223,6 +233,7 @@ func (s *BaseTestSuite) TestSelect() {
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdk.NewCoin(s.gasTokenDenom, math.NewInt(100)),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
@ -339,7 +339,7 @@ the lane either.
|
||||
ProcessLaneHandler func(ctx sdk.Context, txs []sdk.Tx) ([]sdk.Tx, error)
|
||||
```
|
||||
|
||||
Given the invarients above, the default implementation is simple. It will
|
||||
Given the invariants above, the default implementation is simple. It will
|
||||
continue to verify transactions in the block proposal under the following criteria:
|
||||
|
||||
1. If a transaction matches to this lane, verify it and continue. If it is not
|
||||
|
||||
@ -6,8 +6,8 @@ import (
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"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/utils"
|
||||
"github.com/skip-mev/block-sdk/x/auction/types"
|
||||
)
|
||||
@ -17,11 +17,11 @@ import (
|
||||
// will return no transactions if no valid bids are found. If any of the bids are invalid,
|
||||
// it will return them and will only remove the bids and not the bundled transactions.
|
||||
func (l *MEVLane) PrepareLaneHandler() base.PrepareLaneHandler {
|
||||
return func(ctx sdk.Context, proposal block.BlockProposal, maxTxBytes int64) ([][]byte, []sdk.Tx, error) {
|
||||
return func(ctx sdk.Context, proposal proposals.Proposal, limit proposals.LaneLimits) ([]sdk.Tx, []sdk.Tx, error) {
|
||||
// Define all of the info we need to select transactions for the partial proposal.
|
||||
var (
|
||||
txs [][]byte
|
||||
txsToRemove []sdk.Tx
|
||||
txsToInclude []sdk.Tx
|
||||
txsToRemove []sdk.Tx
|
||||
)
|
||||
|
||||
// Attempt to select the highest bid transaction that is valid and whose
|
||||
@ -30,209 +30,228 @@ func (l *MEVLane) PrepareLaneHandler() base.PrepareLaneHandler {
|
||||
selectBidTxLoop:
|
||||
for ; bidTxIterator != nil; bidTxIterator = bidTxIterator.Next() {
|
||||
cacheCtx, write := ctx.CacheContext()
|
||||
tmpBidTx := bidTxIterator.Tx()
|
||||
bidTx := bidTxIterator.Tx()
|
||||
|
||||
bidTxBz, hash, err := utils.GetTxHashStr(l.TxEncoder(), tmpBidTx)
|
||||
txInfo, err := utils.GetTxInfo(l.TxEncoder(), bidTx)
|
||||
if err != nil {
|
||||
l.Logger().Info("failed to get hash of auction bid tx", "err", err)
|
||||
|
||||
txsToRemove = append(txsToRemove, tmpBidTx)
|
||||
txsToRemove = append(txsToRemove, bidTx)
|
||||
continue selectBidTxLoop
|
||||
}
|
||||
|
||||
// if the transaction is already in the (partial) block proposal, we skip it.
|
||||
if proposal.Contains(bidTxBz) {
|
||||
//
|
||||
// TODO: Should we really be panic'ing here?
|
||||
if proposal.Contains(txInfo.Hash) {
|
||||
l.Logger().Info(
|
||||
"failed to select auction bid tx for lane; tx is already in proposal",
|
||||
"tx_hash", hash,
|
||||
"tx_hash", txInfo.Hash,
|
||||
)
|
||||
|
||||
continue selectBidTxLoop
|
||||
}
|
||||
|
||||
bidTxSize := int64(len(bidTxBz))
|
||||
if bidTxSize <= maxTxBytes {
|
||||
// Build the partial proposal by selecting the bid transaction and all of
|
||||
// its bundled transactions.
|
||||
bidInfo, err := l.GetAuctionBidInfo(tmpBidTx)
|
||||
if err != nil {
|
||||
l.Logger().Info(
|
||||
"failed to get auction bid info",
|
||||
"tx_hash", hash,
|
||||
"err", err,
|
||||
)
|
||||
if txInfo.Size > limit.MaxTxBytes {
|
||||
l.Logger().Info(
|
||||
"failed to select auction bid tx for lane; tx size is too large",
|
||||
"tx_size", txInfo.Size,
|
||||
"max_size", limit.MaxTxBytes,
|
||||
"tx_hash", txInfo.Hash,
|
||||
)
|
||||
|
||||
// Some transactions in the bundle may be malformed or invalid, so we
|
||||
// remove the bid transaction and try the next top bid.
|
||||
txsToRemove = append(txsToRemove, tmpBidTx)
|
||||
continue selectBidTxLoop
|
||||
}
|
||||
|
||||
// Verify the bid transaction and all of its bundled transactions.
|
||||
if err := l.VerifyTx(cacheCtx, tmpBidTx, bidInfo); err != nil {
|
||||
l.Logger().Info(
|
||||
"failed to verify auction bid tx",
|
||||
"tx_hash", hash,
|
||||
"err", err,
|
||||
)
|
||||
|
||||
txsToRemove = append(txsToRemove, tmpBidTx)
|
||||
continue selectBidTxLoop
|
||||
}
|
||||
|
||||
// store the bytes of each ref tx as sdk.Tx bytes in order to build a valid proposal
|
||||
bundledTxBz := make([][]byte, len(bidInfo.Transactions))
|
||||
for index, rawRefTx := range bidInfo.Transactions {
|
||||
sdkTx, err := l.WrapBundleTransaction(rawRefTx)
|
||||
if err != nil {
|
||||
l.Logger().Info(
|
||||
"failed to wrap bundled tx",
|
||||
"tx_hash", hash,
|
||||
"err", err,
|
||||
)
|
||||
|
||||
txsToRemove = append(txsToRemove, tmpBidTx)
|
||||
continue selectBidTxLoop
|
||||
}
|
||||
|
||||
sdkTxBz, _, err := utils.GetTxHashStr(l.TxEncoder(), sdkTx)
|
||||
if err != nil {
|
||||
l.Logger().Info(
|
||||
"failed to get hash of bundled tx",
|
||||
"tx_hash", hash,
|
||||
"err", err,
|
||||
)
|
||||
|
||||
txsToRemove = append(txsToRemove, tmpBidTx)
|
||||
continue selectBidTxLoop
|
||||
}
|
||||
|
||||
// if the transaction is already in the (partial) block proposal, we skip it.
|
||||
if proposal.Contains(sdkTxBz) {
|
||||
l.Logger().Info(
|
||||
"failed to select auction bid tx for lane; tx is already in proposal",
|
||||
"tx_hash", hash,
|
||||
)
|
||||
|
||||
continue selectBidTxLoop
|
||||
}
|
||||
|
||||
bundleTxBz := make([]byte, len(sdkTxBz))
|
||||
copy(bundleTxBz, sdkTxBz)
|
||||
bundledTxBz[index] = sdkTxBz
|
||||
}
|
||||
|
||||
// At this point, both the bid transaction itself and all the bundled
|
||||
// transactions are valid. So we select the bid transaction along with
|
||||
// all the bundled transactions. We also mark these transactions as seen and
|
||||
// update the total size selected thus far.
|
||||
txs = append(txs, bidTxBz)
|
||||
txs = append(txs, bundledTxBz...)
|
||||
|
||||
// Write the cache context to the original context when we know we have a
|
||||
// valid bundle.
|
||||
write()
|
||||
|
||||
break selectBidTxLoop
|
||||
txsToRemove = append(txsToRemove, bidTx)
|
||||
continue selectBidTxLoop
|
||||
}
|
||||
|
||||
l.Logger().Info(
|
||||
"failed to select auction bid tx for lane; tx size is too large",
|
||||
"tx_size", bidTxSize,
|
||||
"max_size", maxTxBytes,
|
||||
)
|
||||
if txInfo.GasLimit > limit.MaxGasLimit {
|
||||
l.Logger().Info(
|
||||
"failed to select auction bid tx for lane; tx gas limit is too large",
|
||||
"tx_gas_limit", txInfo.GasLimit,
|
||||
"max_gas_limit", limit.MaxGasLimit,
|
||||
"tx_hash", txInfo.Hash,
|
||||
)
|
||||
|
||||
txsToRemove = append(txsToRemove, bidTx)
|
||||
continue selectBidTxLoop
|
||||
}
|
||||
|
||||
// Build the partial proposal by selecting the bid transaction and all of
|
||||
// its bundled transactions.
|
||||
bidInfo, err := l.GetAuctionBidInfo(bidTx)
|
||||
if err != nil {
|
||||
l.Logger().Info(
|
||||
"failed to get auction bid info",
|
||||
"tx_hash", txInfo.Hash,
|
||||
"err", err,
|
||||
)
|
||||
|
||||
// Some transactions in the bundle may be malformed or invalid, so we
|
||||
// remove the bid transaction and try the next top bid.
|
||||
txsToRemove = append(txsToRemove, bidTx)
|
||||
continue selectBidTxLoop
|
||||
}
|
||||
|
||||
// Verify the bid transaction and all of its bundled transactions.
|
||||
if err := l.VerifyTx(cacheCtx, bidTx, bidInfo); err != nil {
|
||||
l.Logger().Info(
|
||||
"failed to verify auction bid tx",
|
||||
"tx_hash", txInfo.Hash,
|
||||
"err", err,
|
||||
)
|
||||
|
||||
txsToRemove = append(txsToRemove, bidTx)
|
||||
continue selectBidTxLoop
|
||||
}
|
||||
|
||||
// store the bytes of each ref tx as sdk.Tx bytes in order to build a valid proposal
|
||||
gasLimitSum := txInfo.GasLimit
|
||||
bundledTxs := make([]sdk.Tx, len(bidInfo.Transactions))
|
||||
for index, bundledTxBz := range bidInfo.Transactions {
|
||||
bundleTx, err := l.WrapBundleTransaction(bundledTxBz)
|
||||
if err != nil {
|
||||
l.Logger().Info(
|
||||
"failed to wrap bundled tx",
|
||||
"tx_hash", txInfo.Hash,
|
||||
"err", err,
|
||||
)
|
||||
|
||||
txsToRemove = append(txsToRemove, bidTx)
|
||||
continue selectBidTxLoop
|
||||
}
|
||||
|
||||
bundledTxInfo, err := utils.GetTxInfo(l.TxEncoder(), bundleTx)
|
||||
if err != nil {
|
||||
l.Logger().Info(
|
||||
"failed to get hash of bundled tx",
|
||||
"tx_hash", txInfo.Hash,
|
||||
"err", err,
|
||||
)
|
||||
|
||||
txsToRemove = append(txsToRemove, bidTx)
|
||||
continue selectBidTxLoop
|
||||
}
|
||||
|
||||
// if the transaction is already in the (partial) block proposal, we skip it.
|
||||
if proposal.Contains(bundledTxInfo.Hash) {
|
||||
l.Logger().Info(
|
||||
"failed to select auction bid tx for lane; tx is already in proposal",
|
||||
"tx_hash", bundledTxInfo.Hash,
|
||||
)
|
||||
|
||||
continue selectBidTxLoop
|
||||
}
|
||||
|
||||
// If the bundled transaction is a bid transaction, we skip it.
|
||||
if l.Match(ctx, bundleTx) {
|
||||
l.Logger().Info(
|
||||
"failed to select auction bid tx for lane; bundled tx is another bid transaction",
|
||||
"tx_hash", bundledTxInfo.Hash,
|
||||
)
|
||||
|
||||
txsToRemove = append(txsToRemove, bidTx)
|
||||
continue selectBidTxLoop
|
||||
}
|
||||
|
||||
if gasLimitSum += bundledTxInfo.GasLimit; gasLimitSum > limit.MaxGasLimit {
|
||||
l.Logger().Info(
|
||||
"failed to select auction bid tx for lane; tx gas limit is too large",
|
||||
"tx_gas_limit", gasLimitSum,
|
||||
"max_gas_limit", limit.MaxGasLimit,
|
||||
"tx_hash", txInfo.Hash,
|
||||
)
|
||||
|
||||
txsToRemove = append(txsToRemove, bidTx)
|
||||
continue selectBidTxLoop
|
||||
}
|
||||
|
||||
bundleTxBz := make([]byte, bundledTxInfo.Size)
|
||||
copy(bundleTxBz, bundledTxInfo.TxBytes)
|
||||
bundledTxs[index] = bundleTx
|
||||
}
|
||||
|
||||
// At this point, both the bid transaction itself and all the bundled
|
||||
// transactions are valid. So we select the bid transaction along with
|
||||
// all the bundled transactions. We also mark these transactions as seen and
|
||||
// update the total size selected thus far.
|
||||
txsToInclude = append(txsToInclude, bidTx)
|
||||
txsToInclude = append(txsToInclude, bundledTxs...)
|
||||
|
||||
// Write the cache context to the original context when we know we have a
|
||||
// valid bundle.
|
||||
write()
|
||||
|
||||
break selectBidTxLoop
|
||||
}
|
||||
|
||||
return txs, txsToRemove, nil
|
||||
return txsToInclude, txsToRemove, nil
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessLaneHandler will ensure that block proposals that include transactions from
|
||||
// the mev lane are valid.
|
||||
// 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.
|
||||
// 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, txs []sdk.Tx) ([]sdk.Tx, error) {
|
||||
if len(txs) == 0 {
|
||||
return txs, nil
|
||||
return func(ctx sdk.Context, partialProposal []sdk.Tx) error {
|
||||
if len(partialProposal) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
bidTx := txs[0]
|
||||
// If the first transaction does not match the lane, then we return an error.
|
||||
bidTx := partialProposal[0]
|
||||
if !l.Match(ctx, bidTx) {
|
||||
return txs, nil
|
||||
return fmt.Errorf("expected first transaction in lane %s to be a bid transaction", l.Name())
|
||||
}
|
||||
|
||||
bidInfo, err := l.GetAuctionBidInfo(bidTx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get bid info for lane %s: %w", l.Name(), err)
|
||||
return fmt.Errorf("failed to get bid info from auction bid tx for lane %s: %w", l.Name(), err)
|
||||
}
|
||||
|
||||
if err := l.VerifyTx(ctx, bidTx, bidInfo); err != nil {
|
||||
return nil, fmt.Errorf("invalid bid tx: %w", err)
|
||||
}
|
||||
|
||||
return txs[len(bidInfo.Transactions)+1:], nil
|
||||
}
|
||||
}
|
||||
|
||||
// CheckOrderHandler ensures that if a bid transaction is present in a proposal,
|
||||
// - it is the first transaction in the partial proposal
|
||||
// - all of the bundled transactions are included after the bid transaction in the order
|
||||
// they were included in the bid transaction.
|
||||
// - there are no other bid transactions in the proposal
|
||||
// - transactions from other lanes are not interleaved with transactions from the bid
|
||||
// transaction.
|
||||
func (l *MEVLane) CheckOrderHandler() base.CheckOrderHandler {
|
||||
return func(ctx sdk.Context, txs []sdk.Tx) error {
|
||||
if len(txs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
bidTx := txs[0]
|
||||
|
||||
// If there is a bid transaction, it must be the first transaction in the block proposal.
|
||||
if !l.Match(ctx, bidTx) {
|
||||
for _, tx := range txs[1:] {
|
||||
if l.Match(ctx, tx) {
|
||||
return fmt.Errorf("misplaced bid transactions in lane %s", l.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
bidInfo, err := l.GetAuctionBidInfo(bidTx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get bid info for lane %s: %w", l.Name(), err)
|
||||
}
|
||||
|
||||
if len(txs) < len(bidInfo.Transactions)+1 {
|
||||
// Check that all bundled transactions were included.
|
||||
if len(bidInfo.Transactions)+1 != len(partialProposal) {
|
||||
return fmt.Errorf(
|
||||
"invalid number of transactions in lane %s; expected at least %d, got %d",
|
||||
l.Name(),
|
||||
"expected %d transactions in lane %s but got %d",
|
||||
len(bidInfo.Transactions)+1,
|
||||
len(txs),
|
||||
l.Name(),
|
||||
len(partialProposal),
|
||||
)
|
||||
}
|
||||
|
||||
// Ensure that the order of transactions in the bundle is preserved.
|
||||
for i, bundleTx := range txs[1 : len(bidInfo.Transactions)+1] {
|
||||
if l.Match(ctx, bundleTx) {
|
||||
return fmt.Errorf("multiple bid transactions in lane %s", l.Name())
|
||||
}
|
||||
|
||||
txBz, err := l.TxEncoder()(bundleTx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode bundled tx in lane %s: %w", l.Name(), err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(txBz, bidInfo.Transactions[i]) {
|
||||
return fmt.Errorf("invalid order of transactions in lane %s", l.Name())
|
||||
}
|
||||
// Verify the top-level bid transaction.
|
||||
if ctx, err = l.AnteVerifyTx(ctx, bidTx, false); err != nil {
|
||||
return fmt.Errorf("invalid bid tx; failed to execute ante handler: %w", err)
|
||||
}
|
||||
|
||||
// Ensure that there are no more bid transactions in the block proposal.
|
||||
for _, tx := range txs[len(bidInfo.Transactions)+1:] {
|
||||
if l.Match(ctx, tx) {
|
||||
return fmt.Errorf("multiple bid transactions in lane %s", l.Name())
|
||||
// Verify all of the bundled transactions.
|
||||
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)
|
||||
}
|
||||
|
||||
txBz, err := l.TxEncoder()(partialProposal[index+1])
|
||||
if err != nil {
|
||||
return 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(bundledTxBz, txBz) {
|
||||
return fmt.Errorf("invalid bid tx; bundled tx does not match tx in block proposal")
|
||||
}
|
||||
|
||||
// Verify this is not another bid transaction.
|
||||
if l.Match(ctx, bundledTx) {
|
||||
return fmt.Errorf("invalid bid tx; bundled tx is another bid transaction")
|
||||
}
|
||||
|
||||
if ctx, err = l.AnteVerifyTx(ctx, bundledTx, false); err != nil {
|
||||
return fmt.Errorf("invalid bid tx; failed to execute bundled transaction: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ func (suite *MEVTestSuite) TestIsAuctionTx() {
|
||||
{
|
||||
"normal sdk tx",
|
||||
func() sdk.Tx {
|
||||
tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 2, 0)
|
||||
tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 2, 0, 0)
|
||||
suite.Require().NoError(err)
|
||||
return tx
|
||||
},
|
||||
@ -125,7 +125,7 @@ func (suite *MEVTestSuite) TestGetTransactionSigners() {
|
||||
{
|
||||
"normal sdk tx",
|
||||
func() sdk.Tx {
|
||||
tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 10, 0)
|
||||
tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 10, 0, 0)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return tx
|
||||
@ -186,7 +186,7 @@ func (suite *MEVTestSuite) TestWrapBundleTransaction() {
|
||||
{
|
||||
"normal sdk tx",
|
||||
func() (sdk.Tx, []byte) {
|
||||
tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 1, 0)
|
||||
tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 1, 0, 0)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
bz, err := suite.encCfg.TxConfig.TxEncoder()(tx)
|
||||
@ -241,7 +241,7 @@ func (suite *MEVTestSuite) TestGetBidder() {
|
||||
{
|
||||
"normal sdk tx",
|
||||
func() sdk.Tx {
|
||||
tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 1, 0)
|
||||
tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 1, 0, 0)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return tx
|
||||
@ -316,7 +316,7 @@ func (suite *MEVTestSuite) TestGetBid() {
|
||||
{
|
||||
"normal sdk tx",
|
||||
func() sdk.Tx {
|
||||
tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 1, 0)
|
||||
tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 1, 0, 0)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return tx
|
||||
@ -390,7 +390,7 @@ func (suite *MEVTestSuite) TestGetBundledTransactions() {
|
||||
{
|
||||
"normal sdk tx",
|
||||
func() (sdk.Tx, [][]byte) {
|
||||
tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 1, 0)
|
||||
tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 1, 0, 0)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return tx, nil
|
||||
@ -462,7 +462,7 @@ func (suite *MEVTestSuite) TestGetTimeout() {
|
||||
{
|
||||
"normal sdk tx",
|
||||
func() sdk.Tx {
|
||||
tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 1, 1)
|
||||
tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, suite.accounts[0], 0, 1, 1, 0)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
return tx
|
||||
|
||||
@ -67,9 +67,6 @@ func NewMEVLane(
|
||||
// Set the process lane handler to the TOB one
|
||||
lane.SetProcessLaneHandler(lane.ProcessLaneHandler())
|
||||
|
||||
// Set the check order handler to the TOB one
|
||||
lane.SetCheckOrderHandler(lane.CheckOrderHandler())
|
||||
|
||||
if err := lane.ValidateBasic(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import (
|
||||
sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool"
|
||||
|
||||
"github.com/skip-mev/block-sdk/block"
|
||||
"github.com/skip-mev/block-sdk/block/proposals"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -40,18 +41,13 @@ type Terminator struct{}
|
||||
var _ block.Lane = (*Terminator)(nil)
|
||||
|
||||
// PrepareLane is a no-op
|
||||
func (t Terminator) PrepareLane(_ sdk.Context, proposal block.BlockProposal, _ int64, _ block.PrepareLanesHandler) (block.BlockProposal, error) {
|
||||
func (t Terminator) PrepareLane(_ sdk.Context, proposal proposals.Proposal, _ block.PrepareLanesHandler) (proposals.Proposal, error) {
|
||||
return proposal, nil
|
||||
}
|
||||
|
||||
// ValidateLaneBasic is a no-op
|
||||
func (t Terminator) CheckOrder(sdk.Context, []sdk.Tx) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProcessLane is a no-op
|
||||
func (t Terminator) ProcessLane(ctx sdk.Context, _ []sdk.Tx, _ block.ProcessLanesHandler) (sdk.Context, error) {
|
||||
return ctx, nil
|
||||
func (t Terminator) ProcessLane(_ sdk.Context, p proposals.Proposal, _ [][]byte, _ block.ProcessLanesHandler) (proposals.Proposal, error) {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// GetMaxBlockSpace is a no-op
|
||||
|
||||
23
proto/sdk/proposals/v1/types.proto
Normal file
23
proto/sdk/proposals/v1/types.proto
Normal file
@ -0,0 +1,23 @@
|
||||
syntax = "proto3";
|
||||
package sdk.proposals.v1;
|
||||
|
||||
option go_package = "github.com/skip-mev/block-sdk/block/proposals/types";
|
||||
|
||||
// ProposalInfo contains the metadata about a given proposal that was built by
|
||||
// the block-sdk. This is used to verify and consilidate proposal data across
|
||||
// the network.
|
||||
message ProposalInfo {
|
||||
// TxsByLane contains information about how each partial proposal
|
||||
// was constructed by the block-sdk lanes.
|
||||
map<string, uint64> txs_by_lane = 1;
|
||||
// MaxBlockSize corresponds to the upper bound on the size of the
|
||||
// block that was used to construct this block proposal.
|
||||
int64 max_block_size = 2;
|
||||
// MaxGasLimit corresponds to the upper bound on the gas limit of the
|
||||
// block that was used to construct this block proposal.
|
||||
uint64 max_gas_limit = 3;
|
||||
// BlockSize corresponds to the size of this block proposal.
|
||||
int64 block_size = 4;
|
||||
// GasLimit corresponds to the gas limit of this block proposal.
|
||||
uint64 gas_limit = 5;
|
||||
}
|
||||
@ -5,12 +5,11 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/ante"
|
||||
|
||||
"github.com/skip-mev/block-sdk/block"
|
||||
"github.com/skip-mev/block-sdk/block/utils"
|
||||
auctionante "github.com/skip-mev/block-sdk/x/auction/ante"
|
||||
auctionkeeper "github.com/skip-mev/block-sdk/x/auction/keeper"
|
||||
)
|
||||
|
||||
type POBHandlerOptions struct {
|
||||
type BSDKHandlerOptions struct {
|
||||
BaseOptions ante.HandlerOptions
|
||||
Mempool block.Mempool
|
||||
MEVLane auctionante.MEVLane
|
||||
@ -20,8 +19,9 @@ type POBHandlerOptions struct {
|
||||
FreeLane block.Lane
|
||||
}
|
||||
|
||||
// NewPOBAnteHandler wraps all of the default Cosmos SDK AnteDecorators with the POB AnteHandler.
|
||||
func NewPOBAnteHandler(options POBHandlerOptions) sdk.AnteHandler {
|
||||
// NewBSDKAnteHandler wraps all of the default Cosmos SDK AnteDecorators with the custom
|
||||
// block-sdk AnteHandlers.
|
||||
func NewBSDKAnteHandler(options BSDKHandlerOptions) sdk.AnteHandler {
|
||||
if options.BaseOptions.AccountKeeper == nil {
|
||||
panic("account keeper is required for ante builder")
|
||||
}
|
||||
@ -41,7 +41,7 @@ func NewPOBAnteHandler(options POBHandlerOptions) sdk.AnteHandler {
|
||||
ante.NewTxTimeoutHeightDecorator(),
|
||||
ante.NewValidateMemoDecorator(options.BaseOptions.AccountKeeper),
|
||||
ante.NewConsumeGasForTxSizeDecorator(options.BaseOptions.AccountKeeper),
|
||||
utils.NewIgnoreDecorator(
|
||||
block.NewIgnoreDecorator(
|
||||
ante.NewDeductFeeDecorator(
|
||||
options.BaseOptions.AccountKeeper,
|
||||
options.BaseOptions.BankKeeper,
|
||||
|
||||
@ -321,7 +321,7 @@ func New(
|
||||
SigGasConsumer: ante.DefaultSigVerificationGasConsumer,
|
||||
SignModeHandler: app.txConfig.SignModeHandler(),
|
||||
}
|
||||
options := POBHandlerOptions{
|
||||
options := BSDKHandlerOptions{
|
||||
BaseOptions: handlerOptions,
|
||||
auctionkeeper: app.auctionkeeper,
|
||||
TxDecoder: app.txConfig.TxDecoder(),
|
||||
@ -330,7 +330,7 @@ func New(
|
||||
MEVLane: mevLane,
|
||||
Mempool: mempool,
|
||||
}
|
||||
anteHandler := NewPOBAnteHandler(options)
|
||||
anteHandler := NewBSDKAnteHandler(options)
|
||||
|
||||
// Set the lane config on the lanes.
|
||||
for _, lane := range lanes {
|
||||
@ -342,6 +342,7 @@ func New(
|
||||
proposalHandler := abci.NewProposalHandler(
|
||||
app.Logger(),
|
||||
app.TxConfig().TxDecoder(),
|
||||
app.TxConfig().TxEncoder(),
|
||||
mempool,
|
||||
)
|
||||
app.App.SetPrepareProposal(proposalHandler.PrepareProposalHandler())
|
||||
|
||||
@ -54,7 +54,7 @@ func (s *IntegrationTestSuite) WithDenom(denom string) *IntegrationTestSuite {
|
||||
s.denom = denom
|
||||
|
||||
// update the bech32 prefixes
|
||||
sdk.GetConfig().SetBech32PrefixForAccount(s.denom, s.denom+ sdk.PrefixPublic)
|
||||
sdk.GetConfig().SetBech32PrefixForAccount(s.denom, s.denom+sdk.PrefixPublic)
|
||||
sdk.GetConfig().SetBech32PrefixForValidator(s.denom+sdk.PrefixValidator, s.denom+sdk.PrefixValidator+sdk.PrefixPublic)
|
||||
sdk.GetConfig().Seal()
|
||||
return s
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
"os"
|
||||
"io"
|
||||
"path"
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||
@ -39,7 +40,7 @@ import (
|
||||
|
||||
type KeyringOverride struct {
|
||||
keyringOptions keyring.Option
|
||||
cdc codec.Codec
|
||||
cdc codec.Codec
|
||||
}
|
||||
|
||||
// ChainBuilderFromChainSpec creates an interchaintest chain builder factory given a ChainSpec
|
||||
@ -189,8 +190,8 @@ func (s *IntegrationTestSuite) BroadcastTxs(ctx context.Context, chain *cosmos.C
|
||||
// will not block on the tx's inclusion in a block, otherwise this method will block on the tx's inclusion. The callback
|
||||
// function is called for each tx that is included in a block.
|
||||
func (s *IntegrationTestSuite) BroadcastTxsWithCallback(
|
||||
ctx context.Context,
|
||||
chain *cosmos.CosmosChain,
|
||||
ctx context.Context,
|
||||
chain *cosmos.CosmosChain,
|
||||
msgsPerUser []Tx,
|
||||
cb func(tx []byte, resp *rpctypes.ResultTx),
|
||||
) [][]byte {
|
||||
@ -204,6 +205,11 @@ 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 txs {
|
||||
// broadcast tx
|
||||
resp, err := client.BroadcastTxSync(ctx, tx)
|
||||
@ -343,7 +349,7 @@ func Block(t *testing.T, chain *cosmos.CosmosChain, height int64) *rpctypes.Resu
|
||||
// WaitForHeight waits for the chain to reach the given height
|
||||
func WaitForHeight(t *testing.T, chain *cosmos.CosmosChain, height uint64) {
|
||||
// wait for next height
|
||||
err := testutil.WaitForCondition(30*time.Second, 100 * time.Millisecond, func() (bool, error) {
|
||||
err := testutil.WaitForCondition(30*time.Second, 100*time.Millisecond, func() (bool, error) {
|
||||
pollHeight, err := chain.Height(context.Background())
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -357,13 +363,13 @@ func WaitForHeight(t *testing.T, chain *cosmos.CosmosChain, height uint64) {
|
||||
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]))
|
||||
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]))
|
||||
require.Equal(t, TxHash(tx), TxHash(block.Block.Data.Txs[i+offset+1]))
|
||||
}
|
||||
}
|
||||
|
||||
@ -403,7 +409,7 @@ func (s *IntegrationTestSuite) setupBroadcaster() {
|
||||
}
|
||||
|
||||
// sniped from here: https://github.com/strangelove-ventures/interchaintest ref: 9341b001214d26be420f1ca1ab0f15bad17faee6
|
||||
func (s *IntegrationTestSuite) keyringDirFromNode() (string) {
|
||||
func (s *IntegrationTestSuite) keyringDirFromNode() string {
|
||||
node := s.chain.(*cosmos.CosmosChain).Nodes()[0]
|
||||
|
||||
// create a temp-dir
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package test
|
||||
package testutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package test
|
||||
package testutils
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
@ -122,7 +122,7 @@ func CreateFreeTx(txCfg client.TxConfig, account Account, nonce, timeout uint64,
|
||||
return CreateTx(txCfg, account, nonce, timeout, msgs, fees...)
|
||||
}
|
||||
|
||||
func CreateRandomTx(txCfg client.TxConfig, account Account, nonce, numberMsgs, timeout uint64, fees ...sdk.Coin) (authsigning.Tx, error) {
|
||||
func CreateRandomTx(txCfg client.TxConfig, account Account, nonce, numberMsgs, timeout uint64, gasLimit uint64, fees ...sdk.Coin) (authsigning.Tx, error) {
|
||||
msgs := make([]sdk.Msg, numberMsgs)
|
||||
for i := 0; i < int(numberMsgs); i++ {
|
||||
msgs[i] = &banktypes.MsgSend{
|
||||
@ -152,11 +152,13 @@ func CreateRandomTx(txCfg client.TxConfig, account Account, nonce, numberMsgs, t
|
||||
|
||||
txBuilder.SetFeeAmount(fees)
|
||||
|
||||
txBuilder.SetGasLimit(gasLimit)
|
||||
|
||||
return txBuilder.GetTx(), nil
|
||||
}
|
||||
|
||||
func CreateRandomTxBz(txCfg client.TxConfig, account Account, nonce, numberMsgs, timeout uint64) ([]byte, error) {
|
||||
tx, err := CreateRandomTx(txCfg, account, nonce, numberMsgs, timeout)
|
||||
func CreateRandomTxBz(txCfg client.TxConfig, account Account, nonce, numberMsgs, timeout, gasLimit uint64) ([]byte, error) {
|
||||
tx, err := CreateRandomTx(txCfg, account, nonce, numberMsgs, timeout, gasLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -194,7 +196,7 @@ func CreateTxWithSigners(txCfg client.TxConfig, nonce, timeout uint64, signers [
|
||||
return txBuilder.GetTx(), nil
|
||||
}
|
||||
|
||||
func CreateAuctionTx(txCfg client.TxConfig, bidder Account, bid sdk.Coin, nonce, timeout uint64, signers []Account) (authsigning.Tx, []authsigning.Tx, error) {
|
||||
func CreateAuctionTx(txCfg client.TxConfig, bidder Account, bid sdk.Coin, nonce, timeout uint64, signers []Account, gasLimit uint64) (authsigning.Tx, []authsigning.Tx, error) {
|
||||
bidMsg := &auctiontypes.MsgAuctionBid{
|
||||
Bidder: bidder.Address.String(),
|
||||
Bid: bid,
|
||||
@ -238,6 +240,8 @@ func CreateAuctionTx(txCfg client.TxConfig, bidder Account, bid sdk.Coin, nonce,
|
||||
|
||||
txBuilder.SetTimeoutHeight(timeout)
|
||||
|
||||
txBuilder.SetGasLimit(gasLimit)
|
||||
|
||||
return txBuilder.GetTx(), txs, nil
|
||||
}
|
||||
|
||||
|
||||
@ -184,7 +184,7 @@ func (suite *KeeperTestSuite) TestValidateBidInfo() {
|
||||
// Create the bundle of transactions ordered by accounts
|
||||
bundle := make([][]byte, 0)
|
||||
for _, acc := range accounts {
|
||||
tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, acc, 0, 1, 100)
|
||||
tx, err := testutils.CreateRandomTx(suite.encCfg.TxConfig, acc, 0, 1, 100, 0)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
txBz, err := suite.encCfg.TxConfig.TxEncoder()(tx)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user