github.com/dominant-strategies/go-quai@v0.28.2/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 Quai protocol. 18 package eth 19 20 import ( 21 "fmt" 22 "math/big" 23 "sync" 24 "sync/atomic" 25 "time" 26 27 "github.com/dominant-strategies/go-quai/common" 28 "github.com/dominant-strategies/go-quai/consensus" 29 "github.com/dominant-strategies/go-quai/core" 30 "github.com/dominant-strategies/go-quai/core/bloombits" 31 "github.com/dominant-strategies/go-quai/core/rawdb" 32 "github.com/dominant-strategies/go-quai/core/state/pruner" 33 "github.com/dominant-strategies/go-quai/core/types" 34 "github.com/dominant-strategies/go-quai/core/vm" 35 "github.com/dominant-strategies/go-quai/eth/downloader" 36 "github.com/dominant-strategies/go-quai/eth/ethconfig" 37 "github.com/dominant-strategies/go-quai/eth/filters" 38 "github.com/dominant-strategies/go-quai/eth/gasprice" 39 "github.com/dominant-strategies/go-quai/eth/protocols/eth" 40 "github.com/dominant-strategies/go-quai/ethdb" 41 "github.com/dominant-strategies/go-quai/event" 42 "github.com/dominant-strategies/go-quai/internal/quaiapi" 43 "github.com/dominant-strategies/go-quai/log" 44 "github.com/dominant-strategies/go-quai/node" 45 "github.com/dominant-strategies/go-quai/p2p" 46 "github.com/dominant-strategies/go-quai/p2p/dnsdisc" 47 "github.com/dominant-strategies/go-quai/p2p/enode" 48 "github.com/dominant-strategies/go-quai/params" 49 "github.com/dominant-strategies/go-quai/rpc" 50 ) 51 52 // Config contains the configuration options of the ETH protocol. 53 // Deprecated: use ethconfig.Config instead. 54 type Config = ethconfig.Config 55 56 // Quai implements the Quai full node service. 57 type Quai struct { 58 config *ethconfig.Config 59 60 // Handlers 61 core *core.Core 62 handler *handler 63 ethDialCandidates enode.Iterator 64 snapDialCandidates enode.Iterator 65 66 // DB interfaces 67 chainDb ethdb.Database // Block chain database 68 69 eventMux *event.TypeMux 70 engine consensus.Engine 71 72 bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests 73 bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports 74 closeBloomHandler chan struct{} 75 76 APIBackend *QuaiAPIBackend 77 78 gasPrice *big.Int 79 etherbase common.Address 80 81 networkID uint64 82 netRPCService *quaiapi.PublicNetAPI 83 84 p2pServer *p2p.Server 85 86 lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase) 87 } 88 89 // New creates a new Quai object (including the 90 // initialisation of the common Quai object) 91 func New(stack *node.Node, config *ethconfig.Config) (*Quai, error) { 92 nodeCtx := common.NodeLocation.Context() 93 // Ensure configuration values are compatible and sane 94 if !config.SyncMode.IsValid() { 95 return nil, fmt.Errorf("invalid sync mode %d", config.SyncMode) 96 } 97 if config.Miner.GasPrice == nil || config.Miner.GasPrice.Cmp(common.Big0) <= 0 { 98 log.Warn("Sanitizing invalid miner gas price", "provided", config.Miner.GasPrice, "updated", ethconfig.Defaults.Miner.GasPrice) 99 config.Miner.GasPrice = new(big.Int).Set(ethconfig.Defaults.Miner.GasPrice) 100 } 101 if config.NoPruning && config.TrieDirtyCache > 0 { 102 if config.SnapshotCache > 0 { 103 config.TrieCleanCache += config.TrieDirtyCache * 3 / 5 104 config.SnapshotCache += config.TrieDirtyCache * 2 / 5 105 } else { 106 config.TrieCleanCache += config.TrieDirtyCache 107 } 108 config.TrieDirtyCache = 0 109 } 110 log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024) 111 112 // Assemble the Quai object 113 chainDb, err := stack.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/", false) 114 if err != nil { 115 return nil, err 116 } 117 chainConfig, _, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis) 118 if genesisErr != nil { 119 return nil, genesisErr 120 } 121 122 if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb, stack.ResolvePath(config.TrieCleanCacheJournal)); err != nil { 123 log.Error("Failed to recover state", "error", err) 124 } 125 eth := &Quai{ 126 config: config, 127 chainDb: chainDb, 128 eventMux: stack.EventMux(), 129 closeBloomHandler: make(chan struct{}), 130 networkID: config.NetworkId, 131 gasPrice: config.Miner.GasPrice, 132 etherbase: config.Miner.Etherbase, 133 bloomRequests: make(chan chan *bloombits.Retrieval), 134 p2pServer: stack.Server(), 135 } 136 137 if config.ConsensusEngine == "blake3" { 138 blake3Config := config.Blake3Pow 139 blake3Config.NotifyFull = config.Miner.NotifyFull 140 eth.engine = ethconfig.CreateBlake3ConsensusEngine(stack, chainConfig, &blake3Config, config.Miner.Notify, config.Miner.Noverify, chainDb) 141 } else { 142 // Transfer mining-related config to the progpow config. 143 progpowConfig := config.Progpow 144 progpowConfig.NotifyFull = config.Miner.NotifyFull 145 eth.engine = ethconfig.CreateProgpowConsensusEngine(stack, chainConfig, &progpowConfig, config.Miner.Notify, config.Miner.Noverify, chainDb) 146 } 147 log.Info("Initialised chain configuration", "config", chainConfig) 148 149 bcVersion := rawdb.ReadDatabaseVersion(chainDb) 150 var dbVer = "<nil>" 151 if bcVersion != nil { 152 dbVer = fmt.Sprintf("%d", *bcVersion) 153 } 154 log.Info("Initialising Quai protocol", "network", config.NetworkId, "dbversion", dbVer) 155 156 if !config.SkipBcVersionCheck { 157 if bcVersion != nil && *bcVersion > core.BlockChainVersion { 158 return nil, fmt.Errorf("database version is v%d, Quai %s only supports v%d", *bcVersion, params.Version.Full(), core.BlockChainVersion) 159 } else if bcVersion == nil || *bcVersion < core.BlockChainVersion { 160 if bcVersion != nil { // only print warning on upgrade, not on init 161 log.Warn("Upgrade blockchain database version", "from", dbVer, "to", core.BlockChainVersion) 162 } 163 rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion) 164 } 165 } 166 var ( 167 vmConfig = vm.Config{ 168 EnablePreimageRecording: config.EnablePreimageRecording, 169 } 170 cacheConfig = &core.CacheConfig{ 171 TrieCleanLimit: config.TrieCleanCache, 172 TrieCleanJournal: stack.ResolvePath(config.TrieCleanCacheJournal), 173 TrieCleanRejournal: config.TrieCleanCacheRejournal, 174 TrieCleanNoPrefetch: config.NoPrefetch, 175 TrieDirtyLimit: config.TrieDirtyCache, 176 TrieTimeLimit: config.TrieTimeout, 177 SnapshotLimit: config.SnapshotCache, 178 Preimages: config.Preimages, 179 } 180 ) 181 182 if config.TxPool.Journal != "" { 183 config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal) 184 } 185 186 eth.core, err = core.NewCore(chainDb, &config.Miner, eth.isLocalBlock, &config.TxPool, &config.TxLookupLimit, chainConfig, eth.config.SlicesRunning, eth.config.DomUrl, eth.config.SubUrls, eth.engine, cacheConfig, vmConfig, config.Genesis) 187 if err != nil { 188 return nil, err 189 } 190 191 // Only index bloom if processing state 192 if eth.core.ProcessingState() && nodeCtx == common.ZONE_CTX { 193 eth.bloomIndexer = core.NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms) 194 eth.bloomIndexer.Start(eth.Core().Slice().HeaderChain()) 195 } 196 197 // Permit the downloader to use the trie cache allowance during fast sync 198 cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit + cacheConfig.SnapshotLimit 199 if eth.handler, err = newHandler(&handlerConfig{ 200 Database: chainDb, 201 Core: eth.core, 202 TxPool: eth.core.TxPool(), 203 Network: config.NetworkId, 204 Sync: config.SyncMode, 205 BloomCache: uint64(cacheLimit), 206 EventMux: eth.eventMux, 207 Whitelist: config.Whitelist, 208 SlicesRunning: config.SlicesRunning, 209 }); err != nil { 210 return nil, err 211 } 212 213 eth.APIBackend = &QuaiAPIBackend{stack.Config().ExtRPCEnabled(), eth, nil} 214 // Gasprice oracle is only initiated in zone chains 215 if nodeCtx == common.ZONE_CTX && eth.core.ProcessingState() { 216 gpoParams := config.GPO 217 if gpoParams.Default == nil { 218 gpoParams.Default = config.Miner.GasPrice 219 } 220 eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams) 221 } 222 223 // Setup DNS discovery iterators. 224 dnsclient := dnsdisc.NewClient(dnsdisc.Config{}) 225 eth.ethDialCandidates, err = dnsclient.NewIterator(eth.config.EthDiscoveryURLs...) 226 if err != nil { 227 return nil, err 228 } 229 230 // Start the RPC service 231 eth.netRPCService = quaiapi.NewPublicNetAPI(eth.p2pServer, config.NetworkId) 232 233 // Register the backend on the node 234 stack.RegisterAPIs(eth.APIs()) 235 stack.RegisterProtocols(eth.Protocols()) 236 stack.RegisterLifecycle(eth) 237 // Check for unclean shutdown 238 if uncleanShutdowns, discards, err := rawdb.PushUncleanShutdownMarker(chainDb); err != nil { 239 log.Error("Could not update unclean-shutdown-marker list", "error", err) 240 } else { 241 if discards > 0 { 242 log.Warn("Old unclean shutdowns found", "count", discards) 243 } 244 for _, tstamp := range uncleanShutdowns { 245 t := time.Unix(int64(tstamp), 0) 246 log.Warn("Unclean shutdown detected", "booted", t, 247 "age", common.PrettyAge(t)) 248 } 249 } 250 return eth, nil 251 } 252 253 // APIs return the collection of RPC services the go-quai package offers. 254 // NOTE, some of these services probably need to be moved to somewhere else. 255 func (s *Quai) APIs() []rpc.API { 256 apis := quaiapi.GetAPIs(s.APIBackend) 257 258 // Append any APIs exposed explicitly by the consensus engine 259 apis = append(apis, s.engine.APIs(s.Core())...) 260 261 // Append all the local APIs and return 262 return append(apis, []rpc.API{ 263 { 264 Namespace: "eth", 265 Version: "1.0", 266 Service: NewPublicQuaiAPI(s), 267 Public: true, 268 }, { 269 Namespace: "eth", 270 Version: "1.0", 271 Service: NewPublicMinerAPI(s), 272 Public: true, 273 }, { 274 Namespace: "eth", 275 Version: "1.0", 276 Service: downloader.NewPublicDownloaderAPI(s.handler.downloader, s.eventMux), 277 Public: true, 278 }, { 279 Namespace: "miner", 280 Version: "1.0", 281 Service: NewPrivateMinerAPI(s), 282 Public: false, 283 }, { 284 Namespace: "eth", 285 Version: "1.0", 286 Service: filters.NewPublicFilterAPI(s.APIBackend, false, 5*time.Minute), 287 Public: true, 288 }, { 289 Namespace: "quai", 290 Version: "1.0", 291 Service: filters.NewPublicFilterAPI(s.APIBackend, false, 5*time.Minute), 292 Public: true, 293 }, { 294 Namespace: "admin", 295 Version: "1.0", 296 Service: NewPrivateAdminAPI(s), 297 }, { 298 Namespace: "debug", 299 Version: "1.0", 300 Service: NewPublicDebugAPI(s), 301 Public: true, 302 }, { 303 Namespace: "debug", 304 Version: "1.0", 305 Service: NewPrivateDebugAPI(s), 306 }, { 307 Namespace: "net", 308 Version: "1.0", 309 Service: s.netRPCService, 310 Public: true, 311 }, 312 }...) 313 } 314 315 func (s *Quai) Etherbase() (eb common.Address, err error) { 316 s.lock.RLock() 317 etherbase := s.etherbase 318 s.lock.RUnlock() 319 320 if !etherbase.Equal(common.ZeroAddr) { 321 return etherbase, nil 322 } 323 324 return common.ZeroAddr, fmt.Errorf("etherbase must be explicitly specified") 325 } 326 327 // isLocalBlock checks whether the specified block is mined 328 // by local miner accounts. 329 // 330 // We regard two types of accounts as local miner account: etherbase 331 // and accounts specified via `txpool.locals` flag. 332 func (s *Quai) isLocalBlock(header *types.Header) bool { 333 author, err := s.engine.Author(header) 334 if err != nil { 335 log.Warn("Failed to retrieve block author", "number", header.NumberU64(), "hash", header.Hash(), "err", err) 336 return false 337 } 338 // Check whether the given address is etherbase. 339 s.lock.RLock() 340 etherbase := s.etherbase 341 s.lock.RUnlock() 342 if author.Equal(etherbase) { 343 return true 344 } 345 internal, err := author.InternalAddress() 346 if err != nil { 347 log.Error("Failed to retrieve author internal address", "err", err) 348 } 349 // Check whether the given address is specified by `txpool.local` 350 // CLI flag. 351 for _, account := range s.config.TxPool.Locals { 352 if account == internal { 353 return true 354 } 355 } 356 return false 357 } 358 359 // shouldPreserve checks whether we should preserve the given block 360 // during the chain reorg depending on whether the author of block 361 // is a local account. 362 func (s *Quai) shouldPreserve(block *types.Block) bool { 363 return s.isLocalBlock(block.Header()) 364 } 365 366 func (s *Quai) Core() *core.Core { return s.core } 367 func (s *Quai) EventMux() *event.TypeMux { return s.eventMux } 368 func (s *Quai) Engine() consensus.Engine { return s.engine } 369 func (s *Quai) ChainDb() ethdb.Database { return s.chainDb } 370 func (s *Quai) IsListening() bool { return true } // Always listening 371 func (s *Quai) Downloader() *downloader.Downloader { return s.handler.downloader } 372 func (s *Quai) Synced() bool { return atomic.LoadUint32(&s.handler.acceptTxs) == 1 } 373 func (s *Quai) ArchiveMode() bool { return s.config.NoPruning } 374 func (s *Quai) BloomIndexer() *core.ChainIndexer { return s.bloomIndexer } 375 376 // Protocols returns all the currently configured 377 // network protocols to start. 378 func (s *Quai) Protocols() []p2p.Protocol { 379 protos := eth.MakeProtocols((*ethHandler)(s.handler), s.networkID, s.ethDialCandidates) 380 return protos 381 } 382 383 // Start implements node.Lifecycle, starting all internal goroutines needed by the 384 // Quai protocol implementation. 385 func (s *Quai) Start() error { 386 eth.StartENRUpdater(s.core, s.p2pServer.LocalNode()) 387 388 if s.core.ProcessingState() && common.NodeLocation.Context() == common.ZONE_CTX { 389 // Start the bloom bits servicing goroutines 390 s.startBloomHandlers(params.BloomBitsBlocks) 391 } 392 393 // Figure out a max peers count based on the server limits 394 maxPeers := s.p2pServer.MaxPeers 395 // Start the networking layer 396 s.handler.Start(maxPeers) 397 return nil 398 } 399 400 // Stop implements node.Lifecycle, terminating all internal goroutines used by the 401 // Quai protocol. 402 func (s *Quai) Stop() error { 403 // Stop all the peer-related stuff first. 404 s.ethDialCandidates.Close() 405 s.handler.Stop() 406 407 if s.core.ProcessingState() && common.NodeLocation.Context() == common.ZONE_CTX { 408 // Then stop everything else. 409 s.bloomIndexer.Close() 410 close(s.closeBloomHandler) 411 } 412 s.core.Stop() 413 s.engine.Close() 414 rawdb.PopUncleanShutdownMarker(s.chainDb) 415 s.chainDb.Close() 416 s.eventMux.Stop() 417 418 return nil 419 }