feat(BB): Free Lane (#167)

This commit is contained in:
David Terpay 2023-06-07 12:38:42 -04:00 committed by GitHub
parent b7780a3140
commit cfe2147d52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 432 additions and 39 deletions

View 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
}

View 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
View 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)
}

View File

@ -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),

View File

@ -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,
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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
}