github.com/Blockdaemon/celo-blockchain@v0.0.0-20200129231733-e667f6b08419/consensus/istanbul/core/commit.go (about) 1 // Copyright 2017 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package core 18 19 import ( 20 "reflect" 21 22 "github.com/ethereum/go-ethereum/consensus/istanbul" 23 blscrypto "github.com/ethereum/go-ethereum/crypto/bls" 24 ) 25 26 func (c *core) sendCommit() { 27 logger := c.newLogger("func", "sendCommit") 28 logger.Trace("Sending commit") 29 sub := c.current.Subject() 30 c.broadcastCommit(sub) 31 } 32 33 func (c *core) generateCommittedSeal(sub *istanbul.Subject) (blscrypto.SerializedSignature, error) { 34 seal := PrepareCommittedSeal(sub.Digest, sub.View.Round) 35 committedSeal, err := c.backend.SignBlockHeader(seal) 36 if err != nil { 37 return blscrypto.SerializedSignature{}, err 38 } 39 return committedSeal, nil 40 } 41 42 func (c *core) generateEpochValidatorSetData(blockNumber uint64, newValSet istanbul.ValidatorSet) ([]byte, error) { 43 if !istanbul.IsLastBlockOfEpoch(blockNumber, c.config.Epoch) { 44 return nil, errNotLastBlockInEpoch 45 } 46 blsPubKeys := []blscrypto.SerializedPublicKey{} 47 for _, v := range newValSet.List() { 48 blsPubKeys = append(blsPubKeys, v.BLSPublicKey()) 49 } 50 maxNonSignersPlusOne := uint32(newValSet.Size() - newValSet.MinQuorumSize() + 1) 51 epochData, err := blscrypto.EncodeEpochSnarkData(blsPubKeys, maxNonSignersPlusOne, uint16(istanbul.GetEpochNumber(blockNumber, c.config.Epoch))) 52 if err != nil { 53 return nil, err 54 } 55 56 return epochData, nil 57 } 58 59 func (c *core) broadcastCommit(sub *istanbul.Subject) { 60 logger := c.newLogger("func", "broadcastCommit") 61 62 committedSeal, err := c.generateCommittedSeal(sub) 63 if err != nil { 64 logger.Error("Failed to commit seal", "err", err) 65 return 66 } 67 68 currentBlockNumber := c.current.Proposal().Number().Uint64() 69 newValSet, err := c.backend.NextBlockValidators(c.current.Proposal()) 70 if err != nil { 71 logger.Error("Failed to get next block's validators", "err", err) 72 return 73 } 74 epochValidatorSetData, err := c.generateEpochValidatorSetData(currentBlockNumber, newValSet) 75 if err != nil && err != errNotLastBlockInEpoch { 76 logger.Error("Failed to create epoch validator set data", "err", err) 77 return 78 } 79 var epochValidatorSetSeal blscrypto.SerializedSignature 80 if err == nil { 81 epochValidatorSetSeal, err = c.backend.SignBLSWithCompositeHash(epochValidatorSetData[:]) 82 if err != nil { 83 logger.Error("Failed to sign epoch validator set seal", "err", err) 84 return 85 } 86 } 87 88 committedSub := &istanbul.CommittedSubject{ 89 Subject: sub, 90 CommittedSeal: committedSeal[:], 91 EpochValidatorSetSeal: epochValidatorSetSeal[:], 92 } 93 encodedCommittedSubject, err := Encode(committedSub) 94 if err != nil { 95 logger.Error("Failed to encode committedSubject", committedSub) 96 } 97 98 istMsg := istanbul.Message{ 99 Code: istanbul.MsgCommit, 100 Msg: encodedCommittedSubject, 101 } 102 c.broadcast(&istMsg) 103 } 104 105 func (c *core) handleCommit(msg *istanbul.Message) error { 106 // Decode COMMIT message 107 var commit *istanbul.CommittedSubject 108 err := msg.Decode(&commit) 109 if err != nil { 110 return errFailedDecodeCommit 111 } 112 113 err = c.checkMessage(istanbul.MsgCommit, commit.Subject.View) 114 115 if err == errOldMessage { 116 // Discard messages from previous views, unless they are commits from the previous sequence, 117 // with the same round as what we wound up finalizing, as we would be able to include those 118 // to create the ParentAggregatedSeal for our next proposal. 119 lastSubject, err := c.backend.LastSubject() 120 if err != nil { 121 return err 122 } else if commit.Subject.View.Cmp(lastSubject.View) != 0 { 123 return errOldMessage 124 } 125 return c.handleCheckedCommitForPreviousSequence(msg, commit) 126 } else if err != nil { 127 return err 128 } 129 130 return c.handleCheckedCommitForCurrentSequence(msg, commit) 131 } 132 133 func (c *core) handleCheckedCommitForPreviousSequence(msg *istanbul.Message, commit *istanbul.CommittedSubject) error { 134 logger := c.newLogger("func", "handleCheckedCommitForPreviousSequence", "tag", "handleMsg", "msg_view", commit.Subject.View) 135 headBlock := c.backend.GetCurrentHeadBlock() 136 // Retrieve the validator set for the previous proposal (which should 137 // match the one broadcast) 138 parentValset := c.backend.ParentBlockValidators(headBlock) 139 _, validator := parentValset.GetByAddress(msg.Address) 140 if validator == nil { 141 return errInvalidValidatorAddress 142 } 143 if err := c.verifyCommittedSeal(commit, validator); err != nil { 144 return errInvalidCommittedSeal 145 } 146 if headBlock.Number().Uint64() > 0 { 147 if err := c.verifyEpochValidatorSetSeal(commit, headBlock.Number().Uint64(), c.current.ValidatorSet(), validator); err != nil { 148 return errInvalidEpochValidatorSetSeal 149 } 150 } 151 152 // Ensure that the commit's digest (ie the received proposal's hash) matches the head block's hash 153 if headBlock.Number().Uint64() > 0 && commit.Subject.Digest != headBlock.Hash() { 154 logger.Debug("Received a commit message for the previous sequence with an unexpected hash", "expected", headBlock.Hash().String(), "received", commit.Subject.Digest.String()) 155 return errInconsistentSubject 156 } 157 158 // Add the ParentCommit to current round state 159 if err := c.current.AddParentCommit(msg); err != nil { 160 logger.Error("Failed to record parent seal", "m", msg, "err", err) 161 return err 162 } 163 return nil 164 } 165 166 func (c *core) handleCheckedCommitForCurrentSequence(msg *istanbul.Message, commit *istanbul.CommittedSubject) error { 167 logger := c.newLogger("func", "handleCheckedCommitForCurrentSequence", "tag", "handleMsg") 168 validator := c.current.GetValidatorByAddress(msg.Address) 169 if validator == nil { 170 return errInvalidValidatorAddress 171 } 172 173 if err := c.verifyCommittedSeal(commit, validator); err != nil { 174 return errInvalidCommittedSeal 175 } 176 177 newValSet, err := c.backend.NextBlockValidators(c.current.Proposal()) 178 if err != nil { 179 return err 180 } 181 182 if err := c.verifyEpochValidatorSetSeal(commit, c.current.Proposal().Number().Uint64(), newValSet, validator); err != nil { 183 return errInvalidEpochValidatorSetSeal 184 } 185 186 // ensure that the commit is in the current proposal 187 if err := c.verifyCommit(commit); err != nil { 188 return err 189 } 190 191 // Add the COMMIT message to current round state 192 if err := c.current.AddCommit(msg); err != nil { 193 logger.Error("Failed to record commit message", "m", msg, "err", err) 194 return err 195 } 196 numberOfCommits := c.current.Commits().Size() 197 minQuorumSize := c.current.ValidatorSet().MinQuorumSize() 198 logger.Trace("Accepted commit for current sequence", "Number of commits", numberOfCommits) 199 200 // Commit the proposal once we have enough COMMIT messages and we are not in the Committed state. 201 // 202 // If we already have a proposal, we may have chance to speed up the consensus process 203 // by committing the proposal without PREPARE messages. 204 // TODO(joshua): Remove state comparisons (or change the cmp function) 205 if numberOfCommits >= minQuorumSize && c.current.State().Cmp(StateCommitted) < 0 { 206 logger.Trace("Got a quorum of commits", "tag", "stateTransition", "commits", c.current.Commits) 207 err := c.commit() 208 if err != nil { 209 logger.Error("Failed to commit()", "err", err) 210 return err 211 } 212 213 } else if c.current.GetPrepareOrCommitSize() >= minQuorumSize && c.current.State().Cmp(StatePrepared) < 0 { 214 err := c.current.TransitionToPrepared(minQuorumSize) 215 if err != nil { 216 logger.Error("Failed to create and set preprared certificate", "err", err) 217 return err 218 } 219 // Process Backlog Messages 220 c.backlog.updateState(c.current.View(), c.current.State()) 221 222 logger.Trace("Got quorum prepares or commits", "tag", "stateTransition", "commits", c.current.Commits, "prepares", c.current.Prepares) 223 c.sendCommit() 224 } 225 return nil 226 227 } 228 229 // verifyCommit verifies if the received COMMIT message is equivalent to our subject 230 func (c *core) verifyCommit(commit *istanbul.CommittedSubject) error { 231 logger := c.newLogger("func", "verifyCommit") 232 233 sub := c.current.Subject() 234 if !reflect.DeepEqual(commit.Subject, sub) { 235 logger.Warn("Inconsistent subjects between commit and proposal", "expected", sub, "got", commit) 236 return errInconsistentSubject 237 } 238 239 return nil 240 } 241 242 // verifyCommittedSeal verifies the commit seal in the received COMMIT message 243 func (c *core) verifyCommittedSeal(comSub *istanbul.CommittedSubject, src istanbul.Validator) error { 244 seal := PrepareCommittedSeal(comSub.Subject.Digest, comSub.Subject.View.Round) 245 return blscrypto.VerifySignature(src.BLSPublicKey(), seal, []byte{}, comSub.CommittedSeal, false) 246 } 247 248 // verifyEpochValidatorSetSeal verifies the epoch validator set seal in the received COMMIT message 249 func (c *core) verifyEpochValidatorSetSeal(comSub *istanbul.CommittedSubject, blockNumber uint64, newValSet istanbul.ValidatorSet, src istanbul.Validator) error { 250 if blockNumber == 0 { 251 return nil 252 } 253 epochData, err := c.generateEpochValidatorSetData(blockNumber, newValSet) 254 if err != nil { 255 if err == errNotLastBlockInEpoch { 256 return nil 257 } 258 return err 259 } 260 return blscrypto.VerifySignature(src.BLSPublicKey(), epochData[:], []byte{}, comSub.EpochValidatorSetSeal, true) 261 }