github.com/MetalBlockchain/subnet-evm@v0.4.9/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/MetalBlockchain/metalgo/utils/timer/mockable" 38 "github.com/MetalBlockchain/subnet-evm/accounts" 39 "github.com/MetalBlockchain/subnet-evm/consensus" 40 "github.com/MetalBlockchain/subnet-evm/consensus/dummy" 41 "github.com/MetalBlockchain/subnet-evm/core" 42 "github.com/MetalBlockchain/subnet-evm/core/bloombits" 43 "github.com/MetalBlockchain/subnet-evm/core/rawdb" 44 "github.com/MetalBlockchain/subnet-evm/core/state/pruner" 45 "github.com/MetalBlockchain/subnet-evm/core/types" 46 "github.com/MetalBlockchain/subnet-evm/core/vm" 47 "github.com/MetalBlockchain/subnet-evm/eth/ethconfig" 48 "github.com/MetalBlockchain/subnet-evm/eth/filters" 49 "github.com/MetalBlockchain/subnet-evm/eth/gasprice" 50 "github.com/MetalBlockchain/subnet-evm/eth/tracers" 51 "github.com/MetalBlockchain/subnet-evm/ethdb" 52 "github.com/MetalBlockchain/subnet-evm/internal/ethapi" 53 "github.com/MetalBlockchain/subnet-evm/internal/shutdowncheck" 54 "github.com/MetalBlockchain/subnet-evm/miner" 55 "github.com/MetalBlockchain/subnet-evm/node" 56 "github.com/MetalBlockchain/subnet-evm/params" 57 "github.com/MetalBlockchain/subnet-evm/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 chainDb ethdb.Database, 121 settings Settings, 122 lastAcceptedHash common.Hash, 123 clock *mockable.Clock, 124 ) (*Ethereum, error) { 125 if chainDb == nil { 126 return nil, errors.New("chainDb cannot be nil") 127 } 128 129 // round TrieCleanCache and SnapshotCache up to nearest 64MB, since fastcache will mmap 130 // memory in 64MBs chunks. 131 config.TrieCleanCache = roundUpCacheSize(config.TrieCleanCache, 64) 132 config.SnapshotCache = roundUpCacheSize(config.SnapshotCache, 64) 133 134 log.Info( 135 "Allocated memory caches", 136 "trie clean", common.StorageSize(config.TrieCleanCache)*1024*1024, 137 "trie dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024, 138 "snapshot clean", common.StorageSize(config.SnapshotCache)*1024*1024, 139 ) 140 141 chainConfig, genesisErr := core.SetupGenesisBlock(chainDb, config.Genesis, lastAcceptedHash, config.SkipUpgradeCheck) 142 if genesisErr != nil { 143 return nil, genesisErr 144 } 145 log.Info("") 146 log.Info(strings.Repeat("-", 153)) 147 for _, line := range strings.Split(chainConfig.String(), "\n") { 148 log.Info(line) 149 } 150 log.Info(strings.Repeat("-", 153)) 151 log.Info("") 152 // Free airdrop data to save memory usage 153 config.Genesis.AirdropData = nil 154 155 // Note: RecoverPruning must be called to handle the case that we are midway through offline pruning. 156 // If the data directory is changed in between runs preventing RecoverPruning from performing its job correctly, 157 // it may cause DB corruption. 158 // Since RecoverPruning will only continue a pruning run that already began, we do not need to ensure that 159 // reprocessState has already been called and completed successfully. To ensure this, we must maintain 160 // that Prune is only run after reprocessState has finished successfully. 161 if err := pruner.RecoverPruning(config.OfflinePruningDataDirectory, chainDb); err != nil { 162 log.Error("Failed to recover state", "error", err) 163 } 164 eth := &Ethereum{ 165 config: config, 166 chainDb: chainDb, 167 eventMux: new(event.TypeMux), 168 accountManager: stack.AccountManager(), 169 engine: dummy.NewFakerWithClock(clock), 170 closeBloomHandler: make(chan struct{}), 171 networkID: config.NetworkId, 172 etherbase: config.Miner.Etherbase, 173 bloomRequests: make(chan chan *bloombits.Retrieval), 174 bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms), 175 settings: settings, 176 shutdownTracker: shutdowncheck.NewShutdownTracker(chainDb), 177 } 178 179 bcVersion := rawdb.ReadDatabaseVersion(chainDb) 180 dbVer := "<nil>" 181 if bcVersion != nil { 182 dbVer = fmt.Sprintf("%d", *bcVersion) 183 } 184 log.Info("Initialising Ethereum protocol", "network", config.NetworkId, "dbversion", dbVer) 185 186 if !config.SkipBcVersionCheck { 187 if bcVersion != nil && *bcVersion > core.BlockChainVersion { 188 return nil, fmt.Errorf("database version is v%d, Subnet EVM %s only supports v%d", *bcVersion, params.VersionWithMeta, core.BlockChainVersion) 189 } else if bcVersion == nil || *bcVersion < core.BlockChainVersion { 190 log.Warn("Upgrade blockchain database version", "from", dbVer, "to", core.BlockChainVersion) 191 rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion) 192 } 193 } 194 var ( 195 vmConfig = vm.Config{ 196 EnablePreimageRecording: config.EnablePreimageRecording, 197 AllowUnfinalizedQueries: config.AllowUnfinalizedQueries, 198 } 199 cacheConfig = &core.CacheConfig{ 200 TrieCleanLimit: config.TrieCleanCache, 201 TrieCleanJournal: config.TrieCleanJournal, 202 TrieCleanRejournal: config.TrieCleanRejournal, 203 TrieDirtyLimit: config.TrieDirtyCache, 204 TrieDirtyCommitTarget: config.TrieDirtyCommitTarget, 205 Pruning: config.Pruning, 206 AcceptorQueueLimit: config.AcceptorQueueLimit, 207 CommitInterval: config.CommitInterval, 208 PopulateMissingTries: config.PopulateMissingTries, 209 PopulateMissingTriesParallelism: config.PopulateMissingTriesParallelism, 210 AllowMissingTries: config.AllowMissingTries, 211 SnapshotDelayInit: config.SnapshotDelayInit, 212 SnapshotLimit: config.SnapshotCache, 213 SnapshotAsync: config.SnapshotAsync, 214 SnapshotVerify: config.SnapshotVerify, 215 SkipSnapshotRebuild: config.SkipSnapshotRebuild, 216 Preimages: config.Preimages, 217 AcceptedCacheSize: config.AcceptedCacheSize, 218 TxLookupLimit: config.TxLookupLimit, 219 } 220 ) 221 222 if err := eth.precheckPopulateMissingTries(); err != nil { 223 return nil, err 224 } 225 226 var err error 227 eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, lastAcceptedHash) 228 if err != nil { 229 return nil, err 230 } 231 232 if err := eth.handleOfflinePruning(cacheConfig, chainConfig, vmConfig, lastAcceptedHash); err != nil { 233 return nil, err 234 } 235 236 eth.bloomIndexer.Start(eth.blockchain) 237 238 config.TxPool.Journal = "" 239 eth.txPool = core.NewTxPool(config.TxPool, chainConfig, eth.blockchain) 240 241 eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, clock) 242 243 allowUnprotectedTxHashes := make(map[common.Hash]struct{}) 244 for _, txHash := range config.AllowUnprotectedTxHashes { 245 allowUnprotectedTxHashes[txHash] = struct{}{} 246 } 247 248 eth.APIBackend = &EthAPIBackend{ 249 extRPCEnabled: stack.Config().ExtRPCEnabled(), 250 allowUnprotectedTxs: config.AllowUnprotectedTxs, 251 allowUnprotectedTxHashes: allowUnprotectedTxHashes, 252 eth: eth, 253 } 254 if config.AllowUnprotectedTxs { 255 log.Info("Unprotected transactions allowed") 256 } 257 gpoParams := config.GPO 258 eth.APIBackend.gpo, err = gasprice.NewOracle(eth.APIBackend, gpoParams) 259 if err != nil { 260 return nil, err 261 } 262 263 // Start the RPC service 264 eth.netRPCService = ethapi.NewNetAPI(eth.NetVersion()) 265 266 eth.stackRPCs = stack.APIs() 267 268 // Successful startup; push a marker and check previous unclean shutdowns. 269 eth.shutdownTracker.MarkStartup() 270 271 return eth, nil 272 } 273 274 // APIs return the collection of RPC services the ethereum package offers. 275 // NOTE, some of these services probably need to be moved to somewhere else. 276 func (s *Ethereum) APIs() []rpc.API { 277 apis := ethapi.GetAPIs(s.APIBackend) 278 279 // Append tracing APIs 280 apis = append(apis, tracers.APIs(s.APIBackend)...) 281 282 // Add the APIs from the node 283 apis = append(apis, s.stackRPCs...) 284 285 // Create [filterSystem] with the log cache size set in the config. 286 filterSystem := filters.NewFilterSystem(s.APIBackend, filters.Config{ 287 Timeout: 5 * time.Minute, 288 }) 289 290 // Append all the local APIs and return 291 return append(apis, []rpc.API{ 292 { 293 Namespace: "eth", 294 Service: NewEthereumAPI(s), 295 Name: "eth", 296 }, { 297 Namespace: "eth", 298 Service: filters.NewFilterAPI(filterSystem, false /* isLightClient */), 299 Name: "eth-filter", 300 }, { 301 Namespace: "admin", 302 Service: NewAdminAPI(s), 303 Name: "admin", 304 }, { 305 Namespace: "debug", 306 Service: NewDebugAPI(s), 307 Name: "debug", 308 }, { 309 Namespace: "net", 310 Service: s.netRPCService, 311 Name: "net", 312 }, 313 }...) 314 } 315 316 func (s *Ethereum) Etherbase() (eb common.Address, err error) { 317 s.lock.RLock() 318 etherbase := s.etherbase 319 s.lock.RUnlock() 320 321 if etherbase != (common.Address{}) { 322 return etherbase, nil 323 } 324 if wallets := s.AccountManager().Wallets(); len(wallets) > 0 { 325 if accounts := wallets[0].Accounts(); len(accounts) > 0 { 326 etherbase := accounts[0].Address 327 328 s.lock.Lock() 329 s.etherbase = etherbase 330 s.lock.Unlock() 331 332 log.Info("Etherbase automatically configured", "address", etherbase) 333 return etherbase, nil 334 } 335 } 336 return common.Address{}, fmt.Errorf("etherbase must be explicitly specified") 337 } 338 339 // SetEtherbase sets the mining reward address. 340 func (s *Ethereum) SetEtherbase(etherbase common.Address) { 341 s.lock.Lock() 342 s.etherbase = etherbase 343 s.lock.Unlock() 344 345 s.miner.SetEtherbase(etherbase) 346 } 347 348 func (s *Ethereum) Miner() *miner.Miner { return s.miner } 349 350 func (s *Ethereum) AccountManager() *accounts.Manager { return s.accountManager } 351 func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain } 352 func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } 353 func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } 354 func (s *Ethereum) Engine() consensus.Engine { return s.engine } 355 func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } 356 357 func (s *Ethereum) NetVersion() uint64 { return s.networkID } 358 func (s *Ethereum) ArchiveMode() bool { return !s.config.Pruning } 359 func (s *Ethereum) BloomIndexer() *core.ChainIndexer { return s.bloomIndexer } 360 361 // Start implements node.Lifecycle, starting all internal goroutines needed by the 362 // Ethereum protocol implementation. 363 func (s *Ethereum) Start() { 364 // Start the bloom bits servicing goroutines 365 s.startBloomHandlers(params.BloomBitsBlocks) 366 367 // Regularly update shutdown marker 368 s.shutdownTracker.Start() 369 } 370 371 // Stop implements node.Lifecycle, terminating all internal goroutines used by the 372 // Ethereum protocol. 373 // FIXME remove error from type if this will never return an error 374 func (s *Ethereum) Stop() error { 375 s.bloomIndexer.Close() 376 close(s.closeBloomHandler) 377 s.txPool.Stop() 378 s.blockchain.Stop() 379 s.engine.Close() 380 381 // Clean shutdown marker as the last thing before closing db 382 s.shutdownTracker.Stop() 383 384 s.chainDb.Close() 385 s.eventMux.Stop() 386 return nil 387 } 388 389 func (s *Ethereum) LastAcceptedBlock() *types.Block { 390 return s.blockchain.LastAcceptedBlock() 391 } 392 393 // precheckPopulateMissingTries returns an error if config flags should prevent 394 // [populateMissingTries] 395 // 396 // NOTE: [populateMissingTries] is called from [New] to ensure all 397 // state is repaired before any async processes (specifically snapshot re-generation) 398 // are started which could interfere with historical re-generation. 399 func (s *Ethereum) precheckPopulateMissingTries() error { 400 if s.config.PopulateMissingTries != nil && (s.config.Pruning || s.config.OfflinePruning) { 401 return fmt.Errorf("cannot run populate missing tries when pruning (enabled: %t)/offline pruning (enabled: %t) is enabled", s.config.Pruning, s.config.OfflinePruning) 402 } 403 404 if s.config.PopulateMissingTries == nil { 405 // Delete the populate missing tries marker to indicate that the node started with 406 // populate missing tries disabled. 407 if err := rawdb.DeletePopulateMissingTries(s.chainDb); err != nil { 408 return fmt.Errorf("failed to write populate missing tries disabled marker: %w", err) 409 } 410 return nil 411 } 412 413 if lastRun, err := rawdb.ReadPopulateMissingTries(s.chainDb); err == nil { 414 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.") 415 return fmt.Errorf("cannot start chain with populate missing tries enabled on consecutive starts (last=%v)", lastRun) 416 } 417 418 // Note: Time Marker is written inside of [populateMissingTries] once it 419 // succeeds inside of [NewBlockChain] 420 return nil 421 } 422 423 func (s *Ethereum) handleOfflinePruning(cacheConfig *core.CacheConfig, chainConfig *params.ChainConfig, vmConfig vm.Config, lastAcceptedHash common.Hash) error { 424 if s.config.OfflinePruning && !s.config.Pruning { 425 return core.ErrRefuseToCorruptArchiver 426 } 427 428 if !s.config.OfflinePruning { 429 // Delete the offline pruning marker to indicate that the node started with offline pruning disabled. 430 if err := rawdb.DeleteOfflinePruning(s.chainDb); err != nil { 431 return fmt.Errorf("failed to write offline pruning disabled marker: %w", err) 432 } 433 return nil 434 } 435 436 // Perform offline pruning after NewBlockChain has been called to ensure that we have rolled back the chain 437 // to the last accepted block before pruning begins. 438 // If offline pruning marker is on disk, then we force the node to be started with offline pruning disabled 439 // before allowing another run of offline pruning. 440 if lastRun, err := rawdb.ReadOfflinePruning(s.chainDb); err == nil { 441 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.") 442 return fmt.Errorf("cannot start chain with offline pruning enabled on consecutive starts (last=%v)", lastRun) 443 } 444 445 // Clean up middle roots 446 if err := s.blockchain.CleanBlockRootsAboveLastAccepted(); err != nil { 447 return err 448 } 449 targetRoot := s.blockchain.LastAcceptedBlock().Root() 450 451 // Allow the blockchain to be garbage collected immediately, since we will shut down the chain after offline pruning completes. 452 s.blockchain.Stop() 453 s.blockchain = nil 454 log.Info("Starting offline pruning", "dataDir", s.config.OfflinePruningDataDirectory, "bloomFilterSize", s.config.OfflinePruningBloomFilterSize) 455 pruner, err := pruner.NewPruner(s.chainDb, s.config.OfflinePruningDataDirectory, s.config.OfflinePruningBloomFilterSize) 456 if err != nil { 457 return fmt.Errorf("failed to create new pruner with data directory: %s, size: %d, due to: %w", s.config.OfflinePruningDataDirectory, s.config.OfflinePruningBloomFilterSize, err) 458 } 459 if err := pruner.Prune(targetRoot); err != nil { 460 return fmt.Errorf("failed to prune blockchain with target root: %s due to: %w", targetRoot, err) 461 } 462 // Note: Time Marker is written inside of [Prune] before compaction begins 463 // (considered an optional optimization) 464 s.blockchain, err = core.NewBlockChain(s.chainDb, cacheConfig, chainConfig, s.engine, vmConfig, lastAcceptedHash) 465 if err != nil { 466 return fmt.Errorf("failed to re-initialize blockchain after offline pruning: %w", err) 467 } 468 469 return nil 470 }