github.com/516108736/tendermint@v0.36.0/light/detector.go (about) 1 package light 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "sort" 9 "time" 10 11 "github.com/tendermint/tendermint/light/provider" 12 "github.com/tendermint/tendermint/types" 13 ) 14 15 // The detector component of the light client detect and handles attacks on the light client. 16 // More info here: 17 // tendermint/docs/architecture/adr-047-handling-evidence-from-light-client.md 18 19 // detectDivergence is a second wall of defense for the light client. 20 // 21 // It takes the target verified header and compares it with the headers of a set of 22 // witness providers that the light client is connected to. If a conflicting header 23 // is returned it verifies and examines the conflicting header against the verified 24 // trace that was produced from the primary. If successful it produces two sets of evidence 25 // and sends them to the opposite provider before halting. 26 // 27 // If there are no conflictinge headers, the light client deems the verified target header 28 // trusted and saves it to the trusted store. 29 func (c *Client) detectDivergence(ctx context.Context, primaryTrace []*types.LightBlock, now time.Time) error { 30 if primaryTrace == nil || len(primaryTrace) < 2 { 31 return errors.New("nil or single block primary trace") 32 } 33 var ( 34 headerMatched bool 35 lastVerifiedHeader = primaryTrace[len(primaryTrace)-1].SignedHeader 36 witnessesToRemove = make([]int, 0) 37 ) 38 c.logger.Debug("Running detector against trace", "endBlockHeight", lastVerifiedHeader.Height, 39 "endBlockHash", lastVerifiedHeader.Hash, "length", len(primaryTrace)) 40 41 c.providerMutex.Lock() 42 defer c.providerMutex.Unlock() 43 44 if len(c.witnesses) == 0 { 45 return errNoWitnesses{} 46 } 47 48 // launch one goroutine per witness to retrieve the light block of the target height 49 // and compare it with the header from the primary 50 errc := make(chan error, len(c.witnesses)) 51 for i, witness := range c.witnesses { 52 go c.compareNewHeaderWithWitness(ctx, errc, lastVerifiedHeader, witness, i) 53 } 54 55 // handle errors from the header comparisons as they come in 56 for i := 0; i < cap(errc); i++ { 57 err := <-errc 58 59 switch e := err.(type) { 60 case nil: // at least one header matched 61 headerMatched = true 62 case errConflictingHeaders: 63 // We have conflicting headers. This could possibly imply an attack on the light client. 64 // First we need to verify the witness's header using the same skipping verification and then we 65 // need to find the point that the headers diverge and examine this for any evidence of an attack. 66 // 67 // We combine these actions together, verifying the witnesses headers and outputting the trace 68 // which captures the bifurcation point and if successful provides the information to create 69 supportingWitness := c.witnesses[e.WitnessIndex] 70 witnessTrace, primaryBlock, err := c.examineConflictingHeaderAgainstTrace( 71 ctx, 72 primaryTrace, 73 e.Block.SignedHeader, 74 supportingWitness, 75 now, 76 ) 77 if err != nil { 78 c.logger.Info("Error validating witness's divergent header", "witness", supportingWitness, "err", err) 79 witnessesToRemove = append(witnessesToRemove, e.WitnessIndex) 80 continue 81 } 82 83 // We are suspecting that the primary is faulty, hence we hold the witness as the source of truth 84 // and generate evidence against the primary that we can send to the witness 85 primaryEv := newLightClientAttackEvidence(primaryBlock, witnessTrace[len(witnessTrace)-1], witnessTrace[0]) 86 c.logger.Error("Attempted attack detected. Sending evidence againt primary by witness", "ev", primaryEv, 87 "primary", c.primary, "witness", supportingWitness) 88 c.sendEvidence(ctx, primaryEv, supportingWitness) 89 90 // This may not be valid because the witness itself is at fault. So now we reverse it, examining the 91 // trace provided by the witness and holding the primary as the source of truth. Note: primary may not 92 // respond but this is okay as we will halt anyway. 93 primaryTrace, witnessBlock, err := c.examineConflictingHeaderAgainstTrace( 94 ctx, 95 witnessTrace, 96 primaryBlock.SignedHeader, 97 c.primary, 98 now, 99 ) 100 if err != nil { 101 c.logger.Info("Error validating primary's divergent header", "primary", c.primary, "err", err) 102 continue 103 } 104 105 // We now use the primary trace to create evidence against the witness and send it to the primary 106 witnessEv := newLightClientAttackEvidence(witnessBlock, primaryTrace[len(primaryTrace)-1], primaryTrace[0]) 107 c.logger.Error("Sending evidence against witness by primary", "ev", witnessEv, 108 "primary", c.primary, "witness", supportingWitness) 109 c.sendEvidence(ctx, witnessEv, c.primary) 110 // We return the error and don't process anymore witnesses 111 return e 112 113 case errBadWitness: 114 c.logger.Info("Witness returned an error during header comparison", "witness", c.witnesses[e.WitnessIndex], 115 "err", err) 116 // if witness sent us an invalid header, then remove it. If it didn't respond or couldn't find the block, then we 117 // ignore it and move on to the next witness 118 if _, ok := e.Reason.(provider.ErrBadLightBlock); ok { 119 c.logger.Info("Witness sent us invalid header / vals -> removing it", "witness", c.witnesses[e.WitnessIndex]) 120 witnessesToRemove = append(witnessesToRemove, e.WitnessIndex) 121 } 122 } 123 } 124 125 // we need to make sure that we remove witnesses by index in the reverse 126 // order so as to not affect the indexes themselves 127 sort.Ints(witnessesToRemove) 128 for i := len(witnessesToRemove) - 1; i >= 0; i-- { 129 c.removeWitness(witnessesToRemove[i]) 130 } 131 132 // 1. If we had at least one witness that returned the same header then we 133 // conclude that we can trust the header 134 if headerMatched { 135 return nil 136 } 137 138 // 2. ELse all witnesses have either not responded, don't have the block or sent invalid blocks. 139 return ErrFailedHeaderCrossReferencing 140 } 141 142 // compareNewHeaderWithWitness takes the verified header from the primary and compares it with a 143 // header from a specified witness. The function can return one of three errors: 144 // 145 // 1: errConflictingHeaders -> there may have been an attack on this light client 146 // 2: errBadWitness -> the witness has either not responded, doesn't have the header or has given us an invalid one 147 // Note: In the case of an invalid header we remove the witness 148 // 3: nil -> the hashes of the two headers match 149 func (c *Client) compareNewHeaderWithWitness(ctx context.Context, errc chan error, h *types.SignedHeader, 150 witness provider.Provider, witnessIndex int) { 151 152 lightBlock, err := witness.LightBlock(ctx, h.Height) 153 if err != nil { 154 errc <- errBadWitness{Reason: err, WitnessIndex: witnessIndex} 155 return 156 } 157 158 if !bytes.Equal(h.Hash(), lightBlock.Hash()) { 159 errc <- errConflictingHeaders{Block: lightBlock, WitnessIndex: witnessIndex} 160 } 161 162 c.logger.Debug("Matching header received by witness", "height", h.Height, "witness", witnessIndex) 163 errc <- nil 164 } 165 166 // sendEvidence sends evidence to a provider on a best effort basis. 167 func (c *Client) sendEvidence(ctx context.Context, ev *types.LightClientAttackEvidence, receiver provider.Provider) { 168 err := receiver.ReportEvidence(ctx, ev) 169 if err != nil { 170 c.logger.Error("Failed to report evidence to provider", "ev", ev, "provider", receiver) 171 } 172 } 173 174 // examineConflictingHeaderAgainstTrace takes a trace from one provider and a divergent header that 175 // it has received from another and preforms verifySkipping at the heights of each of the intermediate 176 // headers in the trace until it reaches the divergentHeader. 1 of 2 things can happen. 177 // 178 // 1. The light client verifies a header that is different to the intermediate header in the trace. This 179 // is the bifurcation point and the light client can create evidence from it 180 // 2. The source stops responding, doesn't have the block or sends an invalid header in which case we 181 // return the error and remove the witness 182 func (c *Client) examineConflictingHeaderAgainstTrace( 183 ctx context.Context, 184 trace []*types.LightBlock, 185 divergentHeader *types.SignedHeader, 186 source provider.Provider, now time.Time) ([]*types.LightBlock, *types.LightBlock, error) { 187 188 var previouslyVerifiedBlock *types.LightBlock 189 190 for idx, traceBlock := range trace { 191 // The first block in the trace MUST be the same to the light block that the source produces 192 // else we cannot continue with verification. 193 sourceBlock, err := source.LightBlock(ctx, traceBlock.Height) 194 if err != nil { 195 return nil, nil, err 196 } 197 198 if idx == 0 { 199 if shash, thash := sourceBlock.Hash(), traceBlock.Hash(); !bytes.Equal(shash, thash) { 200 return nil, nil, fmt.Errorf("trusted block is different to the source's first block (%X = %X)", 201 thash, shash) 202 } 203 previouslyVerifiedBlock = sourceBlock 204 continue 205 } 206 207 // we check that the source provider can verify a block at the same height of the 208 // intermediate height 209 trace, err := c.verifySkipping(ctx, source, previouslyVerifiedBlock, sourceBlock, now) 210 if err != nil { 211 return nil, nil, fmt.Errorf("verifySkipping of conflicting header failed: %w", err) 212 } 213 // check if the headers verified by the source has diverged from the trace 214 if shash, thash := sourceBlock.Hash(), traceBlock.Hash(); !bytes.Equal(shash, thash) { 215 // Bifurcation point found! 216 return trace, traceBlock, nil 217 } 218 219 // headers are still the same. update the previouslyVerifiedBlock 220 previouslyVerifiedBlock = sourceBlock 221 } 222 223 // We have reached the end of the trace without observing a divergence. The last header is thus different 224 // from the divergent header that the source originally sent us, then we return an error. 225 return nil, nil, fmt.Errorf("source provided different header to the original header it provided (%X != %X)", 226 previouslyVerifiedBlock.Hash(), divergentHeader.Hash()) 227 228 } 229 230 // newLightClientAttackEvidence determines the type of attack and then forms the evidence filling out 231 // all the fields such that it is ready to be sent to a full node. 232 func newLightClientAttackEvidence(conflicted, trusted, common *types.LightBlock) *types.LightClientAttackEvidence { 233 ev := &types.LightClientAttackEvidence{ConflictingBlock: conflicted} 234 // if this is an equivocation or amnesia attack, i.e. the validator sets are the same, then we 235 // return the height of the conflicting block else if it is a lunatic attack and the validator sets 236 // are not the same then we send the height of the common header. 237 if ev.ConflictingHeaderIsInvalid(trusted.Header) { 238 ev.CommonHeight = common.Height 239 ev.Timestamp = common.Time 240 ev.TotalVotingPower = common.ValidatorSet.TotalVotingPower() 241 } else { 242 ev.CommonHeight = trusted.Height 243 ev.Timestamp = trusted.Time 244 ev.TotalVotingPower = trusted.ValidatorSet.TotalVotingPower() 245 } 246 ev.ByzantineValidators = ev.GetByzantineValidators(common.ValidatorSet, trusted.SignedHeader) 247 return ev 248 }