Two minimal scenarios where equivocation harms progress #380
Replies: 2 comments 1 reply
-
Ah, if you want to make it even funnier, starting from
This means that |
Beta Was this translation helpful? Give feedback.
-
Thanks for sharing that. I think from the consensus state machine the situation is clear in this write-up. Could you please also say a bit about the attack surface of accepting all messages from Byzantine nodes? |
Beta Was this translation helpful? Give feedback.
-
Equivocation is probably the most powerful artifice that can be used by Byzantine validators to harm the protocol.
There are two issues, #309 and #103 , describing scenarios and implications. But I feel that is still relevant to fully describe two scenarios where equivocation from at most
f
validators can harm Tendermint's liveneess.Model
First, two main assumptions:
We consider a minimal set of four validators
A
,B
,C
, andD
:A
is an asynchronous validator. It is correct but slow. Therefore, we cannot rely on it to receive messages before the associated timeouts expire while we are not past GST. We should assume it is likely to prevote and precommitnil
mostly;B
is a Byzantine validator, it can do whatever it wants;C
andD
are correct and synchronous validators, we rely on them for progress.Overview
C
orD
, that proposes a valueX
C
andD
receiveX
, validate it and issue a prevote forid(X)
at round0
A
times out in the propose step and prevote fornil
So, the initial scenario, only considering correct validators, is the following:
Notice that
B
ultimately defines the fate of this round of consensus. If it prevotesid(X)
, we have a polka forX
, which is likely to be the decision value. If it prevotes any other value, includingnil
, the round is doomed, as we can only see precommits fornil
. But sinceB
is Byzantine, it can just do both, producing two follow-up scenarios:Scenario
1.a
can lead to progress, i.e. the decision ofX
in the round, with the following scenario in the precommit step:Notice that we are still assuming that validator
A
is slow and precommitsnil
. This can be caused because it timeouts while in scenario(1)
, i.e., it does not receive the prevote fromB
, or because it did not even receive the proposal forX
.It is simple to see that scenario
2
has the same problem as scenario1
: the success or not of the round depends on the vote of the Byzantine validatorB
. Which can equivocate or can just omit itself. This is the basis for the two described scenarios.Precommit equivocation
In this scenario,
B
does not need to equivocate in the prevote step, it can just prevote forid(X)
and we get to scenario1.a
, then to scenario2
. At this point,B
can literally define who is going to decideX
and who is not, as follows:The correct validators that see scenario
2.a
will decideX
and move to round(H+1, 0)
. The correct validators that see scenario2.b
, or after a timeout still have scenario2
, will move to the next round(H, 1)
.The problem is when no enough validators move to round
(H, 1)
, namely, less thanf
validators. In this case, they are stuck in that round, since they will never receive2f + 1
vote messages. The same applies, of course, if less thanf
validators move to the next height, where they will be stuck until at least one more correct validator joins the first round of heightH+1
.This scenario, although tricky, should be addressed by a synchronization protocol. Once a correct validator has moved to height
H+1
, all correct validators should eventually learn the decision valueX
of heightH
, plus aCommit
certificate for that value.Prevote Equivocation and Hidden Lock
In the second scenario, the Byzantine validator does not want any validator to decide
X
in heightH
. So we should consider the scenarios2
and2.b
. All validators move to round(H, 1)
. The problem, however, is what is the state they bring for this round, in terms of the locked and valid value. The equivocation here therefore occurs on the prevote step.Starting from scenario
1
, the Byzantine validator can produce at different validators scenarios1.a
and1.b
. Scenario1.a
means that that validator has probably locked onX
at round0
, or at least has setX
as valid value at round0
. In scenario1.b
, there is no locked or valid value in any correct validator, namely, they can accept any proposed value.No, lets assume that:
C
has seen scenario1.a
and has locked onX
, which is also its valid valueD
has seen scenario1.b
and has no locked or valid valueC
is the next proposerIn this case,
C
will re-proposeX
, as it is a valid value. It will send aProposal(H, 1, X, 0)
, notice thatvr == 0
.This scenario is covered by line 28 of the pseudo-code. Validator
D
, however, has seen scenario1.b
. So it does not know a Polka forX
in round0
. Even though it eventually receive the second prevote fromB
, by assumption it is not considered.As a result,
C
will prevote forid(X)
, whileD
will prevote fornil
. Even ifA
prevotes forid(X)
we are back to a scenario very similar to1
, when is the Byzantine validatorB
that will decide whether the round can succeed or not.D
is the next proposerIn this case,
D
does have any know valid value and will propose a new value, sayY
.This scenario is covered by line 22 of the pseudo-code. Validator
C
is locked on round0
and valueX != Y
. Therefore it has to prevotenil
, whileD
will prevote forid(Y)
. ValidatorA
can prevote fornil
or forid(Y)
. But even in the latter case, we have a scenario where is the Byzantine validatorB
they will decide whether the round can succeed or not.Summary
In summary, we are stuck. The only two validators that are correct and synchronous (
C
andD
) have conflicting states. One of them is locked, while the other is not. One of them will always propose a value that the other will reject.To form a prevote quorum, a Polka, we need 3 prevotes for the same value ID. Having a good vote from slow validator
A
is not enough. We need to rely on the votes of the Byzantine validatorB
, that can block the system forever.How do we solve this problem? In the pseudo-code this is not a problem. Once we get past GST, every correct validator (
A
andD
) will eventually see that there was a polka forX
at round0
. But for that, specially in the case of validatorD
, it needs to "replace" the prevote fromB
at round0
from the value it has stored,nil
, by the value that validatorC
has seen,id(X)
.Without the ability to override the existing vote set upon seeing a Polka certificate, we cannot solve this scenario.
Beta Was this translation helpful? Give feedback.
All reactions