-
Notifications
You must be signed in to change notification settings - Fork 14
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
WIP: Taking finalization states into account when deciding to switch to the new chain #170
Conversation
src/esperanza/finalizationstate.h
Outdated
@@ -216,6 +218,13 @@ class FinalizationState : public FinalizationStateData { | |||
|
|||
mutable CCriticalSection cs_esperanza; | |||
|
|||
struct Storage { | |||
std::map<uint256, FinalizationState> states; |
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.
A problem I see with this is that you never delete stuff from the map. Ideally whenever we reach finalization we can drop everything that comes before. This should behave more like a rolling window.
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.
Good point.
src/esperanza/finalizationstate.cpp
Outdated
auto const hash = index->GetBlockHash(); | ||
auto it = states.find(hash); | ||
if (it == states.end()) { | ||
it = states.emplace(hash, FinalizationState(*FindOrCreate(index->pprev))) |
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 recursion is a good idea here, if someone serves you a very long header chain and then sends you the last block you would create a bazillion of intermediate states (easy DoS).
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.
And again due my wrong assumption, I was thinking that recursion call MUST return in one hop because we already have state for previous tip of the chain new block belongs to.
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 might become a correct solution if we manage to handle the block reconstruction so that we can assure consequentiality and ordering of stored blocks.
src/esperanza/finalizationstate.cpp
Outdated
FinalizationState *FinalizationState::Storage::FindOrCreate( | ||
const CBlockIndex *index) { | ||
LOCK(cs_storage); | ||
if (index == nullptr || index->pprev == nullptr || |
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.
if for some reason the block passed does not have a pointer to the pprev you are going to return the current main chain head state. Why?
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.
It's not current main chain. esperanzaState.get()
returns state for the genesis block. I thought that empty pprev means that it's block is genesis.
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 this you have to check thoroughly cause I wouldn't assume that there is no chance of a pIndex having pprev nullptr because of the previous block not being arrived yet.
src/validation.cpp
Outdated
|
||
if (n->GetLastJustifiedEpoch() < c->GetLastJustifiedEpoch()) { | ||
// UNIT-E TODO delete from the disk? If not, skip this condition. | ||
LogPrintf("Reject outdated justified epoch: %d %d\n", n->GetLastJustifiedEpoch(), c->GetLastJustifiedEpoch()); |
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.
As we discussed we don't reject but we keep it in case it becomes justified at a longer height.
src/validation.cpp
Outdated
} | ||
|
||
// Check whether new block's chain has forked after current's chain last justified block | ||
uint256 checkpoint = c->GetCheckpointHash(c->GetLastJustifiedEpoch()); |
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 should be GetLastFinalizedEpoch()
and in general this check should be done when we receive the header since we don't need the full block to verify that.
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 guess you meant GetLastFinalizedEpoch
?
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 sure, sorry for that! Fixed.
src/validation.cpp
Outdated
// Check whether new block's chain has forked after current's chain last justified block | ||
uint256 checkpoint = c->GetCheckpointHash(c->GetLastJustifiedEpoch()); | ||
if (!checkpoint.IsNull() && !IsForkedAfter(checkpoint, pindex)) { | ||
// UNIT-E TODO delete from the disk? |
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.
If we reject before AcceptBlock()
nothing we'll be saved in disk.
Is a very good point that we should document the fork choice rule in a ADR, this would There are 4 main points for the fork-choice rule from casper (https://arxiv.org/pdf/1710.09437.pdf par.4):
Furthermore blocks that have forking before our last justification should be rejected straight away. |
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 think a feature like this
- needs to be thoroughly tested
- should be documented/discussed in an ADR first
Signed-off-by: Stanislav Frolov <[email protected]>
New code was pushed, I am dismissing my old request changes review.
src/net_processing.cpp
Outdated
} | ||
return error("invalid header received"); |
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 do not think that this move is correct. Previously: Would return this error in case of potential Denial-of-Service. Now with this change: Would return error in that case and if ProcessNewBlockHeaders
/AcceptBlockHeader
fails (for other reviewers: DoS is only checked then, hence the move one layer of if-conditions up – look at the context).
AFAICS: The AcceptBlockHeader
looks at a single header. ProcessNewBlockHeaders
looks at several headers. If it returns false then it did not validate some header, but there are also some valid headers which should be processed further down. Moving the return here will preempt that.
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 had too many attempts to implement fork-choice, it might be an accidental artifact from previous one. Will check, thanks for this catch!
@@ -2537,8 +2570,32 @@ CBlockIndex* CChainState::FindMostWorkChain() { | |||
do { | |||
CBlockIndex *pindexNew = nullptr; | |||
|
|||
if (chainActive.Tip() != nullptr) { |
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 remember that in some pull request I was asked whether the comparison withnullptr
is necessary and a reference to the cpp core guidelines was given. I personally do not give a rats arse about this. But it popped into my mind – in that other pull request I obeyed the cpp core guidelines and removed the comparison with nullptr
actually...
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 remember that conversation. I'm not trying to go against the tide actually but it's just a strong habit. Previously I mentioned implicit type casting, but moreover this way is much more clear to understand I think. And actually cpp guidelines are bit inconsistent in this, e.g. at first they told:
void f(int i)
{
if (i) // suspect
// ...
if (i == success) // possibly better
// ...
}
And then...
// These all mean "if `p` is `nullptr`"
if (!p) { ... } // good
if (p == 0) { ... } // redundant `== 0`; bad: don't use `0` for pointers
if (p == nullptr) { ... } // redundant `== nullptr`, not recommended
So in first example it's not redundant, but in seconds it's it. I don't see any logic in this. For sure I can review and change all such checks, it's not something I gonna fight for. But better I'd like to have discussion and consensus in the team and if we follow this guideline, let's put it in documentation. My point is to not follow guideline just because it's a guideline, but to understand why do we do so.
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.
Actually I do like the explicit versions == nullptr
and != nullptr
better. But above all I value consistency :D
Anyways. Its a very very small nit. @Ruteri If you sent a pull request referencing the cpp core guidelines in developer notes the discussion could be taken there ;-)
@scravy It's not finished yet, I still need to add bunch of tests at least, so I guess your previous change request remains valid. |
Signed-off-by: Stanislav Frolov <[email protected]>
Signed-off-by: Stanislav Frolov <[email protected]>
I'm pausing development on this PR because it's blocked by #219. |
There has not been any activity in the past 10 days. Is this still active? |
4 similar comments
There has not been any activity in the past 10 days. Is this still active? |
There has not been any activity in the past 10 days. Is this still active? |
There has not been any activity in the past 10 days. Is this still active? |
There has not been any activity in the past 10 days. Is this still active? |
Dear Bot, I'm working on the different branch by picking some pieces from here. This guy is not supposed to be merged, and I will close PR and delete this branch after I finish all the commits and fork-choice rule related stuff. Thank you, |
There has not been any activity in the past 10 days. Is this still active? |
Superseded by #525. |
It's a clean version of previous PR #164 after refactoring and fixing style comments.
Rough implementation of applying finalization to chain reorganization. It's WIP and in fact just a result of my initial unite/bitcoin sources research.
Some bullets/todo: