github.com/pokt-network/tendermint@v0.32.11-0.20230426215212-59310158d3e9/docs/architecture/adr-029-check-tx-consensus.md (about)

     1  # ADR 029: Check block txs before prevote
     2  
     3  ## Changelog
     4  
     5  04-10-2018: Update with link to issue
     6  [#2384](https://github.com/tendermint/tendermint/issues/2384) and reason for rejection
     7  19-09-2018: Initial Draft
     8  
     9  ## Context
    10  
    11  We currently check a tx's validity through 2 ways.
    12  
    13  1. Through checkTx in mempool connection.
    14  2. Through deliverTx in consensus connection.
    15  
    16  The 1st is called when external tx comes in, so the node should be a proposer this time. The 2nd is called when external block comes in and reach the commit phase, the node doesn't need to be the proposer of the block, however it should check the txs in that block.
    17  
    18  In the 2nd situation, if there are many invalid txs in the block, it would be too late for all nodes to discover that most txs in the block are invalid, and we'd better not record invalid txs in the blockchain too.
    19  
    20  ## Proposed solution
    21  
    22  Therefore, we should find a way to check the txs' validity before send out a prevote. Currently we have cs.isProposalComplete() to judge whether a block is complete. We can have
    23  
    24  ```
    25  func (blockExec *BlockExecutor) CheckBlock(block *types.Block) error {
    26     // check txs of block.
    27     for _, tx := range block.Txs {
    28        reqRes := blockExec.proxyApp.CheckTxAsync(tx)
    29        reqRes.Wait()
    30        if reqRes.Response == nil || reqRes.Response.GetCheckTx() == nil || reqRes.Response.GetCheckTx().Code != abci.CodeTypeOK {
    31           return errors.Errorf("tx %v check failed. response: %v", tx, reqRes.Response)
    32        }
    33     }
    34     return nil
    35  }
    36  ```
    37  
    38  such a method in BlockExecutor to check all txs' validity in that block.
    39  
    40  However, this method should not be implemented like that, because checkTx will share the same state used in mempool in the app.  So we should define a new interface method checkBlock in Application to indicate it to use the same state as deliverTx.
    41  
    42  ```
    43  type Application interface {
    44     // Info/Query Connection
    45     Info(RequestInfo) ResponseInfo                // Return application info
    46     SetOption(RequestSetOption) ResponseSetOption // Set application option
    47     Query(RequestQuery) ResponseQuery             // Query for state
    48  
    49     // Mempool Connection
    50     CheckTx(tx []byte) ResponseCheckTx // Validate a tx for the mempool
    51  
    52     // Consensus Connection
    53     InitChain(RequestInitChain) ResponseInitChain // Initialize blockchain with validators and other info from TendermintCore
    54     CheckBlock(RequestCheckBlock) ResponseCheckBlock
    55     BeginBlock(RequestBeginBlock) ResponseBeginBlock // Signals the beginning of a block
    56     DeliverTx(tx []byte) ResponseDeliverTx           // Deliver a tx for full processing
    57     EndBlock(RequestEndBlock) ResponseEndBlock       // Signals the end of a block, returns changes to the validator set
    58     Commit() ResponseCommit                          // Commit the state and return the application Merkle root hash
    59  }
    60  ```
    61  
    62  All app should implement that method. For example, counter:
    63  
    64  ```
    65  func (app *CounterApplication) CheckBlock(block types.Request_CheckBlock) types.ResponseCheckBlock {
    66     if app.serial {
    67     	  app.originalTxCount = app.txCount   //backup the txCount state
    68        for _, tx := range block.CheckBlock.Block.Txs {
    69           if len(tx) > 8 {
    70              return types.ResponseCheckBlock{
    71                 Code: code.CodeTypeEncodingError,
    72                 Log:  fmt.Sprintf("Max tx size is 8 bytes, got %d", len(tx))}
    73           }
    74           tx8 := make([]byte, 8)
    75           copy(tx8[len(tx8)-len(tx):], tx)
    76           txValue := binary.BigEndian.Uint64(tx8)
    77           if txValue < uint64(app.txCount) {
    78              return types.ResponseCheckBlock{
    79                 Code: code.CodeTypeBadNonce,
    80                 Log:  fmt.Sprintf("Invalid nonce. Expected >= %v, got %v", app.txCount, txValue)}
    81           }
    82           app.txCount++
    83        }
    84     }
    85     return types.ResponseCheckBlock{Code: code.CodeTypeOK}
    86  }
    87  ```
    88  
    89  In BeginBlock, the app should restore the state to the orignal state before checking the block:
    90  
    91  ```
    92  func (app *CounterApplication) DeliverTx(tx []byte) types.ResponseDeliverTx {
    93     if app.serial {
    94        app.txCount = app.originalTxCount   //restore the txCount state
    95     }
    96     app.txCount++
    97     return types.ResponseDeliverTx{Code: code.CodeTypeOK}
    98  }
    99  ```
   100  
   101  The txCount is like the nonce in ethermint, it should be restored when entering the deliverTx phase. While some operation like checking the tx signature needs not to be done again. So the deliverTx can focus on how a tx can be applied, ignoring the checking of the tx, because all the checking has already been done in the checkBlock phase before.
   102  
   103  An optional optimization is alter the deliverTx to deliverBlock. For the block has already been checked by checkBlock, so all the txs in it are valid. So the app can cache the block, and in the deliverBlock phase, it just needs to apply the block in the cache. This optimization can save network current in deliverTx.
   104  
   105  
   106  
   107  ## Status
   108  
   109  Rejected
   110  
   111  ## Decision
   112  
   113  Performance impact is considered too great. See [#2384](https://github.com/tendermint/tendermint/issues/2384)
   114  
   115  ## Consequences
   116  
   117  ### Positive
   118  
   119  - more robust to defend the adversary to propose a block full of invalid txs.
   120  
   121  ### Negative
   122  
   123  - add a new interface method. app logic needs to adjust to appeal to it.
   124  - sending all the tx data over the ABCI twice
   125  - potentially redundant validations (eg. signature checks in both CheckBlock and
   126    DeliverTx)
   127  
   128  ### Neutral