Skip to main content

Overview

Cosmos SDK applications integrate with the CometBFT (formerly Tendermint) consensus engine through the Application Blockchain Interface (ABCI). This separation allows the consensus layer and application layer to evolve independently.

ABCI Architecture

┌─────────────────────────────────────────┐
│         CometBFT Consensus              │
│  (Block Production, P2P, Consensus)     │
└────────────────┬────────────────────────┘

                 │ ABCI Interface

┌────────────────▼────────────────────────┐
│         Cosmos SDK Application          │
│  (State Machine, Business Logic)        │
└─────────────────────────────────────────┘

ABCI Methods

BaseApp implements all ABCI methods:
// Location: baseapp/abci.go

// Initialization
func (app *BaseApp) InitChain(*abci.RequestInitChain) (*abci.ResponseInitChain, error)

// Information
func (app *BaseApp) Info(*abci.RequestInfo) (*abci.ResponseInfo, error)
func (app *BaseApp) Query(context.Context, *abci.RequestQuery) (*abci.ResponseQuery, error)

// Mempool
func (app *BaseApp) CheckTx(*abci.RequestCheckTx) (*abci.ResponseCheckTx, error)

// Block Execution
func (app *BaseApp) PrepareProposal(*abci.RequestPrepareProposal) (*abci.ResponsePrepareProposal, error)
func (app *BaseApp) ProcessProposal(*abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error)
func (app *BaseApp) FinalizeBlock(*abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error)
func (app *BaseApp) Commit() (*abci.ResponseCommit, error)

Execution Modes

The SDK supports different execution contexts:
// Location: baseapp/baseapp.go:53
const (
    execModeCheck               = sdk.ExecModeCheck               // Validate tx for mempool
    execModeReCheck             = sdk.ExecModeReCheck             // Recheck pending tx
    execModeSimulate            = sdk.ExecModeSimulate            // Estimate gas
    execModePrepareProposal     = sdk.ExecModePrepareProposal     // Proposer prepares block
    execModeProcessProposal     = sdk.ExecModeProcessProposal     // Validators verify block
    execModeVoteExtension       = sdk.ExecModeVoteExtension       // Create vote extension
    execModeVerifyVoteExtension = sdk.ExecModeVerifyVoteExtension // Verify vote extension
    execModeFinalize            = sdk.ExecModeFinalize            // Execute block
)

Block Lifecycle

1

Block Proposal

Proposer calls PrepareProposal to select and order transactions
2

Block Verification

Validators call ProcessProposal to verify the proposed block
3

Vote Extensions (Optional)

Validators can attach additional data to their prevotes
4

Consensus

CometBFT runs Byzantine Fault Tolerant consensus
5

Block Finalization

All nodes call FinalizeBlock to execute transactions
6

Commit

State changes are committed and app hash is computed

InitChain

Called once when the chain starts:
// Location: baseapp/abci.go:53
func (app *BaseApp) InitChain(req *abci.RequestInitChain) (*abci.ResponseInitChain, error) {
    // Validate chain ID
    if req.ChainId != app.chainID {
        return nil, fmt.Errorf("invalid chain-id on InitChain")
    }
    
    // Set initial height
    app.initialHeight = req.InitialHeight
    if app.initialHeight == 0 {
        app.initialHeight = 1
    }
    
    // Initialize states
    initHeader := cmtproto.Header{ChainID: req.ChainId, Time: req.Time}
    app.stateManager.SetState(execModeFinalize, app.cms, initHeader, app.logger, app.streamingManager)
    app.stateManager.SetState(execModeCheck, app.cms, initHeader, app.logger, app.streamingManager)
    
    finalizeState := app.stateManager.GetState(execModeFinalize)
    
    // Store consensus params
    if req.ConsensusParams != nil {
        err := app.StoreConsensusParams(finalizeState.Context(), *req.ConsensusParams)
        if err != nil {
            return nil, err
        }
    }
    
    // Call app's InitChain handler
    if app.abciHandlers.InitChainer != nil {
        res, err := app.abciHandlers.InitChainer(finalizeState.Context(), req)
        if err != nil {
            return nil, err
        }
    }
    
    return &abci.ResponseInitChain{
        ConsensusParams: res.ConsensusParams,
        Validators:      res.Validators,
        AppHash:         app.LastCommitID().Hash,
    }, nil
}

CheckTx (Mempool)

Validates transactions before mempool inclusion:
func (app *BaseApp) CheckTx(req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) {
    // Decode transaction
    tx, err := app.txDecoder(req.Tx)
    if err != nil {
        return sdkerrors.ResponseCheckTxWithEvents(err, 0, 0, nil, false), nil
    }
    
    // Get check state
    checkState := app.stateManager.GetState(execModeCheck)
    ctx := checkState.Context()
    
    // Run transaction in check mode
    gInfo, result, _, err := app.runTx(ctx, tx, execModeCheck)
    if err != nil {
        return sdkerrors.ResponseCheckTxWithEvents(
            err,
            gInfo.GasWanted,
            gInfo.GasUsed,
            nil,
            false,
        ), nil
    }
    
    return &abci.ResponseCheckTx{
        Code:      abci.CodeTypeOK,
        GasWanted: int64(gInfo.GasWanted),
        GasUsed:   int64(gInfo.GasUsed),
        Priority:  result.Priority,
    }, nil
}

CheckTx Types

// New transaction
abci.CheckTxType_New

// Recheck after block (mempool maintenance)
abci.CheckTxType_Recheck

PrepareProposal

Proposer constructs a block:
func (app *BaseApp) PrepareProposal(
    req *abci.RequestPrepareProposal,
) (*abci.ResponsePrepareProposal, error) {
    if app.abciHandlers.PrepareProposal == nil {
        // Default: include all transactions up to MaxTxBytes
        return app.defaultPrepareProposal(req)
    }
    
    // Custom prepare proposal logic
    return app.abciHandlers.PrepareProposal(req)
}

Default Behavior

func (app *BaseApp) defaultPrepareProposal(
    req *abci.RequestPrepareProposal,
) (*abci.ResponsePrepareProposal, error) {
    var txs [][]byte
    var totalBytes int64
    
    // Select transactions from mempool
    for _, txBytes := range req.Txs {
        // Check size limit
        if totalBytes+int64(len(txBytes)) > req.MaxTxBytes {
            break
        }
        
        txs = append(txs, txBytes)
        totalBytes += int64(len(txBytes))
    }
    
    return &abci.ResponsePrepareProposal{
        Txs: txs,
    }, nil
}

ProcessProposal

Validators verify proposed blocks:
func (app *BaseApp) ProcessProposal(
    req *abci.RequestProcessProposal,
) (*abci.ResponseProcessProposal, error) {
    if app.abciHandlers.ProcessProposal == nil {
        // Default: accept if all transactions are valid
        return app.defaultProcessProposal(req)
    }
    
    // Custom verification logic
    return app.abciHandlers.ProcessProposal(req)
}

Validation Checks

All transactions decode successfully and pass basic validation
Total gas wanted doesn’t exceed block gas limit
Transactions are in expected order (if required)
Application-specific validation (e.g., minimum fees)

FinalizeBlock

Executes the agreed-upon block:
func (app *BaseApp) FinalizeBlock(
    req *abci.RequestFinalizeBlock,
) (*abci.ResponseFinalizeBlock, error) {
    // Get finalize state
    finalizeState := app.stateManager.GetState(execModeFinalize)
    ctx := finalizeState.Context()
    
    // Update context with block info
    ctx = ctx.WithBlockHeader(cmtproto.Header{
        ChainID: app.chainID,
        Height:  req.Height,
        Time:    req.Time,
        // ...
    })
    
    // PreBlock (optional)
    if app.abciHandlers.PreBlocker != nil {
        ctx, err := app.abciHandlers.PreBlocker(ctx)
        if err != nil {
            return nil, err
        }
    }
    
    // BeginBlock
    if app.abciHandlers.BeginBlocker != nil {
        err := app.abciHandlers.BeginBlocker(ctx)
        if err != nil {
            return nil, err
        }
    }
    
    // Execute transactions
    txResults := make([]*abci.ExecTxResult, len(req.Txs))
    for i, txBytes := range req.Txs {
        tx, err := app.txDecoder(txBytes)
        if err != nil {
            txResults[i] = &abci.ExecTxResult{
                Code: 1,
                Log:  err.Error(),
            }
            continue
        }
        
        gInfo, result, _, err := app.runTx(ctx, tx, execModeFinalize)
        txResults[i] = &abci.ExecTxResult{
            Code:      codeFromErr(err),
            GasWanted: int64(gInfo.GasWanted),
            GasUsed:   int64(gInfo.GasUsed),
            Events:    result.Events,
        }
    }
    
    // EndBlock
    if app.abciHandlers.EndBlocker != nil {
        err := app.abciHandlers.EndBlocker(ctx)
        if err != nil {
            return nil, err
        }
    }
    
    return &abci.ResponseFinalizeBlock{
        TxResults:             txResults,
        ValidatorUpdates:      validatorUpdates,
        ConsensusParamUpdates: consensusParamUpdates,
    }, nil
}

Execution Order

  1. PreBlock: Process vote extensions, update consensus params
  2. BeginBlock: Mint rewards, distribute fees, update validators
  3. Transactions: Execute each transaction in order
  4. EndBlock: Finalize validator updates, execute governance

Commit

Persists state changes:
func (app *BaseApp) Commit() (*abci.ResponseCommit, error) {
    // Get finalize state
    finalizeState := app.stateManager.GetState(execModeFinalize)
    
    // Write changes from cache to CommitMultiStore
    finalizeState.MultiStore.Write()
    
    // Commit to disk and get commit ID
    commitID := app.cms.Commit()
    
    // Determine retain height for pruning
    retainHeight := app.GetBlockRetentionHeight(commitID.Version)
    
    return &abci.ResponseCommit{
        RetainHeight: retainHeight,
    }, nil
}

Commit ID

type CommitID struct {
    Version int64  // Block height
    Hash    []byte // App hash (state root)
}
The app hash proves state integrity across all nodes.

Vote Extensions

Validators can attach additional signed data to votes:

Extend Vote

func (app *BaseApp) ExtendVote(
    ctx context.Context,
    req *abci.RequestExtendVote,
) (*abci.ResponseExtendVote, error) {
    // Generate vote extension
    voteExt, err := app.abciHandlers.ExtendVoteHandler(ctx, req)
    if err != nil {
        return nil, err
    }
    
    return &abci.ResponseExtendVote{
        VoteExtension: voteExt,
    }, nil
}

Verify Vote Extension

func (app *BaseApp) VerifyVoteExtension(
    ctx context.Context,
    req *abci.RequestVerifyVoteExtension,
) (*abci.ResponseVerifyVoteExtension, error) {
    // Verify vote extension
    err := app.abciHandlers.VerifyVoteExtensionHandler(ctx, req)
    
    return &abci.ResponseVerifyVoteExtension{
        Status: statusFromErr(err),
    }, nil
}

Use Cases

Oracle Prices

Validators submit price feeds in vote extensions

Randomness

Generate verifiable random numbers from validator contributions

Threshold Signatures

Collect signature shares for multisig operations

MEV Prevention

Commit-reveal schemes for transaction ordering

Consensus Parameters

type ConsensusParams struct {
    Block     *BlockParams
    Evidence  *EvidenceParams
    Validator *ValidatorParams
    Version   *VersionParams
    ABCI      *ABCIParams
}

type BlockParams struct {
    MaxBytes int64  // Max block size
    MaxGas   int64  // Max total gas per block
}

Updating Consensus Params

func (app *BaseApp) StoreConsensusParams(
    ctx sdk.Context,
    cp cmtproto.ConsensusParams,
) error {
    if app.paramStore == nil {
        return nil
    }
    
    return app.paramStore.Set(ctx, cp)
}

Info and Query

Info Method

// Location: baseapp/abci.go:156
func (app *BaseApp) Info(_ *abci.RequestInfo) (*abci.ResponseInfo, error) {
    lastCommitID := app.cms.LastCommitID()
    
    return &abci.ResponseInfo{
        Data:             app.name,
        Version:          app.version,
        AppVersion:       app.appVersion,
        LastBlockHeight:  lastCommitID.Version,
        LastBlockAppHash: lastCommitID.Hash,
    }, nil
}

Query Method

// Location: baseapp/abci.go:170
func (app *BaseApp) Query(
    ctx context.Context,
    req *abci.RequestQuery,
) (*abci.ResponseQuery, error) {
    // Default to latest height
    if req.Height == 0 {
        req.Height = app.LastBlockHeight()
    }
    
    // Route query
    path := strings.Split(req.Path, "/")
    switch path[0] {
    case "app":
        return handleAppQuery(app, path, req)
    case "store":
        return handleStoreQuery(app, path, req)
    case "p2p":
        return handleP2PQuery(app, path, req)
    default:
        // gRPC query
        return app.grpcQueryRouter.Route(req.Path)(ctx, req)
    }
}

State Synchronization

CometBFT state sync enables fast bootstrapping:
// List available snapshots
func (app *BaseApp) ListSnapshots(
    req *abci.RequestListSnapshots,
) (*abci.ResponseListSnapshots, error) {
    snapshots, err := app.snapshotManager.List()
    return &abci.ResponseListSnapshots{
        Snapshots: snapshots,
    }, err
}

// Offer snapshot to node
func (app *BaseApp) OfferSnapshot(
    req *abci.RequestOfferSnapshot,
) (*abci.ResponseOfferSnapshot, error) {
    // Accept or reject snapshot
    return app.snapshotManager.Offer(req.Snapshot)
}

// Load snapshot chunk
func (app *BaseApp) LoadSnapshotChunk(
    req *abci.RequestLoadSnapshotChunk,
) (*abci.ResponseLoadSnapshotChunk, error) {
    chunk, err := app.snapshotManager.LoadChunk(req.Height, req.Format, req.Chunk)
    return &abci.ResponseLoadSnapshotChunk{
        Chunk: chunk,
    }, err
}

// Apply snapshot chunk
func (app *BaseApp) ApplySnapshotChunk(
    req *abci.RequestApplySnapshotChunk,
) (*abci.ResponseApplySnapshotChunk, error) {
    return app.snapshotManager.Apply(req.Index, req.Chunk)
}

ABCI++ Features

ABCI++ (introduced in CometBFT v0.38) adds:

PrepareProposal & ProcessProposal

Gives applications control over block construction:
type ABCIHandlers struct {
    PrepareProposal  func(*abci.RequestPrepareProposal) (*abci.ResponsePrepareProposal, error)
    ProcessProposal  func(*abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error)
}
Use cases:
  • Enforce minimum fees
  • Require specific transactions (e.g., oracle updates)
  • Implement MEV protection
  • Custom transaction ordering

Vote Extensions

Enables new consensus mechanisms:
type ABCIHandlers struct {
    ExtendVoteHandler       func(context.Context, *abci.RequestExtendVote) ([]byte, error)
    VerifyVoteExtensionHandler func(context.Context, *abci.RequestVerifyVoteExtension) error
}

Best Practices

ABCI methods block consensus. Avoid expensive operations.
All nodes must produce identical results. Avoid randomness and timestamps.
Reject invalid blocks early to save execution costs.
Return appropriate ABCI response codes, don’t panic.
Track block gas to prevent DoS attacks.

CometBFT Configuration

Key consensus parameters in config.toml:
[consensus]
timeout_propose = "3s"
timeout_propose_delta = "500ms"
timeout_prevote = "1s"
timeout_prevote_delta = "500ms"
timeout_precommit = "1s"
timeout_precommit_delta = "500ms"
timeout_commit = "5s"

[mempool]
size = 5000
cache_size = 10000
max_tx_bytes = 1048576
max_txs_bytes = 1073741824

BaseApp

BaseApp ABCI implementation

Transactions

Transaction lifecycle

CometBFT Docs

CometBFT documentation

ABCI Spec

ABCI specification

Build docs developers (and LLMs) love