|
|
@ -0,0 +1,648 @@ |
|
|
|
# Draft of Light Client Supervisor for discussion |
|
|
|
|
|
|
|
## TODOs |
|
|
|
|
|
|
|
This specification in done in parallel with updates on the |
|
|
|
verification specification. So some hyperlinks have to be placed to |
|
|
|
the correct files eventually. |
|
|
|
|
|
|
|
|
|
|
|
# Light Client Sequential Supervisor |
|
|
|
|
|
|
|
The light client implements a read operation of a |
|
|
|
[header](TMBC-HEADER-link) from the [blockchain](TMBC-SEQ-link), by |
|
|
|
communicating with full nodes, a so-called primary and several |
|
|
|
so-called witnesses. As some full nodes may be faulty, this |
|
|
|
functionality must be implemented in a fault-tolerant way. |
|
|
|
|
|
|
|
In the Tendermint blockchain, the validator set may change with every |
|
|
|
new block. The staking and unbonding mechanism induces a [security |
|
|
|
model](TMBC-FM-2THIRDS-link): starting at time *Time* of the |
|
|
|
[header](TMBC-HEADER-link), |
|
|
|
more than two-thirds of the next validators of a new block are correct |
|
|
|
for the duration of *TrustedPeriod*. |
|
|
|
|
|
|
|
[Light Client Verification](https://informal.systems) implements the fault-tolerant read |
|
|
|
operation designed for this security model. That is, it is safe if the |
|
|
|
model assumptions are satisfied and makes progress if it communicates |
|
|
|
to a correct primary. |
|
|
|
|
|
|
|
However, if the [security model](TMBC-FM-2THIRDS-link) is violated, |
|
|
|
faulty peers (that have been validators at some point in the past) may |
|
|
|
launch attacks on the Tendermint network, and on the light |
|
|
|
client. These attacks as well as an axiomatization of blocks in |
|
|
|
general are defined in [a document that contains the definitions that |
|
|
|
are currently in detection.md](https://informal.systems). |
|
|
|
|
|
|
|
If there is a light client attack (but no |
|
|
|
successful attack on the network), the safety of the verification step |
|
|
|
may be violated (as we operate outside its basic assumption). |
|
|
|
The light client also |
|
|
|
contains a defense mechanism against light clients attacks, called detection. |
|
|
|
|
|
|
|
[Light Client Detection](https://informal.systems) implements a cross check of the result |
|
|
|
of the verification step. If there is a light client attack, and the |
|
|
|
light client is connected to a correct peer, the light client as a |
|
|
|
whole is safe, that is, it will not operate on invalid |
|
|
|
blocks. However, in this case it cannot successfully read, as |
|
|
|
inconsistent blocks are in the system. However, in this case the |
|
|
|
detection performs a distributed computation that results in so-called |
|
|
|
evidence. Evidence can be used to prove |
|
|
|
to a correct full node that there has been a |
|
|
|
light client attack. |
|
|
|
|
|
|
|
[Light Client Evidence Accountability](https://informal.systems) is a protocol run on a |
|
|
|
full node to check whether submitted evidence indeed proves the |
|
|
|
existence of a light client attack. Further, from the evidence and its |
|
|
|
own knowledge about the blockchain, the full node computes a set of |
|
|
|
bonded full nodes (that at some point had more than one third of the |
|
|
|
voting power) that participated in the attack that will be reported |
|
|
|
via ABCI to the application. |
|
|
|
|
|
|
|
In this document we specify |
|
|
|
|
|
|
|
- Initialization of the Light Client |
|
|
|
- The interaction of [verification](https://informal.systems) and [detection](https://informal.systems) |
|
|
|
|
|
|
|
The details of these two protocols are captured in their own |
|
|
|
documents, as is the [accountability](https://informal.systems) protocol. |
|
|
|
|
|
|
|
> Another related line is IBC attack detection and submission at the |
|
|
|
> relayer, as well as attack verification at the IBC handler. This |
|
|
|
> will call for yet another spec. |
|
|
|
|
|
|
|
# Status |
|
|
|
|
|
|
|
This document is work in progress. In order to develop the |
|
|
|
specification step-by-step, |
|
|
|
it assumes certain details of [verification](https://informal.systems) and |
|
|
|
[detection](https://informal.systems) that are not specified in the respective current |
|
|
|
versions yet. This inconsistencies will be addresses over several |
|
|
|
upcoming PRs. |
|
|
|
|
|
|
|
# Part I - Tendermint Blockchain |
|
|
|
|
|
|
|
See [verification spec](addLinksWhenDone) |
|
|
|
|
|
|
|
# Part II - Sequential Problem Definition |
|
|
|
|
|
|
|
#### **[LC-SEQ-INIT-LIVE.1]** |
|
|
|
|
|
|
|
Upon initialization, the light client gets as input a header of the |
|
|
|
blockchain, or the genesis file of the blockchain, and eventually |
|
|
|
stores a header of the blockchain. |
|
|
|
|
|
|
|
|
|
|
|
#### **[LC-SEQ-LIVE.1]** |
|
|
|
|
|
|
|
The light client gets a sequence of heights as inputs. For each input |
|
|
|
height *targetHeight*, it eventually stores the header of height |
|
|
|
*targetHeight*. |
|
|
|
|
|
|
|
#### **[LC-SEQ-SAFE.1]** |
|
|
|
|
|
|
|
The light client never stores a header which is not in the blockchain. |
|
|
|
|
|
|
|
# Part III - Light Client as Distributed System |
|
|
|
|
|
|
|
## Computational Model |
|
|
|
|
|
|
|
The light client communicates with remote processes only via the |
|
|
|
[verification](TODO) and the [detection](TODO) protocols. The |
|
|
|
respective assumptions are given there. |
|
|
|
|
|
|
|
## Distributed Problem Statement |
|
|
|
|
|
|
|
### Two Kinds of Liveness |
|
|
|
|
|
|
|
In case of light client attacks, the sequential problem statement |
|
|
|
cannot always be satisfied. The lightclient cannot decide which block |
|
|
|
is from the chain and which is not. As a result, the light client just |
|
|
|
creates evidence, submits it, and terminates. |
|
|
|
For the liveness property, we thus add the |
|
|
|
possibility that instead of adding a lightblock, we also might terminate |
|
|
|
in case there is an attack. |
|
|
|
|
|
|
|
#### **[LC-DIST-TERM.1]** |
|
|
|
|
|
|
|
The light client either runs forever or it *terminates on attack*. |
|
|
|
|
|
|
|
### Design choices |
|
|
|
|
|
|
|
#### [LC-DIST-STORE.1] |
|
|
|
|
|
|
|
The light client has a local data structure called LightStore |
|
|
|
that contains light blocks (that contain a header). |
|
|
|
|
|
|
|
> The light store exposes functions to query and update it. They are |
|
|
|
> specified [here](TODO:onceVerificationIsMerged). |
|
|
|
|
|
|
|
**TODO:** reference light store invariant [LCV-INV-LS-ROOT.2] once |
|
|
|
verification is merged |
|
|
|
|
|
|
|
|
|
|
|
#### **[LC-DIST-SAFE.1]** |
|
|
|
|
|
|
|
It is always the case that every header in *LightStore* was |
|
|
|
generated by an instance of Tendermint consensus. |
|
|
|
|
|
|
|
|
|
|
|
#### **[LC-DIST-LIVE.1]** |
|
|
|
|
|
|
|
Whenever the light client gets a new height *h* as input, |
|
|
|
|
|
|
|
- and there is |
|
|
|
no light client attack up to height *h*, then the lightclient |
|
|
|
eventually puts the lightblock of height *h* in the lightstore and |
|
|
|
wait for another input. |
|
|
|
- otherwise, that is, if there |
|
|
|
is a light client attack on height *h*, then the light client |
|
|
|
must perform one of the following: |
|
|
|
- it terminates on attack. |
|
|
|
- it eventually puts the lightblock of height *h* in the lightstore and |
|
|
|
wait for another input. |
|
|
|
|
|
|
|
> Observe that the "existence of a lightclient attack" just means that some node has generated a conflicting block. It does not necessarily mean that a (faulty) peer sends such a block to "our" lightclient. Thus, even if there is an attack somewhere in the system, our lightclient might still continue to operate normally. |
|
|
|
|
|
|
|
### Solving the sequential specification |
|
|
|
|
|
|
|
[LC-DIST-SAFE.1] is guaranteed by the detector; in particular it |
|
|
|
follows from |
|
|
|
[[LCD-DIST-INV-STORE.1]](TODO) |
|
|
|
[[LCD-DIST-LIVE.1]](TODO) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Part IV - Light Client Supervisor Protocol |
|
|
|
|
|
|
|
We provide a specification for a sequential Light Client Supervisor. |
|
|
|
The local code for verification is presented by a sequential function |
|
|
|
`Sequential-Supervisor` to highlight the control flow of this |
|
|
|
functionality. Each lightblock is first verified with a primary, and then |
|
|
|
cross-checked with secondaries, and if all goes well, the lightblock |
|
|
|
is |
|
|
|
added (with the attribute "trusted") to the |
|
|
|
lightstore. Intermiate lightblocks that were used to verify the target |
|
|
|
block but were not cross-checked are stored as "verified" |
|
|
|
|
|
|
|
> We note that if a different concurrency model is considered |
|
|
|
> for an implementation, the semantics of the lightstore might change: |
|
|
|
> In a concurrent implementation, we might do verification for some |
|
|
|
> height *h*, add the |
|
|
|
> lightblock to the lightstore, and start concurrent threads that |
|
|
|
> |
|
|
|
> - do verification for the next height *h' != h* |
|
|
|
> - do cross-checking for height *h*. If we find an attack, we remove |
|
|
|
> *h* from the lightstore. |
|
|
|
> - the user might already start to use *h* |
|
|
|
> |
|
|
|
> Thus, this concurrency model changes the semantics of the |
|
|
|
> lightstore (not all lightblocks that are read by the user are |
|
|
|
> trusted; they may be removed if |
|
|
|
> we find a problem). Whether this is desirable, and whether the gain in |
|
|
|
> performance is worth it, we keep for future versions/discussion of |
|
|
|
> lightclient protocols. |
|
|
|
|
|
|
|
|
|
|
|
## Definitions |
|
|
|
|
|
|
|
### Peers |
|
|
|
|
|
|
|
#### **[LC-DATA-PEERS.1]:** |
|
|
|
|
|
|
|
A fixed set of full nodes is provided in the configuration upon |
|
|
|
initialization. Initially this set is partitioned into |
|
|
|
|
|
|
|
- one full node that is the *primary* (singleton set), |
|
|
|
- a set *Secondaries* (of fixed size, e.g., 3), |
|
|
|
- a set *FullNodes*. |
|
|
|
- A set *FaultyNodes* of nodes that the light client suspects of |
|
|
|
being faulty; it is initially empty |
|
|
|
|
|
|
|
#### **[LC-INV-NODES.1]:** |
|
|
|
|
|
|
|
The detector shall maintain the following invariants: |
|
|
|
|
|
|
|
- *FullNodes \intersect Secondaries = {}* |
|
|
|
- *FullNodes \intersect FaultyNodes = {}* |
|
|
|
- *Secondaries \intersect FaultyNodes = {}* |
|
|
|
|
|
|
|
and the following transition invariant |
|
|
|
|
|
|
|
- *FullNodes' \union Secondaries' \union FaultyNodes' = FullNodes |
|
|
|
\union Secondaries \union FaultyNodes* |
|
|
|
|
|
|
|
#### **[LC-FUNC-REPLACE-PRIMARY.1]:** |
|
|
|
|
|
|
|
```go |
|
|
|
Replace_Primary(root-of-trust LightBlock) |
|
|
|
``` |
|
|
|
|
|
|
|
- Implementation remark |
|
|
|
- the primary is replaced by a secondary |
|
|
|
- to maintain a constant size of secondaries, need to |
|
|
|
- pick a new secondary *nsec* while ensuring [LC-INV-ROOT-AGREED.1] |
|
|
|
- that is, we need to ensure that root-of-trust = FetchLightBlock(nsec, root-of-trust.Header.Height) |
|
|
|
- Expected precondition |
|
|
|
- *FullNodes* is nonempty |
|
|
|
- Expected postcondition |
|
|
|
- *primary* is moved to *FaultyNodes* |
|
|
|
- a secondary *s* is moved from *Secondaries* to primary |
|
|
|
- Error condition |
|
|
|
- if precondition is violated |
|
|
|
|
|
|
|
#### **[LC-FUNC-REPLACE-SECONDARY.1]:** |
|
|
|
|
|
|
|
```go |
|
|
|
Replace_Secondary(addr Address, root-of-trust LightBlock) |
|
|
|
``` |
|
|
|
|
|
|
|
- Implementation remark |
|
|
|
- maintain [LCD-INV-ROOT-AGREED.1], that is, |
|
|
|
ensure root-of-trust = FetchLightBlock(nsec, root-of-trust.Header.Height) |
|
|
|
- Expected precondition |
|
|
|
- *FullNodes* is nonempty |
|
|
|
- Expected postcondition |
|
|
|
- addr is moved from *Secondaries* to *FaultyNodes* |
|
|
|
- an address *nsec* is moved from *FullNodes* to *Secondaries* |
|
|
|
- Error condition |
|
|
|
- if precondition is violated |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Data Types |
|
|
|
|
|
|
|
The core data structure of the protocol is the LightBlock. |
|
|
|
|
|
|
|
#### **[LC-DATA-LIGHTBLOCK.1]** |
|
|
|
|
|
|
|
```go |
|
|
|
type LightBlock struct { |
|
|
|
Header Header |
|
|
|
Commit Commit |
|
|
|
Validators ValidatorSet |
|
|
|
NextValidators ValidatorSet |
|
|
|
Provider PeerID |
|
|
|
} |
|
|
|
``` |
|
|
|
|
|
|
|
#### **[LC-DATA-LIGHTSTORE.1]** |
|
|
|
|
|
|
|
LightBlocks are stored in a structure which stores all LightBlock from |
|
|
|
initialization or received from peers. |
|
|
|
|
|
|
|
```go |
|
|
|
type LightStore struct { |
|
|
|
... |
|
|
|
} |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
We use the functions that the LightStore exposes, which |
|
|
|
are defined in the [verification specification](TODO). |
|
|
|
|
|
|
|
|
|
|
|
### Inputs |
|
|
|
|
|
|
|
The lightclient is initialized with LCInitData |
|
|
|
|
|
|
|
#### **[LC-DATA-INIT.1]** |
|
|
|
|
|
|
|
```go |
|
|
|
type LCInitData struct { |
|
|
|
lightBlock LightBlock |
|
|
|
genesisDoc GenesisDoc |
|
|
|
} |
|
|
|
``` |
|
|
|
|
|
|
|
where only one of the components must be provided. `GenesisDoc` is |
|
|
|
defined in the [Tendermint |
|
|
|
Types](https://github.com/tendermint/tendermint/blob/master/types/genesis.go). |
|
|
|
|
|
|
|
#### **[LC-DATA-GENESIS.1]** |
|
|
|
|
|
|
|
```go |
|
|
|
type GenesisDoc struct { |
|
|
|
GenesisTime time.Time `json:"genesis_time"` |
|
|
|
ChainID string `json:"chain_id"` |
|
|
|
InitialHeight int64 `json:"initial_height"` |
|
|
|
ConsensusParams *tmproto.ConsensusParams `json:"consensus_params,omitempty"` |
|
|
|
Validators []GenesisValidator `json:"validators,omitempty"` |
|
|
|
AppHash tmbytes.HexBytes `json:"app_hash"` |
|
|
|
AppState json.RawMessage `json:"app_state,omitempty"` |
|
|
|
} |
|
|
|
``` |
|
|
|
|
|
|
|
We use the following function |
|
|
|
`makeblock` so that we create a lightblock from the genesis |
|
|
|
file in order to do verification based on the data from the genesis |
|
|
|
file using the same verification function we use in normal operation. |
|
|
|
|
|
|
|
#### **[LC-FUNC-MAKEBLOCK.1]** |
|
|
|
|
|
|
|
```go |
|
|
|
func makeblock (genesisDoc GenesisDoc) (lightBlock LightBlock)) |
|
|
|
``` |
|
|
|
|
|
|
|
- Implementation remark |
|
|
|
- none |
|
|
|
- Expected precondition |
|
|
|
- none |
|
|
|
- Expected postcondition |
|
|
|
- lightBlock.Header.Height = genesisDoc.InitialHeight |
|
|
|
- lightBlock.Header.Time = genesisDoc.GenesisTime |
|
|
|
- lightBlock.Header.LastBlockID = nil |
|
|
|
- lightBlock.Header.LastCommit = nil |
|
|
|
- lightBlock.Header.Validators = genesisDoc.Validators |
|
|
|
- lightBlock.Header.NextValidators = genesisDoc.Validators |
|
|
|
- lightBlock.Header.Data = nil |
|
|
|
- lightBlock.Header.AppState = genesisDoc.AppState |
|
|
|
- lightBlock.Header.LastResult = nil |
|
|
|
- lightBlock.Commit = nil |
|
|
|
- lightBlock.Validators = genesisDoc.Validators |
|
|
|
- lightBlock.NextValidators = genesisDoc.Validators |
|
|
|
- lightBlock.Provider = nil |
|
|
|
- Error condition |
|
|
|
- none |
|
|
|
|
|
|
|
---- |
|
|
|
|
|
|
|
### Configuration Parameters |
|
|
|
|
|
|
|
|
|
|
|
#### **[LC-INV-ROOT-AGREED.1]** |
|
|
|
|
|
|
|
In the Sequential-Supervisor, it is always the case that the primary |
|
|
|
and all secondaries agree on lightStore.Latest(). |
|
|
|
|
|
|
|
### Assumptions |
|
|
|
|
|
|
|
We have to assume that the initialization data (the lightblock or the |
|
|
|
genesis file) are consistent with the blockchain. This is subjective |
|
|
|
initialization and it cannot be checked locally. |
|
|
|
|
|
|
|
### Invariants |
|
|
|
|
|
|
|
#### **[LC-INV-PEERLIST.1]:** |
|
|
|
|
|
|
|
The peer list contains a primary and a secondary. |
|
|
|
|
|
|
|
> If the invariant is violated, the light client does not have enough |
|
|
|
> peers to download headers from. As a result, the light client |
|
|
|
> needs to terminate in case this invariant is violated. |
|
|
|
|
|
|
|
## Supervisor |
|
|
|
|
|
|
|
### Outline |
|
|
|
|
|
|
|
The supervisor implements the functionality of the lightclient. It is |
|
|
|
initialized with a genesis file or with a lightblock the user |
|
|
|
trusts. This initialization is subjective, that is, the security of |
|
|
|
the lightclient is based on the validity of the input. If the genesis |
|
|
|
file or the lightblock deviate from the actual ones on the blockchain, |
|
|
|
the lightclient provides no guarantees. |
|
|
|
|
|
|
|
After initialization, the supervisor awaits an input, that is, the |
|
|
|
height of the next lightblock that should be obtained. Then it |
|
|
|
downloads, verifies, and cross-checks a lightblock, and if all tests |
|
|
|
go through, the light block (and possibly other lightblocks) are added |
|
|
|
to the lightstore, which is returned in an output event to the user. |
|
|
|
|
|
|
|
The following main loop does the interaction with the user (input, |
|
|
|
output) and calls the following two functions: |
|
|
|
|
|
|
|
- `InitLightClient`: it initializes the lightstore either with the |
|
|
|
provided lightblock or with the lightblock that corresponds to the |
|
|
|
first block generated by the blockchain (by the validators defined |
|
|
|
by the genesis file) |
|
|
|
- `VerifyAndDetect`: takes as input a lightstore and a height and |
|
|
|
returns the updated lightstore. |
|
|
|
|
|
|
|
#### **[LC-FUNC-SUPERVISOR.1]:** |
|
|
|
|
|
|
|
```go |
|
|
|
func Sequential-Supervisor (initdata LCInitData) (Error) { |
|
|
|
|
|
|
|
lightStore,result := InitLightClient(initData); |
|
|
|
if result != OK { |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
loop { |
|
|
|
// get the next height |
|
|
|
nextHeight := input(); |
|
|
|
|
|
|
|
lightStore,result := VerifyAndDetect(lightStore, nextHeight); |
|
|
|
|
|
|
|
if result == OK { |
|
|
|
output(LightStore.Get(targetHeight)); |
|
|
|
// we only output a trusted lightblock |
|
|
|
} |
|
|
|
else { |
|
|
|
return result |
|
|
|
} |
|
|
|
// QUESTION: is it OK to generate output event in normal case, |
|
|
|
// and terminate with failure in the (light client) attack case? |
|
|
|
} |
|
|
|
} |
|
|
|
``` |
|
|
|
|
|
|
|
- Implementation remark |
|
|
|
- infinite loop unless a light client attack is detected |
|
|
|
- In typical implementations (e.g., the one in Rust), |
|
|
|
there are mutliple input actions: |
|
|
|
`VerifytoLatest`, `LatestTrusted`, and `GetStatus`. The |
|
|
|
information can be easily obtained from the lightstore, so that |
|
|
|
we do not treat these requests explicitly here but just consider |
|
|
|
the request for a block of a given height which requires more |
|
|
|
involved computation and communication. |
|
|
|
- Expected precondition |
|
|
|
- *LCInitData* contains a genesis file or a lightblock. |
|
|
|
- Expected postcondition |
|
|
|
- if a light client attack is detected: it stops and submits |
|
|
|
evidence (in `InitLightClient` or `VerifyAndDetect`) |
|
|
|
- otherwise: non. It runs forever. |
|
|
|
- Invariant: *lightStore* contains trusted lightblocks only. |
|
|
|
- Error condition |
|
|
|
- if `InitLightClient` or `VerifyAndDetect` fails (if a attack is |
|
|
|
detected, or if [LCV-INV-TP.1] is violated) |
|
|
|
|
|
|
|
---- |
|
|
|
|
|
|
|
### Details of the Functions |
|
|
|
|
|
|
|
#### Initialization |
|
|
|
|
|
|
|
The light client is based on subjective initialization. It has to |
|
|
|
trust the initial data given to it by the user. It cannot do any |
|
|
|
detection of attack. So either upon initialization we obtain a |
|
|
|
lightblock and just initialize the lightstore with it. Or in case of a |
|
|
|
genesis file, we download, verify, and cross-check the first block, to |
|
|
|
initialize the lightstore with this first block. The reason is that |
|
|
|
we want to maintain [LCV-INV-TP.1] from the beginning. |
|
|
|
|
|
|
|
> If the lightclient is initialized with a lightblock, one might think |
|
|
|
> it may increase trust, when one cross-checks the initial light |
|
|
|
> block. However, if a peer provides a conflicting |
|
|
|
> lightblock, the question is to distinguish the case of a |
|
|
|
> [bogus](https://informal.systems) block (upon which operation should proceed) from a |
|
|
|
> [light client attack](https://informal.systems) (upon which operation should stop). In |
|
|
|
> case of a bogus block, the lightclient might be forced to do |
|
|
|
> backwards verification until the blocks are out of the trusting |
|
|
|
> period, to make sure no previous validator set could have generated |
|
|
|
> the bogus block, which effectively opens up a DoS attack on the lightclient |
|
|
|
> without adding effective robustness. |
|
|
|
|
|
|
|
#### **[LC-FUNC-INIT.1]:** |
|
|
|
|
|
|
|
```go |
|
|
|
func InitLightClient (initData LCInitData) (LightStore, Error) { |
|
|
|
|
|
|
|
if LCInitData.LightBlock != nil { |
|
|
|
// we trust the provided initial block. |
|
|
|
newblock := LCInitData.LightBlock |
|
|
|
} |
|
|
|
else { |
|
|
|
genesisBlock := makeblock(initData.genesisDoc); |
|
|
|
|
|
|
|
result := NoResult; |
|
|
|
while result != ResultSuccess { |
|
|
|
current = FetchLightBlock(PeerList.primary(), genesisBlock.Header.Height + 1) |
|
|
|
// QUESTION: is the height with "+1" OK? |
|
|
|
|
|
|
|
if CANNOT_VERIFY = ValidAndVerify(genesisBlock, current) { |
|
|
|
Replace_Primary(); |
|
|
|
} |
|
|
|
else { |
|
|
|
result = ResultSuccess |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// cross-check |
|
|
|
auxLS := new LightStore |
|
|
|
auxLS.Add(current) |
|
|
|
Evidences := AttackDetector(genesisBlock, auxLS) |
|
|
|
if Evidences.Empty { |
|
|
|
newBlock := current |
|
|
|
} |
|
|
|
else { |
|
|
|
// [LC-SUMBIT-EVIDENCE.1] |
|
|
|
submitEvidence(Evidences); |
|
|
|
return(nil, ErrorAttack); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
lightStore := new LightStore; |
|
|
|
lightStore.Add(newBlock); |
|
|
|
return (lightStore, OK); |
|
|
|
} |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
- Implementation remark |
|
|
|
- none |
|
|
|
- Expected precondition |
|
|
|
- *LCInitData* contains either a genesis file of a lightblock |
|
|
|
- if genesis it passes `ValidateAndComplete()` see [Tendermint](https://informal.systems) |
|
|
|
- Expected postcondition |
|
|
|
- *lightStore* initialized with trusted lightblock. It has either been |
|
|
|
cross-checked (from genesis) or it has initial trust from the |
|
|
|
user. |
|
|
|
- Error condition |
|
|
|
- if precondition is violated |
|
|
|
- empty peerList |
|
|
|
|
|
|
|
---- |
|
|
|
|
|
|
|
#### Main verification and detection logic |
|
|
|
|
|
|
|
#### **[LC-FUNC-MAIN-VERIF-DETECT.1]:** |
|
|
|
|
|
|
|
```go |
|
|
|
func VerifyAndDetect (lightStore LightStore, targetHeight Height) |
|
|
|
(LightStore, Result) { |
|
|
|
|
|
|
|
b1, r1 = lightStore.Get(targetHeight) |
|
|
|
if r1 == true { |
|
|
|
if b1.State == StateTrusted { |
|
|
|
// block already there and trusted |
|
|
|
return (lightStore, ResultSuccess) |
|
|
|
} |
|
|
|
else { |
|
|
|
// We have a lightblock in the store, but it has not been |
|
|
|
// cross-checked by now. We do that now. |
|
|
|
root_of_trust, auxLS := lightstore.TraceTo(b1); |
|
|
|
|
|
|
|
// Cross-check |
|
|
|
Evidences := AttackDetector(root_of_trust, auxLS); |
|
|
|
if Evidences.Empty { |
|
|
|
// no attack detected, we trust the new lightblock |
|
|
|
lightStore.Update(auxLS.Latest(), |
|
|
|
StateTrusted, |
|
|
|
verfiedLS.Latest().verification-root); |
|
|
|
return (lightStore, OK); |
|
|
|
} |
|
|
|
else { |
|
|
|
// there is an attack, we exit |
|
|
|
return(lightStore, ErrorAttack); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// get the lightblock with maximum height smaller than targetHeight |
|
|
|
// would typically be the heighest, if we always move forward |
|
|
|
root_of_trust, r2 = lightStore.LatestPrevious(targetHeight); |
|
|
|
|
|
|
|
if r2 = false { |
|
|
|
// there is no lightblock from which we can do forward |
|
|
|
// (skipping) verification. Thus we have to go backwards. |
|
|
|
// No cross-check needed. We trust hashes. Therefore, we |
|
|
|
// directly return the result |
|
|
|
return Backwards(primary, lightStore.Lowest(), targetHeight) |
|
|
|
} |
|
|
|
else { |
|
|
|
// Forward verification + detection |
|
|
|
result := NoResult; |
|
|
|
while result != ResultSuccess { |
|
|
|
verifiedLS,result := VerifyToTarget(primary, |
|
|
|
root_of_trust, |
|
|
|
nextHeight); |
|
|
|
if result == ResultFailure { |
|
|
|
// pick new primary (promote a secondary to primary) |
|
|
|
Replace_Primary(root_of_trust); |
|
|
|
} |
|
|
|
else if result == ResultExpired { |
|
|
|
return (lightStore, result) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Cross-check |
|
|
|
Evidences := AttackDetector(root_of_trust, verifiedLS); |
|
|
|
if Evidences.Empty { |
|
|
|
// no attack detected, we trust the new lightblock |
|
|
|
verifiedLS.Update(verfiedLS.Latest(), |
|
|
|
StateTrusted, |
|
|
|
verfiedLS.Latest().verification-root); |
|
|
|
lightStore.store_chain(verifidLS); |
|
|
|
return (lightStore, OK); |
|
|
|
} |
|
|
|
else { |
|
|
|
// there is an attack, we exit |
|
|
|
return(lightStore, ErrorAttack); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
``` |
|
|
|
|
|
|
|
- Implementation remark |
|
|
|
- none |
|
|
|
- Expected precondition |
|
|
|
- none |
|
|
|
- Expected postcondition |
|
|
|
- lightblock of height *targetHeight* (and possibly additional blocks) added to *lightStore* |
|
|
|
- Error condition |
|
|
|
- an attack is detected |
|
|
|
- [LC-DATA-PEERLIST-INV.1] is violated |
|
|
|
|
|
|
|
---- |
|
|
|
|