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