205 lines
6.2 KiB
Go
205 lines
6.2 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
abci "github.com/cometbft/cometbft/api/cometbft/abci/v1"
|
|
|
|
"cosmossdk.io/core/server"
|
|
"cosmossdk.io/core/store"
|
|
"cosmossdk.io/core/transaction"
|
|
"cosmossdk.io/server/v2/cometbft/mempool"
|
|
consensustypes "cosmossdk.io/x/consensus/types"
|
|
)
|
|
|
|
type AppManager[T transaction.Tx] interface {
|
|
ValidateTx(ctx context.Context, tx T) (server.TxResult, error)
|
|
Query(ctx context.Context, version uint64, request transaction.Msg) (response transaction.Msg, err error)
|
|
}
|
|
|
|
type DefaultProposalHandler[T transaction.Tx] struct {
|
|
mempool mempool.Mempool[T]
|
|
txSelector TxSelector[T]
|
|
}
|
|
|
|
func NewDefaultProposalHandler[T transaction.Tx](mp mempool.Mempool[T]) *DefaultProposalHandler[T] {
|
|
return &DefaultProposalHandler[T]{
|
|
mempool: mp,
|
|
txSelector: NewDefaultTxSelector[T](),
|
|
}
|
|
}
|
|
|
|
func (h *DefaultProposalHandler[T]) PrepareHandler() PrepareHandler[T] {
|
|
return func(ctx context.Context, app AppManager[T], codec transaction.Codec[T], req *abci.PrepareProposalRequest, chainID string) ([]T, error) {
|
|
var maxBlockGas uint64
|
|
|
|
res, err := app.Query(ctx, 0, &consensustypes.QueryParamsRequest{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
paramsResp, ok := res.(*consensustypes.QueryParamsResponse)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unexpected consensus params response type; expected: %T, got: %T", &consensustypes.QueryParamsResponse{}, res)
|
|
}
|
|
|
|
if b := paramsResp.GetParams().Block; b != nil {
|
|
maxBlockGas = uint64(b.MaxGas)
|
|
}
|
|
|
|
txs := decodeTxs(codec, req.Txs)
|
|
|
|
defer h.txSelector.Clear()
|
|
|
|
// If the mempool is nil or NoOp we simply return the transactions
|
|
// requested from CometBFT, which, by default, should be in FIFO order.
|
|
//
|
|
// Note, we still need to ensure the transactions returned respect req.MaxTxBytes.
|
|
_, isNoOp := h.mempool.(mempool.NoOpMempool[T])
|
|
if h.mempool == nil || isNoOp {
|
|
for _, tx := range txs {
|
|
stop := h.txSelector.SelectTxForProposal(ctx, uint64(req.MaxTxBytes), maxBlockGas, tx)
|
|
if stop {
|
|
break
|
|
}
|
|
}
|
|
|
|
return h.txSelector.SelectedTxs(ctx), nil
|
|
}
|
|
|
|
iterator := h.mempool.Select(ctx, txs)
|
|
for iterator != nil {
|
|
memTx := iterator.Tx()
|
|
|
|
// NOTE: Since transaction verification was already executed in CheckTx,
|
|
// which calls mempool.Insert, in theory everything in the pool should be
|
|
// valid. But some mempool implementations may insert invalid txs, so we
|
|
// check again.
|
|
_, err := app.ValidateTx(ctx, memTx)
|
|
if err != nil {
|
|
err := h.mempool.Remove(memTx)
|
|
if err != nil && !errors.Is(err, mempool.ErrTxNotFound) {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
stop := h.txSelector.SelectTxForProposal(ctx, uint64(req.MaxTxBytes), maxBlockGas, memTx)
|
|
if stop {
|
|
break
|
|
}
|
|
}
|
|
|
|
iterator = iterator.Next()
|
|
}
|
|
|
|
return h.txSelector.SelectedTxs(ctx), nil
|
|
}
|
|
}
|
|
|
|
func (h *DefaultProposalHandler[T]) ProcessHandler() ProcessHandler[T] {
|
|
return func(ctx context.Context, app AppManager[T], codec transaction.Codec[T], req *abci.ProcessProposalRequest, chainID string) error {
|
|
// If the mempool is nil we simply return ACCEPT,
|
|
// because PrepareProposal may have included txs that could fail verification.
|
|
_, isNoOp := h.mempool.(mempool.NoOpMempool[T])
|
|
if h.mempool == nil || isNoOp {
|
|
return nil
|
|
}
|
|
|
|
res, err := app.Query(ctx, 0, &consensustypes.QueryParamsRequest{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
paramsResp, ok := res.(*consensustypes.QueryParamsResponse)
|
|
if !ok {
|
|
return fmt.Errorf("unexpected consensus params response type; expected: %T, got: %T", &consensustypes.QueryParamsResponse{}, res)
|
|
}
|
|
|
|
var maxBlockGas uint64
|
|
if b := paramsResp.GetParams().Block; b != nil {
|
|
maxBlockGas = uint64(b.MaxGas)
|
|
}
|
|
|
|
// Decode request txs bytes
|
|
// If there an tx decoded fail, return err
|
|
var txs []T
|
|
for _, tx := range req.Txs {
|
|
decTx, err := codec.Decode(tx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decode tx: %w", err)
|
|
}
|
|
txs = append(txs, decTx)
|
|
}
|
|
|
|
var totalTxGas uint64
|
|
for _, tx := range txs {
|
|
_, err := app.ValidateTx(ctx, tx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to validate tx: %w", err)
|
|
}
|
|
|
|
if maxBlockGas > 0 {
|
|
gaslimit, err := tx.GetGasLimit()
|
|
if err != nil {
|
|
return errors.New("failed to get gas limit")
|
|
}
|
|
totalTxGas += gaslimit
|
|
if totalTxGas > maxBlockGas {
|
|
return fmt.Errorf("total tx gas %d exceeds max block gas %d", totalTxGas, maxBlockGas)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// decodeTxs decodes the txs bytes into a decoded txs
|
|
// If there a fail decoding tx, remove from the list
|
|
// Used for prepare proposal
|
|
func decodeTxs[T transaction.Tx](codec transaction.Codec[T], txsBz [][]byte) []T {
|
|
var txs []T
|
|
for _, tx := range txsBz {
|
|
decTx, err := codec.Decode(tx)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
txs = append(txs, decTx)
|
|
}
|
|
return txs
|
|
}
|
|
|
|
// NoOpPrepareProposal defines a no-op PrepareProposal handler. It will always
|
|
// return the transactions sent by the client's request.
|
|
func NoOpPrepareProposal[T transaction.Tx]() PrepareHandler[T] {
|
|
return func(ctx context.Context, app AppManager[T], codec transaction.Codec[T], req *abci.PrepareProposalRequest, chainID string) ([]T, error) {
|
|
return decodeTxs(codec, req.Txs), nil
|
|
}
|
|
}
|
|
|
|
// NoOpProcessProposal defines a no-op ProcessProposal Handler. It will always
|
|
// return ACCEPT.
|
|
func NoOpProcessProposal[T transaction.Tx]() ProcessHandler[T] {
|
|
return func(context.Context, AppManager[T], transaction.Codec[T], *abci.ProcessProposalRequest, string) error {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// NoOpExtendVote defines a no-op ExtendVote handler. It will always return an
|
|
// empty byte slice as the vote extension.
|
|
func NoOpExtendVote() ExtendVoteHandler {
|
|
return func(context.Context, store.ReaderMap, *abci.ExtendVoteRequest) (*abci.ExtendVoteResponse, error) {
|
|
return &abci.ExtendVoteResponse{VoteExtension: []byte{}}, nil
|
|
}
|
|
}
|
|
|
|
// NoOpVerifyVoteExtensionHandler defines a no-op VerifyVoteExtension handler. It
|
|
// will always return an ACCEPT status with no error.
|
|
func NoOpVerifyVoteExtensionHandler() VerifyVoteExtensionHandler {
|
|
return func(context.Context, store.ReaderMap, *abci.VerifyVoteExtensionRequest) (*abci.VerifyVoteExtensionResponse, error) {
|
|
return &abci.VerifyVoteExtensionResponse{Status: abci.VERIFY_VOTE_EXTENSION_STATUS_ACCEPT}, nil
|
|
}
|
|
}
|