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 }