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