github.com/dim4egster/coreth@v0.10.2/eth/backend.go (about) 1 // (c) 2019-2020, Ava Labs, Inc. 2 // 3 // This file is a derived work, based on the go-ethereum library whose original 4 // notices appear below. 5 // 6 // It is distributed under a license compatible with the licensing terms of the 7 // original code from which it is derived. 8 // 9 // Much love to the original authors for their work. 10 // ********** 11 // Copyright 2014 The go-ethereum Authors 12 // This file is part of the go-ethereum library. 13 // 14 // The go-ethereum library is free software: you can redistribute it and/or modify 15 // it under the terms of the GNU Lesser General Public License as published by 16 // the Free Software Foundation, either version 3 of the License, or 17 // (at your option) any later version. 18 // 19 // The go-ethereum library is distributed in the hope that it will be useful, 20 // but WITHOUT ANY WARRANTY; without even the implied warranty of 21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 // GNU Lesser General Public License for more details. 23 // 24 // You should have received a copy of the GNU Lesser General Public License 25 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 26 27 // Package eth implements the Ethereum protocol. 28 package eth 29 30 import ( 31 "errors" 32 "fmt" 33 "strings" 34 "sync" 35 "time" 36 37 "github.com/dim4egster/qmallgo/utils/timer/mockable" 38 "github.com/dim4egster/coreth/accounts" 39 "github.com/dim4egster/coreth/consensus" 40 "github.com/dim4egster/coreth/consensus/dummy" 41 "github.com/dim4egster/coreth/core" 42 "github.com/dim4egster/coreth/core/bloombits" 43 "github.com/dim4egster/coreth/core/rawdb" 44 "github.com/dim4egster/coreth/core/state/pruner" 45 "github.com/dim4egster/coreth/core/types" 46 "github.com/dim4egster/coreth/core/vm" 47 "github.com/dim4egster/coreth/eth/ethconfig" 48 "github.com/dim4egster/coreth/eth/filters" 49 "github.com/dim4egster/coreth/eth/gasprice" 50 "github.com/dim4egster/coreth/eth/tracers" 51 "github.com/dim4egster/coreth/ethdb" 52 "github.com/dim4egster/coreth/internal/ethapi" 53 "github.com/dim4egster/coreth/internal/shutdowncheck" 54 "github.com/dim4egster/coreth/miner" 55 "github.com/dim4egster/coreth/node" 56 "github.com/dim4egster/coreth/params" 57 "github.com/dim4egster/coreth/rpc" 58 "github.com/ethereum/go-ethereum/common" 59 "github.com/ethereum/go-ethereum/event" 60 "github.com/ethereum/go-ethereum/log" 61 ) 62 63 // Config contains the configuration options of the ETH protocol. 64 // Deprecated: use ethconfig.Config instead. 65 type Config = ethconfig.Config 66 67 var DefaultSettings Settings = Settings{MaxBlocksPerRequest: 2000} 68 69 type Settings struct { 70 MaxBlocksPerRequest int64 // Maximum number of blocks to serve per getLogs request 71 } 72 73 // Ethereum implements the Ethereum full node service. 74 type Ethereum struct { 75 config *Config 76 77 // Handlers 78 txPool *core.TxPool 79 blockchain *core.BlockChain 80 81 // DB interfaces 82 chainDb ethdb.Database // Block chain database 83 84 eventMux *event.TypeMux 85 engine consensus.Engine 86 accountManager *accounts.Manager 87 88 bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests 89 bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports 90 closeBloomHandler chan struct{} 91 92 APIBackend *EthAPIBackend 93 94 miner *miner.Miner 95 etherbase common.Address 96 97 networkID uint64 98 netRPCService *ethapi.NetAPI 99 100 lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase) 101 102 shutdownTracker *shutdowncheck.ShutdownTracker // Tracks if and when the node has shutdown ungracefully 103 104 stackRPCs []rpc.API 105 106 settings Settings // Settings for Ethereum API 107 } 108 109 // roundUpCacheSize returns [input] rounded up to the next multiple of [allocSize] 110 func roundUpCacheSize(input int, allocSize int) int { 111 cacheChunks := (input + allocSize - 1) / allocSize 112 return cacheChunks * allocSize 113 } 114 115 // New creates a new Ethereum object (including the 116 // initialisation of the common Ethereum object) 117 func New( 118 stack *node.Node, 119 config *Config, 120 cb *dummy.ConsensusCallbacks, 121 chainDb ethdb.Database, 122 settings Settings, 123 lastAcceptedHash common.Hash, 124 clock *mockable.Clock, 125 ) (*Ethereum, error) { 126 if chainDb == nil { 127 return nil, errors.New("chainDb cannot be nil") 128 } 129 if !config.Pruning && config.TrieDirtyCache > 0 { 130 // If snapshots are enabled, allocate 2/5 of the TrieDirtyCache memory cap to the snapshot cache 131 if config.SnapshotCache > 0 { 132 config.TrieCleanCache += config.TrieDirtyCache * 3 / 5 133 config.SnapshotCache += config.TrieDirtyCache * 2 / 5 134 } else { 135 // If snapshots are disabled, the TrieDirtyCache will be written through to the clean cache 136 // so move the cache allocation from the dirty cache to the clean cache 137 config.TrieCleanCache += config.TrieDirtyCache 138 config.TrieDirtyCache = 0 139 } 140 } 141 142 // round TrieCleanCache and SnapshotCache up to nearest 64MB, since fastcache will mmap 143 // memory in 64MBs chunks. 144 config.TrieCleanCache = roundUpCacheSize(config.TrieCleanCache, 64) 145 config.SnapshotCache = roundUpCacheSize(config.SnapshotCache, 64) 146 147 log.Info( 148 "Allocated trie memory caches", 149 "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, 150 "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024, 151 ) 152 153 chainConfig, genesisErr := core.SetupGenesisBlock(chainDb, config.Genesis) 154 if genesisErr != nil { 155 return nil, genesisErr 156 } 157 log.Info("") 158 log.Info(strings.Repeat("-", 153)) 159 for _, line := range strings.Split(chainConfig.String(), "\n") { 160 log.Info(line) 161 } 162 log.Info(strings.Repeat("-", 153)) 163 log.Info("") 164 165 // Note: RecoverPruning must be called to handle the case that we are midway through offline pruning. 166 // If the data directory is changed in between runs preventing RecoverPruning from performing its job correctly, 167 // it may cause DB corruption. 168 // Since RecoverPruning will only continue a pruning run that already began, we do not need to ensure that 169 // reprocessState has already been called and completed successfully. To ensure this, we must maintain 170 // that Prune is only run after reprocessState has finished successfully. 171 if err := pruner.RecoverPruning(config.OfflinePruningDataDirectory, chainDb); err != nil { 172 log.Error("Failed to recover state", "error", err) 173 } 174 eth := &Ethereum{ 175 config: config, 176 chainDb: chainDb, 177 eventMux: new(event.TypeMux), 178 accountManager: stack.AccountManager(), 179 engine: dummy.NewDummyEngine(cb), 180 closeBloomHandler: make(chan struct{}), 181 networkID: config.NetworkId, 182 etherbase: config.Miner.Etherbase, 183 bloomRequests: make(chan chan *bloombits.Retrieval), 184 bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms), 185 settings: settings, 186 shutdownTracker: shutdowncheck.NewShutdownTracker(chainDb), 187 } 188 189 bcVersion := rawdb.ReadDatabaseVersion(chainDb) 190 dbVer := "<nil>" 191 if bcVersion != nil { 192 dbVer = fmt.Sprintf("%d", *bcVersion) 193 } 194 log.Info("Initialising Ethereum protocol", "network", config.NetworkId, "dbversion", dbVer) 195 196 if !config.SkipBcVersionCheck { 197 if bcVersion != nil && *bcVersion > core.BlockChainVersion { 198 return nil, fmt.Errorf("database version is v%d, Coreth %s only supports v%d", *bcVersion, params.VersionWithMeta, core.BlockChainVersion) 199 } else if bcVersion == nil || *bcVersion < core.BlockChainVersion { 200 log.Warn("Upgrade blockchain database version", "from", dbVer, "to", core.BlockChainVersion) 201 rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion) 202 } 203 } 204 var ( 205 vmConfig = vm.Config{ 206 EnablePreimageRecording: config.EnablePreimageRecording, 207 AllowUnfinalizedQueries: config.AllowUnfinalizedQueries, 208 } 209 cacheConfig = &core.CacheConfig{ 210 TrieCleanLimit: config.TrieCleanCache, 211 TrieDirtyLimit: config.TrieDirtyCache, 212 TrieDirtyCommitTarget: config.TrieDirtyCommitTarget, 213 Pruning: config.Pruning, 214 AcceptorQueueLimit: config.AcceptorQueueLimit, 215 CommitInterval: config.CommitInterval, 216 PopulateMissingTries: config.PopulateMissingTries, 217 PopulateMissingTriesParallelism: config.PopulateMissingTriesParallelism, 218 AllowMissingTries: config.AllowMissingTries, 219 SnapshotDelayInit: config.SnapshotDelayInit, 220 SnapshotLimit: config.SnapshotCache, 221 SnapshotAsync: config.SnapshotAsync, 222 SnapshotVerify: config.SnapshotVerify, 223 SkipSnapshotRebuild: config.SkipSnapshotRebuild, 224 Preimages: config.Preimages, 225 } 226 ) 227 228 if err := eth.precheckPopulateMissingTries(); err != nil { 229 return nil, err 230 } 231 232 var err error 233 eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, lastAcceptedHash) 234 if err != nil { 235 return nil, err 236 } 237 238 if err := eth.handleOfflinePruning(cacheConfig, chainConfig, vmConfig, lastAcceptedHash); err != nil { 239 return nil, err 240 } 241 242 eth.bloomIndexer.Start(eth.blockchain) 243 244 config.TxPool.Journal = "" 245 eth.txPool = core.NewTxPool(config.TxPool, chainConfig, eth.blockchain) 246 247 eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, clock) 248 249 eth.APIBackend = &EthAPIBackend{ 250 extRPCEnabled: stack.Config().ExtRPCEnabled(), 251 allowUnprotectedTxs: config.AllowUnprotectedTxs, 252 eth: eth, 253 } 254 if config.AllowUnprotectedTxs { 255 log.Info("Unprotected transactions allowed") 256 } 257 gpoParams := config.GPO 258 eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams) 259 260 if err != nil { 261 return nil, err 262 } 263 264 // Start the RPC service 265 eth.netRPCService = ethapi.NewNetAPI(eth.NetVersion()) 266 267 eth.stackRPCs = stack.APIs() 268 269 // Successful startup; push a marker and check previous unclean shutdowns. 270 eth.shutdownTracker.MarkStartup() 271 272 return eth, nil 273 } 274 275 // APIs return the collection of RPC services the ethereum package offers. 276 // NOTE, some of these services probably need to be moved to somewhere else. 277 func (s *Ethereum) APIs() []rpc.API { 278 apis := ethapi.GetAPIs(s.APIBackend) 279 280 // Append tracing APIs 281 apis = append(apis, tracers.APIs(s.APIBackend)...) 282 283 // Add the APIs from the node 284 apis = append(apis, s.stackRPCs...) 285 286 // Create [filterSystem] with the log cache size set in the config. 287 ethcfg := s.APIBackend.eth.config 288 filterSystem := filters.NewFilterSystem(s.APIBackend, filters.Config{ 289 LogCacheSize: ethcfg.FilterLogCacheSize, 290 Timeout: 5 * time.Minute, 291 }) 292 293 // Append all the local APIs and return 294 return append(apis, []rpc.API{ 295 { 296 Namespace: "eth", 297 Service: NewEthereumAPI(s), 298 Name: "eth", 299 }, { 300 Namespace: "eth", 301 Service: filters.NewFilterAPI(filterSystem, false /* isLightClient */), 302 Name: "eth-filter", 303 }, { 304 Namespace: "admin", 305 Service: NewAdminAPI(s), 306 Name: "admin", 307 }, { 308 Namespace: "debug", 309 Service: NewDebugAPI(s), 310 Name: "debug", 311 }, { 312 Namespace: "net", 313 Service: s.netRPCService, 314 Name: "net", 315 }, 316 }...) 317 } 318 319 func (s *Ethereum) Etherbase() (eb common.Address, err error) { 320 s.lock.RLock() 321 etherbase := s.etherbase 322 s.lock.RUnlock() 323 324 if etherbase != (common.Address{}) { 325 return etherbase, nil 326 } 327 if wallets := s.AccountManager().Wallets(); len(wallets) > 0 { 328 if accounts := wallets[0].Accounts(); len(accounts) > 0 { 329 etherbase := accounts[0].Address 330 331 s.lock.Lock() 332 s.etherbase = etherbase 333 s.lock.Unlock() 334 335 log.Info("Etherbase automatically configured", "address", etherbase) 336 return etherbase, nil 337 } 338 } 339 return common.Address{}, fmt.Errorf("etherbase must be explicitly specified") 340 } 341 342 // SetEtherbase sets the mining reward address. 343 func (s *Ethereum) SetEtherbase(etherbase common.Address) { 344 s.lock.Lock() 345 s.etherbase = etherbase 346 s.lock.Unlock() 347 348 s.miner.SetEtherbase(etherbase) 349 } 350 351 func (s *Ethereum) Miner() *miner.Miner { return s.miner } 352 353 func (s *Ethereum) AccountManager() *accounts.Manager { return s.accountManager } 354 func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain } 355 func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } 356 func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } 357 func (s *Ethereum) Engine() consensus.Engine { return s.engine } 358 func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } 359 360 func (s *Ethereum) NetVersion() uint64 { return s.networkID } 361 func (s *Ethereum) ArchiveMode() bool { return !s.config.Pruning } 362 func (s *Ethereum) BloomIndexer() *core.ChainIndexer { return s.bloomIndexer } 363 364 // Start implements node.Lifecycle, starting all internal goroutines needed by the 365 // Ethereum protocol implementation. 366 func (s *Ethereum) Start() { 367 // Start the bloom bits servicing goroutines 368 s.startBloomHandlers(params.BloomBitsBlocks) 369 370 // Regularly update shutdown marker 371 s.shutdownTracker.Start() 372 } 373 374 // Stop implements node.Lifecycle, terminating all internal goroutines used by the 375 // Ethereum protocol. 376 // FIXME remove error from type if this will never return an error 377 func (s *Ethereum) Stop() error { 378 s.bloomIndexer.Close() 379 close(s.closeBloomHandler) 380 s.txPool.Stop() 381 s.blockchain.Stop() 382 s.engine.Close() 383 384 // Clean shutdown marker as the last thing before closing db 385 s.shutdownTracker.Stop() 386 387 s.chainDb.Close() 388 s.eventMux.Stop() 389 return nil 390 } 391 392 func (s *Ethereum) LastAcceptedBlock() *types.Block { 393 return s.blockchain.LastAcceptedBlock() 394 } 395 396 // precheckPopulateMissingTries returns an error if config flags should prevent 397 // [populateMissingTries] 398 // 399 // NOTE: [populateMissingTries] is called from [New] to ensure all 400 // state is repaired before any async processes (specifically snapshot re-generation) 401 // are started which could interfere with historical re-generation. 402 func (s *Ethereum) precheckPopulateMissingTries() error { 403 if s.config.PopulateMissingTries != nil && (s.config.Pruning || s.config.OfflinePruning) { 404 return fmt.Errorf("cannot run populate missing tries when pruning (enabled: %t)/offline pruning (enabled: %t) is enabled", s.config.Pruning, s.config.OfflinePruning) 405 } 406 407 if s.config.PopulateMissingTries == nil { 408 // Delete the populate missing tries marker to indicate that the node started with 409 // populate missing tries disabled. 410 if err := rawdb.DeletePopulateMissingTries(s.chainDb); err != nil { 411 return fmt.Errorf("failed to write populate missing tries disabled marker: %w", err) 412 } 413 return nil 414 } 415 416 if lastRun, err := rawdb.ReadPopulateMissingTries(s.chainDb); err == nil { 417 log.Error("Populate missing tries is not meant to be left enabled permanently. Please disable populate missing tries and allow your node to start successfully before running again.") 418 return fmt.Errorf("cannot start chain with populate missing tries enabled on consecutive starts (last=%v)", lastRun) 419 } 420 421 // Note: Time Marker is written inside of [populateMissingTries] once it 422 // succeeds inside of [NewBlockChain] 423 return nil 424 } 425 426 func (s *Ethereum) handleOfflinePruning(cacheConfig *core.CacheConfig, chainConfig *params.ChainConfig, vmConfig vm.Config, lastAcceptedHash common.Hash) error { 427 if s.config.OfflinePruning && !s.config.Pruning { 428 return core.ErrRefuseToCorruptArchiver 429 } 430 431 if !s.config.OfflinePruning { 432 // Delete the offline pruning marker to indicate that the node started with offline pruning disabled. 433 if err := rawdb.DeleteOfflinePruning(s.chainDb); err != nil { 434 return fmt.Errorf("failed to write offline pruning disabled marker: %w", err) 435 } 436 return nil 437 } 438 439 // Perform offline pruning after NewBlockChain has been called to ensure that we have rolled back the chain 440 // to the last accepted block before pruning begins. 441 // If offline pruning marker is on disk, then we force the node to be started with offline pruning disabled 442 // before allowing another run of offline pruning. 443 if lastRun, err := rawdb.ReadOfflinePruning(s.chainDb); err == nil { 444 log.Error("Offline pruning is not meant to be left enabled permanently. Please disable offline pruning and allow your node to start successfully before running offline pruning again.") 445 return fmt.Errorf("cannot start chain with offline pruning enabled on consecutive starts (last=%v)", lastRun) 446 } 447 448 // Clean up middle roots 449 if err := s.blockchain.CleanBlockRootsAboveLastAccepted(); err != nil { 450 return err 451 } 452 targetRoot := s.blockchain.LastAcceptedBlock().Root() 453 454 // Allow the blockchain to be garbage collected immediately, since we will shut down the chain after offline pruning completes. 455 s.blockchain.Stop() 456 s.blockchain = nil 457 log.Info("Starting offline pruning", "dataDir", s.config.OfflinePruningDataDirectory, "bloomFilterSize", s.config.OfflinePruningBloomFilterSize) 458 pruner, err := pruner.NewPruner(s.chainDb, s.config.OfflinePruningDataDirectory, s.config.OfflinePruningBloomFilterSize) 459 if err != nil { 460 return fmt.Errorf("failed to create new pruner with data directory: %s, size: %d, due to: %w", s.config.OfflinePruningDataDirectory, s.config.OfflinePruningBloomFilterSize, err) 461 } 462 if err := pruner.Prune(targetRoot); err != nil { 463 return fmt.Errorf("failed to prune blockchain with target root: %s due to: %w", targetRoot, err) 464 } 465 // Note: Time Marker is written inside of [Prune] before compaction begins 466 // (considered an optional optimization) 467 s.blockchain, err = core.NewBlockChain(s.chainDb, cacheConfig, chainConfig, s.engine, vmConfig, lastAcceptedHash) 468 if err != nil { 469 return fmt.Errorf("failed to re-initialize blockchain after offline pruning: %w", err) 470 } 471 472 return nil 473 }