/*
* 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.samples;
import com.typesafe.config.ConfigFactory;
import org.ethereum.config.SystemProperties;
import org.ethereum.core.Block;
import org.ethereum.core.Transaction;
import org.ethereum.crypto.ECKey;
import org.ethereum.facade.EthereumFactory;
import org.ethereum.mine.Ethash;
import org.ethereum.mine.MinerListener;
import org.ethereum.util.ByteUtil;
import org.spongycastle.util.encoders.Hex;
import org.springframework.context.annotation.Bean;
/**
* The sample creates a small private net with two peers: one is the miner, another is a regular peer
* which is directly connected to the miner peer and starts submitting transactions which are then
* included to blocks by the miner.
*
* Another concept demonstrated by this sample is the ability to run two independently configured
* EthereumJ peers in a single JVM. For this two Spring ApplicationContext's are created which
* are mostly differed by the configuration supplied
*
* Created by Anton Nashatyrev on 05.02.2016.
*/
public class PrivateMinerSample {
/**
* Spring configuration class for the Miner peer
*/
private static class MinerConfig {
private final String config =
// no need for discovery in that small network
"peer.discovery.enabled = false \n" +
"peer.listen.port = 30335 \n" +
// need to have different nodeId's for the peers
"peer.privateKey = 6ef8da380c27cea8fdf7448340ea99e8e2268fc2950d79ed47cbf6f85dc977ec \n" +
// our private net ID
"peer.networkId = 555 \n" +
// we have no peers to sync with
"sync.enabled = false \n" +
// genesis with a lower initial difficulty and some predefined known funded accounts
"genesis = sample-genesis.json \n" +
// two peers need to have separate database dirs
"database.dir = sampleDB-1 \n" +
// when more than 1 miner exist on the network extraData helps to identify the block creator
"mine.extraDataHex = cccccccccccccccccccc \n" +
"mine.cpuMineThreads = 2 \n" +
"cache.flush.blocks = 1";
@Bean
public MinerNode node() {
return new MinerNode();
}
/**
* Instead of supplying properties via config file for the peer
* we are substituting the corresponding bean which returns required
* config for this instance.
*/
@Bean
public SystemProperties systemProperties() {
SystemProperties props = new SystemProperties();
props.overrideParams(ConfigFactory.parseString(config.replaceAll("'", "\"")));
return props;
}
}
/**
* Miner bean, which just start a miner upon creation and prints miner events
*/
static class MinerNode extends BasicSample implements MinerListener{
public MinerNode() {
// peers need different loggers
super("sampleMiner");
}
// overriding run() method since we don't need to wait for any discovery,
// networking or sync events
@Override
public void run() {
if (config.isMineFullDataset()) {
logger.info("Generating Full Dataset (may take up to 10 min if not cached)...");
// calling this just for indication of the dataset generation
// basically this is not required
Ethash ethash = Ethash.getForBlock(config, ethereum.getBlockchain().getBestBlock().getNumber());
ethash.getFullDataset();
logger.info("Full dataset generated (loaded).");
}
ethereum.getBlockMiner().addListener(this);
ethereum.getBlockMiner().startMining();
}
@Override
public void miningStarted() {
logger.info("Miner started");
}
@Override
public void miningStopped() {
logger.info("Miner stopped");
}
@Override
public void blockMiningStarted(Block block) {
logger.info("Start mining block: " + block.getShortDescr());
}
@Override
public void blockMined(Block block) {
logger.info("Block mined! : \n" + block);
}
@Override
public void blockMiningCanceled(Block block) {
logger.info("Cancel mining block: " + block.getShortDescr());
}
}
/**
* Spring configuration class for the Regular peer
*/
private static class RegularConfig {
private final String config =
// no discovery: we are connecting directly to the miner peer
"peer.discovery.enabled = false \n" +
"peer.listen.port = 30336 \n" +
"peer.privateKey = 3ec771c31cac8c0dba77a69e503765701d3c2bb62435888d4ffa38fed60c445c \n" +
"peer.networkId = 555 \n" +
// actively connecting to the miner
"peer.active = [" +
" { url = 'enode://26ba1aadaf59d7607ad7f437146927d79e80312f026cfa635c6b2ccf2c5d3521f5812ca2beb3b295b14f97110e6448c1c7ff68f14c5328d43a3c62b44143e9b1@localhost:30335' }" +
"] \n" +
"sync.enabled = true \n" +
// all peers in the same network need to use the same genesis block
"genesis = sample-genesis.json \n" +
// two peers need to have separate database dirs
"database.dir = sampleDB-2 \n";
@Bean
public RegularNode node() {
return new RegularNode();
}
/**
* Instead of supplying properties via config file for the peer
* we are substituting the corresponding bean which returns required
* config for this instance.
*/
@Bean
public SystemProperties systemProperties() {
SystemProperties props = new SystemProperties();
props.overrideParams(ConfigFactory.parseString(config.replaceAll("'", "\"")));
return props;
}
}
/**
* The second node in the network which connects to the miner
* waits for the sync and starts submitting transactions.
* Those transactions should be included into mined blocks and the peer
* should receive those blocks back
*/
static class RegularNode extends BasicSample {
public RegularNode() {
// peers need different loggers
super("sampleNode");
}
@Override
public void onSyncDone() {
new Thread(new Runnable() {
@Override
public void run() {
try {
generateTransactions();
} catch (Exception e) {
logger.error("Error generating tx: ", e);
}
}
}).start();
}
/**
* Generate one simple value transfer transaction each 7 seconds.
* Thus blocks will include one, several and none transactions
*/
private void generateTransactions() throws Exception{
logger.info("Start generating transactions...");
// the sender which some coins from the genesis
ECKey senderKey = ECKey.fromPrivate(Hex.decode("6ef8da380c27cea8fdf7448340ea99e8e2268fc2950d79ed47cbf6f85dc977ec"));
byte[] receiverAddr = Hex.decode("5db10750e8caff27f906b41c71b3471057dd2004");
for (int i = ethereum.getRepository().getNonce(senderKey.getAddress()).intValue(), j = 0; j < 20000; i++, j++) {
{
Transaction tx = new Transaction(ByteUtil.intToBytesNoLeadZeroes(i),
ByteUtil.longToBytesNoLeadZeroes(50_000_000_000L), ByteUtil.longToBytesNoLeadZeroes(0xfffff),
receiverAddr, new byte[]{77}, new byte[0], ethereum.getChainIdForNextBlock());
tx.sign(senderKey);
logger.info("<== Submitting tx: " + tx);
ethereum.submitTransaction(tx);
}
Thread.sleep(7000);
}
}
}
/**
* Creating two EthereumJ instances with different config classes
*/
public static void main(String[] args) throws Exception {
if (Runtime.getRuntime().maxMemory() < (1250L << 20)) {
MinerNode.sLogger.error("Not enough JVM heap (" + (Runtime.getRuntime().maxMemory() >> 20) + "Mb) to generate DAG for mining (DAG requires min 1G). For this sample it is recommended to set -Xmx2G JVM option");
return;
}
BasicSample.sLogger.info("Starting EthtereumJ miner instance!");
EthereumFactory.createEthereum(MinerConfig.class);
BasicSample.sLogger.info("Starting EthtereumJ regular instance!");
EthereumFactory.createEthereum(RegularConfig.class);
}
}