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 }