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