-
Notifications
You must be signed in to change notification settings - Fork 75
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
[EC-343] Add block header validation during fast sync #380
Conversation
@@ -584,6 +626,11 @@ object FastSync { | |||
validators: Validators, peerEventBus: ActorRef, etcPeerManager: ActorRef, syncConfig: SyncConfig, scheduler: Scheduler): Props = | |||
Props(new FastSync(fastSyncStateStorage, appStateStorage, blockchain, validators, peerEventBus, etcPeerManager, syncConfig, scheduler)) | |||
|
|||
// validation parameters (see: https://github.com/ethereum/go-ethereum/pull/1889) |
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.
Even if these values seem to be defined in the referenced PR, shouldn't be a good idea to put them within application.conf? (hidden from the normal user)
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.
done
log.warning(s"Block header validation failed during fast sync at block ${header.number}: $error") | ||
blacklist(peer.id, blacklistDuration, "block header validation failed") | ||
|
||
// discard last N blocks |
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 quite follow this. If we found that a specific block is invalid, why previous N
ones are dropped? Those might be valid, or not?
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.
Since we only validate every ~Xth block (+ some randomization) we cannot be sure these are valid blocks.
See ethereum/go-ethereum#1889 for comprehensive explanation. In short:
With this caveat calculated, the fast sync should be modified so that up to the pivoting point - X, only every K=100-th header should be verified (at random), after which all headers up to pivot point + X should be fully verified before starting state database downloading. Note: if a sync fails due to header verification the last N headers must be discarded as they cannot be trusted enough.
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.
Should it always be N
when N
are available?
In other words: forget the begining of the chain, I'm talking about a situation when we are in the [target - X, target]
range. Shouldn't it be:
val numberOfBlocksToDiscard = if (target - current <= X) N + X - (target - current) else N
(maths may need double-checking)
So, N
plus the number of blocks already validated in the X
phase?
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.
Why do you think we should drop the extra blocks? For me it doesn't seem to make a difference in this context. (is there a difference if validation fails at block target/2 or target-10? I think it's sufficient to drop N in both cases)
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.
Somehow I thought we should revert to a fully validated block, but since N
is not divisible by K
that is not the case. Anyway, the way put it - no, I don't think it will make a difference 👍
|
||
// discard last N blocks | ||
(header.number to ((header.number - N) max 1) by -1).foreach { n => | ||
blockchain.getBlockHeaderByNumber(n).foreach { header => |
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 getBlockHeaderByNumber
needed if we already have header.hash
and removeBlock
doesn't fail (afaik) if hash does not exist?
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 catch, it's not needed. (I don't expect this piece of code to be called too often, but still)
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 it is needed here. header
was shadowed inside foreach
, we only have one header (the one we start with). I changed to name to make that more clear
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.
Code look goods, but i have one question.
According to
With this caveat calculated, the fast sync should be modified so that up to the pivoting point - X, only every K=100-th header should be verified (at random), after which all headers up to pivot point + X should be fully verified before starting state database downloading
.
Shouldn't we also verify all blocks from targetBlock
to targetBlock+x
to be sure our targetblock
is safe ?
@KonradStaniec |
@LukasGasior1 I see, my misunderstanding comes from the fact that we have a diffrent algorithm that geth. |
It was because we want state as soon as possible, otherwise other peers may start prunning it and we end up with partially downloaded state that we cannot continue downloading (in fact it still may happen). Btw I think geth also does it this way (i.e it downloads state first). |
Right, but if we discover a block was invalid after switching to regular we won't be able to recover, will we? |
Wait, now I'm not sure I follow... @KonradStaniec wrote:
We should full validate from |
@rtkaczyk it comes from this sentence (in geth PR):
according to it, we should also validate pivot + X blocks before starting state download..which doesn't seem to make sense in our case |
# See: https://github.com/ethereum/go-ethereum/pull/1889 | ||
fast-sync-block-validation-k = 100 | ||
fast-sync-block-validation-n = 2048 | ||
fast-sync-block-validation-x = 50 |
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.
How did you choose this X value? In the PR (white paper) X=24
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.
Ahh, so you doubled it because we treat target/pivot differently?
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.
Yes, I was not sure how to interpret that sentence, but my intuition is that validating pivot - 2x to pivot
is the same as validating pivot - x to pivot + x
. (and validation after pivot happens anyway in regular sync).
@LukasGasior1 I am not sure how its implemented in geth ( i will look it up to be sure ) and i am still unsure how it should works but this paragraph makes me little unsure: Also this are maybe question to some futre pr, as till now we were doing pretty fine without validation at all :) |
I think we should address that. At the very least we should check whether our state root hash equals to the one on the accepted target block, and otherwise declare failure to sync (and plan something better for the future) |
So what we need to do is:
But if validation fails on target block (i.e our state != it's state) we declare failure (and drop the whole blockchain?). Note that if validation fails at something like target - 10 it's sufficient to just drop N blocks (as the gap between blocks we validate is smaller than N). wdyt @KonradStaniec @rtkaczyk ? |
sounds good |
Actually it doesn't sound good. |
Let's consider a peer that sends us invalid target block when we start fast sync: |
Since we start with downloading state, we first need to obtain the state root hash for the target block - this is what I meant by So rather than restarting fast sync, or redownloading the state, or whatever, we could do an easy/dumb thing and declare a failure. As a follow-up we can come up with something better. Here's one idea:
|
Ok, it makes sense. If target fails validation we really should declare a failure. Besides that, I think we should also implement what I mentioned (choose different target if current (failed) block is less than N). |
Sorry I did not get that. Do you mean choosing different target number, or different target header in the |
I mean restarting fast sync from scratch, i.e asking different peer for (possibly) different block. But now I'm not sure it'll be needed anymore since we're going to fail if target is invalid anyway |
So there are a few ways you can go about restarting:
\1. and 2. are not perfect, and 3. is similar to my idea above - if that would be implemented we wouldn't need to restart at all. |
|
No description provided.