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.

258 lines
11 KiB

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