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:
David Terpay
2023-09-28 11:10:13 -04:00
committed by GitHub
parent 3abfde4f34
commit b9d6761776
38 changed files with 3702 additions and 1096 deletions
-43
View File
@@ -1,43 +0,0 @@
package utils
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 {
decorator sdk.AnteDecorator
lanes []Lane
}
)
// NewIgnoreDecorator returns a new IgnoreDecorator instance.
func NewIgnoreDecorator(decorator sdk.AnteDecorator, lanes ...Lane) *IgnoreDecorator {
return &IgnoreDecorator{
decorator: decorator,
lanes: lanes,
}
}
// AnteHandle implements the sdk.AnteDecorator interface. If the transaction belongs to
// one of the lanes, the next AnteHandler is called. Otherwise, the decorator's AnteHandler
// is called.
func (sd IgnoreDecorator) AnteHandle(
ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler,
) (sdk.Context, error) {
for _, lane := range sd.lanes {
if lane.Match(ctx, tx) {
return next(ctx, tx, simulate)
}
}
return sd.decorator.AnteHandle(ctx, tx, simulate, next)
}
-257
View File
@@ -1,257 +0,0 @@
// Code generated by mockery v2.30.1. DO NOT EDIT.
package mocks
import (
context "context"
block "github.com/skip-mev/block-sdk/block"
log "cosmossdk.io/log"
math "cosmossdk.io/math"
mempool "github.com/cosmos/cosmos-sdk/types/mempool"
mock "github.com/stretchr/testify/mock"
types "github.com/cosmos/cosmos-sdk/types"
)
// Lane is an autogenerated mock type for the Lane type
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)
var r0 int
if rf, ok := ret.Get(0).(func(types.Context, types.Tx, types.Tx) int); ok {
r0 = rf(ctx, this, other)
} else {
r0 = ret.Get(0).(int)
}
return r0
}
// Contains provides a mock function with given fields: tx
func (_m *Lane) Contains(tx types.Tx) bool {
ret := _m.Called(tx)
var r0 bool
if rf, ok := ret.Get(0).(func(types.Tx) bool); ok {
r0 = rf(tx)
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// CountTx provides a mock function with given fields:
func (_m *Lane) CountTx() int {
ret := _m.Called()
var r0 int
if rf, ok := ret.Get(0).(func() int); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(int)
}
return r0
}
// GetMaxBlockSpace provides a mock function with given fields:
func (_m *Lane) GetMaxBlockSpace() math.LegacyDec {
ret := _m.Called()
var r0 math.LegacyDec
if rf, ok := ret.Get(0).(func() math.LegacyDec); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(math.LegacyDec)
}
return r0
}
// Insert provides a mock function with given fields: _a0, _a1
func (_m *Lane) Insert(_a0 context.Context, _a1 types.Tx) error {
ret := _m.Called(_a0, _a1)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, types.Tx) error); ok {
r0 = rf(_a0, _a1)
} else {
r0 = ret.Error(0)
}
return r0
}
// Logger provides a mock function with given fields:
func (_m *Lane) Logger() log.Logger {
ret := _m.Called()
var r0 log.Logger
if rf, ok := ret.Get(0).(func() log.Logger); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(log.Logger)
}
}
return r0
}
// Match provides a mock function with given fields: ctx, tx
func (_m *Lane) Match(ctx types.Context, tx types.Tx) bool {
ret := _m.Called(ctx, tx)
var r0 bool
if rf, ok := ret.Get(0).(func(types.Context, types.Tx) bool); ok {
r0 = rf(ctx, tx)
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// Name provides a mock function with given fields:
func (_m *Lane) Name() string {
ret := _m.Called()
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(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)
var r0 block.BlockProposal
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, block.BlockProposal, int64, block.PrepareLanesHandler) block.BlockProposal); ok {
r0 = rf(ctx, proposal, maxTxBytes, next)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(block.BlockProposal)
}
}
if rf, ok := ret.Get(1).(func(types.Context, block.BlockProposal, int64, block.PrepareLanesHandler) error); ok {
r1 = rf(ctx, proposal, maxTxBytes, next)
} else {
r1 = ret.Error(1)
}
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)
var r0 types.Context
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, []types.Tx, block.ProcessLanesHandler) types.Context); ok {
r0 = rf(ctx, proposalTxs, next)
} else {
r0 = ret.Get(0).(types.Context)
}
if rf, ok := ret.Get(1).(func(types.Context, []types.Tx, block.ProcessLanesHandler) error); ok {
r1 = rf(ctx, proposalTxs, next)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Remove provides a mock function with given fields: _a0
func (_m *Lane) Remove(_a0 types.Tx) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(types.Tx) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// Select provides a mock function with given fields: _a0, _a1
func (_m *Lane) Select(_a0 context.Context, _a1 [][]byte) mempool.Iterator {
ret := _m.Called(_a0, _a1)
var r0 mempool.Iterator
if rf, ok := ret.Get(0).(func(context.Context, [][]byte) mempool.Iterator); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(mempool.Iterator)
}
}
return r0
}
// SetAnteHandler provides a mock function with given fields: antehander
func (_m *Lane) SetAnteHandler(antehander types.AnteHandler) {
_m.Called(antehander)
}
// SetIgnoreList provides a mock function with given fields: ignoreList
func (_m *Lane) SetIgnoreList(ignoreList []block.Lane) {
_m.Called(ignoreList)
}
// NewLane creates a new instance of Lane. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewLane(t interface {
mock.TestingT
Cleanup(func())
},
) *Lane {
mock := &Lane{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
-118
View File
@@ -1,118 +0,0 @@
// Code generated by mockery v2.30.1. DO NOT EDIT.
package mocks
import (
context "context"
mempool "github.com/cosmos/cosmos-sdk/types/mempool"
mock "github.com/stretchr/testify/mock"
types "github.com/cosmos/cosmos-sdk/types"
)
// LaneMempool is an autogenerated mock type for the LaneMempool type
type LaneMempool struct {
mock.Mock
}
// Compare provides a mock function with given fields: ctx, this, other
func (_m *LaneMempool) Compare(ctx types.Context, this types.Tx, other types.Tx) int {
ret := _m.Called(ctx, this, other)
var r0 int
if rf, ok := ret.Get(0).(func(types.Context, types.Tx, types.Tx) int); ok {
r0 = rf(ctx, this, other)
} else {
r0 = ret.Get(0).(int)
}
return r0
}
// Contains provides a mock function with given fields: tx
func (_m *LaneMempool) Contains(tx types.Tx) bool {
ret := _m.Called(tx)
var r0 bool
if rf, ok := ret.Get(0).(func(types.Tx) bool); ok {
r0 = rf(tx)
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// CountTx provides a mock function with given fields:
func (_m *LaneMempool) CountTx() int {
ret := _m.Called()
var r0 int
if rf, ok := ret.Get(0).(func() int); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(int)
}
return r0
}
// Insert provides a mock function with given fields: _a0, _a1
func (_m *LaneMempool) Insert(_a0 context.Context, _a1 types.Tx) error {
ret := _m.Called(_a0, _a1)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, types.Tx) error); ok {
r0 = rf(_a0, _a1)
} else {
r0 = ret.Error(0)
}
return r0
}
// Remove provides a mock function with given fields: _a0
func (_m *LaneMempool) Remove(_a0 types.Tx) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(types.Tx) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// Select provides a mock function with given fields: _a0, _a1
func (_m *LaneMempool) Select(_a0 context.Context, _a1 [][]byte) mempool.Iterator {
ret := _m.Called(_a0, _a1)
var r0 mempool.Iterator
if rf, ok := ret.Get(0).(func(context.Context, [][]byte) mempool.Iterator); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(mempool.Iterator)
}
}
return r0
}
// NewLaneMempool creates a new instance of LaneMempool. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewLaneMempool(t interface {
mock.TestingT
Cleanup(func())
},
) *LaneMempool {
mock := &LaneMempool{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
+45 -26
View File
@@ -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()
}
-78
View File
@@ -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)
}
})
}
}