feat(BB): Free Lane (#167)
This commit is contained in:
parent
b7780a3140
commit
cfe2147d52
47
blockbuster/lanes/free/factory.go
Normal file
47
blockbuster/lanes/free/factory.go
Normal file
@ -0,0 +1,47 @@
|
||||
package free
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
)
|
||||
|
||||
type (
|
||||
// Factory defines the interface for processing free transactions. It is
|
||||
// a wrapper around all of the functionality that each application chain must implement
|
||||
// in order for free processing to work.
|
||||
Factory interface {
|
||||
// IsFreeTx defines a function that checks if a transaction qualifies as free.
|
||||
IsFreeTx(tx sdk.Tx) bool
|
||||
}
|
||||
|
||||
// DefaultFreeFactory defines a default implmentation for the free factory interface for processing free transactions.
|
||||
DefaultFreeFactory struct {
|
||||
txDecoder sdk.TxDecoder
|
||||
}
|
||||
)
|
||||
|
||||
var _ Factory = (*DefaultFreeFactory)(nil)
|
||||
|
||||
// NewDefaultFreeFactory returns a default free factory interface implementation.
|
||||
func NewDefaultFreeFactory(txDecoder sdk.TxDecoder) Factory {
|
||||
return &DefaultFreeFactory{
|
||||
txDecoder: txDecoder,
|
||||
}
|
||||
}
|
||||
|
||||
// IsFreeTx defines a default function that checks if a transaction is free. In this case,
|
||||
// any transaction that is a delegation/redelegation transaction is free.
|
||||
func (config *DefaultFreeFactory) IsFreeTx(tx sdk.Tx) bool {
|
||||
for _, msg := range tx.GetMsgs() {
|
||||
switch msg.(type) {
|
||||
case *types.MsgDelegate:
|
||||
return true
|
||||
case *types.MsgBeginRedelegate:
|
||||
return true
|
||||
case *types.MsgCancelUnbondingDelegation:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
42
blockbuster/lanes/free/lane.go
Normal file
42
blockbuster/lanes/free/lane.go
Normal file
@ -0,0 +1,42 @@
|
||||
package free
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/skip-mev/pob/blockbuster"
|
||||
"github.com/skip-mev/pob/blockbuster/lanes/base"
|
||||
)
|
||||
|
||||
const (
|
||||
// LaneName defines the name of the free lane.
|
||||
LaneName = "free"
|
||||
)
|
||||
|
||||
var _ blockbuster.Lane = (*Lane)(nil)
|
||||
|
||||
// FreeLane defines the lane that is responsible for processing free transactions.
|
||||
type Lane struct {
|
||||
*base.DefaultLane
|
||||
Factory
|
||||
}
|
||||
|
||||
// NewFreeLane returns a new free lane.
|
||||
func NewFreeLane(cfg blockbuster.BaseLaneConfig, factory Factory) *Lane {
|
||||
if err := cfg.ValidateBasic(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &Lane{
|
||||
DefaultLane: base.NewDefaultLane(cfg),
|
||||
Factory: factory,
|
||||
}
|
||||
}
|
||||
|
||||
// Match returns true if the transaction is a free transaction.
|
||||
func (l *Lane) Match(tx sdk.Tx) bool {
|
||||
return l.IsFreeTx(tx)
|
||||
}
|
||||
|
||||
// Name returns the name of the free lane.
|
||||
func (l *Lane) Name() string {
|
||||
return LaneName
|
||||
}
|
||||
43
blockbuster/utils/ante.go
Normal file
43
blockbuster/utils/ante.go
Normal file
@ -0,0 +1,43 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
type (
|
||||
// Lane defines the required functionality for a lane. The ignore decorator
|
||||
// will check if a transaction belongs to a lane by calling the Match function.
|
||||
Lane interface {
|
||||
Match(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(tx) {
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
}
|
||||
|
||||
return sd.decorator.AnteHandle(ctx, tx, simulate, next)
|
||||
}
|
||||
@ -4,6 +4,7 @@ import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/ante"
|
||||
"github.com/skip-mev/pob/blockbuster"
|
||||
"github.com/skip-mev/pob/blockbuster/utils"
|
||||
builderante "github.com/skip-mev/pob/x/builder/ante"
|
||||
builderkeeper "github.com/skip-mev/pob/x/builder/keeper"
|
||||
)
|
||||
@ -15,6 +16,7 @@ type POBHandlerOptions struct {
|
||||
TxDecoder sdk.TxDecoder
|
||||
TxEncoder sdk.TxEncoder
|
||||
BuilderKeeper builderkeeper.Keeper
|
||||
FreeLane blockbuster.Lane
|
||||
}
|
||||
|
||||
// NewPOBAnteHandler wraps all of the default Cosmos SDK AnteDecorators with the POB AnteHandler.
|
||||
@ -38,11 +40,14 @@ func NewPOBAnteHandler(options POBHandlerOptions) sdk.AnteHandler {
|
||||
ante.NewTxTimeoutHeightDecorator(),
|
||||
ante.NewValidateMemoDecorator(options.BaseOptions.AccountKeeper),
|
||||
ante.NewConsumeGasForTxSizeDecorator(options.BaseOptions.AccountKeeper),
|
||||
ante.NewDeductFeeDecorator(
|
||||
options.BaseOptions.AccountKeeper,
|
||||
options.BaseOptions.BankKeeper,
|
||||
options.BaseOptions.FeegrantKeeper,
|
||||
options.BaseOptions.TxFeeChecker,
|
||||
utils.NewIgnoreDecorator(
|
||||
ante.NewDeductFeeDecorator(
|
||||
options.BaseOptions.AccountKeeper,
|
||||
options.BaseOptions.BankKeeper,
|
||||
options.BaseOptions.FeegrantKeeper,
|
||||
options.BaseOptions.TxFeeChecker,
|
||||
),
|
||||
options.FreeLane,
|
||||
),
|
||||
ante.NewSetPubKeyDecorator(options.BaseOptions.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators
|
||||
ante.NewValidateSigCountDecorator(options.BaseOptions.AccountKeeper),
|
||||
|
||||
@ -71,6 +71,7 @@ import (
|
||||
"github.com/skip-mev/pob/blockbuster/abci"
|
||||
"github.com/skip-mev/pob/blockbuster/lanes/auction"
|
||||
"github.com/skip-mev/pob/blockbuster/lanes/base"
|
||||
"github.com/skip-mev/pob/blockbuster/lanes/free"
|
||||
buildermodule "github.com/skip-mev/pob/x/builder"
|
||||
builderkeeper "github.com/skip-mev/pob/x/builder/keeper"
|
||||
)
|
||||
@ -287,10 +288,17 @@ func New(
|
||||
auction.NewDefaultAuctionFactory(app.txConfig.TxDecoder()),
|
||||
)
|
||||
|
||||
// Free lane allows transactions to be included in the next block for free.
|
||||
freeLane := free.NewFreeLane(
|
||||
config,
|
||||
free.NewDefaultFreeFactory(app.txConfig.TxDecoder()),
|
||||
)
|
||||
|
||||
// Default lane accepts all other transactions.
|
||||
defaultLane := base.NewDefaultLane(config)
|
||||
lanes := []blockbuster.Lane{
|
||||
tobLane,
|
||||
freeLane,
|
||||
defaultLane,
|
||||
}
|
||||
|
||||
@ -311,6 +319,7 @@ func New(
|
||||
BuilderKeeper: app.BuilderKeeper,
|
||||
TxDecoder: app.txConfig.TxDecoder(),
|
||||
TxEncoder: app.txConfig.TxEncoder(),
|
||||
FreeLane: freeLane,
|
||||
TOBLane: tobLane,
|
||||
Mempool: mempool,
|
||||
}
|
||||
|
||||
@ -68,7 +68,7 @@ func (s *IntegrationTestSuite) TestValidBids() {
|
||||
bundleHashes[0]: true,
|
||||
bundleHashes[1]: true,
|
||||
}
|
||||
s.verifyBlock(height+1, bundleHashes, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+1, bundleHashes, expectedExecution)
|
||||
|
||||
// Ensure that the escrow account has the correct balance
|
||||
expectedEscrowFee := s.calculateProposerEscrowSplit(bid)
|
||||
@ -124,7 +124,7 @@ func (s *IntegrationTestSuite) TestValidBids() {
|
||||
expectedExecution[hash] = true
|
||||
}
|
||||
|
||||
s.verifyBlock(height+1, bundleHashes, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+1, bundleHashes, expectedExecution)
|
||||
|
||||
// Ensure that the escrow account has the correct balance
|
||||
expectedEscrowFee := s.calculateProposerEscrowSplit(bid)
|
||||
@ -183,7 +183,7 @@ func (s *IntegrationTestSuite) TestValidBids() {
|
||||
expectedExecution[hash] = true
|
||||
}
|
||||
|
||||
s.verifyBlock(height+1, bundleHashes3, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+1, bundleHashes3, expectedExecution)
|
||||
|
||||
// Ensure that the escrow account has the correct balance
|
||||
expectedEscrowFee := s.calculateProposerEscrowSplit(bid3)
|
||||
@ -243,7 +243,7 @@ func (s *IntegrationTestSuite) TestValidBids() {
|
||||
expectedExecution[hash] = true
|
||||
}
|
||||
|
||||
s.verifyBlock(height+1, bundleHashes, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+1, bundleHashes, expectedExecution)
|
||||
|
||||
// Ensure that the escrow account has the correct balance
|
||||
expectedEscrowFee := s.calculateProposerEscrowSplit(bid)
|
||||
@ -302,7 +302,7 @@ func (s *IntegrationTestSuite) TestValidBids() {
|
||||
expectedExecution[hash] = true
|
||||
}
|
||||
|
||||
s.verifyBlock(height+1, bundleHashes, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+1, bundleHashes, expectedExecution)
|
||||
|
||||
// Ensure that the escrow account has the correct balance
|
||||
expectedEscrowFee := s.calculateProposerEscrowSplit(bid)
|
||||
@ -405,7 +405,7 @@ func (s *IntegrationTestSuite) TestMultipleBids() {
|
||||
}
|
||||
|
||||
// Pass in nil since we don't know the order of transactions that ill be executed
|
||||
s.verifyBlock(height+2, nil, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+2, nil, expectedExecution)
|
||||
|
||||
// Ensure that the escrow account has the correct balance (both bids should have been extracted by this point)
|
||||
expectedEscrowFee := s.calculateProposerEscrowSplit(bid).Add(s.calculateProposerEscrowSplit(bid2))
|
||||
@ -459,7 +459,7 @@ func (s *IntegrationTestSuite) TestMultipleBids() {
|
||||
expectedExecution[hash] = true
|
||||
}
|
||||
|
||||
s.verifyBlock(height+1, bundleHashes2, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+1, bundleHashes2, expectedExecution)
|
||||
|
||||
// Wait for a block to be created
|
||||
s.waitForABlock()
|
||||
@ -473,7 +473,7 @@ func (s *IntegrationTestSuite) TestMultipleBids() {
|
||||
expectedExecution[hash] = true
|
||||
}
|
||||
|
||||
s.verifyBlock(height+2, bundleHashes, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+2, bundleHashes, expectedExecution)
|
||||
|
||||
// Ensure that the escrow account has the correct balance (both bids should have been extracted by this point)
|
||||
expectedEscrowFee := s.calculateProposerEscrowSplit(bid).Add(s.calculateProposerEscrowSplit(bid2))
|
||||
@ -515,7 +515,7 @@ func (s *IntegrationTestSuite) TestMultipleBids() {
|
||||
bundleHashes[1]: true,
|
||||
bundleHashes2[0]: false,
|
||||
}
|
||||
s.verifyBlock(height+1, bundleHashes, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+1, bundleHashes, expectedExecution)
|
||||
|
||||
// Ensure that the escrow account has the correct balance
|
||||
expectedEscrowFee := s.calculateProposerEscrowSplit(bid)
|
||||
@ -523,7 +523,7 @@ func (s *IntegrationTestSuite) TestMultipleBids() {
|
||||
|
||||
// Wait another block to make sure the second bid is not executed
|
||||
s.waitForABlock()
|
||||
s.verifyBlock(height+2, bundleHashes2, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+2, bundleHashes2, expectedExecution)
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -561,7 +561,7 @@ func (s *IntegrationTestSuite) TestMultipleBids() {
|
||||
bundleHashes[1]: true,
|
||||
bundleHashes2[0]: false,
|
||||
}
|
||||
s.verifyBlock(height+1, bundleHashes, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+1, bundleHashes, expectedExecution)
|
||||
|
||||
// Ensure that the escrow account has the correct balance
|
||||
expectedEscrowFee := s.calculateProposerEscrowSplit(bid)
|
||||
@ -569,7 +569,7 @@ func (s *IntegrationTestSuite) TestMultipleBids() {
|
||||
|
||||
// Wait another block to make sure the second bid is not executed
|
||||
s.waitForABlock()
|
||||
s.verifyBlock(height+2, bundleHashes2, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+2, bundleHashes2, expectedExecution)
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -607,7 +607,7 @@ func (s *IntegrationTestSuite) TestMultipleBids() {
|
||||
bundleHashes2[0]: true,
|
||||
bundleHashes2[1]: true,
|
||||
}
|
||||
s.verifyBlock(height+1, bundleHashes2, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+1, bundleHashes2, expectedExecution)
|
||||
|
||||
// Ensure that the escrow account has the correct balance
|
||||
expectedEscrowFee := s.calculateProposerEscrowSplit(bid2)
|
||||
@ -615,7 +615,7 @@ func (s *IntegrationTestSuite) TestMultipleBids() {
|
||||
|
||||
// Wait for a block to be created and ensure that the first bid was not executed
|
||||
s.waitForABlock()
|
||||
s.verifyBlock(height+2, bundleHashes, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+2, bundleHashes, expectedExecution)
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -653,7 +653,7 @@ func (s *IntegrationTestSuite) TestMultipleBids() {
|
||||
bundleHashes2[0]: true,
|
||||
bundleHashes2[1]: true,
|
||||
}
|
||||
s.verifyBlock(height+1, bundleHashes2, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+1, bundleHashes2, expectedExecution)
|
||||
|
||||
// Ensure that the escrow account has the correct balance
|
||||
expectedEscrowFee := s.calculateProposerEscrowSplit(bid2)
|
||||
@ -661,7 +661,7 @@ func (s *IntegrationTestSuite) TestMultipleBids() {
|
||||
|
||||
// Wait for a block to be created and ensure that the first bid was not executed
|
||||
s.waitForABlock()
|
||||
s.verifyBlock(height+2, bundleHashes, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+2, bundleHashes, expectedExecution)
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -705,7 +705,7 @@ func (s *IntegrationTestSuite) TestMultipleBids() {
|
||||
bundleHashes2[0]: true,
|
||||
bundleHashes2[1]: true,
|
||||
}
|
||||
s.verifyBlock(height+1, bundleHashes2, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+1, bundleHashes2, expectedExecution)
|
||||
|
||||
// Ensure that the escrow account has the correct balance
|
||||
expectedEscrowFee := s.calculateProposerEscrowSplit(bid2)
|
||||
@ -713,7 +713,7 @@ func (s *IntegrationTestSuite) TestMultipleBids() {
|
||||
|
||||
// Wait for a block to be created and ensure that the second bid is executed
|
||||
s.waitForABlock()
|
||||
s.verifyBlock(height+2, bundleHashes, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+2, bundleHashes, expectedExecution)
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -771,7 +771,7 @@ func (s *IntegrationTestSuite) TestInvalidBids() {
|
||||
bundleHashes[1]: false,
|
||||
}
|
||||
|
||||
s.verifyBlock(height+1, bundleHashes, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+1, bundleHashes, expectedExecution)
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -799,7 +799,7 @@ func (s *IntegrationTestSuite) TestInvalidBids() {
|
||||
}
|
||||
|
||||
// Ensure that the block was built correctly and that the bid was not executed
|
||||
s.verifyBlock(height+1, bundleHashes, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+1, bundleHashes, expectedExecution)
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -831,7 +831,7 @@ func (s *IntegrationTestSuite) TestInvalidBids() {
|
||||
}
|
||||
|
||||
// Ensure that the block was built correctly and that the bid was not executed
|
||||
s.verifyBlock(height+1, bundleHashes, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+1, bundleHashes, expectedExecution)
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -859,7 +859,7 @@ func (s *IntegrationTestSuite) TestInvalidBids() {
|
||||
}
|
||||
|
||||
// Ensure that the block was built correctly and that the bid was not executed
|
||||
s.verifyBlock(height+1, bundleHashes, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+1, bundleHashes, expectedExecution)
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -887,7 +887,7 @@ func (s *IntegrationTestSuite) TestInvalidBids() {
|
||||
bundleHashes[1]: false,
|
||||
}
|
||||
|
||||
s.verifyBlock(height+1, bundleHashes, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+1, bundleHashes, expectedExecution)
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -916,7 +916,7 @@ func (s *IntegrationTestSuite) TestInvalidBids() {
|
||||
for _, hash := range bundleHashes {
|
||||
expectedExecution[hash] = false
|
||||
}
|
||||
s.verifyBlock(height+1, bundleHashes, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+1, bundleHashes, expectedExecution)
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -944,7 +944,7 @@ func (s *IntegrationTestSuite) TestInvalidBids() {
|
||||
bundleHashes[1]: false,
|
||||
}
|
||||
|
||||
s.verifyBlock(height+1, bundleHashes, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+1, bundleHashes, expectedExecution)
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -986,7 +986,7 @@ func (s *IntegrationTestSuite) TestInvalidBids() {
|
||||
|
||||
expectedExecution[bundleHashes[0]] = false
|
||||
|
||||
s.verifyBlock(height+1, bundleHashes, expectedExecution)
|
||||
s.verifyTopOfBlockAuction(height+1, bundleHashes, expectedExecution)
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -1002,3 +1002,196 @@ func (s *IntegrationTestSuite) TestInvalidBids() {
|
||||
s.Require().Equal(escrowBalance, s.queryBalanceOf(escrowAddress, app.BondDenom))
|
||||
}
|
||||
}
|
||||
|
||||
// TestFreeLane tests that the application correctly handles free lanes. There are a few invariants that are tested:
|
||||
//
|
||||
// 1. Transactions that qualify as free should not be deducted any fees.
|
||||
// 2. Transactions that do not qualify as free should be deducted the correct fees.
|
||||
func (s *IntegrationTestSuite) TestFreeLane() {
|
||||
// Create the accounts that will create transactions to be included in bundles
|
||||
initBalance := sdk.NewInt64Coin(app.BondDenom, 10000000000)
|
||||
numAccounts := 4
|
||||
accounts := s.createTestAccounts(numAccounts, initBalance)
|
||||
|
||||
defaultSendAmount := sdk.NewCoin(app.BondDenom, sdk.NewInt(10))
|
||||
defaultStakeAmount := sdk.NewCoin(app.BondDenom, sdk.NewInt(10))
|
||||
defaultSendAmountCoins := sdk.NewCoins(defaultSendAmount)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
test func()
|
||||
}{
|
||||
{
|
||||
name: "valid free lane transaction",
|
||||
test: func() {
|
||||
balanceBeforeFreeTx := s.queryBalanceOf(accounts[0].Address.String(), app.BondDenom)
|
||||
|
||||
// basic stake amount
|
||||
validators := s.queryValidators()
|
||||
validator := validators[0]
|
||||
tx := s.createMsgDelegateTx(accounts[0], validator.OperatorAddress, defaultStakeAmount, 0, 1000)
|
||||
|
||||
// Broadcast the transaction
|
||||
s.broadcastTx(tx, 0)
|
||||
|
||||
// Wait for a block to be created
|
||||
s.waitForABlock()
|
||||
s.waitForABlock()
|
||||
|
||||
// Ensure that the transaction was executed correctly
|
||||
balanceAfterFreeTx := s.queryBalanceOf(accounts[0].Address.String(), app.BondDenom)
|
||||
s.Require().True(balanceAfterFreeTx.Add(defaultStakeAmount).IsGTE(balanceBeforeFreeTx))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "normal tx with free tx in same block",
|
||||
test: func() {
|
||||
balanceBeforeFreeTx := s.queryBalanceOf(accounts[0].Address.String(), app.BondDenom)
|
||||
balanceBeforeNormalTx := s.queryBalanceOf(accounts[1].Address.String(), app.BondDenom)
|
||||
|
||||
// basic free transaction
|
||||
validators := s.queryValidators()
|
||||
validator := validators[0]
|
||||
freeTx := s.createMsgDelegateTx(accounts[0], validator.OperatorAddress, defaultStakeAmount, 0, 1000)
|
||||
|
||||
// other normal transaction
|
||||
normalTx := s.createMsgSendTx(accounts[1], accounts[2].Address.String(), defaultSendAmountCoins, 0, 1000)
|
||||
|
||||
// Broadcast the transactions
|
||||
s.broadcastTx(freeTx, 0)
|
||||
s.broadcastTx(normalTx, 0)
|
||||
|
||||
// Wait for a block to be created
|
||||
s.waitForABlock()
|
||||
height := s.queryCurrentHeight()
|
||||
|
||||
// Ensure that the transaction was executed
|
||||
balanceAfterFreeTx := s.queryBalanceOf(accounts[0].Address.String(), app.BondDenom)
|
||||
s.Require().True(balanceAfterFreeTx.Add(defaultStakeAmount).IsGTE(balanceBeforeFreeTx))
|
||||
|
||||
// The balance must be strictly less than to account for fees
|
||||
balanceAfterNormalTx := s.queryBalanceOf(accounts[1].Address.String(), app.BondDenom)
|
||||
s.Require().True(balanceAfterNormalTx.IsLT((balanceBeforeNormalTx.Sub(defaultSendAmount))))
|
||||
|
||||
hashes := s.normalTxsToTxHashes([][]byte{freeTx, normalTx})
|
||||
expectedExecution := map[string]bool{
|
||||
hashes[0]: true,
|
||||
hashes[1]: true,
|
||||
}
|
||||
|
||||
// Ensure that the block was built correctly
|
||||
s.verifyBlock(height, hashes, expectedExecution)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple free transactions in same block",
|
||||
test: func() {
|
||||
balanceBeforeFreeTx := s.queryBalanceOf(accounts[0].Address.String(), app.BondDenom)
|
||||
balanceBeforeFreeTx2 := s.queryBalanceOf(accounts[1].Address.String(), app.BondDenom)
|
||||
|
||||
// basic free transaction
|
||||
validators := s.queryValidators()
|
||||
validator := validators[0]
|
||||
freeTx := s.createMsgDelegateTx(accounts[0], validator.OperatorAddress, defaultStakeAmount, 0, 1000)
|
||||
|
||||
// other normal transaction
|
||||
freeTx2 := s.createMsgDelegateTx(accounts[1], validator.OperatorAddress, defaultStakeAmount, 0, 1000)
|
||||
|
||||
// Broadcast the transactions
|
||||
s.broadcastTx(freeTx, 0)
|
||||
s.broadcastTx(freeTx2, 0)
|
||||
|
||||
// Wait for a block to be created
|
||||
s.waitForABlock()
|
||||
|
||||
// Ensure that the transaction was executed
|
||||
balanceAfterFreeTx := s.queryBalanceOf(accounts[0].Address.String(), app.BondDenom)
|
||||
s.Require().True(balanceAfterFreeTx.Add(defaultStakeAmount).IsGTE(balanceBeforeFreeTx))
|
||||
|
||||
balanceAfterFreeTx2 := s.queryBalanceOf(accounts[1].Address.String(), app.BondDenom)
|
||||
s.Require().True(balanceAfterFreeTx2.Add(defaultStakeAmount).IsGTE(balanceBeforeFreeTx2))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
s.waitForABlock()
|
||||
s.Run(tc.name, tc.test)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLanes tests that the application correctly handles lanes. The biggest invarient that is
|
||||
// test here is making sure that transactions are ordered in blocks respecting the lane order.
|
||||
func (s *IntegrationTestSuite) TestLanes() {
|
||||
// Create the accounts that will create transactions to be included in bundles
|
||||
initBalance := sdk.NewInt64Coin(app.BondDenom, 10000000000)
|
||||
numAccounts := 4
|
||||
accounts := s.createTestAccounts(numAccounts, initBalance)
|
||||
|
||||
defaultSendAmount := sdk.NewCoin(app.BondDenom, sdk.NewInt(10))
|
||||
defaultStakeAmount := sdk.NewCoin(app.BondDenom, sdk.NewInt(10))
|
||||
defaultSendAmountCoins := sdk.NewCoins(defaultSendAmount)
|
||||
|
||||
// auction parameters
|
||||
params := s.queryBuilderParams()
|
||||
reserveFee := params.ReserveFee
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
test func()
|
||||
}{
|
||||
{
|
||||
name: "block with tob, free, and normal tx",
|
||||
test: func() {
|
||||
// basic free transaction
|
||||
validators := s.queryValidators()
|
||||
validator := validators[0]
|
||||
freeTx := s.createMsgDelegateTx(accounts[0], validator.OperatorAddress, defaultStakeAmount, 0, 1000)
|
||||
|
||||
// other normal transaction
|
||||
normalTx := s.createMsgSendTx(accounts[1], accounts[2].Address.String(), defaultSendAmountCoins, 0, 1000)
|
||||
|
||||
// Create a bid transaction that includes the bundle and is valid
|
||||
bundle := [][]byte{
|
||||
s.createMsgSendTx(accounts[3], accounts[1].Address.String(), defaultSendAmountCoins, 0, 1000),
|
||||
}
|
||||
bid := reserveFee
|
||||
height := s.queryCurrentHeight()
|
||||
bidTx := s.createAuctionBidTx(accounts[2], bid, bundle, 0, height+5)
|
||||
s.displayExpectedBundle("Valid auction bid", bidTx, bundle)
|
||||
|
||||
// Broadcast the transactions
|
||||
s.waitForABlock()
|
||||
s.broadcastTx(freeTx, 0)
|
||||
s.broadcastTx(normalTx, 0)
|
||||
s.broadcastTx(bidTx, 0)
|
||||
|
||||
// Wait for a block to be created
|
||||
s.waitForABlock()
|
||||
height = s.queryCurrentHeight()
|
||||
|
||||
// Ensure that the transaction was executed
|
||||
hashes := s.normalTxsToTxHashes([][]byte{
|
||||
bidTx,
|
||||
bundle[0],
|
||||
freeTx,
|
||||
normalTx,
|
||||
})
|
||||
expectedExecution := map[string]bool{
|
||||
hashes[0]: true,
|
||||
hashes[1]: true,
|
||||
hashes[2]: true,
|
||||
hashes[3]: true,
|
||||
}
|
||||
|
||||
// Ensure that the block was built correctly
|
||||
s.verifyBlock(height, hashes, expectedExecution)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
s.waitForABlock()
|
||||
s.Run(tc.name, tc.test)
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
"github.com/ory/dockertest/v3/docker"
|
||||
"github.com/skip-mev/pob/tests/app"
|
||||
buildertypes "github.com/skip-mev/pob/x/builder/types"
|
||||
@ -103,6 +104,20 @@ func (s *IntegrationTestSuite) createMsgSendTx(account TestAccount, toAddress st
|
||||
return s.createTx(account, msgs, sequenceOffset, height)
|
||||
}
|
||||
|
||||
// createMsgDelegateTx creates a delegate transaction given the provided signer, validator, amount, sequence number offset
|
||||
// and block height timeout.
|
||||
func (s *IntegrationTestSuite) createMsgDelegateTx(account TestAccount, validator string, amount sdk.Coin, sequenceOffset, height uint64) []byte {
|
||||
msgs := []sdk.Msg{
|
||||
&stakingtypes.MsgDelegate{
|
||||
DelegatorAddress: account.Address.String(),
|
||||
ValidatorAddress: validator,
|
||||
Amount: amount,
|
||||
},
|
||||
}
|
||||
|
||||
return s.createTx(account, msgs, sequenceOffset, height)
|
||||
}
|
||||
|
||||
// createTx creates a transaction given the provided messages, sequence number offset, and block height timeout.
|
||||
func (s *IntegrationTestSuite) createTx(account TestAccount, msgs []sdk.Msg, sequenceOffset, height uint64) []byte {
|
||||
txConfig := encodingConfig.TxConfig
|
||||
|
||||
@ -16,6 +16,7 @@ import (
|
||||
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
buildertypes "github.com/skip-mev/pob/x/builder/types"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
@ -144,9 +145,8 @@ func (s *IntegrationTestSuite) normalTxsToTxHashes(txs [][]byte) []string {
|
||||
return hashes
|
||||
}
|
||||
|
||||
// verifyBlock verifies that the transactions in the block at the given height were seen
|
||||
// and executed in the order they were submitted i.e. how they are broadcasted in the bundle.
|
||||
func (s *IntegrationTestSuite) verifyBlock(height uint64, bundle []string, expectedExecution map[string]bool) {
|
||||
// verifyTopOfBlockAuction verifies that blocks that include a bid transaction execute as expected.
|
||||
func (s *IntegrationTestSuite) verifyTopOfBlockAuction(height uint64, bundle []string, expectedExecution map[string]bool) {
|
||||
s.waitForABlock()
|
||||
s.T().Logf("Verifying block %d", height)
|
||||
|
||||
@ -179,12 +179,40 @@ func (s *IntegrationTestSuite) verifyBlock(height uint64, bundle []string, expec
|
||||
}
|
||||
}
|
||||
|
||||
// verifyBlock verifies that the transactions in the block at the given height were seen
|
||||
// and executed in the order they were submitted.
|
||||
func (s *IntegrationTestSuite) verifyBlock(height uint64, txs []string, expectedExecution map[string]bool) {
|
||||
s.waitForABlock()
|
||||
s.T().Logf("Verifying block %d", height)
|
||||
|
||||
// Get the block's transactions and display the expected and actual block for debugging.
|
||||
blockTxs := s.queryBlockTxs(height)
|
||||
s.displayBlock(blockTxs, txs)
|
||||
|
||||
// Ensure that all transactions executed as expected (i.e. landed or failed to land).
|
||||
for tx, landed := range expectedExecution {
|
||||
s.T().Logf("Verifying tx %s executed as %t", tx, landed)
|
||||
s.Require().Equal(landed, s.queryTxPassed(tx) == nil)
|
||||
}
|
||||
s.T().Logf("All txs executed as expected")
|
||||
|
||||
// Check that the block contains the expected transactions in the expected order.
|
||||
s.Require().Equal(len(txs), len(blockTxs))
|
||||
|
||||
hashBlockTxs := s.normalTxsToTxHashes(blockTxs)
|
||||
for index, tx := range txs {
|
||||
s.Require().Equal(strings.ToUpper(tx), strings.ToUpper(hashBlockTxs[index]))
|
||||
}
|
||||
|
||||
s.T().Logf("Block %d contains the expected transactions in the expected order", height)
|
||||
}
|
||||
|
||||
// displayExpectedBlock displays the expected and actual blocks.
|
||||
func (s *IntegrationTestSuite) displayBlock(txs [][]byte, bundle []string) {
|
||||
if len(bundle) != 0 {
|
||||
expectedBlock := fmt.Sprintf("Expected block:\n\t(%d, %s)\n", 0, bundle[0])
|
||||
for index, bundleTx := range bundle[1:] {
|
||||
expectedBlock += fmt.Sprintf("\t(%d, %s)\n", index+1, bundleTx)
|
||||
func (s *IntegrationTestSuite) displayBlock(txs [][]byte, expectedTxs []string) {
|
||||
if len(expectedTxs) != 0 {
|
||||
expectedBlock := fmt.Sprintf("Expected block:\n\t(%d, %s)\n", 0, expectedTxs[0])
|
||||
for index, expectedTx := range expectedTxs[1:] {
|
||||
expectedBlock += fmt.Sprintf("\t(%d, %s)\n", index+1, expectedTx)
|
||||
}
|
||||
|
||||
s.T().Logf(expectedBlock)
|
||||
@ -326,3 +354,14 @@ func (s *IntegrationTestSuite) queryBlockTxs(height uint64) [][]byte {
|
||||
|
||||
return resp.GetSdkBlock().Data.Txs
|
||||
}
|
||||
|
||||
// queryValidators returns the validators of the network.
|
||||
func (s *IntegrationTestSuite) queryValidators() []stakingtypes.Validator {
|
||||
queryClient := stakingtypes.NewQueryClient(s.createClientContext())
|
||||
|
||||
req := &stakingtypes.QueryValidatorsRequest{}
|
||||
resp, err := queryClient.Validators(context.Background(), req)
|
||||
s.Require().NoError(err)
|
||||
|
||||
return resp.Validators
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user