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.

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