evidence: introduction of LightClientAttackEvidence and refactor of evidence lifecycle (#5361)

evidence: modify evidence types (#5342)

light: detect light client attacks (#5344)

evidence: refactor evidence pool (#5345)

abci: application evidence prepared by evidence pool (#5354)
This commit is contained in:
Callum Waters
2020-09-22 10:22:54 +02:00
committed by GitHub
parent 309e29c245
commit ed002cea7e
57 changed files with 3240 additions and 1241 deletions
+1 -1
View File
@@ -660,7 +660,7 @@ func TestEvidenceDataProtoBuf(t *testing.T) {
const chainID = "mychain"
v := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, 1, 0x01, blockID, time.Now())
v2 := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, 2, 0x01, blockID2, time.Now())
ev := NewDuplicateVoteEvidence(v2, v, v2.Timestamp)
ev := NewDuplicateVoteEvidence(v2, v)
data := &EvidenceData{Evidence: EvidenceList{ev}}
_ = data.Hash()
testCases := []struct {
+225 -189
View File
@@ -2,12 +2,12 @@ package types
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"strings"
"time"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/merkle"
"github.com/tendermint/tendermint/crypto/tmhash"
tmjson "github.com/tendermint/tendermint/libs/json"
@@ -16,17 +16,13 @@ import (
)
// Evidence represents any provable malicious activity by a validator.
// Verification logic for each evidence is part of the evidence module.
type Evidence interface {
Height() int64 // height of the equivocation
Time() time.Time // time of the equivocation
Address() []byte // address of the equivocating validator
Bytes() []byte // bytes which comprise the evidence
Hash() []byte // hash of the evidence
Verify(chainID string, pubKey crypto.PubKey) error // verify the evidence
Equal(Evidence) bool // check equality of evidence
ValidateBasic() error
String() string
Height() int64 // height of the infraction
Bytes() []byte // bytes which comprise the evidence
Hash() []byte // hash of the evidence
ValidateBasic() error // basic consistency check
String() string // string format of the evidence
}
const (
@@ -34,93 +30,20 @@ const (
MaxEvidenceBytes int64 = 444
)
// ErrEvidenceInvalid wraps a piece of evidence and the error denoting how or why it is invalid.
type ErrEvidenceInvalid struct {
Evidence Evidence
ErrorValue error
}
// NewErrEvidenceInvalid returns a new EvidenceInvalid with the given err.
func NewErrEvidenceInvalid(ev Evidence, err error) *ErrEvidenceInvalid {
return &ErrEvidenceInvalid{ev, err}
}
// Error returns a string representation of the error.
func (err *ErrEvidenceInvalid) Error() string {
return fmt.Sprintf("Invalid evidence: %v. Evidence: %v", err.ErrorValue, err.Evidence)
}
// ErrEvidenceOverflow is for when there is too much evidence in a block.
type ErrEvidenceOverflow struct {
MaxNum int
GotNum int
}
// NewErrEvidenceOverflow returns a new ErrEvidenceOverflow where got > max.
func NewErrEvidenceOverflow(max, got int) *ErrEvidenceOverflow {
return &ErrEvidenceOverflow{max, got}
}
// Error returns a string representation of the error.
func (err *ErrEvidenceOverflow) Error() string {
return fmt.Sprintf("Too much evidence: Max %d, got %d", err.MaxNum, err.GotNum)
}
//-------------------------------------------
func EvidenceToProto(evidence Evidence) (*tmproto.Evidence, error) {
if evidence == nil {
return nil, errors.New("nil evidence")
}
switch evi := evidence.(type) {
case *DuplicateVoteEvidence:
pbevi := evi.ToProto()
tp := &tmproto.Evidence{
Sum: &tmproto.Evidence_DuplicateVoteEvidence{
DuplicateVoteEvidence: pbevi,
},
}
return tp, nil
default:
return nil, fmt.Errorf("toproto: evidence is not recognized: %T", evi)
}
}
func EvidenceFromProto(evidence *tmproto.Evidence) (Evidence, error) {
if evidence == nil {
return nil, errors.New("nil evidence")
}
switch evi := evidence.Sum.(type) {
case *tmproto.Evidence_DuplicateVoteEvidence:
return DuplicateVoteEvidenceFromProto(evi.DuplicateVoteEvidence)
default:
return nil, errors.New("evidence is not recognized")
}
}
func init() {
tmjson.RegisterType(&DuplicateVoteEvidence{}, "tendermint/DuplicateVoteEvidence")
}
//-------------------------------------------
//--------------------------------------------------------------------------------------
// DuplicateVoteEvidence contains evidence a validator signed two conflicting
// votes.
type DuplicateVoteEvidence struct {
VoteA *Vote `json:"vote_a"`
VoteB *Vote `json:"vote_b"`
Timestamp time.Time `json:"timestamp"`
}
var _ Evidence = &DuplicateVoteEvidence{}
// NewDuplicateVoteEvidence creates DuplicateVoteEvidence with right ordering given
// two conflicting votes. If one of the votes is nil, evidence returned is nil as well
func NewDuplicateVoteEvidence(vote1, vote2 *Vote, time time.Time) *DuplicateVoteEvidence {
func NewDuplicateVoteEvidence(vote1, vote2 *Vote) *DuplicateVoteEvidence {
var voteA, voteB *Vote
if vote1 == nil || vote2 == nil {
return nil
@@ -135,14 +58,12 @@ func NewDuplicateVoteEvidence(vote1, vote2 *Vote, time time.Time) *DuplicateVote
return &DuplicateVoteEvidence{
VoteA: voteA,
VoteB: voteB,
Timestamp: time,
}
}
// String returns a string representation of the evidence.
func (dve *DuplicateVoteEvidence) String() string {
return fmt.Sprintf("DuplicateVoteEvidence{VoteA: %v, VoteB: %v, Time: %v}", dve.VoteA, dve.VoteB, dve.Timestamp)
return fmt.Sprintf("DuplicateVoteEvidence{VoteA: %v, VoteB: %v}", dve.VoteA, dve.VoteB)
}
// Height returns the height this evidence refers to.
@@ -150,17 +71,7 @@ func (dve *DuplicateVoteEvidence) Height() int64 {
return dve.VoteA.Height
}
// Time returns time of the latest vote.
func (dve *DuplicateVoteEvidence) Time() time.Time {
return dve.Timestamp
}
// Address returns the address of the validator.
func (dve *DuplicateVoteEvidence) Address() []byte {
return dve.VoteA.ValidatorAddress
}
// Hash returns the hash of the evidence.
// Bytes returns the proto-encoded evidence as a byte array.
func (dve *DuplicateVoteEvidence) Bytes() []byte {
pbe := dve.ToProto()
bz, err := pbe.Marshal()
@@ -173,88 +84,7 @@ func (dve *DuplicateVoteEvidence) Bytes() []byte {
// Hash returns the hash of the evidence.
func (dve *DuplicateVoteEvidence) Hash() []byte {
pbe := dve.ToProto()
bz, err := pbe.Marshal()
if err != nil {
panic(err)
}
return tmhash.Sum(bz)
}
// 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.
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("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("validator addresses do not match: %X vs %X",
dve.VoteA.ValidatorAddress,
dve.VoteB.ValidatorAddress,
)
}
// BlockIDs must be different
if dve.VoteA.BlockID.Equals(dve.VoteB.BlockID) {
return fmt.Errorf(
"block IDs are the same (%v) - not a real duplicate vote",
dve.VoteA.BlockID,
)
}
// pubkey must match address (this should already be true, sanity check)
addr := dve.VoteA.ValidatorAddress
if !bytes.Equal(pubKey.Address(), addr) {
return fmt.Errorf("address (%X) doesn't match pubkey (%v - %X)",
addr, pubKey, pubKey.Address())
}
va := dve.VoteA.ToProto()
vb := dve.VoteB.ToProto()
// Signatures must be valid
if !pubKey.VerifySignature(VoteSignBytes(chainID, va), dve.VoteA.Signature) {
return fmt.Errorf("verifying VoteA: %w", ErrVoteInvalidSignature)
}
if !pubKey.VerifySignature(VoteSignBytes(chainID, vb), dve.VoteB.Signature) {
return fmt.Errorf("verifying VoteB: %w", ErrVoteInvalidSignature)
}
return nil
}
// Equal checks if two pieces of evidence are equal.
func (dve *DuplicateVoteEvidence) Equal(ev Evidence) bool {
if _, ok := ev.(*DuplicateVoteEvidence); !ok {
return false
}
pbdev := dve.ToProto()
bz, err := pbdev.Marshal()
if err != nil {
panic(err)
}
var evbz []byte
if ev, ok := ev.(*DuplicateVoteEvidence); ok {
evpb := ev.ToProto()
evbz, err = evpb.Marshal()
if err != nil {
panic(err)
}
}
// just check their hashes
dveHash := tmhash.Sum(bz)
evHash := tmhash.Sum(evbz)
return bytes.Equal(dveHash, evHash)
return tmhash.Sum(dve.Bytes())
}
// ValidateBasic performs basic validation.
@@ -279,17 +109,18 @@ func (dve *DuplicateVoteEvidence) ValidateBasic() error {
return nil
}
// ToProto encodes DuplicateVoteEvidence to protobuf
func (dve *DuplicateVoteEvidence) ToProto() *tmproto.DuplicateVoteEvidence {
voteB := dve.VoteB.ToProto()
voteA := dve.VoteA.ToProto()
tp := tmproto.DuplicateVoteEvidence{
VoteA: voteA,
VoteB: voteB,
Timestamp: dve.Timestamp,
VoteA: voteA,
VoteB: voteB,
}
return &tp
}
// DuplicateVoteEvidenceFromProto decodes protobuf into DuplicateVoteEvidence
func DuplicateVoteEvidenceFromProto(pb *tmproto.DuplicateVoteEvidence) (*DuplicateVoteEvidence, error) {
if pb == nil {
return nil, errors.New("nil duplicate vote evidence")
@@ -305,12 +136,127 @@ func DuplicateVoteEvidenceFromProto(pb *tmproto.DuplicateVoteEvidence) (*Duplica
return nil, err
}
dve := NewDuplicateVoteEvidence(vA, vB, pb.Timestamp)
dve := NewDuplicateVoteEvidence(vA, vB)
return dve, dve.ValidateBasic()
}
//--------------------------------------------------
//------------------------------------ LIGHT EVIDENCE --------------------------------------
// LightClientAttackEvidence is a generalized evidence that captures all forms of known attacks on
// a light client such that a full node can verify, propose and commit the evidence on-chain for
// punishment of the malicious validators. There are three forms of attacks: Lunatic, Equivocation
// and Amnesia. These attacks are exhaustive. You can find a more detailed overview of this at
// tendermint/docs/architecture/adr-047-handling-evidence-from-light-client.md
type LightClientAttackEvidence struct {
ConflictingBlock *LightBlock
CommonHeight int64
}
var _ Evidence = &LightClientAttackEvidence{}
// Height returns the last height at which the primary provider and witness provider had the same header.
// We use this as the height of the infraction rather than the actual conflicting header because we know
// that the malicious validators were bonded at this height which is important for evidence expiry
func (l *LightClientAttackEvidence) Height() int64 {
return l.CommonHeight
}
// Bytes returns the proto-encoded evidence as a byte array
func (l *LightClientAttackEvidence) Bytes() []byte {
pbe, err := l.ToProto()
if err != nil {
panic(err)
}
bz, err := pbe.Marshal()
if err != nil {
panic(err)
}
return bz
}
// Hash returns the hash of the header and the commonHeight. This is designed to cause hash collisions with evidence
// that have the same conflicting header and common height but different permutations of validator commit signatures.
// The reason for this is that we don't want to allow several permutations of the same evidence to be committed on
// chain. Ideally we commit the header with the most commit signatures but anything greater than 1/3 is sufficient.
func (l *LightClientAttackEvidence) Hash() []byte {
buf := make([]byte, binary.MaxVarintLen64)
n := binary.PutVarint(buf, l.CommonHeight)
bz := make([]byte, tmhash.Size+n)
copy(bz[:tmhash.Size-1], l.ConflictingBlock.Hash().Bytes())
copy(bz[tmhash.Size:], buf)
return tmhash.Sum(bz)
}
// ValidateBasic performs basic validation such that the evidence is consistent and can now be used for verification.
func (l *LightClientAttackEvidence) ValidateBasic() error {
if l.ConflictingBlock == nil {
return errors.New("conflicting block is nil")
}
// this check needs to be done before we can run validate basic
if l.ConflictingBlock.Header == nil {
return errors.New("conflicting block missing header")
}
if err := l.ConflictingBlock.ValidateBasic(l.ConflictingBlock.ChainID); err != nil {
return fmt.Errorf("invalid conflicting light block: %w", err)
}
if l.CommonHeight <= 0 {
return errors.New("negative or zero common height")
}
// check that common height isn't ahead of the height of the conflicting block. It
// is possible that they are the same height if the light node witnesses either an
// amnesia or a equivocation attack.
if l.CommonHeight > l.ConflictingBlock.Height {
return fmt.Errorf("common height is ahead of the conflicting block height (%d > %d)",
l.CommonHeight, l.ConflictingBlock.Height)
}
return nil
}
// String returns a string representation of LightClientAttackEvidence
func (l *LightClientAttackEvidence) String() string {
return fmt.Sprintf("LightClientAttackEvidence{ConflictingBlock: %v, CommonHeight: %d}",
l.ConflictingBlock.String(), l.CommonHeight)
}
// ToProto encodes LightClientAttackEvidence to protobuf
func (l *LightClientAttackEvidence) ToProto() (*tmproto.LightClientAttackEvidence, error) {
conflictingBlock, err := l.ConflictingBlock.ToProto()
if err != nil {
return nil, err
}
return &tmproto.LightClientAttackEvidence{
ConflictingBlock: conflictingBlock,
CommonHeight: l.CommonHeight,
}, nil
}
// LightClientAttackEvidenceFromProto decodes protobuf
func LightClientAttackEvidenceFromProto(l *tmproto.LightClientAttackEvidence) (*LightClientAttackEvidence, error) {
if l == nil {
return nil, errors.New("empty light client attack evidence")
}
conflictingBlock, err := LightBlockFromProto(l.ConflictingBlock)
if err != nil {
return nil, err
}
le := &LightClientAttackEvidence{
ConflictingBlock: conflictingBlock,
CommonHeight: l.CommonHeight,
}
return le, le.ValidateBasic()
}
//------------------------------------------------------------------------------------------
// EvidenceList is a list of Evidence. Evidences is not a word.
type EvidenceList []Evidence
@@ -338,13 +284,103 @@ func (evl EvidenceList) String() string {
// 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) {
if bytes.Equal(evidence.Hash(), ev.Hash()) {
return true
}
}
return false
}
//------------------------------------------ PROTO --------------------------------------
// EvidenceToProto is a generalized function for encoding evidence that conforms to the
// evidence interface to protobuf
func EvidenceToProto(evidence Evidence) (*tmproto.Evidence, error) {
if evidence == nil {
return nil, errors.New("nil evidence")
}
switch evi := evidence.(type) {
case *DuplicateVoteEvidence:
pbev := evi.ToProto()
return &tmproto.Evidence{
Sum: &tmproto.Evidence_DuplicateVoteEvidence{
DuplicateVoteEvidence: pbev,
},
}, nil
case *LightClientAttackEvidence:
pbev, err := evi.ToProto()
if err != nil {
return nil, err
}
return &tmproto.Evidence{
Sum: &tmproto.Evidence_LightClientAttackEvidence{
LightClientAttackEvidence: pbev,
},
}, nil
default:
return nil, fmt.Errorf("toproto: evidence is not recognized: %T", evi)
}
}
// EvidenceFromProto is a generalized function for decoding protobuf into the
// evidence interface
func EvidenceFromProto(evidence *tmproto.Evidence) (Evidence, error) {
if evidence == nil {
return nil, errors.New("nil evidence")
}
switch evi := evidence.Sum.(type) {
case *tmproto.Evidence_DuplicateVoteEvidence:
return DuplicateVoteEvidenceFromProto(evi.DuplicateVoteEvidence)
case *tmproto.Evidence_LightClientAttackEvidence:
return LightClientAttackEvidenceFromProto(evi.LightClientAttackEvidence)
default:
return nil, errors.New("evidence is not recognized")
}
}
func init() {
tmjson.RegisterType(&DuplicateVoteEvidence{}, "tendermint/DuplicateVoteEvidence")
tmjson.RegisterType(&LightClientAttackEvidence{}, "tendermint/LightClientAttackEvidence")
}
//-------------------------------------------- ERRORS --------------------------------------
// ErrInvalidEvidence wraps a piece of evidence and the error denoting how or why it is invalid.
type ErrInvalidEvidence struct {
Evidence Evidence
Reason error
}
// NewErrInvalidEvidence returns a new EvidenceInvalid with the given err.
func NewErrInvalidEvidence(ev Evidence, err error) *ErrInvalidEvidence {
return &ErrInvalidEvidence{ev, err}
}
// Error returns a string representation of the error.
func (err *ErrInvalidEvidence) Error() string {
return fmt.Sprintf("Invalid evidence: %v. Evidence: %v", err.Reason, err.Evidence)
}
// ErrEvidenceOverflow is for when there is too much evidence in a block.
type ErrEvidenceOverflow struct {
MaxNum int
GotNum int
}
// NewErrEvidenceOverflow returns a new ErrEvidenceOverflow where got > max.
func NewErrEvidenceOverflow(max, got int) *ErrEvidenceOverflow {
return &ErrEvidenceOverflow{max, got}
}
// Error returns a string representation of the error.
func (err *ErrEvidenceOverflow) Error() string {
return fmt.Sprintf("Too much evidence: Max %d, got %d", err.MaxNum, err.GotNum)
}
//-------------------------------------------- MOCKING --------------------------------------
// unstable - use only for testing
@@ -366,7 +402,7 @@ func NewMockDuplicateVoteEvidenceWithValidator(height int64, time time.Time,
vB := voteB.ToProto()
_ = pv.SignVote(chainID, vB)
voteB.Signature = vB.Signature
return NewDuplicateVoteEvidence(voteA, voteB, time)
return NewDuplicateVoteEvidence(voteA, voteB)
}
func makeMockVote(height int64, round, index int32, addr Address,
+138 -69
View File
@@ -12,78 +12,14 @@ import (
"github.com/tendermint/tendermint/crypto/tmhash"
tmrand "github.com/tendermint/tendermint/libs/rand"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
tmversion "github.com/tendermint/tendermint/proto/tendermint/version"
"github.com/tendermint/tendermint/version"
)
type voteData struct {
vote1 *Vote
vote2 *Vote
valid bool
}
var defaultVoteTime = time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)
func TestDuplicateVoteEvidence(t *testing.T) {
val := NewMockPV()
val2 := NewMockPV()
blockID := makeBlockID([]byte("blockhash"), 1000, []byte("partshash"))
blockID2 := makeBlockID([]byte("blockhash2"), 1000, []byte("partshash"))
blockID3 := makeBlockID([]byte("blockhash"), 10000, []byte("partshash"))
blockID4 := makeBlockID([]byte("blockhash"), 10000, []byte("partshash2"))
const chainID = "mychain"
vote1 := makeVote(t, val, chainID, 0, 10, 2, 1, blockID, defaultVoteTime)
v1 := vote1.ToProto()
err := val.SignVote(chainID, v1)
require.NoError(t, err)
badVote := makeVote(t, val, chainID, 0, 10, 2, 1, blockID, defaultVoteTime)
bv := badVote.ToProto()
err = val2.SignVote(chainID, bv)
require.NoError(t, err)
vote1.Signature = v1.Signature
badVote.Signature = bv.Signature
cases := []voteData{
{vote1, makeVote(t, val, chainID, 0, 10, 2, 1, blockID2, defaultVoteTime), true}, // different block ids
{vote1, makeVote(t, val, chainID, 0, 10, 2, 1, blockID3, defaultVoteTime), true},
{vote1, makeVote(t, val, chainID, 0, 10, 2, 1, blockID4, defaultVoteTime), true},
{vote1, makeVote(t, val, chainID, 0, 10, 2, 1, blockID, defaultVoteTime), false}, // wrong block id
{vote1, makeVote(t, val, "mychain2", 0, 10, 2, 1, blockID2, defaultVoteTime), false}, // wrong chain id
{vote1, makeVote(t, val, chainID, 0, 11, 2, 1, blockID2, defaultVoteTime), false}, // wrong height
{vote1, makeVote(t, val, chainID, 0, 10, 3, 1, blockID2, defaultVoteTime), false}, // wrong round
{vote1, makeVote(t, val, chainID, 0, 10, 2, 2, blockID2, defaultVoteTime), false}, // wrong step
{vote1, makeVote(t, val2, chainID, 0, 10, 2, 1, blockID, defaultVoteTime), false}, // wrong validator
{vote1, makeVote(t, val2, chainID, 0, 10, 2, 1, blockID, time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)), false},
{vote1, badVote, false}, // signed by wrong key
}
pubKey, err := val.GetPubKey()
require.NoError(t, err)
for _, c := range cases {
ev := &DuplicateVoteEvidence{
VoteA: c.vote1,
VoteB: c.vote2,
Timestamp: defaultVoteTime,
}
if c.valid {
assert.Nil(t, ev.Verify(chainID, pubKey), "evidence should be valid")
} else {
assert.NotNil(t, ev.Verify(chainID, pubKey), "evidence should be invalid")
}
}
ev := randomDuplicatedVoteEvidence(t)
assert.True(t, ev.Equal(ev))
assert.False(t, ev.Equal(&DuplicateVoteEvidence{}))
}
func TestEvidenceList(t *testing.T) {
ev := randomDuplicatedVoteEvidence(t)
ev := randomDuplicateVoteEvidence(t)
evl := EvidenceList([]Evidence{ev})
assert.NotNil(t, evl.Hash())
@@ -122,7 +58,7 @@ func TestMaxEvidenceBytes(t *testing.T) {
}
func randomDuplicatedVoteEvidence(t *testing.T) *DuplicateVoteEvidence {
func randomDuplicateVoteEvidence(t *testing.T) *DuplicateVoteEvidence {
val := NewMockPV()
blockID := makeBlockID([]byte("blockhash"), 1000, []byte("partshash"))
blockID2 := makeBlockID([]byte("blockhash2"), 1000, []byte("partshash"))
@@ -133,6 +69,14 @@ func randomDuplicatedVoteEvidence(t *testing.T) *DuplicateVoteEvidence {
}
}
func TestDuplicateVoteEvidence(t *testing.T) {
const height = int64(13)
ev := NewMockDuplicateVoteEvidence(height, time.Now(), "mock-chain-id")
assert.Equal(t, ev.Hash(), tmhash.Sum(ev.Bytes()))
assert.NotNil(t, ev.String())
assert.Equal(t, ev.Height(), height)
}
func TestDuplicateVoteEvidenceValidation(t *testing.T) {
val := NewMockPV()
blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash")))
@@ -165,13 +109,137 @@ func TestDuplicateVoteEvidenceValidation(t *testing.T) {
t.Run(tc.testName, func(t *testing.T) {
vote1 := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0x02, blockID, defaultVoteTime)
vote2 := makeVote(t, val, chainID, math.MaxInt32, math.MaxInt64, math.MaxInt32, 0x02, blockID2, defaultVoteTime)
ev := NewDuplicateVoteEvidence(vote1, vote2, vote1.Timestamp)
ev := NewDuplicateVoteEvidence(vote1, vote2)
tc.malleateEvidence(ev)
assert.Equal(t, tc.expectErr, ev.ValidateBasic() != nil, "Validate Basic had an unexpected result")
})
}
}
func TestLightClientAttackEvidence(t *testing.T) {
height := int64(5)
voteSet, valSet, privVals := randVoteSet(height, 1, tmproto.PrecommitType, 10, 1)
header := makeHeaderRandom()
header.Height = height
blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt32, tmhash.Sum([]byte("partshash")))
commit, err := MakeCommit(blockID, height, 1, voteSet, privVals, defaultVoteTime)
require.NoError(t, err)
lcae := &LightClientAttackEvidence{
ConflictingBlock: &LightBlock{
SignedHeader: &SignedHeader{
Header: header,
Commit: commit,
},
ValidatorSet: valSet,
},
CommonHeight: height - 1,
}
assert.NotNil(t, lcae.String())
assert.NotNil(t, lcae.Hash())
// only 7 validators sign
differentCommit, err := MakeCommit(blockID, height, 1, voteSet, privVals[:7], defaultVoteTime)
require.NoError(t, err)
differentEv := &LightClientAttackEvidence{
ConflictingBlock: &LightBlock{
SignedHeader: &SignedHeader{
Header: header,
Commit: differentCommit,
},
ValidatorSet: valSet,
},
CommonHeight: height - 1,
}
assert.Equal(t, lcae.Hash(), differentEv.Hash())
// different header hash
differentHeader := makeHeaderRandom()
differentEv = &LightClientAttackEvidence{
ConflictingBlock: &LightBlock{
SignedHeader: &SignedHeader{
Header: differentHeader,
Commit: differentCommit,
},
ValidatorSet: valSet,
},
CommonHeight: height - 1,
}
assert.NotEqual(t, lcae.Hash(), differentEv.Hash())
// different common height should produce a different header
differentEv = &LightClientAttackEvidence{
ConflictingBlock: &LightBlock{
SignedHeader: &SignedHeader{
Header: header,
Commit: differentCommit,
},
ValidatorSet: valSet,
},
CommonHeight: height - 2,
}
assert.NotEqual(t, lcae.Hash(), differentEv.Hash())
assert.Equal(t, lcae.Height(), int64(4)) // Height should be the common Height
assert.NotNil(t, lcae.Bytes())
}
func TestLightClientAttackEvidenceValidation(t *testing.T) {
height := int64(5)
voteSet, valSet, privVals := randVoteSet(height, 1, tmproto.PrecommitType, 10, 1)
header := makeHeaderRandom()
header.Height = height
header.ValidatorsHash = valSet.Hash()
blockID := makeBlockID(header.Hash(), math.MaxInt32, tmhash.Sum([]byte("partshash")))
commit, err := MakeCommit(blockID, height, 1, voteSet, privVals, time.Now())
require.NoError(t, err)
lcae := &LightClientAttackEvidence{
ConflictingBlock: &LightBlock{
SignedHeader: &SignedHeader{
Header: header,
Commit: commit,
},
ValidatorSet: valSet,
},
CommonHeight: height - 1,
}
assert.NoError(t, lcae.ValidateBasic())
testCases := []struct {
testName string
malleateEvidence func(*LightClientAttackEvidence)
expectErr bool
}{
{"Good DuplicateVoteEvidence", func(ev *LightClientAttackEvidence) {}, false},
{"Negative height", func(ev *LightClientAttackEvidence) { ev.CommonHeight = -10 }, true},
{"Height is greater than divergent block", func(ev *LightClientAttackEvidence) {
ev.CommonHeight = height + 1
}, true},
{"Nil conflicting header", func(ev *LightClientAttackEvidence) { ev.ConflictingBlock.Header = nil }, true},
{"Nil conflicting blocl", func(ev *LightClientAttackEvidence) { ev.ConflictingBlock = nil }, true},
{"Nil validator set", func(ev *LightClientAttackEvidence) {
ev.ConflictingBlock.ValidatorSet = &ValidatorSet{}
}, true},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.testName, func(t *testing.T) {
lcae := &LightClientAttackEvidence{
ConflictingBlock: &LightBlock{
SignedHeader: &SignedHeader{
Header: header,
Commit: commit,
},
ValidatorSet: valSet,
},
CommonHeight: height - 1,
}
tc.malleateEvidence(lcae)
if tc.expectErr {
assert.Error(t, lcae.ValidateBasic(), tc.testName)
} else {
assert.NoError(t, lcae.ValidateBasic(), tc.testName)
}
})
}
}
func TestMockEvidenceValidateBasic(t *testing.T) {
goodEvidence := NewMockDuplicateVoteEvidence(int64(1), time.Now(), "mock-chain-id")
assert.Nil(t, goodEvidence.ValidateBasic())
@@ -203,6 +271,7 @@ func makeVote(
func makeHeaderRandom() *Header {
return &Header{
Version: tmversion.Consensus{Block: version.BlockProtocol, App: 1},
ChainID: tmrand.Str(12),
Height: int64(tmrand.Uint16()) + 1,
Time: time.Now(),
+2 -8
View File
@@ -112,27 +112,21 @@ func (tm2pb) ConsensusParams(params *tmproto.ConsensusParams) *abci.ConsensusPar
// so Evidence types stays compact.
// XXX: panics on nil or unknown pubkey type
func (tm2pb) Evidence(ev Evidence, valSet *ValidatorSet) abci.Evidence {
addr := ev.Address()
_, val := valSet.GetByAddress(addr)
if val == nil {
// should already have checked this
panic(fmt.Sprintf("validator in evidence is not in val set, val addr: %v", addr))
}
// set type
var evType abci.EvidenceType
switch ev.(type) {
case *DuplicateVoteEvidence:
evType = abci.EvidenceType_DUPLICATE_VOTE
case *LightClientAttackEvidence:
evType = abci.EvidenceType_LIGHT_CLIENT_ATTACK
default:
panic(fmt.Sprintf("unknown evidence type: %v %v", ev, reflect.TypeOf(ev)))
}
return abci.Evidence{
Type: evType,
Validator: TM2PB.Validator(val),
Height: ev.Height(),
Time: ev.Time(),
TotalVotingPower: valSet.TotalVotingPower(),
}
}
-2
View File
@@ -77,8 +77,6 @@ func TestABCIEvidence(t *testing.T) {
)
assert.Equal(t, abci.EvidenceType_DUPLICATE_VOTE, abciEv.Type)
assert.Equal(t, ev.Time(), abciEv.GetTime())
assert.Equal(t, ev.Address(), abciEv.Validator.GetAddress())
assert.Equal(t, ev.Height(), abciEv.GetHeight())
}