-
Notifications
You must be signed in to change notification settings - Fork 177
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
PR 736: suggestions pt4 #774
Merged
durkmurder
merged 4 commits into
yurii/5417-approval-processing-engine
from
alex/5417-approval-processing-engine_-_suggestions_pt4
Jun 2, 2021
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,12 +27,16 @@ const DefaultEmergencySealingThreshold = 400 | |
// helper functor that can be used to retrieve cached block height | ||
type GetCachedBlockHeight = func(blockID flow.Identifier) (uint64, error) | ||
|
||
// AssignmentCollector is responsible collecting approvals that satisfy one assignment, meaning that we will | ||
// have multiple collectorTree for one execution result as same result can be incorporated in multiple forks. | ||
// AssignmentCollector has a strict ordering of processing, before processing approvals at least one incorporated result has to be | ||
// processed. | ||
// AssignmentCollector | ||
// Context: | ||
// * When the same result is incorporated in multiple different forks, | ||
// unique verifier assignment is determined for each fork. | ||
// * The assignment collector is intended to encapsulate the known | ||
// assignments for a particular execution result. | ||
// AssignmentCollector has a strict ordering of processing, before processing | ||
// approvals at least one incorporated result has to be processed. | ||
// AssignmentCollector takes advantage of internal caching to speed up processing approvals for different assignments | ||
// AssignmentCollector is responsible for validating approvals on result-level(checking signature, identity). | ||
// AssignmentCollector is responsible for validating approvals on result-level (checking signature, identity). | ||
// TODO: currently AssignmentCollector doesn't cleanup collectorTree when blocks that incorporate results get orphaned | ||
// For BFT milestone we need to ensure that this cleanup is properly implemented and all orphan collectorTree are pruned by height | ||
// when fork gets orphaned | ||
|
@@ -61,12 +65,19 @@ func NewAssignmentCollector(result *flow.ExecutionResult, state protocol.State, | |
if err != nil { | ||
return nil, err | ||
} | ||
// pre-select all authorized verifiers at the block that is being sealed | ||
authorizedApprovers, err := authorizedVerifiersAtBlock(state, result.BlockID) | ||
if err != nil { | ||
return nil, engine.NewInvalidInputErrorf("could not determine authorized verifiers for sealing candidate: %w", err) | ||
} | ||
|
||
collector := &AssignmentCollector{ | ||
ResultID: result.ID(), | ||
result: result, | ||
BlockHeight: block.Height, | ||
collectors: make(map[flow.Identifier]*ApprovalCollector), | ||
authorizedApprovers: authorizedApprovers, | ||
verifiedApprovalsCache: NewApprovalsCache(uint(result.Chunks.Len() * len(authorizedApprovers))), | ||
state: state, | ||
assigner: assigner, | ||
seals: seals, | ||
|
@@ -76,15 +87,6 @@ func NewAssignmentCollector(result *flow.ExecutionResult, state protocol.State, | |
headers: headers, | ||
requiredApprovalsForSealConstruction: requiredApprovalsForSealConstruction, | ||
} | ||
|
||
// pre-select all authorized verifiers at the block that is being sealed | ||
collector.authorizedApprovers, err = collector.authorizedVerifiersAtBlock(result.BlockID) | ||
if err != nil { | ||
return nil, engine.NewInvalidInputErrorf("could not determine authorized verifiers for sealing candidate: %w", err) | ||
} | ||
|
||
collector.verifiedApprovalsCache = NewApprovalsCache(uint(result.Chunks.Len() * len(collector.authorizedApprovers))) | ||
|
||
return collector, nil | ||
} | ||
|
||
|
@@ -99,36 +101,10 @@ func (ac *AssignmentCollector) collectorByBlockID(incorporatedBlockID flow.Ident | |
return ac.collectors[incorporatedBlockID] | ||
} | ||
|
||
// authorizedVerifiersAtBlock pre-select all authorized Verifiers at the block that incorporates the result. | ||
// The method returns the set of all node IDs that: | ||
// * are authorized members of the network at the given block and | ||
// * have the Verification role and | ||
// * have _positive_ weight and | ||
// * are not ejected | ||
func (ac *AssignmentCollector) authorizedVerifiersAtBlock(blockID flow.Identifier) (map[flow.Identifier]*flow.Identity, error) { | ||
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.
|
||
authorizedVerifierList, err := ac.state.AtBlockID(blockID).Identities( | ||
filter.And( | ||
filter.HasRole(flow.RoleVerification), | ||
filter.HasStake(true), | ||
filter.Not(filter.Ejected), | ||
)) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to retrieve Identities for block %v: %w", blockID, err) | ||
} | ||
if len(authorizedVerifierList) == 0 { | ||
return nil, fmt.Errorf("no authorized verifiers found for block %v", blockID) | ||
} | ||
identities := make(map[flow.Identifier]*flow.Identity) | ||
for _, identity := range authorizedVerifierList { | ||
identities[identity.NodeID] = identity | ||
} | ||
return identities, nil | ||
} | ||
|
||
// emergencySealable determines whether an incorporated Result qualifies for "emergency sealing". | ||
// ATTENTION: this is a temporary solution, which is NOT BFT compatible. When the approval process | ||
// hangs far enough behind finalization (measured in finalized but unsealed blocks), emergency | ||
// sealing kicks in. This will be removed when implementation of seal & verification is finished. | ||
// sealing kicks in. This will be removed when implementation of Sealing & Verification is finished. | ||
func (ac *AssignmentCollector) emergencySealable(collector *ApprovalCollector, finalizedBlockHeight uint64) bool { | ||
// Criterion for emergency sealing: | ||
// there must be at least DefaultEmergencySealingThreshold number of blocks between | ||
|
@@ -162,25 +138,25 @@ func (ac *AssignmentCollector) ProcessIncorporatedResult(incorporatedResult *flo | |
return nil | ||
} | ||
|
||
// This function is not exactly thread safe, it can perform double computation of assignment and authorized verifiers | ||
// It is safe in regards that only one collector will be stored to the cache | ||
// In terms of locking time it's better to perform extra computation in edge cases than lock this logic with mutex | ||
// Constructing ApprovalCollector for IncorporatedResult | ||
// The AssignmentCollector is not locked while instantiating the ApprovalCollector. Hence, it is possible that | ||
// multiple threads simultaneously compute the verifier assignment. Nevertheless, the implementation is safe in | ||
// that only one of the instantiated ApprovalCollectors will be stored in the cache. In terms of locking duration, | ||
// it's better to perform extra computation in edge cases than lock this logic with a mutex, | ||
// since it's quite unlikely that same incorporated result will be processed by multiple goroutines simultaneously | ||
|
||
// chunk assigment is based on the first block in the fork that incorporates the result | ||
assignment, err := ac.assigner.Assign(incorporatedResult.Result, incorporatedBlockID) | ||
if err != nil { | ||
return fmt.Errorf("could not determine chunk assignment: %w", err) | ||
} | ||
|
||
incorporatedBlock, err := ac.headers.ByBlockID(incorporatedBlockID) | ||
if err != nil { | ||
return fmt.Errorf("failed to retrieve header of incorporated block %s: %w", | ||
incorporatedBlockID, err) | ||
} | ||
|
||
collector := NewApprovalCollector(incorporatedResult, incorporatedBlock, assignment, ac.seals, ac.requiredApprovalsForSealConstruction) | ||
|
||
// Now, we add the ApprovalCollector to the AssignmentCollector: | ||
// no-op if an ApprovalCollector has already been added by a different routine | ||
isDuplicate := ac.putCollector(incorporatedBlockID, collector) | ||
if isDuplicate { | ||
return nil | ||
|
@@ -233,7 +209,7 @@ func (ac *AssignmentCollector) verifySignature(approval *flow.ResultApproval, no | |
id := approval.Body.ID() | ||
valid, err := ac.verifier.Verify(id[:], approval.VerifierSignature, nodeIdentity.StakingPubKey) | ||
if err != nil { | ||
return fmt.Errorf("failed to verify signature: %w", err) | ||
return fmt.Errorf("failed to verify approval signature: %w", err) | ||
} | ||
|
||
if !valid { | ||
|
@@ -290,18 +266,22 @@ func (ac *AssignmentCollector) validateApproval(approval *flow.ResultApproval) e | |
} | ||
|
||
func (ac *AssignmentCollector) ProcessApproval(approval *flow.ResultApproval) error { | ||
// we have this approval cached already, no need to process it again | ||
approvalCacheID := approval.Body.PartialID() | ||
if cached := ac.verifiedApprovalsCache.Get(approvalCacheID); cached != nil { | ||
return nil | ||
} | ||
|
||
Comment on lines
+269
to
+274
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. checks first whether an approval is already cached and drops duplicates, before doing expensive crypto verifications |
||
err := ac.validateApproval(approval) | ||
if err != nil { | ||
return fmt.Errorf("could not validate approval: %w", err) | ||
} | ||
|
||
if cached := ac.verifiedApprovalsCache.Get(approval.Body.PartialID()); cached != nil { | ||
// we have this approval cached already, no need to process it again | ||
newlyAdded := ac.verifiedApprovalsCache.Put(approval) | ||
if !newlyAdded { | ||
return nil | ||
} | ||
|
||
ac.verifiedApprovalsCache.Put(approval) | ||
|
||
for _, collector := range ac.allCollectors() { | ||
err := collector.ProcessApproval(approval) | ||
if err != nil { | ||
|
@@ -373,3 +353,29 @@ func (ac *AssignmentCollector) RequestMissingApprovals(sealingTracker *tracker.S | |
} | ||
return requestCount, nil | ||
} | ||
|
||
// authorizedVerifiersAtBlock pre-select all authorized Verifiers at the block that incorporates the result. | ||
// The method returns the set of all node IDs that: | ||
// * are authorized members of the network at the given block and | ||
// * have the Verification role and | ||
// * have _positive_ weight and | ||
// * are not ejected | ||
func authorizedVerifiersAtBlock(state protocol.State, blockID flow.Identifier) (map[flow.Identifier]*flow.Identity, error) { | ||
authorizedVerifierList, err := state.AtBlockID(blockID).Identities( | ||
filter.And( | ||
filter.HasRole(flow.RoleVerification), | ||
filter.HasStake(true), | ||
filter.Not(filter.Ejected), | ||
)) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to retrieve Identities for block %v: %w", blockID, err) | ||
} | ||
if len(authorizedVerifierList) == 0 { | ||
return nil, fmt.Errorf("no authorized verifiers found for block %v", blockID) | ||
} | ||
identities := make(map[flow.Identifier]*flow.Identity, len(authorizedVerifierList)) | ||
for _, identity := range authorizedVerifierList { | ||
identities[identity.NodeID] = identity | ||
} | ||
return identities, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
I just shifted this code up. Thereby, we are initializing the dependencies of
AssignmentCollector
first and subsequently the struct itself.