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.

178 lines
6.9 KiB

  1. # Results of Discussions and Decisions
  2. - Generating a minimal proof of fork (as suggested in [Issue #5083](https://github.com/tendermint/tendermint/issues/5083)) is too costly at the light client
  3. - we do not know all lightblocks from the primary
  4. - therefore there are many scenarios. we might even need to ask
  5. the primary again for additional lightblocks to isolate the
  6. branch.
  7. > For instance, the light node starts with block at height 1 and the
  8. > primary provides a block of height 10 that the light node can
  9. > verify immediately. In cross-checking, a secondary now provides a
  10. > conflicting header b10 of height 10 that needs another header b5
  11. > of height 5 to
  12. > verify. Now, in order for the light node to convince the primary:
  13. >
  14. > - The light node cannot just sent b5, as it is not clear whether
  15. > the fork happened before or after 5
  16. > - The light node cannot just send b10, as the primary would also
  17. > need b5 for verification
  18. > - In order to minimize the evidence, the light node may try to
  19. > figure out where the branch happens, e.g., by asking the primary
  20. > for height 5 (it might be that more queries are required, also
  21. > to the secondary. However, assuming that in this scenario the
  22. > primary is faulty it may not respond.
  23. As the main goal is to catch misbehavior of the primary,
  24. evidence generation and punishment must not depend on their
  25. cooperation. So the moment we have proof of fork (even if it
  26. contains several light blocks) we should submit right away.
  27. - decision: "full" proof of fork consists of two traces that originate in the
  28. same lightblock and lead to conflicting headers of the same height.
  29. - For submission of proof of fork, we may do some optimizations, for
  30. instance, we might just submit a trace of lightblocks that verifies a block
  31. different from the one the full node knows (we do not send the trace
  32. the primary gave us back to the primary)
  33. - The light client attack is via the primary. Thus we try to
  34. catch if the primary installs a bad light block
  35. - We do not check secondary against secondary
  36. - For each secondary, we check the primary against one secondary
  37. - Observe that just two blocks for the same height are not
  38. sufficient proof of fork.
  39. One of the blocks may be bogus [TMBC-BOGUS.1] which does
  40. not constitute slashable behavior.
  41. Which leads to the question whether the light node should try to do
  42. fork detection on its initial block (from subjective
  43. initialization). This could be done by doing backwards verification
  44. (with the hashes) until a bifurcation block is found.
  45. While there are scenarios where a
  46. fork could be found, there is also the scenario where a faulty full
  47. node feeds the light node with bogus light blocks and forces the light
  48. node to check hashes until a bogus chain is out of the trusting period.
  49. As a result, the light client
  50. should not try to detect a fork for its initial header. **The initial
  51. header must be trusted as is.**
  52. # Light Client Sequential Supervisor
  53. **TODO:** decide where (into which specification) to put the
  54. following:
  55. We describe the context on which the fork detector is called by giving
  56. a sequential version of the supervisor function.
  57. Roughly, it alternates two phases namely:
  58. - Light Client Verification. As a result, a header of the required
  59. height has been downloaded from and verified with the primary.
  60. - Light Client Fork Detections. As a result the header has been
  61. cross-checked with the secondaries. In case there is a fork we
  62. submit "proof of fork" and exit.
  63. #### **[LC-FUNC-SUPERVISOR.1]:**
  64. ```go
  65. func Sequential-Supervisor () (Error) {
  66. loop {
  67. // get the next height
  68. nextHeight := input();
  69. // Verify
  70. result := NoResult;
  71. while result != ResultSuccess {
  72. lightStore,result := VerifyToTarget(primary, lightStore, nextHeight);
  73. if result == ResultFailure {
  74. // pick new primary (promote a secondary to primary)
  75. /// and delete all lightblocks above
  76. // LastTrusted (they have not been cross-checked)
  77. Replace_Primary();
  78. }
  79. }
  80. // Cross-check
  81. PoFs := Forkdetector(lightStore, PoFs);
  82. if PoFs.Empty {
  83. // no fork detected with secondaries, we trust the new
  84. // lightblock
  85. LightStore.Update(testedLB, StateTrusted);
  86. }
  87. else {
  88. // there is a fork, we submit the proofs and exit
  89. for i, p range PoFs {
  90. SubmitProofOfFork(p);
  91. }
  92. return(ErrorFork);
  93. }
  94. }
  95. }
  96. ```
  97. **TODO:** finish conditions
  98. - Implementation remark
  99. - Expected precondition
  100. - *lightStore* initialized with trusted header
  101. - *PoFs* empty
  102. - Expected postcondition
  103. - runs forever, or
  104. - is terminated by user and satisfies LightStore invariant, or **TODO**
  105. - has submitted proof of fork upon detecting a fork
  106. - Error condition
  107. - none
  108. ----
  109. # Semantics of the LightStore
  110. Currently, a lightblock in the lightstore can be in one of the
  111. following states:
  112. - StateUnverified
  113. - StateVerified
  114. - StateFailed
  115. - StateTrusted
  116. The intuition is that `StateVerified` captures that the lightblock has
  117. been verified with the primary, and `StateTrusted` is the state after
  118. successful cross-checking with the secondaries.
  119. Assuming there is **always one correct node among primary and
  120. secondaries**, and there is no fork on the blockchain, lightblocks that
  121. are in `StateTrusted` can be used by the user with the guarantee of
  122. "finality". If a block in `StateVerified` is used, it might be that
  123. detection later finds a fork, and a roll-back might be needed.
  124. **Remark:** The assumption of one correct node, does not render
  125. verification useless. It is true that if the primary and the
  126. secondaries return the same block we may trust it. However, if there
  127. is a node that provides a different block, the light node still needs
  128. verification to understand whether there is a fork, or whether the
  129. different block is just bogus (without any support of some previous
  130. validator set).
  131. **Remark:** A light node may choose the full nodes it communicates
  132. with (the light node and the full node might even belong to the same
  133. stakeholder) so the assumption might be justified in some cases.
  134. In the future, we will do the following changes
  135. - we assume that only from time to time, the light node is
  136. connected to a correct full node
  137. - this means for some limited time, the light node might have no
  138. means to defend against light client attacks
  139. - as a result we do not have finality
  140. - once the light node reconnects with a correct full node, it
  141. should detect the light client attack and submit evidence.
  142. Under these assumptions, `StateTrusted` loses its meaning. As a
  143. result, it should be removed from the API. We suggest that we replace
  144. it with a flag "trusted" that can be used
  145. - internally for efficiency reasons (to maintain
  146. [LCD-INV-TRUSTED-AGREED.1] until a fork is detected)
  147. - by light client based on the "one correct full node" assumption
  148. ----