mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
578 lines
20 KiB
Go
578 lines
20 KiB
Go
// (c) 2020-2020, Lux Industries, Inc.
|
|
//
|
|
// This file is a derived work, based on the go-ethereum library whose original
|
|
// notices appear below.
|
|
//
|
|
// It is distributed under a license compatible with the licensing terms of the
|
|
// original code from which it is derived.
|
|
//
|
|
// Much love to the original authors for their work.
|
|
// **********
|
|
// Copyright 2014 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
// Package eth implements the Ethereum protocol.
|
|
package eth
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/luxfi/node/v2/utils/timer/mockable"
|
|
"github.com/luxfi/geth/accounts"
|
|
"github.com/luxfi/evm/consensus"
|
|
"github.com/luxfi/evm/commontype"
|
|
"github.com/luxfi/evm/core"
|
|
"github.com/luxfi/evm/core/bloombits"
|
|
"github.com/luxfi/evm/core/rawdb"
|
|
"github.com/luxfi/evm/core/state"
|
|
"github.com/luxfi/evm/core/state/pruner"
|
|
"github.com/luxfi/evm/core/txpool"
|
|
"github.com/luxfi/evm/core/txpool/legacypool"
|
|
"github.com/luxfi/evm/core/types"
|
|
"github.com/luxfi/evm/core/vm"
|
|
"github.com/luxfi/evm/eth/ethconfig"
|
|
"github.com/luxfi/evm/eth/filters"
|
|
"github.com/luxfi/evm/eth/gasprice"
|
|
"github.com/luxfi/evm/eth/tracers"
|
|
"github.com/luxfi/evm/internal/ethapi"
|
|
"github.com/luxfi/evm/internal/shutdowncheck"
|
|
"github.com/luxfi/evm/miner"
|
|
"github.com/luxfi/geth/node"
|
|
"github.com/luxfi/evm/params"
|
|
"github.com/luxfi/evm/rpc"
|
|
"github.com/luxfi/geth/common"
|
|
"github.com/luxfi/geth/ethdb"
|
|
"github.com/luxfi/geth/event"
|
|
"github.com/luxfi/log"
|
|
)
|
|
|
|
// Config contains the configuration options of the ETH protocol.
|
|
// Deprecated: use ethconfig.Config instead.
|
|
type Config = ethconfig.Config
|
|
|
|
var DefaultSettings Settings = Settings{MaxBlocksPerRequest: 2000}
|
|
|
|
type Settings struct {
|
|
MaxBlocksPerRequest int64 // Maximum number of blocks to serve per getLogs request
|
|
}
|
|
|
|
// PushGossiper sends pushes pending transactions to peers until they are
|
|
// removed from the mempool.
|
|
type PushGossiper interface {
|
|
Add(*types.Transaction)
|
|
}
|
|
|
|
// Ethereum implements the Ethereum full node service.
|
|
type Ethereum struct {
|
|
config *Config
|
|
|
|
// Handlers
|
|
txPool *txpool.TxPool
|
|
|
|
blockchain *core.BlockChain
|
|
gossiper PushGossiper
|
|
|
|
// DB interfaces
|
|
chainDb ethdb.Database // Block chain database
|
|
|
|
eventMux *event.TypeMux
|
|
engine consensus.Engine
|
|
accountManager *accounts.Manager
|
|
|
|
bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
|
|
bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports
|
|
closeBloomHandler chan struct{}
|
|
|
|
APIBackend *EthAPIBackend
|
|
|
|
miner *miner.Miner
|
|
etherbase common.Address
|
|
|
|
networkID uint64
|
|
netRPCService *ethapi.NetAPI
|
|
|
|
lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase)
|
|
|
|
shutdownTracker *shutdowncheck.ShutdownTracker // Tracks if and when the node has shutdown ungracefully
|
|
|
|
stackRPCs []rpc.API
|
|
|
|
settings Settings // Settings for Ethereum API
|
|
}
|
|
|
|
// blockChainWrapper wraps BlockChain to implement the interfaces needed by txpool and legacypool
|
|
type blockChainWrapper struct {
|
|
bc *core.BlockChain
|
|
}
|
|
|
|
// Config returns the chain configuration.
|
|
func (w *blockChainWrapper) Config() *params.ChainConfig {
|
|
// We know the underlying config is *params.ChainConfig
|
|
if cfg, ok := w.bc.Config().(*params.ChainConfig); ok {
|
|
return cfg
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CurrentBlock returns the current head of the chain.
|
|
func (w *blockChainWrapper) CurrentBlock() *types.Header {
|
|
return w.bc.CurrentBlock()
|
|
}
|
|
|
|
// GetBlock retrieves a block by hash and number.
|
|
func (w *blockChainWrapper) GetBlock(hash common.Hash, number uint64) *types.Block {
|
|
return w.bc.GetBlock(hash, number)
|
|
}
|
|
|
|
// StateAt returns a state database for a given root hash.
|
|
func (w *blockChainWrapper) StateAt(root common.Hash) (*state.StateDB, error) {
|
|
return w.bc.StateAt(root)
|
|
}
|
|
|
|
// SubscribeChainHeadEvent registers a subscription of ChainHeadEvent.
|
|
func (w *blockChainWrapper) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
|
|
return w.bc.SubscribeChainHeadEvent(ch)
|
|
}
|
|
|
|
// SenderCacher returns the transaction sender cacher.
|
|
func (w *blockChainWrapper) SenderCacher() *core.TxSenderCacher {
|
|
return w.bc.SenderCacher()
|
|
}
|
|
|
|
// GetFeeConfigAt returns the fee configuration at a given block.
|
|
func (w *blockChainWrapper) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) {
|
|
return w.bc.GetFeeConfigAtHeader(parent)
|
|
}
|
|
|
|
// mockableTimerWrapper wraps mockable.Clock to implement iface.MockableTimer
|
|
type mockableTimerWrapper struct {
|
|
clock *mockable.Clock
|
|
}
|
|
|
|
func (m *mockableTimerWrapper) Time() time.Time {
|
|
return m.clock.Time()
|
|
}
|
|
|
|
func (m *mockableTimerWrapper) Set(time time.Time) {
|
|
m.clock.Set(time)
|
|
}
|
|
|
|
func (m *mockableTimerWrapper) Advance(duration time.Duration) {
|
|
newTime := m.clock.Time().Add(duration)
|
|
m.clock.Set(newTime)
|
|
}
|
|
|
|
// roundUpCacheSize returns [input] rounded up to the next multiple of [allocSize]
|
|
func roundUpCacheSize(input int, allocSize int) int {
|
|
cacheChunks := (input + allocSize - 1) / allocSize
|
|
return cacheChunks * allocSize
|
|
}
|
|
|
|
// New creates a new Ethereum object (including the
|
|
// initialisation of the common Ethereum object)
|
|
func New(
|
|
stack *node.Node,
|
|
config *Config,
|
|
gossiper PushGossiper,
|
|
chainDb ethdb.Database,
|
|
settings Settings,
|
|
lastAcceptedHash common.Hash,
|
|
engine consensus.Engine,
|
|
clock *mockable.Clock,
|
|
) (*Ethereum, error) {
|
|
if chainDb == nil {
|
|
return nil, errors.New("chainDb cannot be nil")
|
|
}
|
|
|
|
// round TrieCleanCache and SnapshotCache up to nearest 64MB, since fastcache will mmap
|
|
// memory in 64MBs chunks.
|
|
config.TrieCleanCache = roundUpCacheSize(config.TrieCleanCache, 64)
|
|
config.SnapshotCache = roundUpCacheSize(config.SnapshotCache, 64)
|
|
|
|
log.Info(
|
|
"Allocated memory caches",
|
|
"trie clean", common.StorageSize(config.TrieCleanCache)*1024*1024,
|
|
"trie dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024,
|
|
"snapshot clean", common.StorageSize(config.SnapshotCache)*1024*1024,
|
|
)
|
|
|
|
scheme, err := rawdb.ParseStateScheme(config.StateScheme, chainDb)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Try to recover offline state pruning only in hash-based.
|
|
if scheme == rawdb.HashScheme {
|
|
// Note: RecoverPruning must be called to handle the case that we are midway through offline pruning.
|
|
// If the data directory is changed in between runs preventing RecoverPruning from performing its job correctly,
|
|
// it may cause DB corruption.
|
|
// Since RecoverPruning will only continue a pruning run that already began, we do not need to ensure that
|
|
// reprocessState has already been called and completed successfully. To ensure this, we must maintain
|
|
// that Prune is only run after reprocessState has finished successfully.
|
|
if err := pruner.RecoverPruning(config.OfflinePruningDataDirectory, chainDb); err != nil {
|
|
log.Error("Failed to recover state", "error", err)
|
|
}
|
|
}
|
|
|
|
networkID := config.NetworkId
|
|
if networkID == 0 {
|
|
networkID = config.Genesis.Config.ChainID.Uint64()
|
|
}
|
|
eth := &Ethereum{
|
|
config: config,
|
|
gossiper: gossiper,
|
|
chainDb: chainDb,
|
|
eventMux: new(event.TypeMux),
|
|
accountManager: stack.AccountManager(),
|
|
engine: engine,
|
|
closeBloomHandler: make(chan struct{}),
|
|
networkID: networkID,
|
|
etherbase: config.Miner.Etherbase,
|
|
bloomRequests: make(chan chan *bloombits.Retrieval),
|
|
bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms),
|
|
settings: settings,
|
|
shutdownTracker: shutdowncheck.NewShutdownTracker(chainDb),
|
|
}
|
|
bcVersion := rawdb.ReadDatabaseVersion(chainDb)
|
|
dbVer := "<nil>"
|
|
if bcVersion != nil {
|
|
dbVer = fmt.Sprintf("%d", *bcVersion)
|
|
}
|
|
log.Info("Initialising Ethereum protocol", "network", networkID, "dbversion", dbVer)
|
|
|
|
if !config.SkipBcVersionCheck {
|
|
if bcVersion != nil && *bcVersion > core.BlockChainVersion {
|
|
return nil, fmt.Errorf("database version is v%d, Geth %s only supports v%d", *bcVersion, params.VersionWithMeta, core.BlockChainVersion)
|
|
} else if bcVersion == nil || *bcVersion < core.BlockChainVersion {
|
|
log.Warn("Upgrade blockchain database version", "from", dbVer, "to", core.BlockChainVersion)
|
|
rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion)
|
|
}
|
|
}
|
|
var (
|
|
vmConfig = vm.Config{
|
|
EnablePreimageRecording: config.EnablePreimageRecording,
|
|
}
|
|
cacheConfig = &core.CacheConfig{
|
|
TrieCleanLimit: config.TrieCleanCache,
|
|
TrieDirtyLimit: config.TrieDirtyCache,
|
|
TrieDirtyCommitTarget: config.TrieDirtyCommitTarget,
|
|
TriePrefetcherParallelism: config.TriePrefetcherParallelism,
|
|
Pruning: config.Pruning,
|
|
AcceptorQueueLimit: config.AcceptorQueueLimit,
|
|
CommitInterval: config.CommitInterval,
|
|
PopulateMissingTries: config.PopulateMissingTries,
|
|
PopulateMissingTriesParallelism: config.PopulateMissingTriesParallelism,
|
|
AllowMissingTries: config.AllowMissingTries,
|
|
SnapshotDelayInit: config.SnapshotDelayInit,
|
|
SnapshotLimit: config.SnapshotCache,
|
|
SnapshotWait: config.SnapshotWait,
|
|
SnapshotVerify: config.SnapshotVerify,
|
|
SnapshotNoBuild: config.SkipSnapshotRebuild,
|
|
Preimages: config.Preimages,
|
|
AcceptedCacheSize: config.AcceptedCacheSize,
|
|
TransactionHistory: config.TransactionHistory,
|
|
SkipTxIndexing: config.SkipTxIndexing,
|
|
StateHistory: config.StateHistory,
|
|
StateScheme: scheme,
|
|
}
|
|
)
|
|
|
|
if err := eth.precheckPopulateMissingTries(); err != nil {
|
|
return nil, err
|
|
}
|
|
eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, eth.engine, vmConfig, lastAcceptedHash, config.SkipUpgradeCheck)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := eth.handleOfflinePruning(cacheConfig, config.Genesis, vmConfig, lastAcceptedHash); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
eth.bloomIndexer.Start(eth.blockchain)
|
|
|
|
// Uncomment the following to enable the new blobpool
|
|
|
|
// config.BlobPool.Datadir = ""
|
|
// blobPool := blobpool.New(config.BlobPool, &chainWithFinalBlock{eth.blockchain})
|
|
|
|
// Create a wrapper that implements the required interfaces
|
|
chainWrapper := &blockChainWrapper{bc: eth.blockchain}
|
|
legacyPool := legacypool.New(config.TxPool, chainWrapper)
|
|
|
|
eth.txPool, err = txpool.New(config.TxPool.PriceLimit, chainWrapper, []txpool.SubPool{legacyPool}) //, blobPool})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Get the concrete ChainConfig
|
|
var chainConfig *params.ChainConfig
|
|
if cfg, ok := eth.blockchain.Config().(*params.ChainConfig); ok {
|
|
chainConfig = cfg
|
|
} else {
|
|
// This should not happen in practice
|
|
return nil, errors.New("blockchain config is not *params.ChainConfig")
|
|
}
|
|
|
|
// Create a timer wrapper
|
|
timerWrapper := &mockableTimerWrapper{clock: clock}
|
|
eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, timerWrapper)
|
|
|
|
allowUnprotectedTxHashes := make(map[common.Hash]struct{})
|
|
for _, txHash := range config.AllowUnprotectedTxHashes {
|
|
allowUnprotectedTxHashes[txHash] = struct{}{}
|
|
}
|
|
|
|
eth.APIBackend = &EthAPIBackend{
|
|
extRPCEnabled: stack.Config().ExtRPCEnabled(),
|
|
allowUnprotectedTxs: config.AllowUnprotectedTxs,
|
|
allowUnprotectedTxHashes: allowUnprotectedTxHashes,
|
|
allowUnfinalizedQueries: config.AllowUnfinalizedQueries,
|
|
historicalProofQueryWindow: config.HistoricalProofQueryWindow,
|
|
eth: eth,
|
|
}
|
|
if config.AllowUnprotectedTxs {
|
|
log.Info("Unprotected transactions allowed")
|
|
}
|
|
gpoParams := config.GPO
|
|
gpoParams.MinPrice = new(big.Int).SetUint64(config.TxPool.PriceLimit)
|
|
gpoWrapper := &gpoBackendWrapper{eth.APIBackend}
|
|
eth.APIBackend.gpo, err = gasprice.NewOracle(gpoWrapper, gpoParams)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Start the RPC service
|
|
eth.netRPCService = ethapi.NewNetAPI(eth.NetVersion())
|
|
|
|
// eth.stackRPCs = stack.APIs() // TODO: Check if this is still needed
|
|
|
|
// Successful startup; push a marker and check previous unclean shutdowns.
|
|
eth.shutdownTracker.MarkStartup()
|
|
|
|
return eth, nil
|
|
}
|
|
|
|
// APIs return the collection of RPC services the ethereum package offers.
|
|
// NOTE, some of these services probably need to be moved to somewhere else.
|
|
func (s *Ethereum) APIs() []rpc.API {
|
|
// Create wrapper for ethapi compatibility
|
|
ethWrapper := ðAPIBackendWrapper{s.APIBackend}
|
|
gethAPIs := ethapi.GetAPIs(ethWrapper)
|
|
|
|
// Convert geth APIs to evm APIs
|
|
apis := make([]rpc.API, len(gethAPIs))
|
|
for i, api := range gethAPIs {
|
|
apis[i] = rpc.API{
|
|
Namespace: api.Namespace,
|
|
Version: api.Version,
|
|
Service: api.Service,
|
|
Name: api.Namespace, // Use namespace as name
|
|
}
|
|
}
|
|
|
|
// Append tracing APIs
|
|
apis = append(apis, tracers.APIs(s.APIBackend)...)
|
|
|
|
// Add the APIs from the node
|
|
// apis = append(apis, s.stackRPCs...) // TODO: Check if this is still needed
|
|
|
|
// Create [filterSystem] with the log cache size set in the config.
|
|
filterSystem := filters.NewFilterSystem(s.APIBackend, filters.Config{
|
|
Timeout: 5 * time.Minute,
|
|
})
|
|
|
|
// Append all the local APIs and return
|
|
return append(apis, []rpc.API{
|
|
{
|
|
Namespace: "eth",
|
|
Service: NewEthereumAPI(s),
|
|
Name: "eth",
|
|
}, {
|
|
Namespace: "eth",
|
|
Service: filters.NewFilterAPI(filterSystem),
|
|
Name: "eth-filter",
|
|
}, {
|
|
Namespace: "admin",
|
|
Service: NewAdminAPI(s),
|
|
Name: "admin",
|
|
}, {
|
|
Namespace: "debug",
|
|
Service: NewDebugAPI(s),
|
|
Name: "debug",
|
|
}, {
|
|
Namespace: "net",
|
|
Service: s.netRPCService,
|
|
Name: "net",
|
|
},
|
|
}...)
|
|
}
|
|
|
|
func (s *Ethereum) Etherbase() (eb common.Address, err error) {
|
|
s.lock.RLock()
|
|
etherbase := s.etherbase
|
|
s.lock.RUnlock()
|
|
|
|
if etherbase != (common.Address{}) {
|
|
return etherbase, nil
|
|
}
|
|
return common.Address{}, fmt.Errorf("etherbase must be explicitly specified")
|
|
}
|
|
|
|
// SetEtherbase sets the mining reward address.
|
|
func (s *Ethereum) SetEtherbase(etherbase common.Address) {
|
|
s.lock.Lock()
|
|
s.etherbase = etherbase
|
|
s.lock.Unlock()
|
|
|
|
s.miner.SetEtherbase(etherbase)
|
|
}
|
|
|
|
func (s *Ethereum) Miner() *miner.Miner { return s.miner }
|
|
|
|
func (s *Ethereum) AccountManager() *accounts.Manager { return s.accountManager }
|
|
func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain }
|
|
func (s *Ethereum) TxPool() *txpool.TxPool { return s.txPool }
|
|
func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux }
|
|
func (s *Ethereum) Engine() consensus.Engine { return s.engine }
|
|
func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb }
|
|
|
|
func (s *Ethereum) NetVersion() uint64 { return s.networkID }
|
|
func (s *Ethereum) ArchiveMode() bool { return !s.config.Pruning }
|
|
func (s *Ethereum) BloomIndexer() *core.ChainIndexer { return s.bloomIndexer }
|
|
|
|
// Start implements node.Lifecycle, starting all internal goroutines needed by the
|
|
// Ethereum protocol implementation.
|
|
func (s *Ethereum) Start() {
|
|
// Start the bloom bits servicing goroutines
|
|
s.startBloomHandlers(params.BloomBitsBlocks)
|
|
|
|
// Regularly update shutdown marker
|
|
s.shutdownTracker.Start()
|
|
}
|
|
|
|
// Stop implements node.Lifecycle, terminating all internal goroutines used by the
|
|
// Ethereum protocol.
|
|
// FIXME remove error from type if this will never return an error
|
|
func (s *Ethereum) Stop() error {
|
|
s.bloomIndexer.Close()
|
|
close(s.closeBloomHandler)
|
|
s.txPool.Close()
|
|
s.blockchain.Stop()
|
|
s.engine.Close()
|
|
|
|
// Clean shutdown marker as the last thing before closing db
|
|
s.shutdownTracker.Stop()
|
|
log.Info("Stopped shutdownTracker")
|
|
|
|
s.chainDb.Close()
|
|
log.Info("Closed chaindb")
|
|
s.eventMux.Stop()
|
|
log.Info("Stopped EventMux")
|
|
return nil
|
|
}
|
|
|
|
func (s *Ethereum) LastAcceptedBlock() *types.Block {
|
|
return s.blockchain.LastAcceptedBlock()
|
|
}
|
|
|
|
// precheckPopulateMissingTries returns an error if config flags should prevent
|
|
// [populateMissingTries]
|
|
//
|
|
// NOTE: [populateMissingTries] is called from [New] to ensure all
|
|
// state is repaired before any async processes (specifically snapshot re-generation)
|
|
// are started which could interfere with historical re-generation.
|
|
func (s *Ethereum) precheckPopulateMissingTries() error {
|
|
if s.config.PopulateMissingTries != nil && (s.config.Pruning || s.config.OfflinePruning) {
|
|
return fmt.Errorf("cannot run populate missing tries when pruning (enabled: %t)/offline pruning (enabled: %t) is enabled", s.config.Pruning, s.config.OfflinePruning)
|
|
}
|
|
|
|
if s.config.PopulateMissingTries == nil {
|
|
// Delete the populate missing tries marker to indicate that the node started with
|
|
// populate missing tries disabled.
|
|
if err := rawdb.DeletePopulateMissingTries(s.chainDb); err != nil {
|
|
return fmt.Errorf("failed to write populate missing tries disabled marker: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if lastRun, err := rawdb.ReadPopulateMissingTries(s.chainDb); err == nil {
|
|
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.")
|
|
return fmt.Errorf("cannot start chain with populate missing tries enabled on consecutive starts (last=%v)", lastRun)
|
|
}
|
|
|
|
// Note: Time Marker is written inside of [populateMissingTries] once it
|
|
// succeeds inside of [NewBlockChain]
|
|
return nil
|
|
}
|
|
|
|
func (s *Ethereum) handleOfflinePruning(cacheConfig *core.CacheConfig, gspec *core.Genesis, vmConfig vm.Config, lastAcceptedHash common.Hash) error {
|
|
if s.config.OfflinePruning && !s.config.Pruning {
|
|
return core.ErrRefuseToCorruptArchiver
|
|
}
|
|
|
|
if !s.config.OfflinePruning {
|
|
// Delete the offline pruning marker to indicate that the node started with offline pruning disabled.
|
|
if err := rawdb.DeleteOfflinePruning(s.chainDb); err != nil {
|
|
return fmt.Errorf("failed to write offline pruning disabled marker: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Perform offline pruning after NewBlockChain has been called to ensure that we have rolled back the chain
|
|
// to the last accepted block before pruning begins.
|
|
// If offline pruning marker is on disk, then we force the node to be started with offline pruning disabled
|
|
// before allowing another run of offline pruning.
|
|
if lastRun, err := rawdb.ReadOfflinePruning(s.chainDb); err == nil {
|
|
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.")
|
|
return fmt.Errorf("cannot start chain with offline pruning enabled on consecutive starts (last=%v)", lastRun)
|
|
}
|
|
|
|
// Clean up middle roots
|
|
if err := s.blockchain.CleanBlockRootsAboveLastAccepted(); err != nil {
|
|
return err
|
|
}
|
|
targetRoot := s.blockchain.LastAcceptedBlock().Root()
|
|
|
|
// Allow the blockchain to be garbage collected immediately, since we will shut down the chain after offline pruning completes.
|
|
s.blockchain.Stop()
|
|
s.blockchain = nil
|
|
log.Info("Starting offline pruning", "dataDir", s.config.OfflinePruningDataDirectory, "bloomFilterSize", s.config.OfflinePruningBloomFilterSize)
|
|
prunerConfig := pruner.Config{
|
|
BloomSize: s.config.OfflinePruningBloomFilterSize,
|
|
Datadir: s.config.OfflinePruningDataDirectory,
|
|
}
|
|
|
|
pruner, err := pruner.NewPruner(s.chainDb, prunerConfig)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create new pruner with data directory: %s, size: %d, due to: %w", s.config.OfflinePruningDataDirectory, s.config.OfflinePruningBloomFilterSize, err)
|
|
}
|
|
if err := pruner.Prune(targetRoot); err != nil {
|
|
return fmt.Errorf("failed to prune blockchain with target root: %s due to: %w", targetRoot, err)
|
|
}
|
|
// Note: Time Marker is written inside of [Prune] before compaction begins
|
|
// (considered an optional optimization)
|
|
s.blockchain, err = core.NewBlockChain(s.chainDb, cacheConfig, gspec, s.engine, vmConfig, lastAcceptedHash, s.config.SkipUpgradeCheck)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to re-initialize blockchain after offline pruning: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|