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.

259 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 and is used
  15. // only in the case of skipping verification which employs the trust level mechanism.
  16. //
  17. // It takes the target verified header and compares it with the headers of a set of
  18. // witness providers that the light client is connected to. If a conflicting header
  19. // is returned it verifies and examines the conflicting header against the verified
  20. // trace that was produced from the primary. If successful it produces two sets of evidence
  21. // and sends them to the opposite provider before halting.
  22. //
  23. // If there are no conflictinge headers, the light client deems the verified target header
  24. // trusted and saves it to the trusted store.
  25. func (c *Client) detectDivergence(ctx context.Context, primaryTrace []*types.LightBlock, now time.Time) error {
  26. if primaryTrace == nil || len(primaryTrace) < 2 {
  27. return errors.New("nil or single block primary trace")
  28. }
  29. var (
  30. headerMatched bool
  31. lastVerifiedHeader = primaryTrace[len(primaryTrace)-1].SignedHeader
  32. witnessesToRemove = make([]int, 0)
  33. )
  34. c.logger.Info("Running detector against trace", "endBlockHeight", lastVerifiedHeader.Height,
  35. "endBlockHash", lastVerifiedHeader.Hash, "length", len(primaryTrace))
  36. c.providerMutex.Lock()
  37. defer c.providerMutex.Unlock()
  38. if len(c.witnesses) == 0 {
  39. return errNoWitnesses{}
  40. }
  41. // launch one goroutine per witness to retrieve the light block of the target height
  42. // and compare it with the header from the primary
  43. errc := make(chan error, len(c.witnesses))
  44. for i, witness := range c.witnesses {
  45. go c.compareNewHeaderWithWitness(ctx, errc, lastVerifiedHeader, witness, i)
  46. }
  47. // handle errors from the header comparisons as they come in
  48. for i := 0; i < cap(errc); i++ {
  49. err := <-errc
  50. switch e := err.(type) {
  51. case nil: // at least one header matched
  52. headerMatched = true
  53. case errConflictingHeaders:
  54. // We have conflicting headers. This could possibly imply an attack on the light client.
  55. // First we need to verify the witness's header using the same skipping verification and then we
  56. // need to find the point that the headers diverge and examine this for any evidence of an attack.
  57. //
  58. // We combine these actions together, verifying the witnesses headers and outputting the trace
  59. // which captures the bifurcation point and if successful provides the information to create
  60. supportingWitness := c.witnesses[e.WitnessIndex]
  61. witnessTrace, primaryBlock, err := c.examineConflictingHeaderAgainstTrace(
  62. ctx,
  63. primaryTrace,
  64. e.Block.SignedHeader,
  65. supportingWitness,
  66. now,
  67. )
  68. if err != nil {
  69. c.logger.Info("Error validating witness's divergent header", "witness", supportingWitness, "err", err)
  70. witnessesToRemove = append(witnessesToRemove, e.WitnessIndex)
  71. continue
  72. }
  73. // if this is an equivocation or amnesia attack, i.e. the validator sets are the same, then we
  74. // return the height of the conflicting block else if it is a lunatic attack and the validator sets
  75. // are not the same then we send the height of the common header.
  76. commonHeight := primaryBlock.Height
  77. if isInvalidHeader(witnessTrace[len(witnessTrace)-1].Header, primaryBlock.Header) {
  78. // height of the common header
  79. commonHeight = witnessTrace[0].Height
  80. }
  81. // We are suspecting that the primary is faulty, hence we hold the witness as the source of truth
  82. // and generate evidence against the primary that we can send to the witness
  83. ev := &types.LightClientAttackEvidence{
  84. ConflictingBlock: primaryBlock,
  85. CommonHeight: commonHeight, // the first block in the bisection is common to both providers
  86. }
  87. c.logger.Error("Attack detected. Sending evidence againt primary by witness", "ev", ev,
  88. "primary", c.primary, "witness", supportingWitness)
  89. c.sendEvidence(ctx, ev, supportingWitness)
  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. // if this is an equivocation or amnesia attack, i.e. the validator sets are the same, then we
  105. // return the height of the conflicting block else if it is a lunatic attack and the validator sets
  106. // are not the same then we send the height of the common header.
  107. commonHeight = primaryBlock.Height
  108. if isInvalidHeader(primaryTrace[len(primaryTrace)-1].Header, witnessBlock.Header) {
  109. // height of the common header
  110. commonHeight = primaryTrace[0].Height
  111. }
  112. // We now use the primary trace to create evidence against the witness and send it to the primary
  113. ev = &types.LightClientAttackEvidence{
  114. ConflictingBlock: witnessBlock,
  115. CommonHeight: commonHeight, // the first block in the bisection is common to both providers
  116. }
  117. c.logger.Error("Sending evidence against witness by primary", "ev", ev,
  118. "primary", c.primary, "witness", supportingWitness)
  119. c.sendEvidence(ctx, ev, c.primary)
  120. // We return the error and don't process anymore witnesses
  121. return e
  122. case errBadWitness:
  123. c.logger.Info("Witness returned an error during header comparison", "witness", c.witnesses[e.WitnessIndex],
  124. "err", err)
  125. // if witness sent us an invalid header, then remove it. If it didn't respond or couldn't find the block, then we
  126. // ignore it and move on to the next witness
  127. if _, ok := e.Reason.(provider.ErrBadLightBlock); ok {
  128. c.logger.Info("Witness sent us invalid header / vals -> removing it", "witness", c.witnesses[e.WitnessIndex])
  129. witnessesToRemove = append(witnessesToRemove, e.WitnessIndex)
  130. }
  131. }
  132. }
  133. for _, idx := range witnessesToRemove {
  134. c.removeWitness(idx)
  135. }
  136. // 1. If we had at least one witness that returned the same header then we
  137. // conclude that we can trust the header
  138. if headerMatched {
  139. return nil
  140. }
  141. // 2. ELse all witnesses have either not responded, don't have the block or sent invalid blocks.
  142. return ErrFailedHeaderCrossReferencing
  143. }
  144. // compareNewHeaderWithWitness takes the verified header from the primary and compares it with a
  145. // header from a specified witness. The function can return one of three errors:
  146. //
  147. // 1: errConflictingHeaders -> there may have been an attack on this light client
  148. // 2: errBadWitness -> the witness has either not responded, doesn't have the header or has given us an invalid one
  149. // Note: In the case of an invalid header we remove the witness
  150. // 3: nil -> the hashes of the two headers match
  151. func (c *Client) compareNewHeaderWithWitness(ctx context.Context, errc chan error, h *types.SignedHeader,
  152. witness provider.Provider, witnessIndex int) {
  153. lightBlock, err := witness.LightBlock(ctx, h.Height)
  154. if err != nil {
  155. errc <- errBadWitness{Reason: err, WitnessIndex: witnessIndex}
  156. return
  157. }
  158. if !bytes.Equal(h.Hash(), lightBlock.Hash()) {
  159. errc <- errConflictingHeaders{Block: lightBlock, WitnessIndex: witnessIndex}
  160. }
  161. c.logger.Info("Matching header received by witness", "height", h.Height, "witness", witnessIndex)
  162. errc <- nil
  163. }
  164. // sendEvidence sends evidence to a provider on a best effort basis.
  165. func (c *Client) sendEvidence(ctx context.Context, ev *types.LightClientAttackEvidence, receiver provider.Provider) {
  166. err := receiver.ReportEvidence(ctx, ev)
  167. if err != nil {
  168. c.logger.Error("Failed to report evidence to provider", "ev", ev, "provider", receiver)
  169. }
  170. }
  171. // examineConflictingHeaderAgainstTrace takes a trace from one provider and a divergent header that
  172. // it has received from another and preforms verifySkipping at the heights of each of the intermediate
  173. // headers in the trace until it reaches the divergentHeader. 1 of 2 things can happen.
  174. //
  175. // 1. The light client verifies a header that is different to the intermediate header in the trace. This
  176. // is the bifurcation point and the light client can create evidence from it
  177. // 2. The source stops responding, doesn't have the block or sends an invalid header in which case we
  178. // return the error and remove the witness
  179. func (c *Client) examineConflictingHeaderAgainstTrace(
  180. ctx context.Context,
  181. trace []*types.LightBlock,
  182. divergentHeader *types.SignedHeader,
  183. source provider.Provider, now time.Time) ([]*types.LightBlock, *types.LightBlock, error) {
  184. var previouslyVerifiedBlock *types.LightBlock
  185. for idx, traceBlock := range trace {
  186. // The first block in the trace MUST be the same to the light block that the source produces
  187. // else we cannot continue with verification.
  188. sourceBlock, err := source.LightBlock(ctx, traceBlock.Height)
  189. if err != nil {
  190. return nil, nil, err
  191. }
  192. if idx == 0 {
  193. if shash, thash := sourceBlock.Hash(), traceBlock.Hash(); !bytes.Equal(shash, thash) {
  194. return nil, nil, fmt.Errorf("trusted block is different to the source's first block (%X = %X)",
  195. thash, shash)
  196. }
  197. previouslyVerifiedBlock = sourceBlock
  198. continue
  199. }
  200. // we check that the source provider can verify a block at the same height of the
  201. // intermediate height
  202. trace, err := c.verifySkipping(ctx, source, previouslyVerifiedBlock, sourceBlock, now)
  203. if err != nil {
  204. return nil, nil, fmt.Errorf("verifySkipping of conflicting header failed: %w", err)
  205. }
  206. // check if the headers verified by the source has diverged from the trace
  207. if shash, thash := sourceBlock.Hash(), traceBlock.Hash(); !bytes.Equal(shash, thash) {
  208. // Bifurcation point found!
  209. return trace, traceBlock, nil
  210. }
  211. // headers are still the same. update the previouslyVerifiedBlock
  212. previouslyVerifiedBlock = sourceBlock
  213. }
  214. // We have reached the end of the trace without observing a divergence. The last header is thus different
  215. // from the divergent header that the source originally sent us, then we return an error.
  216. return nil, nil, fmt.Errorf("source provided different header to the original header it provided (%X != %X)",
  217. previouslyVerifiedBlock.Hash(), divergentHeader.Hash())
  218. }
  219. // isInvalidHeader takes a trusted header and matches it againt a conflicting header
  220. // to determine whether the conflicting header was the product of a valid state transition
  221. // or not. If it is then all the deterministic fields of the header should be the same.
  222. // If not, it is an invalid header and constitutes a lunatic attack.
  223. func isInvalidHeader(trusted, conflicting *types.Header) bool {
  224. return !bytes.Equal(trusted.ValidatorsHash, conflicting.ValidatorsHash) ||
  225. !bytes.Equal(trusted.NextValidatorsHash, conflicting.NextValidatorsHash) ||
  226. !bytes.Equal(trusted.ConsensusHash, conflicting.ConsensusHash) ||
  227. !bytes.Equal(trusted.AppHash, conflicting.AppHash) ||
  228. !bytes.Equal(trusted.LastResultsHash, conflicting.LastResultsHash)
  229. }