github.com/evdatsion/aphelion-dpos-bft@v0.32.1/state/execution.go (about)

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