github.com/kisexp/xdchain@v0.0.0-20211206025815-490d6b732aa7/consensus/istanbul/backend/backend.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 backend 18 19 import ( 20 "crypto/ecdsa" 21 "math/big" 22 "sync" 23 "time" 24 25 "github.com/kisexp/xdchain/common" 26 "github.com/kisexp/xdchain/consensus" 27 "github.com/kisexp/xdchain/consensus/istanbul" 28 istanbulcommon "github.com/kisexp/xdchain/consensus/istanbul/common" 29 ibftcore "github.com/kisexp/xdchain/consensus/istanbul/ibft/core" 30 ibftengine "github.com/kisexp/xdchain/consensus/istanbul/ibft/engine" 31 qbftcore "github.com/kisexp/xdchain/consensus/istanbul/qbft/core" 32 qbftengine "github.com/kisexp/xdchain/consensus/istanbul/qbft/engine" 33 qbfttypes "github.com/kisexp/xdchain/consensus/istanbul/qbft/types" 34 "github.com/kisexp/xdchain/consensus/istanbul/validator" 35 "github.com/kisexp/xdchain/core" 36 "github.com/kisexp/xdchain/core/types" 37 "github.com/kisexp/xdchain/crypto" 38 "github.com/kisexp/xdchain/ethdb" 39 "github.com/kisexp/xdchain/event" 40 "github.com/kisexp/xdchain/log" 41 lru "github.com/hashicorp/golang-lru" 42 ) 43 44 const ( 45 // fetcherID is the ID indicates the block is from Istanbul engine 46 fetcherID = "istanbul" 47 ) 48 49 // New creates an Ethereum backend for Istanbul core engine. 50 func New(config *istanbul.Config, privateKey *ecdsa.PrivateKey, db ethdb.Database) *Backend { 51 // Allocate the snapshot caches and create the engine 52 recents, _ := lru.NewARC(inmemorySnapshots) 53 recentMessages, _ := lru.NewARC(inmemoryPeers) 54 knownMessages, _ := lru.NewARC(inmemoryMessages) 55 56 sb := &Backend{ 57 config: config, 58 istanbulEventMux: new(event.TypeMux), 59 privateKey: privateKey, 60 address: crypto.PubkeyToAddress(privateKey.PublicKey), 61 logger: log.New(), 62 db: db, 63 commitCh: make(chan *types.Block, 1), 64 recents: recents, 65 candidates: make(map[common.Address]bool), 66 coreStarted: false, 67 recentMessages: recentMessages, 68 knownMessages: knownMessages, 69 } 70 71 sb.qbftEngine = qbftengine.NewEngine(sb.config, sb.address, sb.Sign) 72 sb.ibftEngine = ibftengine.NewEngine(sb.config, sb.address, sb.Sign) 73 74 return sb 75 } 76 77 // ---------------------------------------------------------------------------- 78 79 type Backend struct { 80 config *istanbul.Config 81 82 privateKey *ecdsa.PrivateKey 83 address common.Address 84 85 core istanbul.Core 86 87 ibftEngine *ibftengine.Engine 88 qbftEngine *qbftengine.Engine 89 90 istanbulEventMux *event.TypeMux 91 92 logger log.Logger 93 94 db ethdb.Database 95 96 chain consensus.ChainHeaderReader 97 currentBlock func() *types.Block 98 hasBadBlock func(hash common.Hash) bool 99 100 // the channels for istanbul engine notifications 101 commitCh chan *types.Block 102 proposedBlockHash common.Hash 103 sealMu sync.Mutex 104 coreStarted bool 105 coreMu sync.RWMutex 106 107 // Current list of candidates we are pushing 108 candidates map[common.Address]bool 109 // Protects the signer fields 110 candidatesLock sync.RWMutex 111 // Snapshots for recent block to speed up reorgs 112 recents *lru.ARCCache 113 114 // event subscription for ChainHeadEvent event 115 broadcaster consensus.Broadcaster 116 117 recentMessages *lru.ARCCache // the cache of peer's messages 118 knownMessages *lru.ARCCache // the cache of self messages 119 120 qbftConsensusEnabled bool // qbft consensus 121 } 122 123 func (sb *Backend) Engine() istanbul.Engine { 124 return sb.EngineForBlockNumber(nil) 125 } 126 127 func (sb *Backend) EngineForBlockNumber(blockNumber *big.Int) istanbul.Engine { 128 switch { 129 case blockNumber != nil && sb.IsQBFTConsensusAt(blockNumber): 130 return sb.qbftEngine 131 case blockNumber == nil && sb.IsQBFTConsensus(): 132 return sb.qbftEngine 133 default: 134 return sb.ibftEngine 135 } 136 } 137 138 // zekun: HACK 139 func (sb *Backend) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int { 140 return sb.EngineForBlockNumber(parent.Number).CalcDifficulty(chain, time, parent) 141 } 142 143 // Address implements istanbul.Backend.Address 144 func (sb *Backend) Address() common.Address { 145 return sb.Engine().Address() 146 } 147 148 // Validators implements istanbul.Backend.Validators 149 func (sb *Backend) Validators(proposal istanbul.Proposal) istanbul.ValidatorSet { 150 return sb.getValidators(proposal.Number().Uint64(), proposal.Hash()) 151 } 152 153 // Broadcast implements istanbul.Backend.Broadcast 154 func (sb *Backend) Broadcast(valSet istanbul.ValidatorSet, code uint64, payload []byte) error { 155 // send to others 156 sb.Gossip(valSet, code, payload) 157 // send to self 158 msg := istanbul.MessageEvent{ 159 Code: code, 160 Payload: payload, 161 } 162 go sb.istanbulEventMux.Post(msg) 163 return nil 164 } 165 166 // Gossip implements istanbul.Backend.Gossip 167 func (sb *Backend) Gossip(valSet istanbul.ValidatorSet, code uint64, payload []byte) error { 168 hash := istanbul.RLPHash(payload) 169 sb.knownMessages.Add(hash, true) 170 171 targets := make(map[common.Address]bool) 172 for _, val := range valSet.List() { 173 if val.Address() != sb.Address() { 174 targets[val.Address()] = true 175 } 176 } 177 if sb.broadcaster != nil && len(targets) > 0 { 178 ps := sb.broadcaster.FindPeers(targets) 179 for addr, p := range ps { 180 ms, ok := sb.recentMessages.Get(addr) 181 var m *lru.ARCCache 182 if ok { 183 m, _ = ms.(*lru.ARCCache) 184 if _, k := m.Get(hash); k { 185 // This peer had this event, skip it 186 continue 187 } 188 } else { 189 m, _ = lru.NewARC(inmemoryMessages) 190 } 191 192 m.Add(hash, true) 193 sb.recentMessages.Add(addr, m) 194 195 if sb.IsQBFTConsensus() { 196 var outboundCode uint64 = istanbulMsg 197 if _, ok := qbfttypes.MessageCodes()[code]; ok { 198 outboundCode = code 199 } 200 go p.SendQBFTConsensus(outboundCode, payload) 201 } else { 202 go p.SendConsensus(istanbulMsg, payload) 203 } 204 } 205 } 206 return nil 207 } 208 209 // Commit implements istanbul.Backend.Commit 210 func (sb *Backend) Commit(proposal istanbul.Proposal, seals [][]byte, round *big.Int) (err error) { 211 // Check if the proposal is a valid block 212 block, ok := proposal.(*types.Block) 213 if !ok { 214 sb.logger.Error("BFT: invalid block proposal", "proposal", proposal) 215 return istanbulcommon.ErrInvalidProposal 216 } 217 218 // Commit header 219 h := block.Header() 220 err = sb.EngineForBlockNumber(h.Number).CommitHeader(h, seals, round) 221 if err != nil { 222 return 223 } 224 225 // Remove ValidatorSet added to ProposerPolicy registry, if not done, the registry keeps increasing size with each block height 226 sb.config.ProposerPolicy.ClearRegistry() 227 228 // update block's header 229 block = block.WithSeal(h) 230 231 sb.logger.Info("BFT: block proposal committed", "author", sb.Address(), "hash", proposal.Hash(), "number", proposal.Number().Uint64()) 232 233 // - if the proposed and committed blocks are the same, send the proposed hash 234 // to commit channel, which is being watched inside the engine.Seal() function. 235 // - otherwise, we try to insert the block. 236 // -- if success, the ChainHeadEvent event will be broadcasted, try to build 237 // the next block and the previous Seal() will be stopped. 238 // -- otherwise, a error will be returned and a round change event will be fired. 239 if sb.proposedBlockHash == block.Hash() { 240 // feed block hash to Seal() and wait the Seal() result 241 sb.commitCh <- block 242 return nil 243 } 244 245 if sb.broadcaster != nil { 246 sb.broadcaster.Enqueue(fetcherID, block) 247 } 248 249 return nil 250 } 251 252 // EventMux implements istanbul.Backend.EventMux 253 func (sb *Backend) EventMux() *event.TypeMux { 254 return sb.istanbulEventMux 255 } 256 257 // Verify implements istanbul.Backend.Verify 258 func (sb *Backend) Verify(proposal istanbul.Proposal) (time.Duration, error) { 259 // Check if the proposal is a valid block 260 block, ok := proposal.(*types.Block) 261 if !ok { 262 sb.logger.Error("BFT: invalid block proposal", "proposal", proposal) 263 return 0, istanbulcommon.ErrInvalidProposal 264 } 265 266 // check bad block 267 if sb.HasBadProposal(block.Hash()) { 268 sb.logger.Warn("BFT: bad block proposal", "proposal", proposal) 269 return 0, core.ErrBlacklistedHash 270 } 271 272 header := block.Header() 273 snap, err := sb.snapshot(sb.chain, header.Number.Uint64()-1, header.ParentHash, nil) 274 if err != nil { 275 return 0, err 276 } 277 278 return sb.EngineForBlockNumber(header.Number).VerifyBlockProposal(sb.chain, block, snap.ValSet) 279 } 280 281 // Sign implements istanbul.Backend.Sign 282 func (sb *Backend) Sign(data []byte) ([]byte, error) { 283 hashData := crypto.Keccak256(data) 284 return crypto.Sign(hashData, sb.privateKey) 285 } 286 287 // SignWithoutHashing implements istanbul.Backend.SignWithoutHashing and signs input data with the backend's private key without hashing the input data 288 func (sb *Backend) SignWithoutHashing(data []byte) ([]byte, error) { 289 return crypto.Sign(data, sb.privateKey) 290 } 291 292 // CheckSignature implements istanbul.Backend.CheckSignature 293 func (sb *Backend) CheckSignature(data []byte, address common.Address, sig []byte) error { 294 signer, err := istanbul.GetSignatureAddress(data, sig) 295 if err != nil { 296 return err 297 } 298 // Compare derived addresses 299 if signer != address { 300 return istanbulcommon.ErrInvalidSignature 301 } 302 303 return nil 304 } 305 306 // HasPropsal implements istanbul.Backend.HashBlock 307 func (sb *Backend) HasPropsal(hash common.Hash, number *big.Int) bool { 308 return sb.chain.GetHeader(hash, number.Uint64()) != nil 309 } 310 311 // GetProposer implements istanbul.Backend.GetProposer 312 func (sb *Backend) GetProposer(number uint64) common.Address { 313 if h := sb.chain.GetHeaderByNumber(number); h != nil { 314 a, _ := sb.Author(h) 315 return a 316 } 317 return common.Address{} 318 } 319 320 // ParentValidators implements istanbul.Backend.GetParentValidators 321 func (sb *Backend) ParentValidators(proposal istanbul.Proposal) istanbul.ValidatorSet { 322 if block, ok := proposal.(*types.Block); ok { 323 return sb.getValidators(block.Number().Uint64()-1, block.ParentHash()) 324 } 325 return validator.NewSet(nil, sb.config.ProposerPolicy) 326 } 327 328 func (sb *Backend) getValidators(number uint64, hash common.Hash) istanbul.ValidatorSet { 329 snap, err := sb.snapshot(sb.chain, number, hash, nil) 330 if err != nil { 331 return validator.NewSet(nil, sb.config.ProposerPolicy) 332 } 333 return snap.ValSet 334 } 335 336 func (sb *Backend) LastProposal() (istanbul.Proposal, common.Address) { 337 block := sb.currentBlock() 338 339 var proposer common.Address 340 if block.Number().Cmp(common.Big0) > 0 { 341 var err error 342 proposer, err = sb.Author(block.Header()) 343 if err != nil { 344 sb.logger.Error("BFT: last block proposal invalid", "err", err) 345 return nil, common.Address{} 346 } 347 } 348 349 // Return header only block here since we don't need block body 350 return block, proposer 351 } 352 353 func (sb *Backend) HasBadProposal(hash common.Hash) bool { 354 if sb.hasBadBlock == nil { 355 return false 356 } 357 return sb.hasBadBlock(hash) 358 } 359 360 func (sb *Backend) Close() error { 361 return nil 362 } 363 364 // IsQBFTConsensus returns whether qbft consensus should be used 365 func (sb *Backend) IsQBFTConsensus() bool { 366 if sb.qbftConsensusEnabled { 367 return true 368 } 369 if sb.chain != nil { 370 return sb.IsQBFTConsensusAt(sb.chain.CurrentHeader().Number) 371 } 372 return false 373 } 374 375 // IsQBFTConsensusForHeader checks if qbft consensus is enabled for the block height identified by the given header 376 func (sb *Backend) IsQBFTConsensusAt(blockNumber *big.Int) bool { 377 return sb.config.IsQBFTConsensusAt(blockNumber) 378 } 379 380 func (sb *Backend) startIBFT() error { 381 sb.logger.Info("BFT: activate IBFT") 382 sb.logger.Trace("BFT: set ProposerPolicy sorter to ValidatorSortByStringFun") 383 sb.config.ProposerPolicy.Use(istanbul.ValidatorSortByString()) 384 sb.qbftConsensusEnabled = false 385 386 sb.core = ibftcore.New(sb, sb.config) 387 if err := sb.core.Start(); err != nil { 388 sb.logger.Error("BFT: failed to activate IBFT", "err", err) 389 return err 390 } 391 392 return nil 393 } 394 395 func (sb *Backend) startQBFT() error { 396 sb.logger.Info("BFT: activate QBFT") 397 sb.logger.Trace("BFT: set ProposerPolicy sorter to ValidatorSortByByteFunc") 398 sb.config.ProposerPolicy.Use(istanbul.ValidatorSortByByte()) 399 sb.qbftConsensusEnabled = true 400 401 sb.core = qbftcore.New(sb, sb.config) 402 if err := sb.core.Start(); err != nil { 403 sb.logger.Error("BFT: failed to activate QBFT", "err", err) 404 return err 405 } 406 407 return nil 408 } 409 410 func (sb *Backend) stop() error { 411 core := sb.core 412 sb.core = nil 413 414 if core != nil { 415 sb.logger.Info("BFT: deactivate") 416 if err := core.Stop(); err != nil { 417 sb.logger.Error("BFT: failed to deactivate", "err", err) 418 return err 419 } 420 } 421 422 sb.qbftConsensusEnabled = false 423 424 return nil 425 } 426 427 // StartQBFTConsensus stops existing legacy ibft consensus and starts the new qbft consensus 428 func (sb *Backend) StartQBFTConsensus() error { 429 sb.logger.Info("BFT: switch from IBFT to QBFT") 430 if err := sb.stop(); err != nil { 431 return err 432 } 433 434 return sb.startQBFT() 435 }