github.com/okex/exchain@v1.8.0/libs/tendermint/consensus/consensus_commit.go (about) 1 package consensus 2 3 import ( 4 "bytes" 5 "encoding/hex" 6 "fmt" 7 "github.com/okex/exchain/libs/iavl" 8 iavlcfg "github.com/okex/exchain/libs/iavl/config" 9 "github.com/okex/exchain/libs/system/trace" 10 cfg "github.com/okex/exchain/libs/tendermint/config" 11 cstypes "github.com/okex/exchain/libs/tendermint/consensus/types" 12 "github.com/okex/exchain/libs/tendermint/libs/fail" 13 tmos "github.com/okex/exchain/libs/tendermint/libs/os" 14 sm "github.com/okex/exchain/libs/tendermint/state" 15 "github.com/okex/exchain/libs/tendermint/types" 16 tmtime "github.com/okex/exchain/libs/tendermint/types/time" 17 "time" 18 ) 19 20 func (cs *State) dumpElapsed(trc *trace.Tracer, schema string) { 21 trace.GetElapsedInfo().AddInfo(schema, trc.Format()) 22 trc.Reset() 23 } 24 25 func (cs *State) initNewHeight() { 26 // waiting finished and enterNewHeight by timeoutNewHeight 27 if cs.Step == cstypes.RoundStepNewHeight { 28 // init StartTime 29 cs.StartTime = tmtime.Now() 30 cs.dumpElapsed(cs.blockTimeTrc, trace.LastBlockTime) 31 cs.traceDump() 32 } 33 } 34 35 func (cs *State) traceDump() { 36 if cs.Logger == nil { 37 return 38 } 39 40 trace.GetElapsedInfo().AddInfo(trace.CommitRound, fmt.Sprintf("%d", cs.CommitRound)) 41 trace.GetElapsedInfo().AddInfo(trace.Round, fmt.Sprintf("%d", cs.Round)) 42 trace.GetElapsedInfo().AddInfo(trace.BlockParts, fmt.Sprintf("%d|%d|%d|%d/%d", 43 cs.bt.droppedDue2WrongHeight, 44 cs.bt.droppedDue2NotExpected, 45 cs.bt.droppedDue2Error, 46 cs.bt.droppedDue2NotAdded, 47 cs.bt.totalParts, 48 )) 49 50 trace.GetElapsedInfo().AddInfo(trace.BlockPartsP2P, fmt.Sprintf("%d|%d|%d", 51 cs.bt.bpNOTransByACK, cs.bt.bpNOTransByData, cs.bt.bpSend)) 52 53 trace.GetElapsedInfo().AddInfo(trace.Produce, cs.trc.Format()) 54 trace.GetElapsedInfo().Dump(cs.Logger.With("module", "main")) 55 cs.trc.Reset() 56 } 57 58 // Enter: +2/3 precommits for block 59 func (cs *State) enterCommit(height int64, commitRound int) { 60 logger := cs.Logger.With("height", height, "commitRound", commitRound) 61 62 if cs.Height != height || cstypes.RoundStepCommit <= cs.Step { 63 logger.Debug(fmt.Sprintf( 64 "enterCommit(%v/%v): Invalid args. Current step: %v/%v/%v", 65 height, 66 commitRound, 67 cs.Height, 68 cs.Round, 69 cs.Step)) 70 return 71 } 72 73 cs.initNewHeight() 74 cs.trc.Pin("%s-%d", "Commit", cs.Round) 75 76 logger.Info(fmt.Sprintf("enterCommit(%v/%v). Current: %v/%v/%v", height, commitRound, cs.Height, cs.Round, cs.Step)) 77 78 defer func() { 79 // Done enterCommit: 80 // keep cs.Round the same, commitRound points to the right Precommits set. 81 cs.updateRoundStep(cs.Round, cstypes.RoundStepCommit) 82 cs.CommitRound = commitRound 83 cs.newStep() 84 85 // Maybe finalize immediately. 86 cs.tryFinalizeCommit(height) 87 }() 88 89 blockID, ok := cs.Votes.Precommits(commitRound).TwoThirdsMajority() 90 if !ok { 91 panic("RunActionCommit() expects +2/3 precommits") 92 } 93 94 // The Locked* fields no longer matter. 95 // Move them over to ProposalBlock if they match the commit hash, 96 // otherwise they'll be cleared in updateToState. 97 if cs.LockedBlock.HashesTo(blockID.Hash) { 98 logger.Info("Commit is for locked block. Set ProposalBlock=LockedBlock", "blockHash", blockID.Hash) 99 cs.ProposalBlock = cs.LockedBlock 100 cs.ProposalBlockParts = cs.LockedBlockParts 101 } 102 103 // If we don't have the block being committed, set up to get it. 104 if !cs.ProposalBlock.HashesTo(blockID.Hash) { 105 if !cs.ProposalBlockParts.HasHeader(blockID.PartsHeader) { 106 logger.Info( 107 "Commit is for a block we don't know about. Set ProposalBlock=nil", 108 "proposal", 109 cs.ProposalBlock.Hash(), 110 "commit", 111 blockID.Hash) 112 // We're getting the wrong block. 113 // Set up ProposalBlockParts and keep waiting. 114 cs.ProposalBlock = nil 115 cs.Logger.Info("enterCommit proposalBlockPart reset ,because of mismatch hash,", 116 "origin", hex.EncodeToString(cs.ProposalBlockParts.Hash()), "after", blockID.Hash) 117 cs.ProposalBlockParts = types.NewPartSetFromHeader(blockID.PartsHeader) 118 cs.eventBus.PublishEventValidBlock(cs.RoundStateEvent()) 119 cs.evsw.FireEvent(types.EventValidBlock, &cs.RoundState) 120 } 121 // else { 122 // We just need to keep waiting. 123 // } 124 } 125 } 126 127 // If we have the block AND +2/3 commits for it, finalize. 128 func (cs *State) tryFinalizeCommit(height int64) { 129 logger := cs.Logger.With("height", height) 130 131 if cs.Height != height { 132 panic(fmt.Sprintf("tryFinalizeCommit() cs.Height: %v vs height: %v", cs.Height, height)) 133 } 134 135 blockID, ok := cs.Votes.Precommits(cs.CommitRound).TwoThirdsMajority() 136 if !ok || len(blockID.Hash) == 0 { 137 logger.Error("Attempt to finalize failed. There was no +2/3 majority, or +2/3 was for <nil>.") 138 return 139 } 140 if !cs.ProposalBlock.HashesTo(blockID.Hash) { 141 // TODO: this happens every time if we're not a validator (ugly logs) 142 // TODO: ^^ wait, why does it matter that we're a validator? 143 logger.Info( 144 "Attempt to finalize failed. We don't have the commit block.", 145 "proposal-block", 146 cs.ProposalBlock.Hash(), 147 "commit-block", 148 blockID.Hash) 149 return 150 } 151 152 // go 153 cs.finalizeCommit(height) 154 } 155 156 // Increment height and goto cstypes.RoundStepNewHeight 157 func (cs *State) finalizeCommit(height int64) { 158 if cs.Height != height || cs.Step != cstypes.RoundStepCommit { 159 cs.Logger.Debug(fmt.Sprintf( 160 "finalizeCommit(%v): Invalid args. Current step: %v/%v/%v", 161 height, 162 cs.Height, 163 cs.Round, 164 cs.Step)) 165 return 166 } 167 168 blockID, ok := cs.Votes.Precommits(cs.CommitRound).TwoThirdsMajority() 169 block, blockParts := cs.ProposalBlock, cs.ProposalBlockParts 170 171 if !ok { 172 panic(fmt.Sprintf("Cannot finalizeCommit, commit does not have two thirds majority")) 173 } 174 if !blockParts.HasHeader(blockID.PartsHeader) { 175 panic(fmt.Sprintf("Expected ProposalBlockParts header to be commit header")) 176 } 177 if !block.HashesTo(blockID.Hash) { 178 panic(fmt.Sprintf("Cannot finalizeCommit, ProposalBlock does not hash to commit hash")) 179 } 180 if err := cs.blockExec.ValidateBlock(cs.state, block); err != nil { 181 panic(fmt.Sprintf("+2/3 committed an invalid block: %v", err)) 182 } 183 184 cs.Logger.Info("Finalizing commit of block with N txs", 185 "height", block.Height, 186 "hash", block.Hash(), 187 "root", block.AppHash, 188 "N", len(block.Txs)) 189 cs.Logger.Info(fmt.Sprintf("%v", block)) 190 191 fail.Fail() // XXX 192 193 // Save to blockStore. 194 blockTime := block.Time 195 if cs.blockStore.Height() < block.Height { 196 // NOTE: the seenCommit is local justification to commit this block, 197 // but may differ from the LastCommit included in the next block 198 precommits := cs.Votes.Precommits(cs.CommitRound) 199 seenCommit := precommits.MakeCommit() 200 blockTime = sm.MedianTime(seenCommit, cs.Validators) 201 cs.blockStore.SaveBlock(block, blockParts, seenCommit) 202 } else { 203 // Happens during replay if we already saved the block but didn't commit 204 cs.Logger.Info("Calling finalizeCommit on already stored block", "height", block.Height) 205 } 206 trace.GetElapsedInfo().AddInfo(trace.BTInterval, fmt.Sprintf("%dms", blockTime.Sub(block.Time).Milliseconds())) 207 208 fail.Fail() // XXX 209 210 // Write EndHeightMessage{} for this height, implying that the blockstore 211 // has saved the block. 212 // 213 // If we crash before writing this EndHeightMessage{}, we will recover by 214 // running ApplyBlock during the ABCI handshake when we restart. If we 215 // didn't save the block to the blockstore before writing 216 // EndHeightMessage{}, we'd have to change WAL replay -- currently it 217 // complains about replaying for heights where an #ENDHEIGHT entry already 218 // exists. 219 // 220 // Either way, the State should not be resumed until we 221 // successfully call ApplyBlock (ie. later here, or in Handshake after 222 // restart). 223 endMsg := EndHeightMessage{height} 224 if err := cs.wal.WriteSync(endMsg); err != nil { // NOTE: fsync 225 panic(fmt.Sprintf("Failed to write %v msg to consensus wal due to %v. Check your FS and restart the node", 226 endMsg, err)) 227 } 228 229 fail.Fail() // XXX 230 231 // Create a copy of the state for staging and an event cache for txs. 232 stateCopy := cs.state.Copy() 233 234 // Execute and commit the block, update and save the state, and update the mempool. 235 // NOTE The block.AppHash wont reflect these txs until the next block. 236 237 var err error 238 var retainHeight int64 239 240 cs.trc.Pin("%s", trace.ApplyBlock) 241 242 if iavl.EnableAsyncCommit { 243 cs.handleCommitGapOffset(height) 244 } 245 246 stateCopy, retainHeight, err = cs.blockExec.ApplyBlock( 247 stateCopy, 248 types.BlockID{Hash: block.Hash(), PartsHeader: blockParts.Header()}, 249 block) 250 if err != nil { 251 cs.Logger.Error("Error on ApplyBlock. Did the application crash? Please restart tendermint", "err", err) 252 err := tmos.Kill() 253 if err != nil { 254 cs.Logger.Error("Failed to kill this process - please do so manually", "err", err) 255 } 256 return 257 } 258 259 //reset offset after commitGap 260 if iavl.EnableAsyncCommit && 261 height%iavlcfg.DynamicConfig.GetCommitGapHeight() == iavl.GetFinalCommitGapOffset() { 262 iavl.SetFinalCommitGapOffset(0) 263 } 264 265 fail.Fail() // XXX 266 267 cs.trc.Pin("%s", trace.UpdateState) 268 269 // Prune old heights, if requested by ABCI app. 270 if retainHeight > 0 { 271 pruned, err := cs.pruneBlocks(retainHeight) 272 if err != nil { 273 cs.Logger.Error("Failed to prune blocks", "retainHeight", retainHeight, "err", err) 274 } else { 275 cs.Logger.Info("Pruned blocks", "pruned", pruned, "retainHeight", retainHeight) 276 } 277 } 278 279 // must be called before we update state 280 cs.recordMetrics(height, block) 281 282 // NewHeightStep! 283 cs.stateMtx.Lock() 284 cs.updateToState(stateCopy) 285 cs.stateMtx.Unlock() 286 287 fail.Fail() // XXX 288 289 // Private validator might have changed it's key pair => refetch pubkey. 290 if err := cs.updatePrivValidatorPubKey(); err != nil { 291 cs.Logger.Error("Can't get private validator pubkey", "err", err) 292 } 293 294 // publish event 295 if types.EnableEventBlockTime { 296 cs.blockExec.FireBlockTimeEvents(block.Height, len(block.Txs), false) 297 } 298 299 cs.trc.Pin("%s", trace.Waiting) 300 301 // cs.StartTime is already set. 302 // Schedule Round0 to start soon. 303 cs.scheduleRound0(&cs.RoundState) 304 305 // By here, 306 // * cs.Height has been increment to height+1 307 // * cs.Step is now cstypes.RoundStepNewHeight 308 // * cs.StartTime is set to when we will start round0. 309 } 310 311 // Updates State and increments height to match that of state. 312 // The round becomes 0 and cs.Step becomes cstypes.RoundStepNewHeight. 313 func (cs *State) updateToState(state sm.State) { 314 // Do not consider this situation that the consensus machine was stopped 315 // when the fast-sync mode opens. So remove it! 316 //if cs.CommitRound > -1 && 0 < cs.Height && cs.Height != state.LastBlockHeight { 317 // panic(fmt.Sprintf("updateToState() expected state height of %v but found %v", 318 // cs.Height, state.LastBlockHeight)) 319 //} 320 //if !cs.state.IsEmpty() && cs.state.LastBlockHeight+1 != cs.Height { 321 // // This might happen when someone else is mutating cs.state. 322 // // Someone forgot to pass in state.Copy() somewhere?! 323 // panic(fmt.Sprintf("Inconsistent cs.state.LastBlockHeight+1 %v vs cs.Height %v", 324 // cs.state.LastBlockHeight+1, cs.Height)) 325 //} 326 327 cs.HasVC = false 328 if cs.vcMsg != nil && cs.vcMsg.Height <= cs.Height { 329 cs.vcMsg = nil 330 } 331 for k, _ := range cs.vcHeight { 332 if k <= cs.Height { 333 delete(cs.vcHeight, k) 334 } 335 } 336 select { 337 case <-cs.taskResultChan: 338 default: 339 } 340 341 // If state isn't further out than cs.state, just ignore. 342 // This happens when SwitchToConsensus() is called in the reactor. 343 // We don't want to reset e.g. the Votes, but we still want to 344 // signal the new round step, because other services (eg. txNotifier) 345 // depend on having an up-to-date peer state! 346 if !cs.state.IsEmpty() && (state.LastBlockHeight <= cs.state.LastBlockHeight) { 347 cs.Logger.Info( 348 "Ignoring updateToState()", 349 "newHeight", 350 state.LastBlockHeight+1, 351 "oldHeight", 352 cs.state.LastBlockHeight+1) 353 cs.newStep() 354 return 355 } 356 357 // Reset fields based on state. 358 validators := state.Validators 359 switch { 360 case state.LastBlockHeight == types.GetStartBlockHeight(): // Very first commit should be empty. 361 cs.LastCommit = (*types.VoteSet)(nil) 362 case cs.CommitRound > -1 && cs.Votes != nil: // Otherwise, use cs.Votes 363 if !cs.Votes.Precommits(cs.CommitRound).HasTwoThirdsMajority() { 364 panic(fmt.Sprintf( 365 "wanted to form a commit, but precommits (H/R: %d/%d) didn't have 2/3+: %v", 366 state.LastBlockHeight, cs.CommitRound, cs.Votes.Precommits(cs.CommitRound), 367 )) 368 } 369 370 cs.LastCommit = cs.Votes.Precommits(cs.CommitRound) 371 372 case cs.LastCommit == nil: 373 // NOTE: when Tendermint starts, it has no votes. reconstructLastCommit 374 // must be called to reconstruct LastCommit from SeenCommit. 375 panic(fmt.Sprintf( 376 "last commit cannot be empty after initial block (H:%d)", 377 state.LastBlockHeight+1, 378 )) 379 } 380 381 // Next desired block height 382 height := state.LastBlockHeight + 1 383 384 // RoundState fields 385 cs.updateHeight(height) 386 cs.updateRoundStep(0, cstypes.RoundStepNewHeight) 387 cs.bt.reset(height) 388 389 cs.Validators = validators 390 cs.Proposal = nil 391 cs.ProposalBlock = nil 392 cs.ProposalBlockParts = nil 393 cs.LockedRound = -1 394 cs.LockedBlock = nil 395 cs.LockedBlockParts = nil 396 cs.ValidRound = -1 397 cs.ValidBlock = nil 398 cs.ValidBlockParts = nil 399 cs.Votes = cstypes.NewHeightVoteSet(state.ChainID, height, validators) 400 cs.CommitRound = -1 401 cs.LastValidators = state.LastValidators 402 cs.TriggeredTimeoutPrecommit = false 403 cs.state = state 404 405 // Finally, broadcast RoundState 406 cs.newStep() 407 } 408 409 func (cs *State) updateHeight(height int64) { 410 cs.metrics.Height.Set(float64(height)) 411 cs.Height = height 412 } 413 414 func (cs *State) pruneBlocks(retainHeight int64) (uint64, error) { 415 base := cs.blockStore.Base() 416 if retainHeight <= base { 417 return 0, nil 418 } 419 pruned, err := cs.blockStore.PruneBlocks(retainHeight) 420 if err != nil { 421 return 0, fmt.Errorf("failed to prune block store: %w", err) 422 } 423 err = sm.PruneStates(cs.blockExec.DB(), base, retainHeight) 424 if err != nil { 425 return 0, fmt.Errorf("failed to prune state database: %w", err) 426 } 427 return pruned, nil 428 } 429 430 func (cs *State) preMakeBlock(height int64, waiting time.Duration) { 431 tNow := tmtime.Now() 432 block, blockParts := cs.createProposalBlock() 433 cs.taskResultChan <- &preBlockTaskRes{block: block, blockParts: blockParts} 434 435 propBlockID := types.BlockID{Hash: block.Hash(), PartsHeader: blockParts.Header()} 436 proposal := types.NewProposal(height, 0, cs.ValidRound, propBlockID) 437 438 if cs.Height != height { 439 return 440 } 441 isBlockProducer, _ := cs.isBlockProducer() 442 if GetActiveVC() && isBlockProducer != "y" { 443 // request for proposer of new height 444 prMsg := ProposeRequestMessage{Height: height, CurrentProposer: cs.Validators.GetProposer().Address, NewProposer: cs.privValidatorPubKey.Address(), Proposal: proposal} 445 go func() { 446 time.Sleep(waiting - tmtime.Now().Sub(tNow)) 447 cs.requestForProposer(prMsg) 448 }() 449 } 450 } 451 452 func (cs *State) getPreBlockResult(height int64) *preBlockTaskRes { 453 if !GetActiveVC() { 454 return nil 455 } 456 t := time.NewTimer(time.Second) 457 for { 458 select { 459 case res := <-cs.taskResultChan: 460 if res.block.Height == height { 461 if !t.Stop() { 462 <-t.C 463 } 464 return res 465 } else { 466 return nil 467 } 468 case <-t.C: 469 return nil 470 } 471 472 } 473 } 474 475 // handle AC offset to avoid block proposal 476 func (cs *State) handleCommitGapOffset(height int64) { 477 commitGap := iavlcfg.DynamicConfig.GetCommitGapHeight() 478 offset := cfg.DynamicConfig.GetCommitGapOffset() 479 480 // close offset 481 if offset <= 0 || (commitGap <= offset) { 482 iavl.SetFinalCommitGapOffset(0) 483 // only try to offset at commitGap height 484 } else if (height % commitGap) == 0 { 485 selfAddress := cs.privValidatorPubKey.Address() 486 futureValidators := cs.state.Validators.Copy() 487 488 var i int64 489 for ; i < offset; i++ { 490 futureBPAddress := futureValidators.GetProposer().Address 491 492 // self is the validator at the offset height 493 if bytes.Equal(futureBPAddress, selfAddress) { 494 // trigger ac ahead of the offset 495 iavl.SetFinalCommitGapOffset(i + 1) 496 //originACHeight|newACHeight|nextProposeHeight|Offset 497 trace.GetElapsedInfo().AddInfo(trace.ACOffset, fmt.Sprintf("%d|%d|%d|%d|", 498 height, height+i+1, height+i, offset)) 499 break 500 } 501 futureValidators.IncrementProposerPriority(1) 502 } 503 } 504 }