github.com/shrimpyuk/bor@v0.2.15-0.20220224151350-fb4ec6020bae/eth/backend.go (about) 1 // Copyright 2014 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 eth implements the Ethereum protocol. 18 package eth 19 20 import ( 21 "errors" 22 "fmt" 23 "math/big" 24 "runtime" 25 "sync" 26 "sync/atomic" 27 "time" 28 29 "github.com/ethereum/go-ethereum/accounts" 30 "github.com/ethereum/go-ethereum/common" 31 "github.com/ethereum/go-ethereum/common/hexutil" 32 "github.com/ethereum/go-ethereum/consensus" 33 "github.com/ethereum/go-ethereum/consensus/bor" 34 "github.com/ethereum/go-ethereum/consensus/clique" 35 "github.com/ethereum/go-ethereum/core" 36 "github.com/ethereum/go-ethereum/core/bloombits" 37 "github.com/ethereum/go-ethereum/core/rawdb" 38 "github.com/ethereum/go-ethereum/core/state/pruner" 39 "github.com/ethereum/go-ethereum/core/types" 40 "github.com/ethereum/go-ethereum/core/vm" 41 "github.com/ethereum/go-ethereum/eth/downloader" 42 "github.com/ethereum/go-ethereum/eth/ethconfig" 43 "github.com/ethereum/go-ethereum/eth/filters" 44 "github.com/ethereum/go-ethereum/eth/gasprice" 45 "github.com/ethereum/go-ethereum/eth/protocols/eth" 46 "github.com/ethereum/go-ethereum/eth/protocols/snap" 47 "github.com/ethereum/go-ethereum/ethdb" 48 "github.com/ethereum/go-ethereum/event" 49 "github.com/ethereum/go-ethereum/internal/ethapi" 50 "github.com/ethereum/go-ethereum/log" 51 "github.com/ethereum/go-ethereum/miner" 52 "github.com/ethereum/go-ethereum/node" 53 "github.com/ethereum/go-ethereum/p2p" 54 "github.com/ethereum/go-ethereum/p2p/dnsdisc" 55 "github.com/ethereum/go-ethereum/p2p/enode" 56 "github.com/ethereum/go-ethereum/params" 57 "github.com/ethereum/go-ethereum/rlp" 58 "github.com/ethereum/go-ethereum/rpc" 59 ) 60 61 // Config contains the configuration options of the ETH protocol. 62 // Deprecated: use ethconfig.Config instead. 63 type Config = ethconfig.Config 64 65 // Ethereum implements the Ethereum full node service. 66 type Ethereum struct { 67 config *ethconfig.Config 68 69 // Handlers 70 txPool *core.TxPool 71 blockchain *core.BlockChain 72 handler *handler 73 ethDialCandidates enode.Iterator 74 snapDialCandidates enode.Iterator 75 76 // DB interfaces 77 chainDb ethdb.Database // Block chain database 78 79 eventMux *event.TypeMux 80 engine consensus.Engine 81 accountManager *accounts.Manager 82 83 bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests 84 bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports 85 closeBloomHandler chan struct{} 86 87 APIBackend *EthAPIBackend 88 89 miner *miner.Miner 90 gasPrice *big.Int 91 etherbase common.Address 92 93 networkID uint64 94 netRPCService *ethapi.PublicNetAPI 95 96 p2pServer *p2p.Server 97 98 lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase) 99 } 100 101 // New creates a new Ethereum object (including the 102 // initialisation of the common Ethereum object) 103 func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { 104 // Ensure configuration values are compatible and sane 105 if config.SyncMode == downloader.LightSync { 106 return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum") 107 } 108 if !config.SyncMode.IsValid() { 109 return nil, fmt.Errorf("invalid sync mode %d", config.SyncMode) 110 } 111 if config.Miner.GasPrice == nil || config.Miner.GasPrice.Cmp(common.Big0) <= 0 { 112 log.Warn("Sanitizing invalid miner gas price", "provided", config.Miner.GasPrice, "updated", ethconfig.Defaults.Miner.GasPrice) 113 config.Miner.GasPrice = new(big.Int).Set(ethconfig.Defaults.Miner.GasPrice) 114 } 115 if config.NoPruning && config.TrieDirtyCache > 0 { 116 if config.SnapshotCache > 0 { 117 config.TrieCleanCache += config.TrieDirtyCache * 3 / 5 118 config.SnapshotCache += config.TrieDirtyCache * 2 / 5 119 } else { 120 config.TrieCleanCache += config.TrieDirtyCache 121 } 122 config.TrieDirtyCache = 0 123 } 124 log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024) 125 126 // Transfer mining-related config to the ethash config. 127 ethashConfig := config.Ethash 128 ethashConfig.NotifyFull = config.Miner.NotifyFull 129 130 // Assemble the Ethereum object 131 chainDb, err := stack.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/", false) 132 if err != nil { 133 return nil, err 134 } 135 chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, config.OverrideLondon) 136 if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok { 137 return nil, genesisErr 138 } 139 log.Info("Initialised chain configuration", "config", chainConfig) 140 141 if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb, stack.ResolvePath(config.TrieCleanCacheJournal)); err != nil { 142 log.Error("Failed to recover state", "error", err) 143 } 144 eth := &Ethereum{ 145 config: config, 146 chainDb: chainDb, 147 eventMux: stack.EventMux(), 148 accountManager: stack.AccountManager(), 149 engine: nil, 150 closeBloomHandler: make(chan struct{}), 151 networkID: config.NetworkId, 152 gasPrice: config.Miner.GasPrice, 153 etherbase: config.Miner.Etherbase, 154 bloomRequests: make(chan chan *bloombits.Retrieval), 155 bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms), 156 p2pServer: stack.Server(), 157 } 158 159 // START: Bor changes 160 eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil} 161 if eth.APIBackend.allowUnprotectedTxs { 162 log.Info("Unprotected transactions allowed") 163 } 164 gpoParams := config.GPO 165 if gpoParams.Default == nil { 166 gpoParams.Default = config.Miner.GasPrice 167 } 168 eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams) 169 170 // create eth api and set engine 171 ethAPI := ethapi.NewPublicBlockChainAPI(eth.APIBackend) 172 eth.engine = ethconfig.CreateConsensusEngine(stack, chainConfig, config, chainDb, ethAPI) 173 // END: Bor changes 174 175 bcVersion := rawdb.ReadDatabaseVersion(chainDb) 176 var dbVer = "<nil>" 177 if bcVersion != nil { 178 dbVer = fmt.Sprintf("%d", *bcVersion) 179 } 180 log.Info("Initialising Ethereum protocol", "network", config.NetworkId, "dbversion", dbVer) 181 182 if !config.SkipBcVersionCheck { 183 if bcVersion != nil && *bcVersion > core.BlockChainVersion { 184 return nil, fmt.Errorf("database version is v%d, Geth %s only supports v%d", *bcVersion, params.VersionWithMeta, core.BlockChainVersion) 185 } else if bcVersion == nil || *bcVersion < core.BlockChainVersion { 186 if bcVersion != nil { // only print warning on upgrade, not on init 187 log.Warn("Upgrade blockchain database version", "from", dbVer, "to", core.BlockChainVersion) 188 } 189 rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion) 190 } 191 } 192 var ( 193 vmConfig = vm.Config{ 194 EnablePreimageRecording: config.EnablePreimageRecording, 195 } 196 cacheConfig = &core.CacheConfig{ 197 TrieCleanLimit: config.TrieCleanCache, 198 TrieCleanJournal: stack.ResolvePath(config.TrieCleanCacheJournal), 199 TrieCleanRejournal: config.TrieCleanCacheRejournal, 200 TrieCleanNoPrefetch: config.NoPrefetch, 201 TrieDirtyLimit: config.TrieDirtyCache, 202 TrieDirtyDisabled: config.NoPruning, 203 TrieTimeLimit: config.TrieTimeout, 204 SnapshotLimit: config.SnapshotCache, 205 Preimages: config.Preimages, 206 } 207 ) 208 eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve, &config.TxLookupLimit) 209 if err != nil { 210 return nil, err 211 } 212 eth.engine.VerifyHeader(eth.blockchain, eth.blockchain.CurrentHeader(), true) // TODO think on it 213 214 // BOR changes 215 eth.APIBackend.gpo.ProcessCache() 216 // BOR changes 217 218 // Rewind the chain in case of an incompatible config upgrade. 219 if compat, ok := genesisErr.(*params.ConfigCompatError); ok { 220 log.Warn("Rewinding chain to upgrade configuration", "err", compat) 221 eth.blockchain.SetHead(compat.RewindTo) 222 rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig) 223 } 224 eth.bloomIndexer.Start(eth.blockchain) 225 226 if config.TxPool.Journal != "" { 227 config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal) 228 } 229 eth.txPool = core.NewTxPool(config.TxPool, chainConfig, eth.blockchain) 230 231 // Permit the downloader to use the trie cache allowance during fast sync 232 cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit + cacheConfig.SnapshotLimit 233 checkpoint := config.Checkpoint 234 if checkpoint == nil { 235 checkpoint = params.TrustedCheckpoints[genesisHash] 236 } 237 if eth.handler, err = newHandler(&handlerConfig{ 238 Database: chainDb, 239 Chain: eth.blockchain, 240 TxPool: eth.txPool, 241 Network: config.NetworkId, 242 Sync: config.SyncMode, 243 BloomCache: uint64(cacheLimit), 244 EventMux: eth.eventMux, 245 Checkpoint: checkpoint, 246 Whitelist: config.Whitelist, 247 }); err != nil { 248 return nil, err 249 } 250 251 eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock) 252 eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) 253 254 // Setup DNS discovery iterators. 255 dnsclient := dnsdisc.NewClient(dnsdisc.Config{}) 256 eth.ethDialCandidates, err = dnsclient.NewIterator(eth.config.EthDiscoveryURLs...) 257 if err != nil { 258 return nil, err 259 } 260 eth.snapDialCandidates, err = dnsclient.NewIterator(eth.config.SnapDiscoveryURLs...) 261 if err != nil { 262 return nil, err 263 } 264 265 // Start the RPC service 266 eth.netRPCService = ethapi.NewPublicNetAPI(eth.p2pServer, config.NetworkId) 267 268 // Register the backend on the node 269 stack.RegisterAPIs(eth.APIs()) 270 stack.RegisterProtocols(eth.Protocols()) 271 stack.RegisterLifecycle(eth) 272 // Check for unclean shutdown 273 if uncleanShutdowns, discards, err := rawdb.PushUncleanShutdownMarker(chainDb); err != nil { 274 log.Error("Could not update unclean-shutdown-marker list", "error", err) 275 } else { 276 if discards > 0 { 277 log.Warn("Old unclean shutdowns found", "count", discards) 278 } 279 for _, tstamp := range uncleanShutdowns { 280 t := time.Unix(int64(tstamp), 0) 281 log.Warn("Unclean shutdown detected", "booted", t, 282 "age", common.PrettyAge(t)) 283 } 284 } 285 return eth, nil 286 } 287 288 func makeExtraData(extra []byte) []byte { 289 if len(extra) == 0 { 290 // create default extradata 291 extra, _ = rlp.EncodeToBytes([]interface{}{ 292 uint(params.VersionMajor<<16 | params.VersionMinor<<8 | params.VersionPatch), 293 "bor", 294 runtime.Version(), 295 runtime.GOOS, 296 }) 297 } 298 if uint64(len(extra)) > params.MaximumExtraDataSize { 299 log.Warn("Miner extra data exceed limit", "extra", hexutil.Bytes(extra), "limit", params.MaximumExtraDataSize) 300 extra = nil 301 } 302 return extra 303 } 304 305 // APIs return the collection of RPC services the ethereum package offers. 306 // NOTE, some of these services probably need to be moved to somewhere else. 307 func (s *Ethereum) APIs() []rpc.API { 308 apis := ethapi.GetAPIs(s.APIBackend) 309 310 // Append any APIs exposed explicitly by the consensus engine 311 apis = append(apis, s.engine.APIs(s.BlockChain())...) 312 313 // BOR change starts 314 // set genesis to public filter api 315 publicFilterAPI := filters.NewPublicFilterAPI(s.APIBackend, false, 5*time.Minute, s.config.BorLogs) 316 // avoiding constructor changed by introducing new method to set genesis 317 publicFilterAPI.SetChainConfig(s.blockchain.Config()) 318 // BOR change ends 319 320 // Append all the local APIs and return 321 return append(apis, []rpc.API{ 322 { 323 Namespace: "eth", 324 Version: "1.0", 325 Service: NewPublicEthereumAPI(s), 326 Public: true, 327 }, { 328 Namespace: "eth", 329 Version: "1.0", 330 Service: NewPublicMinerAPI(s), 331 Public: true, 332 }, { 333 Namespace: "eth", 334 Version: "1.0", 335 Service: downloader.NewPublicDownloaderAPI(s.handler.downloader, s.eventMux), 336 Public: true, 337 }, { 338 Namespace: "miner", 339 Version: "1.0", 340 Service: NewPrivateMinerAPI(s), 341 Public: false, 342 }, { 343 Namespace: "eth", 344 Version: "1.0", 345 Service: publicFilterAPI, // BOR related change 346 Public: true, 347 }, { 348 Namespace: "admin", 349 Version: "1.0", 350 Service: NewPrivateAdminAPI(s), 351 }, { 352 Namespace: "debug", 353 Version: "1.0", 354 Service: NewPublicDebugAPI(s), 355 Public: true, 356 }, { 357 Namespace: "debug", 358 Version: "1.0", 359 Service: NewPrivateDebugAPI(s), 360 }, { 361 Namespace: "net", 362 Version: "1.0", 363 Service: s.netRPCService, 364 Public: true, 365 }, 366 }...) 367 } 368 369 func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) { 370 s.blockchain.ResetWithGenesisBlock(gb) 371 } 372 373 func (s *Ethereum) Etherbase() (eb common.Address, err error) { 374 s.lock.RLock() 375 etherbase := s.etherbase 376 s.lock.RUnlock() 377 378 if etherbase != (common.Address{}) { 379 return etherbase, nil 380 } 381 if wallets := s.AccountManager().Wallets(); len(wallets) > 0 { 382 if accounts := wallets[0].Accounts(); len(accounts) > 0 { 383 etherbase := accounts[0].Address 384 385 s.lock.Lock() 386 s.etherbase = etherbase 387 s.lock.Unlock() 388 389 log.Info("Etherbase automatically configured", "address", etherbase) 390 return etherbase, nil 391 } 392 } 393 return common.Address{}, fmt.Errorf("etherbase must be explicitly specified") 394 } 395 396 // isLocalBlock checks whether the specified block is mined 397 // by local miner accounts. 398 // 399 // We regard two types of accounts as local miner account: etherbase 400 // and accounts specified via `txpool.locals` flag. 401 func (s *Ethereum) isLocalBlock(block *types.Block) bool { 402 author, err := s.engine.Author(block.Header()) 403 if err != nil { 404 log.Warn("Failed to retrieve block author", "number", block.NumberU64(), "hash", block.Hash(), "err", err) 405 return false 406 } 407 // Check whether the given address is etherbase. 408 s.lock.RLock() 409 etherbase := s.etherbase 410 s.lock.RUnlock() 411 if author == etherbase { 412 return true 413 } 414 // Check whether the given address is specified by `txpool.local` 415 // CLI flag. 416 for _, account := range s.config.TxPool.Locals { 417 if account == author { 418 return true 419 } 420 } 421 return false 422 } 423 424 // shouldPreserve checks whether we should preserve the given block 425 // during the chain reorg depending on whether the author of block 426 // is a local account. 427 func (s *Ethereum) shouldPreserve(block *types.Block) bool { 428 // The reason we need to disable the self-reorg preserving for clique 429 // is it can be probable to introduce a deadlock. 430 // 431 // e.g. If there are 7 available signers 432 // 433 // r1 A 434 // r2 B 435 // r3 C 436 // r4 D 437 // r5 A [X] F G 438 // r6 [X] 439 // 440 // In the round5, the inturn signer E is offline, so the worst case 441 // is A, F and G sign the block of round5 and reject the block of opponents 442 // and in the round6, the last available signer B is offline, the whole 443 // network is stuck. 444 if _, ok := s.engine.(*clique.Clique); ok { 445 return false 446 } 447 return s.isLocalBlock(block) 448 } 449 450 // SetEtherbase sets the mining reward address. 451 func (s *Ethereum) SetEtherbase(etherbase common.Address) { 452 s.lock.Lock() 453 s.etherbase = etherbase 454 s.lock.Unlock() 455 456 s.miner.SetEtherbase(etherbase) 457 } 458 459 // StartMining starts the miner with the given number of CPU threads. If mining 460 // is already running, this method adjust the number of threads allowed to use 461 // and updates the minimum price required by the transaction pool. 462 func (s *Ethereum) StartMining(threads int) error { 463 // Update the thread count within the consensus engine 464 type threaded interface { 465 SetThreads(threads int) 466 } 467 if th, ok := s.engine.(threaded); ok { 468 log.Info("Updated mining threads", "threads", threads) 469 if threads == 0 { 470 threads = -1 // Disable the miner from within 471 } 472 th.SetThreads(threads) 473 } 474 // If the miner was not running, initialize it 475 if !s.IsMining() { 476 // Propagate the initial price point to the transaction pool 477 s.lock.RLock() 478 price := s.gasPrice 479 s.lock.RUnlock() 480 s.txPool.SetGasPrice(price) 481 482 // Configure the local mining address 483 eb, err := s.Etherbase() 484 if err != nil { 485 log.Error("Cannot start mining without etherbase", "err", err) 486 return fmt.Errorf("etherbase missing: %v", err) 487 } 488 if clique, ok := s.engine.(*clique.Clique); ok { 489 wallet, err := s.accountManager.Find(accounts.Account{Address: eb}) 490 if wallet == nil || err != nil { 491 log.Error("Etherbase account unavailable locally", "err", err) 492 return fmt.Errorf("signer missing: %v", err) 493 } 494 clique.Authorize(eb, wallet.SignData) 495 } 496 if bor, ok := s.engine.(*bor.Bor); ok { 497 wallet, err := s.accountManager.Find(accounts.Account{Address: eb}) 498 if wallet == nil || err != nil { 499 log.Error("Etherbase account unavailable locally", "err", err) 500 return fmt.Errorf("signer missing: %v", err) 501 } 502 bor.Authorize(eb, wallet.SignData) 503 } 504 // If mining is started, we can disable the transaction rejection mechanism 505 // introduced to speed sync times. 506 atomic.StoreUint32(&s.handler.acceptTxs, 1) 507 508 go s.miner.Start(eb) 509 } 510 return nil 511 } 512 513 // StopMining terminates the miner, both at the consensus engine level as well as 514 // at the block creation level. 515 func (s *Ethereum) StopMining() { 516 // Update the thread count within the consensus engine 517 type threaded interface { 518 SetThreads(threads int) 519 } 520 if th, ok := s.engine.(threaded); ok { 521 th.SetThreads(-1) 522 } 523 // Stop the block creating itself 524 s.miner.Stop() 525 } 526 527 func (s *Ethereum) IsMining() bool { return s.miner.Mining() } 528 func (s *Ethereum) Miner() *miner.Miner { return s.miner } 529 530 func (s *Ethereum) AccountManager() *accounts.Manager { return s.accountManager } 531 func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain } 532 func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } 533 func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } 534 func (s *Ethereum) Engine() consensus.Engine { return s.engine } 535 func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } 536 func (s *Ethereum) IsListening() bool { return true } // Always listening 537 func (s *Ethereum) Downloader() *downloader.Downloader { return s.handler.downloader } 538 func (s *Ethereum) Synced() bool { return atomic.LoadUint32(&s.handler.acceptTxs) == 1 } 539 func (s *Ethereum) ArchiveMode() bool { return s.config.NoPruning } 540 func (s *Ethereum) BloomIndexer() *core.ChainIndexer { return s.bloomIndexer } 541 542 // Protocols returns all the currently configured 543 // network protocols to start. 544 func (s *Ethereum) Protocols() []p2p.Protocol { 545 protos := eth.MakeProtocols((*ethHandler)(s.handler), s.networkID, s.ethDialCandidates) 546 if s.config.SnapshotCache > 0 { 547 protos = append(protos, snap.MakeProtocols((*snapHandler)(s.handler), s.snapDialCandidates)...) 548 } 549 return protos 550 } 551 552 // Start implements node.Lifecycle, starting all internal goroutines needed by the 553 // Ethereum protocol implementation. 554 func (s *Ethereum) Start() error { 555 eth.StartENRUpdater(s.blockchain, s.p2pServer.LocalNode()) 556 557 // Start the bloom bits servicing goroutines 558 s.startBloomHandlers(params.BloomBitsBlocks) 559 560 // Figure out a max peers count based on the server limits 561 maxPeers := s.p2pServer.MaxPeers 562 if s.config.LightServ > 0 { 563 if s.config.LightPeers >= s.p2pServer.MaxPeers { 564 return fmt.Errorf("invalid peer config: light peer count (%d) >= total peer count (%d)", s.config.LightPeers, s.p2pServer.MaxPeers) 565 } 566 maxPeers -= s.config.LightPeers 567 } 568 // Start the networking layer and the light server if requested 569 s.handler.Start(maxPeers) 570 return nil 571 } 572 573 // Stop implements node.Lifecycle, terminating all internal goroutines used by the 574 // Ethereum protocol. 575 func (s *Ethereum) Stop() error { 576 // Stop all the peer-related stuff first. 577 s.ethDialCandidates.Close() 578 s.snapDialCandidates.Close() 579 s.handler.Stop() 580 581 // Then stop everything else. 582 s.bloomIndexer.Close() 583 close(s.closeBloomHandler) 584 s.txPool.Stop() 585 s.miner.Close() 586 s.blockchain.Stop() 587 s.engine.Close() 588 rawdb.PopUncleanShutdownMarker(s.chainDb) 589 s.chainDb.Close() 590 s.eventMux.Stop() 591 592 return nil 593 } 594 595 // 596 // Bor related methods 597 // 598 599 // SetBlockchain set blockchain while testing 600 func (s *Ethereum) SetBlockchain(blockchain *core.BlockChain) { 601 s.blockchain = blockchain 602 }