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 }