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.

252 lines
9.8 KiB

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