github.com/tri-stone/burrow@v0.25.0/consensus/tendermint/sign_info.go (about)

     1  package tendermint
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/hyperledger/burrow/binary"
    11  	"github.com/tendermint/tendermint/types"
    12  	tmtime "github.com/tendermint/tendermint/types/time"
    13  )
    14  
    15  // TODO: type ?
    16  const (
    17  	stepNone      int8 = 0 // Used to distinguish the initial state
    18  	stepPropose   int8 = 1
    19  	stepPrevote   int8 = 2
    20  	stepPrecommit int8 = 3
    21  )
    22  
    23  func voteToStep(vote *types.Vote) int8 {
    24  	switch vote.Type {
    25  	case types.PrevoteType:
    26  		return stepPrevote
    27  	case types.PrecommitType:
    28  		return stepPrecommit
    29  	default:
    30  		panic("Unknown vote type")
    31  		return 0
    32  	}
    33  }
    34  
    35  // LastSignedInfo contains information about the latest
    36  // data signed by a validator to help prevent double signing.
    37  type LastSignedInfo struct {
    38  	sync.Mutex
    39  	Height    int64           `json:"height"`
    40  	Round     int             `json:"round"`
    41  	Step      int8            `json:"step"`
    42  	Signature []byte          `json:"signature,omitempty"` // so we dont lose signatures
    43  	SignBytes binary.HexBytes `json:"signbytes,omitempty"` // so we dont lose signatures
    44  }
    45  
    46  func NewLastSignedInfo() *LastSignedInfo {
    47  	return &LastSignedInfo{
    48  		Step: stepNone,
    49  	}
    50  }
    51  
    52  type tmCryptoSigner func(msg []byte) []byte
    53  
    54  // SignVote signs a canonical representation of the vote, along with the
    55  // chainID. Implements PrivValidator.
    56  func (lsi *LastSignedInfo) SignVote(sign tmCryptoSigner, chainID string, vote *types.Vote) error {
    57  	lsi.Lock()
    58  	defer lsi.Unlock()
    59  	if err := lsi.signVote(sign, chainID, vote); err != nil {
    60  		return fmt.Errorf("error signing vote: %v", err)
    61  	}
    62  	return nil
    63  }
    64  
    65  // SignProposal signs a canonical representation of the proposal, along with
    66  // the chainID. Implements PrivValidator.
    67  func (lsi *LastSignedInfo) SignProposal(sign tmCryptoSigner, chainID string, proposal *types.Proposal) error {
    68  	lsi.Lock()
    69  	defer lsi.Unlock()
    70  	if err := lsi.signProposal(sign, chainID, proposal); err != nil {
    71  		return fmt.Errorf("error signing proposal: %v", err)
    72  	}
    73  	return nil
    74  }
    75  
    76  // returns error if HRS regression or no SignBytes. returns true if HRS is unchanged
    77  func (lsi *LastSignedInfo) checkHRS(height int64, round int, step int8) (bool, error) {
    78  	if lsi.Height > height {
    79  		return false, errors.New("Height regression")
    80  	}
    81  
    82  	if lsi.Height == height {
    83  		if lsi.Round > round {
    84  			return false, errors.New("Round regression")
    85  		}
    86  
    87  		if lsi.Round == round {
    88  			if lsi.Step > step {
    89  				return false, errors.New("Step regression")
    90  			} else if lsi.Step == step {
    91  				if lsi.SignBytes != nil {
    92  					if lsi.Signature == nil {
    93  						panic("pv: Signature is nil but SignBytes is not!")
    94  					}
    95  					return true, nil
    96  				}
    97  				return false, errors.New("No Signature found")
    98  			}
    99  		}
   100  	}
   101  	return false, nil
   102  }
   103  
   104  // signVote checks if the vote is good to sign and sets the vote signature.
   105  // It may need to set the timestamp as well if the vote is otherwise the same as
   106  // a previously signed vote (ie. we crashed after signing but before the vote hit the WAL).
   107  func (lsi *LastSignedInfo) signVote(sign tmCryptoSigner, chainID string, vote *types.Vote) error {
   108  	height, round, step := vote.Height, vote.Round, voteToStep(vote)
   109  	signBytes := vote.SignBytes(chainID)
   110  
   111  	sameHRS, err := lsi.checkHRS(height, round, step)
   112  	if err != nil {
   113  		return err
   114  	}
   115  
   116  	// We might crash before writing to the wal,
   117  	// causing us to try to re-sign for the same HRS.
   118  	// If signbytes are the same, use the last signature.
   119  	// If they only differ by timestamp, use last timestamp and signature
   120  	// Otherwise, return error
   121  	if sameHRS {
   122  		if bytes.Equal(signBytes, lsi.SignBytes) {
   123  			vote.Signature = lsi.Signature
   124  		} else if timestamp, ok := checkVotesOnlyDifferByTimestamp(lsi.SignBytes, signBytes); ok {
   125  			vote.Timestamp = timestamp
   126  			vote.Signature = lsi.Signature
   127  		} else {
   128  			err = fmt.Errorf("Conflicting data")
   129  		}
   130  		return err
   131  	}
   132  
   133  	// It passed the checks. Sign the vote
   134  	sig := sign(signBytes)
   135  	lsi.saveSigned(height, round, step, signBytes, sig)
   136  	vote.Signature = sig
   137  	return nil
   138  }
   139  
   140  // signProposal checks if the proposal is good to sign and sets the proposal signature.
   141  // It may need to set the timestamp as well if the proposal is otherwise the same as
   142  // a previously signed proposal ie. we crashed after signing but before the proposal hit the WAL).
   143  func (lsi *LastSignedInfo) signProposal(sign tmCryptoSigner, chainID string, proposal *types.Proposal) error {
   144  	height, round, step := proposal.Height, proposal.Round, stepPropose
   145  	signBytes := proposal.SignBytes(chainID)
   146  
   147  	sameHRS, err := lsi.checkHRS(height, round, step)
   148  	if err != nil {
   149  		return err
   150  	}
   151  
   152  	// We might crash before writing to the wal,
   153  	// causing us to try to re-sign for the same HRS.
   154  	// If signbytes are the same, use the last signature.
   155  	// If they only differ by timestamp, use last timestamp and signature
   156  	// Otherwise, return error
   157  	if sameHRS {
   158  		if bytes.Equal(signBytes, lsi.SignBytes) {
   159  			proposal.Signature = lsi.Signature
   160  		} else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(lsi.SignBytes, signBytes); ok {
   161  			proposal.Timestamp = timestamp
   162  			proposal.Signature = lsi.Signature
   163  		} else {
   164  			err = fmt.Errorf("Conflicting data")
   165  		}
   166  		return err
   167  	}
   168  
   169  	// It passed the checks. Sign the proposal
   170  	sig := sign(signBytes)
   171  	lsi.saveSigned(height, round, step, signBytes, sig)
   172  	proposal.Signature = sig
   173  	return nil
   174  }
   175  
   176  // Persist height/round/step and signature
   177  func (lsi *LastSignedInfo) saveSigned(height int64, round int, step int8,
   178  	signBytes []byte, sig []byte) {
   179  
   180  	lsi.Height = height
   181  	lsi.Round = round
   182  	lsi.Step = step
   183  	lsi.Signature = sig
   184  	lsi.SignBytes = signBytes
   185  }
   186  
   187  // String returns a string representation of the LastSignedInfo.
   188  func (lsi *LastSignedInfo) String() string {
   189  	return fmt.Sprintf("PrivValidator{LH:%v, LR:%v, LS:%v}", lsi.Height, lsi.Round, lsi.Step)
   190  }
   191  
   192  //-------------------------------------
   193  
   194  // returns the timestamp from the lastSignBytes.
   195  // returns true if the only difference in the votes is their timestamp.
   196  func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) {
   197  	var lastVote, newVote types.CanonicalVote
   198  	if err := cdc.UnmarshalBinaryLengthPrefixed(lastSignBytes, &lastVote); err != nil {
   199  		panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into vote: %v", err))
   200  	}
   201  	if err := cdc.UnmarshalBinaryLengthPrefixed(newSignBytes, &newVote); err != nil {
   202  		panic(fmt.Sprintf("signBytes cannot be unmarshalled into vote: %v", err))
   203  	}
   204  
   205  	lastTime := lastVote.Timestamp
   206  
   207  	// set the times to the same value and check equality
   208  	now := tmtime.Now()
   209  	lastVote.Timestamp = now
   210  	newVote.Timestamp = now
   211  	lastVoteBytes, _ := cdc.MarshalJSON(lastVote)
   212  	newVoteBytes, _ := cdc.MarshalJSON(newVote)
   213  
   214  	return lastTime, bytes.Equal(newVoteBytes, lastVoteBytes)
   215  }
   216  
   217  // returns the timestamp from the lastSignBytes.
   218  // returns true if the only difference in the proposals is their timestamp
   219  func checkProposalsOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) {
   220  	var lastProposal, newProposal types.CanonicalProposal
   221  	if err := cdc.UnmarshalBinaryLengthPrefixed(lastSignBytes, &lastProposal); err != nil {
   222  		panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into proposal: %v", err))
   223  	}
   224  	if err := cdc.UnmarshalBinaryLengthPrefixed(newSignBytes, &newProposal); err != nil {
   225  		panic(fmt.Sprintf("signBytes cannot be unmarshalled into proposal: %v", err))
   226  	}
   227  
   228  	lastTime := lastProposal.Timestamp
   229  	// set the times to the same value and check equality
   230  	now := tmtime.Now()
   231  	lastProposal.Timestamp = now
   232  	newProposal.Timestamp = now
   233  	lastProposalBytes, _ := cdc.MarshalBinaryLengthPrefixed(lastProposal)
   234  	newProposalBytes, _ := cdc.MarshalBinaryLengthPrefixed(newProposal)
   235  
   236  	return lastTime, bytes.Equal(newProposalBytes, lastProposalBytes)
   237  }