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 }