@ -9,6 +9,7 @@ import (
"github.com/pkg/errors"
"github.com/tendermint/tendermint/crypto/tmhash"
tmmath "github.com/tendermint/tendermint/libs/math"
amino "github.com/tendermint/go-amino"
@ -19,6 +20,14 @@ import (
const (
// MaxEvidenceBytes is a maximum size of any evidence (including amino overhead).
MaxEvidenceBytes int64 = 484
// An invalid field in the header from LunaticValidatorEvidence.
// Must be a function of the ABCI application state.
ValidatorsHashField = "ValidatorsHash"
NextValidatorsHashField = "NextValidatorsHash"
ConsensusHashField = "ConsensusHash"
AppHashField = "AppHash"
LastResultsHashField = "LastResultsHash"
)
// ErrEvidenceInvalid wraps a piece of evidence and the error denoting how or why it is invalid.
@ -55,7 +64,7 @@ func (err *ErrEvidenceOverflow) Error() string {
//-------------------------------------------
// Evidence represents any provable malicious activity by a validator
// Evidence represents any provable malicious activity by a validator.
type Evidence interface {
Height ( ) int64 // height of the equivocation
Time ( ) time . Time // time of the equivocation
@ -69,9 +78,18 @@ type Evidence interface {
String ( ) string
}
type CompositeEvidence interface {
VerifyComposite ( committedHeader * Header , valSet * ValidatorSet ) error
Split ( committedHeader * Header , valSet * ValidatorSet , valToLastHeight map [ string ] int64 ) [ ] Evidence
}
func RegisterEvidences ( cdc * amino . Codec ) {
cdc . RegisterInterface ( ( * Evidence ) ( nil ) , nil )
cdc . RegisterConcrete ( & DuplicateVoteEvidence { } , "tendermint/DuplicateVoteEvidence" , nil )
cdc . RegisterConcrete ( & ConflictingHeadersEvidence { } , "tendermint/ConflictingHeadersEvidence" , nil )
cdc . RegisterConcrete ( & PhantomValidatorEvidence { } , "tendermint/PhantomValidatorEvidence" , nil )
cdc . RegisterConcrete ( & LunaticValidatorEvidence { } , "tendermint/LunaticValidatorEvidence" , nil )
cdc . RegisterConcrete ( & PotentialAmnesiaEvidence { } , "tendermint/PotentialAmnesiaEvidence" , nil )
}
func RegisterMockEvidences ( cdc * amino . Codec ) {
@ -129,7 +147,7 @@ func NewDuplicateVoteEvidence(pubkey crypto.PubKey, vote1 *Vote, vote2 *Vote) *D
// String returns a string representation of the evidence.
func ( dve * DuplicateVoteEvidence ) String ( ) string {
return fmt . Sprintf ( "VoteA: %v; VoteB: %v " , dve . VoteA , dve . VoteB )
return fmt . Sprintf ( "DuplicateVoteEvidence{VoteA: %v, VoteB: %v} " , dve . VoteA , dve . VoteB )
}
@ -138,7 +156,7 @@ func (dve *DuplicateVoteEvidence) Height() int64 {
return dve . VoteA . Height
}
// Time return the time the evidence was created
// Time returns the time the evidence was created.
func ( dve * DuplicateVoteEvidence ) Time ( ) time . Time {
return dve . VoteA . Timestamp
}
@ -159,19 +177,22 @@ func (dve *DuplicateVoteEvidence) Hash() []byte {
}
// Verify returns an error if the two votes aren't conflicting.
// To be conflicting, they must be from the same validator, for the same H/R/S, but for different blocks.
//
// To be conflicting, they must be from the same validator, for the same H/R/S,
// but for different blocks.
func ( dve * DuplicateVoteEvidence ) Verify ( chainID string , pubKey crypto . PubKey ) error {
// H/R/S must be the same
if dve . VoteA . Height != dve . VoteB . Height ||
dve . VoteA . Round != dve . VoteB . Round ||
dve . VoteA . Type != dve . VoteB . Type {
return fmt . Errorf ( "duplicateVoteEvidence Error: H/R/S does not match. Got %v and %v" , dve . VoteA , dve . VoteB )
return fmt . Errorf ( "h/r/s does not match: %d/%d/%v vs %d/%d/%v" ,
dve . VoteA . Height , dve . VoteA . Round , dve . VoteA . Type ,
dve . VoteB . Height , dve . VoteB . Round , dve . VoteB . Type )
}
// Address must be the same
if ! bytes . Equal ( dve . VoteA . ValidatorAddress , dve . VoteB . ValidatorAddress ) {
return fmt . Errorf (
"duplicateVoteEvidence Error: Validator addresses do not match. Got %X and %X" ,
return fmt . Errorf ( "validator addresses do not match: %X vs %X" ,
dve . VoteA . ValidatorAddress ,
dve . VoteB . ValidatorAddress ,
)
@ -180,7 +201,7 @@ func (dve *DuplicateVoteEvidence) Verify(chainID string, pubKey crypto.PubKey) e
// Index must be the same
if dve . VoteA . ValidatorIndex != dve . VoteB . ValidatorIndex {
return fmt . Errorf (
"duplicateVoteEvidence Error: Validator indices do not match. Got %d and %d" ,
"validator indices do not match: %d and %d" ,
dve . VoteA . ValidatorIndex ,
dve . VoteB . ValidatorIndex ,
)
@ -189,7 +210,7 @@ func (dve *DuplicateVoteEvidence) Verify(chainID string, pubKey crypto.PubKey) e
// BlockIDs must be different
if dve . VoteA . BlockID . Equals ( dve . VoteB . BlockID ) {
return fmt . Errorf (
"duplicateVoteEvidence Error: Block IDs are the same (%v) - not a real duplicate vote" ,
"block IDs are the same (%v) - not a real duplicate vote" ,
dve . VoteA . BlockID ,
)
}
@ -197,16 +218,16 @@ func (dve *DuplicateVoteEvidence) Verify(chainID string, pubKey crypto.PubKey) e
// pubkey must match address (this should already be true, sanity check)
addr := dve . VoteA . ValidatorAddress
if ! bytes . Equal ( pubKey . Address ( ) , addr ) {
return fmt . Errorf ( "duplicateVoteEvidence FAILED SANITY CHECK - address (%X) doesn't match pubkey (%v - %X)" ,
return fmt . Errorf ( "address (%X) doesn't match pubkey (%v - %X)" ,
addr , pubKey , pubKey . Address ( ) )
}
// Signatures must be valid
if ! pubKey . VerifyBytes ( dve . VoteA . SignBytes ( chainID ) , dve . VoteA . Signature ) {
return fmt . Errorf ( "duplicateVoteEvidence Error verifying VoteA: %v " , ErrVoteInvalidSignature )
return fmt . Errorf ( "verifying VoteA: %w " , ErrVoteInvalidSignature )
}
if ! pubKey . VerifyBytes ( dve . VoteB . SignBytes ( chainID ) , dve . VoteB . Signature ) {
return fmt . Errorf ( "duplicateVoteEvidence Error verifying VoteB: %v " , ErrVoteInvalidSignature )
return fmt . Errorf ( "verifying VoteB: %w " , ErrVoteInvalidSignature )
}
return nil
@ -233,10 +254,10 @@ func (dve *DuplicateVoteEvidence) ValidateBasic() error {
return fmt . Errorf ( "one or both of the votes are empty %v, %v" , dve . VoteA , dve . VoteB )
}
if err := dve . VoteA . ValidateBasic ( ) ; err != nil {
return fmt . Errorf ( "invalid VoteA: %v " , err )
return fmt . Errorf ( "invalid VoteA: %w " , err )
}
if err := dve . VoteB . ValidateBasic ( ) ; err != nil {
return fmt . Errorf ( "invalid VoteB: %v " , err )
return fmt . Errorf ( "invalid VoteB: %w " , err )
}
// Enforce Votes are lexicographically sorted on blockID
if strings . Compare ( dve . VoteA . BlockID . Key ( ) , dve . VoteB . BlockID . Key ( ) ) >= 0 {
@ -245,6 +266,663 @@ func (dve *DuplicateVoteEvidence) ValidateBasic() error {
return nil
}
//-------------------------------------------
// EvidenceList is a list of Evidence. Evidences is not a word.
type EvidenceList [ ] Evidence
// Hash returns the simple merkle root hash of the EvidenceList.
func ( evl EvidenceList ) Hash ( ) [ ] byte {
// These allocations are required because Evidence is not of type Bytes, and
// golang slices can't be typed cast. This shouldn't be a performance problem since
// the Evidence size is capped.
evidenceBzs := make ( [ ] [ ] byte , len ( evl ) )
for i := 0 ; i < len ( evl ) ; i ++ {
evidenceBzs [ i ] = evl [ i ] . Bytes ( )
}
return merkle . SimpleHashFromByteSlices ( evidenceBzs )
}
func ( evl EvidenceList ) String ( ) string {
s := ""
for _ , e := range evl {
s += fmt . Sprintf ( "%s\t\t" , e )
}
return s
}
// Has returns true if the evidence is in the EvidenceList.
func ( evl EvidenceList ) Has ( evidence Evidence ) bool {
for _ , ev := range evl {
if ev . Equal ( evidence ) {
return true
}
}
return false
}
//-------------------------------------------
// ConflictingHeadersEvidence is primarily used by the light client when it
// observes two conflicting headers, both having 1/3+ of the voting power of
// the currently trusted validator set.
type ConflictingHeadersEvidence struct {
H1 * SignedHeader ` json:"h_1" `
H2 * SignedHeader ` json:"h_2" `
}
var _ Evidence = & ConflictingHeadersEvidence { }
var _ CompositeEvidence = & ConflictingHeadersEvidence { }
var _ Evidence = ConflictingHeadersEvidence { }
var _ CompositeEvidence = ConflictingHeadersEvidence { }
// Split breaks up eviddence into smaller chunks (one per validator except for
// PotentialAmnesiaEvidence): PhantomValidatorEvidence,
// LunaticValidatorEvidence, DuplicateVoteEvidence and
// PotentialAmnesiaEvidence.
//
// committedHeader - header at height H1.Height == H2.Height
// valSet - validator set at height H1.Height == H2.Height
// valToLastHeight - map between active validators and respective last heights
func ( ev ConflictingHeadersEvidence ) Split ( committedHeader * Header , valSet * ValidatorSet ,
valToLastHeight map [ string ] int64 ) [ ] Evidence {
evList := make ( [ ] Evidence , 0 )
var alternativeHeader * SignedHeader
if bytes . Equal ( committedHeader . Hash ( ) , ev . H1 . Hash ( ) ) {
alternativeHeader = ev . H2
} else {
alternativeHeader = ev . H1
}
// If there are signers(alternativeHeader) that are not part of
// validators(committedHeader), they misbehaved as they are signing protocol
// messages in heights they are not validators => immediately slashable
// (#F4).
for i , sig := range alternativeHeader . Commit . Signatures {
if sig . Absent ( ) {
continue
}
lastHeightValidatorWasInSet , ok := valToLastHeight [ string ( sig . ValidatorAddress ) ]
if ! ok {
continue
}
if ! valSet . HasAddress ( sig . ValidatorAddress ) {
evList = append ( evList , & PhantomValidatorEvidence {
Header : alternativeHeader . Header ,
Vote : alternativeHeader . Commit . GetVote ( i ) ,
LastHeightValidatorWasInSet : lastHeightValidatorWasInSet ,
} )
}
}
// If ValidatorsHash, NextValidatorsHash, ConsensusHash, AppHash, and
// LastResultsHash in alternativeHeader are different (incorrect application
// state transition), then it is a lunatic misbehavior => immediately
// slashable (#F5).
var invalidField string
switch {
case ! bytes . Equal ( committedHeader . ValidatorsHash , alternativeHeader . ValidatorsHash ) :
invalidField = "ValidatorsHash"
case ! bytes . Equal ( committedHeader . NextValidatorsHash , alternativeHeader . NextValidatorsHash ) :
invalidField = "NextValidatorsHash"
case ! bytes . Equal ( committedHeader . ConsensusHash , alternativeHeader . ConsensusHash ) :
invalidField = "ConsensusHash"
case ! bytes . Equal ( committedHeader . AppHash , alternativeHeader . AppHash ) :
invalidField = "AppHash"
case ! bytes . Equal ( committedHeader . LastResultsHash , alternativeHeader . LastResultsHash ) :
invalidField = "LastResultsHash"
}
if invalidField != "" {
for i , sig := range alternativeHeader . Commit . Signatures {
if sig . Absent ( ) {
continue
}
evList = append ( evList , & LunaticValidatorEvidence {
Header : alternativeHeader . Header ,
Vote : alternativeHeader . Commit . GetVote ( i ) ,
InvalidHeaderField : invalidField ,
} )
}
return evList
}
// Use the fact that signatures are sorted by ValidatorAddress.
var (
i = 0
j = 0
)
OUTER_LOOP :
for i < len ( ev . H1 . Commit . Signatures ) {
sigA := ev . H1 . Commit . Signatures [ i ]
if sigA . Absent ( ) {
i ++
continue
}
// FIXME: Replace with HasAddress once DuplicateVoteEvidence#PubKey is
// removed.
_ , val := valSet . GetByAddress ( sigA . ValidatorAddress )
if val == nil {
i ++
continue
}
for j < len ( ev . H2 . Commit . Signatures ) {
sigB := ev . H2 . Commit . Signatures [ j ]
if sigB . Absent ( ) {
j ++
continue
}
switch bytes . Compare ( sigA . ValidatorAddress , sigB . ValidatorAddress ) {
case 0 :
// if H1.Round == H2.Round, and some signers signed different precommit
// messages in both commits, then it is an equivocation misbehavior =>
// immediately slashable (#F1).
if ev . H1 . Commit . Round == ev . H2 . Commit . Round {
evList = append ( evList , & DuplicateVoteEvidence {
PubKey : val . PubKey ,
VoteA : ev . H1 . Commit . GetVote ( i ) ,
VoteB : ev . H2 . Commit . GetVote ( j ) ,
} )
} else {
// if H1.Round != H2.Round we need to run full detection procedure => not
// immediately slashable.
evList = append ( evList , & PotentialAmnesiaEvidence {
VoteA : ev . H1 . Commit . GetVote ( i ) ,
VoteB : ev . H2 . Commit . GetVote ( j ) ,
} )
}
i ++
j ++
continue OUTER_LOOP
case 1 :
i ++
continue OUTER_LOOP
case - 1 :
j ++
}
}
}
return evList
}
func ( ev ConflictingHeadersEvidence ) Height ( ) int64 { return ev . H1 . Height }
// XXX: this is not the time of equivocation
func ( ev ConflictingHeadersEvidence ) Time ( ) time . Time { return ev . H1 . Time }
func ( ev ConflictingHeadersEvidence ) Address ( ) [ ] byte {
panic ( "use ConflictingHeadersEvidence#Split to split evidence into individual pieces" )
}
func ( ev ConflictingHeadersEvidence ) Bytes ( ) [ ] byte {
return cdcEncode ( ev )
}
func ( ev ConflictingHeadersEvidence ) Hash ( ) [ ] byte {
bz := make ( [ ] byte , tmhash . Size * 2 )
copy ( bz [ : tmhash . Size - 1 ] , ev . H1 . Hash ( ) . Bytes ( ) )
copy ( bz [ tmhash . Size : ] , ev . H2 . Hash ( ) . Bytes ( ) )
return tmhash . Sum ( bz )
}
func ( ev ConflictingHeadersEvidence ) Verify ( chainID string , _ crypto . PubKey ) error {
panic ( "use ConflictingHeadersEvidence#VerifyComposite to verify composite evidence" )
}
// VerifyComposite verifies that both headers belong to the same chain, same
// height and signed by 1/3+ of validators at height H1.Height == H2.Height.
func ( ev ConflictingHeadersEvidence ) VerifyComposite ( committedHeader * Header , valSet * ValidatorSet ) error {
var alternativeHeader * SignedHeader
switch {
case bytes . Equal ( committedHeader . Hash ( ) , ev . H1 . Hash ( ) ) :
alternativeHeader = ev . H2
case bytes . Equal ( committedHeader . Hash ( ) , ev . H2 . Hash ( ) ) :
alternativeHeader = ev . H1
default :
return errors . New ( "none of the headers are committed from this node's perspective" )
}
// ChainID must be the same
if committedHeader . ChainID != alternativeHeader . ChainID {
return errors . New ( "alt header is from a different chain" )
}
// Height must be the same
if committedHeader . Height != alternativeHeader . Height {
return errors . New ( "alt header is from a different height" )
}
// Limit the number of signatures to avoid DoS attacks where a header
// contains too many signatures.
//
// Validator set size = 100 [node]
// Max validator set size = 100 * 2 = 200 [fork?]
maxNumValidators := valSet . Size ( ) * 2
if len ( alternativeHeader . Commit . Signatures ) > maxNumValidators {
return errors . Errorf ( "alt commit contains too many signatures: %d, expected no more than %d" ,
len ( alternativeHeader . Commit . Signatures ) ,
maxNumValidators )
}
// Header must be signed by at least 1/3+ of voting power of currently
// trusted validator set.
if err := valSet . VerifyCommitTrusting (
alternativeHeader . ChainID ,
alternativeHeader . Commit . BlockID ,
alternativeHeader . Height ,
alternativeHeader . Commit ,
tmmath . Fraction { Numerator : 1 , Denominator : 3 } ) ; err != nil {
return errors . Wrap ( err , "alt header does not have 1/3+ of voting power of our validator set" )
}
return nil
}
func ( ev ConflictingHeadersEvidence ) Equal ( ev2 Evidence ) bool {
switch e2 := ev2 . ( type ) {
case ConflictingHeadersEvidence :
return bytes . Equal ( ev . H1 . Hash ( ) , e2 . H1 . Hash ( ) ) && bytes . Equal ( ev . H2 . Hash ( ) , e2 . H2 . Hash ( ) )
case * ConflictingHeadersEvidence :
return bytes . Equal ( ev . H1 . Hash ( ) , e2 . H1 . Hash ( ) ) && bytes . Equal ( ev . H2 . Hash ( ) , e2 . H2 . Hash ( ) )
default :
return false
}
}
func ( ev ConflictingHeadersEvidence ) ValidateBasic ( ) error {
if ev . H1 == nil {
return errors . New ( "first header is missing" )
}
if ev . H2 == nil {
return errors . New ( "second header is missing" )
}
if err := ev . H1 . ValidateBasic ( ev . H1 . ChainID ) ; err != nil {
return fmt . Errorf ( "h1: %w" , err )
}
if err := ev . H2 . ValidateBasic ( ev . H2 . ChainID ) ; err != nil {
return fmt . Errorf ( "h2: %w" , err )
}
return nil
}
func ( ev ConflictingHeadersEvidence ) String ( ) string {
return fmt . Sprintf ( "ConflictingHeadersEvidence{H1: %d#%X, H2: %d#%X}" ,
ev . H1 . Height , ev . H1 . Hash ( ) ,
ev . H2 . Height , ev . H2 . Hash ( ) )
}
//-------------------------------------------
type PhantomValidatorEvidence struct {
Header * Header ` json:"header" `
Vote * Vote ` json:"vote" `
LastHeightValidatorWasInSet int64 ` json:"last_height_validator_was_in_set" `
}
var _ Evidence = & PhantomValidatorEvidence { }
var _ Evidence = PhantomValidatorEvidence { }
func ( e PhantomValidatorEvidence ) Height ( ) int64 {
return e . Header . Height
}
func ( e PhantomValidatorEvidence ) Time ( ) time . Time {
return e . Header . Time
}
func ( e PhantomValidatorEvidence ) Address ( ) [ ] byte {
return e . Vote . ValidatorAddress
}
func ( e PhantomValidatorEvidence ) Hash ( ) [ ] byte {
bz := make ( [ ] byte , tmhash . Size + crypto . AddressSize )
copy ( bz [ : tmhash . Size - 1 ] , e . Header . Hash ( ) . Bytes ( ) )
copy ( bz [ tmhash . Size : ] , e . Vote . ValidatorAddress . Bytes ( ) )
return tmhash . Sum ( bz )
}
func ( e PhantomValidatorEvidence ) Bytes ( ) [ ] byte {
return cdcEncode ( e )
}
func ( e PhantomValidatorEvidence ) Verify ( chainID string , pubKey crypto . PubKey ) error {
// chainID must be the same
if chainID != e . Header . ChainID {
return fmt . Errorf ( "chainID do not match: %s vs %s" ,
chainID ,
e . Header . ChainID ,
)
}
if ! pubKey . VerifyBytes ( e . Vote . SignBytes ( chainID ) , e . Vote . Signature ) {
return errors . New ( "invalid signature" )
}
return nil
}
func ( e PhantomValidatorEvidence ) Equal ( ev Evidence ) bool {
switch e2 := ev . ( type ) {
case PhantomValidatorEvidence :
return bytes . Equal ( e . Header . Hash ( ) , e2 . Header . Hash ( ) ) &&
bytes . Equal ( e . Vote . ValidatorAddress , e2 . Vote . ValidatorAddress )
case * PhantomValidatorEvidence :
return bytes . Equal ( e . Header . Hash ( ) , e2 . Header . Hash ( ) ) &&
bytes . Equal ( e . Vote . ValidatorAddress , e2 . Vote . ValidatorAddress )
default :
return false
}
}
func ( e PhantomValidatorEvidence ) ValidateBasic ( ) error {
if e . Header == nil {
return errors . New ( "empty header" )
}
if e . Vote == nil {
return errors . New ( "empty vote" )
}
// if err := e.Header.ValidateBasic(); err != nil {
// return fmt.Errorf("invalid header: %v", err)
// }
if err := e . Vote . ValidateBasic ( ) ; err != nil {
return fmt . Errorf ( "invalid signature: %v" , err )
}
if ! e . Vote . BlockID . IsComplete ( ) {
return errors . New ( "expected vote for block" )
}
if e . Header . Height != e . Vote . Height {
return fmt . Errorf ( "header and vote have different heights: %d vs %d" ,
e . Header . Height ,
e . Vote . Height ,
)
}
if e . LastHeightValidatorWasInSet <= 0 {
return errors . New ( "negative or zero LastHeightValidatorWasInSet" )
}
return nil
}
func ( e PhantomValidatorEvidence ) String ( ) string {
return fmt . Sprintf ( "PhantomValidatorEvidence{%X voted for %d/%X}" ,
e . Vote . ValidatorAddress , e . Header . Height , e . Header . Hash ( ) )
}
//-------------------------------------------
type LunaticValidatorEvidence struct {
Header * Header ` json:"header" `
Vote * Vote ` json:"vote" `
InvalidHeaderField string ` json:"invalid_header_field" `
}
var _ Evidence = & LunaticValidatorEvidence { }
var _ Evidence = LunaticValidatorEvidence { }
func ( e LunaticValidatorEvidence ) Height ( ) int64 {
return e . Header . Height
}
func ( e LunaticValidatorEvidence ) Time ( ) time . Time {
return e . Header . Time
}
func ( e LunaticValidatorEvidence ) Address ( ) [ ] byte {
return e . Vote . ValidatorAddress
}
func ( e LunaticValidatorEvidence ) Hash ( ) [ ] byte {
bz := make ( [ ] byte , tmhash . Size + crypto . AddressSize )
copy ( bz [ : tmhash . Size - 1 ] , e . Header . Hash ( ) . Bytes ( ) )
copy ( bz [ tmhash . Size : ] , e . Vote . ValidatorAddress . Bytes ( ) )
return tmhash . Sum ( bz )
}
func ( e LunaticValidatorEvidence ) Bytes ( ) [ ] byte {
return cdcEncode ( e )
}
func ( e LunaticValidatorEvidence ) Verify ( chainID string , pubKey crypto . PubKey ) error {
// chainID must be the same
if chainID != e . Header . ChainID {
return fmt . Errorf ( "chainID do not match: %s vs %s" ,
chainID ,
e . Header . ChainID ,
)
}
if ! pubKey . VerifyBytes ( e . Vote . SignBytes ( chainID ) , e . Vote . Signature ) {
return errors . New ( "invalid signature" )
}
return nil
}
func ( e LunaticValidatorEvidence ) Equal ( ev Evidence ) bool {
switch e2 := ev . ( type ) {
case LunaticValidatorEvidence :
return bytes . Equal ( e . Header . Hash ( ) , e2 . Header . Hash ( ) ) &&
bytes . Equal ( e . Vote . ValidatorAddress , e2 . Vote . ValidatorAddress )
case * LunaticValidatorEvidence :
return bytes . Equal ( e . Header . Hash ( ) , e2 . Header . Hash ( ) ) &&
bytes . Equal ( e . Vote . ValidatorAddress , e2 . Vote . ValidatorAddress )
default :
return false
}
}
func ( e LunaticValidatorEvidence ) ValidateBasic ( ) error {
if e . Header == nil {
return errors . New ( "empty header" )
}
if e . Vote == nil {
return errors . New ( "empty vote" )
}
// if err := e.Header.ValidateBasic(); err != nil {
// return fmt.Errorf("invalid header: %v", err)
// }
if err := e . Vote . ValidateBasic ( ) ; err != nil {
return fmt . Errorf ( "invalid signature: %v" , err )
}
if ! e . Vote . BlockID . IsComplete ( ) {
return errors . New ( "expected vote for block" )
}
if e . Header . Height != e . Vote . Height {
return fmt . Errorf ( "header and vote have different heights: %d vs %d" ,
e . Header . Height ,
e . Vote . Height ,
)
}
switch e . InvalidHeaderField {
case "ValidatorsHash" , "NextValidatorsHash" , "ConsensusHash" , "AppHash" , "LastResultsHash" :
return nil
default :
return errors . New ( "unknown invalid header field" )
}
}
func ( e LunaticValidatorEvidence ) String ( ) string {
return fmt . Sprintf ( "LunaticValidatorEvidence{%X voted for %d/%X, which contains invalid %s}" ,
e . Vote . ValidatorAddress , e . Header . Height , e . Header . Hash ( ) , e . InvalidHeaderField )
}
func ( e LunaticValidatorEvidence ) VerifyHeader ( committedHeader * Header ) error {
matchErr := func ( field string ) error {
return fmt . Errorf ( "%s matches committed hash" , field )
}
switch e . InvalidHeaderField {
case ValidatorsHashField :
if bytes . Equal ( committedHeader . ValidatorsHash , e . Header . ValidatorsHash ) {
return matchErr ( ValidatorsHashField )
}
case NextValidatorsHashField :
if bytes . Equal ( committedHeader . NextValidatorsHash , e . Header . NextValidatorsHash ) {
return matchErr ( NextValidatorsHashField )
}
case ConsensusHashField :
if bytes . Equal ( committedHeader . ConsensusHash , e . Header . ConsensusHash ) {
return matchErr ( ConsensusHashField )
}
case AppHashField :
if bytes . Equal ( committedHeader . AppHash , e . Header . AppHash ) {
return matchErr ( AppHashField )
}
case LastResultsHashField :
if bytes . Equal ( committedHeader . LastResultsHash , e . Header . LastResultsHash ) {
return matchErr ( LastResultsHashField )
}
default :
return errors . New ( "unknown InvalidHeaderField" )
}
return nil
}
//-------------------------------------------
type PotentialAmnesiaEvidence struct {
VoteA * Vote ` json:"vote_a" `
VoteB * Vote ` json:"vote_b" `
}
var _ Evidence = & PotentialAmnesiaEvidence { }
var _ Evidence = PotentialAmnesiaEvidence { }
func ( e PotentialAmnesiaEvidence ) Height ( ) int64 {
return e . VoteA . Height
}
func ( e PotentialAmnesiaEvidence ) Time ( ) time . Time {
if e . VoteA . Timestamp . Before ( e . VoteB . Timestamp ) {
return e . VoteA . Timestamp
}
return e . VoteB . Timestamp
}
func ( e PotentialAmnesiaEvidence ) Address ( ) [ ] byte {
return e . VoteA . ValidatorAddress
}
func ( e PotentialAmnesiaEvidence ) Hash ( ) [ ] byte {
return tmhash . Sum ( cdcEncode ( e ) )
}
func ( e PotentialAmnesiaEvidence ) Bytes ( ) [ ] byte {
return cdcEncode ( e )
}
func ( e PotentialAmnesiaEvidence ) Verify ( chainID string , pubKey crypto . PubKey ) error {
// pubkey must match address (this should already be true, sanity check)
addr := e . VoteA . ValidatorAddress
if ! bytes . Equal ( pubKey . Address ( ) , addr ) {
return fmt . Errorf ( "address (%X) doesn't match pubkey (%v - %X)" ,
addr , pubKey , pubKey . Address ( ) )
}
// Signatures must be valid
if ! pubKey . VerifyBytes ( e . VoteA . SignBytes ( chainID ) , e . VoteA . Signature ) {
return fmt . Errorf ( "verifying VoteA: %w" , ErrVoteInvalidSignature )
}
if ! pubKey . VerifyBytes ( e . VoteB . SignBytes ( chainID ) , e . VoteB . Signature ) {
return fmt . Errorf ( "verifying VoteB: %w" , ErrVoteInvalidSignature )
}
return nil
}
func ( e PotentialAmnesiaEvidence ) Equal ( ev Evidence ) bool {
switch e2 := ev . ( type ) {
case PotentialAmnesiaEvidence :
return bytes . Equal ( e . Hash ( ) , e2 . Hash ( ) )
case * PotentialAmnesiaEvidence :
return bytes . Equal ( e . Hash ( ) , e2 . Hash ( ) )
default :
return false
}
}
func ( e PotentialAmnesiaEvidence ) ValidateBasic ( ) error {
if e . VoteA == nil || e . VoteB == nil {
return fmt . Errorf ( "one or both of the votes are empty %v, %v" , e . VoteA , e . VoteB )
}
if err := e . VoteA . ValidateBasic ( ) ; err != nil {
return fmt . Errorf ( "invalid VoteA: %v" , err )
}
if err := e . VoteB . ValidateBasic ( ) ; err != nil {
return fmt . Errorf ( "invalid VoteB: %v" , err )
}
// Enforce Votes are lexicographically sorted on blockID
if strings . Compare ( e . VoteA . BlockID . Key ( ) , e . VoteB . BlockID . Key ( ) ) >= 0 {
return errors . New ( "amnesia votes in invalid order" )
}
// H/S must be the same
if e . VoteA . Height != e . VoteB . Height ||
e . VoteA . Type != e . VoteB . Type {
return fmt . Errorf ( "h/s do not match: %d/%v vs %d/%v" ,
e . VoteA . Height , e . VoteA . Type , e . VoteB . Height , e . VoteB . Type )
}
// R must be different
if e . VoteA . Round == e . VoteB . Round {
return fmt . Errorf ( "expected votes from different rounds, got %d" , e . VoteA . Round )
}
// Address must be the same
if ! bytes . Equal ( e . VoteA . ValidatorAddress , e . VoteB . ValidatorAddress ) {
return fmt . Errorf ( "validator addresses do not match: %X vs %X" ,
e . VoteA . ValidatorAddress ,
e . VoteB . ValidatorAddress ,
)
}
// Index must be the same
// https://github.com/tendermint/tendermint/issues/4619
if e . VoteA . ValidatorIndex != e . VoteB . ValidatorIndex {
return fmt . Errorf (
"duplicateVoteEvidence Error: Validator indices do not match. Got %d and %d" ,
e . VoteA . ValidatorIndex ,
e . VoteB . ValidatorIndex ,
)
}
// BlockIDs must be different
if e . VoteA . BlockID . Equals ( e . VoteB . BlockID ) {
return fmt . Errorf (
"block IDs are the same (%v) - not a real duplicate vote" ,
e . VoteA . BlockID ,
)
}
return nil
}
func ( e PotentialAmnesiaEvidence ) String ( ) string {
return fmt . Sprintf ( "PotentialAmnesiaEvidence{VoteA: %v, VoteB: %v}" , e . VoteA , e . VoteB )
}
//-----------------------------------------------------------------
// UNSTABLE
@ -307,38 +985,3 @@ func (e MockEvidence) ValidateBasic() error { return nil }
func ( e MockEvidence ) String ( ) string {
return fmt . Sprintf ( "Evidence: %d/%s/%s" , e . EvidenceHeight , e . Time ( ) , e . EvidenceAddress )
}
//-------------------------------------------
// EvidenceList is a list of Evidence. Evidences is not a word.
type EvidenceList [ ] Evidence
// Hash returns the simple merkle root hash of the EvidenceList.
func ( evl EvidenceList ) Hash ( ) [ ] byte {
// These allocations are required because Evidence is not of type Bytes, and
// golang slices can't be typed cast. This shouldn't be a performance problem since
// the Evidence size is capped.
evidenceBzs := make ( [ ] [ ] byte , len ( evl ) )
for i := 0 ; i < len ( evl ) ; i ++ {
evidenceBzs [ i ] = evl [ i ] . Bytes ( )
}
return merkle . SimpleHashFromByteSlices ( evidenceBzs )
}
func ( evl EvidenceList ) String ( ) string {
s := ""
for _ , e := range evl {
s += fmt . Sprintf ( "%s\t\t" , e )
}
return s
}
// Has returns true if the evidence is in the EvidenceList.
func ( evl EvidenceList ) Has ( evidence Evidence ) bool {
for _ , ev := range evl {
if ev . Equal ( evidence ) {
return true
}
}
return false
}