github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/consensus/abci/app.go (about)

     1  package abci
     2  
     3  import (
     4  	"fmt"
     5  	"math/big"
     6  	"runtime/debug"
     7  	"sync"
     8  
     9  	"github.com/hyperledger/burrow/acm/validator"
    10  	"github.com/hyperledger/burrow/bcm"
    11  	"github.com/hyperledger/burrow/consensus/tendermint/codes"
    12  	"github.com/hyperledger/burrow/crypto"
    13  	"github.com/hyperledger/burrow/execution"
    14  	"github.com/hyperledger/burrow/execution/errors"
    15  	"github.com/hyperledger/burrow/logging"
    16  	"github.com/hyperledger/burrow/logging/structure"
    17  	"github.com/hyperledger/burrow/project"
    18  	"github.com/hyperledger/burrow/txs"
    19  	"github.com/tendermint/tendermint/abci/types"
    20  	"github.com/tendermint/tendermint/crypto/encoding"
    21  )
    22  
    23  type Validators interface {
    24  	validator.History
    25  }
    26  
    27  const (
    28  	TendermintValidatorDelayInBlocks = 2
    29  	BurrowValidatorDelayInBlocks     = 1
    30  )
    31  
    32  type App struct {
    33  	// Provides a no-op implementation for all methods (in particular snapshots for now will abort)
    34  	types.BaseApplication
    35  	// Node information to return in Info
    36  	nodeInfo string
    37  	// State
    38  	blockchain      *bcm.Blockchain
    39  	validators      Validators
    40  	mempoolLocker   sync.Locker
    41  	authorizedPeers AuthorizedPeers
    42  	// We need to cache these from BeginBlock for when we need actually need it in Commit
    43  	block *types.RequestBeginBlock
    44  	// Function to use to fail gracefully from panic rather than letting Tendermint make us a zombie
    45  	panicFunc func(error)
    46  	checker   execution.BatchExecutor
    47  	committer execution.BatchCommitter
    48  	txDecoder txs.Decoder
    49  	logger    *logging.Logger
    50  }
    51  
    52  var _ types.Application = &App{}
    53  
    54  func NewApp(nodeInfo string, blockchain *bcm.Blockchain, validators Validators, checker execution.BatchExecutor,
    55  	committer execution.BatchCommitter, txDecoder txs.Decoder, authorizedPeers AuthorizedPeers,
    56  	panicFunc func(error), logger *logging.Logger) *App {
    57  	return &App{
    58  		nodeInfo:        nodeInfo,
    59  		blockchain:      blockchain,
    60  		validators:      validators,
    61  		checker:         checker,
    62  		committer:       committer,
    63  		txDecoder:       txDecoder,
    64  		authorizedPeers: authorizedPeers,
    65  		panicFunc:       panicFunc,
    66  		logger: logger.WithScope("abci.NewApp").With(structure.ComponentKey, "ABCI_App",
    67  			"node_info", nodeInfo),
    68  	}
    69  }
    70  
    71  // Provide the Mempool lock. When provided we will attempt to acquire this lock in a goroutine during the Commit. We
    72  // will keep the checker cache locked until we are able to acquire the mempool lock which signals the end of the commit
    73  // and possible recheck on Tendermint's side.
    74  func (app *App) SetMempoolLocker(mempoolLocker sync.Locker) {
    75  	app.mempoolLocker = mempoolLocker
    76  }
    77  
    78  func (app *App) Info(info types.RequestInfo) types.ResponseInfo {
    79  	return types.ResponseInfo{
    80  		Data:             app.nodeInfo,
    81  		Version:          project.History.CurrentVersion().String(),
    82  		LastBlockHeight:  int64(app.blockchain.LastBlockHeight()),
    83  		LastBlockAppHash: app.blockchain.AppHashAfterLastBlock(),
    84  	}
    85  }
    86  
    87  func (app *App) SetOption(option types.RequestSetOption) (respSetOption types.ResponseSetOption) {
    88  	respSetOption.Log = "SetOption not supported"
    89  	respSetOption.Code = codes.UnsupportedRequestCode
    90  	return
    91  }
    92  
    93  func (app *App) Query(reqQuery types.RequestQuery) (respQuery types.ResponseQuery) {
    94  	defer func() {
    95  		if r := recover(); r != nil {
    96  			app.panicFunc(fmt.Errorf("panic occurred in abci.App/Query: %v\n%s", r, debug.Stack()))
    97  		}
    98  	}()
    99  	respQuery.Log = "Query not supported"
   100  	respQuery.Code = codes.UnsupportedRequestCode
   101  
   102  	switch {
   103  	case isPeersFilterQuery(&reqQuery):
   104  		app.peersFilter(&reqQuery, &respQuery)
   105  	}
   106  	return
   107  }
   108  
   109  func (app *App) InitChain(chain types.RequestInitChain) types.ResponseInitChain {
   110  	defer func() {
   111  		if r := recover(); r != nil {
   112  			app.panicFunc(fmt.Errorf("panic occurred in abci.App/InitChain: %v\n%s", r, debug.Stack()))
   113  		}
   114  	}()
   115  	currentSet := validator.NewTrimSet()
   116  	err := validator.Write(currentSet, app.validators.Validators(0))
   117  	if err != nil {
   118  		panic(fmt.Errorf("could not build current validator set: %v", err))
   119  	}
   120  	if len(chain.Validators) != currentSet.Size() {
   121  		panic(fmt.Errorf("Tendermint passes %d validators to InitChain but Burrow's Blockchain has %d",
   122  			len(chain.Validators), currentSet.Size()))
   123  	}
   124  	for _, v := range chain.Validators {
   125  		pk, err := encoding.PubKeyFromProto(v.GetPubKey())
   126  		if err != nil {
   127  			panic(err)
   128  		}
   129  		err = app.checkValidatorMatches(currentSet, types.Validator{Address: pk.Address().Bytes(), Power: v.Power})
   130  		if err != nil {
   131  			panic(err)
   132  		}
   133  	}
   134  	app.logger.InfoMsg("Initial validator set matches")
   135  	return types.ResponseInitChain{
   136  		AppHash: app.blockchain.AppHashAfterLastBlock(),
   137  	}
   138  }
   139  
   140  func (app *App) BeginBlock(block types.RequestBeginBlock) (respBeginBlock types.ResponseBeginBlock) {
   141  	app.block = &block
   142  	defer func() {
   143  		if r := recover(); r != nil {
   144  			app.panicFunc(fmt.Errorf("panic occurred in abci.App/BeginBlock: %v\n%s", r, debug.Stack()))
   145  		}
   146  	}()
   147  	if block.Header.Height > 1 {
   148  		var err error
   149  		previousValidators := validator.NewTrimSet()
   150  		// Tendermint runs two blocks behind plus we are updating in end block validators updated last round
   151  		err = validator.Write(previousValidators,
   152  			app.validators.Validators(BurrowValidatorDelayInBlocks+TendermintValidatorDelayInBlocks))
   153  		if err != nil {
   154  			panic(fmt.Errorf("could not build current validator set: %v", err))
   155  		}
   156  		if len(block.LastCommitInfo.Votes) != previousValidators.Size() {
   157  			err = fmt.Errorf("Tendermint passes %d validators to BeginBlock but Burrow's has %d:\n %v",
   158  				len(block.LastCommitInfo.Votes), previousValidators.Size(), previousValidators.String())
   159  			panic(err)
   160  		}
   161  		for _, v := range block.LastCommitInfo.Votes {
   162  			err = app.checkValidatorMatches(previousValidators, v.Validator)
   163  			if err != nil {
   164  				panic(err)
   165  			}
   166  		}
   167  	}
   168  	return
   169  }
   170  
   171  func (app *App) checkValidatorMatches(ours validator.Reader, v types.Validator) error {
   172  	address, err := crypto.AddressFromBytes(v.Address)
   173  	if err != nil {
   174  		return err
   175  	}
   176  	power, err := ours.Power(address)
   177  	if err != nil {
   178  		return err
   179  	}
   180  	if power.Cmp(big.NewInt(v.Power)) != 0 {
   181  		return fmt.Errorf("validator %v has power %d from Tendermint but power %d from Burrow",
   182  			address, v.Power, power)
   183  	}
   184  	return nil
   185  }
   186  
   187  func (app *App) CheckTx(req types.RequestCheckTx) types.ResponseCheckTx {
   188  	const logHeader = "CheckTx"
   189  	defer func() {
   190  		if r := recover(); r != nil {
   191  			app.panicFunc(fmt.Errorf("panic occurred in abci.App/CheckTx: %v\n%s", r, debug.Stack()))
   192  		}
   193  	}()
   194  
   195  	checkTx := ExecuteTx(logHeader, app.checker, app.txDecoder, req.GetTx())
   196  
   197  	logger := WithEvents(app.logger, checkTx.Events)
   198  
   199  	if checkTx.Code == codes.TxExecutionSuccessCode {
   200  		logger.InfoMsg("Execution success")
   201  	} else {
   202  		logger.InfoMsg("Execution error",
   203  			"code", checkTx.Code,
   204  			"log", checkTx.Log)
   205  	}
   206  
   207  	return checkTx
   208  }
   209  
   210  func (app *App) DeliverTx(req types.RequestDeliverTx) types.ResponseDeliverTx {
   211  	const logHeader = "DeliverTx"
   212  	defer func() {
   213  		if r := recover(); r != nil {
   214  			app.panicFunc(fmt.Errorf("panic occurred in abci.App/DeliverTx: %v\n%s", r, debug.Stack()))
   215  		}
   216  	}()
   217  
   218  	checkTx := ExecuteTx(logHeader, app.committer, app.txDecoder, req.GetTx())
   219  
   220  	logger := WithEvents(app.logger, checkTx.Events)
   221  
   222  	if checkTx.Code == codes.TxExecutionSuccessCode {
   223  		logger.InfoMsg("Execution success")
   224  	} else {
   225  		logger.InfoMsg("Execution error",
   226  			"code", checkTx.Code,
   227  			"log", checkTx.Log)
   228  	}
   229  
   230  	return DeliverTxFromCheckTx(checkTx)
   231  }
   232  
   233  func (app *App) EndBlock(reqEndBlock types.RequestEndBlock) types.ResponseEndBlock {
   234  	var validatorUpdates []types.ValidatorUpdate
   235  	defer func() {
   236  		if r := recover(); r != nil {
   237  			app.panicFunc(fmt.Errorf("panic occurred in abci.App/EndBlock: %v\n%s", r, debug.Stack()))
   238  		}
   239  	}()
   240  	err := app.validators.ValidatorChanges(BurrowValidatorDelayInBlocks).IterateValidators(func(id crypto.Addressable, power *big.Int) error {
   241  		app.logger.InfoMsg("Updating validator power", "validator_address", id.GetAddress(),
   242  			"new_power", power)
   243  		pk, err := encoding.PubKeyToProto(id.GetPublicKey().TendermintPubKey())
   244  		if err != nil {
   245  			panic(err)
   246  		}
   247  		validatorUpdates = append(validatorUpdates, types.ValidatorUpdate{
   248  			PubKey: pk,
   249  			// Must ensure power fits in an int64 during execution
   250  			Power: power.Int64(),
   251  		})
   252  		return nil
   253  	})
   254  	if err != nil {
   255  		panic(err)
   256  	}
   257  	return types.ResponseEndBlock{
   258  		ValidatorUpdates: validatorUpdates,
   259  	}
   260  }
   261  
   262  func (app *App) Commit() types.ResponseCommit {
   263  	defer func() {
   264  		if r := recover(); r != nil {
   265  			app.panicFunc(fmt.Errorf("panic occurred in abci.App/Commit: %v\n%s", r, debug.Stack()))
   266  		}
   267  	}()
   268  	blockTime := app.block.Header.Time
   269  	app.logger.InfoMsg("Committing block",
   270  		"tag", "Commit",
   271  		structure.ScopeKey, "Commit()",
   272  		"height", app.block.Header.Height,
   273  		"hash", app.block.Hash,
   274  		"block_time", blockTime,
   275  		"last_block_time", app.blockchain.LastBlockTime(),
   276  		"last_block_duration", app.blockchain.LastCommitDuration(),
   277  		"last_block_hash", app.blockchain.LastBlockHash(),
   278  	)
   279  
   280  	// Lock the checker while we reset it and possibly while recheckTxs replays transactions
   281  	app.checker.Lock()
   282  	defer func() {
   283  		// Tendermint may replay transactions to the check cache during a recheck, which happens after we have returned
   284  		// from Commit(). The mempool is locked by Tendermint for the duration of the commit phase; during Commit() and
   285  		// the subsequent mempool.Update() so we schedule an acquisition of the mempool lock in a goroutine in order to
   286  		// 'observe' the mempool unlock event that happens later on. By keeping the checker read locked during that
   287  		// period we can ensure that anything querying the checker (such as service.MempoolAccounts()) will block until
   288  		// the full Tendermint commit phase has completed.
   289  		if app.mempoolLocker != nil {
   290  			go func() {
   291  				// we won't get this until after the commit and we will acquire strictly after this commit phase has
   292  				// ended (i.e. when Tendermint's BlockExecutor.Commit() returns
   293  				app.mempoolLocker.Lock()
   294  				// Prevent any mempool getting relocked while we unlock - we could just unlock immediately but if a new
   295  				// commit starts gives goroutines blocked on checker a chance to progress before the next commit phase
   296  				defer app.mempoolLocker.Unlock()
   297  				app.checker.Unlock()
   298  			}()
   299  		} else {
   300  			// If we have not be provided with access to the mempool lock
   301  			app.checker.Unlock()
   302  		}
   303  	}()
   304  
   305  	appHash, err := app.committer.Commit(&app.block.Header)
   306  	if err != nil {
   307  		panic(errors.Wrap(err, "Could not commit transactions in block to execution state"))
   308  	}
   309  	err = app.checker.Reset()
   310  	if err != nil {
   311  		panic(errors.Wrap(err, "could not reset check cache during commit"))
   312  	}
   313  	// Commit to our blockchain state which will checkpoint the previous app hash by saving it to the database
   314  	// (we know the previous app hash is safely committed because we are about to commit the next)
   315  	err = app.blockchain.CommitBlock(blockTime, app.block.Hash, appHash)
   316  	if err != nil {
   317  		panic(fmt.Errorf("could not commit block to blockchain state: %v", err))
   318  	}
   319  	app.logger.InfoMsg("Committed block")
   320  
   321  	return types.ResponseCommit{
   322  		Data: appHash,
   323  	}
   324  }