github.com/DFWallet/tendermint-cosmos@v0.0.2/evidence/verify.go (about) 1 package evidence 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "time" 8 9 "github.com/DFWallet/tendermint-cosmos/light" 10 "github.com/DFWallet/tendermint-cosmos/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 // must check that the evidence has not expired (i.e. is outside the maximum age threshold) 112 func VerifyLightClientAttack(e *types.LightClientAttackEvidence, commonHeader, trustedHeader *types.SignedHeader, 113 commonVals *types.ValidatorSet, now time.Time, trustPeriod time.Duration) error { 114 // In the case of lunatic attack there will be a different commonHeader height. Therefore the node perform a single 115 // verification jump between the common header and the conflicting one 116 if commonHeader.Height != e.ConflictingBlock.Height { 117 err := commonVals.VerifyCommitLightTrusting(trustedHeader.ChainID, e.ConflictingBlock.Commit, light.DefaultTrustLevel) 118 if err != nil { 119 return fmt.Errorf("skipping verification of conflicting block failed: %w", err) 120 } 121 122 // In the case of equivocation and amnesia we expect all header hashes to be correctly derived 123 } else if e.ConflictingHeaderIsInvalid(trustedHeader.Header) { 124 return errors.New("common height is the same as conflicting block height so expected the conflicting" + 125 " block to be correctly derived yet it wasn't") 126 } 127 128 // Verify that the 2/3+ commits from the conflicting validator set were for the conflicting header 129 if err := e.ConflictingBlock.ValidatorSet.VerifyCommitLight(trustedHeader.ChainID, e.ConflictingBlock.Commit.BlockID, 130 e.ConflictingBlock.Height, e.ConflictingBlock.Commit); err != nil { 131 return fmt.Errorf("invalid commit from conflicting block: %w", err) 132 } 133 134 // Assert the correct amount of voting power of the validator set 135 if evTotal, valsTotal := e.TotalVotingPower, commonVals.TotalVotingPower(); evTotal != valsTotal { 136 return fmt.Errorf("total voting power from the evidence and our validator set does not match (%d != %d)", 137 evTotal, valsTotal) 138 } 139 140 // check in the case of a forward lunatic attack that monotonically increasing time has been violated 141 if e.ConflictingBlock.Height > trustedHeader.Height && e.ConflictingBlock.Time.After(trustedHeader.Time) { 142 return fmt.Errorf("conflicting block doesn't violate monotonically increasing time (%v is after %v)", 143 e.ConflictingBlock.Time, trustedHeader.Time, 144 ) 145 146 // In all other cases check that the hashes of the conflicting header and the trusted header are different 147 } else if bytes.Equal(trustedHeader.Hash(), e.ConflictingBlock.Hash()) { 148 return fmt.Errorf("trusted header hash matches the evidence's conflicting header hash: %X", 149 trustedHeader.Hash()) 150 } 151 152 return validateABCIEvidence(e, commonVals, trustedHeader) 153 } 154 155 // VerifyDuplicateVote verifies DuplicateVoteEvidence against the state of full node. This involves the 156 // following checks: 157 // - the validator is in the validator set at the height of the evidence 158 // - the height, round, type and validator address of the votes must be the same 159 // - the block ID's must be different 160 // - The signatures must both be valid 161 func VerifyDuplicateVote(e *types.DuplicateVoteEvidence, chainID string, valSet *types.ValidatorSet) error { 162 _, val := valSet.GetByAddress(e.VoteA.ValidatorAddress) 163 if val == nil { 164 return fmt.Errorf("address %X was not a validator at height %d", e.VoteA.ValidatorAddress, e.Height()) 165 } 166 pubKey := val.PubKey 167 168 // H/R/S must be the same 169 if e.VoteA.Height != e.VoteB.Height || 170 e.VoteA.Round != e.VoteB.Round || 171 e.VoteA.Type != e.VoteB.Type { 172 return fmt.Errorf("h/r/s does not match: %d/%d/%v vs %d/%d/%v", 173 e.VoteA.Height, e.VoteA.Round, e.VoteA.Type, 174 e.VoteB.Height, e.VoteB.Round, e.VoteB.Type) 175 } 176 177 // Address must be the same 178 if !bytes.Equal(e.VoteA.ValidatorAddress, e.VoteB.ValidatorAddress) { 179 return fmt.Errorf("validator addresses do not match: %X vs %X", 180 e.VoteA.ValidatorAddress, 181 e.VoteB.ValidatorAddress, 182 ) 183 } 184 185 // BlockIDs must be different 186 if e.VoteA.BlockID.Equals(e.VoteB.BlockID) { 187 return fmt.Errorf( 188 "block IDs are the same (%v) - not a real duplicate vote", 189 e.VoteA.BlockID, 190 ) 191 } 192 193 // pubkey must match address (this should already be true, sanity check) 194 addr := e.VoteA.ValidatorAddress 195 if !bytes.Equal(pubKey.Address(), addr) { 196 return fmt.Errorf("address (%X) doesn't match pubkey (%v - %X)", 197 addr, pubKey, pubKey.Address()) 198 } 199 200 // validator voting power and total voting power must match 201 if val.VotingPower != e.ValidatorPower { 202 return fmt.Errorf("validator power from evidence and our validator set does not match (%d != %d)", 203 e.ValidatorPower, val.VotingPower) 204 } 205 if valSet.TotalVotingPower() != e.TotalVotingPower { 206 return fmt.Errorf("total voting power from the evidence and our validator set does not match (%d != %d)", 207 e.TotalVotingPower, valSet.TotalVotingPower()) 208 } 209 210 va := e.VoteA.ToProto() 211 vb := e.VoteB.ToProto() 212 // Signatures must be valid 213 if !pubKey.VerifySignature(types.VoteSignBytes(chainID, va), e.VoteA.Signature) { 214 return fmt.Errorf("verifying VoteA: %w", types.ErrVoteInvalidSignature) 215 } 216 if !pubKey.VerifySignature(types.VoteSignBytes(chainID, vb), e.VoteB.Signature) { 217 return fmt.Errorf("verifying VoteB: %w", types.ErrVoteInvalidSignature) 218 } 219 220 return nil 221 } 222 223 // validateABCIEvidence validates the ABCI component of the light client attack 224 // evidence i.e voting power and byzantine validators 225 func validateABCIEvidence( 226 ev *types.LightClientAttackEvidence, 227 commonVals *types.ValidatorSet, 228 trustedHeader *types.SignedHeader, 229 ) error { 230 if evTotal, valsTotal := ev.TotalVotingPower, commonVals.TotalVotingPower(); evTotal != valsTotal { 231 return fmt.Errorf("total voting power from the evidence and our validator set does not match (%d != %d)", 232 evTotal, valsTotal) 233 } 234 235 // Find out what type of attack this was and thus extract the malicious 236 // validators. Note, in the case of an Amnesia attack we don't have any 237 // malicious validators. 238 validators := ev.GetByzantineValidators(commonVals, trustedHeader) 239 240 // Ensure this matches the validators that are listed in the evidence. They 241 // should be ordered based on power. 242 if validators == nil && ev.ByzantineValidators != nil { 243 return fmt.Errorf( 244 "expected nil validators from an amnesia light client attack but got %d", 245 len(ev.ByzantineValidators), 246 ) 247 } 248 249 if exp, got := len(validators), len(ev.ByzantineValidators); exp != got { 250 return fmt.Errorf("expected %d byzantine validators from evidence but got %d", exp, got) 251 } 252 253 for idx, val := range validators { 254 if !bytes.Equal(ev.ByzantineValidators[idx].Address, val.Address) { 255 return fmt.Errorf( 256 "evidence contained an unexpected byzantine validator address; expected: %v, got: %v", 257 val.Address, ev.ByzantineValidators[idx].Address, 258 ) 259 } 260 261 if ev.ByzantineValidators[idx].VotingPower != val.VotingPower { 262 return fmt.Errorf( 263 "evidence contained unexpected byzantine validator power; expected %d, got %d", 264 val.VotingPower, ev.ByzantineValidators[idx].VotingPower, 265 ) 266 } 267 } 268 269 return nil 270 } 271 272 func getSignedHeader(blockStore BlockStore, height int64) (*types.SignedHeader, error) { 273 blockMeta := blockStore.LoadBlockMeta(height) 274 if blockMeta == nil { 275 return nil, fmt.Errorf("don't have header at height #%d", height) 276 } 277 commit := blockStore.LoadBlockCommit(height) 278 if commit == nil { 279 return nil, fmt.Errorf("don't have commit at height #%d", height) 280 } 281 return &types.SignedHeader{ 282 Header: &blockMeta.Header, 283 Commit: commit, 284 }, nil 285 }