-
Notifications
You must be signed in to change notification settings - Fork 919
Forward sync columns by root #7946
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: unstable
Are you sure you want to change the base?
Conversation
This one should be ready for another round of review. Used claude to fix up the sync tests, lmk if you wanted a different approach. I don't have strong opinions on the test suite. |
This pull request has merge conflicts. Could you please resolve them @pawanjay176? 🙏 |
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've gone through about half of this PR. Mostly just nits
In general there are probably opportunities to refactor/reduce code duplication but I agree with you that this PR is already big and any refactors should be made in a subsequent PR
I'll aim to finish my review tomorrow
if matches!(epe, ExecutionPayloadError::RejectedByExecutionEngine { .. }) { | ||
debug!( | ||
error = ?err, | ||
"Invalid execution payload rejected by EE" | ||
); | ||
Err(ChainSegmentFailed { | ||
message: format!( | ||
"Peer sent a block containing invalid execution payload. Reason: {:?}", | ||
err | ||
), | ||
peer_action: Some(PeerAction::LowToleranceError), | ||
faulty_component: Some(FaultyComponent::Blocks), // todo(pawan): recheck this | ||
}) |
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 there a reason why RejectedExecutionEngine
is a low tolerance error but other ExecutionPayloadErrors
are not? Like InvalidPayoadTimestamp
for example
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.
/// Note: this variant starts out in an uninitialized state because we typically make | ||
/// the column requests by root only **after** we have fetched the corresponding blocks. | ||
/// We can initialize this variant only after the columns requests have been made. | ||
DataColumnsFromRoot { |
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.
maybe we could rename the old DataColumns
variant to DataColumnsByRange
just for clarity
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 tested this branch on devnet-3. I ran both range sync and backfill sync in the fullnode and supernode case and it worked well. Supernode backfill is slow, but its also the same on unstable. I guess we wouldn't really see any sync improvements unless we tried syncing on a forky network. I'm happy to green check this once CI is passing
@pawanjay176 this commit merges data_columns_by_root_range_requests and data_columns_by_root_requests with a -216 lines diff. Feel free to cherry-pick into your branch but I feel the |
} | ||
debug!(?requests, "Successfully retried requests"); | ||
} | ||
Err(err) => { |
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.
You should match Err(RpcRequestSendError::NoPeer(err))
explicitly and if it's an InternalError discard the request. Otherwise we may infinte loop
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.
Discarding the request is also not great as that may stall sync silently. I thought this way, atleast we know what's causing the stalling by making some noise in the logs
/// can result in undefined state, so it's treated as a hard error and the lookup is dropped. | ||
UnexpectedRequestId { | ||
expected_req_id: DataColumnsByRootRequestId, | ||
req_id: DataColumnsByRootRequestId, |
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 change necessary? Pushed a commit that reverts this change and everything compiles fine. Feel free to cherry-pick 51ab53b
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.
Yeah don't remember why i did this tbh
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.
Ohh I remember now. In this PR, the DataColumnsByRootRequestId
struct became larger because DataColumnsByRootRequester
became an enum.
So the linter was throwing a bunch of errors like
error: the `Err`-variant returned from this function is very large
--> beacon_node/network/src/sync/network_context/custody.rs:455:10
|
48 | / UnexpectedRequestId {
49 | | expected_req_id: DataColumnsByRootRequestId,
50 | | req_id: DataColumnsByRootRequestId,
51 | | },
| |_____- the largest variant contains at least 224 bytes
...
455 | ) -> Result<(), Error> {
| ^^^^^^^^^^^^^^^^^
|
= help: try reducing the size of `sync::network_context::custody::Error`, for example by boxing large elements or replacing it with `Box<sync::network_context::custody::Error>`
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#result_large_err
Changed it to Id to keep the struct smaller. I felt it gave us similar info to debug if required.
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.
We implement custom impl Display for all these Id types so the visual result is the same
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.
Yeah but we would need to silence the clippy warnings all around places which return RpcRequestErrors in that case.
…uled (sigp#8109) sigp#8105 (to be confirmed) I noticed a large number of failed discovery requests after deploying latest `unstable` to some of our testnet and mainnet nodes. This is because of a recent PeerDAS change to attempt to maintain sufficient peers across data column subnets - this shouldn't be enabled on network without peerdas scheduled, otherwise it will keep retrying discovery on these subnets and never succeed. Also removed some unused files. Co-Authored-By: Jimmy Chen <[email protected]> Co-Authored-By: Jimmy Chen <[email protected]>
…igp#8112) - PR sigp#8045 introduced a regression of how lookup sync interacts with the da_checker. Now in unstable block import from the HTTP API also insert the block in the da_checker while the block is being execution verified. If lookup sync finds the block in the da_checker in `NotValidated` state it expects a `GossipBlockProcessResult` message sometime later. That message is only sent after block import in gossip. I confirmed in our node's logs for 4/4 cases of stuck lookups are caused by this sequence of events: - Receive block through API, insert into da_checker in fn process_block in put_pre_execution_block - Create lookup and leave in AwaitingDownload(block in processing cache) state - Block from HTTP API finishes importing - Lookup is left stuck Closes sigp#8104 - sigp#8110 was my initial solution attempt but we can't send the `GossipBlockProcessResult` event from the `http_api` crate without adding new channels, which seems messy. For a given node it's rare that a lookup is created at the same time that a block is being published. This PR solves sigp#8104 by allowing lookup sync to import the block twice in that case. Co-Authored-By: dapplion <[email protected]>
This reverts commit 6ea14016f3d164456bc4c3cae0355ab532fe1a86.
Issue Addressed
N/A
The Problem
Our current strategy of syncing blocks + columns by range works roughly as follows for each batch:
SyncingChain
to fetch blocks from and send aBlocksByRange
requestDataColumnsByRange
request at the same timeblock_root
and thekzg_commitment
matches. If the coupling failed, try to re-request the failed columns.This strategy works decently well when the chain is finalizing as most of our peers are on the same chain. However, in times of non-finality we need to potentially sync multiple head chains.
This leads to issues with our current approach because the block peer and the data column peers might have a different view of the canonical chain due to multiple heads. So when we use the above approach, it is possible that the block peer returns us a batch of blocks for chain A while some or all data column peers send us the batch of data columns for a different chain B. Different data column peers might also be following different chains.
We initially tried to get around this problem by selecting column peers only from within the current
SyncingChain
. EachSyncingChain
represents ahead_root
that we are trying to sync to and we group peers based on samehead_root
. That way, we know for sure that the block and column peers are on the same chain. This works in theory, but in practice, during long periods of non-finality, we tend to create multiple head chains based on thehead_root
and split the global peerset. Pre-fulu, this isn't a big deal since all peers are supposed to have all the blob data.But splitting peers with peerdas is a big challenge due to not all peers having the full data available. There are supernodes, but during bad network conditions, supernodes would be getting way too many requests and not even have any incoming peer slots. As we saw on fusaka devnets, this strategy leads to sync getting stalled and not progressing.
Proposed Changes
1. Use
DataColumnsByRoot
instead ofDataColumnsByRange
to fetch columns for forward syncThis is the main change. The new strategy would go as follows:
SyncingChain
to fetch blocks from and send aBlocksByRange
requestblock_roots
and trigger aDataColumnsByRoot
request for every block in the response that has any blobs based on theexpected_kzg_commitments
field.(4) kinda assumes that most synced/advanced peers would have different chains in their fork choice to be able to serve specific by root requests. My hunch is that this is true, but we should validate this in a devnet 4 like chain split scenario.
Note: that we currently use this by root strategy only for forward sync, not for backfill. Backfill has to deal with only a single canonical chain so byrange requests should work well there.
2. ResponsiblePeers to attribute peer fault correctly
Adds the
ResponsiblePeers
struct which stores the block and the column peers that we made the download requests to.For most of our peer attributable errors, the processing error indicates whether the block peer was at fault or if the column peer was at fault.
We now communicate this information back to sync and downscore specific peers based on the fault type. This imo, is an improvement over current unstable where most of the time, we attribute fault to the peer that "completed" the request by being the last peer to respond.
Due to this ambiguity in fault attribution, we weren't downscoring pretty serious processing errors like
InvalidKzgProofs
,InvalidExecutionPayload
etc. I think this PR attributes the errors to the right peers. Reviewers please check that this claim is actually true.3. Make
AwaitingDownload
an allowable in-between stateNote: This has been extracted to its own PR here and merged #7984
Prior to peerdas, a batch should never have been in
AwaitingDownload
state because we immediataly try to move fromAwaitingDownload
toDownloading
state by sending batches. This was always possible as long as we had peers in theSyncingChain
in the pre-peerdas world.However, this is no longer the case as a batch can be stuck waiting in
AwaitingDownload
state if we have no peers to request the columns from. This PR makesAwaitingDownload
to be an allowable in between state. If a batch is found to be in this state, then we attempt to send the batch instead of erroring like before.Note to reviewer: We need to make sure that this doesn't lead to a bunch of batches stuck in
AwaitingDownload
state if the chain can be progressed.