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