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.

289 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) (*info, error) {
  17. var (
  18. state = evpool.State()
  19. height = state.LastBlockHeight
  20. evidenceParams = state.ConsensusParams.Evidence
  21. ageNumBlocks = height - evidence.Height()
  22. )
  23. // check that the evidence isn't already committed
  24. if evpool.isCommitted(evidence) {
  25. return nil, errors.New("evidence was already committed")
  26. }
  27. // verify the time of the evidence
  28. blockMeta := evpool.blockStore.LoadBlockMeta(evidence.Height())
  29. if blockMeta == nil {
  30. return nil, fmt.Errorf("don't have header at height #%d", evidence.Height())
  31. }
  32. evTime := blockMeta.Header.Time
  33. ageDuration := state.LastBlockTime.Sub(evTime)
  34. // check that the evidence hasn't expired
  35. if ageDuration > evidenceParams.MaxAgeDuration && ageNumBlocks > evidenceParams.MaxAgeNumBlocks {
  36. return nil, 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 nil, err
  50. }
  51. err = VerifyDuplicateVote(ev, state.ChainID, valSet)
  52. if err != nil {
  53. return nil, fmt.Errorf("verifying duplicate vote evidence: %w", err)
  54. }
  55. _, val := valSet.GetByAddress(ev.VoteA.ValidatorAddress)
  56. return &info{
  57. Evidence: evidence,
  58. Time: evTime,
  59. Validators: []*types.Validator{val}, // just a single validator for duplicate vote evidence
  60. TotalVotingPower: valSet.TotalVotingPower(),
  61. }, nil
  62. case *types.LightClientAttackEvidence:
  63. commonHeader, err := getSignedHeader(evpool.blockStore, evidence.Height())
  64. if err != nil {
  65. return nil, err
  66. }
  67. commonVals, err := evpool.stateDB.LoadValidators(evidence.Height())
  68. if err != nil {
  69. return nil, err
  70. }
  71. trustedHeader := commonHeader
  72. // in the case of lunatic the trusted header is different to the common header
  73. if evidence.Height() != ev.ConflictingBlock.Height {
  74. trustedHeader, err = getSignedHeader(evpool.blockStore, ev.ConflictingBlock.Height)
  75. if err != nil {
  76. return nil, err
  77. }
  78. }
  79. err = VerifyLightClientAttack(ev, commonHeader, trustedHeader, commonVals, state.LastBlockTime,
  80. state.ConsensusParams.Evidence.MaxAgeDuration)
  81. if err != nil {
  82. return nil, err
  83. }
  84. // find out what type of attack this was and thus extract the malicious validators. Note in the case of an
  85. // Amnesia attack we don't have any malicious validators.
  86. validators, attackType := getMaliciousValidators(ev, commonVals, trustedHeader)
  87. totalVotingPower := ev.ConflictingBlock.ValidatorSet.TotalVotingPower()
  88. if attackType == lunaticType {
  89. totalVotingPower = commonVals.TotalVotingPower()
  90. }
  91. return &info{
  92. Evidence: evidence,
  93. Time: evTime,
  94. Validators: validators,
  95. TotalVotingPower: totalVotingPower,
  96. }, nil
  97. default:
  98. return nil, fmt.Errorf("unrecognized evidence type: %T", evidence)
  99. }
  100. }
  101. // VerifyLightClientAttack verifies LightClientAttackEvidence against the state of the full node. This involves
  102. // the following checks:
  103. // - the common header from the full node has at least 1/3 voting power which is also present in
  104. // the conflicting header's commit
  105. // - the nodes trusted header at the same height as the conflicting header has a different hash
  106. func VerifyLightClientAttack(e *types.LightClientAttackEvidence, commonHeader, trustedHeader *types.SignedHeader,
  107. commonVals *types.ValidatorSet, now time.Time, trustPeriod time.Duration) error {
  108. // In the case of lunatic attack we need to perform a single verification jump between the
  109. // common header and the conflicting one
  110. if commonHeader.Height != trustedHeader.Height {
  111. err := light.Verify(commonHeader, commonVals, e.ConflictingBlock.SignedHeader, e.ConflictingBlock.ValidatorSet,
  112. trustPeriod, now, 0*time.Second, light.DefaultTrustLevel)
  113. if err != nil {
  114. return fmt.Errorf("skipping verification from common to conflicting header failed: %w", err)
  115. }
  116. } else {
  117. // in the case of equivocation and amnesia we expect some header hashes to be correctly derived
  118. if isInvalidHeader(trustedHeader.Header, e.ConflictingBlock.Header) {
  119. return errors.New("common height is the same as conflicting block height so expected the conflicting" +
  120. " block to be correctly derived yet it wasn't")
  121. }
  122. // ensure that 2/3 of the validator set did vote for this block
  123. if err := e.ConflictingBlock.ValidatorSet.VerifyCommitLight(trustedHeader.ChainID, e.ConflictingBlock.Commit.BlockID,
  124. e.ConflictingBlock.Height, e.ConflictingBlock.Commit); err != nil {
  125. return fmt.Errorf("invalid commit from conflicting block: %w", err)
  126. }
  127. }
  128. if bytes.Equal(trustedHeader.Hash(), e.ConflictingBlock.Hash()) {
  129. return fmt.Errorf("trusted header hash matches the evidence conflicting header hash: %X",
  130. trustedHeader.Hash())
  131. }
  132. return nil
  133. }
  134. // VerifyDuplicateVote verifies DuplicateVoteEvidence against the state of full node. This involves the
  135. // following checks:
  136. // - the validator is in the validator set at the height of the evidence
  137. // - the height, round, type and validator address of the votes must be the same
  138. // - the block ID's must be different
  139. // - The signatures must both be valid
  140. func VerifyDuplicateVote(e *types.DuplicateVoteEvidence, chainID string, valSet *types.ValidatorSet) error {
  141. _, val := valSet.GetByAddress(e.VoteA.ValidatorAddress)
  142. if val == nil {
  143. return fmt.Errorf("address %X was not a validator at height %d", e.VoteA.ValidatorAddress, e.Height())
  144. }
  145. pubKey := val.PubKey
  146. // H/R/S must be the same
  147. if e.VoteA.Height != e.VoteB.Height ||
  148. e.VoteA.Round != e.VoteB.Round ||
  149. e.VoteA.Type != e.VoteB.Type {
  150. return fmt.Errorf("h/r/s does not match: %d/%d/%v vs %d/%d/%v",
  151. e.VoteA.Height, e.VoteA.Round, e.VoteA.Type,
  152. e.VoteB.Height, e.VoteB.Round, e.VoteB.Type)
  153. }
  154. // Address must be the same
  155. if !bytes.Equal(e.VoteA.ValidatorAddress, e.VoteB.ValidatorAddress) {
  156. return fmt.Errorf("validator addresses do not match: %X vs %X",
  157. e.VoteA.ValidatorAddress,
  158. e.VoteB.ValidatorAddress,
  159. )
  160. }
  161. // BlockIDs must be different
  162. if e.VoteA.BlockID.Equals(e.VoteB.BlockID) {
  163. return fmt.Errorf(
  164. "block IDs are the same (%v) - not a real duplicate vote",
  165. e.VoteA.BlockID,
  166. )
  167. }
  168. // pubkey must match address (this should already be true, sanity check)
  169. addr := e.VoteA.ValidatorAddress
  170. if !bytes.Equal(pubKey.Address(), addr) {
  171. return fmt.Errorf("address (%X) doesn't match pubkey (%v - %X)",
  172. addr, pubKey, pubKey.Address())
  173. }
  174. va := e.VoteA.ToProto()
  175. vb := e.VoteB.ToProto()
  176. // Signatures must be valid
  177. if !pubKey.VerifySignature(types.VoteSignBytes(chainID, va), e.VoteA.Signature) {
  178. return fmt.Errorf("verifying VoteA: %w", types.ErrVoteInvalidSignature)
  179. }
  180. if !pubKey.VerifySignature(types.VoteSignBytes(chainID, vb), e.VoteB.Signature) {
  181. return fmt.Errorf("verifying VoteB: %w", types.ErrVoteInvalidSignature)
  182. }
  183. return nil
  184. }
  185. func getSignedHeader(blockStore BlockStore, height int64) (*types.SignedHeader, error) {
  186. blockMeta := blockStore.LoadBlockMeta(height)
  187. if blockMeta == nil {
  188. return nil, fmt.Errorf("don't have header at height #%d", height)
  189. }
  190. commit := blockStore.LoadBlockCommit(height)
  191. if commit == nil {
  192. return nil, fmt.Errorf("don't have commit at height #%d", height)
  193. }
  194. return &types.SignedHeader{
  195. Header: &blockMeta.Header,
  196. Commit: commit,
  197. }, nil
  198. }
  199. // getMaliciousValidators finds out what style of attack LightClientAttackEvidence was and then works out who
  200. // the malicious validators were and returns them.
  201. func getMaliciousValidators(evidence *types.LightClientAttackEvidence, commonVals *types.ValidatorSet,
  202. trusted *types.SignedHeader) ([]*types.Validator, lightClientAttackType) {
  203. var validators []*types.Validator
  204. // First check if the header is invalid. This means that it is a lunatic attack and therefore we take the
  205. // validators who are in the commonVals and voted for the lunatic header
  206. if isInvalidHeader(trusted.Header, evidence.ConflictingBlock.Header) {
  207. for _, commitSig := range evidence.ConflictingBlock.Commit.Signatures {
  208. if !commitSig.ForBlock() {
  209. continue
  210. }
  211. _, val := commonVals.GetByAddress(commitSig.ValidatorAddress)
  212. if val == nil {
  213. // validator wasn't in the common validator set
  214. continue
  215. }
  216. validators = append(validators, val)
  217. }
  218. return validators, lunaticType
  219. // Next, check to see if it is an equivocation attack and both commits are in the same round. If this is the
  220. // case then we take the validators from the conflicting light block validator set that voted in both headers.
  221. } else if trusted.Commit.Round == evidence.ConflictingBlock.Commit.Round {
  222. // validator hashes are the same therefore the indexing order of validators are the same and thus we
  223. // only need a single loop to find the validators that voted twice.
  224. for i := 0; i < len(evidence.ConflictingBlock.Commit.Signatures); i++ {
  225. sigA := evidence.ConflictingBlock.Commit.Signatures[i]
  226. if sigA.Absent() {
  227. continue
  228. }
  229. sigB := trusted.Commit.Signatures[i]
  230. if sigB.Absent() {
  231. continue
  232. }
  233. _, val := evidence.ConflictingBlock.ValidatorSet.GetByAddress(sigA.ValidatorAddress)
  234. validators = append(validators, val)
  235. }
  236. return validators, equivocationType
  237. }
  238. // if the rounds are different then this is an amnesia attack. Unfortunately, given the nature of the attack,
  239. // we aren't able yet to deduce which are malicious validators and which are not hence we return an
  240. // empty validator set.
  241. return validators, amnesiaType
  242. }
  243. // isInvalidHeader takes a trusted header and matches it againt a conflicting header
  244. // to determine whether the conflicting header was the product of a valid state transition
  245. // or not. If it is then all the deterministic fields of the header should be the same.
  246. // If not, it is an invalid header and constitutes a lunatic attack.
  247. func isInvalidHeader(trusted, conflicting *types.Header) bool {
  248. return !bytes.Equal(trusted.ValidatorsHash, conflicting.ValidatorsHash) ||
  249. !bytes.Equal(trusted.NextValidatorsHash, conflicting.NextValidatorsHash) ||
  250. !bytes.Equal(trusted.ConsensusHash, conflicting.ConsensusHash) ||
  251. !bytes.Equal(trusted.AppHash, conflicting.AppHash) ||
  252. !bytes.Equal(trusted.LastResultsHash, conflicting.LastResultsHash)
  253. }
  254. type lightClientAttackType int
  255. const (
  256. lunaticType lightClientAttackType = iota + 1
  257. equivocationType
  258. amnesiaType
  259. )