diff --git a/ethereumj-core-android/build.gradle b/ethereumj-core-android/build.gradle index 5fdaf362..da95a032 100644 --- a/ethereumj-core-android/build.gradle +++ b/ethereumj-core-android/build.gradle @@ -46,6 +46,7 @@ dependencies { apt 'com.google.dagger:dagger-compiler:2.0' compile fileTree(dir: 'libs', include: ['*.jar']) compile (project(':ethereumj-core')) { + exclude group: 'commons-logging', module: 'commons-logging' exclude group: "org.apache.commons", module: "commons-pool2" exclude group: "org.slf4j", module: "slf4j-log4j12" exclude group: "org.hibernate", module: "hibernate-core" diff --git a/ethereumj-core-android/src/main/java/org/ethereum/android/EthereumManager.java b/ethereumj-core-android/src/main/java/org/ethereum/android/EthereumManager.java index 373fa837..ec5e693f 100644 --- a/ethereumj-core-android/src/main/java/org/ethereum/android/EthereumManager.java +++ b/ethereumj-core-android/src/main/java/org/ethereum/android/EthereumManager.java @@ -46,7 +46,7 @@ public class EthereumManager { SystemProperties.CONFIG.activePeerPort(), SystemProperties.CONFIG.activePeerNodeid()); } else { - duration = ethereum.getBlockLoader().loadBlocks(); + ethereum.getBlockLoader().loadBlocks(); } return duration; } diff --git a/ethereumj-core-android/src/main/java/org/ethereum/android/db/OrmLiteBlockStoreDatabase.java b/ethereumj-core-android/src/main/java/org/ethereum/android/db/OrmLiteBlockStoreDatabase.java index 782f6bac..089ba4e8 100644 --- a/ethereumj-core-android/src/main/java/org/ethereum/android/db/OrmLiteBlockStoreDatabase.java +++ b/ethereumj-core-android/src/main/java/org/ethereum/android/db/OrmLiteBlockStoreDatabase.java @@ -260,11 +260,14 @@ public class OrmLiteBlockStoreDatabase extends OrmLiteSqliteOpenHelper implement public boolean flush(final List blocks) { + reset(); try { TransactionManager.callInTransaction(getBlockDao().getConnectionSource(), new Callable() { public Void call() throws Exception { - for (Block block : blocks) { + int lastIndex = blocks.size() - 1; + for (int i = 0; i < 1000; ++i){ + Block block = blocks.get(lastIndex - i); BlockVO blockVO = new BlockVO(block.getNumber(), block.getHash(), block.getEncoded(), block.getCumulativeDifficulty()); save(blockVO); } diff --git a/ethereumj-core-android/src/main/java/org/ethereum/android/di/modules/EthereumModule.java b/ethereumj-core-android/src/main/java/org/ethereum/android/di/modules/EthereumModule.java index 8bc0db7b..a4b94458 100644 --- a/ethereumj-core-android/src/main/java/org/ethereum/android/di/modules/EthereumModule.java +++ b/ethereumj-core-android/src/main/java/org/ethereum/android/di/modules/EthereumModule.java @@ -7,7 +7,6 @@ import com.j256.ormlite.android.apptools.OpenHelperManager; import org.ethereum.android.datasource.LevelDbDataSource; import org.ethereum.android.db.InMemoryBlockStore; import org.ethereum.android.db.OrmLiteBlockStoreDatabase; -import org.ethereum.android.db.BlockStoreImpl; import org.ethereum.config.SystemProperties; import org.ethereum.core.BlockchainImpl; import org.ethereum.core.Wallet; @@ -20,7 +19,7 @@ import org.ethereum.facade.Repository; import org.ethereum.listener.CompositeEthereumListener; import org.ethereum.listener.EthereumListener; import org.ethereum.manager.AdminInfo; -import org.ethereum.manager.BlockLoader; +import org.ethereum.android.manager.BlockLoader; import org.ethereum.manager.WorldManager; import org.ethereum.net.MessageQueue; import org.ethereum.net.client.PeerClient; diff --git a/ethereumj-core-android/src/main/java/org/ethereum/android/manager/BlockLoader.java b/ethereumj-core-android/src/main/java/org/ethereum/android/manager/BlockLoader.java new file mode 100644 index 00000000..522dbd0d --- /dev/null +++ b/ethereumj-core-android/src/main/java/org/ethereum/android/manager/BlockLoader.java @@ -0,0 +1,85 @@ +package org.ethereum.android.manager; + + +import org.ethereum.core.Block; +import org.ethereum.core.ImportResult; +import org.ethereum.facade.Blockchain; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; + +import java.io.FileInputStream; +import java.io.IOException; +import org.ethereum.android.util.Scanner; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import static org.ethereum.config.SystemProperties.CONFIG; + +@Singleton +public class BlockLoader extends org.ethereum.manager.BlockLoader { + + private static final Logger logger = LoggerFactory.getLogger("BlockLoader"); + + Scanner scanner = null; + + @Inject + public BlockLoader(Blockchain blockchain) { + super(blockchain); + } + + public void loadBlocks(){ + + String fileSrc = CONFIG.blocksLoader(); + try { + long startTime = System.currentTimeMillis(); + FileInputStream inputStream = null; + inputStream = new FileInputStream(fileSrc); + scanner = new Scanner(inputStream); + + System.out.println("Loading blocks: " + fileSrc); + + while (scanner.hasNext()) { + + byte[] blockRLPBytes = Hex.decode(scanner.nextLine()); + Block block = new Block(blockRLPBytes); + + long t1 = System.nanoTime(); + if (block.getNumber() > blockchain.getBestBlock().getNumber()){ + blockchain.tryToConnect(block); + long t1_ = System.nanoTime(); + float elapsed = ((float)(t1_ - t1) / 1_000_000); + + if (block.getNumber() % 1000 == 0 || elapsed > 10_000) { + String result = String.format("Imported block #%d took: [%02.2f msec]", + block.getNumber(), elapsed); + + System.out.println(result); + } + } else { + + if (block.getNumber() % 10000 == 0) + System.out.println("Skipping block #" + block.getNumber()); + } + block = null; + blockRLPBytes = null; + + } + long duration = System.currentTimeMillis() - startTime; + System.out.println("Finished loading blocks in " + (duration / 1000) + " seconds (" + (duration / 60000) + " minutes)"); + //return duration; + } catch (IOException e) { + e.printStackTrace(); + logger.error(e.getMessage(), e); + } catch (Exception e) { + logger.error(e.getMessage(), e); + System.out.println(e.getMessage()); + } + //return 0; + } + + + + +} diff --git a/ethereumj-core-android/src/main/java/org/ethereum/android/util/Scanner.java b/ethereumj-core-android/src/main/java/org/ethereum/android/util/Scanner.java new file mode 100644 index 00000000..19cd3fed --- /dev/null +++ b/ethereumj-core-android/src/main/java/org/ethereum/android/util/Scanner.java @@ -0,0 +1,92 @@ +package org.ethereum.android.util; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; + +class Locale { + final static int US=0; +} + +public class Scanner { + + private BufferedInputStream in; + + int c; + + int offset; + + boolean atBeginningOfLine; + + public Scanner(InputStream stream) { + + in = new BufferedInputStream(stream); + try { + atBeginningOfLine = true; + c = (char)in.read(); + } catch (IOException e) { + c = -1; + } + } + + public boolean hasNext() { + + if (!atBeginningOfLine) + throw new Error("hasNext only works "+ + "after a call to nextLine"); + return c != -1; + } + + public String next() { + StringBuffer sb = new StringBuffer(); + atBeginningOfLine = false; + try { + while (c <= ' ') { + c = in.read(); + } + while (c > ' ') { + sb.append((char)c); + c = in.read(); + } + } catch (IOException e) { + c = -1; + return ""; + } + return sb.toString(); + } + + public String nextLine() { + StringBuffer sb = new StringBuffer(); + atBeginningOfLine = true; + try { + while (c != '\n') { + sb.append((char)c); + c = in.read(); + } + c = in.read(); + } catch (IOException e) { + c = -1; + return ""; + } + return sb.toString(); + } + + public int nextInt() { + String s = next(); + try { + return Integer.parseInt(s); + } catch (NumberFormatException e) { + return 0; //throw new Error("Malformed number " + s); + } + } + + public double nextDouble() { + return new Double(next()); + } + + public long nextLong() { + return Long.parseLong(next()); + } + + public void useLocale(int l) {} +} \ No newline at end of file diff --git a/ethereumj-core/build.gradle b/ethereumj-core/build.gradle index 10fdc447..6b817343 100644 --- a/ethereumj-core/build.gradle +++ b/ethereumj-core/build.gradle @@ -9,13 +9,11 @@ buildscript { dependencies { classpath 'me.champeau.gradle:antlr4-gradle-plugin:0.1' classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:3.0.1' - //classpath "gradle.plugin.com.ewerk.gradle.plugins:dagger-plugin:1.0.0" } } plugins { id 'java' - // id "com.ewerk.gradle.plugins.dagger" version "1.0.0" id 'application' id 'jacoco' id 'com.github.johnrengelman.shadow' version '1.2.1' @@ -32,7 +30,7 @@ repositories { sourceCompatibility = 1.7 mainClassName = 'org.ethereum.Start' -applicationDefaultJvmArgs = ["-server", "-Xms3g", "-Xss32m"] +applicationDefaultJvmArgs = ["-server", "-Xss32m"] ext.generatedSrcDir = file('src/gen/java') @@ -129,43 +127,41 @@ dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') - compile('io.netty:netty-all:4.0.28.Final') { - exclude group: 'commons-logging', module: 'commons-logging' - } - compile "com.madgag.spongycastle:core:${scastleVersion}" - // for SHA3 and SECP256K1 - compile "com.madgag.spongycastle:prov:${scastleVersion}" - // for SHA3 and SECP256K1 - + compile('io.netty:netty-all:4.0.28.Final') + compile "com.madgag.spongycastle:core:${scastleVersion}" // for SHA3 and SECP256K1 + compile "com.madgag.spongycastle:prov:${scastleVersion}" // for SHA3 and SECP256K1 compile "org.iq80.leveldb:leveldb:${leveldbVersion}" - compile('com.cedarsoftware:java-util:1.8.0') { - exclude group: 'commons-logging', module: 'commons-logging' - } // for deep equals + compile "org.fusesource.leveldbjni:leveldbjni:1.8" - compile 'org.antlr:antlr4-runtime:4.5' // for serpent compilation - - compile 'com.yuvalshavit:antlr-denter:1.1' - - compile "org.slf4j:slf4j-log4j12:${slf4jVersion}" - - compile 'org.codehaus.jackson:jackson-mapper-asl:1.9.13' - compile 'com.google.code.findbugs:jsr305:3.0.0' - compile 'com.fasterxml.jackson.core:jackson-databind:2.2.0' - compile 'org.apache.commons:commons-collections4:4.0' - compile 'commons-io:commons-io:2.4' + compile "com.cedarsoftware:java-util:1.8.0" // for deep equals + compile "org.antlr:antlr4-runtime:4.5" // for serpent compilation + compile "com.yuvalshavit:antlr-denter:1.1" + compile "org.javassist:javassist:3.15.0-GA" + compile "org.slf4j:slf4j-api:${slf4jVersion}" + compile "log4j:log4j:${log4jVersion}" + compile "org.codehaus.jackson:jackson-mapper-asl:1.9.13" + compile "com.google.code.findbugs:jsr305:3.0.0" + compile "com.fasterxml.jackson.core:jackson-databind:2.5.1" + compile "org.apache.commons:commons-collections4:4.0" compile "commons-codec:commons-codec:1.10" - compile "com.h2database:h2:1.4.187" compile "org.hibernate:hibernate-core:${hibernateVersion}" compile "org.hibernate:hibernate-entitymanager:${hibernateVersion}" - + compile "commons-dbcp:commons-dbcp:1.4" compile "redis.clients:jedis:2.6.0" - compile('com.googlecode.json-simple:json-simple:1.1.1') { + compile "com.h2database:h2:1.4.187" + + compile "org.slf4j:slf4j-log4j12:${slf4jVersion}" + compile "log4j:apache-log4j-extras:${log4jVersion}" + + compile("com.googlecode.json-simple:json-simple:1.1.1") { exclude group: 'junit', module: 'junit' - exclude group: 'xml-apis', module: 'xml-apis' } + compile 'commons-io:commons-io:2.4' + testCompile "junit:junit:${junitVersion}" testCompile 'com.google.dagger:dagger:2.1-SNAPSHOT' testCompile 'com.google.dagger:dagger-compiler:2.0' -} \ No newline at end of file +} + diff --git a/ethereumj-core/src/main/java/org/ethereum/core/BlockchainImpl.java b/ethereumj-core/src/main/java/org/ethereum/core/BlockchainImpl.java index 6e890bd1..68b68ad6 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/BlockchainImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/BlockchainImpl.java @@ -295,7 +295,8 @@ public class BlockchainImpl implements Blockchain { storeBlock(block, receipts); - if (block.getNumber() % 20_000 == 0) { + if (adminInfo.isConsensus() && + block.getNumber() % 5_000 == 0) { repository.flush(); blockStore.flush(); } diff --git a/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java b/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java index 3d1ae931..39abcb12 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java @@ -324,9 +324,8 @@ public class TransactionExecutor { track.delete(address.getLast20Bytes()); } - // Keep execution logs todo: that yet -//*cpp* if (m_ext) -// m_logs = m_ext->sub.logs; + if (result != null) + logs = result.getLogInfoList(); if (result.getLogInfoList() != null){ diff --git a/ethereumj-core/src/main/java/org/ethereum/datasource/LevelDbDataSource.java b/ethereumj-core/src/main/java/org/ethereum/datasource/LevelDbDataSource.java index d18e2426..413d1724 100644 --- a/ethereumj-core/src/main/java/org/ethereum/datasource/LevelDbDataSource.java +++ b/ethereumj-core/src/main/java/org/ethereum/datasource/LevelDbDataSource.java @@ -2,12 +2,15 @@ package org.ethereum.datasource; import org.ethereum.config.SystemProperties; +import org.fusesource.leveldbjni.JniDBFactory; +import org.fusesource.leveldbjni.internal.JniDB; import org.iq80.leveldb.CompressionType; import org.iq80.leveldb.DB; import org.iq80.leveldb.DBIterator; import org.iq80.leveldb.Options; import org.iq80.leveldb.WriteBatch; +import org.iq80.leveldb.impl.Iq80DBFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,6 +49,10 @@ public class LevelDbDataSource implements KeyValueDataSource { Options options = new Options(); options.createIfMissing(true); options.compressionType(CompressionType.NONE); + options.blockSize(10 * 1024); + options.writeBufferSize(10 * 1024); + options.cacheSize(0); + try { logger.debug("Opening database"); File dbLocation = new File(System.getProperty("user.dir") + "/" + @@ -57,7 +64,23 @@ public class LevelDbDataSource implements KeyValueDataSource { } logger.debug("Initializing new or existing database: '{}'", name); - db = factory.open(fileLocation, options); + + try { + db = JniDBFactory.factory.open(fileLocation, options); + } catch (Throwable e) { + System.out.println("No native version of LevelDB found"); + } + + String cpu = System.getProperty("sun.arch.data.model"); + String os = System.getProperty("os.name"); + + if (db instanceof JniDB) + System.out.println("Native version of LevelDB loaded for: " + os + "." + cpu + "bit"); + else{ + System.out.println("Pure Java version of LevelDB loaded"); + db = Iq80DBFactory.factory.open(fileLocation, options); + } + } catch (IOException ioe) { logger.error(ioe.getMessage(), ioe); diff --git a/ethereumj-core/src/main/java/org/ethereum/db/DetailsDataStore.java b/ethereumj-core/src/main/java/org/ethereum/db/DetailsDataStore.java index a277d7f4..e362e632 100644 --- a/ethereumj-core/src/main/java/org/ethereum/db/DetailsDataStore.java +++ b/ethereumj-core/src/main/java/org/ethereum/db/DetailsDataStore.java @@ -2,6 +2,7 @@ package org.ethereum.db; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; import java.util.HashMap; import java.util.HashSet; @@ -35,6 +36,12 @@ public class DetailsDataStore { details = new ContractDetailsImpl(data); cache.put( wrap(key), details); + + float out = ((float)data.length) / 1048576; + if (out > 10) { + String sizeFmt = String.format("%02.2f", out); + System.out.println("loaded: key: " + Hex.toHexString(key) + " size: " + sizeFmt + "MB"); + } } return details; @@ -57,11 +64,13 @@ public class DetailsDataStore { long t = System.nanoTime(); Map batch = new HashMap<>(); + long totalSize = 0; for (ByteArrayWrapper key : cache.keySet()){ ContractDetails contractDetails = cache.get(key); byte[] value = contractDetails.getEncoded(); db.put(key.getData(), value); batch.put(key.getData(), value); + totalSize += value.length; } db.getDb().updateBatch(batch); @@ -72,11 +81,18 @@ public class DetailsDataStore { long keys = cache.size(); + byte[] aKey = Hex.decode("b61662398570293e4f0d25525e2b3002b7fe0836"); + ContractDetails aDetails = cache.get(wrap(aKey)); + cache.clear(); removes.clear(); + if (aDetails != null) cache.put(wrap(aKey), aDetails); + long t_ = System.nanoTime(); - gLogger.info("Flush details in: {} ms, {} keys", ((float)(t_ - t) / 1_000_000), keys); + String sizeFmt = String.format("%02.2f", ((float)totalSize) / 1048576); + gLogger.info("Flush details in: {} ms, {} keys, {}MB", + ((float)(t_ - t) / 1_000_000), keys, sizeFmt); } diff --git a/ethereumj-core/src/main/java/org/ethereum/db/InMemoryBlockStore.java b/ethereumj-core/src/main/java/org/ethereum/db/InMemoryBlockStore.java index 063b0669..fa525e46 100644 --- a/ethereumj-core/src/main/java/org/ethereum/db/InMemoryBlockStore.java +++ b/ethereumj-core/src/main/java/org/ethereum/db/InMemoryBlockStore.java @@ -189,10 +189,19 @@ public class InMemoryBlockStore implements BlockStore{ long t_ = System.nanoTime(); Session s = sessionFactory.openSession(); + + // clear old blocks s.beginTransaction(); - for (Block block : blocks){ + s.createQuery("delete from BlockVO").executeUpdate(); + s.getTransaction().commit(); + + s.beginTransaction(); + + int lastIndex = blocks.size() - 1; + for (int i = 0; i < 1000; ++i){ + Block block = blocks.get(lastIndex - i); BlockVO blockVO = new BlockVO(block.getNumber(), block.getHash(), block.getEncoded(), block.getCumulativeDifficulty()); - s.saveOrUpdate(blockVO); + s.save(blockVO); } s.getTransaction().commit(); diff --git a/ethereumj-core/src/main/java/org/ethereum/db/RepositoryImpl.java b/ethereumj-core/src/main/java/org/ethereum/db/RepositoryImpl.java index 5696ac68..be73d04f 100644 --- a/ethereumj-core/src/main/java/org/ethereum/db/RepositoryImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/db/RepositoryImpl.java @@ -171,12 +171,7 @@ public class RepositoryImpl implements Repository { gLogger.info("flushing to disk"); dds.flush(); - - long t = System.nanoTime(); worldState.sync(); - long t__ = System.nanoTime(); - - gLogger.info("Flush state in: {} ms", ((float)(t__ - t) / 1_000_000)); } diff --git a/ethereumj-core/src/main/java/org/ethereum/manager/BlockLoader.java b/ethereumj-core/src/main/java/org/ethereum/manager/BlockLoader.java index b827339a..56bf42fd 100644 --- a/ethereumj-core/src/main/java/org/ethereum/manager/BlockLoader.java +++ b/ethereumj-core/src/main/java/org/ethereum/manager/BlockLoader.java @@ -3,12 +3,12 @@ package org.ethereum.manager; import org.ethereum.core.Block; import org.ethereum.facade.Blockchain; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.spongycastle.util.encoders.Hex; - import java.io.FileInputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.List; import java.util.Scanner; import javax.inject.Inject; @@ -19,9 +19,7 @@ import static org.ethereum.config.SystemProperties.CONFIG; @Singleton public class BlockLoader { - private static final Logger logger = LoggerFactory.getLogger("BlockLoader"); - - private Blockchain blockchain; + protected Blockchain blockchain; Scanner scanner = null; @@ -30,11 +28,11 @@ public class BlockLoader { this.blockchain = blockchain; } - public long loadBlocks(){ + public void loadBlocks(){ String fileSrc = CONFIG.blocksLoader(); try { - long startTime = System.currentTimeMillis(); + FileInputStream inputStream = null; inputStream = new FileInputStream(fileSrc); scanner = new Scanner(inputStream, "UTF-8"); @@ -50,11 +48,16 @@ public class BlockLoader { if (block.getNumber() > blockchain.getBestBlock().getNumber()){ blockchain.tryToConnect(block); long t1_ = System.nanoTime(); - String result = String.format("Imported block #%d took: [%02.2f msec]", - block.getNumber(), ((float)(t1_ - t1) / 1_000_000)); - System.out.println(result); - } else { + float elapsed = ((float)(t1_ - t1) / 1_000_000); + + if (block.getNumber() % 1000 == 0 || elapsed > 10_000) { + String result = String.format("Imported block #%d took: [%02.2f msec]", + block.getNumber(), elapsed); + + System.out.println(result); + } + } else{ if (block.getNumber() % 10000 == 0) System.out.println("Skipping block #" + block.getNumber()); @@ -62,16 +65,9 @@ public class BlockLoader { } - long duration = System.currentTimeMillis() - startTime; - System.out.println("Finished loading blocks in " + (duration / 1000) + " seconds (" + (duration / 60000) + " minutes)"); - return duration; } catch (IOException e) { e.printStackTrace(); - logger.error(e.getMessage(), e); - } catch (Exception e) { - logger.error(e.getMessage(), e); } - return 0; } diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/Message.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/Message.java index bece2831..7da57c1e 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/Message.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/Message.java @@ -71,6 +71,8 @@ public abstract class Message { /* [2] Crate signature*/ ECKey.ECDSASignature signature = privKey.sign(forSig); + signature.v -= 27; + byte[] sigBytes = merge(BigIntegers.asUnsignedByteArray(signature.r), BigIntegers.asUnsignedByteArray(signature.s), new byte[]{signature.v}); @@ -116,6 +118,12 @@ public abstract class Message { return outKey; } + public byte[] getNodeId() { + byte[] nodeID = new byte[64]; + System.arraycopy(getKey().getPubKey(), 1, nodeID, 0, 64); + return nodeID; + } + public byte[] getPacket() { return wire; } diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/Node.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/Node.java index c11dfdd5..cc2195c4 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/Node.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/Node.java @@ -5,9 +5,10 @@ import org.ethereum.util.RLPList; import org.spongycastle.util.encoders.Hex; import java.nio.charset.Charset; +import java.util.Arrays; import static org.ethereum.util.ByteUtil.byteArrayToInt; -import static org.ethereum.util.ByteUtil.intToBytesNoLeadZeroes; +import static org.ethereum.util.ByteUtil.intToBytes; public class Node { @@ -28,9 +29,25 @@ public class Node { byte[] hostB = nodeRLP.get(0).getRLPData(); byte[] portB = nodeRLP.get(1).getRLPData(); - byte[] idB = nodeRLP.get(2).getRLPData(); + byte[] idB; - String host = new String(hostB, Charset.forName("UTF-8")); + if (nodeRLP.size() > 3) { + idB = nodeRLP.get(3).getRLPData(); + } else { + idB = nodeRLP.get(2).getRLPData(); + } + + StringBuilder sb = new StringBuilder(); + sb.append(hostB[0] & 0xFF); + sb.append("."); + sb.append(hostB[1] & 0xFF); + sb.append("."); + sb.append(hostB[2] & 0xFF); + sb.append("."); + sb.append(hostB[3] & 0xFF); + +// String host = new String(hostB, Charset.forName("UTF-8")); + String host = sb.toString(); int port = byteArrayToInt(portB); this.host = host; @@ -66,7 +83,7 @@ public class Node { public byte[] getRLP() { byte[] rlphost = RLP.encodeElement(host.getBytes(Charset.forName("UTF-8"))); - byte[] rlpPort = RLP.encodeElement(intToBytesNoLeadZeroes(port)); + byte[] rlpPort = RLP.encodeElement(intToBytes(port)); byte[] rlpId = RLP.encodeElement(id); byte[] data = RLP.encodeList(rlphost, rlpPort, rlpId); @@ -81,4 +98,26 @@ public class Node { ", id=" + Hex.toHexString(id) + '}'; } + + @Override + public int hashCode() { + return this.toString().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + + if (o == this) { + return true; + } + + if (o instanceof Node) { + return Arrays.equals(((Node) o).getId(), this.getId()); + } + + return false; + } } diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/PingMessage.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/PingMessage.java index f7e15f88..7f6f77f9 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/PingMessage.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/PingMessage.java @@ -5,7 +5,7 @@ import org.ethereum.util.ByteUtil; import org.ethereum.util.RLP; import org.ethereum.util.RLPItem; import org.ethereum.util.RLPList; -import org.spongycastle.util.encoders.Hex; +//import org.spongycastle.util.encoders.Hex; import java.nio.charset.Charset; @@ -28,11 +28,20 @@ public class PingMessage extends Message { byte[] tmpPort = longToBytes(port); byte[] rlpPort = RLP.encodeElement(stripLeadingZeroes(tmpPort)); + byte[] rlpIpTo = RLP.encodeElement(host.getBytes()); + + byte[] tmpPortTo = longToBytes(port); + byte[] rlpPortTo = RLP.encodeElement(stripLeadingZeroes(tmpPortTo)); + byte[] tmpExp = longToBytes(expiration); byte[] rlpExp = RLP.encodeElement(stripLeadingZeroes(tmpExp)); byte[] type = new byte[]{1}; - byte[] data = RLP.encodeList(rlpIp, rlpPort, rlpExp); + byte[] version = new byte[]{4}; + byte[] rlpVer = RLP.encodeElement(version); + byte[] rlpFromList = RLP.encodeList(rlpIp, rlpPort, rlpPort); + byte[] rlpToList = RLP.encodeList(rlpIpTo, rlpPortTo, rlpPortTo); + byte[] data = RLP.encodeList(rlpVer, rlpFromList, rlpToList, rlpExp); PingMessage ping = new PingMessage(); ping.encode(type, data, privKey); @@ -48,14 +57,15 @@ public class PingMessage extends Message { public void parse(byte[] data) { RLPList list = RLP.decode2(data); - list = (RLPList) list.get(0); + RLPList dataList = (RLPList) list.get(0); + RLPList fromList = (RLPList) dataList.get(2); - byte[] ipB = list.get(0).getRLPData(); + byte[] ipB = fromList.get(0).getRLPData(); this.host = new String(ipB, Charset.forName("UTF-8")); - this.port = ByteUtil.byteArrayToInt(list.get(1).getRLPData()); + this.port = ByteUtil.byteArrayToInt(fromList.get(1).getRLPData()); - RLPItem expires = (RLPItem) list.get(2); + RLPItem expires = (RLPItem) dataList.get(3); this.expires = ByteUtil.byteArrayToLong(expires.getRLPData()); } diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/PongMessage.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/PongMessage.java index 6b4e3add..a52cfc81 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/PongMessage.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/PongMessage.java @@ -7,13 +7,42 @@ import org.ethereum.util.RLPItem; import org.ethereum.util.RLPList; import org.spongycastle.util.encoders.Hex; -import java.nio.charset.Charset; +//import java.nio.charset.Charset; + +import static org.ethereum.util.ByteUtil.longToBytes; +import static org.ethereum.util.ByteUtil.stripLeadingZeroes; public class PongMessage extends Message { byte[] token; // token is the MDC of the ping long expires; + public static PongMessage create(byte[] token, String host, int port, ECKey privKey) { + + long expiration = 3 + System.currentTimeMillis() / 1000; + + byte[] rlpIp = RLP.encodeElement(host.getBytes()); + + byte[] tmpPort = longToBytes(port); + byte[] rlpPort = RLP.encodeElement(stripLeadingZeroes(tmpPort)); + byte[] rlpToList = RLP.encodeList(rlpIp, rlpPort, rlpPort); + + /* RLP Encode data */ + byte[] rlpToken = RLP.encodeElement(token); + byte[] tmpExp = longToBytes(expiration); + byte[] rlpExp = RLP.encodeElement(stripLeadingZeroes(tmpExp)); + + byte[] type = new byte[]{2}; + byte[] data = RLP.encodeList(rlpToList, rlpToken, rlpExp); + + PongMessage pong = new PongMessage(); + pong.encode(type, data, privKey); + + pong.token = token; + pong.expires = expiration; + + return pong; + } public static PongMessage create(byte[] token, ECKey privKey) { diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/DiscoverTask.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/DiscoverTask.java new file mode 100644 index 00000000..c239fa3f --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/DiscoverTask.java @@ -0,0 +1,92 @@ +package org.ethereum.net.rlpx.discover; + +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.socket.DatagramPacket; +import org.ethereum.crypto.ECKey; +import org.ethereum.net.rlpx.FindNodeMessage; +import org.ethereum.net.rlpx.Message; +import org.ethereum.net.rlpx.Node; +import org.ethereum.net.rlpx.discover.table.KademliaOptions; +import org.ethereum.net.rlpx.discover.table.NodeTable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; + +public class DiscoverTask implements Runnable { + private static final Logger logger = LoggerFactory.getLogger("discover"); + + Channel channel; + + NodeTable table; + + ECKey key; + + byte[] nodeId; + + DiscoverTask(byte[] nodeId, Channel channel, ECKey key, NodeTable table) { + this.nodeId = nodeId; + this.channel = channel; + this.key = key; + this.table = table; + } + + @Override + public void run() { + discover(nodeId, 0, new ArrayList()); + } + + public synchronized void discover(byte[] nodeId, int round, List prevTried) { + + try { +// if (!channel.isOpen() || round == KademliaOptions.MAX_STEPS) { +// logger.info("{}", String.format("Nodes discovered %d ", table.getAllNodes().size())); +// return; +// } + + if (round == KademliaOptions.MAX_STEPS) { + logger.info("{}", String.format("Terminating discover after %d rounds.", round)); + logger.info("{}", String.format("Nodes discovered %d ", table.getNodesCount())); + return; + } + + List closest = table.getClosestNodes(nodeId); + List tried = new ArrayList<>(); + + for (Node n : closest) { + if (!tried.contains(n) && !prevTried.contains(n)) { + try { + Message findNode = FindNodeMessage.create(nodeId, key); + DatagramPacket packet = new DatagramPacket( + Unpooled.copiedBuffer(findNode.getPacket()), + new InetSocketAddress(n.getHost(), n.getPort())); + channel.write(packet); + tried.add(n); + } catch (Exception ex) { + logger.info("{}", ex); + } + } + if (tried.size() == KademliaOptions.ALPHA) { + break; + } + } + + channel.flush(); + + if (tried.isEmpty()) { + logger.info("{}", String.format("Terminating discover after %d rounds.", round)); + logger.info("{}", String.format("Nodes discovered %d ", table.getNodesCount())); + return; + } + + tried.addAll(prevTried); + + discover(nodeId, round + 1, tried); + } catch (Exception ex) { + logger.info("{}", ex); + } + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/DiscoveryEvent.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/DiscoveryEvent.java new file mode 100644 index 00000000..36cd10cd --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/DiscoveryEvent.java @@ -0,0 +1,32 @@ +package org.ethereum.net.rlpx.discover; + + +import org.ethereum.net.rlpx.Message; + +import java.net.InetSocketAddress; + +public class DiscoveryEvent { + private Message message; + private InetSocketAddress address; + + public DiscoveryEvent(Message m, InetSocketAddress a) { + message = m; + address = a; + } + + public Message getMessage() { + return message; + } + + public void setMessage(Message message) { + this.message = message; + } + + public InetSocketAddress getAddress() { + return address; + } + + public void setAddress(InetSocketAddress address) { + this.address = address; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/DiscoveryExecutor.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/DiscoveryExecutor.java new file mode 100644 index 00000000..e1b582b2 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/DiscoveryExecutor.java @@ -0,0 +1,38 @@ +package org.ethereum.net.rlpx.discover; + +import io.netty.channel.Channel; +import org.ethereum.crypto.ECKey; +import org.ethereum.net.rlpx.discover.table.KademliaOptions; +import org.ethereum.net.rlpx.discover.table.NodeTable; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class DiscoveryExecutor { + Channel channel; + NodeTable table; + ECKey key; + + ScheduledExecutorService discoverer = Executors.newSingleThreadScheduledExecutor(); + ScheduledExecutorService refresher = Executors.newSingleThreadScheduledExecutor(); + + DiscoveryExecutor(Channel channel, NodeTable table, ECKey key) { + this.channel = channel; + this.table = table; + this.key = key; + } + + public void discover() { + + discoverer.scheduleWithFixedDelay( + new DiscoverTask(table.getNode().getId(), channel, key, table), + 0, KademliaOptions.DISCOVER_CYCLE, TimeUnit.SECONDS); + + refresher.scheduleWithFixedDelay( + new RefreshTask(channel, key, table), + 0, KademliaOptions.BUCKET_REFRESH, TimeUnit.MILLISECONDS); + + } + + +} diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/MessageHandler.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/MessageHandler.java new file mode 100644 index 00000000..fc915a52 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/MessageHandler.java @@ -0,0 +1,187 @@ +package org.ethereum.net.rlpx.discover; + +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.socket.DatagramPacket; +import org.antlr.v4.runtime.misc.Triple; +import org.ethereum.crypto.ECKey; +import org.ethereum.net.rlpx.*; +import org.ethereum.net.rlpx.discover.table.KademliaOptions; +import org.ethereum.net.rlpx.discover.table.NodeTable; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; + +import java.math.BigInteger; +import java.net.InetSocketAddress; +import java.util.*; + + +public class MessageHandler extends SimpleChannelInboundHandler { + private static final org.slf4j.Logger logger = LoggerFactory.getLogger("discover"); + + private ECKey key; + private NodeTable table; + private Map evictedCandidates = new HashMap<>(); + private Map expectedPongs = new HashMap<>(); + + public MessageHandler(ECKey key, NodeTable table) { + this.key = key; + this.table = table; + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, DiscoveryEvent event) throws Exception { + Message m = event.getMessage(); + InetSocketAddress sender = event.getAddress(); + byte type = m.getType()[0]; + switch (type) { + case 1: + handlePing(ctx, m, sender); + break; + case 2: + handlePong(ctx, m, sender); + break; + case 3: + handleFindNode(ctx, m, sender); + break; + case 4: + handleNeighbours(ctx, m, sender); + break; + } + } + + private void handlePing(ChannelHandlerContext ctx, Message m, InetSocketAddress sender) { + PingMessage ping = (PingMessage) m; + logger.info("{}", String.format("PING from %s", sender.toString())); + Node n = new Node(ping.getNodeId(), sender.getHostName(), sender.getPort()); + if (!table.getNode().equals(n)) { + update(ctx, n); + sendPong(ctx, ping.getMdc(), sender); + } + } + + private void handlePong(ChannelHandlerContext ctx, Message m, InetSocketAddress sender) { +// logger.info("{}", String.format("PONG from %s", sender.toString())); + PongMessage pong = (PongMessage) m; + Node n = new Node(pong.getNodeId(), sender.getHostName(), sender.getPort()); + update(ctx, n); + } + + private void handleNeighbours(ChannelHandlerContext ctx, Message m, InetSocketAddress sender) { + NeighborsMessage neighborsMessage = (NeighborsMessage) m; + logger.info("{}", String.format("NEIGHBOURS from %s", sender.toString())); + update(ctx, new Node(neighborsMessage.getNodeId(), sender.getHostName(), sender.getPort())); + for (Node n : neighborsMessage.getNodes()) { + update(ctx, n); + } + } + + private void update(ChannelHandlerContext ctx, Node n) { + + if(table.getNode().equals(n)) { + return; + } + + if (!table.contains(n)) { + if (expectedPongs.containsKey(n)) { + if (System.currentTimeMillis() - expectedPongs.get(n).getTime() + < KademliaOptions.REQ_TIMEOUT) { + if (evictedCandidates.containsKey(n)) { + logger.info("{}", String.format("Evicted node remains %s:%d, remove expected node %s:%d", n, evictedCandidates.get(n))); + expectedPongs.remove(n); + evictedCandidates.remove(n); + } else { + addNode(ctx, n); + } + } else { + if (evictedCandidates.containsKey(n)) { + logger.info("{}", String.format("Drop evicted %s:%d, add node %s:%d", n, evictedCandidates.get(n))); + dropNode(n); + addNode(ctx, evictedCandidates.get(n)); + } + expectedPongs.remove(n); + evictedCandidates.remove(n); + } + } else { + expectedPongs.put(n, new Date()); + sendPing(ctx, new InetSocketAddress(n.getHost(), n.getPort())); + } + } else { + table.touchNode(n); + } + + Set expiredExpected = new HashSet<>(); + long now = System.currentTimeMillis(); + for (Map.Entry e : expectedPongs.entrySet()) { + if (now - e.getValue().getTime() > KademliaOptions.REQ_TIMEOUT) { + if (evictedCandidates.containsKey(e.getKey())) { + Node evictionCandidate = e.getKey(); + Node replacement = evictedCandidates.get(evictionCandidate); + logger.info("{}", String.format("Drop evicted %s:%d, add node %s:%d", + evictionCandidate.getHost(), evictionCandidate.getPort(), + replacement.getHost(), replacement.getPort())); + dropNode(evictionCandidate); + addNode(ctx, replacement); + } + expiredExpected.add(e.getKey()); + } + } + + expectedPongs.keySet().removeAll(expiredExpected); + evictedCandidates.keySet().removeAll(expiredExpected); + } + + private void addNode(ChannelHandlerContext ctx, Node n) { + Node evictedCandidate = table.addNode(n); + if (evictedCandidate != null) { + expectedPongs.put(evictedCandidate, new Date()); + evictedCandidates.put(evictedCandidate, n); + expectedPongs.remove(n); + sendPing(ctx, new InetSocketAddress(evictedCandidate.getHost(), evictedCandidate.getPort())); + } + } + + private void dropNode(Node n) { + table.dropNode(n); + } + + private void sendPong(ChannelHandlerContext ctx, byte[] mdc, InetSocketAddress address) { + Message pong = PongMessage.create(mdc, address.getHostName(), address.getPort(), key); + sendPacket(ctx, pong.getPacket(), address); + } + + private void sendPing(ChannelHandlerContext ctx, InetSocketAddress address) { + Message ping = PingMessage.create(table.getNode().getHost(), table.getNode().getPort(), key); +// logger.info("{}", String.format("PING to %s:%d", address.getHostName(), address.getPort())); + sendPacket(ctx, ping.getPacket(), address); + } + + private void handleFindNode(ChannelHandlerContext ctx, Message m, InetSocketAddress sender) { + logger.info("{}", String.format("FIND from %s", sender.toString())); + FindNodeMessage msg = (FindNodeMessage) m; + List closest = table.getClosestNodes(msg.getTarget()); + sendNeighbours(ctx, closest, sender); + } + + private void sendNeighbours(ChannelHandlerContext ctx, List closest, InetSocketAddress address) { + NeighborsMessage neighbors = NeighborsMessage.create(closest, key); + sendPacket(ctx, neighbors.getPacket(), address); + } + + private void sendPacket(ChannelHandlerContext ctx, byte[] wire, InetSocketAddress address) { + DatagramPacket packet = new DatagramPacket(Unpooled.copiedBuffer(wire), address); + ctx.write(packet); + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) { + ctx.flush(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + cause.printStackTrace(); + // We don't close the channel because we can keep serving requests. + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/PacketDecoder.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/PacketDecoder.java new file mode 100644 index 00000000..7d083329 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/PacketDecoder.java @@ -0,0 +1,26 @@ +package org.ethereum.net.rlpx.discover; + + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.socket.DatagramPacket; +import io.netty.handler.codec.MessageToMessageDecoder; +import org.ethereum.crypto.ECKey; +import org.ethereum.net.rlpx.Message; +import org.slf4j.LoggerFactory; + +import java.util.List; + +public class PacketDecoder extends MessageToMessageDecoder { + private static final org.slf4j.Logger logger = LoggerFactory.getLogger("discover"); + + @Override + public void decode(ChannelHandlerContext ctx, DatagramPacket packet, List out) throws Exception { + ByteBuf buf = packet.content(); + byte[] encoded = new byte[buf.readableBytes()]; + buf.readBytes(encoded); + Message msg = Message.decode(encoded); + DiscoveryEvent event = new DiscoveryEvent(msg, packet.sender()); + out.add(event); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/RefreshTask.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/RefreshTask.java new file mode 100644 index 00000000..16661a48 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/RefreshTask.java @@ -0,0 +1,31 @@ +package org.ethereum.net.rlpx.discover; + +import io.netty.channel.Channel; +import org.ethereum.crypto.ECKey; +import org.ethereum.net.rlpx.Node; +import org.ethereum.net.rlpx.discover.table.NodeTable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Random; + +public class RefreshTask extends DiscoverTask { + private static final Logger logger = LoggerFactory.getLogger("discover"); + + RefreshTask(Channel channel, ECKey key, NodeTable table) { + super(getNodeId(), channel, key, table); + } + + public static byte[] getNodeId() { + Random gen = new Random(); + byte[] id = new byte[64]; + gen.nextBytes(id); + return id; + } + + @Override + public void run() { + discover(getNodeId(), 0, new ArrayList()); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/UDPListener.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/UDPListener.java new file mode 100644 index 00000000..d414ac1d --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/UDPListener.java @@ -0,0 +1,91 @@ +package org.ethereum.net.rlpx.discover; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioDatagramChannel; +import org.ethereum.crypto.ECKey; +import org.ethereum.net.rlpx.Node; +import org.ethereum.net.rlpx.discover.table.NodeTable; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + + +public class UDPListener { + private static final org.slf4j.Logger logger = LoggerFactory.getLogger("discover"); + + private final int port; + private final String address; + private final ECKey key; + private NodeTable table; + + public UDPListener(String address, int port) { + this.address = address; + this.port = port; + key = ECKey.fromPrivate(BigInteger.TEN).decompress(); + } + + public void start() throws Exception { + + NioEventLoopGroup group = new NioEventLoopGroup(); + byte[] nodeID = new byte[64]; + System.arraycopy(key.getPubKey(), 1, nodeID, 0, 64); + Node homeNode = new Node(nodeID, address, port); + table = new NodeTable(homeNode); + + //add default node on local to connect +// byte[] peerId = Hex.decode("621168019b7491921722649cd1aa9608f23f8857d782e7495fb6765b821002c4aac6ba5da28a5c91b432e5fcc078931f802ffb5a3ababa42adee7a0c927ff49e"); +// Node p = new Node(peerId, "127.0.0.1", 30303); +// table.addNode(p); + + //Persist the list of known nodes with their reputation + byte[] cppId = Hex.decode("487611428e6c99a11a9795a6abe7b529e81315ca6aad66e2a2fc76e3adf263faba0d35466c2f8f68d561dbefa8878d4df5f1f2ddb1fbeab7f42ffb8cd328bd4a"); + Node cppBootstrap = new Node(cppId, "5.1.83.226", 30303); + table.addNode(cppBootstrap); + + byte[] cpp2Id = Hex.decode("1637a970d987ddb8fd18c5ca01931210cd2ac5d2fe0f42873c0b31f110c5cbedf68589ec608ec5421e1d259b06cba224127c6bbddbb7c26eaaea56423a23bd31"); + Node cpp2Bootstrap = new Node(cpp2Id, "69.140.163.94", 30320); + table.addNode(cpp2Bootstrap); + + try { + Bootstrap b = new Bootstrap(); + b.group(group) + .option(ChannelOption.SO_TIMEOUT, 1000) + .channel(NioDatagramChannel.class) + .handler(new ChannelInitializer() { + @Override + public void initChannel(NioDatagramChannel ch) + throws Exception { + ch.pipeline().addLast(new PacketDecoder()); + ch.pipeline().addLast(new MessageHandler(key, table)); + } + }); + + Channel channel = b.bind(address, port).sync().channel(); + + DiscoveryExecutor discoveryExecutor = new DiscoveryExecutor(channel, table, key); + discoveryExecutor.discover(); + + channel.closeFuture().sync(); + + } catch (Exception e) { + logger.error("{}", e); + } finally { + group.shutdownGracefully().sync(); + } + } + + public static void main(String[] args) throws Exception { + String address = "0.0.0.0"; + int port = 30303; + if (args.length >= 2) { + address = args[0]; + port = Integer.parseInt(args[1]); + } + new UDPListener(address, port).start(); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/table/DistanceComparator.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/table/DistanceComparator.java new file mode 100644 index 00000000..744dd7ab --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/table/DistanceComparator.java @@ -0,0 +1,28 @@ +package org.ethereum.net.rlpx.discover.table; + +import java.util.Comparator; + +/** + * Created by kest on 5/26/15. + */ +public class DistanceComparator implements Comparator { + byte[] targetId; + + DistanceComparator(byte[] targetId) { + this.targetId = targetId; + } + + @Override + public int compare(NodeEntry e1, NodeEntry e2) { + int d1 = NodeEntry.distance(targetId, e1.getNode().getId()); + int d2 = NodeEntry.distance(targetId, e2.getNode().getId()); + + if (d1 > d2) { + return 1; + } else if (d1 < d2) { + return -1; + } else { + return 0; + } + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/table/KademliaOptions.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/table/KademliaOptions.java new file mode 100644 index 00000000..154be65f --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/table/KademliaOptions.java @@ -0,0 +1,15 @@ +package org.ethereum.net.rlpx.discover.table; + +/** + * Created by kest on 5/25/15. + */ +public class KademliaOptions { + public static final int BUCKET_SIZE = 16; + public static final int ALPHA = 3; + public static final int BINS = 256; + public static final int MAX_STEPS = 8; + + public static final long REQ_TIMEOUT = 300; + public static final long BUCKET_REFRESH = 7200; //bucket refreshing interval in millis + public static final long DISCOVER_CYCLE = 30; //discovery cycle interval in seconds +} diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/table/NodeBucket.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/table/NodeBucket.java new file mode 100644 index 00000000..7069110a --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/table/NodeBucket.java @@ -0,0 +1,62 @@ +package org.ethereum.net.rlpx.discover.table; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * Created by kest on 5/25/15. + */ +public class NodeBucket { + + private final int depth; + private List nodes = new ArrayList<>(); + + NodeBucket(int depth) { + this.depth = depth; + } + + public int getDepth() { + return depth; + } + + public synchronized NodeEntry addNode(NodeEntry e) { + if (!nodes.contains(e)) { + if (nodes.size() >= KademliaOptions.BUCKET_SIZE) { + return getLastSeen(); + } else { + nodes.add(e); + } + } + + return null; + } + + private NodeEntry getLastSeen() { + List sorted = nodes; + Collections.sort(sorted, new TimeComparator()); + return sorted.get(0); + } + + public synchronized void dropNode(NodeEntry entry) { + for (NodeEntry e : nodes) { + if (e.getId().equals(entry.getId())) { + nodes.remove(e); + break; + } + } + } + + public int getNodesCount() { + return nodes.size(); + } + + public List getNodes() { + List nodes = new ArrayList<>(); + for (NodeEntry e : this.nodes) { + nodes.add(e); + } + return nodes; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/table/NodeEntry.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/table/NodeEntry.java new file mode 100644 index 00000000..36816c4c --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/table/NodeEntry.java @@ -0,0 +1,111 @@ +package org.ethereum.net.rlpx.discover.table; + +import org.ethereum.net.rlpx.Node; + +import static org.ethereum.crypto.HashUtil.sha3; + +/** + * Created by kest on 5/25/15. + */ +public class NodeEntry { + private byte[] ownerId; + Node node; + private String entryId; + private int distance; + private long modified; + + public NodeEntry(Node n) { + this.node = n; + this.ownerId = n.getId(); + entryId = n.toString(); + distance = distance(ownerId, n.getId()); + touch(); + } + + public NodeEntry(byte[] ownerId, Node n) { + this.node = n; + this.ownerId = ownerId; + entryId = n.toString(); + distance = distance(ownerId, n.getId()); + touch(); + } + + public void touch() { + modified = System.currentTimeMillis(); + } + + public int getDistance() { + return distance; + } + + public String getId() { + return entryId; + } + + public Node getNode() { + return node; + } + + public long getModified() { + return modified; + } + + @Override + public boolean equals(Object o) { + boolean ret = false; + + if (o instanceof NodeEntry){ + NodeEntry e = (NodeEntry) o; + ret = this.getId().equals(e.getId()); + } + + return ret; + } + + @Override + public int hashCode() { + return this.node.hashCode(); + } + + public static int distance(byte[] ownerId, byte[] targetId) { + byte[] h1 = sha3(targetId); + byte[] h2 = sha3(ownerId); + + byte[] hash = new byte[Math.min(h1.length, h2.length)]; + + for (int i = 0; i < hash.length; i++) { + hash[i] = (byte) (((int) h1[i]) ^ ((int) h2[i])); + } + + int d = KademliaOptions.BINS; + + for (byte b : hash) + { + if (b == 0) + { + d -= 8; + } + else + { + int count = 0; + for (int i = 7; i >= 0; i--) + { + boolean a = (b & (1 << i)) == 0; + if (a) + { + count++; + } + else + { + break; + } + } + + d -= count; + + break; + } + } + return d; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/table/NodeTable.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/table/NodeTable.java new file mode 100644 index 00000000..ed538c0c --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/table/NodeTable.java @@ -0,0 +1,129 @@ +package org.ethereum.net.rlpx.discover.table; + +import org.ethereum.net.rlpx.Node; + +import java.util.*; + +/** + * Created by kest on 5/25/15. + */ +public class NodeTable { + + private final Node node; // our node + private transient NodeBucket[] buckets; + private transient List nodes; + private Map evictedCandidates = new HashMap<>(); + private Map expectedPongs = new HashMap<>(); + + public NodeTable(Node n) { + this.node = n; + initialize(); + addNode(this.node); + } + + public Node getNode() { + return node; + } + + public final void initialize() + { + nodes = new ArrayList<>(); + buckets = new NodeBucket[KademliaOptions.BINS]; + for (int i = 0; i < KademliaOptions.BINS; i++) + { + buckets[i] = new NodeBucket(i); + } + } + + public synchronized Node addNode(Node n) { + NodeEntry e = new NodeEntry(node.getId(), n); + NodeEntry lastSeen = buckets[getBucketId(e)].addNode(e); + if (lastSeen != null) { + return lastSeen.getNode(); + } + if (!nodes.contains(e)) { + nodes.add(e); + } + return null; + } + + public synchronized void dropNode(Node n) { + NodeEntry e = new NodeEntry(node.getId(), n); + buckets[getBucketId(e)].dropNode(e); + nodes.remove(e); + } + + public synchronized boolean contains(Node n) { + NodeEntry e = new NodeEntry(node.getId(), n); + for (NodeBucket b : buckets) { + if (b.getNodes().contains(e)) { + return true; + } + } + return false; + } + + public synchronized void touchNode(Node n) { + NodeEntry e = new NodeEntry(node.getId(), n); + for (NodeBucket b : buckets) { + if (b.getNodes().contains(e)) { + b.getNodes().get(b.getNodes().indexOf(e)).touch(); + break; + } + } + } + + public int getBucketsCount() { + int i = 0; + for (NodeBucket b : buckets) { + if (b.getNodesCount() > 0) { + i++; + } + } + return i; + } + + public synchronized NodeBucket[] getBuckets() { + return buckets; + } + + public int getBucketId(NodeEntry e) { + int id = e.getDistance() - 1; + return id < 0 ? 0 : id; + } + + public synchronized int getNodesCount() { + return nodes.size(); + } + + public synchronized List getAllNodes() + { + List nodes = new ArrayList<>(); + + for (NodeBucket b : buckets) + { + for (NodeEntry e : b.getNodes()) + { + if (!e.getNode().equals(node)) { + nodes.add(e); + } + } + } + + return nodes; + } + + public synchronized List getClosestNodes(byte[] targetId) { + List closestEntries = getAllNodes(); + List closestNodes = new ArrayList<>(); + Collections.sort(closestEntries, new DistanceComparator(targetId)); + if (closestEntries.size() > KademliaOptions.BUCKET_SIZE) { + closestEntries = closestEntries.subList(0, KademliaOptions.BUCKET_SIZE); + } + + for (NodeEntry e : closestEntries) { + closestNodes.add(e.getNode()); + } + return closestNodes; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/table/TimeComparator.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/table/TimeComparator.java new file mode 100644 index 00000000..1de11adb --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/table/TimeComparator.java @@ -0,0 +1,23 @@ +package org.ethereum.net.rlpx.discover.table; + +import java.util.Comparator; + +/** + * Created by kest on 5/26/15. + */ +public class TimeComparator implements Comparator { + + @Override + public int compare(NodeEntry e1, NodeEntry e2) { + long t1 = e1.getModified(); + long t2 = e2.getModified(); + + if (t1 < t2) { + return 1; + } else if (t1 >= t2) { + return -1; + } else { + return 0; + } + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/net/shh/Envelope.java b/ethereumj-core/src/main/java/org/ethereum/net/shh/Envelope.java new file mode 100644 index 00000000..bab318ab --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/net/shh/Envelope.java @@ -0,0 +1,200 @@ +package org.ethereum.net.shh; + +import org.ethereum.crypto.ECKey; +import org.ethereum.util.ByteUtil; +import org.ethereum.util.RLP; +import org.ethereum.util.RLPElement; +import org.ethereum.util.RLPList; +import org.spongycastle.util.encoders.Hex; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Vector; + +import static org.ethereum.net.shh.ShhMessageCodes.MESSAGE; +import static org.ethereum.crypto.HashUtil.sha3; + +/** + * Created by kest on 6/12/15. + */ +public class Envelope extends ShhMessage { + + private long expire; + private long ttl; + + private Topic[] topics; + private byte[] data; + + private int nonce = 0; + + public Envelope(byte[] encoded) { + super(encoded); + } + + public Envelope(long ttl, Topic[] topics, Message msg) { + this.expire = System.currentTimeMillis() + ttl; + this.ttl = ttl; + this.topics = topics; + this.data = msg.getBytes(); + this.nonce = 0; + } + + public Message open(ECKey privKey) { + if (!parsed) { + parse(); + } + byte[] data = this.data; + + long sent = this.expire - this.ttl; + Message m = new Message(data[0], sent, this.ttl, hash()); + + if ((m.getFlags() & Message.SIGNATURE_FLAG) == Message.SIGNATURE_FLAG) { + if (data.length < Message.SIGNATURE_LENGTH) { + throw new Error("Unable to open the envelope. First bit set but len(data) < len(signature)"); + } + byte[] signature = new byte[Message.SIGNATURE_LENGTH]; + System.arraycopy(data, 1, signature, 0, Message.SIGNATURE_LENGTH); + m.setSignature(signature); + byte[] payload = new byte[data.length - Message.SIGNATURE_LENGTH - 1]; + System.arraycopy(data, Message.SIGNATURE_LENGTH + 1, payload, 0, payload.length); + m.setPayload(payload); + } else { + byte[] payload = new byte[data.length - 1]; + System.arraycopy(data, 1, payload, 0, payload.length); + m.setPayload(payload); + } + + if (privKey == null) { + return m; + } + + m.decrypt(privKey); + + return m; + } + + private void parse() { + if (encoded == null) encode(); + RLPList paramsList = (RLPList) RLP.decode2(encoded).get(0); + + this.expire = ByteUtil.byteArrayToLong(paramsList.get(0).getRLPData()); + this.ttl = ByteUtil.byteArrayToLong(paramsList.get(1).getRLPData()); + + List topics = new ArrayList<>(); + RLPList topicsList = (RLPList) RLP.decode2(paramsList.get(2).getRLPData()).get(0); + for (RLPElement e : topicsList) { + topics.add(new Topic(e.getRLPData())); + } + this.topics = new Topic[topics.size()]; + topics.toArray(this.topics); + + this.data = paramsList.get(3).getRLPData(); + this.nonce = ByteUtil.byteArrayToInt(paramsList.get(4).getRLPData()); + + this.parsed = true; + } + + private void encode() { + byte[] expire = RLP.encode(this.expire); + byte[] ttl = RLP.encode(this.expire); + + List topics = new Vector<>(); + for (Topic t : this.topics) { + topics.add(RLP.encodeElement(t.getBytes())); + } + byte[][] topicsArray = topics.toArray(new byte[topics.size()][]); + byte[] encodedTopics = RLP.encodeList(topicsArray); + + byte[] data = RLP.encodeElement(this.data); + byte[] nonce = RLP.encodeInt(this.nonce); + + this.encoded = RLP.encodeList(expire, ttl, encodedTopics, data, nonce); + } + + private byte[] encodeWithoutNonce() { + byte[] expire = RLP.encode(this.expire); + byte[] ttl = RLP.encode(this.expire); + + List topics = new Vector<>(); + for (Topic t : this.topics) { + topics.add(RLP.encodeElement(t.getBytes())); + } + byte[][] topicsArray = topics.toArray(new byte[topics.size()][]); + byte[] encodedTopics = RLP.encodeList(topicsArray); + + byte[] data = RLP.encodeElement(this.data); + + return RLP.encodeList(expire, ttl, encodedTopics, data); + } + + //TODO: complete the nonce implementation + public void seal(long pow) { + byte[] d = new byte[64]; + Arrays.fill(d, (byte) 0); + byte[] rlp = encodeWithoutNonce(); + + long then = System.currentTimeMillis() + pow; + this.nonce = 0; + for (int bestBit = 0; System.currentTimeMillis() < then; ) { + for (int i = 0; i < 1024; ++i, ++bestBit) { + + } + } + } + + private byte[] hash() { + if (encoded == null) encode(); + return sha3(encoded); + } + + public long getExpire() { + if(!parsed) { + parse(); + } + return expire; + } + + public long getTtl() { + if(!parsed) { + parse(); + } + return ttl; + } + + public Topic[] getTopics() { + if(!parsed) { + parse(); + } + return topics; + } + + public byte[] getData() { + if(!parsed) { + parse(); + } + return data; + } + + @Override + public ShhMessageCodes getCommand() { + return MESSAGE; + } + + @Override + public byte[] getEncoded() { + if (encoded == null) encode(); + return encoded; + } + + @Override + public Class getAnswerMessage() { + return null; + } + + @Override + public String toString() { + return this.toString(); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/net/shh/Message.java b/ethereumj-core/src/main/java/org/ethereum/net/shh/Message.java new file mode 100644 index 00000000..181457f4 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/net/shh/Message.java @@ -0,0 +1,186 @@ +package org.ethereum.net.shh; + +import org.ethereum.crypto.ECIESCoder; +import org.ethereum.crypto.ECKey; +import org.spongycastle.util.BigIntegers; + +import org.spongycastle.math.ec.ECPoint; + +import java.security.SignatureException; +import java.util.Random; + +import static org.ethereum.crypto.HashUtil.sha3; +import static org.ethereum.net.shh.ShhMessageCodes.MESSAGE; +import static org.ethereum.util.ByteUtil.merge; + +/** + * Created by kest on 6/12/15. + */ +public class Message extends ShhMessage { + + private byte flags; + private byte[] signature; + private byte[] payload; + + private long sent; + private long ttl; + + private byte[] envelopeHash; + + public static final byte SIGNATURE_FLAG = 127; + public static final int SIGNATURE_LENGTH = 65; + +// public Message(byte[] encoded) { +// super(encoded); +// } + + public Message(byte[] payload) { + super(null); + Random r = new Random(); + byte[] randByte = new byte[1]; + r.nextBytes(randByte); + flags = randByte[0]; + if (flags < 0) { + flags = (byte)(flags & 0xF); + } + flags &= ~SIGNATURE_FLAG; + + this.sent = System.currentTimeMillis(); + this.payload = payload; + } + + public Message(byte flags, long sent, long ttl, byte[] envelopeHash) { + this.flags = flags; + this.sent = sent; + this.ttl = ttl; + this.envelopeHash = envelopeHash; + } + + public Envelope wrap(long pow, Options options) { + //check ttl is not null + if (options.getPrivateKey() != null) { + sign(options.getPrivateKey()); + } + + if (options.getToPublicKey() != null) { + encrypt(options.getToPublicKey()); + } + + Envelope e = new Envelope(options.getTtl(), options.getTopics(), this); + return e; + } + + public byte getFlags() { + return flags; + } + + public void setPayload(byte[] payload) { + this.payload = payload; + } + + public byte[] getPayload() { + return payload; + } + + public byte[] getSignature() { + return signature; + } + + public void setSignature(byte[] signature) { + this.signature = signature; + } + + public byte[] getBytes() { + if (signature != null) { + return merge(new byte[]{flags}, signature, payload); + } else { + return merge(new byte[]{flags}, payload); + } + } + + private void encrypt(byte[] toPublicKey) { + try { + ECKey key = ECKey.fromPublicOnly(toPublicKey); + ECPoint pubKeyPoint = key.getPubKeyPoint(); + payload = ECIESCoder.encrypt(pubKeyPoint, payload); + } catch (Exception e) { + + } + } + + public boolean decrypt(ECKey privateKey) { + try { + payload = ECIESCoder.decrypt(privateKey.getPrivKey(), payload); + return true; + } catch (Exception e) { + System.out.println("The message payload isn't encrypted or something is wrong"); + } catch (Throwable e) { + + } + return false; + } + + private void sign(ECKey privateKey) { + flags |= SIGNATURE_FLAG; + byte[] forSig = hash(); + + ECKey.ECDSASignature signature = privateKey.sign(forSig); + + this.signature = + merge(BigIntegers.asUnsignedByteArray(signature.r), + BigIntegers.asUnsignedByteArray(signature.s), new byte[]{signature.v}); + } + + public ECKey recover() { + if (signature == null) { + return null; + } + + byte[] r = new byte[32]; + byte[] s = new byte[32]; + byte v = signature[64]; + + if (v == 1) v = 28; + if (v == 0) v = 27; + + System.arraycopy(signature, 0, r, 0, 32); + System.arraycopy(signature, 32, s, 0, 32); + + ECKey.ECDSASignature signature = ECKey.ECDSASignature.fromComponents(r, s, v); + byte[] msgHash = hash(); + + ECKey outKey = null; + try { + outKey = ECKey.signatureToKey(msgHash, signature.toBase64()); + } catch (SignatureException e) { + e.printStackTrace(); + } + + return outKey; + } + + private byte[] hash() { + return sha3(merge(new byte[]{flags}, payload)); + } + + @Override + public ShhMessageCodes getCommand() { + return MESSAGE; + } + + @Override + public byte[] getEncoded() { + return encoded; + } + + @Override + public Class getAnswerMessage() { + return null; + } + + @Override + public String toString() { + return this.toString(); + } + +} diff --git a/ethereumj-core/src/main/java/org/ethereum/net/shh/Options.java b/ethereumj-core/src/main/java/org/ethereum/net/shh/Options.java new file mode 100644 index 00000000..3a4d9170 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/net/shh/Options.java @@ -0,0 +1,52 @@ +package org.ethereum.net.shh; + +import org.ethereum.crypto.ECKey; + +/** + * Created by kest on 6/13/15. + */ +public class Options { + private ECKey privateKey; + private byte[] toPublicKey; + private Topic[] topics; + private long ttl; + + public Options(ECKey privateKey, byte[] toPublicKey, Topic[] topics, long ttl) { + this.privateKey = privateKey; + this.toPublicKey = toPublicKey; + this.topics = topics; + this.ttl = ttl; + } + + public ECKey getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(ECKey privateKey) { + this.privateKey = privateKey; + } + + public byte[] getToPublicKey() { + return toPublicKey; + } + + public void setToPublicKey(byte[] toPublicKey) { + this.toPublicKey = toPublicKey; + } + + public Topic[] getTopics() { + return topics; + } + + public void setTopics(Topic[] topics) { + this.topics = topics; + } + + public long getTtl() { + return ttl; + } + + public void setTtl(long ttl) { + this.ttl = ttl; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/net/shh/Topic.java b/ethereumj-core/src/main/java/org/ethereum/net/shh/Topic.java new file mode 100644 index 00000000..18631598 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/net/shh/Topic.java @@ -0,0 +1,25 @@ +package org.ethereum.net.shh; + +import static org.ethereum.crypto.HashUtil.sha3; + +/** + * Created by kest on 6/12/15. + */ +public class Topic { + + + private byte[] topic = new byte[4]; + + public Topic(byte[] data) { + byte[] topic = sha3(data); + System.arraycopy(topic, 0, this.topic, 0, 4); + } + + public Topic(String data) { + this(data.getBytes()); + } + + public byte[] getBytes() { + return topic; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/trie/Cache.java b/ethereumj-core/src/main/java/org/ethereum/trie/Cache.java index 9213b7da..2113441e 100644 --- a/ethereumj-core/src/main/java/org/ethereum/trie/Cache.java +++ b/ethereumj-core/src/main/java/org/ethereum/trie/Cache.java @@ -4,18 +4,24 @@ import org.ethereum.crypto.HashUtil; import org.ethereum.datasource.KeyValueDataSource; import org.ethereum.db.ByteArrayWrapper; import org.ethereum.util.Value; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import static org.ethereum.util.ByteUtil.wrap; + /** * @author Nick Savers * @since 20.05.2014 */ public class Cache { + private static final Logger gLogger = LoggerFactory.getLogger("general"); + private final KeyValueDataSource dataSource; private Map nodes = new ConcurrentHashMap<>(); private boolean isDirty; @@ -34,8 +40,8 @@ public class Cache { Value value = new Value(o); byte[] enc = value.encode(); if (enc.length >= 32) { - byte[] sha = HashUtil.sha3(enc); - this.nodes.put(new ByteArrayWrapper(sha), new Node(value, true)); + byte[] sha = value.hash(); + this.nodes.put(wrap(sha), new Node(value, true)); this.isDirty = true; return sha; } @@ -68,6 +74,7 @@ public class Cache { public void commit() { + long t = System.nanoTime(); if (dataSource == null) return; // Don't try to commit if it isn't dirty @@ -76,17 +83,31 @@ public class Cache { } + long size = 0; + long keys = 0; Map batch = new HashMap<>(); for (ByteArrayWrapper key : this.nodes.keySet()) { Node node = this.nodes.get(key); if (node.isDirty()) { - batch.put(key.getData(), node.getValue().encode()); + + byte[] value = node.getValue().encode(); + batch.put(key.getData(), value); node.setDirty(false); + + size += value.length; + keys += 1; } } dataSource.updateBatch(batch); this.isDirty = false; + this.nodes.clear(); + + long t_ = System.nanoTime(); + String sizeFmt = String.format("%02.2f", ((float)size) / 1048576); + gLogger.info("Flush state in: {} ms, {} nodes, {}MB", + ((float)(t_ - t) / 1_000_000), keys, sizeFmt); + } public void undo() { diff --git a/ethereumj-core/src/main/java/org/ethereum/trie/TrieImpl.java b/ethereumj-core/src/main/java/org/ethereum/trie/TrieImpl.java index c0d00145..f62dc156 100644 --- a/ethereumj-core/src/main/java/org/ethereum/trie/TrieImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/trie/TrieImpl.java @@ -163,8 +163,7 @@ public class TrieImpl implements Trie { return (byte[]) this.getRoot(); } else { Value rootValue = new Value(this.getRoot()); - byte[] val = rootValue.encode(); - return HashUtil.sha3(val); + return rootValue.hash(); } } diff --git a/ethereumj-core/src/main/java/org/ethereum/util/ByteUtil.java b/ethereumj-core/src/main/java/org/ethereum/util/ByteUtil.java index fbe5011c..352d01ea 100644 --- a/ethereumj-core/src/main/java/org/ethereum/util/ByteUtil.java +++ b/ethereumj-core/src/main/java/org/ethereum/util/ByteUtil.java @@ -115,19 +115,32 @@ public class ByteUtil { return stripLeadingZeroes(data); } + public static byte[] intToBytes(int val){ - /** - * Converts a int value into a byte array. - * - * @param val - int value to convert - * @return decimal value with leading byte that are zeroes striped - */ - public static byte[] intToBytesNoLeadZeroes(int val) { - return longToBytesNoLeadZeroes((long)val); + if (val == 0) return EMPTY_BYTE_ARRAY; + + int lenght = 0; + + int tmpVal = val; + while (tmpVal != 0){ + tmpVal = tmpVal >> 8; + ++lenght; + } + + byte[] result = new byte[lenght]; + + int index = result.length - 1; + while(val != 0){ + + result[index] = (byte)(val & 0xFF); + val = val >> 8; + index -= 1; + } + + return result; } - /** * Convert a byte-array into a hex String.
* Works similar to {@link Hex#toHexString} diff --git a/ethereumj-core/src/main/java/org/ethereum/util/RLP.java b/ethereumj-core/src/main/java/org/ethereum/util/RLP.java index 23d70010..6dc96617 100644 --- a/ethereumj-core/src/main/java/org/ethereum/util/RLP.java +++ b/ethereumj-core/src/main/java/org/ethereum/util/RLP.java @@ -7,9 +7,7 @@ import java.nio.ByteBuffer; import java.util.*; import static java.util.Arrays.copyOfRange; -import static org.ethereum.util.ByteUtil.byteArrayToInt; -import static org.ethereum.util.ByteUtil.isNullOrZeroArray; -import static org.ethereum.util.ByteUtil.isSingleZero; +import static org.ethereum.util.ByteUtil.*; import static org.spongycastle.util.Arrays.concatenate; import static org.spongycastle.util.BigIntegers.asUnsignedByteArray; @@ -687,7 +685,7 @@ public class RLP { } else if (length < MAX_ITEM_LENGTH) { byte[] binaryLength; if (length > 0xFF) - binaryLength = BigInteger.valueOf(length).toByteArray(); + binaryLength = intToBytes(length); else binaryLength = new byte[]{(byte) length}; byte firstByte = (byte) (binaryLength.length + offset + SIZE_THRESHOLD - 1); @@ -700,7 +698,7 @@ public class RLP { public static byte[] encodeByte(byte singleByte) { if ((singleByte & 0xFF) == 0) { return new byte[]{(byte) OFFSET_SHORT_ITEM}; - } else if ((singleByte & 0xFF) < 0x7F) { + } else if ((singleByte & 0xFF) <= 0x7F) { return new byte[]{singleByte}; } else { return new byte[]{(byte) (OFFSET_SHORT_ITEM + 1), singleByte}; diff --git a/ethereumj-core/src/main/java/org/ethereum/util/Value.java b/ethereumj-core/src/main/java/org/ethereum/util/Value.java index 81c3f762..d2878520 100644 --- a/ethereumj-core/src/main/java/org/ethereum/util/Value.java +++ b/ethereumj-core/src/main/java/org/ethereum/util/Value.java @@ -2,6 +2,7 @@ package org.ethereum.util; import com.cedarsoftware.util.DeepEquals; +import org.ethereum.crypto.HashUtil; import org.spongycastle.util.encoders.Hex; import java.math.BigInteger; @@ -15,6 +16,8 @@ import java.util.List; public class Value { private Object value; + private byte[] rlp; + private byte[] sha3; public static Value fromRlpEncoded(byte[] data) { if (data != null && data.length != 0) { @@ -120,7 +123,15 @@ public class Value { * *****************/ public byte[] encode() { - return RLP.encode(value); + if (rlp == null) + rlp = RLP.encode(value); + return rlp; + } + + public byte[] hash(){ + if (sha3 == null) + sha3 = HashUtil.sha3(encode()); + return sha3; } public boolean cmp(Value o) { diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/DataWord.java b/ethereumj-core/src/main/java/org/ethereum/vm/DataWord.java index c828ac47..c42447da 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/DataWord.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/DataWord.java @@ -296,6 +296,17 @@ public class DataWord implements Comparable { return Hex.toHexString(data); } + public String toPrefixString() { + + byte[] pref = getNoLeadZeroesData(); + if (pref.length == 0) return ""; + + if (pref.length < 7) + return Hex.toHexString(pref); + + return Hex.toHexString(pref).substring(0, 6); + } + public String shortHex() { String hexValue = Hex.toHexString(getNoLeadZeroesData()).toUpperCase(); return "0x" + hexValue.replaceFirst("^0+(?!$)", ""); diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/Memory.java b/ethereumj-core/src/main/java/org/ethereum/vm/Memory.java index aebd80b2..5c738415 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/Memory.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/Memory.java @@ -28,7 +28,7 @@ public class Memory implements ProgramTraceListenerAware { public byte[] read(int address, int size) { if (size <= 0) return EMPTY_BYTE_ARRAY; - + extend(address, size); byte[] data = new byte[size]; @@ -53,13 +53,21 @@ public class Memory implements ProgramTraceListenerAware { return data; } - public void write(int address, byte[] data) { - extend(address, data.length); + + public void write(int address, byte[] data, boolean limited) { + + if (!limited) + extend(address, data.length); int chunkIndex = address / CHUNK_SIZE; int chunkOffset = address % CHUNK_SIZE; - int toCapture = data.length; + int toCapture = 0; + if (limited) + toCapture = (address + data.length > softSize) ? softSize - address : data.length; + else + toCapture = data.length; + int start = 0; while (toCapture > 0) { @@ -73,18 +81,18 @@ public class Memory implements ProgramTraceListenerAware { toCapture -= captured; start += captured; } - + if (traceListener != null) traceListener.onMemoryWrite(address, data); } public void extendAndWrite(int address, int allocSize, byte[] data) { extend(address, allocSize); - write(address, data); + write(address, data, false); } public void extend(int address, int size) { if (size <= 0) return; - + final int newSize = address + size; int toAllocate = newSize - internalSize(); @@ -96,7 +104,7 @@ public class Memory implements ProgramTraceListenerAware { if (toAllocate > 0) { toAllocate = (int) ceil((double) toAllocate / WORD_SIZE) * WORD_SIZE; softSize += toAllocate; - + if (traceListener != null) traceListener.onMemoryExtend(toAllocate); } } diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/Program.java b/ethereumj-core/src/main/java/org/ethereum/vm/Program.java index 9ea6268a..ee7bffee 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/Program.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/Program.java @@ -215,11 +215,15 @@ public class Program { } public void memorySave(DataWord addrB, DataWord value) { - memory.write(addrB.intValue(), value.getData()); + memory.write(addrB.intValue(), value.getData(), false); + } + + public void memorySaveLimited(int addr, byte[] value) { + memory.write(addr, value, true); } public void memorySave(int addr, byte[] value) { - memory.write(addr, value); + memory.write(addr, value, false); } public void memoryExpand(DataWord outDataOffs, DataWord outDataSize) { @@ -241,6 +245,8 @@ public class Program { memory.extendAndWrite(addr, allocSize, value); } + + public DataWord memoryLoad(DataWord addr) { return memory.readWord(addr.intValue()); } @@ -493,15 +499,8 @@ public class Program { // 3. APPLY RESULTS: result.getHReturn() into out_memory allocated if (result != null) { byte[] buffer = result.getHReturn(); - int allocSize = msg.getOutDataSize().intValue(); - if (buffer != null && allocSize > 0) { - int retSize = buffer.length; - int offset = msg.getOutDataOffs().intValue(); - if (retSize > allocSize) - this.memorySave(offset, buffer); - else - this.memorySave(offset, allocSize, buffer); - } + int offset = msg.getOutDataOffs().intValue(); + this.memorySaveLimited(offset, buffer); } // 4. THE FLAG OF SUCCESS IS ONE PUSHED INTO THE STACK @@ -1024,7 +1023,7 @@ public class Program { * used mostly for testing reasons */ public void initMem(byte[] data) { - this.memory.write(0, data); + this.memory.write(0, data, false); } diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/VM.java b/ethereumj-core/src/main/java/org/ethereum/vm/VM.java index a0c6a886..974e1790 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/VM.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/VM.java @@ -914,7 +914,7 @@ public class VM { DataWord value = program.stackPop(); if (logger.isInfoEnabled()) - hint = "addr: " + addr + " value: " + value; + hint = "[" + program.programAddress.toPrefixString() + "] key: " + addr + " value: " + value; program.storageSave(addr, value); program.step(); diff --git a/ethereumj-core/src/main/resources/META-INF/native/linux32/libleveldbjni.so b/ethereumj-core/src/main/resources/META-INF/native/linux32/libleveldbjni.so new file mode 100644 index 00000000..9000896e Binary files /dev/null and b/ethereumj-core/src/main/resources/META-INF/native/linux32/libleveldbjni.so differ diff --git a/ethereumj-core/src/main/resources/META-INF/native/linux64/libleveldbjni.so b/ethereumj-core/src/main/resources/META-INF/native/linux64/libleveldbjni.so new file mode 100644 index 00000000..91b1a492 Binary files /dev/null and b/ethereumj-core/src/main/resources/META-INF/native/linux64/libleveldbjni.so differ diff --git a/ethereumj-core/src/main/resources/META-INF/native/osx/leveldbjni.jnilib b/ethereumj-core/src/main/resources/META-INF/native/osx/leveldbjni.jnilib new file mode 100644 index 00000000..e67c7a22 Binary files /dev/null and b/ethereumj-core/src/main/resources/META-INF/native/osx/leveldbjni.jnilib differ diff --git a/ethereumj-core/src/main/resources/META-INF/native/windows32/leveldbjni.dll b/ethereumj-core/src/main/resources/META-INF/native/windows32/leveldbjni.dll new file mode 100644 index 00000000..88a8e5b9 Binary files /dev/null and b/ethereumj-core/src/main/resources/META-INF/native/windows32/leveldbjni.dll differ diff --git a/ethereumj-core/src/main/resources/META-INF/native/windows64/leveldbjni.dll b/ethereumj-core/src/main/resources/META-INF/native/windows64/leveldbjni.dll new file mode 100644 index 00000000..8dd9fb8e Binary files /dev/null and b/ethereumj-core/src/main/resources/META-INF/native/windows64/leveldbjni.dll differ diff --git a/ethereumj-core/src/test/java/org/ethereum/blockstore/InMemoryBlockStoreTest.java b/ethereumj-core/src/test/java/org/ethereum/blockstore/InMemoryBlockStoreTest.java deleted file mode 100644 index 4a13e5a3..00000000 --- a/ethereumj-core/src/test/java/org/ethereum/blockstore/InMemoryBlockStoreTest.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.ethereum.blockstore; - -import org.ethereum.core.Block; -import org.ethereum.db.InMemoryBlockStore; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.spongycastle.util.encoders.Hex; - -import java.io.File; -import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.List; - -import static org.junit.Assert.*; - -/** - * @author: Roman Mandeleil - * Created on: 30/01/2015 11:04 - */ -@Ignore -public class InMemoryBlockStoreTest { - - private static final Logger logger = LoggerFactory.getLogger("test"); - private InMemoryBlockStore blockStore; - - @Before - public void setup() throws URISyntaxException, IOException { - - blockStore = new InMemoryBlockStore(); - URL scenario1 = ClassLoader - .getSystemResource("blockstore/load.dmp"); - - File file = new File(scenario1.toURI()); - List strData = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8); - - for (String blockRLP : strData) { - Block block = new Block( - Hex.decode(blockRLP)); - logger.trace("adding block.hash: [{}] block.number: [{}]", - block.getShortHash(), - block.getNumber()); - blockStore.saveBlock(block, null); - } - } - - @Test - public void testSaving8003Blocks() { - - Block bestBlock = blockStore.getBestBlock(); - Long bestIndex = blockStore.getBestBlock().getNumber(); - Long firstIndex = bestIndex - 1000;//InMemoryBlockStore.MAX_BLOCKS; - - assertTrue(bestIndex == 8003); - assertTrue(firstIndex == 7003); - - assertTrue(blockStore.getBlockByNumber(7000) == null); - assertTrue(blockStore.getBlockByNumber(8004) == null); - - Block byHashBlock = blockStore.getBlockByHash(bestBlock.getHash()); - assertTrue(bestBlock.getNumber() == byHashBlock.getNumber()); - - byte[] hashFor7500 = blockStore.getBlockByNumber(7500).getHash(); - Block block7500 = blockStore.getBlockByHash(hashFor7500); - assertTrue(block7500.getNumber() == 7500); - } - - @Test - public void testListOfHashes(){ - - Block block = blockStore.getBlockByNumber(7500); - byte[] hash = block.getHash(); - - List hashes = blockStore.getListOfHashesStartFrom(hash, 700); - - byte[] lastHash = hashes.get(hashes.size() - 1); - - assertEquals(Hex.toHexString(blockStore.getBestBlock().getHash()), - Hex.toHexString(lastHash)); - - assertTrue(hashes.size() == 504); - } - -} diff --git a/ethereumj-core/src/test/java/org/ethereum/db/InMemoryBlockStoreTest.java b/ethereumj-core/src/test/java/org/ethereum/db/InMemoryBlockStoreTest.java index 676758b8..ddb66ded 100644 --- a/ethereumj-core/src/test/java/org/ethereum/db/InMemoryBlockStoreTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/db/InMemoryBlockStoreTest.java @@ -29,7 +29,7 @@ import static org.junit.Assert.*; * @author: Roman Mandeleil * Created on: 30/01/2015 11:04 */ - +@Ignore public class InMemoryBlockStoreTest extends AbstractInMemoryBlockStoreTest { private static final Logger logger = LoggerFactory.getLogger("test"); @@ -64,7 +64,6 @@ public class InMemoryBlockStoreTest extends AbstractInMemoryBlockStoreTest { logger.info("total difficulty: {}", cumDifficulty); } - @Ignore @Test public void testEmpty(){ BlockStore blockStore = new InMemoryBlockStore(); @@ -72,7 +71,6 @@ public class InMemoryBlockStoreTest extends AbstractInMemoryBlockStoreTest { assertNull(blockStore.getBestBlock()); } - @Ignore @Test public void testFlush(){ BlockStore blockStore = new InMemoryBlockStore(); @@ -85,7 +83,6 @@ public class InMemoryBlockStoreTest extends AbstractInMemoryBlockStoreTest { blockStore.flush(); } - @Ignore @Test public void testSimpleLoad(){ @@ -107,7 +104,6 @@ public class InMemoryBlockStoreTest extends AbstractInMemoryBlockStoreTest { assertTrue(blockStore.getBestBlock().getNumber() == 8003); } - @Ignore @Test public void testFlushEach1000(){ @@ -125,7 +121,7 @@ public class InMemoryBlockStoreTest extends AbstractInMemoryBlockStoreTest { } } - @Ignore + @Test public void testBlockHashByNumber(){ @@ -158,7 +154,6 @@ public class InMemoryBlockStoreTest extends AbstractInMemoryBlockStoreTest { assertTrue(hash.startsWith("820aa7")); } - @Ignore @Test public void testBlockByNumber(){ @@ -191,7 +186,7 @@ public class InMemoryBlockStoreTest extends AbstractInMemoryBlockStoreTest { assertTrue(hash.startsWith("820aa7")); } - @Ignore + @Test public void testGetBlockByNumber() { @@ -209,7 +204,7 @@ public class InMemoryBlockStoreTest extends AbstractInMemoryBlockStoreTest { assertEquals("4312750101", blockStore.getTotalDifficulty().toString()); } - @Ignore + @Test public void testDbGetBlockByHash(){ diff --git a/ethereumj-core/src/test/java/org/ethereum/net/rlpx/KademliaTest.java b/ethereumj-core/src/test/java/org/ethereum/net/rlpx/KademliaTest.java new file mode 100644 index 00000000..5021d253 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/net/rlpx/KademliaTest.java @@ -0,0 +1,119 @@ +package org.ethereum.net.rlpx; + +import org.ethereum.net.rlpx.discover.table.KademliaOptions; +import org.ethereum.net.rlpx.discover.table.NodeBucket; +import org.ethereum.net.rlpx.discover.table.NodeEntry; +import org.ethereum.net.rlpx.discover.table.NodeTable; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.List; +import java.util.Random; + +import static org.junit.Assert.*; + +public class KademliaTest { + + @Ignore + @Test + public void test1() { + //init table with one home node + NodeTable t = getTestNodeTable(0); + Node homeNode = t.getNode(); + + //table should contain the home node only + assertEquals(t.getAllNodes().size(), 1); + + Node bucketNode = t.getAllNodes().get(0).getNode(); + + assertEquals(homeNode, bucketNode); + + } + + @Test + public void test2() { + NodeTable t = getTestNodeTable(0); + Node n = getNode(); + t.addNode(n); + + assertTrue(containsNode(t, n)); + + t.dropNode(n); + + assertFalse(containsNode(t, n)); + } + + @Test + public void test3() { + NodeTable t = getTestNodeTable(1000); + showBuckets(t); + + List closest1 = t.getClosestNodes(t.getNode().getId()); + List closest2 = t.getClosestNodes(getNodeId()); + + assertNotEquals(closest1, closest2); + } + + @Test + public void test4() { + NodeTable t = getTestNodeTable(0); + Node homeNode = t.getNode(); + + //t.getBucketsCount() returns non empty buckets + assertEquals(t.getBucketsCount(), 1); + + //creates very close nodes + for (int i = 1; i < KademliaOptions.BUCKET_SIZE; i++) { + Node n= getNode(homeNode.getId(), i); + t.addNode(n); + } + + assertEquals(t.getBucketsCount(), 1); + assertEquals(t.getBuckets()[0].getNodesCount(), KademliaOptions.BUCKET_SIZE); + } + + public static byte[] getNodeId() { + Random gen = new Random(); + byte[] id = new byte[64]; + gen.nextBytes(id); + return id; + } + + public static Node getNode(byte[] id, int i) { + id[0] += (byte) i; + Node n = getNode(); + n.setId(id); + return n; + } + + public static Node getNode() { + return new Node(getNodeId(), "127.0.0.1", 30303); + } + + public static NodeTable getTestNodeTable(int nodesQuantity) { + NodeTable testTable = new NodeTable(getNode()); + + for (int i = 0; i < nodesQuantity; i++) { + testTable.addNode(getNode()); + } + + return testTable; + } + + public static void showBuckets(NodeTable t) { + for (NodeBucket b : t.getBuckets()) { + if (b.getNodesCount() > 0) { + System.out.println(String.format("Bucket %d nodes %d depth %d", b.getDepth(), b.getNodesCount(), b.getDepth())); + } + } + } + + public static boolean containsNode(NodeTable t, Node n) { + for (NodeEntry e : t.getAllNodes()) { + if (e.getNode().toString().equals(n.toString())) { + return true; + } + } + return false; + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/net/rlpx/RLPXTest.java b/ethereumj-core/src/test/java/org/ethereum/net/rlpx/RLPXTest.java index 9f7b348c..01acfcdf 100644 --- a/ethereumj-core/src/test/java/org/ethereum/net/rlpx/RLPXTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/net/rlpx/RLPXTest.java @@ -1,9 +1,6 @@ package org.ethereum.net.rlpx; import org.ethereum.crypto.ECKey; -import org.ethereum.net.rlpx.*; -import org.ethereum.util.RLP; -import org.ethereum.util.RLPList; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; @@ -15,7 +12,6 @@ import java.nio.charset.Charset; import java.util.Arrays; import java.util.List; -import org.junit.Ignore; import static org.ethereum.crypto.HashUtil.sha3; import static org.ethereum.util.ByteUtil.merge; import static org.junit.Assert.assertEquals; @@ -25,6 +21,7 @@ public class RLPXTest { private static final org.slf4j.Logger logger = LoggerFactory.getLogger("test"); + @Ignore @Test // ping test public void test1() { @@ -65,6 +62,7 @@ public class RLPXTest { assertEquals(key.toString(), key2.toString()); } + @Ignore @Test // neighbors message public void test3() { @@ -133,6 +131,7 @@ public class RLPXTest { } + @Ignore @Test public void test6() { @@ -153,7 +152,7 @@ public class RLPXTest { } - + @Ignore @Test // Neighbors parse data public void test7() { diff --git a/ethereumj-core/src/test/java/org/ethereum/net/shh/ShhTest.java b/ethereumj-core/src/test/java/org/ethereum/net/shh/ShhTest.java new file mode 100644 index 00000000..774d3499 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/net/shh/ShhTest.java @@ -0,0 +1,103 @@ +package org.ethereum.net.shh; + +import org.ethereum.crypto.ECKey; +import org.ethereum.util.RLP; +import org.ethereum.util.RLPList; +import org.junit.Before; +import org.junit.Test; + +import java.math.BigInteger; +import org.spongycastle.util.encoders.Hex; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +/** + * Created by kest on 6/13/15. + */ +public class ShhTest { + + private byte[] payload = "Hello whisper!".getBytes(); + private ECKey privKey = ECKey.fromPrivate(BigInteger.TEN); + private byte[] pubKey = privKey.decompress().getPubKey(); + private long ttl = 10000; + private Topic[] topics = new Topic[]{ + new Topic("topic 1"), + new Topic("topic 2"), + new Topic("topic 3")}; + + + @Test /* Tests whether a message can be wrapped without any identity or encryption. */ + public void test1() { + Message sent = new Message(payload); + Options options = new Options(null, null, topics, ttl); + Envelope e = sent.wrap(0, options); + + RLPList rlpList = RLP.decode2(e.getEncoded()); + RLPList.recursivePrint(rlpList); + System.out.println(); + + assertEquals(Hex.toHexString(e.getData()), Hex.toHexString(sent.getBytes())); + assertEquals(Hex.toHexString(sent.getPayload()), Hex.toHexString(payload)); + assertTrue(sent.getSignature() == null); + + Message received = e.open(null); + + ECKey recovered = received.recover(); + assertTrue(recovered == null); + } + + @Test /* Tests whether a message can be signed, and wrapped in plain-text. */ + public void test2() { + Message sent = new Message(payload); + Options options = new Options(privKey, null, topics, ttl); + Envelope e = sent.wrap(0, options); + + assertEquals(Hex.toHexString(e.getData()), Hex.toHexString(sent.getBytes())); + assertEquals(Hex.toHexString(sent.getPayload()), Hex.toHexString(payload)); + assertTrue(sent.getSignature() != null); + + Message received = e.open(null); + ECKey recovered = received.recover(); + + assertEquals(Hex.toHexString(pubKey), Hex.toHexString(recovered.decompress().getPubKey())); + } + + @Test /* Tests whether a message can be encrypted and decrypted using an anonymous sender (i.e. no signature).*/ + public void test3() { + Message sent = new Message(payload); + Options options = new Options(null, pubKey, topics, ttl); + Envelope e = sent.wrap(0, options); + + assertEquals(Hex.toHexString(e.getData()), Hex.toHexString(sent.getBytes())); + assertNotEquals(Hex.toHexString(sent.getPayload()), Hex.toHexString(payload)); + assertTrue(sent.getSignature() == null); + + Message received = e.open(null); + + assertEquals(Hex.toHexString(sent.getBytes()), Hex.toHexString(received.getBytes())); + + ECKey recovered = received.recover(); + assertTrue(recovered == null); + } + + @Test /* Tests whether a message can be properly signed and encrypted. */ + public void test4() { + Message sent = new Message(payload); + Options options = new Options(privKey, pubKey, topics, ttl); + Envelope e = sent.wrap(0, options); + + assertEquals(Hex.toHexString(e.getData()), Hex.toHexString(sent.getBytes())); + assertNotEquals(Hex.toHexString(sent.getPayload()), Hex.toHexString(payload)); + assertTrue(sent.getSignature() != null); + + Message received = e.open(privKey); + ECKey recovered = received.recover(); + sent.decrypt(privKey); + + assertEquals(Hex.toHexString(sent.getBytes()), Hex.toHexString(received.getBytes())); + assertEquals(Hex.toHexString(sent.getPayload()), Hex.toHexString(payload)); + assertEquals(Hex.toHexString(pubKey), Hex.toHexString(recovered.decompress().getPubKey())); + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/util/RLPTest.java b/ethereumj-core/src/test/java/org/ethereum/util/RLPTest.java index 1d72daf9..18131731 100644 --- a/ethereumj-core/src/test/java/org/ethereum/util/RLPTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/util/RLPTest.java @@ -128,7 +128,7 @@ public class RLPTest { data = RLP.encodeByte((byte) 120); assertArrayEquals(expected2, data); - byte[] expected3 = {(byte) 0x81, (byte) 0x7F}; + byte[] expected3 = {(byte) 0x7F}; data = RLP.encodeByte((byte) 127); assertArrayEquals(expected3, data); } @@ -145,7 +145,7 @@ public class RLPTest { data = RLP.encodeShort((byte) 120); assertArrayEquals(expected2, data); - byte[] expected3 = {(byte) 0x81, (byte) 0x7F}; + byte[] expected3 = { (byte) 0x7F}; data = RLP.encodeShort((byte) 127); assertArrayEquals(expected3, data); @@ -170,7 +170,7 @@ public class RLPTest { data = RLP.encodeInt(120); assertArrayEquals(expected2, data); - byte[] expected3 = {(byte) 0x81, (byte) 0x7F}; + byte[] expected3 = {(byte) 0x7F}; data = RLP.encodeInt(127); assertArrayEquals(expected3, data); @@ -1029,6 +1029,21 @@ public class RLPTest { assertEquals("c0", Hex.toHexString(setEncoded)); } + @Test + public void testEncodeInt_7f(){ + String result = Hex.toHexString(RLP.encodeInt(0x7f)); + String expected = "7f"; + assertEquals(expected, result); + } + + @Test + public void testEncodeInt_80(){ + String result = Hex.toHexString(RLP.encodeInt(0x80)); + String expected = "8180"; + assertEquals(expected, result); + } + + } \ No newline at end of file diff --git a/ethereumj-core/src/test/java/org/ethereum/vm/MemoryTest.java b/ethereumj-core/src/test/java/org/ethereum/vm/MemoryTest.java index b7cf0a07..90cdc9c4 100644 --- a/ethereumj-core/src/test/java/org/ethereum/vm/MemoryTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/vm/MemoryTest.java @@ -9,6 +9,7 @@ import java.util.Arrays; import static java.lang.Math.ceil; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class MemoryTest { @@ -44,18 +45,18 @@ public class MemoryTest { Memory memoryBuffer = new Memory(); byte[] data = {1, 1, 1, 1}; - memoryBuffer.write(0, data); + memoryBuffer.write(0, data, false); - Assert.assertTrue(1 == memoryBuffer.getChunks().size()); + assertTrue(1 == memoryBuffer.getChunks().size()); byte[] chunk = memoryBuffer.getChunks().get(0); - Assert.assertTrue(chunk[0] == 1); - Assert.assertTrue(chunk[1] == 1); - Assert.assertTrue(chunk[2] == 1); - Assert.assertTrue(chunk[3] == 1); - Assert.assertTrue(chunk[4] == 0); + assertTrue(chunk[0] == 1); + assertTrue(chunk[1] == 1); + assertTrue(chunk[2] == 1); + assertTrue(chunk[3] == 1); + assertTrue(chunk[4] == 0); - Assert.assertTrue(memoryBuffer.size() == 32); + assertTrue(memoryBuffer.size() == 32); } @Test @@ -64,19 +65,19 @@ public class MemoryTest { Memory memoryBuffer = new Memory(); byte[] data = Hex.decode("0101010101010101010101010101010101010101010101010101010101010101"); - memoryBuffer.write(0, data); + memoryBuffer.write(0, data, false); - Assert.assertTrue(1 == memoryBuffer.getChunks().size()); + assertTrue(1 == memoryBuffer.getChunks().size()); byte[] chunk = memoryBuffer.getChunks().get(0); - Assert.assertTrue(chunk[0] == 1); - Assert.assertTrue(chunk[1] == 1); + assertTrue(chunk[0] == 1); + assertTrue(chunk[1] == 1); - Assert.assertTrue(chunk[30] == 1); - Assert.assertTrue(chunk[31] == 1); - Assert.assertTrue(chunk[32] == 0); + assertTrue(chunk[30] == 1); + assertTrue(chunk[31] == 1); + assertTrue(chunk[32] == 0); - Assert.assertTrue(memoryBuffer.size() == 32); + assertTrue(memoryBuffer.size() == 32); } @Test @@ -85,20 +86,20 @@ public class MemoryTest { Memory memoryBuffer = new Memory(); byte[] data = Hex.decode("010101010101010101010101010101010101010101010101010101010101010101"); - memoryBuffer.write(0, data); + memoryBuffer.write(0, data, false); - Assert.assertTrue(1 == memoryBuffer.getChunks().size()); + assertTrue(1 == memoryBuffer.getChunks().size()); byte[] chunk = memoryBuffer.getChunks().get(0); - Assert.assertTrue(chunk[0] == 1); - Assert.assertTrue(chunk[1] == 1); + assertTrue(chunk[0] == 1); + assertTrue(chunk[1] == 1); - Assert.assertTrue(chunk[30] == 1); - Assert.assertTrue(chunk[31] == 1); - Assert.assertTrue(chunk[32] == 1); - Assert.assertTrue(chunk[33] == 0); + assertTrue(chunk[30] == 1); + assertTrue(chunk[31] == 1); + assertTrue(chunk[32] == 1); + assertTrue(chunk[33] == 0); - Assert.assertTrue(memoryBuffer.size() == 64); + assertTrue(memoryBuffer.size() == 64); } @Test @@ -108,18 +109,18 @@ public class MemoryTest { byte[] data = new byte[1024]; Arrays.fill(data, (byte) 1); - memoryBuffer.write(0, data); + memoryBuffer.write(0, data, false); - Assert.assertTrue(1 == memoryBuffer.getChunks().size()); + assertTrue(1 == memoryBuffer.getChunks().size()); byte[] chunk = memoryBuffer.getChunks().get(0); - Assert.assertTrue(chunk[0] == 1); - Assert.assertTrue(chunk[1] == 1); + assertTrue(chunk[0] == 1); + assertTrue(chunk[1] == 1); - Assert.assertTrue(chunk[1022] == 1); - Assert.assertTrue(chunk[1023] == 1); + assertTrue(chunk[1022] == 1); + assertTrue(chunk[1023] == 1); - Assert.assertTrue(memoryBuffer.size() == 1024); + assertTrue(memoryBuffer.size() == 1024); } @Test @@ -130,22 +131,22 @@ public class MemoryTest { byte[] data = new byte[1025]; Arrays.fill(data, (byte) 1); - memoryBuffer.write(0, data); + memoryBuffer.write(0, data, false); - Assert.assertTrue(2 == memoryBuffer.getChunks().size()); + assertTrue(2 == memoryBuffer.getChunks().size()); byte[] chunk1 = memoryBuffer.getChunks().get(0); - Assert.assertTrue(chunk1[0] == 1); - Assert.assertTrue(chunk1[1] == 1); + assertTrue(chunk1[0] == 1); + assertTrue(chunk1[1] == 1); - Assert.assertTrue(chunk1[1022] == 1); - Assert.assertTrue(chunk1[1023] == 1); + assertTrue(chunk1[1022] == 1); + assertTrue(chunk1[1023] == 1); byte[] chunk2 = memoryBuffer.getChunks().get(1); - Assert.assertTrue(chunk2[0] == 1); - Assert.assertTrue(chunk2[1] == 0); + assertTrue(chunk2[0] == 1); + assertTrue(chunk2[1] == 0); - Assert.assertTrue(memoryBuffer.size() == 1056); + assertTrue(memoryBuffer.size() == 1056); } @Test @@ -159,26 +160,26 @@ public class MemoryTest { byte[] data2 = new byte[1024]; Arrays.fill(data2, (byte) 2); - memoryBuffer.write(0, data1); - memoryBuffer.write(1024, data2); + memoryBuffer.write(0, data1, false); + memoryBuffer.write(1024, data2, false); - Assert.assertTrue(2 == memoryBuffer.getChunks().size()); + assertTrue(2 == memoryBuffer.getChunks().size()); byte[] chunk1 = memoryBuffer.getChunks().get(0); - Assert.assertTrue(chunk1[0] == 1); - Assert.assertTrue(chunk1[1] == 1); + assertTrue(chunk1[0] == 1); + assertTrue(chunk1[1] == 1); - Assert.assertTrue(chunk1[1022] == 1); - Assert.assertTrue(chunk1[1023] == 1); + assertTrue(chunk1[1022] == 1); + assertTrue(chunk1[1023] == 1); byte[] chunk2 = memoryBuffer.getChunks().get(1); - Assert.assertTrue(chunk2[0] == 2); - Assert.assertTrue(chunk2[1] == 2); + assertTrue(chunk2[0] == 2); + assertTrue(chunk2[1] == 2); - Assert.assertTrue(chunk2[1022] == 2); - Assert.assertTrue(chunk2[1023] == 2); + assertTrue(chunk2[1022] == 2); + assertTrue(chunk2[1023] == 2); - Assert.assertTrue(memoryBuffer.size() == 2048); + assertTrue(memoryBuffer.size() == 2048); } @Test @@ -195,30 +196,30 @@ public class MemoryTest { byte[] data3 = new byte[1]; Arrays.fill(data3, (byte) 3); - memoryBuffer.write(0, data1); - memoryBuffer.write(1024, data2); - memoryBuffer.write(2048, data3); + memoryBuffer.write(0, data1, false); + memoryBuffer.write(1024, data2, false); + memoryBuffer.write(2048, data3, false); - Assert.assertTrue(3 == memoryBuffer.getChunks().size()); + assertTrue(3 == memoryBuffer.getChunks().size()); byte[] chunk1 = memoryBuffer.getChunks().get(0); - Assert.assertTrue(chunk1[0] == 1); - Assert.assertTrue(chunk1[1] == 1); + assertTrue(chunk1[0] == 1); + assertTrue(chunk1[1] == 1); - Assert.assertTrue(chunk1[1022] == 1); - Assert.assertTrue(chunk1[1023] == 1); + assertTrue(chunk1[1022] == 1); + assertTrue(chunk1[1023] == 1); byte[] chunk2 = memoryBuffer.getChunks().get(1); - Assert.assertTrue(chunk2[0] == 2); - Assert.assertTrue(chunk2[1] == 2); + assertTrue(chunk2[0] == 2); + assertTrue(chunk2[1] == 2); - Assert.assertTrue(chunk2[1022] == 2); - Assert.assertTrue(chunk2[1023] == 2); + assertTrue(chunk2[1022] == 2); + assertTrue(chunk2[1023] == 2); byte[] chunk3 = memoryBuffer.getChunks().get(2); - Assert.assertTrue(chunk3[0] == 3); + assertTrue(chunk3[0] == 3); - Assert.assertTrue(memoryBuffer.size() == 2080); + assertTrue(memoryBuffer.size() == 2080); } @Test @@ -237,8 +238,8 @@ public class MemoryTest { if (memoryBuffer.readByte(i) == 0) ++zeroes; } - Assert.assertTrue(ones == zeroes); - Assert.assertTrue(256 == memoryBuffer.size()); + assertTrue(ones == zeroes); + assertTrue(256 == memoryBuffer.size()); } @@ -248,9 +249,9 @@ public class MemoryTest { Memory memoryBuffer = new Memory(); DataWord value = memoryBuffer.readWord(100); - Assert.assertTrue(value.intValue() == 0); - Assert.assertTrue(memoryBuffer.getChunks().size() == 1); - Assert.assertTrue(memoryBuffer.size() == 32 * 5); + assertTrue(value.intValue() == 0); + assertTrue(memoryBuffer.getChunks().size() == 1); + assertTrue(memoryBuffer.size() == 32 * 5); } @Test @@ -258,9 +259,9 @@ public class MemoryTest { Memory memoryBuffer = new Memory(); DataWord value = memoryBuffer.readWord(2015); - Assert.assertTrue(value.intValue() == 0); - Assert.assertTrue(memoryBuffer.getChunks().size() == 2); - Assert.assertTrue(memoryBuffer.size() == 2048); + assertTrue(value.intValue() == 0); + assertTrue(memoryBuffer.getChunks().size() == 2); + assertTrue(memoryBuffer.size() == 2048); } @Test @@ -268,9 +269,9 @@ public class MemoryTest { Memory memoryBuffer = new Memory(); DataWord value = memoryBuffer.readWord(2016); - Assert.assertTrue(value.intValue() == 0); - Assert.assertTrue(memoryBuffer.getChunks().size() == 2); - Assert.assertTrue(memoryBuffer.size() == 2048); + assertTrue(value.intValue() == 0); + assertTrue(memoryBuffer.getChunks().size() == 2); + assertTrue(memoryBuffer.size() == 2048); } @Test @@ -278,9 +279,9 @@ public class MemoryTest { Memory memoryBuffer = new Memory(); DataWord value = memoryBuffer.readWord(2017); - Assert.assertTrue(value.intValue() == 0); - Assert.assertTrue(memoryBuffer.getChunks().size() == 3); - Assert.assertTrue(memoryBuffer.size() == 2080); + assertTrue(value.intValue() == 0); + assertTrue(memoryBuffer.getChunks().size() == 3); + assertTrue(memoryBuffer.size() == 2080); } @Test @@ -294,11 +295,11 @@ public class MemoryTest { byte[] data2 = new byte[1024]; Arrays.fill(data2, (byte) 2); - memoryBuffer.write(0, data1); - memoryBuffer.write(1024, data2); + memoryBuffer.write(0, data1, false); + memoryBuffer.write(1024, data2, false); - Assert.assertTrue(memoryBuffer.getChunks().size() == 2); - Assert.assertTrue(memoryBuffer.size() == 2048); + assertTrue(memoryBuffer.getChunks().size() == 2); + assertTrue(memoryBuffer.size() == 2048); DataWord val1 = memoryBuffer.readWord(0x3df); DataWord val2 = memoryBuffer.readWord(0x3e0); @@ -315,7 +316,7 @@ public class MemoryTest { assertArrayEquals( Hex.decode("0101010101010101010101010101010101010101010101010101010101010102"), val3.getData()); - Assert.assertTrue(memoryBuffer.size() == 2048); + assertTrue(memoryBuffer.size() == 2048); } @@ -329,8 +330,8 @@ public class MemoryTest { byte[] data2 = new byte[32]; Arrays.fill(data2, (byte) 2); - memoryBuffer.write(0, data1); - memoryBuffer.write(32, data2); + memoryBuffer.write(0, data1, false); + memoryBuffer.write(32, data2, false); byte[] data = memoryBuffer.read(0, 64); @@ -351,8 +352,8 @@ public class MemoryTest { byte[] data1 = new byte[32]; Arrays.fill(data1, (byte) 1); - memoryBuffer.write(0, data1); - Assert.assertTrue(32 == memoryBuffer.size()); + memoryBuffer.write(0, data1, false); + assertTrue(32 == memoryBuffer.size()); byte[] data = memoryBuffer.read(0, 64); @@ -376,8 +377,8 @@ public class MemoryTest { byte[] data2 = new byte[1024]; Arrays.fill(data2, (byte) 2); - memoryBuffer.write(0, data1); - memoryBuffer.write(1024, data2); + memoryBuffer.write(0, data1, false); + memoryBuffer.write(1024, data2, false); byte[] data = memoryBuffer.read(0, 2048); @@ -387,8 +388,8 @@ public class MemoryTest { if (data[i] == 2) ++twos; } - Assert.assertTrue(ones == twos); - Assert.assertTrue(2048 == memoryBuffer.size()); + assertTrue(ones == twos); + assertTrue(2048 == memoryBuffer.size()); } @Test @@ -402,8 +403,8 @@ public class MemoryTest { byte[] data2 = new byte[1024]; Arrays.fill(data2, (byte) 2); - memoryBuffer.write(0, data1); - memoryBuffer.write(1024, data2); + memoryBuffer.write(0, data1, false); + memoryBuffer.write(1024, data2, false); byte[] data = memoryBuffer.read(0, 2049); @@ -414,11 +415,40 @@ public class MemoryTest { if (data[i] == 0) ++zero; } - Assert.assertTrue(zero == 1); - Assert.assertTrue(ones == twos); - Assert.assertTrue(2080 == memoryBuffer.size()); + assertTrue(zero == 1); + assertTrue(ones == twos); + assertTrue(2080 == memoryBuffer.size()); } + @Test + public void memoryWriteLimited_1(){ + + Memory memoryBuffer = new Memory(); + memoryBuffer.extend(0, 3072); + + byte[] data1 = new byte[6272]; + Arrays.fill(data1, (byte) 1); + + memoryBuffer.write(2720, data1, true); + + byte lastZero = memoryBuffer.readByte(2719); + byte firstOne = memoryBuffer.readByte(2721); + + assertTrue(memoryBuffer.internalSize() == 3072); + assertTrue(lastZero == 0); + assertTrue(firstOne == 1); + + byte[] data = memoryBuffer.read(2720, 352); + + int ones = 0; int zero = 0; + for (int i = 0; i < data.length; ++i){ + if (data[i] == 1) ++ones; + if (data[i] == 0) ++zero; + } + + assertTrue(ones == data.length); + assertTrue(zero == 0); + } } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b1d77dbf..a1a7df3b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.3-rc-4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip