-
Notifications
You must be signed in to change notification settings - Fork 4
Build block immediately when transitioning epoch #514
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -271,6 +271,103 @@ func (sm *StateMachine) maybeInitializeApprovalStore(validatorSet NodeBLSMapping | |||||
| return sm.approvalStore | ||||||
| } | ||||||
|
|
||||||
| // WaitForPendingBlock waits for either the VM to signal that a block is ready to be built, | ||||||
| // or for the state machine to determine that a block should be built immediately due to an epoch transition. | ||||||
| // In the latter case, we only wait up to MaxBlockBuildingWaitTime before returning. | ||||||
| func (sm *StateMachine) WaitForPendingBlock(ctx context.Context, currentRoundMetadata common.ProtocolMetadata) { | ||||||
| if currentRoundMetadata.Seq == 0 { | ||||||
| // This shouldn't happen because we never build the genesis block. | ||||||
| // Just call the VM's WaitForPendingBlock to avoid blocking the state machine. | ||||||
| sm.Logger.Debug("WaitForPendingBlock called with seq 0, which is invalid; forwarding to VM to avoid blocking") | ||||||
| sm.BlockBuilder.WaitForPendingBlock(ctx) | ||||||
| return | ||||||
| } | ||||||
|
|
||||||
| // In order to know whether we're transitioning to a new epoch, or should transition to one, | ||||||
| // we need to look at the previous block's metadata. | ||||||
|
|
||||||
| prevBlockSeq := currentRoundMetadata.Seq - 1 | ||||||
|
|
||||||
| parentBlock, finalization, err := sm.GetBlock(prevBlockSeq, currentRoundMetadata.Prev) | ||||||
| if err != nil { | ||||||
| sm.Logger.Debug("WaitForPendingBlock failed to get block", zap.Uint64("seq", prevBlockSeq), zap.Error(err)) | ||||||
| sm.BlockBuilder.WaitForPendingBlock(ctx) | ||||||
| return | ||||||
| } | ||||||
|
|
||||||
| // In case the parent block is a Telock, we want to look at the sealing block's metadata instead. | ||||||
| if parentBlock.Type() == BlockTypeTelock { | ||||||
| sealingBlockSeq := parentBlock.Metadata.SimplexEpochInfo.SealingBlockSeq | ||||||
| var sealingBlock StateMachineBlock | ||||||
| sealingBlock, finalization, err = sm.GetBlock(sealingBlockSeq, common.Digest{}) | ||||||
| // The only two reasons why we would not be able to retrieve the sealing block are: | ||||||
| // (1) The sealing block is finalized and there is a storage failure, or | ||||||
| // (2) The sealing block is not finalized yet, in which case we are still transitioning to a new epoch. | ||||||
| // Either way and in case the sealing block isn't finalized, we shouldn't wait indefinitely for the VM to build a block. | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why shouldn't we wait indefinetly? if the sealing block is not finalized, the epoch will either keep broadcasting empty votes to advance the round or it will eventually notarize/finalize it. If we empty notarize the round, then the next block that will be proposed may be another sealing block. So I think waiting like normal is fine here, the
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If we wait indefinitely and the leader is stuck, we will not send an empty vote, right? |
||||||
| if err != nil || sealingBlock.Type() != BlockTypeSealing || finalization == nil { | ||||||
| if err != nil { | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we move this error outside of the if?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
| sm.Logger.Debug("Failed retrieving sealing block for previous epoch", zap.Uint64("seq", sealingBlockSeq), zap.Error(err)) | ||||||
| sm.BlockBuilder.WaitForPendingBlock(ctx) | ||||||
| return | ||||||
| } | ||||||
|
|
||||||
| ctx, cancel := context.WithTimeout(ctx, sm.MaxBlockBuildingWaitTime) | ||||||
| defer cancel() | ||||||
| sm.BlockBuilder.WaitForPendingBlock(ctx) | ||||||
| return | ||||||
| } | ||||||
| // Else, err is nil and the sealing block is finalized, | ||||||
| // so we can use the sealing block's metadata to determine whether we should build a block immediately or not. | ||||||
| parentBlock = sealingBlock | ||||||
| } | ||||||
|
|
||||||
| currentState := parentBlock.Metadata.SimplexEpochInfo.NextState() | ||||||
|
|
||||||
| // We first check if we have obvious signs that we need to build a block immediately: | ||||||
| var shouldObviouslyBuildBlockImmediately bool | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no action but seems weird naming a variable with
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so, anything that isn't |
||||||
|
|
||||||
| switch currentState { | ||||||
| case stateFirstSimplexBlock, stateBuildCollectingApprovals: | ||||||
| // In case of the first simplex block we need to persist the validator set, | ||||||
| // and if we're collecting approvals then we're in the process of an epoch change already. | ||||||
| shouldObviouslyBuildBlockImmediately = true | ||||||
| case stateBuildBlockEpochSealed: | ||||||
| // The parent is a sealing block, whose finalization we already fetched above. | ||||||
| // If it isn't finalized we must build a Telock immediately to extend the epoch; | ||||||
| // if it is finalized, we fall through to the normal epoch-transition check below. | ||||||
| if finalization == nil { | ||||||
|
samliok marked this conversation as resolved.
|
||||||
| shouldObviouslyBuildBlockImmediately = true | ||||||
| } | ||||||
| default: // Handles stateBuildBlockNormalOp or any unknown state, don't do anything and just exit the switch. | ||||||
| } | ||||||
|
|
||||||
| // If it's obvious that we should build a block immediately, | ||||||
| // we wait up to MaxBlockBuildingWaitTime and then return immediately. | ||||||
| if shouldObviouslyBuildBlockImmediately { | ||||||
| ctx, cancel := context.WithTimeout(ctx, sm.MaxBlockBuildingWaitTime) | ||||||
| defer cancel() | ||||||
| sm.BlockBuilder.WaitForPendingBlock(ctx) | ||||||
| return | ||||||
| } | ||||||
|
|
||||||
| // Otherwise, we might need to build a block if we detect that we should transition to a new epoch, | ||||||
| // so we initialize a blockBuildingDecider and listen while waiting for the VM. | ||||||
| // We return when either the VM signals that a block is ready to be built, | ||||||
| // or that the blockBuildingDecider detects that we should transition to a new epoch. | ||||||
| pChainReferenceHeight := parentBlock.Metadata.SimplexEpochInfo.PChainReferenceHeight | ||||||
| if parentBlock.Type() == BlockTypeSealing { | ||||||
| // We've moved to a new epoch, so we need to use the next P-chain reference height of the sealing block, | ||||||
| // because the P-chain reference height is of the next epoch is inherited from the P-chain reference height of the sealing block. | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| pChainReferenceHeight = parentBlock.Metadata.SimplexEpochInfo.NextPChainReferenceHeight | ||||||
| } | ||||||
| blockBuildingDecider := sm.createBlockBuildingDecider(pChainReferenceHeight) | ||||||
| _, err = blockBuildingDecider.shouldBuildBlock(ctx) | ||||||
| if err != nil { | ||||||
| sm.Logger.Debug("Error while deciding whether to build a block", zap.Error(err)) | ||||||
| return | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // BuildBlock constructs the next block on top of the given parent block, and passes in the provided simplex metadata and blacklist. | ||||||
| func (sm *StateMachine) BuildBlock(ctx context.Context, metadata common.ProtocolMetadata, blacklist common.Blacklist) (*StateMachineBlock, error) { | ||||||
| // The zero sequence number is reserved for the genesis block, which should never be built. | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this comment is not true, if we have advanced to a round caused by a telock then we have verified the sealing block. This means it's in the cached storage and
GetBlockshould return it regardless of finalization status.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The block is in the cache, correct. However, notice that we fetch it with a wildcard digest (any digest) becauseit might be buried several Telocks under.
When we fetch by a wildcard digest, we iterate over the cache and we might return a block that is on a different chain than we intended to.
That's why I said that only if it's finalized this does not happen, because then we will delete the block from the cache.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slightly changed the code, please check, I think it's more clear now: 8d8d5e3