From 7928659f70511deab1c89a0eaacf127f916a075f Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 9 Jul 2017 14:10:00 -0400 Subject: [PATCH] track evidence, include in block --- consensus/state.go | 15 ++++++++----- state/execution.go | 2 ++ types/block.go | 50 +++++++++++++++++++++++++++++++++++++++++++- types/evidence.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 types/evidence.go diff --git a/consensus/state.go b/consensus/state.go index 67a1d8218..0241cbd95 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -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. diff --git a/state/execution.go b/state/execution.go index dc38ac988..31f2662bf 100644 --- a/state/execution.go +++ b/state/execution.go @@ -309,6 +309,8 @@ func (s *State) validateBlock(b *types.Block) error { } } + // TODO: Validate evidence + return nil } diff --git a/types/block.go b/types/block.go index 29775466e..8d733d502 100644 --- a/types/block.go +++ b/types/block.go @@ -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 diff --git a/types/evidence.go b/types/evidence.go new file mode 100644 index 000000000..b3cf33175 --- /dev/null +++ b/types/evidence.go @@ -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 +}