/* * Copyright (c) [2016] [ <ether.camp> ] * This file is part of the ethereumJ library. * * The ethereumJ 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 ethereumJ 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 ethereumJ library. If not, see <http://www.gnu.org/licenses/>. */ package org.ethereum.manager; import org.ethereum.config.SystemProperties; import org.ethereum.core.*; import org.ethereum.db.BlockStore; import org.ethereum.db.ByteArrayWrapper; import org.ethereum.db.DbFlushManager; import org.ethereum.listener.CompositeEthereumListener; import org.ethereum.listener.EthereumListener; import org.ethereum.net.client.PeerClient; import org.ethereum.net.rlpx.discover.UDPListener; import org.ethereum.sync.FastSyncManager; import org.ethereum.sync.SyncManager; import org.ethereum.net.rlpx.discover.NodeManager; import org.ethereum.net.server.ChannelManager; import org.ethereum.sync.SyncPool; import org.ethereum.util.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spongycastle.util.encoders.Hex; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import static org.ethereum.crypto.HashUtil.EMPTY_TRIE_HASH; /** * WorldManager is a singleton containing references to different parts of the system. * * @author Roman Mandeleil * @since 01.06.2014 */ @Component public class WorldManager { private static final Logger logger = LoggerFactory.getLogger("general"); @Autowired private PeerClient activePeer; @Autowired private ChannelManager channelManager; @Autowired private AdminInfo adminInfo; @Autowired private NodeManager nodeManager; @Autowired private SyncManager syncManager; @Autowired private FastSyncManager fastSyncManager; @Autowired private SyncPool pool; @Autowired private PendingState pendingState; @Autowired private UDPListener discoveryUdpListener; @Autowired private EventDispatchThread eventDispatchThread; @Autowired private DbFlushManager dbFlushManager; @Autowired private ApplicationContext ctx; private SystemProperties config; private EthereumListener listener; private Blockchain blockchain; private Repository repository; private BlockStore blockStore; @Autowired public WorldManager(final SystemProperties config, final Repository repository, final EthereumListener listener, final Blockchain blockchain, final BlockStore blockStore) { this.listener = listener; this.blockchain = blockchain; this.repository = repository; this.blockStore = blockStore; this.config = config; loadBlockchain(); } @PostConstruct private void init() { syncManager.init(channelManager, pool); } public void addListener(EthereumListener listener) { logger.info("Ethereum listener added"); ((CompositeEthereumListener) this.listener).addListener(listener); } public void startPeerDiscovery() { } public void stopPeerDiscovery() { discoveryUdpListener.close(); nodeManager.close(); } public void initSyncing() { config.setSyncEnabled(true); syncManager.init(channelManager, pool); } public ChannelManager getChannelManager() { return channelManager; } public EthereumListener getListener() { return listener; } public org.ethereum.facade.Repository getRepository() { return (org.ethereum.facade.Repository)repository; } public Blockchain getBlockchain() { return blockchain; } public PeerClient getActivePeer() { return activePeer; } public BlockStore getBlockStore() { return blockStore; } public PendingState getPendingState() { return pendingState; } public void loadBlockchain() { if (!config.databaseReset() || config.databaseResetBlock() != 0) blockStore.load(); if (blockStore.getBestBlock() == null) { logger.info("DB is empty - adding Genesis"); Genesis genesis = Genesis.getInstance(config); Genesis.populateRepository(repository, genesis); // repository.commitBlock(genesis.getHeader()); repository.commit(); blockStore.saveBlock(Genesis.getInstance(config), Genesis.getInstance(config).getCumulativeDifficulty(), true); blockchain.setBestBlock(Genesis.getInstance(config)); blockchain.setTotalDifficulty(Genesis.getInstance(config).getCumulativeDifficulty()); listener.onBlock(new BlockSummary(Genesis.getInstance(config), new HashMap<byte[], BigInteger>(), new ArrayList<TransactionReceipt>(), new ArrayList<TransactionExecutionSummary>())); // repository.dumpState(Genesis.getInstance(config), 0, 0, null); logger.info("Genesis block loaded"); } else { if (!config.databaseReset() && !Arrays.equals(blockchain.getBlockByNumber(0).getHash(), config.getGenesis().getHash())) { // fatal exit Utils.showErrorAndExit("*** DB is incorrect, 0 block in DB doesn't match genesis"); } Block bestBlock = blockStore.getBestBlock(); if (config.databaseReset() && config.databaseResetBlock() > 0) { if (config.databaseResetBlock() > bestBlock.getNumber()) { logger.error("*** Can't reset to block [{}] since block store is at block [{}].", config.databaseResetBlock(), bestBlock); throw new RuntimeException("Reset block ahead of block store."); } bestBlock = blockStore.getChainBlockByNumber(config.databaseResetBlock()); Repository snapshot = repository.getSnapshotTo(bestBlock.getStateRoot()); if (false) { // TODO: some way to tell if the snapshot hasn't been pruned logger.error("*** Could not reset database to block [{}] with stateRoot [{}], since state information is " + "unavailable. It might have been pruned from the database."); throw new RuntimeException("State unavailable for reset block."); } } blockchain.setBestBlock(bestBlock); BigInteger totalDifficulty = blockStore.getTotalDifficultyForHash(bestBlock.getHash()); blockchain.setTotalDifficulty(totalDifficulty); logger.info("*** Loaded up to block [{}] totalDifficulty [{}] with stateRoot [{}]", blockchain.getBestBlock().getNumber(), blockchain.getTotalDifficulty().toString(), Hex.toHexString(blockchain.getBestBlock().getStateRoot())); } if (config.rootHashStart() != null) { // update world state by dummy hash byte[] rootHash = Hex.decode(config.rootHashStart()); logger.info("Loading root hash from property file: [{}]", config.rootHashStart()); this.repository.syncToRoot(rootHash); } else { // Update world state to latest loaded block from db // if state is not generated from empty premine list // todo this is just a workaround, move EMPTY_TRIE_HASH logic to Trie implementation if (!Arrays.equals(blockchain.getBestBlock().getStateRoot(), EMPTY_TRIE_HASH)) { this.repository.syncToRoot(blockchain.getBestBlock().getStateRoot()); } } /* todo: return it when there is no state conflicts on the chain boolean dbValid = this.repository.getWorldState().validate() || bestBlock.isGenesis(); if (!dbValid){ logger.error("The DB is not valid for that blockchain"); System.exit(-1); // todo: reset the repository and blockchain } */ } public void close() { logger.info("close: stopping peer discovery ..."); stopPeerDiscovery(); logger.info("close: stopping ChannelManager ..."); channelManager.close(); logger.info("close: stopping SyncManager ..."); syncManager.close(); logger.info("close: stopping PeerClient ..."); activePeer.close(); logger.info("close: shutting down event dispatch thread used by EventBus ..."); eventDispatchThread.shutdown(); logger.info("close: closing Blockchain instance ..."); blockchain.close(); logger.info("close: closing main repository ..."); repository.close(); logger.info("close: database flush manager ..."); dbFlushManager.close(); } }