/*
* 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.facade;
import org.apache.commons.lang3.ArrayUtils;
import org.ethereum.config.BlockchainConfig;
import org.ethereum.config.CommonConfig;
import org.ethereum.config.SystemProperties;
import org.ethereum.core.*;
import org.ethereum.core.PendingState;
import org.ethereum.core.Repository;
import org.ethereum.crypto.ECKey;
import org.ethereum.listener.CompositeEthereumListener;
import org.ethereum.listener.EthereumListener;
import org.ethereum.listener.EthereumListenerAdapter;
import org.ethereum.listener.GasPriceTracker;
import org.ethereum.manager.AdminInfo;
import org.ethereum.manager.BlockLoader;
import org.ethereum.manager.WorldManager;
import org.ethereum.mine.BlockMiner;
import org.ethereum.net.client.PeerClient;
import org.ethereum.net.rlpx.Node;
import org.ethereum.net.server.ChannelManager;
import org.ethereum.net.shh.Whisper;
import org.ethereum.net.submit.TransactionExecutor;
import org.ethereum.net.submit.TransactionTask;
import org.ethereum.sync.SyncManager;
import org.ethereum.util.ByteUtil;
import org.ethereum.vm.program.ProgramResult;
import org.ethereum.vm.program.invoke.ProgramInvokeFactory;
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.context.SmartLifecycle;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.concurrent.FutureAdapter;
import java.math.BigInteger;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
* @author Roman Mandeleil
* @since 27.07.2014
*/
@Component
public class EthereumImpl implements Ethereum, SmartLifecycle {
private static final Logger logger = LoggerFactory.getLogger("facade");
private static final Logger gLogger = LoggerFactory.getLogger("general");
@Autowired
WorldManager worldManager;
@Autowired
AdminInfo adminInfo;
@Autowired
ChannelManager channelManager;
@Autowired
ApplicationContext ctx;
@Autowired
BlockLoader blockLoader;
@Autowired
ProgramInvokeFactory programInvokeFactory;
@Autowired
Whisper whisper;
@Autowired
PendingState pendingState;
@Autowired
SyncManager syncManager;
@Autowired
CommonConfig commonConfig = CommonConfig.getDefault();
private SystemProperties config;
private CompositeEthereumListener compositeEthereumListener;
private GasPriceTracker gasPriceTracker = new GasPriceTracker();
@Autowired
public EthereumImpl(final SystemProperties config, final CompositeEthereumListener compositeEthereumListener) {
this.compositeEthereumListener = compositeEthereumListener;
this.config = config;
System.out.println();
this.compositeEthereumListener.addListener(gasPriceTracker);
gLogger.info("EthereumJ node started: enode://" + Hex.toHexString(config.nodeId()) + "@" + config.externalIp() + ":" + config.listenPort());
}
@Override
public void startPeerDiscovery() {
worldManager.startPeerDiscovery();
}
@Override
public void stopPeerDiscovery() {
worldManager.stopPeerDiscovery();
}
@Override
public void connect(InetAddress addr, int port, String remoteId) {
connect(addr.getHostName(), port, remoteId);
}
@Override
public void connect(final String ip, final int port, final String remoteId) {
logger.debug("Connecting to: {}:{}", ip, port);
worldManager.getActivePeer().connectAsync(ip, port, remoteId, false);
}
@Override
public void connect(Node node) {
connect(node.getHost(), node.getPort(), Hex.toHexString(node.getId()));
}
@Override
public org.ethereum.facade.Blockchain getBlockchain() {
return (org.ethereum.facade.Blockchain) worldManager.getBlockchain();
}
public ImportResult addNewMinedBlock(Block block) {
ImportResult importResult = worldManager.getBlockchain().tryToConnect(block);
if (importResult == ImportResult.IMPORTED_BEST) {
channelManager.sendNewBlock(block);
}
return importResult;
}
@Override
public BlockMiner getBlockMiner() {
return ctx.getBean(BlockMiner.class);
}
@Override
public void addListener(EthereumListener listener) {
worldManager.addListener(listener);
}
@Override
public void close() {
logger.info("### Shutdown initiated ### ");
((AbstractApplicationContext) getApplicationContext()).close();
}
@Override
public SyncStatus getSyncStatus() {
return syncManager.getSyncStatus();
}
@Override
public PeerClient getDefaultPeer() {
return worldManager.getActivePeer();
}
@Override
public boolean isConnected() {
return worldManager.getActivePeer() != null;
}
@Override
public Transaction createTransaction(BigInteger nonce,
BigInteger gasPrice,
BigInteger gas,
byte[] receiveAddress,
BigInteger value, byte[] data) {
byte[] nonceBytes = ByteUtil.bigIntegerToBytes(nonce);
byte[] gasPriceBytes = ByteUtil.bigIntegerToBytes(gasPrice);
byte[] gasBytes = ByteUtil.bigIntegerToBytes(gas);
byte[] valueBytes = ByteUtil.bigIntegerToBytes(value);
return new Transaction(nonceBytes, gasPriceBytes, gasBytes,
receiveAddress, valueBytes, data, getChainIdForNextBlock());
}
@Override
public Future<Transaction> submitTransaction(Transaction transaction) {
TransactionTask transactionTask = new TransactionTask(transaction, channelManager);
final Future<List<Transaction>> listFuture =
TransactionExecutor.instance.submitTransaction(transactionTask);
pendingState.addPendingTransaction(transaction);
return new FutureAdapter<Transaction, List<Transaction>>(listFuture) {
@Override
protected Transaction adapt(List<Transaction> adapteeResult) throws ExecutionException {
return adapteeResult.get(0);
}
};
}
@Override
public TransactionReceipt callConstant(Transaction tx, Block block) {
if (tx.getSignature() == null) {
tx.sign(ECKey.fromPrivate(new byte[32]));
}
return callConstantImpl(tx, block).getReceipt();
}
public BlockSummary replayBlock(Block block) {
List<TransactionReceipt> receipts = new ArrayList<>();
List<TransactionExecutionSummary> summaries = new ArrayList<>();
Repository repository = ((Repository) worldManager.getRepository())
.getSnapshotTo(block.getStateRoot())
.startTracking();
try {
for (Transaction tx : block.getTransactionsList()) {
org.ethereum.core.TransactionExecutor executor = new org.ethereum.core.TransactionExecutor(
tx, block.getCoinbase(), repository, worldManager.getBlockStore(),
programInvokeFactory, block, worldManager.getListener(), 0)
.withCommonConfig(commonConfig);
executor.setLocalCall(true);
executor.init();
executor.execute();
executor.go();
TransactionExecutionSummary summary = executor.finalization();
TransactionReceipt receipt = executor.getReceipt();
// TODO: change to repository.getRoot() after RepositoryTrack implementation
receipt.setPostTxState(ArrayUtils.EMPTY_BYTE_ARRAY);
receipts.add(receipt);
summaries.add(summary);
}
} finally {
repository.rollback();
}
return new BlockSummary(block, new HashMap<byte[], BigInteger>(), receipts, summaries);
}
private org.ethereum.core.TransactionExecutor callConstantImpl(Transaction tx, Block block) {
Repository repository = ((Repository) worldManager.getRepository())
.getSnapshotTo(block.getStateRoot())
.startTracking();
try {
org.ethereum.core.TransactionExecutor executor = new org.ethereum.core.TransactionExecutor
(tx, block.getCoinbase(), repository, worldManager.getBlockStore(),
programInvokeFactory, block, new EthereumListenerAdapter(), 0)
.withCommonConfig(commonConfig)
.setLocalCall(true);
executor.init();
executor.execute();
executor.go();
executor.finalization();
return executor;
} finally {
repository.rollback();
}
}
@Override
public ProgramResult callConstantFunction(String receiveAddress,
CallTransaction.Function function, Object... funcArgs) {
return callConstantFunction(receiveAddress, ECKey.fromPrivate(new byte[32]), function, funcArgs);
}
@Override
public ProgramResult callConstantFunction(String receiveAddress, ECKey senderPrivateKey,
CallTransaction.Function function, Object... funcArgs) {
Transaction tx = CallTransaction.createCallTransaction(0, 0, 100000000000000L,
receiveAddress, 0, function, funcArgs);
tx.sign(senderPrivateKey);
Block bestBlock = worldManager.getBlockchain().getBestBlock();
return callConstantImpl(tx, bestBlock).getResult();
}
@Override
public org.ethereum.facade.Repository getRepository() {
return worldManager.getRepository();
}
@Override
public org.ethereum.facade.Repository getLastRepositorySnapshot() {
return getSnapshotTo(getBlockchain().getBestBlock().getStateRoot());
}
@Override
public org.ethereum.facade.Repository getPendingState() {
return worldManager.getPendingState().getRepository();
}
@Override
public org.ethereum.facade.Repository getSnapshotTo(byte[] root) {
Repository repository = (Repository) worldManager.getRepository();
org.ethereum.facade.Repository snapshot = repository.getSnapshotTo(root);
return snapshot;
}
@Override
public AdminInfo getAdminInfo() {
return adminInfo;
}
@Override
public ChannelManager getChannelManager() {
return channelManager;
}
@Override
public List<Transaction> getWireTransactions() {
return worldManager.getPendingState().getPendingTransactions();
}
@Override
public List<Transaction> getPendingStateTransactions() {
return worldManager.getPendingState().getPendingTransactions();
}
@Override
public BlockLoader getBlockLoader() {
return blockLoader;
}
@Override
public Whisper getWhisper() {
return whisper;
}
@Override
public long getGasPrice() {
return gasPriceTracker.getGasPrice();
}
@Override
public Integer getChainIdForNextBlock() {
BlockchainConfig nextBlockConfig = config.getBlockchainConfig().getConfigForBlock(getBlockchain()
.getBestBlock().getNumber() + 1);
return nextBlockConfig.getChainId();
}
@Override
public void exitOn(long number) {
worldManager.getBlockchain().setExitOn(number);
}
@Override
public void initSyncing() {
worldManager.initSyncing();
}
/**
* For testing purposes and 'hackers'
*/
public ApplicationContext getApplicationContext() {
return ctx;
}
@Override
public boolean isAutoStartup() {
return false;
}
/**
* Shutting down all app beans
*/
@Override
public void stop(Runnable callback) {
logger.info("Shutting down Ethereum instance...");
worldManager.close();
callback.run();
}
@Override
public void start() {}
@Override
public void stop() {}
@Override
public boolean isRunning() {
return true;
}
/**
* Called first on shutdown lifecycle
*/
@Override
public int getPhase() {
return Integer.MAX_VALUE;
}
}