fix: CheckTx Handler (#141)

This commit is contained in:
David Terpay
2023-05-23 22:09:37 +00:00
committed by GitHub
parent af94805b48
commit 648a2f88d6
13 changed files with 1362 additions and 503 deletions
+1 -1
View File
@@ -48,7 +48,7 @@ func NewPOBAnteHandler(options POBHandlerOptions) sdk.AnteHandler {
ante.NewSigGasConsumeDecorator(options.BaseOptions.AccountKeeper, options.BaseOptions.SigGasConsumer),
ante.NewSigVerificationDecorator(options.BaseOptions.AccountKeeper, options.BaseOptions.SignModeHandler),
ante.NewIncrementSequenceDecorator(options.BaseOptions.AccountKeeper),
builderante.NewBuilderDecorator(options.BuilderKeeper, options.TxDecoder, options.TxEncoder, options.Mempool),
builderante.NewBuilderDecorator(options.BuilderKeeper, options.TxEncoder, options.Mempool),
}
return sdk.ChainAnteDecorators(anteDecorators...)
+33 -1
View File
@@ -9,6 +9,7 @@ import (
"cosmossdk.io/depinject"
dbm "github.com/cometbft/cometbft-db"
cometabci "github.com/cometbft/cometbft/abci/types"
"github.com/cometbft/cometbft/libs/log"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client"
@@ -72,6 +73,10 @@ import (
builderkeeper "github.com/skip-mev/pob/x/builder/keeper"
)
const (
ChainID = "chain-id-0"
)
var (
BondDenom = sdk.DefaultBondDenom
@@ -142,6 +147,9 @@ type TestApp struct {
GroupKeeper groupkeeper.Keeper
ConsensusParamsKeeper consensuskeeper.Keeper
BuilderKeeper builderkeeper.Keeper
// custom checkTx handler
checkTxHandler abci.CheckTx
}
func init() {
@@ -275,7 +283,7 @@ func New(
}
anteHandler := NewPOBAnteHandler(options)
// Set the proposal handlers on the BaseApp.
// Set the proposal handlers on the BaseApp along with the custom antehandler.
proposalHandlers := abci.NewProposalHandler(
mempool,
app.App.Logger(),
@@ -285,6 +293,17 @@ func New(
)
app.App.SetPrepareProposal(proposalHandlers.PrepareProposalHandler())
app.App.SetProcessProposal(proposalHandlers.ProcessProposalHandler())
app.App.SetAnteHandler(anteHandler)
// Set the custom CheckTx handler on BaseApp.
checkTxHandler := abci.NewCheckTxHandler(
app.App,
app.txConfig.TxDecoder(),
mempool,
anteHandler,
ChainID,
)
app.SetCheckTx(checkTxHandler.CheckTx())
// load state streaming if enabled
if _, _, err := streaming.LoadStreamingServices(app.App.BaseApp, appOpts, app.appCodec, logger, app.kvStoreKeys()); err != nil {
@@ -320,6 +339,19 @@ func New(
return app
}
// CheckTx will check the transaction with the provided checkTxHandler. We override the default
// handler so that we can verify bid transactions before they are inserted into the mempool.
// With the POB CheckTx, we can verify the bid transaction and all of the bundled transactions
// before inserting the bid transaction into the mempool.
func (app *TestApp) CheckTx(req cometabci.RequestCheckTx) cometabci.ResponseCheckTx {
return app.checkTxHandler(req)
}
// SetCheckTx sets the checkTxHandler for the app.
func (app *TestApp) SetCheckTx(handler abci.CheckTx) {
app.checkTxHandler = handler
}
// Name returns the name of the App
func (app *TestApp) Name() string { return app.BaseApp.Name() }
+1 -2
View File
@@ -6,7 +6,6 @@ import (
dbm "github.com/cometbft/cometbft-db"
"github.com/cometbft/cometbft/libs/log"
cometrand "github.com/cometbft/cometbft/libs/rand"
"github.com/cosmos/cosmos-sdk/codec"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
"github.com/skip-mev/pob/tests/app"
@@ -47,7 +46,7 @@ func newChain() (*chain, error) {
}
return &chain{
id: "chain-" + cometrand.NewRand().Str(6),
id: app.ChainID,
dataDir: tmpDir,
}, nil
}
+906 -359
View File
File diff suppressed because it is too large Load Diff
+27 -72
View File
@@ -3,7 +3,6 @@ package e2e
import (
"bytes"
"context"
"encoding/hex"
"fmt"
"strings"
"time"
@@ -16,68 +15,9 @@ import (
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/ory/dockertest/v3/docker"
"github.com/skip-mev/pob/tests/app"
buildertypes "github.com/skip-mev/pob/x/builder/types"
)
// execAuctionBidTx executes an auction bid transaction on the given validator given the provided
// bid, timeout, and bundle. This function returns the transaction hash. It does not wait for the
// transaction to be committed.
func (s *IntegrationTestSuite) execAuctionBidTx(valIdx int, bid sdk.Coin, timeout int64, bundle []string) string {
address, err := s.chain.validators[valIdx].keyInfo.GetAddress()
s.Require().NoError(err)
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
exec, err := s.dkrPool.Client.CreateExec(docker.CreateExecOptions{
Context: ctx,
AttachStdout: true,
AttachStderr: true,
Container: s.valResources[valIdx].Container.ID,
User: "root",
Cmd: []string{
"testappd",
"tx",
"builder",
"auction-bid",
address.String(), // bidder
bid.String(), // bid
strings.Join(bundle, ","), // bundle
fmt.Sprintf("--%s=%d", flags.FlagTimeoutHeight, timeout), // timeout
fmt.Sprintf("--%s=%s", flags.FlagFrom, s.chain.validators[valIdx].keyInfo.Name),
fmt.Sprintf("--%s=%s", flags.FlagChainID, s.chain.id),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoin(app.BondDenom, sdk.NewInt(1000000000)).String()),
"--keyring-backend=test",
"--broadcast-mode=sync",
"-y",
},
})
s.Require().NoError(err)
var (
outBuf bytes.Buffer
errBuf bytes.Buffer
)
err = s.dkrPool.Client.StartExec(exec.ID, docker.StartExecOptions{
Context: ctx,
Detach: false,
OutputStream: &outBuf,
ErrorStream: &errBuf,
})
s.Require().NoErrorf(err, "stdout: %s, stderr: %s", outBuf.String(), errBuf.String())
output := outBuf.String()
resp := strings.Split(output, ":")
txHash := strings.TrimSpace(resp[len(resp)-1])
s.T().Logf(
"broadcasted bid tx %s with bid %s timeout %d and %d bundled txs",
txHash, bid, timeout, len(bundle),
)
return txHash
}
// execMsgSendTx executes a send transaction on the given validator given the provided
// recipient and amount. This function returns the transaction hash. It does not wait for the
// transaction to be committed.
@@ -136,12 +76,22 @@ func (s *IntegrationTestSuite) execMsgSendTx(valIdx int, to sdk.AccAddress, amou
return txHash
}
// createAuctionBidTx creates a transaction that bids on an auction given the provided bidder, bid, and transactions.
func (s *IntegrationTestSuite) createAuctionBidTx(account TestAccount, bid sdk.Coin, transactions [][]byte, sequenceOffset, height uint64) []byte {
msgs := []sdk.Msg{
&buildertypes.MsgAuctionBid{
Bidder: account.Address.String(),
Bid: bid,
Transactions: transactions,
},
}
return s.createTx(account, msgs, sequenceOffset, height)
}
// createMsgSendTx creates a send transaction given the provided signer, recipient, amount, sequence number offset, and block height timeout.
// This function is primarily used to create bundles of transactions.
func (s *IntegrationTestSuite) createMsgSendTx(account TestAccount, toAddress string, amount sdk.Coins, sequenceOffset, height int) string {
txConfig := encodingConfig.TxConfig
txBuilder := txConfig.NewTxBuilder()
func (s *IntegrationTestSuite) createMsgSendTx(account TestAccount, toAddress string, amount sdk.Coins, sequenceOffset, height uint64) []byte {
msgs := []sdk.Msg{
&banktypes.MsgSend{
FromAddress: account.Address.String(),
@@ -150,15 +100,23 @@ func (s *IntegrationTestSuite) createMsgSendTx(account TestAccount, toAddress st
},
}
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
txBuilder := txConfig.NewTxBuilder()
// Get account info of the sender to set the account number and sequence number
baseAccount := s.queryAccount(account.Address)
sequenceNumber := baseAccount.Sequence + uint64(sequenceOffset)
sequenceNumber := baseAccount.Sequence + sequenceOffset
// Set the messages, fees, and timeout.
txBuilder.SetMsgs(msgs...)
txBuilder.SetGasLimit(5000000)
txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(75000))))
txBuilder.SetTimeoutHeight(uint64(height))
txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(150000))))
txBuilder.SetTimeoutHeight(height)
sigV2 := signing.SignatureV2{
PubKey: account.PrivateKey.PubKey(),
@@ -191,8 +149,5 @@ func (s *IntegrationTestSuite) createMsgSendTx(account TestAccount, toAddress st
bz, err := txConfig.TxEncoder()(txBuilder.GetTx())
s.Require().NoError(err)
// Hex encode the transaction
hash := hex.EncodeToString(bz)
return hash
return bz
}
+68 -30
View File
@@ -116,15 +116,29 @@ func (s *IntegrationTestSuite) waitForABlock() {
}
// bundleToTxHashes converts a bundle to a slice of transaction hashes.
func (s *IntegrationTestSuite) bundleToTxHashes(bundle []string) []string {
hashes := make([]string, len(bundle))
func (s *IntegrationTestSuite) bundleToTxHashes(bidTx []byte, bundle [][]byte) []string {
hashes := make([]string, len(bundle)+1)
for i, tx := range bundle {
hashBz, err := hex.DecodeString(tx)
s.Require().NoError(err)
// encode the bid transaction into a hash
hashBz := sha256.Sum256(bidTx)
hash := hex.EncodeToString(hashBz[:])
hashes[0] = hash
shaBz := sha256.Sum256(hashBz)
hashes[i] = hex.EncodeToString(shaBz[:])
for i, hash := range s.normalTxsToTxHashes(bundle) {
hashes[i+1] = hash
}
return hashes
}
// normalTxsToTxHashes converts a slice of normal transactions to a slice of transaction hashes.
func (s *IntegrationTestSuite) normalTxsToTxHashes(txs [][]byte) []string {
hashes := make([]string, len(txs))
for i, tx := range txs {
hashBz := sha256.Sum256(tx)
hash := hex.EncodeToString(hashBz[:])
hashes[i] = hash
}
return hashes
@@ -132,13 +146,13 @@ func (s *IntegrationTestSuite) bundleToTxHashes(bundle []string) []string {
// 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 int64, bidTx string, bundle []string, expectedExecution map[string]bool) {
func (s *IntegrationTestSuite) verifyBlock(height uint64, bundle []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.
txs := s.queryBlockTxs(height)
s.displayBlock(txs, bidTx, bundle)
s.displayBlock(txs, bundle)
// Ensure that all transactions executed as expected (i.e. landed or failed to land).
for tx, landed := range expectedExecution {
@@ -149,28 +163,32 @@ func (s *IntegrationTestSuite) verifyBlock(height int64, bidTx string, bundle []
// Check that the block contains the expected transactions in the expected order
// iff the bid transaction was expected to execute.
if expectedExecution[bidTx] {
hashBz := sha256.Sum256(txs[0])
hash := hex.EncodeToString(hashBz[:])
s.Require().Equal(strings.ToUpper(bidTx), strings.ToUpper(hash))
if len(bundle) > 0 && expectedExecution[bundle[0]] {
if expectedExecution[bundle[0]] {
hashBz := sha256.Sum256(txs[0])
hash := hex.EncodeToString(hashBz[:])
s.Require().Equal(strings.ToUpper(bundle[0]), strings.ToUpper(hash))
for index, bundleTx := range bundle {
hashBz := sha256.Sum256(txs[index+1])
txHash := hex.EncodeToString(hashBz[:])
for index, bundleTx := range bundle[1:] {
hashBz := sha256.Sum256(txs[index+1])
txHash := hex.EncodeToString(hashBz[:])
s.Require().Equal(strings.ToUpper(bundleTx), strings.ToUpper(txHash))
s.Require().Equal(strings.ToUpper(bundleTx), strings.ToUpper(txHash))
}
}
}
}
// displayExpectedBlock displays the expected and actual blocks.
func (s *IntegrationTestSuite) displayBlock(txs [][]byte, bidTx string, bundle []string) {
expectedBlock := fmt.Sprintf("Expected block:\n\t(%d, %s)\n", 0, bidTx)
for index, bundleTx := range bundle {
expectedBlock += fmt.Sprintf("\t(%d, %s)\n", index+1, bundleTx)
}
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)
}
s.T().Logf(expectedBlock)
s.T().Logf(expectedBlock)
}
// Display the actual block.
if len(txs) == 0 {
@@ -192,15 +210,35 @@ func (s *IntegrationTestSuite) displayBlock(txs [][]byte, bidTx string, bundle [
}
// displayExpectedBundle displays the expected order of the bid and bundled transactions.
func (s *IntegrationTestSuite) displayExpectedBundle(prefix, bidTx string, bundle []string) {
expectedBundle := fmt.Sprintf("%s expected bundle:\n\t(%d, %s)\n", prefix, 0, bidTx)
for index, bundleTx := range s.bundleToTxHashes(bundle) {
func (s *IntegrationTestSuite) displayExpectedBundle(prefix string, bidTx []byte, bundle [][]byte) {
// encode the bid transaction into a hash
hashes := s.bundleToTxHashes(bidTx, bundle)
expectedBundle := fmt.Sprintf("%s expected bundle:\n\t(%d, %s)\n", prefix, 0, hashes[0])
for index, bundleTx := range hashes[1:] {
expectedBundle += fmt.Sprintf("\t(%d, %s)\n", index+1, bundleTx)
}
s.T().Logf(expectedBundle)
}
// broadcastTx broadcasts a transaction to the network using the given validator.
func (s *IntegrationTestSuite) broadcastTx(tx []byte, valIdx int) {
node := s.valResources[valIdx]
gRPCURI := node.GetHostPort("9090/tcp")
grpcConn, err := grpc.Dial(
gRPCURI,
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
client := txtypes.NewServiceClient(grpcConn)
req := &txtypes.BroadcastTxRequest{TxBytes: tx, Mode: txtypes.BroadcastMode_BROADCAST_MODE_SYNC}
_, err = client.BroadcastTx(context.Background(), req)
s.Require().NoError(err)
}
// queryTx queries a transaction by its hash and returns whether there was an
// error in including the transaction in a block.
func (s *IntegrationTestSuite) queryTxPassed(txHash string) error {
@@ -268,21 +306,21 @@ func (s *IntegrationTestSuite) queryAccount(address sdk.AccAddress) *authtypes.B
}
// queryCurrentHeight returns the current block height.
func (s *IntegrationTestSuite) queryCurrentHeight() int64 {
func (s *IntegrationTestSuite) queryCurrentHeight() uint64 {
queryClient := tmclient.NewServiceClient(s.createClientContext())
req := &tmclient.GetLatestBlockRequest{}
resp, err := queryClient.GetLatestBlock(context.Background(), req)
s.Require().NoError(err)
return resp.SdkBlock.Header.Height
return uint64(resp.SdkBlock.Header.Height)
}
// queryBlockTxs returns the txs of the block at the given height.
func (s *IntegrationTestSuite) queryBlockTxs(height int64) [][]byte {
func (s *IntegrationTestSuite) queryBlockTxs(height uint64) [][]byte {
queryClient := tmclient.NewServiceClient(s.createClientContext())
req := &tmclient.GetBlockByHeightRequest{Height: height}
req := &tmclient.GetBlockByHeightRequest{Height: int64(height)}
resp, err := queryClient.GetBlockByHeight(context.Background(), req)
s.Require().NoError(err)