From 3f9d9cb36178d84d7136074e7e1696acdccbc958 Mon Sep 17 00:00:00 2001
From: Silas Davis <silas@monax.io>
Date: Mon, 29 Jan 2018 21:20:20 +0000
Subject: [PATCH] Update PrivValidator to pre-released verifier types

Signed-off-by: Silas Davis <silas@monax.io>
---
 .../validator/priv_validator_memory.go        |  15 +-
 consensus/tendermint/validator/sign_info.go   | 240 ++++++++++++++++++
 consensus/tendermint/validator/verifier.go    | 143 -----------
 genesis/genesis.go                            |   4 -
 4 files changed, 249 insertions(+), 153 deletions(-)
 create mode 100644 consensus/tendermint/validator/sign_info.go
 delete mode 100644 consensus/tendermint/validator/verifier.go

diff --git a/consensus/tendermint/validator/priv_validator_memory.go b/consensus/tendermint/validator/priv_validator_memory.go
index 90a51fa9..9e450bb7 100644
--- a/consensus/tendermint/validator/priv_validator_memory.go
+++ b/consensus/tendermint/validator/priv_validator_memory.go
@@ -9,7 +9,7 @@ import (
 
 type privValidatorMemory struct {
 	acm.Addressable
-	acm.Signer
+	tm_types.Signer
 	lastSignedInfo *LastSignedInfo
 }
 
@@ -20,8 +20,8 @@ var _ tm_types.PrivValidator = &privValidatorMemory{}
 func NewPrivValidatorMemory(addressable acm.Addressable, signer acm.Signer) *privValidatorMemory {
 	return &privValidatorMemory{
 		Addressable:    addressable,
-		Signer:         signer,
-		lastSignedInfo: new(LastSignedInfo),
+		Signer:         asTendermintSigner(signer),
+		lastSignedInfo: NewLastSignedInfo(),
 	}
 }
 
@@ -33,16 +33,19 @@ func (pvm *privValidatorMemory) GetPubKey() crypto.PubKey {
 	return pvm.PublicKey().PubKey
 }
 
+// TODO: consider persistence to disk/database to avoid double signing after a crash
 func (pvm *privValidatorMemory) SignVote(chainID string, vote *tm_types.Vote) error {
-	return pvm.lastSignedInfo.SignVote(asTendermintSigner(pvm.Signer), chainID, vote)
+	return pvm.lastSignedInfo.SignVote(pvm.Signer, chainID, vote)
 }
 
 func (pvm *privValidatorMemory) SignProposal(chainID string, proposal *tm_types.Proposal) error {
-	return pvm.lastSignedInfo.SignProposal(asTendermintSigner(pvm.Signer), chainID, proposal)
+	return pvm.lastSignedInfo.SignProposal(pvm.Signer, chainID, proposal)
 }
 
 func (pvm *privValidatorMemory) SignHeartbeat(chainID string, heartbeat *tm_types.Heartbeat) error {
-	return pvm.lastSignedInfo.SignHeartbeat(asTendermintSigner(pvm.Signer), chainID, heartbeat)
+	var err error
+	heartbeat.Signature, err = pvm.Signer.Sign(tm_types.SignBytes(chainID, heartbeat))
+	return err
 }
 
 func asTendermintSigner(signer acm.Signer) tm_types.Signer {
diff --git a/consensus/tendermint/validator/sign_info.go b/consensus/tendermint/validator/sign_info.go
new file mode 100644
index 00000000..12f7cbbe
--- /dev/null
+++ b/consensus/tendermint/validator/sign_info.go
@@ -0,0 +1,240 @@
+package validator
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"time"
+
+	crypto "github.com/tendermint/go-crypto"
+	data "github.com/tendermint/go-wire/data"
+	"github.com/tendermint/tendermint/types"
+)
+
+// This file is copy and pasted from (and pending): https://github.com/tendermint/tendermint/pull/1044
+
+// TODO: type ?
+const (
+	stepNone      int8 = 0 // Used to distinguish the initial state
+	stepPropose   int8 = 1
+	stepPrevote   int8 = 2
+	stepPrecommit int8 = 3
+)
+
+func voteToStep(vote *types.Vote) int8 {
+	switch vote.Type {
+	case types.VoteTypePrevote:
+		return stepPrevote
+	case types.VoteTypePrecommit:
+		return stepPrecommit
+	default:
+		panic("Unknown vote type")
+	}
+}
+
+//-------------------------------------
+
+// LastSignedInfo contains information about the latest
+// data signed by a validator to help prevent double signing.
+type LastSignedInfo struct {
+	Height    int64            `json:"height"`
+	Round     int              `json:"round"`
+	Step      int8             `json:"step"`
+	Signature crypto.Signature `json:"signature,omitempty"` // so we dont lose signatures
+	SignBytes data.Bytes       `json:"signbytes,omitempty"` // so we dont lose signatures
+}
+
+func NewLastSignedInfo() *LastSignedInfo {
+	return &LastSignedInfo{
+		Step: stepNone,
+	}
+}
+
+func (info *LastSignedInfo) String() string {
+	return fmt.Sprintf("LH:%v, LR:%v, LS:%v", info.Height, info.Round, info.Step)
+}
+
+// Verify returns an error if there is a height/round/step regression
+// or if the HRS matches but there are no LastSignBytes.
+// It returns true if HRS matches exactly and the LastSignature exists.
+// It panics if the HRS matches, the LastSignBytes are not empty, but the LastSignature is empty.
+func (info LastSignedInfo) Verify(height int64, round int, step int8) (bool, error) {
+	if info.Height > height {
+		return false, errors.New("Height regression")
+	}
+
+	if info.Height == height {
+		if info.Round > round {
+			return false, errors.New("Round regression")
+		}
+
+		if info.Round == round {
+			if info.Step > step {
+				return false, errors.New("Step regression")
+			} else if info.Step == step {
+				if info.SignBytes != nil {
+					if info.Signature.Empty() {
+						panic("info: LastSignature is nil but LastSignBytes is not!")
+					}
+					return true, nil
+				}
+				return false, errors.New("No LastSignature found")
+			}
+		}
+	}
+	return false, nil
+}
+
+// Set height/round/step and signature on the info
+func (info *LastSignedInfo) Set(height int64, round int, step int8,
+	signBytes []byte, sig crypto.Signature) {
+
+	info.Height = height
+	info.Round = round
+	info.Step = step
+	info.Signature = sig
+	info.SignBytes = signBytes
+}
+
+// Reset resets all the values.
+// XXX: Unsafe.
+func (info *LastSignedInfo) Reset() {
+	info.Height = 0
+	info.Round = 0
+	info.Step = 0
+	info.Signature = crypto.Signature{}
+	info.SignBytes = nil
+}
+
+// SignVote checks the height/round/step (HRS) are greater than the latest state of the LastSignedInfo.
+// If so, it signs the vote, updates the LastSignedInfo, and sets the signature on the vote.
+// If the HRS are equal and the only thing changed is the timestamp, it sets the vote.Timestamp to the previous
+// value and the Signature to the LastSignedInfo.Signature.
+// Else it returns an error.
+func (lsi *LastSignedInfo) SignVote(signer types.Signer, chainID string, vote *types.Vote) error {
+	height, round, step := vote.Height, vote.Round, voteToStep(vote)
+	signBytes := types.SignBytes(chainID, vote)
+
+	sameHRS, err := lsi.Verify(height, round, step)
+	if err != nil {
+		return err
+	}
+
+	// We might crash before writing to the wal,
+	// causing us to try to re-sign for the same HRS.
+	// If signbytes are the same, use the last signature.
+	// If they only differ by timestamp, use last timestamp and signature
+	// Otherwise, return error
+	if sameHRS {
+		if bytes.Equal(signBytes, lsi.SignBytes) {
+			vote.Signature = lsi.Signature
+		} else if timestamp, ok := checkVotesOnlyDifferByTimestamp(lsi.SignBytes, signBytes); ok {
+			vote.Timestamp = timestamp
+			vote.Signature = lsi.Signature
+		} else {
+			err = fmt.Errorf("Conflicting data")
+		}
+		return err
+	}
+	sig, err := signer.Sign(signBytes)
+	if err != nil {
+		return err
+	}
+	lsi.Set(height, round, step, signBytes, sig)
+	vote.Signature = sig
+	return nil
+}
+
+// SignProposal checks if the height/round/step (HRS) are greater than the latest state of the LastSignedInfo.
+// If so, it signs the proposal, updates the LastSignedInfo, and sets the signature on the proposal.
+// If the HRS are equal and the only thing changed is the timestamp, it sets the timestamp to the previous
+// value and the Signature to the LastSignedInfo.Signature.
+// Else it returns an error.
+func (lsi *LastSignedInfo) SignProposal(signer types.Signer, chainID string, proposal *types.Proposal) error {
+	height, round, step := proposal.Height, proposal.Round, stepPropose
+	signBytes := types.SignBytes(chainID, proposal)
+
+	sameHRS, err := lsi.Verify(height, round, step)
+	if err != nil {
+		return err
+	}
+
+	// We might crash before writing to the wal,
+	// causing us to try to re-sign for the same HRS.
+	// If signbytes are the same, use the last signature.
+	// If they only differ by timestamp, use last timestamp and signature
+	// Otherwise, return error
+	if sameHRS {
+		if bytes.Equal(signBytes, lsi.SignBytes) {
+			proposal.Signature = lsi.Signature
+		} else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(lsi.SignBytes, signBytes); ok {
+			proposal.Timestamp = timestamp
+			proposal.Signature = lsi.Signature
+		} else {
+			err = fmt.Errorf("Conflicting data")
+		}
+		return err
+	}
+	sig, err := signer.Sign(signBytes)
+	if err != nil {
+		return err
+	}
+	lsi.Set(height, round, step, signBytes, sig)
+	proposal.Signature = sig
+	return nil
+}
+
+//-------------------------------------
+
+// returns the timestamp from the lastSignBytes.
+// returns true if the only difference in the votes is their timestamp.
+func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) {
+	var lastVote, newVote types.CanonicalJSONOnceVote
+	if err := json.Unmarshal(lastSignBytes, &lastVote); err != nil {
+		panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into vote: %v", err))
+	}
+	if err := json.Unmarshal(newSignBytes, &newVote); err != nil {
+		panic(fmt.Sprintf("signBytes cannot be unmarshalled into vote: %v", err))
+	}
+
+	lastTime, err := time.Parse(time.RFC3339Nano, lastVote.Vote.Timestamp)
+	if err != nil {
+		panic(err)
+	}
+
+	// set the times to the same value and check equality
+	now := types.CanonicalTime(time.Now())
+	lastVote.Vote.Timestamp = now
+	newVote.Vote.Timestamp = now
+	lastVoteBytes, _ := json.Marshal(lastVote)
+	newVoteBytes, _ := json.Marshal(newVote)
+
+	return lastTime, bytes.Equal(newVoteBytes, lastVoteBytes)
+}
+
+// returns the timestamp from the lastSignBytes.
+// returns true if the only difference in the proposals is their timestamp
+func checkProposalsOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) {
+	var lastProposal, newProposal types.CanonicalJSONOnceProposal
+	if err := json.Unmarshal(lastSignBytes, &lastProposal); err != nil {
+		panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into proposal: %v", err))
+	}
+	if err := json.Unmarshal(newSignBytes, &newProposal); err != nil {
+		panic(fmt.Sprintf("signBytes cannot be unmarshalled into proposal: %v", err))
+	}
+
+	lastTime, err := time.Parse(time.RFC3339Nano, lastProposal.Proposal.Timestamp)
+	if err != nil {
+		panic(err)
+	}
+
+	// set the times to the same value and check equality
+	now := types.CanonicalTime(time.Now())
+	lastProposal.Proposal.Timestamp = now
+	newProposal.Proposal.Timestamp = now
+	lastProposalBytes, _ := json.Marshal(lastProposal)
+	newProposalBytes, _ := json.Marshal(newProposal)
+
+	return lastTime, bytes.Equal(newProposalBytes, lastProposalBytes)
+}
diff --git a/consensus/tendermint/validator/verifier.go b/consensus/tendermint/validator/verifier.go
deleted file mode 100644
index 20d174dd..00000000
--- a/consensus/tendermint/validator/verifier.go
+++ /dev/null
@@ -1,143 +0,0 @@
-package validator
-
-import (
-	"bytes"
-	"errors"
-	"fmt"
-	"sync"
-
-	"github.com/tendermint/go-crypto"
-	"github.com/tendermint/go-wire/data"
-	tm_types "github.com/tendermint/tendermint/types"
-)
-
-const (
-	StepError     = -1
-	StepNone      = 0 // Used to distinguish the initial state
-	StepPropose   = 1
-	StepPrevote   = 2
-	StepPrecommit = 3
-)
-
-func VoteToStep(vote *tm_types.Vote) int8 {
-	switch vote.Type {
-	case tm_types.VoteTypePrevote:
-		return StepPrevote
-	case tm_types.VoteTypePrecommit:
-		return StepPrecommit
-	default:
-		return StepError
-	}
-}
-
-type Verifier interface {
-	SignVote(signer tm_types.Signer, chainID string, vote *tm_types.Vote) error
-	SignProposal(signer tm_types.Signer, chainID string, proposal *tm_types.Proposal) error
-	SignHeartbeat(signer tm_types.Signer, chainID string, heartbeat *tm_types.Heartbeat) error
-}
-
-type LastSignedInfo struct {
-	mtx           sync.Mutex
-	LastHeight    int              `json:"last_height"`
-	LastRound     int              `json:"last_round"`
-	LastStep      int8             `json:"last_step"`
-	LastSignature crypto.Signature `json:"last_signature,omitempty"` // so we dont lose signatures
-	LastSignBytes data.Bytes       `json:"last_signbytes,omitempty"` // so we dont lose signatures
-}
-
-var _ Verifier = &LastSignedInfo{}
-
-// SignVote signs a canonical representation of the vote, along with the chainID.
-// Implements PrivValidator.
-func (lsi *LastSignedInfo) SignVote(signer tm_types.Signer, chainID string, vote *tm_types.Vote) error {
-	lsi.mtx.Lock()
-	defer lsi.mtx.Unlock()
-	signature, err := lsi.signBytesHRS(signer, vote.Height, vote.Round, VoteToStep(vote), tm_types.SignBytes(chainID, vote))
-	if err != nil {
-		return fmt.Errorf("error signing vote: %v", err)
-	}
-	vote.Signature = signature
-	return nil
-}
-
-// SignProposal signs a canonical representation of the proposal, along with the chainID.
-// Implements PrivValidator.
-func (lsi *LastSignedInfo) SignProposal(signer tm_types.Signer, chainID string, proposal *tm_types.Proposal) error {
-	lsi.mtx.Lock()
-	defer lsi.mtx.Unlock()
-	signature, err := lsi.signBytesHRS(signer, proposal.Height, proposal.Round, StepPropose, tm_types.SignBytes(chainID, proposal))
-	if err != nil {
-		return fmt.Errorf("error signing proposal: %v", err)
-	}
-	proposal.Signature = signature
-	return nil
-}
-
-// SignHeartbeat signs a canonical representation of the heartbeat, along with the chainID.
-// Implements PrivValidator.
-func (lsi *LastSignedInfo) SignHeartbeat(signer tm_types.Signer, chainID string, heartbeat *tm_types.Heartbeat) error {
-	lsi.mtx.Lock()
-	defer lsi.mtx.Unlock()
-	var err error
-	heartbeat.Signature, err = signer.Sign(tm_types.SignBytes(chainID, heartbeat))
-	return err
-}
-
-// signBytesHRS signs the given signBytes if the height/round/step (HRS)
-// are greater than the latest state. If the HRS are equal,
-// it returns the privValidator.LastSignature.
-func (lsi *LastSignedInfo) signBytesHRS(signer tm_types.Signer, height, round int, step int8, signBytes []byte) (crypto.Signature, error) {
-
-	sig := crypto.Signature{}
-	// If height regression, err
-	if lsi.LastHeight > height {
-		return sig, errors.New("height regression")
-	}
-	// More cases for when the height matches
-	if lsi.LastHeight == height {
-		// If round regression, err
-		if lsi.LastRound > round {
-			return sig, errors.New("round regression")
-		}
-		// If step regression, err
-		if lsi.LastRound == round {
-			if lsi.LastStep > step {
-				return sig, errors.New("step regression")
-			} else if lsi.LastStep == step {
-				if lsi.LastSignBytes != nil {
-					if lsi.LastSignature.Empty() {
-						return crypto.Signature{}, errors.New("lsi: LastSignature is nil but LastSignBytes is not")
-					}
-					// so we dont sign a conflicting vote or proposal
-					// NOTE: proposals are non-deterministic (include time),
-					// so we can actually lose them, but will still never sign conflicting ones
-					if bytes.Equal(lsi.LastSignBytes, signBytes) {
-						// log.Notice("Using lsi.LastSignature", "sig", lsi.LastSignature)
-						return lsi.LastSignature, nil
-					}
-				}
-				return sig, errors.New("step regression")
-			}
-		}
-	}
-
-	// Sign
-	sig, err := signer.Sign(signBytes)
-	if err != nil {
-		return sig, err
-	}
-
-	// Persist height/round/step
-	lsi.LastHeight = height
-	lsi.LastRound = round
-	lsi.LastStep = step
-	lsi.LastSignature = sig
-	lsi.LastSignBytes = signBytes
-
-	return sig, nil
-}
-
-func (lsi *LastSignedInfo) String() string {
-	return fmt.Sprintf("LastSignedInfo{LastHeight:%v, LastRound:%v, LastStep:%v}",
-		lsi.LastHeight, lsi.LastRound, lsi.LastStep)
-}
diff --git a/genesis/genesis.go b/genesis/genesis.go
index 7d62594e..1c4d0341 100644
--- a/genesis/genesis.go
+++ b/genesis/genesis.go
@@ -31,10 +31,6 @@ import (
 // of replay attack between chains with the same name.
 const ShortHashSuffixBytes = 3
 
-// we store the GenesisDoc in the db under this key
-
-var GenDocKey = []byte("GenDocKey")
-
 //------------------------------------------------------------
 // core types for a genesis definition
 
-- 
GitLab