Repository navigation
fix(pushsync): prevent silent chunk loss on shallow receipts - #5390
gacevicljubisa wants to merge 21 commits into
Conversation
| // error budget and wait for any other inflight parallel pushes | ||
| // (e.g. multiplex forwards) before giving up. Only return | ||
| // ErrShallowReceipt once the entire budget is spent. | ||
| shallowReceiptResult = result.receipt |
There was a problem hiding this comment.
you've removed the skiplist addition line that was on the left side of the diff (L493). any particular reason?
There was a problem hiding this comment.
actually it is not removed, it is on the common failure path at pushsync.go:524
| // (e.g. multiplex forwards) before giving up. Only return | ||
| // ErrShallowReceipt once the entire budget is spent. | ||
| shallowReceiptResult = result.receipt | ||
| result.err = err |
There was a problem hiding this comment.
not sure if I understand this fully, but this statement and the next one in the else block seem like assigned but never used where is result.err used?
There was a problem hiding this comment.
This is used now. The shallow case sets result.err and falls through to the shared failure path. That path logs result.err in the "could not push to peer" debug line, counts one failed attempt and adds the peer to errSkip. So a shallow receipt is handled like any other failed attempt, and the deepest shallow receipt is kept for the ErrShallowReceipt return at the end of the call.
| // Retry budget exhausted: no peer in the correct neighborhood stored the | ||
| // chunk. Report CouldNotSync rather than Synced to avoid falsely marking | ||
| // the chunk as delivered. | ||
| if err := s.storer.Report(ctx, op.Chunk, storage.ChunkCouldNotSync); err != nil { |
There was a problem hiding this comment.
Changing to ChunkCouldNotSync, will cause some false reporting because the ChunkCouldNotSync state is not checked and updated in uploadstore.go ->Report function. Maybe you would need to alsi include in that switch statement also ChunkCouldNotSync.
| // is outside our AOR we would store it in a low bin and unreserve() | ||
| // would evict it almost immediately, leaving the chunk nowhere on the | ||
| // network while the origin believes it was delivered. | ||
| if swarm.Proximity(ps.address.Bytes(), chunkAddress.Bytes()) < rad { |
There was a problem hiding this comment.
What happens if you have doubling set on, and the chunk is saved into a sister neighborhood? Should we have here a check based on the committed depth?
There was a problem hiding this comment.
No change needed. The handler uses ps.radius(), which is the reserve radius. A doubling node has a reserve radius one bin shallower than its committed depth, so it stores both its own and its sister neighborhood. A chunk from the sister neighborhood passes the check and the node stores it. That is correct, because the node really holds that chunk and serves it on retrieval. A committed-depth check would refuse it.
The origin sees this receipt as shallow, because it is one bin below the network radius, and pushes again.
There was a problem hiding this comment.
Update: this is covered by SWIP-48 (ethersphere/SWIPs#97). There every full in-AOR node stores the chunk and keeps forwarding, and signs its own receipt only as a fallback. A doubling node is in AOR by its reserve radius, so it stores and forwards the chunk to the sister neighborhood and returns that receipt. The refuse check stays on the reserve radius. No change in this PR.
Updated comments to correct formatting and clarity.
1e13f0a to
99a2bc6
Compare
|
This looks good to me still. |
… to be the closest
…-depth-chunk-loss
…of-depth refusals
06be75b to
cd00dda
Compare
Checklist
Description
On master, pushsync can report a chunk as synced when no node in its neighborhood stores it. The upload returns 201 and the tag says Synced. No other node can retrieve such a chunk, and the storer evicts it when its reserve runs over capacity. This PR turns that false success into an error or a retry.
What changes
po >= r. Master also acceptedpo >= r - 1on a node without capacity doubling. The tolerance is removed fromnode.go, andcheckReceiptalso returns the PO.ErrShallowReceiptonly when the budget or the peer list runs out. On master the first shallow receipt ended the call.ErrWantSelfa forwarder now widens the peer filter, like onErrNotFound, and waits 600 ms when peers are skipped for overdraft. A forwarder stores the chunk only inside its own AOR. Outside its AOR it returnsErrOutOfDepthStoringto the previous hop. On master it stored the chunk and signed a receipt.ErrOutOfDepthStoring. The deferred pusher counts it as one of its 6 calls. A direct upload fails at once. On master the origin stored the chunk on itself.ChunkCouldNotSyncinstead ofChunkSynced. The tag's Synced count stays below Split. A direct upload returnsErrCouldNotSync, so the API answers 500 instead of 201.ErrNotFoundbefore it submits the chunk again, instead of a tight loop.out_of_depth_storingandorigin_no_peers_leftin pushsync,total_could_not_syncin pusher.total_syncednow counts only chunks with a valid receipt or stored by this node.What does not change
errSkip.ErrShallowReceipt.Follow-up
PR #5667 is stacked on this PR. It gives a chunk one more round when a direct upload gets
ErrOutOfDepthStoring, so the two PRs go together.Open API Spec Version Changes (if applicable)
Motivation and Context (Optional)
Related Issue (Optional)
Screenshots (if appropriate):