github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/tendermint/consensus/consensus_commit.go (about) 1 package consensus 2 3 import ( 4 "bytes" 5 "encoding/hex" 6 "fmt" 7 "time" 8 9 "github.com/fibonacci-chain/fbc/libs/iavl" 10 iavlcfg "github.com/fibonacci-chain/fbc/libs/iavl/config" 11 "github.com/fibonacci-chain/fbc/libs/system/trace" 12 cfg "github.com/fibonacci-chain/fbc/libs/tendermint/config" 13 cstypes "github.com/fibonacci-chain/fbc/libs/tendermint/consensus/types" 14 "github.com/fibonacci-chain/fbc/libs/tendermint/libs/fail" 15 tmos "github.com/fibonacci-chain/fbc/libs/tendermint/libs/os" 16 sm "github.com/fibonacci-chain/fbc/libs/tendermint/state" 17 "github.com/fibonacci-chain/fbc/libs/tendermint/types" 18 tmtime "github.com/fibonacci-chain/fbc/libs/tendermint/types/time" 19 ) 20 21 func (cs *State) dumpElapsed(trc *trace.Tracer, schema string) { 22 trace.GetElapsedInfo().AddInfo(schema, trc.Format()) 23 trc.Reset() 24 } 25 26 func (cs *State) initNewHeight() { 27 // waiting finished and enterNewHeight by timeoutNewHeight 28 if cs.Step == cstypes.RoundStepNewHeight { 29 // init StartTime 30 cs.StartTime = tmtime.Now() 31 cs.dumpElapsed(cs.blockTimeTrc, trace.LastBlockTime) 32 cs.traceDump() 33 } 34 } 35 36 func (cs *State) traceDump() { 37 if cs.Logger == nil { 38 return 39 } 40 41 trace.GetElapsedInfo().AddInfo(trace.CommitRound, fmt.Sprintf("%d", cs.CommitRound)) 42 trace.GetElapsedInfo().AddInfo(trace.Round, fmt.Sprintf("%d", cs.Round)) 43 trace.GetElapsedInfo().AddInfo(trace.BlockParts, fmt.Sprintf("%d|%d|%d|%d/%d", 44 cs.bt.droppedDue2WrongHeight, 45 cs.bt.droppedDue2NotExpected, 46 cs.bt.droppedDue2Error, 47 cs.bt.droppedDue2NotAdded, 48 cs.bt.totalParts, 49 )) 50 51 trace.GetElapsedInfo().AddInfo(trace.BlockPartsP2P, fmt.Sprintf("%d|%d|%d", 52 cs.bt.bpNOTransByACK, cs.bt.bpNOTransByData, cs.bt.bpSend)) 53 54 trace.GetElapsedInfo().AddInfo(trace.Produce, cs.trc.Format()) 55 trace.GetElapsedInfo().Dump(cs.Logger.With("module", "main")) 56 cs.trc.Reset() 57 } 58 59 // Enter: +2/3 precommits for block 60 func (cs *State) enterCommit(height int64, commitRound int) { 61 logger := cs.Logger.With("height", height, "commitRound", commitRound) 62 63 if cs.Height != height || cstypes.RoundStepCommit <= cs.Step { 64 logger.Debug(fmt.Sprintf( 65 "enterCommit(%v/%v): Invalid args. Current step: %v/%v/%v", 66 height, 67 commitRound, 68 cs.Height, 69 cs.Round, 70 cs.Step)) 71 return 72 } 73 74 cs.initNewHeight() 75 cs.trc.Pin("%s-%d", "Commit", cs.Round) 76 77 logger.Info(fmt.Sprintf("enterCommit(%v/%v). Current: %v/%v/%v", height, commitRound, cs.Height, cs.Round, cs.Step)) 78 79 defer func() { 80 // Done enterCommit: 81 // keep cs.Round the same, commitRound points to the right Precommits set. 82 cs.updateRoundStep(cs.Round, cstypes.RoundStepCommit) 83 cs.CommitRound = commitRound 84 cs.newStep() 85 86 // Maybe finalize immediately. 87 cs.tryFinalizeCommit(height) 88 }() 89 90 blockID, ok := cs.Votes.Precommits(commitRound).TwoThirdsMajority() 91 if !ok { 92 panic("RunActionCommit() expects +2/3 precommits") 93 } 94 95 // The Locked* fields no longer matter. 96 // Move them over to ProposalBlock if they match the commit hash, 97 // otherwise they'll be cleared in updateToState. 98 if cs.LockedBlock.HashesTo(blockID.Hash) { 99 logger.Info("Commit is for locked block. Set ProposalBlock=LockedBlock", "blockHash", blockID.Hash) 100 cs.ProposalBlock = cs.LockedBlock 101 cs.ProposalBlockParts = cs.LockedBlockParts 102 } 103 104 // If we don't have the block being committed, set up to get it. 105 if !cs.ProposalBlock.HashesTo(blockID.Hash) { 106 if !cs.ProposalBlockParts.HasHeader(blockID.PartsHeader) { 107 logger.Info( 108 "Commit is for a block we don't know about. Set ProposalBlock=nil", 109 "proposal", 110 cs.ProposalBlock.Hash(), 111 "commit", 112 blockID.Hash) 113 // We're getting the wrong block. 114 // Set up ProposalBlockParts and keep waiting. 115 cs.ProposalBlock = nil 116 cs.Logger.Info("enterCommit proposalBlockPart reset ,because of mismatch hash,", 117 "origin", hex.EncodeToString(cs.ProposalBlockParts.Hash()), "after", blockID.Hash) 118 cs.ProposalBlockParts = types.NewPartSetFromHeader(blockID.PartsHeader) 119 cs.eventBus.PublishEventValidBlock(cs.RoundStateEvent()) 120 cs.evsw.FireEvent(types.EventValidBlock, &cs.RoundState) 121 } 122 // else { 123 // We just need to keep waiting. 124 // } 125 } 126 } 127 128 // If we have the block AND +2/3 commits for it, finalize. 129 func (cs *State) tryFinalizeCommit(height int64) { 130 logger := cs.Logger.With("height", height) 131 132 if cs.Height != height { 133 panic(fmt.Sprintf("tryFinalizeCommit() cs.Height: %v vs height: %v", cs.Height, height)) 134 } 135 136 blockID, ok := cs.Votes.Precommits(cs.CommitRound).TwoThirdsMajority() 137 if !ok || len(blockID.Hash) == 0 { 138 logger.Error("Attempt to finalize failed. There was no +2/3 majority, or +2/3 was for <nil>.") 139 return 140 } 141 if !cs.ProposalBlock.HashesTo(blockID.Hash) { 142 // TODO: this happens every time if we're not a validator (ugly logs) 143 // TODO: ^^ wait, why does it matter that we're a validator? 144 logger.Info( 145 "Attempt to finalize failed. We don't have the commit block.", 146 "proposal-block", 147 cs.ProposalBlock.Hash(), 148 "commit-block", 149 blockID.Hash) 150 return 151 } 152 153 // go 154 cs.finalizeCommit(height) 155 } 156 157 // Increment height and goto cstypes.RoundStepNewHeight 158 func (cs *State) finalizeCommit(height int64) { 159 if cs.Height != height || cs.Step != cstypes.RoundStepCommit { 160 cs.Logger.Debug(fmt.Sprintf( 161 "finalizeCommit(%v): Invalid args. Current step: %v/%v/%v", 162 height, 163 cs.Height, 164 cs.Round, 165 cs.Step)) 166 return 167 } 168 169 blockID, ok := cs.Votes.Precommits(cs.CommitRound).TwoThirdsMajority() 170 block, blockParts := cs.ProposalBlock, cs.ProposalBlockParts 171 172 if !ok { 173 panic(fmt.Sprintf("Cannot finalizeCommit, commit does not have two thirds majority")) 174 } 175 if !blockParts.HasHeader(blockID.PartsHeader) { 176 panic(fmt.Sprintf("Expected ProposalBlockParts header to be commit header")) 177 } 178 if !block.HashesTo(blockID.Hash) { 179 panic(fmt.Sprintf("Cannot finalizeCommit, ProposalBlock does not hash to commit hash")) 180 } 181 if err := cs.blockExec.ValidateBlock(cs.state, block); err != nil { 182 panic(fmt.Sprintf("+2/3 committed an invalid block: %v", err)) 183 } 184 185 cs.Logger.Info("Finalizing commit of block with N txs", 186 "height", block.Height, 187 "hash", block.Hash(), 188 "root", block.AppHash, 189 "N", len(block.Txs)) 190 cs.Logger.Info(fmt.Sprintf("%v", block)) 191 192 fail.Fail() // XXX 193 194 // Save to blockStore. 195 blockTime := block.Time 196 if cs.blockStore.Height() < block.Height { 197 // NOTE: the seenCommit is local justification to commit this block, 198 // but may differ from the LastCommit included in the next block 199 precommits := cs.Votes.Precommits(cs.CommitRound) 200 seenCommit := precommits.MakeCommit() 201 blockTime = sm.MedianTime(seenCommit, cs.Validators) 202 cs.blockStore.SaveBlock(block, blockParts, seenCommit) 203 } else { 204 // Happens during replay if we already saved the block but didn't commit 205 cs.Logger.Info("Calling finalizeCommit on already stored block", "height", block.Height) 206 } 207 trace.GetElapsedInfo().AddInfo(trace.BTInterval, fmt.Sprintf("%dms", blockTime.Sub(block.Time).Milliseconds())) 208 209 fail.Fail() // XXX 210 211 // Write EndHeightMessage{} for this height, implying that the blockstore 212 // has saved the block. 213 // 214 // If we crash before writing this EndHeightMessage{}, we will recover by 215 // running ApplyBlock during the ABCI handshake when we restart. If we 216 // didn't save the block to the blockstore before writing 217 // EndHeightMessage{}, we'd have to change WAL replay -- currently it 218 // complains about replaying for heights where an #ENDHEIGHT entry already 219 // exists. 220 // 221 // Either way, the State should not be resumed until we 222 // successfully call ApplyBlock (ie. later here, or in Handshake after 223 // restart). 224 endMsg := EndHeightMessage{height} 225 if err := cs.wal.WriteSync(endMsg); err != nil { // NOTE: fsync 226 panic(fmt.Sprintf("Failed to write %v msg to consensus wal due to %v. Check your FS and restart the node", 227 endMsg, err)) 228 } 229 230 fail.Fail() // XXX 231 232 // Create a copy of the state for staging and an event cache for txs. 233 stateCopy := cs.state.Copy() 234 235 // Execute and commit the block, update and save the state, and update the mempool. 236 // NOTE The block.AppHash wont reflect these txs until the next block. 237 238 var err error 239 var retainHeight int64 240 241 cs.trc.Pin("%s", trace.ApplyBlock) 242 243 if iavl.EnableAsyncCommit { 244 cs.handleCommitGapOffset(height) 245 } 246 247 stateCopy, retainHeight, err = cs.blockExec.ApplyBlock( 248 stateCopy, 249 types.BlockID{Hash: block.Hash(), PartsHeader: blockParts.Header()}, 250 block) 251 if err != nil { 252 cs.Logger.Error("Error on ApplyBlock. Did the application crash? Please restart tendermint", "err", err) 253 err := tmos.Kill() 254 if err != nil { 255 cs.Logger.Error("Failed to kill this process - please do so manually", "err", err) 256 } 257 return 258 } 259 260 //reset offset after commitGap 261 if iavl.EnableAsyncCommit && 262 height%iavlcfg.DynamicConfig.GetCommitGapHeight() == iavl.GetFinalCommitGapOffset() { 263 iavl.SetFinalCommitGapOffset(0) 264 } 265 266 fail.Fail() // XXX 267 268 cs.trc.Pin("%s", trace.UpdateState) 269 270 // Prune old heights, if requested by ABCI app. 271 if retainHeight > 0 { 272 pruned, err := cs.pruneBlocks(retainHeight) 273 if err != nil { 274 cs.Logger.Error("Failed to prune blocks", "retainHeight", retainHeight, "err", err) 275 } else { 276 cs.Logger.Info("Pruned blocks", "pruned", pruned, "retainHeight", retainHeight) 277 } 278 } 279 280 // must be called before we update state 281 cs.recordMetrics(height, block) 282 283 // NewHeightStep! 284 cs.stateMtx.Lock() 285 cs.updateToState(stateCopy) 286 cs.stateMtx.Unlock() 287 288 fail.Fail() // XXX 289 290 // Private validator might have changed it's key pair => refetch pubkey. 291 if err := cs.updatePrivValidatorPubKey(); err != nil { 292 cs.Logger.Error("Can't get private validator pubkey", "err", err) 293 } 294 295 // publish event 296 if types.EnableEventBlockTime { 297 cs.blockExec.FireBlockTimeEvents(block.Height, len(block.Txs), false) 298 } 299 300 cs.trc.Pin("%s", trace.Waiting) 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 }