Browse Source

track evidence, include in block

pull/592/head
Ethan Buchman 7 years ago
parent
commit
7928659f70
4 changed files with 113 additions and 6 deletions
  1. +10
    -5
      consensus/state.go
  2. +2
    -0
      state/execution.go
  3. +49
    -1
      types/block.go
  4. +52
    -0
      types/evidence.go

+ 10
- 5
consensus/state.go View File

@ -863,7 +863,9 @@ func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts
// Mempool validated transactions
txs := cs.mempool.Reap(cs.config.MaxBlockSizeTxs)
return cs.state.MakeBlock(cs.Height, txs, commit)
block, parts := cs.state.MakeBlock(cs.Height, txs, commit)
block.AddEvidence(cs.Evidence)
return block, parts
}
// Enter: `timeoutPropose` after entering Propose.
@ -1231,6 +1233,10 @@ func (cs *ConsensusState) finalizeCommit(height int64) {
fail.Fail() // XXX
// TODO: remove included evidence
// and persist remaining evidence
// ... is this the right spot? need to ensure we never lose evidence
// NewHeightStep!
cs.updateToState(stateCopy)
@ -1327,15 +1333,14 @@ func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerKey string) error {
// If it's otherwise invalid, punish peer.
if err == ErrVoteHeightMismatch {
return err
} else if _, ok := err.(*types.ErrVoteConflictingVotes); ok {
} else if voteErr, ok := err.(*types.ErrVoteConflictingVotes); ok {
if bytes.Equal(vote.ValidatorAddress, cs.privValidator.GetAddress()) {
cs.Logger.Error("Found conflicting vote from ourselves. Did you unsafe_reset a validator?", "height", vote.Height, "round", vote.Round, "type", vote.Type)
return err
}
cs.Logger.Error("Found conflicting vote. Publish evidence (TODO)", "height", vote.Height, "round", vote.Round, "type", vote.Type, "valAddr", vote.ValidatorAddress, "valIndex", vote.ValidatorIndex)
// TODO: track evidence for inclusion in a block
cs.Logger.Error("Found conflicting vote. Recording evidence in the RoundState", "height", vote.Height, "round", vote.Round, "type", vote.Type, "valAddr", vote.ValidatorAddress, "valIndex", vote.ValidatorIndex)
cs.Evidence = append(cs.Evidence, &types.DuplicateVoteEvidence{voteErr.VoteA, voteErr.VoteB})
return err
} else {
// Probably an invalid signature / Bad peer.


+ 2
- 0
state/execution.go View File

@ -309,6 +309,8 @@ func (s *State) validateBlock(b *types.Block) error {
}
}
// TODO: Validate evidence
return nil
}


+ 49
- 1
types/block.go View File

@ -19,7 +19,8 @@ import (
type Block struct {
*Header `json:"header"`
*Data `json:"data"`
LastCommit *Commit `json:"last_commit"`
Evidence EvidenceData `json:"evidence"`
LastCommit *Commit `json:"last_commit"`
}
// MakeBlock returns a new block with an empty header, except what can be computed from itself.
@ -40,6 +41,11 @@ func MakeBlock(height int64, txs []Tx, commit *Commit) *Block {
return block
}
// AddEvidence appends the given evidence to the block
func (b *Block) AddEvidence(evidence []Evidence) {
b.Evidence.Evidence = append(b.Evidence.Evidence, evidence...)
}
// ValidateBasic performs basic validation that doesn't involve state data.
// It checks the internal consistency of the block.
func (b *Block) ValidateBasic() error {
@ -58,6 +64,9 @@ func (b *Block) ValidateBasic() error {
if !bytes.Equal(b.DataHash, b.Data.Hash()) {
return fmt.Errorf("Wrong Block.Header.DataHash. Expected %v, got %v", b.DataHash, b.Data.Hash())
}
if !bytes.Equal(b.EvidenceHash, b.Evidence.Hash()) {
return errors.New(cmn.Fmt("Wrong Block.Header.EvidenceHash. Expected %v, got %v", b.EvidenceHash, b.Evidence.Hash()))
}
return nil
}
@ -69,6 +78,9 @@ func (b *Block) FillHeader() {
if b.DataHash == nil {
b.DataHash = b.Data.Hash()
}
if b.EvidenceHash == nil {
b.EvidenceHash = b.Evidence.Hash()
}
}
// Hash computes and returns the block hash.
@ -114,9 +126,11 @@ func (b *Block) StringIndented(indent string) string {
%s %v
%s %v
%s %v
%s %v
%s}#%v`,
indent, b.Header.StringIndented(indent+" "),
indent, b.Data.StringIndented(indent+" "),
indent, b.Evidence.StringIndented(indent+" "),
indent, b.LastCommit.StringIndented(indent+" "),
indent, b.Hash())
}
@ -134,6 +148,7 @@ func (b *Block) StringShort() string {
// Header defines the structure of a Tendermint block header
// TODO: limit header size
// NOTE: changes to the Header should be duplicated in the abci Header
type Header struct {
// basic block info
ChainID string `json:"chain_id"`
@ -154,6 +169,9 @@ type Header struct {
ConsensusHash data.Bytes `json:"consensus_hash"` // consensus params for current block
AppHash data.Bytes `json:"app_hash"` // state after txs from the previous block
LastResultsHash data.Bytes `json:"last_results_hash"` // root hash of all results from the txs from the previous block
// consensus info
EvidenceHash data.Bytes `json:"evidence_hash"` // evidence included in the block
}
// Hash returns the hash of the header.
@ -175,6 +193,7 @@ func (h *Header) Hash() data.Bytes {
"App": h.AppHash,
"Consensus": h.ConsensusHash,
"Results": h.LastResultsHash,
"Evidence": h.EvidenceHash,
})
}
@ -196,6 +215,7 @@ func (h *Header) StringIndented(indent string) string {
%s App: %v
%s Conensus: %v
%s Results: %v
%s Evidence: %v
%s}#%v`,
indent, h.ChainID,
indent, h.Height,
@ -209,6 +229,7 @@ func (h *Header) StringIndented(indent string) string {
indent, h.AppHash,
indent, h.ConsensusHash,
indent, h.LastResultsHash,
indent, h.EvidenceHash,
indent, h.Hash())
}
@ -413,6 +434,33 @@ func (data *Data) StringIndented(indent string) string {
indent, data.hash)
}
//-----------------------------------------------------------------------------
// EvidenceData contains any evidence of malicious wrong-doing by validators
type EvidenceData struct {
Evidence []Evidence `json:"evidence"`
// Volatile
hash data.Bytes
}
// Hash returns the hash of the data
func (data *EvidenceData) Hash() data.Bytes {
if data.hash == nil {
// TODO
}
return data.hash
}
// StringIndented returns a string representation of the transactions
func (data *EvidenceData) StringIndented(indent string) string {
if data == nil {
return "nil-Data"
}
// TODO
return ""
}
//--------------------------------------------------------------------------------
// BlockID defines the unique ID of a block as its Hash and its PartSetHeader


+ 52
- 0
types/evidence.go View File

@ -0,0 +1,52 @@
package types
import (
"bytes"
"fmt"
)
// Evidence represents any provable malicious activity by a validator
type Evidence interface {
Verify() error
Address() []byte
}
//-------------------------------------------
type DuplicateVoteEvidence struct {
VoteA *Vote
VoteB *Vote
}
// Address returns the address of the validator
func (dve *DuplicateVoteEvidence) Address() []byte {
return dve.VoteA.ValidatorAddress
}
// Verify returns an error if the two votes aren't from the same validator, for the same H/R/S, but for different blocks
func (dve *DuplicateVoteEvidence) Verify() 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 and Index 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)
}
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)
}
// Signatures must be valid
// TODO
return nil
}

Loading…
Cancel
Save