Some checks are pending
docker-build-cometbft / vars (push) Waiting to run
docker-build-cometbft / build-images (amd64, ubuntu-24.04) (push) Blocked by required conditions
docker-build-cometbft / build-images (arm64, ubuntu-24.04-arm) (push) Blocked by required conditions
docker-build-cometbft / merge-images (push) Blocked by required conditions
docker-build-e2e-node / vars (push) Waiting to run
docker-build-e2e-node / build-images (amd64, ubuntu-24.04) (push) Blocked by required conditions
docker-build-e2e-node / build-images (arm64, ubuntu-24.04-arm) (push) Blocked by required conditions
docker-build-e2e-node / merge-images (push) Blocked by required conditions
785 lines
28 KiB
Markdown
785 lines
28 KiB
Markdown
<!-- markdown-link-check-disable -->
|
|
# ***This an unfinished draft. Comments are welcome!***
|
|
|
|
**TODO:** We will need to do small adaptations to the verification
|
|
spec to reflect the semantics in the LightStore (verified, trusted,
|
|
untrusted, etc. not needed anymore). In more detail:
|
|
|
|
- The state of the Lightstore needs to go. Functions like `LatestVerified` can
|
|
keep the name but will ignore state as it will not exist anymore.
|
|
|
|
- verification spec should be adapted to the second parameter of
|
|
`VerifyToTarget`
|
|
being a lightblock; new version number of function tag;
|
|
|
|
- We should clarify what is the expectation of VerifyToTarget
|
|
so if it returns TimeoutError it can be assumed faulty. I guess that
|
|
VerifyToTarget with correct full node should never terminate with
|
|
TimeoutError.
|
|
|
|
- We need to introduce a new version number for the new
|
|
specification. So we should decide how
|
|
to handle that.
|
|
|
|
# Light Client Attack Detector
|
|
|
|
In this specification, we strengthen the light client to be resistant
|
|
against so-called light client attacks. In a light client attack, all
|
|
the correct Cosmos full nodes agree on the sequence of generated
|
|
blocks (no fork), but a set of faulty full nodes attack a light client
|
|
by generating (signing) a block that deviates from the block of the
|
|
same height on the blockchain. In order to do so, some of these faulty
|
|
full nodes must have been validators before and violate
|
|
[[CMBC-FM-2THIRDS]](CMBC-FM-2THIRDS-link), as otherwise, if
|
|
[[CMBC-FM-2THIRDS]](CMBC-FM-2THIRDS-link) would hold,
|
|
[verification](verification) would satisfy
|
|
[[LCV-SEQ-SAFE.1]](LCV-SEQ-SAFE-link).
|
|
|
|
An attack detector (or detector for short) is a mechanism that is used
|
|
by the light client [supervisor](supervisor) after
|
|
[verification](verification) of a new light block
|
|
with the primary, to cross-check the newly learned light block with
|
|
other peers (secondaries). It expects as input a light block with some
|
|
height *root* (that serves as a root of trust), and a verification
|
|
trace (a sequence of lightblocks) that the primary provided.
|
|
|
|
In case the detector observes a light client attack, it computes
|
|
evidence data that can be used by Cosmos full nodes to isolate a
|
|
set of faulty full nodes that are still within the unbonding period
|
|
(more than 1/3 of the voting power of the validator set at some block of the chain),
|
|
and report them via ABCI to the application of a Cosmos blockchain
|
|
in order to punish faulty nodes.
|
|
|
|
## Context of this document
|
|
|
|
The light client [verification](verification) specification is
|
|
designed for the Cosmos failure model (1/3 assumption)
|
|
[[CMBC-FM-2THIRDS]](CMBC-FM-2THIRDS-link). It is safe under this
|
|
assumption, and live if it can reliably (that is, no message loss, no
|
|
duplication, and eventually delivered) and timely communicate with a
|
|
correct full node. If [[CMBC-FM-2THIRDS]](CMBC-FM-2THIRDS-link) assumption is violated, the light client
|
|
can be fooled to trust a light block that was not generated by
|
|
Tendermint consensus.
|
|
|
|
This specification, the attack detector, is a "second line of
|
|
defense", in case the 1/3 assumption is violated. Its goal is to
|
|
detect a light client attack (conflicting light blocks) and collect
|
|
evidence. However, it is impractical to probe all full nodes. At this
|
|
time we consider a simple scheme of maintaining an address book of
|
|
known full nodes from which a small subset (e.g., 4) are chosen
|
|
initially to communicate with. More involved book keeping with
|
|
probabilistic guarantees can be considered at later stages of the
|
|
project.
|
|
|
|
The light client maintains a simple address book containing addresses
|
|
of full nodes that it can pick as primary and secondaries. To obtain
|
|
a new light block, the light client first does
|
|
[verification](verification) with the primary, and then cross-checks
|
|
the light block (and the trace of light blocks that led to it) with
|
|
the secondaries using this specification.
|
|
|
|
## Tendermint Consensus and Light Client Attacks
|
|
|
|
In this section we will give some mathematical definitions of what we
|
|
mean by light client attacks (that are considered in this
|
|
specification) and how they differ from main-chain forks. To this end
|
|
we start by defining some properties of the sequence of blocks that is
|
|
decided upon by Tendermint consensus in normal operation (if the
|
|
Cosmos failure model holds
|
|
[[CMBC-FM-2THIRDS]](CMBC-FM-2THIRDS-link)),
|
|
and then define different
|
|
deviations that correspond to attack scenarios.
|
|
|
|
#### **[CMBC-GENESIS.1]**
|
|
|
|
Let *Genesis* be the agreed-upon initial block (file).
|
|
|
|
#### **[CMBC-FUNC-SIGN.1]**
|
|
|
|
Let *b* and *c* be two light blocks with *b.Header.Height + 1 =
|
|
c.Header.Height*. We define the predicate **signs(b,c)** to hold
|
|
iff *c.Header.LastCommit* is in *PossibleCommit(b)*.
|
|
[[CMBC-SOUND-DISTR-POSS-COMMIT.1]](CMBC-SOUND-DISTR-POSS-COMMIT-link).
|
|
|
|
> The above encodes sequential verification, that is, intuitively,
|
|
> b.Header.NextValidators = c.Header.Validators and 2/3 of
|
|
> these Validators signed c?
|
|
|
|
#### **[CMBC-FUNC-SUPPORT.1]**
|
|
|
|
Let *b* and *c* be two light blocks. We define the predicate
|
|
**supports(b,c,t)** to hold iff
|
|
|
|
- *t - trustingPeriod < b.Header.Time < t*
|
|
- the voting power in *b.NextValidators* of nodes in *c.Commit*
|
|
is more than 1/3 of *TotalVotingPower(b.Header.NextValidators)*
|
|
|
|
> That is, if the [Cosmos failure model](CMBC-FM-2THIRDS-link)
|
|
> holds, then *c* has been signed by at least one correct full node, cf.
|
|
> [[CMBC-VAL-CONTAINS-CORR.1]](CMBC-VAL-CONTAINS-CORR-link).
|
|
> The following formalizes that *b* was properly generated by
|
|
> Tendermint; *b* can be traced back to genesis
|
|
|
|
#### **[CMBC-SEQ-ROOTED.1]**
|
|
|
|
Let *b* be a light block.
|
|
We define *sequ-rooted(b)* iff for all *i*, *1 <= i < h = b.Header.Height*,
|
|
there exist light blocks *a(i)* s.t.
|
|
|
|
- *a(1) = Genesis* and
|
|
- *a(h) = b* and
|
|
- *signs( a(i) , a(i+1) )*.
|
|
|
|
> The following formalizes that *c* is trusted based on *b* in
|
|
> skipping verification. Observe that we do not require here (yet)
|
|
> that *b* was properly generated.
|
|
|
|
#### **[CMBC-SKIP-TRACE.1]**
|
|
|
|
Let *b* and *c* be light blocks. We define *skip-trace(b,c,t)* if at
|
|
time t there exists an *h* and a sequence *a(1)*, ... *a(h)* s.t.
|
|
|
|
- *a(1) = b* and
|
|
- *a(h) = c* and
|
|
- *supports( a(i), a(i+1), t)*, for all i, *1 <= i < h*.
|
|
|
|
We call such a sequence *a(1)*, ... *a(h)* a **verification trace**.
|
|
|
|
> The following formalizes that two light blocks of the same height
|
|
> should agree on the content of the header. Observe that *b* and *c*
|
|
> may disagree on the Commit. This is a special case if the canonical
|
|
> commit has not been decided on, that is, if b.Header.Height is the
|
|
> maximum height of all blocks decided upon by Tendermint at this
|
|
> moment.
|
|
|
|
#### **[CMBC-SIGN-SKIP-MATCH.1]**
|
|
|
|
Let *a*, *b*, *c*, be light blocks and *t* a time, we define
|
|
*sign-skip-match(a,b,c,t) = true* iff the following implication
|
|
evaluates to true:
|
|
|
|
- *sequ-rooted(a)* and
|
|
- *b.Header.Height = c.Header.Height* and
|
|
- *skip-trace(a,b,t)*
|
|
- *skip-trace(a,c,t)*
|
|
|
|
implies *b.Header = c.Header*.
|
|
|
|
> Observe that *sign-skip-match* is defined via an implication. If it
|
|
> evaluates to false this means that the left-hand-side of the
|
|
> implication evaluates to true, and the right-hand-side evaluates to
|
|
> false. In particular, there are two **different** headers *b* and
|
|
> *c* that both can be verified from a common block *a* from the
|
|
> chain. Thus, the following describes an attack.
|
|
|
|
#### **[CMBC-ATTACK.1]**
|
|
|
|
If there exists three light blocks a, b, and c, with
|
|
*sign-skip-match(a,b,c,t) = false* then we have an *attack*. We say
|
|
we have **an attack at height** *b.Header.Height* and write
|
|
*attack(a,b,c,t)*.
|
|
|
|
> The lightblock *a* need not be unique, that is, there may be
|
|
> several blocks that satisfy the above requirement for the same
|
|
> blocks *b* and *c*.
|
|
|
|
[[CMBC-ATTACK.1]](#CMBC-ATTACK1) is a formalization of the violation
|
|
of the agreement property based on the result of consensus, that is,
|
|
the generated blocks.
|
|
|
|
**Remark.**
|
|
Violation of agreement is only possible if more than 1/3 of the validators (or
|
|
next validators) of some previous block deviated from the protocol. The
|
|
upcoming "accountability" specification will describe how to compute
|
|
a set of at least 1/3 faulty nodes from two conflicting blocks. []
|
|
|
|
There are different ways to characterize forks
|
|
and attack scenarios. This specification uses the "node-based
|
|
characterization of attacks" which focuses on what kinds of nodes are
|
|
affected (light nodes vs. full nodes). For future reference and
|
|
discussion we also provide a
|
|
"block-based characterization of attacks" below.
|
|
|
|
### Node-based characterization of attacks
|
|
|
|
#### **[CMBC-MC-FORK.1]**
|
|
|
|
We say there is a (main chain) fork at time *t* if
|
|
|
|
- there are two correct full nodes *i* and *j* and
|
|
- *i* is different from *j* and
|
|
- *i* has decided on *b* and
|
|
- *j* has decided on *c* and
|
|
- there exist *a* such that *attack(a,b,c,t)*.
|
|
|
|
#### **[CMBC-LC-ATTACK.1]**
|
|
|
|
We say there is a light client attack at time *t*, if
|
|
|
|
- there is **no** (main chain) fork [[CMBC-MC-FORK.1]](#CMBC-MC-FORK1), and
|
|
- there exist nodes that have computed light blocks *b* and *c* and
|
|
- there exist *a* such that *attack(a,b,c,t)*.
|
|
|
|
We say the attack is at height *a.Header.Height*.
|
|
|
|
> In this specification we consider detection of light client
|
|
> attacks. Intuitively, the case we consider is that
|
|
> light block *b* is the one from the
|
|
> blockchain, and some attacker has computed *c* and tries to wrongly
|
|
> convince
|
|
> the light client that *c* is the block from the chain.
|
|
|
|
#### **[CMBC-LC-ATTACK-EVIDENCE.1]**
|
|
|
|
We consider the following case of a light client attack
|
|
[[CMBC-LC-ATTACK.1]](#CMBC-LC-ATTACK1):
|
|
|
|
- *attack(a,b,c,t)*
|
|
- there is a peer p1 that has a sequence *chain* of blocks from *a* to *b*
|
|
- *skip-trace(a,c,t)*: by [[CMBC-SKIP-TRACE.1]](#CMBC-SKIP-TRACE1) there is a
|
|
verification trace *v* of the form *a = v(1)*, ... *v(h) = c*
|
|
|
|
Evidence for p1 (that proves an attack) consists for index i
|
|
of v(i) and v(i+1) such that
|
|
|
|
- E1(i). v(i) is equal to the block of *chain* at height v(i).Height, and
|
|
- E2(i). v(i+1) that is different from the block of *chain* at
|
|
height v(i+1).height
|
|
|
|
> Observe p1 can
|
|
>
|
|
> - check that v(i+1) differs from its block at that height, and
|
|
> - verify v(i+1) in one step from v(i) as v is a verification trace.
|
|
|
|
**Proposition.** In the case of attack, evidence exists.
|
|
*Proof.* First observe that
|
|
|
|
- (A). (NOT E2(i)) implies E1(i+1)
|
|
|
|
Now by contradiction assume there is no evidence. Thus
|
|
|
|
- for all i, we have NOT E1(i) or NOT E2(i)
|
|
- for i = 1 we have E1(1) and thus NOT E2(1)
|
|
thus by induction on i, by (A) we have for all i that **E1(i)**
|
|
- from attack we have E2(h-1), and as there is no evidence for
|
|
i = h - 1 we get **NOT E1(h-1)**. Contradiction.
|
|
QED.
|
|
|
|
#### **[CMBC-LC-EVIDENCE-DATA.1]**
|
|
|
|
To prove the attack to p1, because of Point E1, it is sufficient to
|
|
submit
|
|
|
|
- v(i).Height (rather than v(i)).
|
|
- v(i+1)
|
|
|
|
This information is *evidence for height v(i).Height*.
|
|
|
|
### Block-based characterization of attacks
|
|
|
|
In this section we provide a different characterization of attacks. It
|
|
is not defined on the nodes that are affected but purely on the
|
|
content of the blocks. In that sense these definitions are less
|
|
operational.
|
|
|
|
> They might be relevant for a closer analysis of fork scenarios on the
|
|
> chain, which is out of the scope of this specification.
|
|
|
|
#### **[CMBC-SIGN-UNIQUE.1]**
|
|
|
|
Let *b* and *c* be light blocks, we define the predicate
|
|
*sign-unique(b,c)* to evaluate to true iff the following implication
|
|
evaluates to true:
|
|
|
|
- *b.Header.Height = c.Header.Height* and
|
|
- *sequ-rooted(b)* and
|
|
- *sequ-rooted(c)*
|
|
|
|
implies *b = c*.
|
|
|
|
#### **[CMBC-BLOCKS-MCFORK.1]**
|
|
|
|
If there exists two light blocks b and c, with *sign-unique(b,c) =
|
|
false* then we have a *fork*.
|
|
|
|
> The difference of the above definition to
|
|
> [[CMBC-MC-FORK.1]](#CMBC-MC-FORK1) is subtle. The latter requires a
|
|
> full node being affected by a bad block while
|
|
> [[CMBC-BLOCKS-MCFORK.1]](#CMBC-BLOCKS-MCFORK1) just requires that a
|
|
> bad block exists, possibly in memory of an attacker.
|
|
> The following captures a light client fork. There is no fork up to
|
|
> the height of block b. However, c is of that height, is different,
|
|
> and passes skipping verification. It is a stricter property than
|
|
> [[CMBC-LC-ATTACK.1]](#CMBC-LC-ATTACK1), as
|
|
> [[CMBC-LC-ATTACK.1]](#CMBC-LC-ATTACK1) requires that no correct full
|
|
> node is affected.
|
|
|
|
#### **[CMBC-BLOCKS-LCFORK.1]**
|
|
|
|
Let *a*, *b*, *c*, be light blocks and *t* a time. We define
|
|
*light-client-fork(a,b,c,t)* iff
|
|
|
|
- *sign-skip-match(a,b,c,t) = false* and
|
|
- *sequ-rooted(b)* and
|
|
- *b* is "unique", that is, for all *d*, *sequ-rooted(d)* and
|
|
*d.Header.Height = b.Header.Height* implies *d = b*
|
|
|
|
> Finally, let us also define bogus blocks that have no support.
|
|
> Observe that bogus is even defined if there is a fork.
|
|
> Also, for the definition it would be sufficient to restrict *a* to
|
|
> *a.height < b.height* (which is implied by the definitions which
|
|
> unfold until *supports()*).
|
|
|
|
#### **[CMBC-BOGUS.1]**
|
|
|
|
Let *b* be a light block and *t* a time. We define *bogus(b,t)* iff
|
|
|
|
- *sequ-rooted(b) = false* and
|
|
- for all *a*, *sequ-rooted(a)* implies *skip-trace(a,b,t) = false*
|
|
|
|
### Informal Problem statement
|
|
|
|
There is no sequential specification: the detector only makes sense
|
|
in a distributed systems where some nodes misbehave.
|
|
|
|
We work under the assumption that full nodes and validators are
|
|
responsible for detecting attacks on the main chain, and the evidence
|
|
reactor takes care of broadcasting evidence to communicate
|
|
misbehaving nodes via ABCI to the application, and halt the chain in
|
|
case of a fork. The point of this specification is to shield a light
|
|
clients against attacks that cannot be detected by full nodes, and
|
|
are fully addressed at light clients (and consequently IBC relayers,
|
|
which use the light client protocols to observe the state of a
|
|
blockchain). In order to provide full nodes the incentive to follow
|
|
the protocols when communicating with the light client, this
|
|
specification also considers the generation of evidence that will
|
|
also be processed by the Cosmos blockchain.
|
|
|
|
#### **[LCD-IP-MODEL.1]**
|
|
|
|
The detector is designed under the assumption that
|
|
|
|
- [[CMBC-FM-2THIRDS]](CMBC-FM-2THIRDS-link) may be violated
|
|
- there is no fork on the main chain.
|
|
|
|
> As a result some faulty full nodes may launch an attack on a light
|
|
> client.
|
|
|
|
The following requirements are operational in that they describe how
|
|
things should be done, rather than what should be done. However, they
|
|
do not constitute temporal logic verification conditions. For those,
|
|
see [LCD-DIST-*] below.
|
|
|
|
The detector is called in the [supervisor](supervisor) as follows
|
|
|
|
```go
|
|
Evidences := AttackDetector(root_of_trust, verifiedLS);`
|
|
```
|
|
|
|
where
|
|
|
|
- `root-of-trust` is a light block that is trusted (that is,
|
|
except upon initialization, the primary and the secondaries
|
|
agreed on in the past), and
|
|
- `verifiedLS` is a lightstore that contains a verification trace that
|
|
starts from a lightblock that can be verified with the
|
|
`root-of-trust` in one step and ends with a lightblock of the height
|
|
requested by the user
|
|
- `Evidences` is a list of evidences for misbehavior
|
|
|
|
#### **[LCD-IP-STATEMENT.1]**
|
|
|
|
Whenever AttackDetector is called, the detector should for each
|
|
secondary try to replay the verification trace `verifiedLS` with the
|
|
secondary
|
|
|
|
- in case replaying leads to detection of a light client attack
|
|
(one of the lightblocks differ from the one in verifiedLS with
|
|
the same height), we should return evidence
|
|
- if the secondary cannot provide a verification trace, we have no
|
|
proof for an attack. Block *b* may be bogus. In this case the
|
|
secondary is faulty and it should be replaced.
|
|
|
|
## Assumptions/Incentives/Environment
|
|
|
|
It is not in the interest of faulty full nodes to talk to the
|
|
detector as long as the detector is connected to at least one
|
|
correct full node. This would only increase the likelihood of
|
|
misbehavior being detected. Also we cannot punish them easily
|
|
(cheaply). The absence of a response need not be the fault of the full
|
|
node.
|
|
|
|
Correct full nodes have the incentive to respond, because the
|
|
detector may help them to understand whether their header is a good
|
|
one. We can thus base liveness arguments of the detector on
|
|
the assumptions that correct full nodes reliably talk to the
|
|
detector.
|
|
|
|
### Assumptions
|
|
|
|
#### **[LCD-A-CorrFull.1]**
|
|
|
|
At all times there is at least one correct full
|
|
node among the primary and the secondaries.
|
|
|
|
> For this version of the detection we take this assumption. It
|
|
> allows us to establish the invariant that the lightblock
|
|
> `root-of-trust` is always the one from the blockchain, and we can
|
|
> use it as starting point for the evidence computation. Moreover, it
|
|
> allows us to establish the invariant at the supervisor that any
|
|
> lightblock in the (top-level) lightstore is from the blockchain.
|
|
> In the future we might design a lightclient based on the assumption
|
|
> that at least in regular intervals the lightclient is connected to a
|
|
> correct full node. This will require the detector to reconsider
|
|
> `root-of-trust`, and remove lightblocks from the top-level
|
|
> lightstore.
|
|
|
|
#### **[LCD-A-RelComm.1]**
|
|
|
|
Communication between the detector and a correct full node is
|
|
reliable and bounded in time. Reliable communication means that
|
|
messages are not lost, not duplicated, and eventually delivered. There
|
|
is a (known) end-to-end delay *Delta*, such that if a message is sent
|
|
at time *t* then it is received and processed by time *t + Delta*.
|
|
This implies that we need a timeout of at least *2 Delta* for remote
|
|
procedure calls to ensure that the response of a correct peer arrives
|
|
before the timeout expires.
|
|
|
|
## Definitions
|
|
|
|
### Evidence
|
|
|
|
Following the definition of
|
|
[[CMBC-LC-ATTACK-EVIDENCE.1]](#CMBC-LC-ATTACK-EVIDENCE1), by evidence
|
|
we refer to a variable of the following type
|
|
|
|
#### **[LC-DATA-EVIDENCE.1]**
|
|
|
|
```go
|
|
type LightClientAttackEvidence struct {
|
|
ConflictingBlock LightBlock
|
|
CommonHeight int64
|
|
}
|
|
```
|
|
|
|
As the above data is computed for a specific peer, the following
|
|
data structure wraps the evidence and adds the peerID.
|
|
|
|
#### **[LC-DATA-EVIDENCE-INT.1]**
|
|
|
|
```go
|
|
type InternalEvidence struct {
|
|
Evidence LightClientAttackEvidence
|
|
Peer PeerID
|
|
}
|
|
```
|
|
|
|
#### **[LC-SUMBIT-EVIDENCE.1]**
|
|
|
|
```go
|
|
func submitEvidence(Evidences []InternalEvidence)
|
|
```
|
|
|
|
- Expected postcondition
|
|
- for each `ev` in `Evidences`: submit `ev.Evidence` to `ev.Peer`
|
|
|
|
---
|
|
|
|
### LightStore
|
|
|
|
Lightblocks and LightStores are defined in the verification
|
|
specification [LCV-DATA-LIGHTBLOCK.1] and [LCV-DATA-LIGHTSTORE.1]. See
|
|
the [verification specification][verification] for details.
|
|
|
|
## (Distributed) Problem statement
|
|
|
|
> As the attack detector is there to reduce the impact of faulty
|
|
> nodes, and faulty nodes imply that there is a distributed system,
|
|
> there is no sequential specification to which this distributed
|
|
> problem statement may refer to.
|
|
|
|
The detector gets as input a trusted lightblock called *root* and an
|
|
auxiliary lightstore called *primary_trace* with lightblocks that have
|
|
been verified before, and that were provided by the primary.
|
|
|
|
#### **[LCD-DIST-INV-ATTACK.1]**
|
|
|
|
If the detector returns evidence for height *h*
|
|
[[CMBC-LC-EVIDENCE-DATA.1]](#CMBC-LC-EVIDENCE-DATA1), then there is an
|
|
attack at height *h*. [[CMBC-LC-ATTACK.1]](#CMBC-LC-ATTACK1)
|
|
|
|
#### **[LCD-DIST-INV-STORE.1]**
|
|
|
|
If the detector does not return evidence, then *primary_trace*
|
|
contains only blocks from the blockchain.
|
|
|
|
#### **[LCD-DIST-LIVE.1]**
|
|
|
|
The detector eventually terminates.
|
|
|
|
#### **[LCD-DIST-TERM-NORMAL.1]**
|
|
|
|
If
|
|
|
|
- the *primary_trace* contains only blocks from the blockchain, and
|
|
- there is no attack, and
|
|
- *Secondaries* is always non-empty, and
|
|
- the age of *root* is always less than the trusting period,
|
|
|
|
then the detector does not return evidence.
|
|
|
|
#### **[LCD-DIST-TERM-ATTACK.1]**
|
|
|
|
If
|
|
|
|
- there is an attack, and
|
|
- a secondary reports a block that conflicts
|
|
with one of the blocks in *primary_trace*, and
|
|
- *Secondaries* is always non-empty, and
|
|
- the age of *root* is always less than the trusting period,
|
|
|
|
then the detector returns evidence.
|
|
|
|
> Observe that above we require that "a secondary reports a block that
|
|
> conflicts". If there is an attack, but no secondary tries to launch
|
|
> it against the detector (or the message from the secondary is lost
|
|
> by the network), then there is nothing to detect for us.
|
|
|
|
#### **[LCD-DIST-SAFE-SECONDARY.1]**
|
|
|
|
No correct secondary is ever replaced.
|
|
|
|
#### **[LCD-DIST-SAFE-BOGUS.1]**
|
|
|
|
If
|
|
|
|
- a secondary reports a bogus lightblock,
|
|
- the age of *root* is always less than the trusting period,
|
|
|
|
then the secondary is replaced before the detector terminates.
|
|
|
|
> The above property is quite operational ("reports"), but it captures
|
|
> quite closely the requirement. As the
|
|
> detector only makes sense in a distributed setting, and does
|
|
> not have a sequential specification, less "pure"
|
|
> specification are acceptable.
|
|
|
|
# Protocol
|
|
|
|
## Functions and Data defined in other Specifications
|
|
|
|
### From the supervisor
|
|
|
|
```go
|
|
Replace_Secondary(addr Address, root-of-trust LightBlock)
|
|
```
|
|
|
|
### From the verifier
|
|
|
|
```go
|
|
func VerifyToTarget(primary PeerID, root LightBlock,
|
|
targetHeight Height) (LightStore, Result)
|
|
```
|
|
|
|
> Note: the above differs from the current version in the second
|
|
> parameter. verification will be revised.
|
|
|
|
Observe that `VerifyToTarget` does communication with the secondaries
|
|
via the function [FetchLightBlock](fetch).
|
|
|
|
### Shared data of the light client
|
|
|
|
- a pool of full nodes *FullNodes* that have not been contacted before
|
|
- peer set called *Secondaries*
|
|
- primary
|
|
|
|
> Note that the lightStore is not needed to be shared.
|
|
|
|
## Outline
|
|
|
|
The problem laid out is solved by calling the function `AttackDetector`
|
|
with a lightstore that contains a light block that has just been
|
|
verified by the verifier.
|
|
|
|
Then `AttackDetector` downloads headers from the secondaries. In case
|
|
a conflicting header is downloaded from a secondary,
|
|
`CreateEvidenceForPeer` which computes evidence in the case that
|
|
indeed an attack is confirmed. It could be that the secondary reports
|
|
a bogus block, which means that there need not be an attack, and the
|
|
secondary is replaced.
|
|
|
|
## Details of the functions
|
|
|
|
#### **[LCD-FUNC-DETECTOR.1]:**
|
|
|
|
```go
|
|
func AttackDetector(root LightBlock, primary_trace []LightBlock)
|
|
([]InternalEvidence) {
|
|
|
|
Evidences := new []InternalEvidence;
|
|
|
|
for each secondary in Secondaries {
|
|
// we replay the primary trace with the secondary, in
|
|
// order to generate evidence that we can submit to the
|
|
// secodary. We return the evidence + the trace the
|
|
// secondary told us that spans the evidence at its local store
|
|
|
|
EvidenceForSecondary, newroot, secondary_trace, result :=
|
|
CreateEvidenceForPeer(secondary,
|
|
root,
|
|
primary_trace);
|
|
if result == FaultyPeer {
|
|
Replace_Secondary(root);
|
|
}
|
|
else if result == FoundEvidence {
|
|
// the conflict is not bogus
|
|
Evidences.Add(EvidenceForSecondary);
|
|
// we replay the secondary trace with the primary, ...
|
|
EvidenceForPrimary, _, result :=
|
|
CreateEvidenceForPeer(primary,
|
|
newroot,
|
|
secondary_trace);
|
|
if result == FoundEvidence {
|
|
Evidences.Add(EvidenceForPrimary);
|
|
}
|
|
// At this point we do not care about the other error
|
|
// codes. We already have generated evidence for an
|
|
// attack and need to stop the lightclient. It does not
|
|
// help to call replace_primary. Also we will use the
|
|
// same primary to check with other secondaries in
|
|
// later iterations of the loop
|
|
}
|
|
// In the case where the secondary reports NoEvidence
|
|
// we do nothing
|
|
}
|
|
return Evidences;
|
|
}
|
|
```
|
|
|
|
- Expected precondition
|
|
- root and primary trace are a verification trace
|
|
- Expected postcondition
|
|
- solves the problem statement (if attack found, then evidence is reported)
|
|
- Error condition
|
|
- `ErrorTrustExpired`: fails if root expires (outside trusting
|
|
period) [[LCV-INV-TP.1]](LCV-INV-TP1-link)
|
|
- `ErrorNoPeers`: if no peers are left to replace secondaries, and
|
|
no evidence was found before that happened
|
|
|
|
---
|
|
|
|
```go
|
|
func CreateEvidenceForPeer(peer PeerID, root LightBlock, trace LightStore)
|
|
(Evidence, LightBlock, LightStore, result) {
|
|
|
|
common := root;
|
|
|
|
for i in 1 .. len(trace) {
|
|
auxLS, result := VerifyToTarget(peer, common, trace[i].Header.Height)
|
|
|
|
if result != ResultSuccess {
|
|
// something went wrong; peer did not provide a verifyable block
|
|
return (nil, nil, nil, FaultyPeer)
|
|
}
|
|
else {
|
|
if auxLS.LatestVerified().Header != trace[i].Header {
|
|
// the header reported by the peer differs from the
|
|
// reference header in trace but both could be
|
|
// verified from common in one step.
|
|
// we can create evidence for submission to the secondary
|
|
ev := new InternalEvidence;
|
|
ev.Evidence.ConflictingBlock := trace[i];
|
|
ev.Evidence.CommonHeight := common.Height;
|
|
ev.Peer := peer
|
|
return (ev, common, auxLS, FoundEvidence)
|
|
}
|
|
else {
|
|
// the peer agrees with the trace, we move common forward
|
|
// we could delete auxLS as it will be overwritten in
|
|
// the next iteration
|
|
common := trace[i]
|
|
}
|
|
}
|
|
}
|
|
return (nil, nil, nil, NoEvidence)
|
|
}
|
|
```
|
|
|
|
- Expected precondition
|
|
- root and trace are a verification trace
|
|
- Expected postcondition
|
|
- finds evidence where trace and peer diverge
|
|
- Error condition
|
|
- `ErrorTrustExpired`: fails if root expires (outside trusting
|
|
period) [[LCV-INV-TP.1]](LCV-INV-TP1-link)
|
|
- If `VerifyToTarget` returns error but root is not expired then return
|
|
`FaultyPeer`
|
|
|
|
---
|
|
|
|
## Correctness arguments
|
|
|
|
#### Argument for [[LCD-DIST-INV-ATTACK.1]](#LCD-DIST-INV-ATTACK1)
|
|
|
|
Under the assumption that root and trace are a verification trace,
|
|
when in `CreateEvidenceForPeer` the detector the detector creates
|
|
evidence, then the lightclient has seen two different headers (one via
|
|
`trace` and one via `VerifyToTarget` for the same height that can both
|
|
be verified in one step.
|
|
|
|
#### Argument for [[LCD-DIST-INV-STORE.1]](#LCD-DIST-INV-STORE1)
|
|
|
|
We assume that there is at least one correct peer, and there is no
|
|
fork. As a result the correct peer has the correct sequence of
|
|
blocks. Since the primary_trace is checked block-by-block also against
|
|
each secondary, and at no point evidence was generated that means at
|
|
no point there were conflicting blocks.
|
|
|
|
#### Argument for [[LCD-DIST-LIVE.1]](#LCD-DIST-LIVE1)
|
|
|
|
At the latest when [[LCV-INV-TP.1]](LCV-INV-TP1-link) is violated,
|
|
`AttackDetector` terminates.
|
|
|
|
#### Argument for [[LCD-DIST-TERM-NORMAL.1]](#LCD-DIST-TERM-NORMAL1)
|
|
|
|
As there are finitely many peers, eventually the main loop
|
|
terminates. As there is no attack no evidence can be generated.
|
|
|
|
#### Argument for [[LCD-DIST-TERM-ATTACK.1]](#LCD-DIST-TERM-ATTACK1)
|
|
|
|
Argument similar to [[LCD-DIST-TERM-NORMAL.1]](#LCD-DIST-TERM-NORMAL1)
|
|
|
|
#### Argument for [[LCD-DIST-SAFE-SECONDARY.1]](#LCD-DIST-SAFE-SECONDARY1)
|
|
|
|
Secondaries are only replaced if they time-out or if they report bogus
|
|
blocks. The former is ruled out by the timing assumption, the latter
|
|
by correct peers only reporting blocks from the chain.
|
|
|
|
#### Argument for [[LCD-DIST-SAFE-BOGUS.1]](#LCD-DIST-SAFE-BOGUS1)
|
|
|
|
Once a bogus block is recognized as such the secondary is removed.
|
|
|
|
# References
|
|
|
|
> links to other specifications/ADRs this document refers to
|
|
|
|
[[verification]] The specification of the light client verification.
|
|
|
|
[[supervisor]] The specification of the light client supervisor.
|
|
|
|
[verification]: https://github.com/cometbft/cometbft/tree/v0.38.x/spec/light-client/verification
|
|
|
|
[supervisor]: https://github.com/cometbft/cometbft/tree/v0.38.x/spec/light-client/supervisor
|
|
|
|
|
|
|
|
|
|
|
|
[CMBC-VAL-CONTAINS-CORR-link]:
|
|
https://github.com/cometbft/cometbft/blob/v0.38.x/spec/light-client/verification/verification_002_draft.md#cmbc-val-contains-corr1
|
|
|
|
[fetch]:
|
|
https://github.com/cometbft/cometbft/blob/v0.38.x/spec/light-client/verification/verification_002_draft.md#lcv-func-fetch1
|
|
|
|
[LCV-INV-TP1-link]:
|
|
https://github.com/cometbft/cometbft/blob/v0.38.x/spec/light-client/verification/verification_002_draft.md#lcv-inv-tp1
|