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.

285 lines
11 KiB

  1. package evidence
  2. import (
  3. "bytes"
  4. "errors"
  5. "fmt"
  6. "time"
  7. "github.com/tendermint/tendermint/light"
  8. "github.com/tendermint/tendermint/types"
  9. )
  10. // verify verifies the evidence fully by checking:
  11. // - It has not already been committed
  12. // - it is sufficiently recent (MaxAge)
  13. // - it is from a key who was a validator at the given height
  14. // - it is internally consistent with state
  15. // - it was properly signed by the alleged equivocator and meets the individual evidence verification requirements
  16. func (evpool *Pool) verify(evidence types.Evidence) error {
  17. var (
  18. state = evpool.State()
  19. height = state.LastBlockHeight
  20. evidenceParams = state.ConsensusParams.Evidence
  21. ageNumBlocks = height - evidence.Height()
  22. )
  23. // verify the time of the evidence
  24. blockMeta := evpool.blockStore.LoadBlockMeta(evidence.Height())
  25. if blockMeta == nil {
  26. return fmt.Errorf("don't have header #%d", evidence.Height())
  27. }
  28. evTime := blockMeta.Header.Time
  29. if evidence.Time() != evTime {
  30. return fmt.Errorf("evidence has a different time to the block it is associated with (%v != %v)",
  31. evidence.Time(), evTime)
  32. }
  33. ageDuration := state.LastBlockTime.Sub(evTime)
  34. // check that the evidence hasn't expired
  35. if ageDuration > evidenceParams.MaxAgeDuration && ageNumBlocks > evidenceParams.MaxAgeNumBlocks {
  36. return fmt.Errorf(
  37. "evidence from height %d (created at: %v) is too old; min height is %d and evidence can not be older than %v",
  38. evidence.Height(),
  39. evTime,
  40. height-evidenceParams.MaxAgeNumBlocks,
  41. state.LastBlockTime.Add(evidenceParams.MaxAgeDuration),
  42. )
  43. }
  44. // apply the evidence-specific verification logic
  45. switch ev := evidence.(type) {
  46. case *types.DuplicateVoteEvidence:
  47. valSet, err := evpool.stateDB.LoadValidators(evidence.Height())
  48. if err != nil {
  49. return err
  50. }
  51. return VerifyDuplicateVote(ev, state.ChainID, valSet)
  52. case *types.LightClientAttackEvidence:
  53. commonHeader, err := getSignedHeader(evpool.blockStore, evidence.Height())
  54. if err != nil {
  55. return err
  56. }
  57. commonVals, err := evpool.stateDB.LoadValidators(evidence.Height())
  58. if err != nil {
  59. return err
  60. }
  61. trustedHeader := commonHeader
  62. // in the case of lunatic the trusted header is different to the common header
  63. if evidence.Height() != ev.ConflictingBlock.Height {
  64. trustedHeader, err = getSignedHeader(evpool.blockStore, ev.ConflictingBlock.Height)
  65. if err != nil {
  66. // FIXME: This multi step process is a bit unergonomic. We may want to consider a more efficient process
  67. // that doesn't require as much io and is atomic.
  68. // If the node doesn't have a block at the height of the conflicting block, then this could be
  69. // a forward lunatic attack. Thus the node must get the latest height it has
  70. latestHeight := evpool.blockStore.Height()
  71. trustedHeader, err = getSignedHeader(evpool.blockStore, latestHeight)
  72. if err != nil {
  73. return err
  74. }
  75. if trustedHeader.Time.Before(ev.ConflictingBlock.Time) {
  76. return fmt.Errorf("latest block time (%v) is before conflicting block time (%v)",
  77. trustedHeader.Time, ev.ConflictingBlock.Time,
  78. )
  79. }
  80. }
  81. }
  82. err = VerifyLightClientAttack(ev, commonHeader, trustedHeader, commonVals, state.LastBlockTime,
  83. state.ConsensusParams.Evidence.MaxAgeDuration)
  84. if err != nil {
  85. return err
  86. }
  87. return nil
  88. default:
  89. return fmt.Errorf("unrecognized evidence type: %T", evidence)
  90. }
  91. }
  92. // VerifyLightClientAttack verifies LightClientAttackEvidence against the state of the full node. This involves
  93. // the following checks:
  94. // - the common header from the full node has at least 1/3 voting power which is also present in
  95. // the conflicting header's commit
  96. // - 2/3+ of the conflicting validator set correctly signed the conflicting block
  97. // - the nodes trusted header at the same height as the conflicting header has a different hash
  98. //
  99. // CONTRACT: must run ValidateBasic() on the evidence before verifying
  100. // must check that the evidence has not expired (i.e. is outside the maximum age threshold)
  101. func VerifyLightClientAttack(e *types.LightClientAttackEvidence, commonHeader, trustedHeader *types.SignedHeader,
  102. commonVals *types.ValidatorSet, now time.Time, trustPeriod time.Duration) error {
  103. // In the case of lunatic attack there will be a different commonHeader height. Therefore the node perform a single
  104. // verification jump between the common header and the conflicting one
  105. if commonHeader.Height != e.ConflictingBlock.Height {
  106. err := commonVals.VerifyCommitLightTrusting(trustedHeader.ChainID, e.ConflictingBlock.Commit, light.DefaultTrustLevel)
  107. if err != nil {
  108. return fmt.Errorf("skipping verification of conflicting block failed: %w", err)
  109. }
  110. // In the case of equivocation and amnesia we expect all header hashes to be correctly derived
  111. } else if e.ConflictingHeaderIsInvalid(trustedHeader.Header) {
  112. return errors.New("common height is the same as conflicting block height so expected the conflicting" +
  113. " block to be correctly derived yet it wasn't")
  114. }
  115. // Verify that the 2/3+ commits from the conflicting validator set were for the conflicting header
  116. if err := e.ConflictingBlock.ValidatorSet.VerifyCommitLight(trustedHeader.ChainID, e.ConflictingBlock.Commit.BlockID,
  117. e.ConflictingBlock.Height, e.ConflictingBlock.Commit); err != nil {
  118. return fmt.Errorf("invalid commit from conflicting block: %w", err)
  119. }
  120. // Assert the correct amount of voting power of the validator set
  121. if evTotal, valsTotal := e.TotalVotingPower, commonVals.TotalVotingPower(); evTotal != valsTotal {
  122. return fmt.Errorf("total voting power from the evidence and our validator set does not match (%d != %d)",
  123. evTotal, valsTotal)
  124. }
  125. // check in the case of a forward lunatic attack that monotonically increasing time has been violated
  126. if e.ConflictingBlock.Height > trustedHeader.Height && e.ConflictingBlock.Time.After(trustedHeader.Time) {
  127. return fmt.Errorf("conflicting block doesn't violate monotonically increasing time (%v is after %v)",
  128. e.ConflictingBlock.Time, trustedHeader.Time,
  129. )
  130. // In all other cases check that the hashes of the conflicting header and the trusted header are different
  131. } else if bytes.Equal(trustedHeader.Hash(), e.ConflictingBlock.Hash()) {
  132. return fmt.Errorf("trusted header hash matches the evidence's conflicting header hash: %X",
  133. trustedHeader.Hash())
  134. }
  135. return validateABCIEvidence(e, commonVals, trustedHeader)
  136. }
  137. // VerifyDuplicateVote verifies DuplicateVoteEvidence against the state of full node. This involves the
  138. // following checks:
  139. // - the validator is in the validator set at the height of the evidence
  140. // - the height, round, type and validator address of the votes must be the same
  141. // - the block ID's must be different
  142. // - The signatures must both be valid
  143. func VerifyDuplicateVote(e *types.DuplicateVoteEvidence, chainID string, valSet *types.ValidatorSet) error {
  144. _, val := valSet.GetByAddress(e.VoteA.ValidatorAddress)
  145. if val == nil {
  146. return fmt.Errorf("address %X was not a validator at height %d", e.VoteA.ValidatorAddress, e.Height())
  147. }
  148. pubKey := val.PubKey
  149. // H/R/S must be the same
  150. if e.VoteA.Height != e.VoteB.Height ||
  151. e.VoteA.Round != e.VoteB.Round ||
  152. e.VoteA.Type != e.VoteB.Type {
  153. return fmt.Errorf("h/r/s does not match: %d/%d/%v vs %d/%d/%v",
  154. e.VoteA.Height, e.VoteA.Round, e.VoteA.Type,
  155. e.VoteB.Height, e.VoteB.Round, e.VoteB.Type)
  156. }
  157. // Address must be the same
  158. if !bytes.Equal(e.VoteA.ValidatorAddress, e.VoteB.ValidatorAddress) {
  159. return fmt.Errorf("validator addresses do not match: %X vs %X",
  160. e.VoteA.ValidatorAddress,
  161. e.VoteB.ValidatorAddress,
  162. )
  163. }
  164. // BlockIDs must be different
  165. if e.VoteA.BlockID.Equals(e.VoteB.BlockID) {
  166. return fmt.Errorf(
  167. "block IDs are the same (%v) - not a real duplicate vote",
  168. e.VoteA.BlockID,
  169. )
  170. }
  171. // pubkey must match address (this should already be true, sanity check)
  172. addr := e.VoteA.ValidatorAddress
  173. if !bytes.Equal(pubKey.Address(), addr) {
  174. return fmt.Errorf("address (%X) doesn't match pubkey (%v - %X)",
  175. addr, pubKey, pubKey.Address())
  176. }
  177. // validator voting power and total voting power must match
  178. if val.VotingPower != e.ValidatorPower {
  179. return fmt.Errorf("validator power from evidence and our validator set does not match (%d != %d)",
  180. e.ValidatorPower, val.VotingPower)
  181. }
  182. if valSet.TotalVotingPower() != e.TotalVotingPower {
  183. return fmt.Errorf("total voting power from the evidence and our validator set does not match (%d != %d)",
  184. e.TotalVotingPower, valSet.TotalVotingPower())
  185. }
  186. va := e.VoteA.ToProto()
  187. vb := e.VoteB.ToProto()
  188. // Signatures must be valid
  189. if !pubKey.VerifySignature(types.VoteSignBytes(chainID, va), e.VoteA.Signature) {
  190. return fmt.Errorf("verifying VoteA: %w", types.ErrVoteInvalidSignature)
  191. }
  192. if !pubKey.VerifySignature(types.VoteSignBytes(chainID, vb), e.VoteB.Signature) {
  193. return fmt.Errorf("verifying VoteB: %w", types.ErrVoteInvalidSignature)
  194. }
  195. return nil
  196. }
  197. // validateABCIEvidence validates the ABCI component of the light client attack
  198. // evidence i.e voting power and byzantine validators
  199. func validateABCIEvidence(
  200. ev *types.LightClientAttackEvidence,
  201. commonVals *types.ValidatorSet,
  202. trustedHeader *types.SignedHeader,
  203. ) error {
  204. if evTotal, valsTotal := ev.TotalVotingPower, commonVals.TotalVotingPower(); evTotal != valsTotal {
  205. return fmt.Errorf("total voting power from the evidence and our validator set does not match (%d != %d)",
  206. evTotal, valsTotal)
  207. }
  208. // Find out what type of attack this was and thus extract the malicious
  209. // validators. Note, in the case of an Amnesia attack we don't have any
  210. // malicious validators.
  211. validators := ev.GetByzantineValidators(commonVals, trustedHeader)
  212. // Ensure this matches the validators that are listed in the evidence. They
  213. // should be ordered based on power.
  214. if validators == nil && ev.ByzantineValidators != nil {
  215. return fmt.Errorf(
  216. "expected nil validators from an amnesia light client attack but got %d",
  217. len(ev.ByzantineValidators),
  218. )
  219. }
  220. if exp, got := len(validators), len(ev.ByzantineValidators); exp != got {
  221. return fmt.Errorf("expected %d byzantine validators from evidence but got %d", exp, got)
  222. }
  223. for idx, val := range validators {
  224. if !bytes.Equal(ev.ByzantineValidators[idx].Address, val.Address) {
  225. return fmt.Errorf(
  226. "evidence contained an unexpected byzantine validator address; expected: %v, got: %v",
  227. val.Address, ev.ByzantineValidators[idx].Address,
  228. )
  229. }
  230. if ev.ByzantineValidators[idx].VotingPower != val.VotingPower {
  231. return fmt.Errorf(
  232. "evidence contained unexpected byzantine validator power; expected %d, got %d",
  233. val.VotingPower, ev.ByzantineValidators[idx].VotingPower,
  234. )
  235. }
  236. }
  237. return nil
  238. }
  239. func getSignedHeader(blockStore BlockStore, height int64) (*types.SignedHeader, error) {
  240. blockMeta := blockStore.LoadBlockMeta(height)
  241. if blockMeta == nil {
  242. return nil, fmt.Errorf("don't have header at height #%d", height)
  243. }
  244. commit := blockStore.LoadBlockCommit(height)
  245. if commit == nil {
  246. return nil, fmt.Errorf("don't have commit at height #%d", height)
  247. }
  248. return &types.SignedHeader{
  249. Header: &blockMeta.Header,
  250. Commit: commit,
  251. }, nil
  252. }