-
Notifications
You must be signed in to change notification settings - Fork 203
[DataAvailability] Implement new unit tests for execution data backend #8200
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
base: feature/optimistic-sync
Are you sure you want to change the base?
[DataAvailability] Implement new unit tests for execution data backend #8200
Conversation
Refactored execution data tracker. I completely removed the indexer dependency from it. Response handler in execution data backend was refactored in a way it supports criteria and execution forks. More work to be done on this but this is the first step forward.
d0c8497 to
661a1d5
Compare
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
e0cbb11 to
5ecb6ab
Compare
5ecb6ab to
720d4c8
Compare
|
@peterargue can u look into that ? |
- Removed tests for different types of backfill. - Simplify tests by removing unecessary goroutines.
…handler-function-with-interface Illia malachyn/replace response handler function with interface
|
@peterargue @UlyanaAndrukhiv @AndriiDiachuk, guys, I guess this is ready for review. I'd really appreciate your comments/criticisms. If it feels too clumsy and complex, I can split it up into several smaller PRs. |
Also, skip the tests for events and account statutes as they depend on execution data test suite.
UlyanaAndrukhiv
left a comment
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.
Overall, this looks good! I’ve left a few suggestions for improvements. I mostly skipped missing godocs for this round. I am also considering a refactoring of ExecutionDataBackend and executionDataProvider, but I’d like to try it first—if it works, I will provide a separate comment afterward.
module/executiondatasync/optimistic_sync/execution_result/info_provider.go
Outdated
Show resolved
Hide resolved
module/executiondatasync/optimistic_sync/execution_result/info_provider.go
Outdated
Show resolved
Hide resolved
module/executiondatasync/optimistic_sync/execution_result_info_provider.go
Outdated
Show resolved
Hide resolved
module/executiondatasync/optimistic_sync/execution_result/info_provider_test.go
Outdated
Show resolved
Hide resolved
engine/access/state_stream/backend/backend_executiondata2_test.go
Outdated
Show resolved
Hide resolved
engine/access/state_stream/backend/backend_executiondata2_test.go
Outdated
Show resolved
Hide resolved
engine/access/state_stream/backend/backend_executiondata2_test.go
Outdated
Show resolved
Hide resolved
peterargue
left a comment
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.
thanks for the cleanup. I like your refactor of the provider.
engine/access/state_stream/backend/backend_executiondata2_test.go
Outdated
Show resolved
Hide resolved
module/executiondatasync/optimistic_sync/execution_result_info_provider.go
Outdated
Show resolved
Hide resolved
| if startBlockID == b.state.Params().SporkRootBlock().ID() { | ||
| return b.rootBlockHeight, nil | ||
| } |
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.
is this needed? shouldn't this also be returned by b.headers.ByBlockID(startBlockID)?
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.
IDK actually. This code existed for a long period of time so I'm afraid to remove it. I'm not even sure we need these trackers at all. I've created a task to investigate it and remove them in case we don't #8238
engine/access/state_stream/backend/backend_executiondata2_test.go
Outdated
Show resolved
Hide resolved
engine/access/state_stream/backend/backend_executiondata2_test.go
Outdated
Show resolved
Hide resolved
| // ignorable errors correctly. It ensures that the provider retries or waits when encountering | ||
| // errors like missing required executors, rather than terminating the subscription immediately, | ||
| // until the context is canceled. | ||
| func (s *BackendExecutionDataSuite2) TestExecutionDataProviderIgnorableErrors() { |
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 don't think "ignorable" is the right framing. how about
| func (s *BackendExecutionDataSuite2) TestExecutionDataProviderIgnorableErrors() { | |
| func (s *BackendExecutionDataSuite2) TestExecutionDataProviderCriteriaNotMet() { |
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.
TestExecutionDataProviderErrors is also a spectre of errors where a criteria is not met. However, in one case, we stop streaming data, and in another we keep doing it.
module/executiondatasync/optimistic_sync/execution_result/info_provider_test.go
Outdated
Show resolved
Hide resolved
module/executiondatasync/optimistic_sync/execution_result/info_provider_test.go
Outdated
Show resolved
Hide resolved
This reduces the complexity of the tests and makes them less brittle. Anyway, we achieved nothing with the Times() expectations here.
module/executiondatasync/optimistic_sync/execution_result_info_provider.go
Outdated
Show resolved
Hide resolved
module/executiondatasync/optimistic_sync/execution_result/info_provider_test.go
Outdated
Show resolved
Hide resolved
| // that we wouldn't consider anyway. | ||
| if criteria.ParentExecutionResultID != flow.ZeroID && | ||
| executionResult.PreviousResultID != criteria.ParentExecutionResultID { | ||
| return optimistic_sync.ErrForkAbandoned |
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 doesn't mean the fork was abandoned, just that the provided result's fork does not include the parent result.
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.
Hmm, will sth like that work?
func (p *Provider) findResultAndExecutors(...) (flow.Identifier, flow.IdentifierList, error) {
// ... (ByBlockID call)
var (
hasParentMismatch bool
lastErr error
)
for executionResultID, executionReceiptList := range allReceiptsForBlock.GroupByResultID() {
// ...
if err := p.isExecutorGroupMeetingCriteria(result, receipts, criteria); err != nil {
if errors.Is(err, optimistic_sync.ErrParentResultMismatch) { // 1. We return a different error
hasParentMismatch = true
}
lastErr = err
continue
}
// ... (collect matchingResults)
}
if len(matchingResults) == 0 {
// If we found results but they all had the wrong parent,
// this is where we might eventually conclude the fork we are following is gone.
if hasParentMismatch {
return flow.ZeroID, nil, optimistic_sync.ErrForkAbandoned // 2. We state that this is a fork-abandoned error
}
if lastErr != nil {
return flow.ZeroID, nil, lastErr
}
return flow.ZeroID, nil, optimistic_sync.ErrNotEnoughAgreeingExecutors
}
// ...
}
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.
When we talk about an abandoned result, we mean a result that conflicts with a sealed result for a given block. Given results form a chain, if an ancestor result is abandoned, you can deduce that all of its ancestors must be as well.
Checking if a result conflicts with a sealed result is simple. All you have to do is check if there was a match for the block, and if not, check if the block is sealed (i.e. has a sealed result). I think Uliana added functionality to check this already.
In the case where the result is not yet sealed, but its ancestor is abandoned, you could optimize by flagging the result abandoned early so the client could react sooner. I don't think this is required in the first version.
my preference is to keep this functionality simple and and focused. The job of the results provider is to find the result given the provided criteria. then given a result, the snapshot provider can indicate if the result is abandoned or not. I think in the optimized case, we could do something like check for the next result. if there is no match, check the status of the previous result to see if it is abandoned. I'm not 100% sure if this is the right approach though, so let's leave it for later and just use the simple sealed check for now.
| // - [optimistic_sync.ErrForkAbandoned]: If the execution fork of an execution node from which we were getting the | ||
| // execution results was abandoned. | ||
| // - [optimistic_sync.ErrNotEnoughAgreeingExecutors]: If there are not enough execution nodes that produced the | ||
| // execution result. | ||
| // - [optimistic_sync.ErrRequiredExecutorNotFound]: If the criteria's required executor is not in the group of | ||
| // execution nodes that produced the execution result. |
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.
these errors relate to a specific execution result, but this method queries a set of results for a block. Instead of returning these, I think this method should simply state that no result matched the requested criteria.
looking at the returned errors, it seems there are a few cases:
- The block has not been executed yet (no receipts found)
- The block is before the node's available history
- No results found that matched the criteria (unsealed block)
- No results found that matched the criteria (sealed block)
- The criteria was invalid
| // Expected errors during normal operations: | ||
| // - [optimistic_sync.ErrForkAbandoned]: If the execution result is in a different fork than the one specified in the criteria. | ||
| // - [optimistic_sync.ErrNotEnoughAgreeingExecutors]: If the group does not have enough agreeing executors. | ||
| // - [optimistic_sync.ErrRequiredExecutorNotFound]: If the required executor is not in the group. |
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'm not sure it's useful to return the specific error here. we generally just want to know if this result matched the criteria.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the ✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Closes #8059, #7587, #8231, #8135
Changes:
Streamer streams all available blocks at the start, so either such tests should be there, or they should be removed, since they're basically useless - no matter how many blocks are available, all of them are streamed to the subscription.
Initially, I wanted to do it for testing, so I could easily mock this behavior and write good tests for the backend. However, the problem is that this logic is not injected into the endpoints. Therefore, it is not as useful for the backend tests, but they're useful for the tests in the subscription package (which might be extended/refactored in the [Data Availability] Introduction of a new subscription package #8131). Also, the interface makes it easier to reason about both the backend and subscription packages, as well as their interaction.
Please, review in the following order:
optimistic sync package (changes to result provider + its tests) -> subscription package (changes to streamer and
response handler/data provider interface) -> backend -> tests for backendNote, the tests for events and account statutes MUST fail in this PR, as the result provider and response handler func were significantly changed. We should merge PRs for the events/accounts into this one, then check if CI passes, and only after that we can merge everything into the feature/optimistic_sync
To be done in this PR: