mirror of
https://github.com/tendermint/tendermint.git
synced 2026-02-05 03:20:44 +00:00
these proto files are meant to help unblock ibc in their quest of migrating the ibc module to proto.
458 lines
13 KiB
Go
458 lines
13 KiB
Go
package types
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
amino "github.com/tendermint/go-amino"
|
|
|
|
"github.com/tendermint/tendermint/crypto"
|
|
cryptoenc "github.com/tendermint/tendermint/crypto/encoding"
|
|
"github.com/tendermint/tendermint/crypto/merkle"
|
|
"github.com/tendermint/tendermint/crypto/tmhash"
|
|
tmproto "github.com/tendermint/tendermint/proto/types"
|
|
)
|
|
|
|
const (
|
|
// MaxEvidenceBytes is a maximum size of any evidence (including amino overhead).
|
|
MaxEvidenceBytes int64 = 484
|
|
)
|
|
|
|
// 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 int64
|
|
GotNum int64
|
|
}
|
|
|
|
// NewErrEvidenceOverflow returns a new ErrEvidenceOverflow where got > max.
|
|
func NewErrEvidenceOverflow(max, got int64) *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)
|
|
}
|
|
|
|
//-------------------------------------------
|
|
|
|
// 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
|
|
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
|
|
}
|
|
|
|
func EvidenceToProto(evidence Evidence) (*tmproto.Evidence, error) {
|
|
if evidence == nil {
|
|
return nil, errors.New("nil evidence")
|
|
}
|
|
|
|
switch evi := evidence.(type) {
|
|
case *DuplicateVoteEvidence:
|
|
voteB := evi.VoteB.ToProto()
|
|
voteA := evi.VoteA.ToProto()
|
|
pk, err := cryptoenc.PubKeyToProto(evi.PubKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tp := &tmproto.Evidence{
|
|
Sum: &tmproto.Evidence_DuplicateVoteEvidence{
|
|
DuplicateVoteEvidence: &tmproto.DuplicateVoteEvidence{
|
|
PubKey: &pk,
|
|
VoteA: voteA,
|
|
VoteB: voteB,
|
|
},
|
|
},
|
|
}
|
|
return tp, nil
|
|
case MockEvidence:
|
|
if err := evi.ValidateBasic(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tp := &tmproto.Evidence{
|
|
Sum: &tmproto.Evidence_MockEvidence{
|
|
MockEvidence: &tmproto.MockEvidence{
|
|
EvidenceHeight: evi.Height(),
|
|
EvidenceTime: evi.Time(),
|
|
EvidenceAddress: evi.Address(),
|
|
},
|
|
},
|
|
}
|
|
|
|
return tp, nil
|
|
case MockRandomEvidence:
|
|
if err := evi.ValidateBasic(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tp := &tmproto.Evidence{
|
|
Sum: &tmproto.Evidence_MockRandomEvidence{
|
|
MockRandomEvidence: &tmproto.MockRandomEvidence{
|
|
EvidenceHeight: evi.Height(),
|
|
EvidenceTime: evi.Time(),
|
|
EvidenceAddress: evi.Address(),
|
|
RandBytes: evi.randBytes,
|
|
},
|
|
},
|
|
}
|
|
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:
|
|
|
|
vA, err := VoteFromProto(evi.DuplicateVoteEvidence.VoteA)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
vB, err := VoteFromProto(evi.DuplicateVoteEvidence.VoteB)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pk, err := cryptoenc.PubKeyFromProto(evi.DuplicateVoteEvidence.GetPubKey())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dve := DuplicateVoteEvidence{
|
|
PubKey: pk,
|
|
VoteA: vA,
|
|
VoteB: vB,
|
|
}
|
|
|
|
return &dve, dve.ValidateBasic()
|
|
case *tmproto.Evidence_MockEvidence:
|
|
me := MockEvidence{
|
|
EvidenceHeight: evi.MockEvidence.GetEvidenceHeight(),
|
|
EvidenceAddress: evi.MockEvidence.GetEvidenceAddress(),
|
|
EvidenceTime: evi.MockEvidence.GetEvidenceTime(),
|
|
}
|
|
return me, me.ValidateBasic()
|
|
case *tmproto.Evidence_MockRandomEvidence:
|
|
mre := MockRandomEvidence{
|
|
MockEvidence: MockEvidence{
|
|
EvidenceHeight: evi.MockRandomEvidence.GetEvidenceHeight(),
|
|
EvidenceAddress: evi.MockRandomEvidence.GetEvidenceAddress(),
|
|
EvidenceTime: evi.MockRandomEvidence.GetEvidenceTime(),
|
|
},
|
|
randBytes: evi.MockRandomEvidence.RandBytes,
|
|
}
|
|
return mre, mre.ValidateBasic()
|
|
default:
|
|
return nil, errors.New("evidence is not recognized")
|
|
}
|
|
}
|
|
|
|
func RegisterEvidences(cdc *amino.Codec) {
|
|
cdc.RegisterInterface((*Evidence)(nil), nil)
|
|
cdc.RegisterConcrete(&DuplicateVoteEvidence{}, "tendermint/DuplicateVoteEvidence", nil)
|
|
}
|
|
|
|
func RegisterMockEvidences(cdc *amino.Codec) {
|
|
cdc.RegisterConcrete(MockEvidence{}, "tendermint/MockEvidence", nil)
|
|
cdc.RegisterConcrete(MockRandomEvidence{}, "tendermint/MockRandomEvidence", nil)
|
|
}
|
|
|
|
const (
|
|
MaxEvidenceBytesDenominator = 10
|
|
)
|
|
|
|
// MaxEvidencePerBlock returns the maximum number of evidences
|
|
// allowed in the block and their maximum total size (limitted to 1/10th
|
|
// of the maximum block size).
|
|
// TODO: change to a constant, or to a fraction of the validator set size.
|
|
// See https://github.com/tendermint/tendermint/issues/2590
|
|
func MaxEvidencePerBlock(blockMaxBytes int64) (int64, int64) {
|
|
maxBytes := blockMaxBytes / MaxEvidenceBytesDenominator
|
|
maxNum := maxBytes / MaxEvidenceBytes
|
|
return maxNum, maxBytes
|
|
}
|
|
|
|
//-------------------------------------------
|
|
|
|
// DuplicateVoteEvidence contains evidence a validator signed two conflicting
|
|
// votes.
|
|
type DuplicateVoteEvidence struct {
|
|
PubKey crypto.PubKey
|
|
VoteA *Vote
|
|
VoteB *Vote
|
|
}
|
|
|
|
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(pubkey crypto.PubKey, vote1 *Vote, vote2 *Vote) *DuplicateVoteEvidence {
|
|
var voteA, voteB *Vote
|
|
if vote1 == nil || vote2 == nil {
|
|
return nil
|
|
}
|
|
if strings.Compare(vote1.BlockID.Key(), vote2.BlockID.Key()) == -1 {
|
|
voteA = vote1
|
|
voteB = vote2
|
|
} else {
|
|
voteA = vote2
|
|
voteB = vote1
|
|
}
|
|
return &DuplicateVoteEvidence{
|
|
PubKey: pubkey,
|
|
VoteA: voteA,
|
|
VoteB: voteB,
|
|
}
|
|
}
|
|
|
|
// String returns a string representation of the evidence.
|
|
func (dve *DuplicateVoteEvidence) String() string {
|
|
return fmt.Sprintf("VoteA: %v; VoteB: %v", dve.VoteA, dve.VoteB)
|
|
|
|
}
|
|
|
|
// Height returns the height this evidence refers to.
|
|
func (dve *DuplicateVoteEvidence) Height() int64 {
|
|
return dve.VoteA.Height
|
|
}
|
|
|
|
// Time return the time the evidence was created
|
|
func (dve *DuplicateVoteEvidence) Time() time.Time {
|
|
return dve.VoteA.Timestamp
|
|
}
|
|
|
|
// Address returns the address of the validator.
|
|
func (dve *DuplicateVoteEvidence) Address() []byte {
|
|
return dve.PubKey.Address()
|
|
}
|
|
|
|
// Hash returns the hash of the evidence.
|
|
func (dve *DuplicateVoteEvidence) Bytes() []byte {
|
|
return cdcEncode(dve)
|
|
}
|
|
|
|
// Hash returns the hash of the evidence.
|
|
func (dve *DuplicateVoteEvidence) Hash() []byte {
|
|
return tmhash.Sum(cdcEncode(dve))
|
|
}
|
|
|
|
// 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("duplicateVoteEvidence Error: H/R/S does not match. Got %v and %v", dve.VoteA, dve.VoteB)
|
|
}
|
|
|
|
// 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",
|
|
dve.VoteA.ValidatorAddress,
|
|
dve.VoteB.ValidatorAddress,
|
|
)
|
|
}
|
|
|
|
// 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",
|
|
dve.VoteA.ValidatorIndex,
|
|
dve.VoteB.ValidatorIndex,
|
|
)
|
|
}
|
|
|
|
// BlockIDs must be different
|
|
if dve.VoteA.BlockID.Equals(dve.VoteB.BlockID) {
|
|
return fmt.Errorf(
|
|
"duplicateVoteEvidence Error: BlockIDs 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("duplicateVoteEvidence FAILED SANITY CHECK - 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)
|
|
}
|
|
if !pubKey.VerifyBytes(dve.VoteB.SignBytes(chainID), dve.VoteB.Signature) {
|
|
return fmt.Errorf("duplicateVoteEvidence Error verifying VoteB: %v", 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
|
|
}
|
|
|
|
// just check their hashes
|
|
dveHash := tmhash.Sum(cdcEncode(dve))
|
|
evHash := tmhash.Sum(cdcEncode(ev))
|
|
fmt.Println(dveHash, evHash)
|
|
return bytes.Equal(dveHash, evHash)
|
|
}
|
|
|
|
// ValidateBasic performs basic validation.
|
|
func (dve *DuplicateVoteEvidence) ValidateBasic() error {
|
|
if len(dve.PubKey.Bytes()) == 0 {
|
|
return errors.New("empty PubKey")
|
|
}
|
|
if dve.VoteA == nil || dve.VoteB == nil {
|
|
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)
|
|
}
|
|
if err := dve.VoteB.ValidateBasic(); err != nil {
|
|
return fmt.Errorf("invalid VoteB: %v", err)
|
|
}
|
|
// Enforce Votes are lexicographically sorted on blockID
|
|
if strings.Compare(dve.VoteA.BlockID.Key(), dve.VoteB.BlockID.Key()) >= 0 {
|
|
return errors.New("duplicate votes in invalid order")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
//-----------------------------------------------------------------
|
|
|
|
// UNSTABLE
|
|
type MockRandomEvidence struct {
|
|
MockEvidence
|
|
randBytes []byte
|
|
}
|
|
|
|
var _ Evidence = &MockRandomEvidence{}
|
|
|
|
// UNSTABLE
|
|
func NewMockRandomEvidence(height int64, eTime time.Time, address []byte, randBytes []byte) MockRandomEvidence {
|
|
return MockRandomEvidence{
|
|
MockEvidence{
|
|
EvidenceHeight: height,
|
|
EvidenceTime: eTime,
|
|
EvidenceAddress: address}, randBytes,
|
|
}
|
|
}
|
|
|
|
func (e MockRandomEvidence) Hash() []byte {
|
|
return []byte(fmt.Sprintf("%d-%x", e.EvidenceHeight, e.randBytes))
|
|
}
|
|
|
|
// UNSTABLE
|
|
type MockEvidence struct {
|
|
EvidenceHeight int64
|
|
EvidenceTime time.Time
|
|
EvidenceAddress []byte
|
|
}
|
|
|
|
var _ Evidence = &MockEvidence{}
|
|
|
|
// UNSTABLE
|
|
func NewMockEvidence(height int64, eTime time.Time, idx int, address []byte) MockEvidence {
|
|
return MockEvidence{
|
|
EvidenceHeight: height,
|
|
EvidenceTime: eTime,
|
|
EvidenceAddress: address}
|
|
}
|
|
|
|
func (e MockEvidence) Height() int64 { return e.EvidenceHeight }
|
|
func (e MockEvidence) Time() time.Time { return e.EvidenceTime }
|
|
func (e MockEvidence) Address() []byte { return e.EvidenceAddress }
|
|
func (e MockEvidence) Hash() []byte {
|
|
return []byte(fmt.Sprintf("%d-%x-%s",
|
|
e.EvidenceHeight, e.EvidenceAddress, e.EvidenceTime))
|
|
}
|
|
func (e MockEvidence) Bytes() []byte {
|
|
return []byte(fmt.Sprintf("%d-%x-%s",
|
|
e.EvidenceHeight, e.EvidenceAddress, e.EvidenceTime))
|
|
}
|
|
func (e MockEvidence) Verify(chainID string, pubKey crypto.PubKey) error { return nil }
|
|
func (e MockEvidence) Equal(ev Evidence) bool {
|
|
e2 := ev.(MockEvidence)
|
|
return e.EvidenceHeight == e2.EvidenceHeight &&
|
|
bytes.Equal(e.EvidenceAddress, e2.EvidenceAddress)
|
|
}
|
|
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
|
|
}
|