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