mirror of
https://github.com/tendermint/tendermint.git
synced 2026-08-21 14:46:15 +00:00
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:
+1
-1
@@ -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
@@ -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
@@ -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
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user