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.

423 lines
17 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 detects 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 valid evidence.
  59. err := c.handleConflictingHeaders(ctx, primaryTrace, e.Block, e.WitnessIndex, now)
  60. if err != nil {
  61. // return information of the attack
  62. return err
  63. }
  64. // if attempt to generate conflicting headers failed then remove witness
  65. witnessesToRemove = append(witnessesToRemove, e.WitnessIndex)
  66. case errBadWitness:
  67. c.logger.Info("witness returned an error during header comparison, removing...",
  68. "witness", c.witnesses[e.WitnessIndex], "err", err)
  69. witnessesToRemove = append(witnessesToRemove, e.WitnessIndex)
  70. default:
  71. if errors.Is(e, context.Canceled) || errors.Is(e, context.DeadlineExceeded) {
  72. return e
  73. }
  74. c.logger.Info("error in light block request to witness", "err", err)
  75. }
  76. }
  77. // remove witnesses that have misbehaved
  78. if err := c.removeWitnesses(witnessesToRemove); err != nil {
  79. return err
  80. }
  81. // 1. If we had at least one witness that returned the same header then we
  82. // conclude that we can trust the header
  83. if headerMatched {
  84. return nil
  85. }
  86. // 2. Else all witnesses have either not responded, don't have the block or sent invalid blocks.
  87. return ErrFailedHeaderCrossReferencing
  88. }
  89. // compareNewHeaderWithWitness takes the verified header from the primary and compares it with a
  90. // header from a specified witness. The function can return one of three errors:
  91. //
  92. // 1: errConflictingHeaders -> there may have been an attack on this light client
  93. // 2: errBadWitness -> the witness has either not responded, doesn't have the header or has given us an invalid one
  94. // Note: In the case of an invalid header we remove the witness
  95. // 3: nil -> the hashes of the two headers match
  96. func (c *Client) compareNewHeaderWithWitness(ctx context.Context, errc chan error, h *types.SignedHeader,
  97. witness provider.Provider, witnessIndex int) {
  98. lightBlock, err := c.getLightBlock(ctx, witness, h.Height)
  99. switch err {
  100. // no error means we move on to checking the hash of the two headers
  101. case nil:
  102. break
  103. // the witness hasn't been helpful in comparing headers, we mark the response and continue
  104. // comparing with the rest of the witnesses
  105. case provider.ErrNoResponse, provider.ErrLightBlockNotFound, context.DeadlineExceeded, context.Canceled:
  106. errc <- err
  107. return
  108. // the witness' head of the blockchain is lower than the height of the primary. This could be one of
  109. // two things:
  110. // 1) The witness is lagging behind
  111. // 2) The primary may be performing a lunatic attack with a height and time in the future
  112. case provider.ErrHeightTooHigh:
  113. // The light client now asks for the latest header that the witness has
  114. var isTargetHeight bool
  115. isTargetHeight, lightBlock, err = c.getTargetBlockOrLatest(ctx, h.Height, witness)
  116. if err != nil {
  117. if c.providerShouldBeRemoved(err) {
  118. errc <- errBadWitness{Reason: err, WitnessIndex: witnessIndex}
  119. } else {
  120. errc <- err
  121. }
  122. return
  123. }
  124. // if the witness caught up and has returned a block of the target height then we can
  125. // break from this switch case and continue to verify the hashes
  126. if isTargetHeight {
  127. break
  128. }
  129. // witness' last header is below the primary's header. We check the times to see if the blocks
  130. // have conflicting times
  131. if !lightBlock.Time.Before(h.Time) {
  132. errc <- errConflictingHeaders{Block: lightBlock, WitnessIndex: witnessIndex}
  133. return
  134. }
  135. // the witness is behind. We wait for a period WAITING = 2 * DRIFT + LAG.
  136. // This should give the witness ample time if it is a participating member
  137. // of consensus to produce a block that has a time that is after the primary's
  138. // block time. If not the witness is too far behind and the light client removes it
  139. time.Sleep(2*c.maxClockDrift + c.maxBlockLag)
  140. isTargetHeight, lightBlock, err = c.getTargetBlockOrLatest(ctx, h.Height, witness)
  141. if err != nil {
  142. if c.providerShouldBeRemoved(err) {
  143. errc <- errBadWitness{Reason: err, WitnessIndex: witnessIndex}
  144. } else {
  145. errc <- err
  146. }
  147. return
  148. }
  149. if isTargetHeight {
  150. break
  151. }
  152. // the witness still doesn't have a block at the height of the primary.
  153. // Check if there is a conflicting time
  154. if !lightBlock.Time.Before(h.Time) {
  155. errc <- errConflictingHeaders{Block: lightBlock, WitnessIndex: witnessIndex}
  156. return
  157. }
  158. // Following this request response procedure, the witness has been unable to produce a block
  159. // that can somehow conflict with the primary's block. We thus conclude that the witness
  160. // is too far behind and thus we return a no response error.
  161. //
  162. // NOTE: If the clock drift / lag has been miscalibrated it is feasible that the light client has
  163. // drifted too far ahead for any witness to be able provide a comparable block and thus may allow
  164. // for a malicious primary to attack it
  165. errc <- provider.ErrNoResponse
  166. return
  167. default:
  168. // all other errors (i.e. invalid block, closed connection or unreliable provider) we mark the
  169. // witness as bad and remove it
  170. errc <- errBadWitness{Reason: err, WitnessIndex: witnessIndex}
  171. return
  172. }
  173. if !bytes.Equal(h.Header.Hash(), lightBlock.Header.Hash()) {
  174. errc <- errConflictingHeaders{Block: lightBlock, WitnessIndex: witnessIndex}
  175. }
  176. c.logger.Debug("matching header received by witness", "height", h.Height, "witness", witnessIndex)
  177. errc <- nil
  178. }
  179. // sendEvidence sends evidence to a provider on a best effort basis.
  180. func (c *Client) sendEvidence(ctx context.Context, ev *types.LightClientAttackEvidence, receiver provider.Provider) {
  181. err := receiver.ReportEvidence(ctx, ev)
  182. if err != nil {
  183. c.logger.Error("failed to report evidence to provider", "ev", ev, "provider", receiver)
  184. }
  185. }
  186. // handleConflictingHeaders handles the primary style of attack, which is where a primary and witness have
  187. // two headers of the same height but with different hashes
  188. func (c *Client) handleConflictingHeaders(
  189. ctx context.Context,
  190. primaryTrace []*types.LightBlock,
  191. challendingBlock *types.LightBlock,
  192. witnessIndex int,
  193. now time.Time,
  194. ) error {
  195. supportingWitness := c.witnesses[witnessIndex]
  196. witnessTrace, primaryBlock, err := c.examineConflictingHeaderAgainstTrace(
  197. ctx,
  198. primaryTrace,
  199. challendingBlock,
  200. supportingWitness,
  201. now,
  202. )
  203. if err != nil {
  204. c.logger.Info("error validating witness's divergent header", "witness", supportingWitness, "err", err)
  205. return nil
  206. }
  207. // We are suspecting that the primary is faulty, hence we hold the witness as the source of truth
  208. // and generate evidence against the primary that we can send to the witness
  209. commonBlock, trustedBlock := witnessTrace[0], witnessTrace[len(witnessTrace)-1]
  210. evidenceAgainstPrimary := newLightClientAttackEvidence(primaryBlock, trustedBlock, commonBlock)
  211. c.logger.Error("ATTEMPTED ATTACK DETECTED. Sending evidence againt primary by witness", "ev", evidenceAgainstPrimary,
  212. "primary", c.primary, "witness", supportingWitness)
  213. c.sendEvidence(ctx, evidenceAgainstPrimary, supportingWitness)
  214. if primaryBlock.Commit.Round != witnessTrace[len(witnessTrace)-1].Commit.Round {
  215. c.logger.Info("The light client has detected, and prevented, an attempted amnesia attack." +
  216. " We think this attack is pretty unlikely, so if you see it, that's interesting to us." +
  217. " Can you let us know by opening an issue through https://github.com/tendermint/tendermint/issues/new?")
  218. }
  219. // This may not be valid because the witness itself is at fault. So now we reverse it, examining the
  220. // trace provided by the witness and holding the primary as the source of truth. Note: primary may not
  221. // respond but this is okay as we will halt anyway.
  222. primaryTrace, witnessBlock, err := c.examineConflictingHeaderAgainstTrace(
  223. ctx,
  224. witnessTrace,
  225. primaryBlock,
  226. c.primary,
  227. now,
  228. )
  229. if err != nil {
  230. c.logger.Info("Error validating primary's divergent header", "primary", c.primary, "err", err)
  231. return ErrLightClientAttack
  232. }
  233. // We now use the primary trace to create evidence against the witness and send it to the primary
  234. commonBlock, trustedBlock = primaryTrace[0], primaryTrace[len(primaryTrace)-1]
  235. evidenceAgainstWitness := newLightClientAttackEvidence(witnessBlock, trustedBlock, commonBlock)
  236. c.logger.Error("Sending evidence against witness by primary", "ev", evidenceAgainstWitness,
  237. "primary", c.primary, "witness", supportingWitness)
  238. c.sendEvidence(ctx, evidenceAgainstWitness, c.primary)
  239. // We return the error and don't process anymore witnesses
  240. return ErrLightClientAttack
  241. }
  242. // examineConflictingHeaderAgainstTrace takes a trace from one provider and a divergent header that
  243. // it has received from another and preforms verifySkipping at the heights of each of the intermediate
  244. // headers in the trace until it reaches the divergentHeader. 1 of 2 things can happen.
  245. //
  246. // 1. The light client verifies a header that is different to the intermediate header in the trace. This
  247. // is the bifurcation point and the light client can create evidence from it
  248. // 2. The source stops responding, doesn't have the block or sends an invalid header in which case we
  249. // return the error and remove the witness
  250. //
  251. // CONTRACT:
  252. // 1. Trace can not be empty len(trace) > 0
  253. // 2. The last block in the trace can not be of a lower height than the target block
  254. // trace[len(trace)-1].Height >= targetBlock.Height
  255. // 3. The
  256. func (c *Client) examineConflictingHeaderAgainstTrace(
  257. ctx context.Context,
  258. trace []*types.LightBlock,
  259. targetBlock *types.LightBlock,
  260. source provider.Provider, now time.Time,
  261. ) ([]*types.LightBlock, *types.LightBlock, error) {
  262. var (
  263. previouslyVerifiedBlock, sourceBlock *types.LightBlock
  264. sourceTrace []*types.LightBlock
  265. err error
  266. )
  267. if targetBlock.Height < trace[0].Height {
  268. return nil, nil, fmt.Errorf("target block has a height lower than the trusted height (%d < %d)",
  269. targetBlock.Height, trace[0].Height)
  270. }
  271. for idx, traceBlock := range trace {
  272. // this case only happens in a forward lunatic attack. We treat the block with the
  273. // height directly after the targetBlock as the divergent block
  274. if traceBlock.Height > targetBlock.Height {
  275. // sanity check that the time of the traceBlock is indeed less than that of the targetBlock. If the trace
  276. // was correctly verified we should expect monotonically increasing time. This means that if the block at
  277. // the end of the trace has a lesser time than the target block then all blocks in the trace should have a
  278. // lesser time
  279. if traceBlock.Time.After(targetBlock.Time) {
  280. return nil, nil,
  281. errors.New("sanity check failed: expected traceblock to have a lesser time than the target block")
  282. }
  283. // before sending back the divergent block and trace we need to ensure we have verified
  284. // the final gap between the previouslyVerifiedBlock and the targetBlock
  285. if previouslyVerifiedBlock.Height != targetBlock.Height {
  286. sourceTrace, err = c.verifySkipping(ctx, source, previouslyVerifiedBlock, targetBlock, now)
  287. if err != nil {
  288. return nil, nil, fmt.Errorf("verifySkipping of conflicting header failed: %w", err)
  289. }
  290. }
  291. return sourceTrace, traceBlock, nil
  292. }
  293. // get the corresponding block from the source to verify and match up against the traceBlock
  294. if traceBlock.Height == targetBlock.Height {
  295. sourceBlock = targetBlock
  296. } else {
  297. sourceBlock, err = c.getLightBlock(ctx, source, traceBlock.Height)
  298. if err != nil {
  299. return nil, nil, fmt.Errorf("failed to examine trace: %w", err)
  300. }
  301. }
  302. // The first block in the trace MUST be the same to the light block that the source produces
  303. // else we cannot continue with verification.
  304. if idx == 0 {
  305. if shash, thash := sourceBlock.Hash(), traceBlock.Hash(); !bytes.Equal(shash, thash) {
  306. return nil, nil, fmt.Errorf("trusted block is different to the source's first block (%X = %X)",
  307. thash, shash)
  308. }
  309. previouslyVerifiedBlock = sourceBlock
  310. continue
  311. }
  312. // we check that the source provider can verify a block at the same height of the
  313. // intermediate height
  314. sourceTrace, err = c.verifySkipping(ctx, source, previouslyVerifiedBlock, sourceBlock, now)
  315. if err != nil {
  316. return nil, nil, fmt.Errorf("verifySkipping of conflicting header failed: %w", err)
  317. }
  318. // check if the headers verified by the source has diverged from the trace
  319. if shash, thash := sourceBlock.Hash(), traceBlock.Hash(); !bytes.Equal(shash, thash) {
  320. // Bifurcation point found!
  321. return sourceTrace, traceBlock, nil
  322. }
  323. // headers are still the same. update the previouslyVerifiedBlock
  324. previouslyVerifiedBlock = sourceBlock
  325. }
  326. // We have reached the end of the trace. This should never happen. This can only happen if one of the stated
  327. // prerequisites to this function were not met. Namely that either trace[len(trace)-1].Height < targetBlock.Height
  328. // or that trace[i].Hash() != targetBlock.Hash()
  329. return nil, nil, errNoDivergence
  330. }
  331. // getTargetBlockOrLatest gets the latest height, if it is greater than the target height then it queries
  332. // the target heght else it returns the latest. returns true if it successfully managed to acquire the target
  333. // height.
  334. func (c *Client) getTargetBlockOrLatest(
  335. ctx context.Context,
  336. height int64,
  337. witness provider.Provider,
  338. ) (bool, *types.LightBlock, error) {
  339. lightBlock, err := c.getLightBlock(ctx, witness, 0)
  340. if err != nil {
  341. return false, nil, err
  342. }
  343. if lightBlock.Height == height {
  344. // the witness has caught up to the height of the provider's signed header. We
  345. // can resume with checking the hashes.
  346. return true, lightBlock, nil
  347. }
  348. if lightBlock.Height > height {
  349. // the witness has caught up. We recursively call the function again. However in order
  350. // to avoud a wild goose chase where the witness sends us one header below and one header
  351. // above the height we set a timeout to the context
  352. lightBlock, err := c.getLightBlock(ctx, witness, height)
  353. return true, lightBlock, err
  354. }
  355. return false, lightBlock, nil
  356. }
  357. // newLightClientAttackEvidence determines the type of attack and then forms the evidence filling out
  358. // all the fields such that it is ready to be sent to a full node.
  359. func newLightClientAttackEvidence(conflicted, trusted, common *types.LightBlock) *types.LightClientAttackEvidence {
  360. ev := &types.LightClientAttackEvidence{ConflictingBlock: conflicted}
  361. // We use the common height to indicate the form of the attack.
  362. // if this is an equivocation or amnesia attack, i.e. the validator sets are the same, then we
  363. // return the height of the conflicting block as the common height. If instead it is a lunatic
  364. // attack and the validator sets are not the same then we send the height of the common header.
  365. if ev.ConflictingHeaderIsInvalid(trusted.Header) {
  366. ev.CommonHeight = common.Height
  367. ev.Timestamp = common.Time
  368. ev.TotalVotingPower = common.ValidatorSet.TotalVotingPower()
  369. } else {
  370. ev.CommonHeight = trusted.Height
  371. ev.Timestamp = trusted.Time
  372. ev.TotalVotingPower = trusted.ValidatorSet.TotalVotingPower()
  373. }
  374. ev.ByzantineValidators = ev.GetByzantineValidators(common.ValidatorSet, trusted.SignedHeader)
  375. return ev
  376. }