github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/bft/state/execution.go (about)

     1  package state
     2  
     3  import (
     4  	"fmt"
     5  	"log/slog"
     6  
     7  	"github.com/gnolang/gno/tm2/pkg/amino"
     8  	abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types"
     9  	"github.com/gnolang/gno/tm2/pkg/bft/appconn"
    10  	"github.com/gnolang/gno/tm2/pkg/bft/fail"
    11  	mempl "github.com/gnolang/gno/tm2/pkg/bft/mempool"
    12  	"github.com/gnolang/gno/tm2/pkg/bft/types"
    13  	typesver "github.com/gnolang/gno/tm2/pkg/bft/types/version"
    14  	tmver "github.com/gnolang/gno/tm2/pkg/bft/version"
    15  	"github.com/gnolang/gno/tm2/pkg/crypto"
    16  	dbm "github.com/gnolang/gno/tm2/pkg/db"
    17  	"github.com/gnolang/gno/tm2/pkg/events"
    18  )
    19  
    20  // -----------------------------------------------------------------------------
    21  // BlockExecutor handles block execution and state updates.
    22  // It exposes ApplyBlock(), which validates & executes the block, updates state w/ ABCI responses,
    23  // then commits and updates the mempool atomically, then saves state.
    24  
    25  // BlockExecutor provides the context and accessories for properly executing a block.
    26  type BlockExecutor struct {
    27  	// save state, validators, consensus params, abci responses here
    28  	db dbm.DB
    29  
    30  	// execute the app against this
    31  	proxyApp appconn.Consensus
    32  
    33  	// events
    34  	evsw events.EventSwitch
    35  
    36  	// manage the mempool lock during commit
    37  	// and update both with block results after commit.
    38  	mempool mempl.Mempool
    39  
    40  	logger *slog.Logger
    41  }
    42  
    43  type BlockExecutorOption func(executor *BlockExecutor)
    44  
    45  // NewBlockExecutor returns a new BlockExecutor with a NopEventBus.
    46  // Call SetEventBus to provide one.
    47  func NewBlockExecutor(db dbm.DB, logger *slog.Logger, proxyApp appconn.Consensus, mempool mempl.Mempool, options ...BlockExecutorOption) *BlockExecutor {
    48  	res := &BlockExecutor{
    49  		db:       db,
    50  		proxyApp: proxyApp,
    51  		evsw:     events.NilEventSwitch(),
    52  		mempool:  mempool,
    53  		logger:   logger,
    54  	}
    55  
    56  	for _, option := range options {
    57  		option(res)
    58  	}
    59  
    60  	return res
    61  }
    62  
    63  func (blockExec *BlockExecutor) DB() dbm.DB {
    64  	return blockExec.db
    65  }
    66  
    67  func (blockExec *BlockExecutor) SetEventSwitch(evsw events.EventSwitch) {
    68  	blockExec.evsw = evsw
    69  }
    70  
    71  // CreateProposalBlock calls state.MakeBlock with txs from the mempool.
    72  func (blockExec *BlockExecutor) CreateProposalBlock(
    73  	height int64,
    74  	state State, commit *types.Commit,
    75  	proposerAddr crypto.Address,
    76  ) (*types.Block, *types.PartSet) {
    77  	maxDataBytes := state.ConsensusParams.Block.MaxDataBytes
    78  	maxGas := state.ConsensusParams.Block.MaxGas
    79  
    80  	txs := blockExec.mempool.ReapMaxBytesMaxGas(maxDataBytes, maxGas)
    81  
    82  	return state.MakeBlock(height, txs, commit, proposerAddr)
    83  }
    84  
    85  // ValidateBlock validates the given block against the given state.
    86  // If the block is invalid, it returns an error.
    87  // Validation does not mutate state, but does require historical information from the stateDB
    88  func (blockExec *BlockExecutor) ValidateBlock(state State, block *types.Block) error {
    89  	return validateBlock(blockExec.db, state, block)
    90  }
    91  
    92  // ApplyBlock validates the block against the state, executes it against the app,
    93  // fires the relevant events, commits the app, and saves the new state and responses.
    94  // It's the only function that needs to be called
    95  // from outside this package to process and commit an entire block.
    96  // It takes a blockID to avoid recomputing the parts hash.
    97  func (blockExec *BlockExecutor) ApplyBlock(state State, blockID types.BlockID, block *types.Block) (State, error) {
    98  	if err := blockExec.ValidateBlock(state, block); err != nil {
    99  		return state, InvalidBlockError(err)
   100  	}
   101  
   102  	abciResponses, err := execBlockOnProxyApp(blockExec.logger, blockExec.proxyApp, block, blockExec.db)
   103  	if err != nil {
   104  		return state, ProxyAppConnError(err)
   105  	}
   106  
   107  	fail.Fail() // XXX
   108  
   109  	// Save the results before we commit.
   110  	saveABCIResponses(blockExec.db, block.Height, abciResponses)
   111  
   112  	// Save the transaction results
   113  	for index, tx := range block.Txs {
   114  		saveTxResultIndex(
   115  			blockExec.db,
   116  			tx.Hash(),
   117  			TxResultIndex{
   118  				BlockNum: block.Height,
   119  				TxIndex:  uint32(index),
   120  			},
   121  		)
   122  	}
   123  
   124  	fail.Fail() // XXX
   125  
   126  	// validate the validator updates and convert to tendermint types
   127  	abciValUpdates := abciResponses.EndBlock.ValidatorUpdates
   128  	err = validateValidatorUpdates(abciValUpdates, *state.ConsensusParams.Validator)
   129  	if err != nil {
   130  		return state, fmt.Errorf("Error in validator updates: %w", err)
   131  	}
   132  	if len(abciValUpdates) > 0 {
   133  		blockExec.logger.Info("Updates to validators", "updates", abciValUpdates)
   134  	}
   135  
   136  	// Update the state with the block and responses.
   137  	state, err = updateState(state, blockID, &block.Header, abciResponses)
   138  	if err != nil {
   139  		return state, fmt.Errorf("Commit failed for application: %w", err)
   140  	}
   141  
   142  	// Lock mempool, commit app state, update mempoool.
   143  	appHash, err := blockExec.Commit(state, block, abciResponses.DeliverTxs)
   144  	if err != nil {
   145  		return state, fmt.Errorf("Commit failed for application: %w", err)
   146  	}
   147  
   148  	fail.Fail() // XXX
   149  
   150  	// Update the app hash and save the state.
   151  	state.AppHash = appHash
   152  	SaveState(blockExec.db, state)
   153  
   154  	fail.Fail() // XXX
   155  
   156  	// Events are fired after everything else.
   157  	// NOTE: if we crash between Commit and Save, events wont be fired during replay
   158  	fireEvents(blockExec.evsw, block, abciResponses)
   159  
   160  	return state, nil
   161  }
   162  
   163  // Commit locks the mempool, runs the ABCI Commit message, and updates the
   164  // mempool.
   165  // It returns the result of calling abci.Commit (the AppHash), and an error.
   166  // The Mempool must be locked during commit and update because state is
   167  // typically reset on Commit and old txs must be replayed against committed
   168  // state before new txs are run in the mempool, lest they be invalid.
   169  func (blockExec *BlockExecutor) Commit(
   170  	state State,
   171  	block *types.Block,
   172  	deliverTxResponses []abci.ResponseDeliverTx,
   173  ) ([]byte, error) {
   174  	blockExec.mempool.Lock()
   175  	defer blockExec.mempool.Unlock()
   176  
   177  	// while mempool is Locked, flush to ensure all async requests have completed
   178  	// in the ABCI app before Commit.
   179  	err := blockExec.mempool.FlushAppConn()
   180  	if err != nil {
   181  		blockExec.logger.Error("Client error during mempool.FlushAppConn", "err", err)
   182  		return nil, err
   183  	}
   184  
   185  	// Commit block, get hash back
   186  	res, err := blockExec.proxyApp.CommitSync()
   187  	if err != nil {
   188  		blockExec.logger.Error(
   189  			"Client error during proxyAppConn.CommitSync",
   190  			"err", err,
   191  		)
   192  		return nil, err
   193  	}
   194  	// ResponseCommit has no error code - just data
   195  
   196  	blockExec.logger.Info(
   197  		"Committed state",
   198  		"height", block.Height,
   199  		"txs", block.NumTxs,
   200  		"appHash", fmt.Sprintf("%X", res.Data),
   201  	)
   202  
   203  	// Update mempool.
   204  	err = blockExec.mempool.Update(
   205  		block.Height,
   206  		block.Txs,
   207  		deliverTxResponses,
   208  		TxPreCheck(state),
   209  		state.ConsensusParams.Block.MaxTxBytes,
   210  	)
   211  
   212  	return res.Data, err
   213  }
   214  
   215  // ---------------------------------------------------------
   216  // Helper functions for executing blocks and updating state
   217  
   218  // Executes block's transactions on proxyAppConn.
   219  // Returns a list of transaction results and updates to the validator set
   220  func execBlockOnProxyApp(
   221  	logger *slog.Logger,
   222  	proxyAppConn appconn.Consensus,
   223  	block *types.Block,
   224  	stateDB dbm.DB,
   225  ) (*ABCIResponses, error) {
   226  	validTxs, invalidTxs := 0, 0
   227  
   228  	txIndex := 0
   229  	abciResponses := NewABCIResponses(block)
   230  
   231  	// Execute transactions and get hash.
   232  	proxyCb := func(req abci.Request, res abci.Response) {
   233  		if res, ok := res.(abci.ResponseDeliverTx); ok {
   234  			// TODO: make use of res.Log
   235  			// TODO: make use of this info
   236  			// Blocks may include invalid txs.
   237  			if res.Error == nil {
   238  				validTxs++
   239  			} else {
   240  				logger.Debug("Invalid tx", "error", res.Error, "log", res.Log)
   241  				invalidTxs++
   242  			}
   243  			abciResponses.DeliverTxs[txIndex] = res
   244  			txIndex++
   245  		}
   246  	}
   247  	proxyAppConn.SetResponseCallback(proxyCb)
   248  
   249  	commitInfo := getBeginBlockLastCommitInfo(block, stateDB)
   250  
   251  	// Begin block
   252  	var err error
   253  	abciResponses.BeginBlock, err = proxyAppConn.BeginBlockSync(abci.RequestBeginBlock{
   254  		Hash:           block.Hash(),
   255  		Header:         block.Header.Copy(),
   256  		LastCommitInfo: &commitInfo,
   257  	})
   258  	if err != nil {
   259  		logger.Error("Error in proxyAppConn.BeginBlock", "err", err)
   260  		return nil, err
   261  	}
   262  
   263  	// Run txs of block.
   264  	for _, tx := range block.Txs {
   265  		proxyAppConn.DeliverTxAsync(abci.RequestDeliverTx{Tx: tx})
   266  		if err := proxyAppConn.Error(); err != nil {
   267  			return nil, err
   268  		}
   269  	}
   270  
   271  	// End block.
   272  	abciResponses.EndBlock, err = proxyAppConn.EndBlockSync(abci.RequestEndBlock{Height: block.Height})
   273  	if err != nil {
   274  		logger.Error("Error in proxyAppConn.EndBlock", "err", err)
   275  		return nil, err
   276  	}
   277  
   278  	logger.Info("Executed block", "height", block.Height, "validTxs", validTxs, "invalidTxs", invalidTxs)
   279  
   280  	return abciResponses, nil
   281  }
   282  
   283  func getBeginBlockLastCommitInfo(block *types.Block, stateDB dbm.DB) abci.LastCommitInfo {
   284  	voteInfos := make([]abci.VoteInfo, block.LastCommit.Size())
   285  	var lastValSet *types.ValidatorSet
   286  	var err error
   287  	if block.Height > 1 {
   288  		lastValSet, err = LoadValidators(stateDB, block.Height-1)
   289  		if err != nil {
   290  			panic(err) // shouldn't happen
   291  		}
   292  
   293  		// Sanity check that commit length matches validator set size -
   294  		// only applies after first block
   295  
   296  		precommitLen := block.LastCommit.Size()
   297  		valSetLen := len(lastValSet.Validators)
   298  		if precommitLen != valSetLen {
   299  			// sanity check
   300  			panic(fmt.Sprintf("precommit length (%d) doesn't match valset length (%d) at height %d\n\n%v\n\n%v",
   301  				precommitLen, valSetLen, block.Height, block.LastCommit.Precommits, lastValSet.Validators))
   302  		}
   303  	} else {
   304  		lastValSet = types.NewValidatorSet(nil)
   305  	}
   306  
   307  	for i, val := range lastValSet.Validators {
   308  		var vote *types.CommitSig
   309  		if i < len(block.LastCommit.Precommits) {
   310  			vote = block.LastCommit.Precommits[i]
   311  		}
   312  		voteInfo := abci.VoteInfo{
   313  			Address:         val.Address,
   314  			Power:           val.VotingPower,
   315  			SignedLastBlock: vote != nil,
   316  		}
   317  		voteInfos[i] = voteInfo
   318  	}
   319  
   320  	commitInfo := abci.LastCommitInfo{
   321  		Round: int32(block.LastCommit.Round()),
   322  		Votes: voteInfos,
   323  	}
   324  	return commitInfo
   325  }
   326  
   327  func validateValidatorUpdates(abciUpdates []abci.ValidatorUpdate,
   328  	params abci.ValidatorParams,
   329  ) error {
   330  	for _, valUpdate := range abciUpdates {
   331  		if valUpdate.Power < 0 {
   332  			return fmt.Errorf("voting power can't be negative %v", valUpdate)
   333  		} else if valUpdate.Power == 0 {
   334  			// continue, since this is deleting the validator, and thus there is no
   335  			// pubkey to check
   336  			continue
   337  		}
   338  
   339  		// Check if validator's pubkey matches an ABCI type in the consensus params
   340  		pubkeyTypeURL := amino.GetTypeURL(valUpdate.PubKey)
   341  		if !params.IsValidPubKeyTypeURL(pubkeyTypeURL) {
   342  			return fmt.Errorf("validator %v is using pubkey %s, which is unsupported for consensus",
   343  				valUpdate, pubkeyTypeURL)
   344  		}
   345  	}
   346  	return nil
   347  }
   348  
   349  // updateState returns a new State updated according to the header and responses.
   350  func updateState(
   351  	state State,
   352  	blockID types.BlockID,
   353  	header *types.Header,
   354  	abciResponses *ABCIResponses,
   355  ) (State, error) {
   356  	// Copy the valset so we can apply changes from EndBlock
   357  	// and update s.LastValidators and s.Validators.
   358  	nValSet := state.NextValidators.Copy()
   359  
   360  	// Update the validator set with the latest abciResponses.
   361  	lastHeightValsChanged := state.LastHeightValidatorsChanged
   362  	if u := abciResponses.EndBlock.ValidatorUpdates; len(u) > 0 {
   363  		err := nValSet.UpdateWithABCIValidatorUpdates(u)
   364  		if err != nil {
   365  			return state, fmt.Errorf("Error changing validator set: %w", err)
   366  		}
   367  		// Change results from this height but only applies to the next next height.
   368  		lastHeightValsChanged = header.Height + 1 + 1
   369  	}
   370  
   371  	// Update validator proposer priority and set state variables.
   372  	nValSet.IncrementProposerPriority(1)
   373  
   374  	// Update the params with the latest abciResponses.
   375  	nextParams := state.ConsensusParams
   376  	lastHeightParamsChanged := state.LastHeightConsensusParamsChanged
   377  	if abciResponses.EndBlock.ConsensusParams != nil {
   378  		// NOTE: must not mutate s.ConsensusParams
   379  		nextParams = state.ConsensusParams.Update(*abciResponses.EndBlock.ConsensusParams)
   380  		err := types.ValidateConsensusParams(nextParams)
   381  		if err != nil {
   382  			return state, fmt.Errorf("Error updating consensus params: %w", err)
   383  		}
   384  		// Change results from this height but only applies to the next height.
   385  		lastHeightParamsChanged = header.Height + 1
   386  	}
   387  
   388  	// NOTE: the AppHash has not been populated.
   389  	// It will be filled on state.Save.
   390  	return State{
   391  		SoftwareVersion:                  tmver.Version,
   392  		BlockVersion:                     typesver.BlockVersion,
   393  		AppVersion:                       state.AppVersion, // TODO
   394  		ChainID:                          state.ChainID,
   395  		LastBlockHeight:                  header.Height,
   396  		LastBlockTotalTx:                 state.LastBlockTotalTx + header.NumTxs,
   397  		LastBlockID:                      blockID,
   398  		LastBlockTime:                    header.Time,
   399  		NextValidators:                   nValSet,
   400  		Validators:                       state.NextValidators.Copy(),
   401  		LastValidators:                   state.Validators.Copy(),
   402  		LastHeightValidatorsChanged:      lastHeightValsChanged,
   403  		ConsensusParams:                  nextParams,
   404  		LastHeightConsensusParamsChanged: lastHeightParamsChanged,
   405  		LastResultsHash:                  abciResponses.ResultsHash(),
   406  		AppHash:                          nil,
   407  	}, nil
   408  }
   409  
   410  // Fire NewBlock, NewBlockHeader.
   411  // Fire TxEvent for every tx.
   412  // NOTE: if Tendermint crashes before commit, some or all of these events may be published again.
   413  func fireEvents(evsw events.EventSwitch, block *types.Block, abciResponses *ABCIResponses) {
   414  	evsw.FireEvent(types.EventNewBlock{
   415  		Block:            block,
   416  		ResultBeginBlock: abciResponses.BeginBlock,
   417  		ResultEndBlock:   abciResponses.EndBlock,
   418  	})
   419  	evsw.FireEvent(types.EventNewBlockHeader{
   420  		Header:           block.Header,
   421  		ResultBeginBlock: abciResponses.BeginBlock,
   422  		ResultEndBlock:   abciResponses.EndBlock,
   423  	})
   424  
   425  	for i, tx := range block.Data.Txs {
   426  		evsw.FireEvent(types.EventTx{Result: types.TxResult{
   427  			Height:   block.Height,
   428  			Index:    uint32(i),
   429  			Tx:       tx,
   430  			Response: (abciResponses.DeliverTxs[i]),
   431  		}})
   432  	}
   433  
   434  	if u := abciResponses.EndBlock.ValidatorUpdates; len(u) > 0 {
   435  		evsw.FireEvent(
   436  			types.EventValidatorSetUpdates{ValidatorUpdates: u})
   437  	}
   438  }
   439  
   440  // ----------------------------------------------------------------------------------------------------
   441  // Execute block without state. TODO: eliminate
   442  
   443  // ExecCommitBlock executes and commits a block on the proxyApp without validating or mutating the state.
   444  // It returns the application root hash (result of abci.Commit).
   445  func ExecCommitBlock(
   446  	appConnConsensus appconn.Consensus,
   447  	block *types.Block,
   448  	logger *slog.Logger,
   449  	stateDB dbm.DB,
   450  ) ([]byte, error) {
   451  	_, err := execBlockOnProxyApp(logger, appConnConsensus, block, stateDB)
   452  	if err != nil {
   453  		logger.Error("Error executing block on proxy app", "height", block.Height, "err", err)
   454  		return nil, err
   455  	}
   456  	// Commit block, get hash back
   457  	res, err := appConnConsensus.CommitSync()
   458  	if err != nil {
   459  		logger.Error("Client error during proxyAppConn.CommitSync", "err", res)
   460  		return nil, err
   461  	}
   462  	// ResponseCommit has no error or log, just data
   463  	return res.Data, nil
   464  }