You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

637 lines
22 KiB

  1. # Draft of Light Client Supervisor for discussion
  2. ## TODOs
  3. This specification in done in parallel with updates on the
  4. verification specification. So some hyperlinks have to be placed to
  5. the correct files eventually.
  6. # Light Client Sequential Supervisor
  7. The light client implements a read operation of a
  8. [header](TMBC-HEADER-link) from the [blockchain](TMBC-SEQ-link), by
  9. communicating with full nodes, a so-called primary and several
  10. so-called witnesses. As some full nodes may be faulty, this
  11. functionality must be implemented in a fault-tolerant way.
  12. In the Tendermint blockchain, the validator set may change with every
  13. new block. The staking and unbonding mechanism induces a [security
  14. model](TMBC-FM-2THIRDS-link): starting at time *Time* of the
  15. [header](TMBC-HEADER-link),
  16. more than two-thirds of the next validators of a new block are correct
  17. for the duration of *TrustedPeriod*.
  18. [Light Client Verification](https://informal.systems) implements the fault-tolerant read
  19. operation designed for this security model. That is, it is safe if the
  20. model assumptions are satisfied and makes progress if it communicates
  21. to a correct primary.
  22. However, if the [security model](TMBC-FM-2THIRDS-link) is violated,
  23. faulty peers (that have been validators at some point in the past) may
  24. launch attacks on the Tendermint network, and on the light
  25. client. These attacks as well as an axiomatization of blocks in
  26. general are defined in [a document that contains the definitions that
  27. are currently in detection.md](https://informal.systems).
  28. If there is a light client attack (but no
  29. successful attack on the network), the safety of the verification step
  30. may be violated (as we operate outside its basic assumption).
  31. The light client also
  32. contains a defense mechanism against light clients attacks, called detection.
  33. [Light Client Detection](https://informal.systems) implements a cross check of the result
  34. of the verification step. If there is a light client attack, and the
  35. light client is connected to a correct peer, the light client as a
  36. whole is safe, that is, it will not operate on invalid
  37. blocks. However, in this case it cannot successfully read, as
  38. inconsistent blocks are in the system. However, in this case the
  39. detection performs a distributed computation that results in so-called
  40. evidence. Evidence can be used to prove
  41. to a correct full node that there has been a
  42. light client attack.
  43. [Light Client Evidence Accountability](https://informal.systems) is a protocol run on a
  44. full node to check whether submitted evidence indeed proves the
  45. existence of a light client attack. Further, from the evidence and its
  46. own knowledge about the blockchain, the full node computes a set of
  47. bonded full nodes (that at some point had more than one third of the
  48. voting power) that participated in the attack that will be reported
  49. via ABCI to the application.
  50. In this document we specify
  51. - Initialization of the Light Client
  52. - The interaction of [verification](https://informal.systems) and [detection](https://informal.systems)
  53. The details of these two protocols are captured in their own
  54. documents, as is the [accountability](https://informal.systems) protocol.
  55. > Another related line is IBC attack detection and submission at the
  56. > relayer, as well as attack verification at the IBC handler. This
  57. > will call for yet another spec.
  58. # Status
  59. This document is work in progress. In order to develop the
  60. specification step-by-step,
  61. it assumes certain details of [verification](https://informal.systems) and
  62. [detection](https://informal.systems) that are not specified in the respective current
  63. versions yet. This inconsistencies will be addresses over several
  64. upcoming PRs.
  65. # Part I - Tendermint Blockchain
  66. See [verification spec](addLinksWhenDone)
  67. # Part II - Sequential Problem Definition
  68. #### **[LC-SEQ-INIT-LIVE.1]**
  69. Upon initialization, the light client gets as input a header of the
  70. blockchain, or the genesis file of the blockchain, and eventually
  71. stores a header of the blockchain.
  72. #### **[LC-SEQ-LIVE.1]**
  73. The light client gets a sequence of heights as inputs. For each input
  74. height *targetHeight*, it eventually stores the header of height
  75. *targetHeight*.
  76. #### **[LC-SEQ-SAFE.1]**
  77. The light client never stores a header which is not in the blockchain.
  78. # Part III - Light Client as Distributed System
  79. ## Computational Model
  80. The light client communicates with remote processes only via the
  81. [verification](TODO) and the [detection](TODO) protocols. The
  82. respective assumptions are given there.
  83. ## Distributed Problem Statement
  84. ### Two Kinds of Liveness
  85. In case of light client attacks, the sequential problem statement
  86. cannot always be satisfied. The lightclient cannot decide which block
  87. is from the chain and which is not. As a result, the light client just
  88. creates evidence, submits it, and terminates.
  89. For the liveness property, we thus add the
  90. possibility that instead of adding a lightblock, we also might terminate
  91. in case there is an attack.
  92. #### **[LC-DIST-TERM.1]**
  93. The light client either runs forever or it *terminates on attack*.
  94. ### Design choices
  95. #### [LC-DIST-STORE.1]
  96. The light client has a local data structure called LightStore
  97. that contains light blocks (that contain a header).
  98. > The light store exposes functions to query and update it. They are
  99. > specified [here](TODO:onceVerificationIsMerged).
  100. **TODO:** reference light store invariant [LCV-INV-LS-ROOT.2] once
  101. verification is merged
  102. #### **[LC-DIST-SAFE.1]**
  103. It is always the case that every header in *LightStore* was
  104. generated by an instance of Tendermint consensus.
  105. #### **[LC-DIST-LIVE.1]**
  106. Whenever the light client gets a new height *h* as input,
  107. - and there is
  108. no light client attack up to height *h*, then the lightclient
  109. eventually puts the lightblock of height *h* in the lightstore and
  110. wait for another input.
  111. - otherwise, that is, if there
  112. is a light client attack on height *h*, then the light client
  113. must perform one of the following:
  114. - it terminates on attack.
  115. - it eventually puts the lightblock of height *h* in the lightstore and
  116. wait for another input.
  117. > 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.
  118. ### Solving the sequential specification
  119. [LC-DIST-SAFE.1] is guaranteed by the detector; in particular it
  120. follows from
  121. [[LCD-DIST-INV-STORE.1]](TODO)
  122. [[LCD-DIST-LIVE.1]](TODO)
  123. # Part IV - Light Client Supervisor Protocol
  124. We provide a specification for a sequential Light Client Supervisor.
  125. The local code for verification is presented by a sequential function
  126. `Sequential-Supervisor` to highlight the control flow of this
  127. functionality. Each lightblock is first verified with a primary, and then
  128. cross-checked with secondaries, and if all goes well, the lightblock
  129. is
  130. added (with the attribute "trusted") to the
  131. lightstore. Intermiate lightblocks that were used to verify the target
  132. block but were not cross-checked are stored as "verified"
  133. > We note that if a different concurrency model is considered
  134. > for an implementation, the semantics of the lightstore might change:
  135. > In a concurrent implementation, we might do verification for some
  136. > height *h*, add the
  137. > lightblock to the lightstore, and start concurrent threads that
  138. >
  139. > - do verification for the next height *h' != h*
  140. > - do cross-checking for height *h*. If we find an attack, we remove
  141. > *h* from the lightstore.
  142. > - the user might already start to use *h*
  143. >
  144. > Thus, this concurrency model changes the semantics of the
  145. > lightstore (not all lightblocks that are read by the user are
  146. > trusted; they may be removed if
  147. > we find a problem). Whether this is desirable, and whether the gain in
  148. > performance is worth it, we keep for future versions/discussion of
  149. > lightclient protocols.
  150. ## Definitions
  151. ### Peers
  152. #### **[LC-DATA-PEERS.1]:**
  153. A fixed set of full nodes is provided in the configuration upon
  154. initialization. Initially this set is partitioned into
  155. - one full node that is the *primary* (singleton set),
  156. - a set *Secondaries* (of fixed size, e.g., 3),
  157. - a set *FullNodes*.
  158. - A set *FaultyNodes* of nodes that the light client suspects of
  159. being faulty; it is initially empty
  160. #### **[LC-INV-NODES.1]:**
  161. The detector shall maintain the following invariants:
  162. - *FullNodes \intersect Secondaries = {}*
  163. - *FullNodes \intersect FaultyNodes = {}*
  164. - *Secondaries \intersect FaultyNodes = {}*
  165. and the following transition invariant
  166. - *FullNodes' \union Secondaries' \union FaultyNodes' = FullNodes
  167. \union Secondaries \union FaultyNodes*
  168. #### **[LC-FUNC-REPLACE-PRIMARY.1]:**
  169. ```go
  170. Replace_Primary(root-of-trust LightBlock)
  171. ```
  172. - Implementation remark
  173. - the primary is replaced by a secondary
  174. - to maintain a constant size of secondaries, need to
  175. - pick a new secondary *nsec* while ensuring [LC-INV-ROOT-AGREED.1]
  176. - that is, we need to ensure that root-of-trust = FetchLightBlock(nsec, root-of-trust.Header.Height)
  177. - Expected precondition
  178. - *FullNodes* is nonempty
  179. - Expected postcondition
  180. - *primary* is moved to *FaultyNodes*
  181. - a secondary *s* is moved from *Secondaries* to primary
  182. - Error condition
  183. - if precondition is violated
  184. #### **[LC-FUNC-REPLACE-SECONDARY.1]:**
  185. ```go
  186. Replace_Secondary(addr Address, root-of-trust LightBlock)
  187. ```
  188. - Implementation remark
  189. - maintain [LCD-INV-ROOT-AGREED.1], that is,
  190. ensure root-of-trust = FetchLightBlock(nsec, root-of-trust.Header.Height)
  191. - Expected precondition
  192. - *FullNodes* is nonempty
  193. - Expected postcondition
  194. - addr is moved from *Secondaries* to *FaultyNodes*
  195. - an address *nsec* is moved from *FullNodes* to *Secondaries*
  196. - Error condition
  197. - if precondition is violated
  198. ### Data Types
  199. The core data structure of the protocol is the LightBlock.
  200. #### **[LC-DATA-LIGHTBLOCK.1]**
  201. ```go
  202. type LightBlock struct {
  203. Header Header
  204. Commit Commit
  205. Validators ValidatorSet
  206. NextValidators ValidatorSet
  207. Provider PeerID
  208. }
  209. ```
  210. #### **[LC-DATA-LIGHTSTORE.1]**
  211. LightBlocks are stored in a structure which stores all LightBlock from
  212. initialization or received from peers.
  213. ```go
  214. type LightStore struct {
  215. ...
  216. }
  217. ```
  218. We use the functions that the LightStore exposes, which
  219. are defined in the [verification specification](TODO).
  220. ### Inputs
  221. The lightclient is initialized with LCInitData
  222. #### **[LC-DATA-INIT.1]**
  223. ```go
  224. type LCInitData struct {
  225. lightBlock LightBlock
  226. genesisDoc GenesisDoc
  227. }
  228. ```
  229. where only one of the components must be provided. `GenesisDoc` is
  230. defined in the [Tendermint
  231. Types](https://github.com/tendermint/tendermint/blob/master/types/genesis.go).
  232. #### **[LC-DATA-GENESIS.1]**
  233. ```go
  234. type GenesisDoc struct {
  235. GenesisTime time.Time `json:"genesis_time"`
  236. ChainID string `json:"chain_id"`
  237. InitialHeight int64 `json:"initial_height"`
  238. ConsensusParams *tmproto.ConsensusParams `json:"consensus_params,omitempty"`
  239. Validators []GenesisValidator `json:"validators,omitempty"`
  240. AppHash tmbytes.HexBytes `json:"app_hash"`
  241. AppState json.RawMessage `json:"app_state,omitempty"`
  242. }
  243. ```
  244. We use the following function
  245. `makeblock` so that we create a lightblock from the genesis
  246. file in order to do verification based on the data from the genesis
  247. file using the same verification function we use in normal operation.
  248. #### **[LC-FUNC-MAKEBLOCK.1]**
  249. ```go
  250. func makeblock (genesisDoc GenesisDoc) (lightBlock LightBlock))
  251. ```
  252. - Implementation remark
  253. - none
  254. - Expected precondition
  255. - none
  256. - Expected postcondition
  257. - lightBlock.Header.Height = genesisDoc.InitialHeight
  258. - lightBlock.Header.Time = genesisDoc.GenesisTime
  259. - lightBlock.Header.LastBlockID = nil
  260. - lightBlock.Header.LastCommit = nil
  261. - lightBlock.Header.Validators = genesisDoc.Validators
  262. - lightBlock.Header.NextValidators = genesisDoc.Validators
  263. - lightBlock.Header.Data = nil
  264. - lightBlock.Header.AppState = genesisDoc.AppState
  265. - lightBlock.Header.LastResult = nil
  266. - lightBlock.Commit = nil
  267. - lightBlock.Validators = genesisDoc.Validators
  268. - lightBlock.NextValidators = genesisDoc.Validators
  269. - lightBlock.Provider = nil
  270. - Error condition
  271. - none
  272. ----
  273. ### Configuration Parameters
  274. #### **[LC-INV-ROOT-AGREED.1]**
  275. In the Sequential-Supervisor, it is always the case that the primary
  276. and all secondaries agree on lightStore.Latest().
  277. ### Assumptions
  278. We have to assume that the initialization data (the lightblock or the
  279. genesis file) are consistent with the blockchain. This is subjective
  280. initialization and it cannot be checked locally.
  281. ### Invariants
  282. #### **[LC-INV-PEERLIST.1]:**
  283. The peer list contains a primary and a secondary.
  284. > If the invariant is violated, the light client does not have enough
  285. > peers to download headers from. As a result, the light client
  286. > needs to terminate in case this invariant is violated.
  287. ## Supervisor
  288. ### Outline
  289. The supervisor implements the functionality of the lightclient. It is
  290. initialized with a genesis file or with a lightblock the user
  291. trusts. This initialization is subjective, that is, the security of
  292. the lightclient is based on the validity of the input. If the genesis
  293. file or the lightblock deviate from the actual ones on the blockchain,
  294. the lightclient provides no guarantees.
  295. After initialization, the supervisor awaits an input, that is, the
  296. height of the next lightblock that should be obtained. Then it
  297. downloads, verifies, and cross-checks a lightblock, and if all tests
  298. go through, the light block (and possibly other lightblocks) are added
  299. to the lightstore, which is returned in an output event to the user.
  300. The following main loop does the interaction with the user (input,
  301. output) and calls the following two functions:
  302. - `InitLightClient`: it initializes the lightstore either with the
  303. provided lightblock or with the lightblock that corresponds to the
  304. first block generated by the blockchain (by the validators defined
  305. by the genesis file)
  306. - `VerifyAndDetect`: takes as input a lightstore and a height and
  307. returns the updated lightstore.
  308. #### **[LC-FUNC-SUPERVISOR.1]:**
  309. ```go
  310. func Sequential-Supervisor (initdata LCInitData) (Error) {
  311. lightStore,result := InitLightClient(initData);
  312. if result != OK {
  313. return result;
  314. }
  315. loop {
  316. // get the next height
  317. nextHeight := input();
  318. lightStore,result := VerifyAndDetect(lightStore, nextHeight);
  319. if result == OK {
  320. output(LightStore.Get(targetHeight));
  321. // we only output a trusted lightblock
  322. }
  323. else {
  324. return result
  325. }
  326. // QUESTION: is it OK to generate output event in normal case,
  327. // and terminate with failure in the (light client) attack case?
  328. }
  329. }
  330. ```
  331. - Implementation remark
  332. - infinite loop unless a light client attack is detected
  333. - In typical implementations (e.g., the one in Rust),
  334. there are mutliple input actions:
  335. `VerifytoLatest`, `LatestTrusted`, and `GetStatus`. The
  336. information can be easily obtained from the lightstore, so that
  337. we do not treat these requests explicitly here but just consider
  338. the request for a block of a given height which requires more
  339. involved computation and communication.
  340. - Expected precondition
  341. - *LCInitData* contains a genesis file or a lightblock.
  342. - Expected postcondition
  343. - if a light client attack is detected: it stops and submits
  344. evidence (in `InitLightClient` or `VerifyAndDetect`)
  345. - otherwise: non. It runs forever.
  346. - Invariant: *lightStore* contains trusted lightblocks only.
  347. - Error condition
  348. - if `InitLightClient` or `VerifyAndDetect` fails (if a attack is
  349. detected, or if [LCV-INV-TP.1] is violated)
  350. ----
  351. ### Details of the Functions
  352. #### Initialization
  353. The light client is based on subjective initialization. It has to
  354. trust the initial data given to it by the user. It cannot do any
  355. detection of attack. So either upon initialization we obtain a
  356. lightblock and just initialize the lightstore with it. Or in case of a
  357. genesis file, we download, verify, and cross-check the first block, to
  358. initialize the lightstore with this first block. The reason is that
  359. we want to maintain [LCV-INV-TP.1] from the beginning.
  360. > If the lightclient is initialized with a lightblock, one might think
  361. > it may increase trust, when one cross-checks the initial light
  362. > block. However, if a peer provides a conflicting
  363. > lightblock, the question is to distinguish the case of a
  364. > [bogus](https://informal.systems) block (upon which operation should proceed) from a
  365. > [light client attack](https://informal.systems) (upon which operation should stop). In
  366. > case of a bogus block, the lightclient might be forced to do
  367. > backwards verification until the blocks are out of the trusting
  368. > period, to make sure no previous validator set could have generated
  369. > the bogus block, which effectively opens up a DoS attack on the lightclient
  370. > without adding effective robustness.
  371. #### **[LC-FUNC-INIT.1]:**
  372. ```go
  373. func InitLightClient (initData LCInitData) (LightStore, Error) {
  374. if LCInitData.LightBlock != nil {
  375. // we trust the provided initial block.
  376. newblock := LCInitData.LightBlock
  377. }
  378. else {
  379. genesisBlock := makeblock(initData.genesisDoc);
  380. result := NoResult;
  381. while result != ResultSuccess {
  382. current = FetchLightBlock(PeerList.primary(), genesisBlock.Header.Height + 1)
  383. // QUESTION: is the height with "+1" OK?
  384. if CANNOT_VERIFY = ValidAndVerify(genesisBlock, current) {
  385. Replace_Primary();
  386. }
  387. else {
  388. result = ResultSuccess
  389. }
  390. }
  391. // cross-check
  392. auxLS := new LightStore
  393. auxLS.Add(current)
  394. Evidences := AttackDetector(genesisBlock, auxLS)
  395. if Evidences.Empty {
  396. newBlock := current
  397. }
  398. else {
  399. // [LC-SUMBIT-EVIDENCE.1]
  400. submitEvidence(Evidences);
  401. return(nil, ErrorAttack);
  402. }
  403. }
  404. lightStore := new LightStore;
  405. lightStore.Add(newBlock);
  406. return (lightStore, OK);
  407. }
  408. ```
  409. - Implementation remark
  410. - none
  411. - Expected precondition
  412. - *LCInitData* contains either a genesis file of a lightblock
  413. - if genesis it passes `ValidateAndComplete()` see [Tendermint](https://informal.systems)
  414. - Expected postcondition
  415. - *lightStore* initialized with trusted lightblock. It has either been
  416. cross-checked (from genesis) or it has initial trust from the
  417. user.
  418. - Error condition
  419. - if precondition is violated
  420. - empty peerList
  421. ----
  422. #### Main verification and detection logic
  423. #### **[LC-FUNC-MAIN-VERIF-DETECT.1]:**
  424. ```go
  425. func VerifyAndDetect (lightStore LightStore, targetHeight Height)
  426. (LightStore, Result) {
  427. b1, r1 = lightStore.Get(targetHeight)
  428. if r1 == true {
  429. if b1.State == StateTrusted {
  430. // block already there and trusted
  431. return (lightStore, ResultSuccess)
  432. }
  433. else {
  434. // We have a lightblock in the store, but it has not been
  435. // cross-checked by now. We do that now.
  436. root_of_trust, auxLS := lightstore.TraceTo(b1);
  437. // Cross-check
  438. Evidences := AttackDetector(root_of_trust, auxLS);
  439. if Evidences.Empty {
  440. // no attack detected, we trust the new lightblock
  441. lightStore.Update(auxLS.Latest(),
  442. StateTrusted,
  443. verfiedLS.Latest().verification-root);
  444. return (lightStore, OK);
  445. }
  446. else {
  447. // there is an attack, we exit
  448. submitEvidence(Evidences);
  449. return(lightStore, ErrorAttack);
  450. }
  451. }
  452. }
  453. // get the lightblock with maximum height smaller than targetHeight
  454. // would typically be the heighest, if we always move forward
  455. root_of_trust, r2 = lightStore.LatestPrevious(targetHeight);
  456. if r2 = false {
  457. // there is no lightblock from which we can do forward
  458. // (skipping) verification. Thus we have to go backwards.
  459. // No cross-check needed. We trust hashes. Therefore, we
  460. // directly return the result
  461. return Backwards(primary, lightStore.Lowest(), targetHeight)
  462. }
  463. else {
  464. // Forward verification + detection
  465. result := NoResult;
  466. while result != ResultSuccess {
  467. verifiedLS,result := VerifyToTarget(primary,
  468. root_of_trust,
  469. nextHeight);
  470. if result == ResultFailure {
  471. // pick new primary (promote a secondary to primary)
  472. Replace_Primary(root_of_trust);
  473. }
  474. else if result == ResultExpired {
  475. return (lightStore, result)
  476. }
  477. }
  478. // Cross-check
  479. Evidences := AttackDetector(root_of_trust, verifiedLS);
  480. if Evidences.Empty {
  481. // no attack detected, we trust the new lightblock
  482. verifiedLS.Update(verfiedLS.Latest(),
  483. StateTrusted,
  484. verfiedLS.Latest().verification-root);
  485. lightStore.store_chain(verifidLS);
  486. return (lightStore, OK);
  487. }
  488. else {
  489. // there is an attack, we exit
  490. return(lightStore, ErrorAttack);
  491. }
  492. }
  493. }
  494. ```
  495. - Implementation remark
  496. - none
  497. - Expected precondition
  498. - none
  499. - Expected postcondition
  500. - lightblock of height *targetHeight* (and possibly additional blocks) added to *lightStore*
  501. - Error condition
  502. - an attack is detected
  503. - [LC-DATA-PEERLIST-INV.1] is violated
  504. ----