feat: x/blocksdk module integration (#201)

* module proto

* lane proto

* proto-gen

* proto-format

* regenerate

* genesis

* stub

* test

* comment

* new keeper functions

* finalize

* lint fix

* proto format

* encounteredLanes

* make protos

* generate

* generate

* msgs

* msgs test

* msg service

* grpc query

* add module

* add module assertions

* add client

* config

* app

* clean

* wip

* mocks

* integrate into mempool

* lane utils

* wip

* fix and test

* format

* debug logs

* add

* integrate

* format

* fix

* rm

* add

* fmt imports

* better error

* simplify

* simplify

* simplify

* format

* deep copy

* Update block/lane.go

Co-authored-by: Keefer Taylor | Tessellated <keefer@tessellated.io>

---------

Co-authored-by: aljo242 <alex@ingenuity.build>
Co-authored-by: Keefer Taylor | Tessellated <keefer@tessellated.io>
This commit is contained in:
Alex Johnson
2023-11-07 16:22:17 -05:00
committed by GitHub
co-authored by Keefer Taylor | Tessellated aljo242
parent f5457e3e54
commit 41e85e9cfe
16 changed files with 804 additions and 104 deletions
+6
View File
@@ -173,3 +173,9 @@ func (l *BaseLane) TxEncoder() sdk.TxEncoder {
func (l *BaseLane) GetMaxBlockSpace() math.LegacyDec {
return l.cfg.MaxBlockSpace
}
// SetMaxBlockSpace sets the maximum amount of block space that the lane is
// allowed to consume as a percentage of the total block space.
func (l *BaseLane) SetMaxBlockSpace(maxBlockSpace math.LegacyDec) {
l.cfg.MaxBlockSpace = maxBlockSpace
}
+15
View File
@@ -57,6 +57,9 @@ type Lane interface {
// GetMaxBlockSpace returns the max block space for the lane as a relative percentage.
GetMaxBlockSpace() math.LegacyDec
// SetMaxBlockSpace sets the max block space for the lane as a relative percentage.
SetMaxBlockSpace(math.LegacyDec)
// Name returns the name of the lane.
Name() string
@@ -69,3 +72,15 @@ type Lane interface {
// Match determines if a transaction belongs to this lane.
Match(ctx sdk.Context, tx sdk.Tx) bool
}
// FindLane finds a Lanes from in an array of Lanes and returns it and its index if found.
// Returns nil, 0 and false if not found.
func FindLane(lanes []Lane, name string) (lane Lane, index int, found bool) {
for i, lane := range lanes {
if lane.Name() == name {
return lanes[i], i, true
}
}
return nil, 0, false
}
+78
View File
@@ -0,0 +1,78 @@
package block_test
import (
"fmt"
"github.com/skip-mev/block-sdk/block"
"github.com/skip-mev/block-sdk/block/mocks"
)
func (suite *BlockBusterTestSuite) TestFindLane() {
lanes := make([]block.Lane, 30)
cleanup := func() {
for i := range lanes {
_ = lanes[i].Name()
}
}
defer cleanup()
for i := range lanes {
laneMock := mocks.NewLane(suite.T())
laneMock.On("Name").Return(fmt.Sprintf("lane%d", i))
lanes[i] = laneMock
}
type args struct {
lanes []block.Lane
name string
}
tests := []struct {
name string
args args
wantLane block.Lane
wantIndex int
wantFound bool
}{
{
name: "invalid lane not found",
args: args{
lanes: lanes,
name: "invalid",
},
wantFound: false,
},
{
name: "valid lane1",
args: args{
lanes: lanes,
name: "lane1",
},
wantLane: lanes[1],
wantIndex: 1,
wantFound: true,
},
{
name: "valid lane15",
args: args{
lanes: lanes,
name: "lane15",
},
wantLane: lanes[15],
wantIndex: 15,
wantFound: true,
},
}
for _, tc := range tests {
suite.Run(tc.name, func() {
gotLane, gotIndex, gotFound := block.FindLane(tc.args.lanes, tc.args.name)
if tc.wantFound {
suite.Require().True(gotFound)
suite.Require().Equal(tc.wantLane, gotLane)
suite.Require().Equal(tc.wantIndex, gotIndex)
return
}
suite.Require().False(gotFound)
})
}
}
+71 -14
View File
@@ -9,19 +9,27 @@ import (
"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool"
blocksdkmoduletypes "github.com/skip-mev/block-sdk/x/blocksdk/types"
)
var _ Mempool = (*LanedMempool)(nil)
// LaneFetcher defines the interface to get a lane stored in the x/blocksdk module.
type LaneFetcher interface {
GetLane(ctx sdk.Context, id string) (lane blocksdkmoduletypes.Lane, err error)
GetLanes(ctx sdk.Context) []blocksdkmoduletypes.Lane
}
type (
// Mempool defines the Block SDK mempool interface.
Mempool interface {
sdkmempool.Mempool
// Registry returns the mempool's lane registry.
Registry() []Lane
Registry(ctx sdk.Context) ([]Lane, error)
// Contains returns the any of the lanes currently contain the transaction.
// Contains returns true if any of the lanes currently contain the transaction.
Contains(tx sdk.Tx) bool
// GetTxDistribution returns the number of transactions in each lane.
@@ -37,11 +45,17 @@ type (
// according to their priority. The first lane in the registry has the
// highest priority and the last lane has the lowest priority.
registry []Lane
// moduleLaneFetcher is the mempool's interface to read on-chain lane
// information in the x/blocksdk module.
moduleLaneFetcher LaneFetcher
}
)
// NewLanedMempool returns a new Block SDK LanedMempool. The laned mempool is
// comprised of a registry of lanes. Each lane is responsible for selecting
// NewLanedMempool returns a new Block SDK LanedMempool. The laned mempool comprises
//
// a registry of lanes. Each lane is responsible for selecting
//
// transactions according to its own selection logic. The lanes are ordered
// according to their priority. The first lane in the registry has the highest
// priority. Proposals are verified according to the order of the lanes in the
@@ -51,10 +65,11 @@ type (
// attempt to insert, remove transactions from all lanes it belongs to. It is recommended,
// that mutex is set to true when creating the mempool. This will ensure that each
// transaction cannot be inserted into the lanes before it.
func NewLanedMempool(logger log.Logger, mutex bool, lanes ...Lane) Mempool {
func NewLanedMempool(logger log.Logger, mutex bool, laneFetcher LaneFetcher, lanes ...Lane) Mempool {
mempool := &LanedMempool{
logger: logger,
registry: lanes,
logger: logger,
registry: lanes,
moduleLaneFetcher: laneFetcher,
}
if err := mempool.ValidateBasic(); err != nil {
@@ -63,8 +78,10 @@ func NewLanedMempool(logger log.Logger, mutex bool, lanes ...Lane) Mempool {
// Set the ignore list for each lane
if mutex {
registry := mempool.registry
for index, lane := range mempool.registry {
// perform full copy to prevent GC
registry := make([]Lane, len(mempool.registry))
copy(registry, mempool.registry)
for index, lane := range registry {
if index > 0 {
lane.SetIgnoreList(registry[:index])
}
@@ -193,11 +210,6 @@ func (m *LanedMempool) Contains(tx sdk.Tx) (contains bool) {
return false
}
// Registry returns the mempool's lane registry.
func (m *LanedMempool) Registry() []Lane {
return m.registry
}
// ValidateBasic validates the mempools configuration. ValidateBasic ensures
// the following:
// - The sum of the lane max block space percentages is less than or equal to 1.
@@ -225,5 +237,50 @@ func (m *LanedMempool) ValidateBasic() error {
return fmt.Errorf("sum of total block space percentages will be less than 1")
}
if m.moduleLaneFetcher == nil {
return fmt.Errorf("moduleLaneFetcher muset be set on mempool")
}
return nil
}
// Registry returns the mempool's lane registry.
func (m *LanedMempool) Registry(ctx sdk.Context) (newRegistry []Lane, err error) {
if m.moduleLaneFetcher == nil {
return m.registry, fmt.Errorf("module lane fetcher not set")
}
// TODO add a last block updated check ?
// potential future optimization
chainLanes := m.moduleLaneFetcher.GetLanes(ctx)
// order lanes and populate the necessary fields (maxBlockSize, etc)
m.registry, err = m.OrderLanes(chainLanes)
return m.registry, err
}
func (m *LanedMempool) OrderLanes(chainLanes []blocksdkmoduletypes.Lane) (orderedLanes []Lane, err error) {
orderedLanes = make([]Lane, len(chainLanes))
for _, chainLane := range chainLanes {
// panic protect
if chainLane.GetOrder() >= uint64(len(orderedLanes)) {
return orderedLanes, fmt.Errorf("lane order %d out of bounds, invalid configuration", chainLane.GetOrder())
}
_, index, found := FindLane(m.registry, chainLane.Id)
if !found {
return orderedLanes, fmt.Errorf("lane %s not found in registry, invalid configuration", chainLane.Id)
}
lane := m.registry[index]
lane.SetMaxBlockSpace(chainLane.MaxBlockSpace)
orderedLanes[chainLane.GetOrder()] = lane
// remove found lane from registry lanes for quicker find()
m.registry[index] = m.registry[len(m.registry)-1] // Copy last element to index i.
m.registry[len(m.registry)-1] = nil // Erase last element (write zero value).
m.registry = m.registry[:len(m.registry)-1] // Truncate slice.
}
return orderedLanes, nil
}
+298 -3
View File
@@ -5,6 +5,10 @@ import (
"testing"
"time"
blocksdkmoduletypes "github.com/skip-mev/block-sdk/x/blocksdk/types"
"github.com/skip-mev/block-sdk/block/mocks"
"cosmossdk.io/log"
"cosmossdk.io/math"
storetypes "cosmossdk.io/store/types"
@@ -35,8 +39,14 @@ type BlockBusterTestSuite struct {
freeLane *free.FreeLane
gasTokenDenom string
lanes []block.Lane
mempool block.Mempool
// sdk module lanes
mevSDKLane blocksdkmoduletypes.Lane
baseSDKLane blocksdkmoduletypes.Lane
freeSDKLane blocksdkmoduletypes.Lane
chainLanes []blocksdkmoduletypes.Lane
lanes []block.Lane
mempool block.Mempool
// account set up
accounts []testutils.Account
@@ -73,6 +83,12 @@ func (suite *BlockBusterTestSuite) SetupTest() {
mev.NewDefaultAuctionFactory(suite.encodingConfig.TxConfig.TxDecoder(), signer_extraction.NewDefaultAdapter()),
)
suite.mevSDKLane = blocksdkmoduletypes.Lane{
Id: suite.mevLane.Name(),
MaxBlockSpace: suite.mevLane.GetMaxBlockSpace(),
Order: 0,
}
// Free lane set up
freeConfig := base.LaneConfig{
Logger: log.NewNopLogger(),
@@ -88,6 +104,12 @@ func (suite *BlockBusterTestSuite) SetupTest() {
free.DefaultMatchHandler(),
)
suite.freeSDKLane = blocksdkmoduletypes.Lane{
Id: suite.freeLane.Name(),
MaxBlockSpace: suite.freeLane.GetMaxBlockSpace(),
Order: 1,
}
// Base lane set up
baseConfig := base.LaneConfig{
Logger: log.NewNopLogger(),
@@ -101,9 +123,25 @@ func (suite *BlockBusterTestSuite) SetupTest() {
baseConfig,
)
suite.baseSDKLane = blocksdkmoduletypes.Lane{
Id: suite.baseLane.Name(),
MaxBlockSpace: suite.baseLane.GetMaxBlockSpace(),
Order: 2,
}
// Mempool set up
suite.lanes = []block.Lane{suite.mevLane, suite.freeLane, suite.baseLane}
suite.mempool = block.NewLanedMempool(log.NewTestLogger(suite.T()), true, suite.lanes...)
suite.chainLanes = []blocksdkmoduletypes.Lane{suite.mevSDKLane, suite.freeSDKLane, suite.baseSDKLane}
suite.mempool = block.NewLanedMempool(
log.NewTestLogger(suite.T()),
true,
mocks.NewMockLaneFetcher(func() (blocksdkmoduletypes.Lane, error) {
return suite.baseSDKLane, nil
}, func() []blocksdkmoduletypes.Lane {
return suite.chainLanes
}),
suite.lanes...,
)
// Accounts set up
suite.accounts = testutils.RandomAccounts(suite.random, 10)
@@ -376,3 +414,260 @@ func (suite *BlockBusterTestSuite) fillFreeLane(numTxs int) {
suite.Require().NoError(suite.mempool.Insert(suite.ctx, tx))
}
}
func (suite *BlockBusterTestSuite) TestLanedMempool_Registry() {
tests := []struct {
name string
chainLanes []blocksdkmoduletypes.Lane
registryLanes []block.Lane
expectedNewRegistry []block.Lane
wantErr bool
}{
{
name: "invalid lanes in chain",
chainLanes: []blocksdkmoduletypes.Lane{
suite.mevSDKLane, // order = 0
suite.baseSDKLane, // order = 2
},
registryLanes: []block.Lane{
suite.freeLane,
suite.mevLane,
suite.baseLane,
},
wantErr: true,
},
{
name: "invalid duplicate lanes in chain",
chainLanes: []blocksdkmoduletypes.Lane{
suite.mevSDKLane, // order = 0
suite.baseSDKLane, // order = 2
suite.baseSDKLane, // order = 2
},
registryLanes: []block.Lane{
suite.freeLane,
suite.mevLane,
suite.baseLane,
},
wantErr: true,
},
{
name: "invalid lanes in registry",
chainLanes: []blocksdkmoduletypes.Lane{
suite.mevSDKLane, // order = 0
suite.freeSDKLane, // order = 1
suite.baseSDKLane, // order = 2
},
registryLanes: []block.Lane{
suite.freeLane,
suite.baseLane,
},
wantErr: true,
},
{
name: "invalid duplicate lanes in registry",
chainLanes: []blocksdkmoduletypes.Lane{
suite.mevSDKLane, // order = 0
suite.freeSDKLane, // order = 1
suite.baseSDKLane, // order = 2
},
registryLanes: []block.Lane{
suite.freeLane,
suite.baseLane,
suite.baseLane,
},
wantErr: true,
},
{
name: "valid reorder",
chainLanes: []blocksdkmoduletypes.Lane{
suite.mevSDKLane, // order = 0
suite.freeSDKLane, // order = 1
suite.baseSDKLane, // order = 2
},
registryLanes: []block.Lane{
suite.freeLane,
suite.mevLane,
suite.baseLane,
},
expectedNewRegistry: []block.Lane{
suite.mevLane,
suite.freeLane,
suite.baseLane,
},
wantErr: false,
},
{
name: "valid no reorder",
chainLanes: []blocksdkmoduletypes.Lane{
suite.mevSDKLane, // order = 0
suite.freeSDKLane, // order = 1
suite.baseSDKLane, // order = 2
},
registryLanes: []block.Lane{
suite.mevLane,
suite.freeLane,
suite.baseLane,
},
expectedNewRegistry: []block.Lane{
suite.mevLane,
suite.freeLane,
suite.baseLane,
},
wantErr: false,
},
}
for _, tc := range tests {
suite.Run(tc.name, func() {
// setup mock mempool
mempool := block.NewLanedMempool(
log.NewTestLogger(suite.T()),
true,
mocks.NewMockLaneFetcher(func() (blocksdkmoduletypes.Lane, error) {
return blocksdkmoduletypes.Lane{}, nil
}, func() []blocksdkmoduletypes.Lane {
return tc.chainLanes
}),
tc.registryLanes...,
)
gotOrderedLanes, err := mempool.Registry(suite.ctx)
if tc.wantErr {
suite.Require().Error(err)
return
}
suite.Require().NoError(err)
suite.Require().Equal(tc.expectedNewRegistry, gotOrderedLanes)
})
}
}
func (suite *BlockBusterTestSuite) TestLanedMempool_OrderLanes() {
tests := []struct {
name string
chainLanes []blocksdkmoduletypes.Lane
registryLanes []block.Lane
expectedOrderedLane []block.Lane
wantErr bool
}{
{
name: "invalid lanes in chain",
chainLanes: []blocksdkmoduletypes.Lane{
suite.mevSDKLane, // order = 0
suite.baseSDKLane, // order = 2
},
registryLanes: []block.Lane{
suite.freeLane,
suite.mevLane,
suite.baseLane,
},
wantErr: true,
},
{
name: "invalid duplicate lanes in chain",
chainLanes: []blocksdkmoduletypes.Lane{
suite.mevSDKLane, // order = 0
suite.baseSDKLane, // order = 2
suite.baseSDKLane, // order = 2
},
registryLanes: []block.Lane{
suite.freeLane,
suite.mevLane,
suite.baseLane,
},
wantErr: true,
},
{
name: "invalid lanes in registry",
chainLanes: []blocksdkmoduletypes.Lane{
suite.mevSDKLane, // order = 0
suite.freeSDKLane, // order = 1
suite.baseSDKLane, // order = 2
},
registryLanes: []block.Lane{
suite.freeLane,
suite.baseLane,
},
wantErr: true,
},
{
name: "invalid duplicate lanes in registry",
chainLanes: []blocksdkmoduletypes.Lane{
suite.mevSDKLane, // order = 0
suite.freeSDKLane, // order = 1
suite.baseSDKLane, // order = 2
},
registryLanes: []block.Lane{
suite.freeLane,
suite.baseLane,
suite.baseLane,
},
wantErr: true,
},
{
name: "valid reorder",
chainLanes: []blocksdkmoduletypes.Lane{
suite.mevSDKLane, // order = 0
suite.freeSDKLane, // order = 1
suite.baseSDKLane, // order = 2
},
registryLanes: []block.Lane{
suite.freeLane,
suite.mevLane,
suite.baseLane,
},
expectedOrderedLane: []block.Lane{
suite.mevLane,
suite.freeLane,
suite.baseLane,
},
wantErr: false,
},
{
name: "valid no reorder",
chainLanes: []blocksdkmoduletypes.Lane{
suite.mevSDKLane, // order = 0
suite.freeSDKLane, // order = 1
suite.baseSDKLane, // order = 2
},
registryLanes: []block.Lane{
suite.mevLane,
suite.freeLane,
suite.baseLane,
},
expectedOrderedLane: []block.Lane{
suite.mevLane,
suite.freeLane,
suite.baseLane,
},
wantErr: false,
},
}
for _, tc := range tests {
suite.Run(tc.name, func() {
// setup mock mempool
mempool := block.NewLanedMempool(
log.NewTestLogger(suite.T()),
true,
mocks.NewMockLaneFetcher(func() (blocksdkmoduletypes.Lane, error) {
return blocksdkmoduletypes.Lane{}, nil
}, func() []blocksdkmoduletypes.Lane {
return []blocksdkmoduletypes.Lane{}
}),
tc.registryLanes...,
)
lanedMempool, ok := mempool.(*block.LanedMempool)
suite.Require().True(ok)
gotOrderedLanes, err := lanedMempool.OrderLanes(tc.chainLanes)
if tc.wantErr {
suite.Require().Error(err)
return
}
suite.Require().NoError(err)
suite.Require().Equal(tc.expectedOrderedLane, gotOrderedLanes)
})
}
}
+18 -3
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.30.1. DO NOT EDIT.
// Code generated by mockery v0.0.0-dev. DO NOT EDIT.
package mocks
@@ -24,17 +24,27 @@ type Lane struct {
}
// 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 {
func (_m *Lane) Compare(ctx types.Context, this types.Tx, other types.Tx) (int, error) {
ret := _m.Called(ctx, this, other)
var r0 int
var r1 error
if rf, ok := ret.Get(0).(func(types.Context, types.Tx, types.Tx) (int, error)); ok {
return rf(ctx, this, other)
}
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
if rf, ok := ret.Get(1).(func(types.Context, types.Tx, types.Tx) error); ok {
r1 = rf(ctx, this, other)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Contains provides a mock function with given fields: tx
@@ -209,6 +219,11 @@ func (_m *Lane) SetIgnoreList(ignoreList []block.Lane) {
_m.Called(ignoreList)
}
// SetMaxBlockSpace provides a mock function with given fields: _a0
func (_m *Lane) SetMaxBlockSpace(_a0 math.LegacyDec) {
_m.Called(_a0)
}
// 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 {
+35
View File
@@ -0,0 +1,35 @@
package mocks
import (
sdk "github.com/cosmos/cosmos-sdk/types"
blocksdkmoduletypes "github.com/skip-mev/block-sdk/x/blocksdk/types"
)
type MockLaneFetcher struct {
getLaneHandler func() (blocksdkmoduletypes.Lane, error)
getLanesHandler func() []blocksdkmoduletypes.Lane
}
func NewMockLaneFetcher(getLane func() (blocksdkmoduletypes.Lane, error), getLanes func() []blocksdkmoduletypes.Lane) MockLaneFetcher {
return MockLaneFetcher{
getLaneHandler: getLane,
getLanesHandler: getLanes,
}
}
func (m *MockLaneFetcher) SetGetLaneHandler(h func() (blocksdkmoduletypes.Lane, error)) {
m.getLaneHandler = h
}
func (m MockLaneFetcher) GetLane(_ sdk.Context, _ string) (blocksdkmoduletypes.Lane, error) {
return m.getLaneHandler()
}
func (m *MockLaneFetcher) SetGetLanesHandler(h func() []blocksdkmoduletypes.Lane) {
m.getLanesHandler = h
}
func (m MockLaneFetcher) GetLanes(_ sdk.Context) []blocksdkmoduletypes.Lane {
return m.getLanesHandler()
}
+13 -3
View File
@@ -1,4 +1,4 @@
// Code generated by mockery v2.30.1. DO NOT EDIT.
// Code generated by mockery v0.0.0-dev. DO NOT EDIT.
package mocks
@@ -17,17 +17,27 @@ type LaneMempool struct {
}
// 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 {
func (_m *LaneMempool) Compare(ctx types.Context, this types.Tx, other types.Tx) (int, error) {
ret := _m.Called(ctx, this, other)
var r0 int
var r1 error
if rf, ok := ret.Get(0).(func(types.Context, types.Tx, types.Tx) (int, error)); ok {
return rf(ctx, this, other)
}
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
if rf, ok := ret.Get(1).(func(types.Context, types.Tx, types.Tx) error); ok {
r1 = rf(ctx, this, other)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Contains provides a mock function with given fields: tx