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:
@@ -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 {
|
||||
+68
-20
@@ -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
|
||||
|
||||
+55
-65
@@ -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
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
// 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, partialProposal[index-1], tx) == -1 {
|
||||
return fmt.Errorf("transaction at index %d has a higher priority than %d", index, index-1)
|
||||
}
|
||||
|
||||
// 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
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
+12
-20
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
+20
-17
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
+6
-5
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+45
-26
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user