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.

277 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. 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. // FIXME: This multi step process is a bit unergonomic. We may want to consider a more efficient process
  68. // that doesn't require as much io and is atomic.
  69. // If the node doesn't have a block at the height of the conflicting block, then this could be
  70. // a forward lunatic attack. Thus the node must get the latest height it has
  71. latestHeight := evpool.blockStore.Height()
  72. trustedHeader, err = getSignedHeader(evpool.blockStore, latestHeight)
  73. if err != nil {
  74. return err
  75. }
  76. if trustedHeader.Time.Before(ev.ConflictingBlock.Time) {
  77. return fmt.Errorf("latest block time (%v) is before conflicting block time (%v)",
  78. trustedHeader.Time, ev.ConflictingBlock.Time,
  79. )
  80. }
  81. }
  82. }
  83. err = VerifyLightClientAttack(ev, commonHeader, trustedHeader, commonVals, state.LastBlockTime,
  84. state.ConsensusParams.Evidence.MaxAgeDuration)
  85. if err != nil {
  86. return err
  87. }
  88. // find out what type of attack this was and thus extract the malicious validators. Note in the case of an
  89. // Amnesia attack we don't have any malicious validators.
  90. validators := ev.GetByzantineValidators(commonVals, trustedHeader)
  91. // ensure this matches the validators that are listed in the evidence. They should be ordered based on power.
  92. if validators == nil && ev.ByzantineValidators != nil {
  93. return fmt.Errorf("expected nil validators from an amnesia light client attack but got %d",
  94. len(ev.ByzantineValidators))
  95. }
  96. if exp, got := len(validators), len(ev.ByzantineValidators); exp != got {
  97. return fmt.Errorf("expected %d byzantine validators from evidence but got %d",
  98. exp, got)
  99. }
  100. // ensure that both validator arrays are in the same order
  101. sort.Sort(types.ValidatorsByVotingPower(ev.ByzantineValidators))
  102. for idx, val := range validators {
  103. if !bytes.Equal(ev.ByzantineValidators[idx].Address, val.Address) {
  104. return fmt.Errorf("evidence contained a different byzantine validator address to the one we were expecting."+
  105. "Expected %v, got %v", val.Address, ev.ByzantineValidators[idx].Address)
  106. }
  107. if ev.ByzantineValidators[idx].VotingPower != val.VotingPower {
  108. return fmt.Errorf("evidence contained a byzantine validator with a different power to the one we were expecting."+
  109. "Expected %d, got %d", val.VotingPower, ev.ByzantineValidators[idx].VotingPower)
  110. }
  111. }
  112. return nil
  113. default:
  114. return fmt.Errorf("unrecognized evidence type: %T", evidence)
  115. }
  116. }
  117. // VerifyLightClientAttack verifies LightClientAttackEvidence against the state of the full node. This involves
  118. // the following checks:
  119. // - the common header from the full node has at least 1/3 voting power which is also present in
  120. // the conflicting header's commit
  121. // - 2/3+ of the conflicting validator set correctly signed the conflicting block
  122. // - the nodes trusted header at the same height as the conflicting header has a different hash
  123. //
  124. // CONTRACT: must run ValidateBasic() on the evidence before verifying
  125. // must check that the evidence has not expired (i.e. is outside the maximum age threshold)
  126. func VerifyLightClientAttack(e *types.LightClientAttackEvidence, commonHeader, trustedHeader *types.SignedHeader,
  127. commonVals *types.ValidatorSet, now time.Time, trustPeriod time.Duration) error {
  128. // In the case of lunatic attack there will be a different commonHeader height. Therefore the node perform a single
  129. // verification jump between the common header and the conflicting one
  130. if commonHeader.Height != e.ConflictingBlock.Height {
  131. err := commonVals.VerifyCommitLightTrusting(trustedHeader.ChainID, e.ConflictingBlock.Commit, light.DefaultTrustLevel)
  132. if err != nil {
  133. return fmt.Errorf("skipping verification of conflicting block failed: %w", err)
  134. }
  135. // In the case of equivocation and amnesia we expect all header hashes to be correctly derived
  136. } else if isInvalidHeader(trustedHeader.Header, e.ConflictingBlock.Header) {
  137. return errors.New("common height is the same as conflicting block height so expected the conflicting" +
  138. " block to be correctly derived yet it wasn't")
  139. }
  140. // Verify that the 2/3+ commits from the conflicting validator set were for the conflicting header
  141. if err := e.ConflictingBlock.ValidatorSet.VerifyCommitLight(trustedHeader.ChainID, e.ConflictingBlock.Commit.BlockID,
  142. e.ConflictingBlock.Height, e.ConflictingBlock.Commit); err != nil {
  143. return fmt.Errorf("invalid commit from conflicting block: %w", err)
  144. }
  145. // Assert the correct amount of voting power of the validator set
  146. if evTotal, valsTotal := e.TotalVotingPower, commonVals.TotalVotingPower(); evTotal != valsTotal {
  147. return fmt.Errorf("total voting power from the evidence and our validator set does not match (%d != %d)",
  148. evTotal, valsTotal)
  149. }
  150. // check in the case of a forward lunatic attack that monotonically increasing time has been violated
  151. if e.ConflictingBlock.Height > trustedHeader.Height && e.ConflictingBlock.Time.After(trustedHeader.Time) {
  152. return fmt.Errorf("conflicting block doesn't violate monotonically increasing time (%v is after %v)",
  153. e.ConflictingBlock.Time, trustedHeader.Time,
  154. )
  155. // In all other cases check that the hashes of the conflicting header and the trusted header are different
  156. } else if bytes.Equal(trustedHeader.Hash(), e.ConflictingBlock.Hash()) {
  157. return fmt.Errorf("trusted header hash matches the evidence's conflicting header hash: %X",
  158. trustedHeader.Hash())
  159. }
  160. return nil
  161. }
  162. // VerifyDuplicateVote verifies DuplicateVoteEvidence against the state of full node. This involves the
  163. // following checks:
  164. // - the validator is in the validator set at the height of the evidence
  165. // - the height, round, type and validator address of the votes must be the same
  166. // - the block ID's must be different
  167. // - The signatures must both be valid
  168. func VerifyDuplicateVote(e *types.DuplicateVoteEvidence, chainID string, valSet *types.ValidatorSet) error {
  169. _, val := valSet.GetByAddress(e.VoteA.ValidatorAddress)
  170. if val == nil {
  171. return fmt.Errorf("address %X was not a validator at height %d", e.VoteA.ValidatorAddress, e.Height())
  172. }
  173. pubKey := val.PubKey
  174. // H/R/S must be the same
  175. if e.VoteA.Height != e.VoteB.Height ||
  176. e.VoteA.Round != e.VoteB.Round ||
  177. e.VoteA.Type != e.VoteB.Type {
  178. return fmt.Errorf("h/r/s does not match: %d/%d/%v vs %d/%d/%v",
  179. e.VoteA.Height, e.VoteA.Round, e.VoteA.Type,
  180. e.VoteB.Height, e.VoteB.Round, e.VoteB.Type)
  181. }
  182. // Address must be the same
  183. if !bytes.Equal(e.VoteA.ValidatorAddress, e.VoteB.ValidatorAddress) {
  184. return fmt.Errorf("validator addresses do not match: %X vs %X",
  185. e.VoteA.ValidatorAddress,
  186. e.VoteB.ValidatorAddress,
  187. )
  188. }
  189. // BlockIDs must be different
  190. if e.VoteA.BlockID.Equals(e.VoteB.BlockID) {
  191. return fmt.Errorf(
  192. "block IDs are the same (%v) - not a real duplicate vote",
  193. e.VoteA.BlockID,
  194. )
  195. }
  196. // pubkey must match address (this should already be true, sanity check)
  197. addr := e.VoteA.ValidatorAddress
  198. if !bytes.Equal(pubKey.Address(), addr) {
  199. return fmt.Errorf("address (%X) doesn't match pubkey (%v - %X)",
  200. addr, pubKey, pubKey.Address())
  201. }
  202. // validator voting power and total voting power must match
  203. if val.VotingPower != e.ValidatorPower {
  204. return fmt.Errorf("validator power from evidence and our validator set does not match (%d != %d)",
  205. e.ValidatorPower, val.VotingPower)
  206. }
  207. if valSet.TotalVotingPower() != e.TotalVotingPower {
  208. return fmt.Errorf("total voting power from the evidence and our validator set does not match (%d != %d)",
  209. e.TotalVotingPower, valSet.TotalVotingPower())
  210. }
  211. va := e.VoteA.ToProto()
  212. vb := e.VoteB.ToProto()
  213. // Signatures must be valid
  214. if !pubKey.VerifySignature(types.VoteSignBytes(chainID, va), e.VoteA.Signature) {
  215. return fmt.Errorf("verifying VoteA: %w", types.ErrVoteInvalidSignature)
  216. }
  217. if !pubKey.VerifySignature(types.VoteSignBytes(chainID, vb), e.VoteB.Signature) {
  218. return fmt.Errorf("verifying VoteB: %w", types.ErrVoteInvalidSignature)
  219. }
  220. return nil
  221. }
  222. func getSignedHeader(blockStore BlockStore, height int64) (*types.SignedHeader, error) {
  223. blockMeta := blockStore.LoadBlockMeta(height)
  224. if blockMeta == nil {
  225. return nil, fmt.Errorf("don't have header at height #%d", height)
  226. }
  227. commit := blockStore.LoadBlockCommit(height)
  228. if commit == nil {
  229. return nil, fmt.Errorf("don't have commit at height #%d", height)
  230. }
  231. return &types.SignedHeader{
  232. Header: &blockMeta.Header,
  233. Commit: commit,
  234. }, nil
  235. }
  236. // isInvalidHeader takes a trusted header and matches it againt a conflicting header
  237. // to determine whether the conflicting header was the product of a valid state transition
  238. // or not. If it is then all the deterministic fields of the header should be the same.
  239. // If not, it is an invalid header and constitutes a lunatic attack.
  240. func isInvalidHeader(trusted, conflicting *types.Header) bool {
  241. return !bytes.Equal(trusted.ValidatorsHash, conflicting.ValidatorsHash) ||
  242. !bytes.Equal(trusted.NextValidatorsHash, conflicting.NextValidatorsHash) ||
  243. !bytes.Equal(trusted.ConsensusHash, conflicting.ConsensusHash) ||
  244. !bytes.Equal(trusted.AppHash, conflicting.AppHash) ||
  245. !bytes.Equal(trusted.LastResultsHash, conflicting.LastResultsHash)
  246. }