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
[Integration] Fixes finalized block height with block state bug #936
Conversation
if b.Header.Height < 3 { | ||
return | ||
} | ||
|
||
confirmsHeight := b.Header.Height - uint64(3) | ||
if confirmsHeight < bs.highestFinalized { | ||
return | ||
} |
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.
same logic as before just simplified the indentations (addressed todo).
Codecov Report
@@ Coverage Diff @@
## master #936 +/- ##
==========================================
- Coverage 56.44% 54.91% -1.54%
==========================================
Files 426 277 -149
Lines 25049 18339 -6710
==========================================
- Hits 14140 10071 -4069
+ Misses 8995 6904 -2091
+ Partials 1914 1364 -550
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
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 detailed explaination
// highestFinalized is only updated when not only the block is added to finalizedByHeight, but also | ||
// it is "exactly" the next height compared to the current value. | ||
// doing so prevents the illusion of highestFinalized indicating the highest finalized height "with an available block". | ||
if h == bs.highestFinalized+1 { |
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 worried that this logic can cause the highestFinalized
to lag permanently behind because we are looping from high to low heights. Suppose there is a gap in the blocks (block 10 is missing), then we receive blocks 11-20. The highestFinalized
block height must be less than 10 while the latest unfinalized height is 20. When we receive block 10, and each subsequent block, we will increase the highestFinalized
value by at most 1 because of this condition and the iteration order. So the gap between latest unfinalized and latest finalized will remain >=10.
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.
Addressed this comment by separating the logic of processing ancestors (high-to-low) and updating the highest finalized height (low-to-heigh) in 7af850e.
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.
🎸
Abstract
This PR fixes a bug with the way block state tracks the finalized block, which causes a
nil
dereference panic on integration tests that use the finalized blocks.Note: block state tracker is a test helper (not part of protocol code).
Problem Definition
While developing integration tests for sealing and verification, a flakey behavior observed that from time to time would cause a
nil
segmentation panic:This panic happens when
s.BlockState.WaitForFirstFinalized(s.T())
returns anil
pointer instead of the first finalized block. Looking at its method, we see there is most probably a discrepancy between thehighestFinalized
variable andfinalizedByHeight
map inBlockState
: there can be cases that although thehighestFinalized
is updated to heightx
blocks of heights <x
are still missing infinalizedByHeight
map, which leads to anil
dereference panic if any of those missing blocks are requested.The lowest-hanging fruit fails
As the lowest hanging fruit, one may suggest ignoring
highestFinalized
and relying solely on thefinalizedByHeight
map:This solution is though failing since there can be cases that
finalizedByHeight[1]
permanently remainsnil
although the consensus progresses fine. Based on the way thatfinalizedByHeight
map is updated there can be inputs that make some finalized blocks _never make their way to thefinalizedByHeight
map. This is basically since the code assumes finalized blocks are coming in order, which is a wrong assumption. As a realistic execution trace of the code, the finalized blocks are coming in this order:So by the time block height 5 arrives, it progresses finalized height to 2 (while block 1 is missing), and even worse than that, when block height 1 arrives it does not make its way to the
finalizedByHeight
map, basically, since it is below the highest finalized block (i.e., 2).Ultimate solution
In this PR we refactor the model block state updates the finalized block imitating the similar way that consensus nodes make a block finalized:
highestFinalized
reflects the highest height that is both finalized, and has its block available infinalizedByHeight
map, with all previous blocks already available in thefinalizedByHeight
map.h
is added tofinalizedByHeight
map only if heighth-1
already both finalized and exists infinalizedByHeight
.This solves both of the problems we encounter with the block state tracker:
finalizedByHeight
map.highestFinalized = h
then all heights[1, h]
are already available on the map.