diff --git a/.gitignore b/.gitignore index 5242e9d1b0..725e440052 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ target/ # Output database* +out logs test_db* testnetSampleDb @@ -41,3 +42,6 @@ sampleDB* **/test-user.conf **/out.log **/user.log4j.properties + +# jenv +.java-version diff --git a/README.md b/README.md index f43fc55b0a..460ce4c176 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,9 @@ # About -ethereumj is a pure-Java implementation of the Ethereum protocol. For high-level information about Ethereum and its goals, visit [ethereum.org](https://ethereum.org). The [ethereum white paper](https://github.com/ethereum/wiki/wiki/White-Paper) provides a complete conceptual overview, and the [yellow paper](http://gavwood.com/Paper.pdf) provides a formal definition of the protocol. +EthereumJ is a pure-Java implementation of the Ethereum protocol. For high-level information about Ethereum and its goals, visit [ethereum.org](https://ethereum.org). The [ethereum white paper](https://github.com/ethereum/wiki/wiki/White-Paper) provides a complete conceptual overview, and the [yellow paper](http://gavwood.com/Paper.pdf) provides a formal definition of the protocol. -# Check our blog -http://ethereumj.io +We keep EthereumJ as thin as possible. For [JSON-RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) support and other client features check [Ethereum Harmony](https://github.com/ether-camp/ethereum-harmony). # Running EthereumJ @@ -20,7 +19,7 @@ http://ethereumj.io org.ethereum ethereumj-core - 1.5.0-RELEASE + 1.6.3-RELEASE ``` @@ -29,8 +28,10 @@ http://ethereumj.io ``` repositories { mavenCentral() + jcenter() + maven { url "https://dl.bintray.com/ethereum/maven/" } } - compile "org.ethereum:ethereumj-core:1.5.+" + compile "org.ethereum:ethereumj-core:1.6.+" ``` As a starting point for your own project take a look at https://github.com/ether-camp/ethereumj.starter @@ -86,6 +87,17 @@ To override needed options you may use one of the following ways: * programmatically using by overriding Spring `SystemProperties` bean Note that don’t need to put all the options to your custom config, just those you want to override. + +# Special thanks +YourKit for providing us with their nice profiler absolutely for free. + +YourKit supports open source projects with its full-featured Java Profiler. +YourKit, LLC is the creator of YourKit Java Profiler +and YourKit .NET Profiler, +innovative and intelligent tools for profiling Java and .NET applications. + +![YourKit Logo](https://www.yourkit.com/images/yklogo.png) + # Contact Chat with us via [Gitter](https://gitter.im/ethereum/ethereumj) diff --git a/build.gradle b/build.gradle index fccac1ce29..9cd22a32ad 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,4 @@ -wrapper.gradleVersion = '2.14.1' +wrapper.gradleVersion = '4.6' allprojects { apply plugin: 'eclipse' diff --git a/circle.yml b/circle.yml index f366825107..2d46999d4d 100644 --- a/circle.yml +++ b/circle.yml @@ -12,17 +12,19 @@ dependencies: - for img in `ls ~/.docker`; do docker load -i ~/.docker/$img; done # Pull in and hive, restore cached ethash DAGs and do a dry run - - go get -u github.com/ether-camp/hive - - (cd ~/.go_workspace/src/github.com/ether-camp/hive && mkdir -p workspace/ethash/ ~/.ethash) - - (cd ~/.go_workspace/src/github.com/ether-camp/hive && cp -r ~/.ethash/. workspace/ethash/) - - (cd ~/.go_workspace/src/github.com/ether-camp/hive && hive --docker-noshell --client=NONE --test=. --sim=. --loglevel=6) + - go get -u github.com/karalabe/hive + - (cd ~/.go_workspace/src/github.com/karalabe/hive && mkdir -p workspace/ethash/ ~/.ethash) + - (cd ~/.go_workspace/src/github.com/karalabe/hive && cp -r ~/.ethash/. workspace/ethash/) + - (cd ~/.go_workspace/src/github.com/karalabe/hive && hive --docker-noshell --client=NONE --test=. --sim=. --loglevel=6) + # Generating DAG + - (cd ~/ethereumj && ./gradlew run -PmainClass=org.ethereum.Start -PjvmArgs="-Dethash.dir=~/.go_workspace/src/github.com/karalabe/hive/workspace/ethash -Dethash.blockNumber=0 -Xmx2G") # timeout 20 min - - (cd ~/.go_workspace/src/github.com/ether-camp/hive && hive --docker-noshell --client=ethereumj:local --test=NONE --sim=NONE --loglevel=6): + - (cd ~/.go_workspace/src/github.com/karalabe/hive && hive --docker-noshell --client=ethereumj:local --test=NONE --sim=NONE --loglevel=6): timeout: 1200 # Cache all the docker images and the ethash DAGs - for img in `docker images | grep -v "^" | tail -n +2 | awk '{print $1}'`; do docker save $img > ~/.docker/`echo $img | tr '/' ':'`.tar; done - - cp -r ~/.go_workspace/src/github.com/ether-camp/hive/workspace/ethash/. ~/.ethash + - cp -r ~/.go_workspace/src/github.com/karalabe/hive/workspace/ethash/. ~/.ethash # Cache gradle artifacts - (cd ~/ethereumj && ./gradlew compileJava) - (cd ~ && git clone --depth 1 -b develop https://github.com/ether-camp/ethereum-harmony.git) @@ -31,12 +33,12 @@ dependencies: test: override: # Build app and move into a known folder - - (cd ~/ethereumj && ./gradlew install -x test) - - (cd ~/ethereum-harmony && ./gradlew stage -x test -PuseMavenLocal) - - cp ~/ethereum-harmony/build/distributions/harmony.ether.camp.tar ~/harmony.ether.camp.tar + - (cd ~/.go_workspace/src/github.com/karalabe/hive/clients/ethereumj\:local && ./build.sh ~/ethereumj ~/ethereum-harmony ~) # Run hive and move all generated logs into the public artifacts folder - - (cd ~/.go_workspace/src/github.com/ether-camp/hive && hive --docker-noshell=true --client=ethereumj:local --override=$HOME/harmony.ether.camp.tar --test=. --sim=dao-hard-fork --loglevel=3): + - (cd ~/.go_workspace/src/github.com/karalabe/hive && hive --docker-noshell --client=ethereumj:local --override=$HOME/harmony.ether.camp.tar --smoke --loglevel=3): timeout: 4000 - - cp -r ~/.go_workspace/src/github.com/ether-camp/hive/workspace/logs/* $CIRCLE_ARTIFACTS + - (cd ~/.go_workspace/src/github.com/karalabe/hive && hive --docker-noshell --client=ethereumj:local --override=$HOME/harmony.ether.camp.tar --test=. --loglevel=3): + timeout: 4000 + - cp -r ~/.go_workspace/src/github.com/karalabe/hive/workspace/logs/* $CIRCLE_ARTIFACTS diff --git a/ethereumj-core/build.gradle b/ethereumj-core/build.gradle index 37a4867c76..f04354bc42 100644 --- a/ethereumj-core/build.gradle +++ b/ethereumj-core/build.gradle @@ -3,10 +3,10 @@ import java.text.SimpleDateFormat buildscript { repositories { jcenter() - maven { url 'http://repo.spring.io/plugins-release-local' } + maven { url 'http://repo.spring.io/plugins-release' } } dependencies { - classpath 'org.springframework.build.gradle:propdeps-plugin:0.0.7' + classpath 'io.spring.gradle:propdeps-plugin:0.0.9.RELEASE' classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:3.0.1' } } @@ -15,7 +15,6 @@ buildscript { plugins { id 'application' id 'jacoco' - id 'com.github.johnrengelman.shadow' version '1.2.3' id 'com.github.kt3k.coveralls' version '2.6.3' id 'com.jfrog.bintray' version '1.0' } @@ -23,10 +22,10 @@ plugins { apply plugin: 'propdeps-maven' apply plugin: 'com.jfrog.artifactory-upload' -sourceCompatibility = 1.7 +sourceCompatibility = 1.8 mainClassName = 'org.ethereum.Start' -applicationDefaultJvmArgs = ["-server", "-Xss4m", "-Xmx2G", "-XX:-OmitStackTraceInFastThrow"] +applicationDefaultJvmArgs = ["-server", "-Xss2m", "-Xmx2G", "-XX:-OmitStackTraceInFastThrow"] if ( project.hasProperty('jvmArgs') ) { applicationDefaultJvmArgs = applicationDefaultJvmArgs + project.jvmArgs.split('\\s+').toList() @@ -45,12 +44,6 @@ tasks.withType(JavaCompile){ options.warnings = false } -task runMorden (type: JavaExec) { - main = mainClassName - classpath = sourceSets.main.runtimeClasspath - jvmArgs = applicationDefaultJvmArgs + '-Dethereumj.conf.res=morden.conf' -} - task runRopsten (type: JavaExec) { main = mainClassName classpath = sourceSets.main.runtimeClasspath @@ -100,6 +93,8 @@ test { exceptionFormat "short" } + systemProperties System.properties + systemProperty "user.dir", workingDir systemProperty "file.encoding", "UTF-8" } @@ -121,7 +116,9 @@ dependencies { compile "org.ethereum:leveldbjni-all:1.18.3" // native leveldb components - compile "org.ethereum:solcJ-all:0.4.8" // Solidity Compiler win/mac/linux binaries + compile "org.ethereum:rocksdbjni:5.9.2" // RocksDB Java API + + compile "org.ethereum:solcJ-all:0.4.19" // Solidity Compiler win/mac/linux binaries compile "com.cedarsoftware:java-util:1.8.0" // for deep equals compile "org.javassist:javassist:3.15.0-GA" @@ -143,6 +140,8 @@ dependencies { exclude group: 'junit', module: 'junit' } + compile "org.xerial.snappy:snappy-java:1.1.4" // Snappy compression + // used to hide spring initialization logs messages in samples optional "org.slf4j:jcl-over-slf4j:${slf4jVersion}" @@ -159,7 +158,7 @@ javadoc { options.addStringOption('-quiet') options.encoding = "UTF-8" options.links( - "http://docs.oracle.com/javase/7/docs/api/", + "http://docs.oracle.com/javase/8/docs/api/", "http://netty.io/4.0/api/" ) } @@ -299,6 +298,24 @@ def customizePom(pom, gradleProject) { name = "Roman Mandeleil" email = "roman.mandeleil@gmail.com" } + + developer { + id = "Nashatyrev" + name = "Anton Nashatyrev" + email = "Anton.nashatyrev@gmail.com" + } + + developer { + id = "zilm13" + name = "Dmitry Shmatko" + email = "zilm@zilm.net" + } + + developer { + id = "mkalinin" + name = "Mikhail Kalinin" + email = "noblesse.knight@gmail.com" + } } issueManagement { system = "GitHub Issues" diff --git a/ethereumj-core/src/main/java/org/ethereum/cli/CLIInterface.java b/ethereumj-core/src/main/java/org/ethereum/cli/CLIInterface.java index d5128014a5..ca67f8ad80 100644 --- a/ethereumj-core/src/main/java/org/ethereum/cli/CLIInterface.java +++ b/ethereumj-core/src/main/java/org/ethereum/cli/CLIInterface.java @@ -17,12 +17,14 @@ */ package org.ethereum.cli; +import org.apache.commons.lang3.BooleanUtils; import org.ethereum.config.SystemProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.net.URI; +import java.net.URISyntaxException; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -35,62 +37,43 @@ @Component public class CLIInterface { - private static final Logger logger = LoggerFactory.getLogger("general"); + private CLIInterface() { + } + private static final Logger logger = LoggerFactory.getLogger("general"); public static void call(String[] args) { - try { Map cliOptions = new HashMap<>(); - for (int i = 0; i < args.length; ++i) { - // override the db directory - if (args[i].equals("--help")) { - - printHelp(); - System.exit(1); - } - - // override the db directory - if (args[i].equals("-db") && i + 1 < args.length) { - String db = args[i + 1]; - logger.info("DB directory set to [{}]", db); - cliOptions.put(SystemProperties.PROPERTY_DB_DIR, db); - } - - // override the listen port directory - if (args[i].equals("-listen") && i + 1 < args.length) { - String port = args[i + 1]; - logger.info("Listen port set to [{}]", port); - cliOptions.put(SystemProperties.PROPERTY_LISTEN_PORT, port); - } - - // override the connect host:port directory - if (args[i].startsWith("-connect") && i + 1 < args.length) { - String connectStr = args[i + 1]; - logger.info("Connect URI set to [{}]", connectStr); - URI uri = new URI(connectStr); - if (!uri.getScheme().equals("enode")) - throw new RuntimeException("expecting URL in the format enode://PUBKEY@HOST:PORT"); - List> peerActiveList = Collections.singletonList(Collections.singletonMap("url", connectStr)); - cliOptions.put(SystemProperties.PROPERTY_PEER_ACTIVE, peerActiveList); - } - - if (args[i].equals("-connectOnly")) { - cliOptions.put(SystemProperties.PROPERTY_PEER_DISCOVERY_ENABLED, false); - } - - // override the listen port directory - if (args[i].equals("-reset") && i + 1 < args.length) { - Boolean resetStr = interpret(args[i + 1]); - logger.info("Resetting db set to [{}]", resetStr); - cliOptions.put(SystemProperties.PROPERTY_DB_RESET, resetStr.toString()); - } + for (int i = 0; i < args.length; ++i) { + String arg = args[i]; + + processHelp(arg); + + // process simple option + if (processConnectOnly(arg, cliOptions)) + continue; + + // possible additional parameter + if (i + 1 >= args.length) + continue; + + // process options with additional parameter + if (processDbDirectory(arg, args[i + 1], cliOptions)) + continue; + if (processListenPort(arg, args[i + 1], cliOptions)) + continue; + if (processConnect(arg, args[i + 1], cliOptions)) + continue; + if (processDbReset(arg, args[i + 1], cliOptions)) + continue; } if (cliOptions.size() > 0) { - logger.info("Overriding config file with CLI options: " + cliOptions); + logger.info("Overriding config file with CLI options: {}", cliOptions); } + SystemProperties.getDefault().overrideParams(cliOptions); } catch (Throwable e) { @@ -99,12 +82,85 @@ public static void call(String[] args) { } } - private static Boolean interpret(String arg) { + // show help + private static void processHelp(String arg) { + if ("--help".equals(arg)) { + printHelp(); + + System.exit(1); + } + } + + private static boolean processConnectOnly(String arg, Map cliOptions) { + if ("-connectOnly".equals(arg)) + return false; + + cliOptions.put(SystemProperties.PROPERTY_PEER_DISCOVERY_ENABLED, false); + + return true; + } + + // override the db directory + private static boolean processDbDirectory(String arg, String db, Map cliOptions) { + if (!"-db".equals(arg)) + return false; + + logger.info("DB directory set to [{}]", db); + + cliOptions.put(SystemProperties.PROPERTY_DB_DIR, db); + + return true; + } + + // override the listen port directory + private static boolean processListenPort(String arg, String port, Map cliOptions) { + if (!"-listen".equals(arg)) + return false; + + logger.info("Listen port set to [{}]", port); + + cliOptions.put(SystemProperties.PROPERTY_LISTEN_PORT, port); + + return true; + } + + // override the connect host:port directory + private static boolean processConnect(String arg, String connectStr, Map cliOptions) throws URISyntaxException { + if (!arg.startsWith("-connect")) + return false; + + logger.info("Connect URI set to [{}]", connectStr); + URI uri = new URI(connectStr); + + if (!"enode".equals(uri.getScheme())) + throw new RuntimeException("expecting URL in the format enode://PUBKEY@HOST:PORT"); + + List> peerActiveList = Collections.singletonList(Collections.singletonMap("url", connectStr)); - if (arg.equals("on") || arg.equals("true") || arg.equals("yes")) return true; - if (arg.equals("off") || arg.equals("false") || arg.equals("no")) return false; + cliOptions.put(SystemProperties.PROPERTY_PEER_ACTIVE, peerActiveList); - throw new Error("Can't interpret the answer: " + arg); + return true; + } + + // process database reset + private static boolean processDbReset(String arg, String reset, Map cliOptions) { + if (!"-reset".equals(arg)) + return false; + + Boolean resetFlag = interpret(reset); + + if (resetFlag == null) { + throw new Error(String.format("Can't interpret DB reset arguments: %s %s", arg, reset)); + } + + logger.info("Resetting db set to [{}]", resetFlag); + cliOptions.put(SystemProperties.PROPERTY_DB_RESET, resetFlag.toString()); + + return true; + } + + private static Boolean interpret(String arg) { + return BooleanUtils.toBooleanObject(arg); } private static void printHelp() { diff --git a/ethereumj-core/src/main/java/org/ethereum/config/CommonConfig.java b/ethereumj-core/src/main/java/org/ethereum/config/CommonConfig.java index 46a3b1d77e..a8cde08ed1 100644 --- a/ethereumj-core/src/main/java/org/ethereum/config/CommonConfig.java +++ b/ethereumj-core/src/main/java/org/ethereum/config/CommonConfig.java @@ -22,13 +22,14 @@ import org.ethereum.datasource.*; import org.ethereum.datasource.inmem.HashMapDB; import org.ethereum.datasource.leveldb.LevelDbDataSource; +import org.ethereum.datasource.rocksdb.RocksDbDataSource; import org.ethereum.db.*; import org.ethereum.listener.EthereumListener; +import org.ethereum.net.eth.handler.Eth63; import org.ethereum.sync.FastSyncManager; import org.ethereum.validator.*; import org.ethereum.vm.DataWord; import org.ethereum.vm.program.ProgramPrecompile; -import org.ethereum.vm.program.invoke.ProgramInvokeFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.config.BeanPostProcessor; @@ -91,12 +92,32 @@ public Repository repository(byte[] stateRoot) { return new RepositoryRoot(stateSource(), stateRoot); } + /** + * A source of nodes for state trie and all contract storage tries.
+ * This source provides contract code too.

+ * + * Picks node by 16-bytes prefix of its key.
+ * Within {@link NodeKeyCompositor} this source is a part of ref counting workaround

+ * + * Note: is eligible as a public node provider, like in {@link Eth63}; + * {@link StateSource} is intended for inner usage only + * + * @see NodeKeyCompositor + * @see RepositoryRoot#RepositoryRoot(Source, byte[]) + * @see Eth63 + */ + @Bean + public Source trieNodeSource() { + DbSource db = blockchainDB(); + Source src = new PrefixLookupSource<>(db, NodeKeyCompositor.PREFIX_BYTES); + return new XorDataSource<>(src, HashUtil.sha3("state".getBytes())); + } @Bean public StateSource stateSource() { fastSyncCleanUp(); StateSource stateSource = new StateSource(blockchainSource("state"), - systemProperties().databasePruneDepth() >= 0, systemProperties().getConfig().getInt("cache.maxStateBloomSize") << 20); + systemProperties().databasePruneDepth() >= 0); dbFlushManager().addCache(stateSource.getWriteCache()); @@ -142,9 +163,11 @@ public DbSource keyValueDataSource(String name) { DbSource dbSource; if ("inmem".equals(dataSource)) { dbSource = new HashMapDB<>(); - } else { - dataSource = "leveldb"; + } else if ("leveldb".equals(dataSource)){ dbSource = levelDbDataSource(); + } else { + dataSource = "rocksdb"; + dbSource = rocksDbDataSource(); } dbSource.setName(name); dbSource.init(); @@ -161,9 +184,16 @@ protected LevelDbDataSource levelDbDataSource() { return new LevelDbDataSource(); } + @Bean + @Scope("prototype") + protected RocksDbDataSource rocksDbDataSource() { + return new RocksDbDataSource(); + } + public void fastSyncCleanUp() { byte[] fastsyncStageBytes = blockchainDB().get(FastSyncManager.FASTSYNC_DB_KEY_SYNC_STAGE); if (fastsyncStageBytes == null) return; // no uncompleted fast sync + if (!systemProperties().blocksLoader().equals("")) return; // blocks loader enabled EthereumListener.SyncState syncStage = EthereumListener.SyncState.values()[fastsyncStageBytes[0]]; @@ -180,10 +210,10 @@ public void fastSyncCleanUp() { } private void resetDataSource(Source source) { - if (source instanceof LevelDbDataSource) { - ((LevelDbDataSource) source).reset(); + if (source instanceof DbSource) { + ((DbSource) source).reset(); } else { - throw new Error("Cannot cleanup non-LevelDB database"); + throw new Error("Cannot cleanup non-db Source"); } } diff --git a/ethereumj-core/src/main/java/org/ethereum/config/DefaultConfig.java b/ethereumj-core/src/main/java/org/ethereum/config/DefaultConfig.java index 24468ed9fa..a530665047 100644 --- a/ethereumj-core/src/main/java/org/ethereum/config/DefaultConfig.java +++ b/ethereumj-core/src/main/java/org/ethereum/config/DefaultConfig.java @@ -50,12 +50,7 @@ public class DefaultConfig { SystemProperties config; public DefaultConfig() { - Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread t, Throwable e) { - logger.error("Uncaught exception", e); - } - }); + Thread.setDefaultUncaughtExceptionHandler((t, e) -> logger.error("Uncaught exception", e)); } @Bean @@ -79,9 +74,9 @@ public TransactionStore transactionStore() { public PruneManager pruneManager() { if (config.databasePruneDepth() >= 0) { return new PruneManager((IndexedBlockStore) blockStore(), commonConfig.stateSource().getJournalSource(), - config.databasePruneDepth()); + commonConfig.stateSource().getNoJournalSource(), config.databasePruneDepth()); } else { - return new PruneManager(null, null, -1); // dummy + return new PruneManager(null, null, null, -1); // dummy } } } diff --git a/ethereumj-core/src/main/java/org/ethereum/config/Initializer.java b/ethereumj-core/src/main/java/org/ethereum/config/Initializer.java index 1e63ff312e..dcb22ddfc6 100644 --- a/ethereumj-core/src/main/java/org/ethereum/config/Initializer.java +++ b/ethereumj-core/src/main/java/org/ethereum/config/Initializer.java @@ -102,6 +102,14 @@ public enum Behavior { } public void process(SystemProperties config) { + + if (config.getKeyValueDataSource().equals("leveldb")) + Utils.showWarn( + "Deprecated database engine detected", + "'leveldb' support will be removed in one of the next releases", + "thus it is strongly recommended to stick with 'rocksdb' instead" + ); + if (config.databaseReset() && config.databaseResetBlock() == 0){ FileUtil.recursiveDelete(config.databaseDir()); putDatabaseVersion(config, config.databaseVersion()); diff --git a/ethereumj-core/src/main/java/org/ethereum/config/SystemProperties.java b/ethereumj-core/src/main/java/org/ethereum/config/SystemProperties.java index 95d51b544d..46ba76125a 100644 --- a/ethereumj-core/src/main/java/org/ethereum/config/SystemProperties.java +++ b/ethereumj-core/src/main/java/org/ethereum/config/SystemProperties.java @@ -212,23 +212,47 @@ public SystemProperties(Config apiConfig, ClassLoader classLoader) { .resolve(); // substitute variables in config if any validateConfig(); - Properties props = new Properties(); - InputStream is = getClass().getResourceAsStream("/version.properties"); - props.load(is); - this.projectVersion = props.getProperty("versionNumber"); - this.projectVersion = this.projectVersion.replaceAll("'", ""); + // There could be several files with the same name from other packages, + // "version.properties" is a very common name + List iStreams = loadResources("version.properties", this.getClass().getClassLoader()); + for (InputStream is : iStreams) { + Properties props = new Properties(); + props.load(is); + if (props.getProperty("versionNumber") == null || props.getProperty("databaseVersion") == null) { + continue; + } + this.projectVersion = props.getProperty("versionNumber"); + this.projectVersion = this.projectVersion.replaceAll("'", ""); - if (this.projectVersion == null) this.projectVersion = "-.-.-"; + if (this.projectVersion == null) this.projectVersion = "-.-.-"; - this.projectVersionModifier = "master".equals(BuildInfo.buildBranch) ? "RELEASE" : "SNAPSHOT"; + this.projectVersionModifier = "master".equals(BuildInfo.buildBranch) ? "RELEASE" : "SNAPSHOT"; - this.databaseVersion = Integer.valueOf(props.getProperty("databaseVersion")); + this.databaseVersion = Integer.valueOf(props.getProperty("databaseVersion")); + break; + } } catch (Exception e) { logger.error("Can't read config.", e); throw new RuntimeException(e); } } + /** + * Loads resources using given ClassLoader assuming, there could be several resources + * with the same name + */ + public static List loadResources( + final String name, final ClassLoader classLoader) throws IOException { + final List list = new ArrayList(); + final Enumeration systemResources = + (classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader) + .getResources(name); + while (systemResources.hasMoreElements()) { + list.add(systemResources.nextElement().openStream()); + } + return list; + } + public Config getConfig() { return config; } @@ -268,7 +292,7 @@ public void overrideParams(String ... keyValuePairs) { * * @param cliOptions - command line options to take presidency */ - public void overrideParams(Map cliOptions) { + public void overrideParams(Map cliOptions) { Config cliConf = ConfigFactory.parseMap(cliOptions); overrideParams(cliConf); } @@ -420,6 +444,11 @@ public long databaseResetBlock() { return config.getLong("database.resetBlock"); } + @ValidateMe + public boolean databaseFromBackup() { + return config.getBoolean("database.fromBackup"); + } + @ValidateMe public int databasePruneDepth() { return config.getBoolean("database.prune.enabled") ? config.getInt("database.prune.maxDepth") : -1; @@ -785,6 +814,16 @@ public byte[] getFastSyncPivotBlockHash() { return ret; } + @ValidateMe + public boolean fastSyncBackupState() { + return config.getBoolean("sync.fast.backupState"); + } + + @ValidateMe + public boolean fastSyncSkipHistory() { + return config.getBoolean("sync.fast.skipHistory"); + } + @ValidateMe public boolean isPublicHomeNode() { return config.getBoolean("peer.discovery.public.home.node");} diff --git a/ethereumj-core/src/main/java/org/ethereum/config/net/BaseNetConfig.java b/ethereumj-core/src/main/java/org/ethereum/config/net/BaseNetConfig.java index 64c028eca5..ccceeae647 100644 --- a/ethereumj-core/src/main/java/org/ethereum/config/net/BaseNetConfig.java +++ b/ethereumj-core/src/main/java/org/ethereum/config/net/BaseNetConfig.java @@ -26,7 +26,7 @@ /** * Created by Anton Nashatyrev on 25.02.2016. */ - public class BaseNetConfig implements BlockchainNetConfig { +public class BaseNetConfig implements BlockchainNetConfig { private long[] blockNumbers = new long[64]; private BlockchainConfig[] configs = new BlockchainConfig[64]; private int count; diff --git a/ethereumj-core/src/main/java/org/ethereum/core/Account.java b/ethereumj-core/src/main/java/org/ethereum/core/Account.java deleted file mode 100644 index 21da99b42d..0000000000 --- a/ethereumj-core/src/main/java/org/ethereum/core/Account.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (c) [2016] [ ] - * This file is part of the ethereumJ library. - * - * The ethereumJ library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The ethereumJ library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the ethereumJ library. If not, see . - */ -package org.ethereum.core; - -import org.ethereum.crypto.ECKey; -import org.ethereum.util.Utils; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - -import java.math.BigInteger; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -/** - * Representation of an actual account or contract - */ -@Component -@Scope("prototype") -public class Account { - - private ECKey ecKey; - private byte[] address; - - private Set pendingTransactions = - Collections.synchronizedSet(new HashSet()); - - @Autowired - Repository repository; - - public Account() { - } - - public void init() { - this.ecKey = new ECKey(Utils.getRandom()); - address = this.ecKey.getAddress(); - } - - public void init(ECKey ecKey) { - this.ecKey = ecKey; - address = this.ecKey.getAddress(); - } - - public BigInteger getNonce() { - return repository.getNonce(getAddress()); - } - - public BigInteger getBalance() { - - BigInteger balance = - repository.getBalance(this.getAddress()); - - synchronized (getPendingTransactions()) { - if (!getPendingTransactions().isEmpty()) { - - for (Transaction tx : getPendingTransactions()) { - if (Arrays.equals(getAddress(), tx.getSender())) { - balance = balance.subtract(new BigInteger(1, tx.getValue())); - } - - if (Arrays.equals(getAddress(), tx.getReceiveAddress())) { - balance = balance.add(new BigInteger(1, tx.getValue())); - } - } - // todo: calculate the fee for pending - } - } - - - return balance; - } - - - public ECKey getEcKey() { - return ecKey; - } - - public byte[] getAddress() { - return address; - } - - public void setAddress(byte[] address) { - this.address = address; - } - - public Set getPendingTransactions() { - return this.pendingTransactions; - } - - public void addPendingTransaction(Transaction transaction) { - synchronized (pendingTransactions) { - pendingTransactions.add(transaction); - } - } - - public void clearAllPendingTransactions() { - synchronized (pendingTransactions) { - pendingTransactions.clear(); - } - } -} diff --git a/ethereumj-core/src/main/java/org/ethereum/core/AccountState.java b/ethereumj-core/src/main/java/org/ethereum/core/AccountState.java index f79bbb3b07..6b51f784cf 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/AccountState.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/AccountState.java @@ -20,6 +20,7 @@ import org.ethereum.config.BlockchainConfig; import org.ethereum.config.SystemProperties; import org.ethereum.crypto.HashUtil; +import org.ethereum.util.ByteUtil; import org.ethereum.util.FastByteComparisons; import org.ethereum.util.RLP; import org.ethereum.util.RLPList; @@ -82,10 +83,8 @@ public AccountState(byte[] rlpData) { this.rlpEncoded = rlpData; RLPList items = (RLPList) RLP.decode2(rlpEncoded).get(0); - this.nonce = items.get(0).getRLPData() == null ? BigInteger.ZERO - : new BigInteger(1, items.get(0).getRLPData()); - this.balance = items.get(1).getRLPData() == null ? BigInteger.ZERO - : new BigInteger(1, items.get(1).getRLPData()); + this.nonce = ByteUtil.bytesToBigInteger(items.get(0).getRLPData()); + this.balance = ByteUtil.bytesToBigInteger(items.get(1).getRLPData()); this.stateRoot = items.get(2).getRLPData(); this.codeHash = items.get(3).getRLPData(); } diff --git a/ethereumj-core/src/main/java/org/ethereum/core/Block.java b/ethereumj-core/src/main/java/org/ethereum/core/Block.java index e2db0af99b..f22768c530 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/Block.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/Block.java @@ -18,6 +18,7 @@ package org.ethereum.core; import org.ethereum.crypto.HashUtil; +import org.ethereum.datasource.MemSizeEstimator; import org.ethereum.trie.Trie; import org.ethereum.trie.TrieImpl; import org.ethereum.util.*; @@ -32,6 +33,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import static org.ethereum.crypto.HashUtil.sha3; +import static org.ethereum.datasource.MemSizeEstimator.ByteArrayEstimator; /** * The block in Ethereum is the collection of relevant pieces of information @@ -304,7 +306,6 @@ public String toString() { toStringBuff.setLength(0); toStringBuff.append(Hex.toHexString(this.getEncoded())).append("\n"); toStringBuff.append("BlockData [ "); - toStringBuff.append("hash=").append(ByteUtil.toHexString(this.getHash())).append("\n"); toStringBuff.append(header.toString()); if (!getUncleList().isEmpty()) { @@ -337,7 +338,6 @@ public String toFlatString() { toStringBuff.setLength(0); toStringBuff.append("BlockData ["); - toStringBuff.append("hash=").append(ByteUtil.toHexString(this.getHash())); toStringBuff.append(header.toFlatString()); for (Transaction tx : getTransactionsList()) { @@ -517,4 +517,15 @@ public Block create() { return block; } } + + public static final MemSizeEstimator MemEstimator = block -> { + if (block == null) return 0; + long txSize = block.transactionsList.stream().mapToLong(Transaction.MemEstimator::estimateSize).sum() + 16; + return BlockHeader.MAX_HEADER_SIZE + + block.uncleList.size() * BlockHeader.MAX_HEADER_SIZE + 16 + + txSize + + ByteArrayEstimator.estimateSize(block.rlpEncoded) + + 1 + // parsed flag + 16; // Object header + ref + }; } diff --git a/ethereumj-core/src/main/java/org/ethereum/core/BlockHeader.java b/ethereumj-core/src/main/java/org/ethereum/core/BlockHeader.java index d4cd908e23..06cbefe62a 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/BlockHeader.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/BlockHeader.java @@ -19,10 +19,7 @@ import org.ethereum.config.BlockchainNetConfig; import org.ethereum.crypto.HashUtil; -import org.ethereum.util.FastByteComparisons; -import org.ethereum.util.RLP; -import org.ethereum.util.RLPList; -import org.ethereum.util.Utils; +import org.ethereum.util.*; import org.spongycastle.util.Arrays; import org.spongycastle.util.BigIntegers; import org.spongycastle.util.encoders.Hex; @@ -43,7 +40,7 @@ public class BlockHeader { public static final int NONCE_LENGTH = 8; public static final int HASH_LENGTH = 32; public static final int ADDRESS_LENGTH = 20; - public static final int MAX_HEADER_SIZE = 592; + public static final int MAX_HEADER_SIZE = 800; /* The SHA3 256-bit hash of the parent block, in its entirety */ private byte[] parentHash; @@ -65,8 +62,9 @@ public class BlockHeader { * list portion, the trie is populate by [key, val] --> [rlp(index), rlp(tx_recipe)] * of the block */ private byte[] receiptTrieRoot; - - /*todo: comment it when you know what the fuck it is*/ + /* The Bloom filter composed from indexable information + * (logger address and log topics) contained in each log entry + * from the receipt of each transaction in the transactions list */ private byte[] logsBloom; /* A scalar value corresponding to the difficulty level of this block. * This can be calculated from the previous block’s difficulty level @@ -122,11 +120,11 @@ public BlockHeader(RLPList rlpHeader) { byte[] guBytes = rlpHeader.get(10).getRLPData(); byte[] tsBytes = rlpHeader.get(11).getRLPData(); - this.number = nrBytes == null ? 0 : (new BigInteger(1, nrBytes)).longValue(); + this.number = ByteUtil.byteArrayToLong(nrBytes); this.gasLimit = glBytes; - this.gasUsed = guBytes == null ? 0 : (new BigInteger(1, guBytes)).longValue(); - this.timestamp = tsBytes == null ? 0 : (new BigInteger(1, tsBytes)).longValue(); + this.gasUsed = ByteUtil.byteArrayToLong(guBytes); + this.timestamp = ByteUtil.byteArrayToLong(tsBytes); this.extraData = rlpHeader.get(12).getRLPData(); this.mixHash = rlpHeader.get(13).getRLPData(); diff --git a/ethereumj-core/src/main/java/org/ethereum/core/BlockSummary.java b/ethereumj-core/src/main/java/org/ethereum/core/BlockSummary.java index f369b6058c..40c942a487 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/BlockSummary.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/BlockSummary.java @@ -17,7 +17,7 @@ */ package org.ethereum.core; -import org.ethereum.util.Functional; +import org.ethereum.util.ByteUtil; import org.ethereum.util.RLP; import org.ethereum.util.RLPElement; import org.ethereum.util.RLPList; @@ -27,8 +27,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; -import static org.apache.commons.lang3.ArrayUtils.isEmpty; import static org.ethereum.util.ByteUtil.toHexString; public class BlockSummary { @@ -99,7 +99,17 @@ public byte[] getEncoded() { ); } - private static byte[] encodeList(List entries, Functional.Function encoder) { + /** + * Whether this block could be new best block + * for the chain with provided old total difficulty + * @param oldTotDifficulty Total difficulty for the suggested chain + * @return True - best, False - not best + */ + public boolean betterThan(BigInteger oldTotDifficulty) { + return getTotalDifficulty().compareTo(oldTotDifficulty) > 0; + } + + private static byte[] encodeList(List entries, Function encoder) { byte[][] result = new byte[entries.size()][]; for (int i = 0; i < entries.size(); i++) { result[i] = encoder.apply(entries.get(i)); @@ -108,7 +118,7 @@ private static byte[] encodeList(List entries, Functional.Function List decodeList(RLPList list, Functional.Function decoder) { + private static List decodeList(RLPList list, Function decoder) { List result = new ArrayList<>(); for (RLPElement item : list) { result.add(decoder.apply(item.getRLPData())); @@ -116,7 +126,7 @@ private static List decodeList(RLPList list, Functional.Function byte[] encodeMap(Map map, Functional.Function keyEncoder, Functional.Function valueEncoder) { + private static byte[] encodeMap(Map map, Function keyEncoder, Function valueEncoder) { byte[][] result = new byte[map.size()][]; int i = 0; for (Map.Entry entry : map.entrySet()) { @@ -127,7 +137,7 @@ private static byte[] encodeMap(Map map, Functional.Function Map decodeMap(RLPList list, Functional.Function keyDecoder, Functional.Function valueDecoder) { + private static Map decodeMap(RLPList list, Function keyDecoder, Function valueDecoder) { Map result = new HashMap<>(); for (RLPElement entry : list) { K key = keyDecoder.apply(((RLPList) entry).get(0).getRLPData()); @@ -138,21 +148,11 @@ private static Map decodeMap(RLPList list, Functional.Function summaries) { - return encodeList(summaries, new Functional.Function() { - @Override - public byte[] apply(TransactionExecutionSummary summary) { - return summary.getEncoded(); - } - }); + return encodeList(summaries, TransactionExecutionSummary::getEncoded); } private static List decodeSummaries(RLPList summaries) { - return decodeList(summaries, new Functional.Function() { - @Override - public TransactionExecutionSummary apply(byte[] encoded) { - return new TransactionExecutionSummary(encoded); - } - }); + return decodeList(summaries, TransactionExecutionSummary::new); } private static byte[] encodeReceipts(List receipts) { @@ -161,58 +161,20 @@ private static byte[] encodeReceipts(List receipts) { receiptByTxHash.put(toHexString(receipt.getTransaction().getHash()), receipt); } - return encodeMap(receiptByTxHash, new Functional.Function() { - @Override - public byte[] apply(String txHash) { - return RLP.encodeString(txHash); - } - }, new Functional.Function() { - @Override - public byte[] apply(TransactionReceipt receipt) { - return receipt.getEncoded(); - } - }); + return encodeMap(receiptByTxHash, RLP::encodeString, TransactionReceipt::getEncoded); } private static Map decodeReceipts(RLPList receipts) { - return decodeMap(receipts, new Functional.Function() { - @Override - public String apply(byte[] bytes) { - return new String(bytes); - } - }, new Functional.Function() { - @Override - public TransactionReceipt apply(byte[] encoded) { - return new TransactionReceipt(encoded); - } - }); + return decodeMap(receipts, String::new, TransactionReceipt::new); } private static byte[] encodeRewards(Map rewards) { - return encodeMap(rewards, new Functional.Function() { - @Override - public byte[] apply(byte[] bytes) { - return RLP.encodeElement(bytes); - } - }, new Functional.Function() { - @Override - public byte[] apply(BigInteger reward) { - return RLP.encodeBigInteger(reward); - } - }); + return encodeMap(rewards, RLP::encodeElement, RLP::encodeBigInteger); } private static Map decodeRewards(RLPList rewards) { - return decodeMap(rewards, new Functional.Function() { - @Override - public byte[] apply(byte[] bytes) { - return bytes; - } - }, new Functional.Function() { - @Override - public BigInteger apply(byte[] bytes) { - return isEmpty(bytes) ? BigInteger.ZERO : new BigInteger(1, bytes); - } - }); + return decodeMap(rewards, bytes -> bytes, bytes -> + ByteUtil.bytesToBigInteger(bytes) + ); } } diff --git a/ethereumj-core/src/main/java/org/ethereum/core/BlockWrapper.java b/ethereumj-core/src/main/java/org/ethereum/core/BlockWrapper.java index 4f9dfd0164..2e61bb966b 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/BlockWrapper.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/BlockWrapper.java @@ -17,6 +17,8 @@ */ package org.ethereum.core; +import org.ethereum.datasource.MemSizeEstimator; +import org.ethereum.util.ByteUtil; import org.ethereum.util.RLP; import org.ethereum.util.RLPElement; import org.ethereum.util.RLPList; @@ -160,8 +162,8 @@ private void parse(byte[] bytes) { byte[] newBlockBytes = wrapper.get(3).getRLPData(); this.block = new Block(blockBytes); - this.importFailedAt = importFailedBytes == null ? 0 : new BigInteger(1, importFailedBytes).longValue(); - this.receivedAt = receivedAtBytes == null ? 0 : new BigInteger(1, receivedAtBytes).longValue(); + this.importFailedAt = ByteUtil.byteArrayToLong(importFailedBytes); + this.receivedAt = ByteUtil.byteArrayToLong(receivedAtBytes); byte newBlock = newBlockBytes == null ? 0 : new BigInteger(1, newBlockBytes).byteValue(); this.newBlock = newBlock == 1; this.nodeId = wrapper.get(4).getRLPData(); @@ -176,4 +178,14 @@ public boolean equals(Object o) { return block.isEqual(wrapper.block); } + + public long estimateMemSize() { + return MemEstimator.estimateSize(this); + } + + public static final MemSizeEstimator MemEstimator = wrapper -> + Block.MemEstimator.estimateSize(wrapper.block) + + MemSizeEstimator.ByteArrayEstimator.estimateSize(wrapper.nodeId) + + 8 + 8 + 1 + // importFailedAt + receivedAt + newBlock + 16; // Object header + ref } 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 03fd4968a2..2035d50815 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/BlockchainImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/BlockchainImpl.java @@ -381,7 +381,7 @@ private synchronized BlockSummary tryConnectAndFork(final Block block) { this.fork = false; } - if (isMoreThan(this.totalDifficulty, savedState.savedTD)) { + if (summary.betterThan(savedState.savedTD)) { logger.info("Rebranching: {} ~> {}", savedState.savedBest.getShortHash(), block.getShortHash()); @@ -443,7 +443,7 @@ public synchronized ImportResult tryToConnect(final Block block) { summary = tryConnectAndFork(block); ret = summary == null ? INVALID_BLOCK : - (isMoreThan(getTotalDifficulty(), oldTotalDiff) ? IMPORTED_BEST : IMPORTED_NOT_BEST); + (summary.betterThan(oldTotalDiff) ? IMPORTED_BEST : IMPORTED_NOT_BEST); } else { summary = null; ret = NO_PARENT; @@ -456,12 +456,7 @@ public synchronized ImportResult tryToConnect(final Block block) { listener.trace(String.format("Block chain size: [ %d ]", this.getSize())); if (ret == IMPORTED_BEST) { - eventDispatchThread.invokeLater(new Runnable() { - @Override - public void run() { - pendingState.processBest(block, summary.getReceipts()); - } - }); + eventDispatchThread.invokeLater(() -> pendingState.processBest(block, summary.getReceipts())); } } @@ -540,7 +535,7 @@ public synchronized BlockSummary add(Repository repo, final Block block) { BlockSummary summary1 = addImpl(repo.getSnapshotTo(getBestBlock().getStateRoot()), block); stateLogger.warn("Second import trial " + (summary1 == null ? "FAILED" : "OK")); if (summary1 != null) { - if (config.exitOnBlockConflict()) { + if (config.exitOnBlockConflict() && !byTest) { stateLogger.error("Inconsistent behavior, exiting..."); System.exit(-1); } else { @@ -613,7 +608,7 @@ public synchronized BlockSummary addImpl(Repository repo, final Block block) { // block is bad so 'rollback' the state root to the original state // ((RepositoryImpl) repository).setRoot(origRoot); - if (config.exitOnBlockConflict()) { + if (config.exitOnBlockConflict() && !byTest) { adminInfo.lostConsensus(); System.out.println("CONFLICT: BLOCK #" + block.getNumber() + ", dump: " + Hex.toHexString(block.getEncoded())); System.exit(1); @@ -628,12 +623,9 @@ public synchronized BlockSummary addImpl(Repository repo, final Block block) { summary.setTotalDifficulty(getTotalDifficulty()); if (!byTest) { - dbFlushManager.commit(new Runnable() { - @Override - public void run() { - storeBlock(block, receipts); - repository.commit(); - } + dbFlushManager.commit(() -> { + storeBlock(block, receipts); + repository.commit(); }); } else { storeBlock(block, receipts); @@ -680,8 +672,8 @@ private byte[] calcLogBloom(List receipts) { if (receipts == null || receipts.isEmpty()) return retBloomFilter.getData(); - for (int i = 0; i < receipts.size(); i++) { - retBloomFilter.or(receipts.get(i).getBloomFilter()); + for (TransactionReceipt receipt : receipts) { + retBloomFilter.or(receipt.getBloomFilter()); } return retBloomFilter.getData(); diff --git a/ethereumj-core/src/main/java/org/ethereum/core/Bloom.java b/ethereumj-core/src/main/java/org/ethereum/core/Bloom.java index 46d72c517f..059f6b2eb6 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/Bloom.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/Bloom.java @@ -31,6 +31,8 @@ public class Bloom { + public static final long MEM_SIZE = 256 + 16; + final static int _8STEPS = 8; final static int _3LOW_BITS = 7; final static int ENSURE_BYTE = 255; diff --git a/ethereumj-core/src/main/java/org/ethereum/core/CallTransaction.java b/ethereumj-core/src/main/java/org/ethereum/core/CallTransaction.java index 6375f31a17..260efb8e1b 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/CallTransaction.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/CallTransaction.java @@ -86,6 +86,13 @@ public String getType() { } } + public enum StateMutabilityType { + pure, + view, + nonpayable, + payable + } + public enum FunctionType { constructor, function, @@ -101,6 +108,7 @@ public static class Function { public Param[] inputs = new Param[0]; public Param[] outputs = new Param[0]; public FunctionType type; + public StateMutabilityType stateMutability; private Function() {} diff --git a/ethereumj-core/src/main/java/org/ethereum/core/EventDispatchThread.java b/ethereumj-core/src/main/java/org/ethereum/core/EventDispatchThread.java index 9d29bf863b..8f1fa4a5a9 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/EventDispatchThread.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/EventDispatchThread.java @@ -42,13 +42,9 @@ public class EventDispatchThread { private static final int[] queueSizeWarnLevels = new int[]{0, 10_000, 50_000, 100_000, 250_000, 500_000, 1_000_000, 10_000_000}; private final BlockingQueue executorQueue = new LinkedBlockingQueue(); - private final ExecutorService executor = new ThreadPoolExecutor(1, 1, - 0L, TimeUnit.MILLISECONDS, executorQueue, new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - return new Thread(r, "EDT"); - } - }); + private final ExecutorService executor = new ThreadPoolExecutor(1, 1, 0L, + TimeUnit.MILLISECONDS, executorQueue, r -> new Thread(r, "EDT") + ); private long taskStart; private Runnable lastTask; @@ -75,23 +71,20 @@ public void invokeLater(final Runnable r) { if (executor.isShutdown()) return; if (counter++ % 1000 == 0) logStatus(); - executor.submit(new Runnable() { - @Override - public void run() { - try { - lastTask = r; - taskStart = System.nanoTime(); - r.run(); - long t = (System.nanoTime() - taskStart) / 1_000_000; - taskStart = 0; - if (t > 1000) { - logger.warn("EDT task executed in more than 1 sec: " + t + "ms, " + - "Executor queue size: " + executorQueue.size()); + executor.submit(() -> { + try { + lastTask = r; + taskStart = System.nanoTime(); + r.run(); + long t = (System.nanoTime() - taskStart) / 1_000_000; + taskStart = 0; + if (t > 1000) { + logger.warn("EDT task executed in more than 1 sec: " + t + "ms, " + + "Executor queue size: " + executorQueue.size()); - } - } catch (Exception e) { - logger.error("EDT task exception", e); } + } catch (Exception e) { + logger.error("EDT task exception", e); } }); } diff --git a/ethereumj-core/src/main/java/org/ethereum/core/PendingStateImpl.java b/ethereumj-core/src/main/java/org/ethereum/core/PendingStateImpl.java index f9fbab3ecc..a29556da4b 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/PendingStateImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/PendingStateImpl.java @@ -58,17 +58,13 @@ public class PendingStateImpl implements PendingState { public static class TransactionSortedSet extends TreeSet { public TransactionSortedSet() { - super(new Comparator() { - - @Override - public int compare(Transaction tx1, Transaction tx2) { - long nonceDiff = ByteUtil.byteArrayToLong(tx1.getNonce()) - - ByteUtil.byteArrayToLong(tx2.getNonce()); - if (nonceDiff != 0) { - return nonceDiff > 0 ? 1 : -1; - } - return FastByteComparisons.compareTo(tx1.getHash(), 0, 32, tx2.getHash(), 0, 32); + super((tx1, tx2) -> { + long nonceDiff = ByteUtil.byteArrayToLong(tx1.getNonce()) - + ByteUtil.byteArrayToLong(tx2.getNonce()); + if (nonceDiff != 0) { + return nonceDiff > 0 ? 1 : -1; } + return FastByteComparisons.compareTo(tx1.getHash(), 0, 32, tx2.getHash(), 0, 32); }); } } @@ -399,10 +395,15 @@ private void updateState(Block block) { pendingState = getOrigRepository().startTracking(); + long t = System.nanoTime(); + for (PendingTransaction tx : pendingTransactions) { TransactionReceipt receipt = executeTx(tx.getTransaction()); fireTxUpdate(receipt, PENDING, block); } + + logger.debug("Successfully processed #{}, txs: {}, time: {}s", block.getNumber(), pendingTransactions.size(), + String.format("%.3f", (System.nanoTime() - t) / 1_000_000_000d)); } private TransactionReceipt executeTx(Transaction tx) { diff --git a/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java b/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java index 1b55649174..6789ffac0e 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java @@ -18,6 +18,7 @@ package org.ethereum.core; import static org.apache.commons.lang3.ArrayUtils.isEmpty; +import static org.ethereum.datasource.MemSizeEstimator.ByteArrayEstimator; import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY; import static org.ethereum.util.ByteUtil.ZERO_BYTE_ARRAY; @@ -30,6 +31,7 @@ import org.ethereum.crypto.ECKey.ECDSASignature; import org.ethereum.crypto.ECKey.MissingPrivateKeyException; import org.ethereum.crypto.HashUtil; +import org.ethereum.datasource.MemSizeEstimator; import org.ethereum.util.ByteUtil; import org.ethereum.util.RLP; import org.ethereum.util.RLPElement; @@ -100,7 +102,7 @@ public class Transaction { /* Tx in encoded form */ protected byte[] rlpEncoded; - private byte[] rlpRaw; + private byte[] rawHash; /* Indicates if this transaction has been parsed * from the RLP-encoded data */ protected boolean parsed = false; @@ -258,8 +260,9 @@ public byte[] getHash() { public byte[] getRawHash() { rlpParse(); + if (rawHash != null) return rawHash; byte[] plainMsg = this.getEncodedRaw(); - return HashUtil.sha3(plainMsg); + return rawHash = HashUtil.sha3(plainMsg); } @@ -438,7 +441,7 @@ public String toString(int maxDataSize) { public byte[] getEncodedRaw() { rlpParse(); - if (rlpRaw != null) return rlpRaw; + byte[] rlpRaw; // parse null as 0 for nonce byte[] nonce = null; @@ -568,4 +571,18 @@ public static Transaction create(String to, BigInteger amount, BigInteger nonce, null, chainId); } + + public static final MemSizeEstimator MemEstimator = tx -> + ByteArrayEstimator.estimateSize(tx.hash) + + ByteArrayEstimator.estimateSize(tx.nonce) + + ByteArrayEstimator.estimateSize(tx.value) + + ByteArrayEstimator.estimateSize(tx.gasPrice) + + ByteArrayEstimator.estimateSize(tx.gasLimit) + + ByteArrayEstimator.estimateSize(tx.data) + + ByteArrayEstimator.estimateSize(tx.sendAddress) + + ByteArrayEstimator.estimateSize(tx.rlpEncoded) + + ByteArrayEstimator.estimateSize(tx.rawHash) + + (tx.chainId != null ? 24 : 0) + + (tx.signature != null ? 208 : 0) + // approximate size of signature + 16; // Object header + ref } diff --git a/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutionSummary.java b/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutionSummary.java index 420b724836..07db98ffef 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutionSummary.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutionSummary.java @@ -17,6 +17,7 @@ */ package org.ethereum.core; +import org.ethereum.util.ByteUtil; import org.ethereum.util.RLP; import org.ethereum.util.RLPElement; import org.ethereum.util.RLPList; @@ -29,7 +30,6 @@ import java.util.*; import static java.util.Collections.*; -import static org.apache.commons.lang3.ArrayUtils.isEmpty; import static org.apache.commons.lang3.ArrayUtils.isNotEmpty; import static org.ethereum.util.BIUtil.toBI; @@ -93,7 +93,7 @@ public void rlpParse() { } private static BigInteger decodeBigInteger(byte[] encoded) { - return isEmpty(encoded) ? BigInteger.ZERO : new BigInteger(1, encoded); + return ByteUtil.bytesToBigInteger(encoded); } public byte[] getEncoded() { diff --git a/ethereumj-core/src/main/java/org/ethereum/core/TransactionReceipt.java b/ethereumj-core/src/main/java/org/ethereum/core/TransactionReceipt.java index b7f9256180..27e9247378 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/TransactionReceipt.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/TransactionReceipt.java @@ -17,7 +17,12 @@ */ package org.ethereum.core; -import org.ethereum.util.*; +import org.ethereum.datasource.MemSizeEstimator; +import org.ethereum.util.ByteUtil; +import org.ethereum.util.RLP; +import org.ethereum.util.RLPElement; +import org.ethereum.util.RLPItem; +import org.ethereum.util.RLPList; import org.ethereum.vm.LogInfo; import org.spongycastle.util.BigIntegers; import org.spongycastle.util.encoders.Hex; @@ -28,6 +33,7 @@ import java.util.List; import static org.apache.commons.lang3.ArrayUtils.nullToEmpty; +import static org.ethereum.datasource.MemSizeEstimator.ByteArrayEstimator; import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY; /** @@ -282,4 +288,23 @@ public String toString() { ']'; } + public long estimateMemSize() { + return MemEstimator.estimateSize(this); + } + + public static final MemSizeEstimator MemEstimator = receipt -> { + if (receipt == null) { + return 0; + } + long logSize = receipt.logInfoList.stream().mapToLong(LogInfo.MemEstimator::estimateSize).sum() + 16; + return (receipt.transaction == null ? 0 : Transaction.MemEstimator.estimateSize(receipt.transaction)) + + (receipt.postTxState == EMPTY_BYTE_ARRAY ? 0 : ByteArrayEstimator.estimateSize(receipt.postTxState)) + + (receipt.cumulativeGas == EMPTY_BYTE_ARRAY ? 0 : ByteArrayEstimator.estimateSize(receipt.cumulativeGas)) + + (receipt.gasUsed == EMPTY_BYTE_ARRAY ? 0 : ByteArrayEstimator.estimateSize(receipt.gasUsed)) + + (receipt.executionResult == EMPTY_BYTE_ARRAY ? 0 : ByteArrayEstimator.estimateSize(receipt.executionResult)) + + ByteArrayEstimator.estimateSize(receipt.rlpEncoded) + + Bloom.MEM_SIZE + + receipt.error.getBytes().length + 40 + + logSize; + }; } diff --git a/ethereumj-core/src/main/java/org/ethereum/core/TransactionTouchedStorage.java b/ethereumj-core/src/main/java/org/ethereum/core/TransactionTouchedStorage.java index cd31220f66..98e0787ee5 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/TransactionTouchedStorage.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/TransactionTouchedStorage.java @@ -21,12 +21,12 @@ import com.fasterxml.jackson.annotation.JsonValue; import org.apache.commons.collections4.MapUtils; import org.apache.commons.collections4.keyvalue.AbstractKeyValue; -import org.ethereum.util.Functional; import org.ethereum.vm.DataWord; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.function.Function; public class TransactionTouchedStorage { @@ -103,7 +103,7 @@ void addWriting(Map entries) { } } - private Map keyValues(Functional.Function filter) { + private Map keyValues(Function filter) { Map result = new HashMap<>(); for (Entry entry : getEntries()) { if (filter == null || filter.apply(entry)) { @@ -115,21 +115,11 @@ private Map keyValues(Functional.Function fi } public Map getChanged() { - return keyValues(new Functional.Function() { - @Override - public Boolean apply(Entry entry) { - return entry.isChanged(); - } - }); + return keyValues(Entry::isChanged); } public Map getReadOnly() { - return keyValues(new Functional.Function() { - @Override - public Boolean apply(Entry entry) { - return !entry.isChanged(); - } - }); + return keyValues(entry -> !entry.isChanged()); } public Map getAll() { diff --git a/ethereumj-core/src/main/java/org/ethereum/crypto/HashUtil.java b/ethereumj-core/src/main/java/org/ethereum/crypto/HashUtil.java index a1ba76940b..3b46e1d680 100644 --- a/ethereumj-core/src/main/java/org/ethereum/crypto/HashUtil.java +++ b/ethereumj-core/src/main/java/org/ethereum/crypto/HashUtil.java @@ -49,20 +49,12 @@ public class HashUtil { private static final String HASH_256_ALGORITHM_NAME; private static final String HASH_512_ALGORITHM_NAME; - private static final MessageDigest sha256digest; - static { SystemProperties props = SystemProperties.getDefault(); Security.addProvider(SpongyCastleProvider.getInstance()); CRYPTO_PROVIDER = Security.getProvider(props.getCryptoProviderName()); HASH_256_ALGORITHM_NAME = props.getHash256AlgName(); HASH_512_ALGORITHM_NAME = props.getHash512AlgName(); - try { - sha256digest = MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - LOG.error("Can't initialize HashUtils", e); - throw new RuntimeException(e); // Can't happen. - } EMPTY_DATA_HASH = sha3(EMPTY_BYTE_ARRAY); EMPTY_LIST_HASH = sha3(RLP.encodeList()); EMPTY_TRIE_HASH = sha3(RLP.encodeElement(EMPTY_BYTE_ARRAY)); @@ -74,7 +66,13 @@ public class HashUtil { * @return - sha256 hash of the data */ public static byte[] sha256(byte[] input) { - return sha256digest.digest(input); + try { + MessageDigest sha256digest = MessageDigest.getInstance("SHA-256"); + return sha256digest.digest(input); + } catch (NoSuchAlgorithmException e) { + LOG.error("Can't find such algorithm", e); + throw new RuntimeException(e); + } } public static byte[] sha3(byte[] input) { @@ -209,11 +207,15 @@ public static byte[] doubleDigest(byte[] input) { * @return - */ public static byte[] doubleDigest(byte[] input, int offset, int length) { - synchronized (sha256digest) { + try { + MessageDigest sha256digest = MessageDigest.getInstance("SHA-256"); sha256digest.reset(); sha256digest.update(input, offset, length); byte[] first = sha256digest.digest(); return sha256digest.digest(first); + } catch (NoSuchAlgorithmException e) { + LOG.error("Can't find such algorithm", e); + throw new RuntimeException(e); } } diff --git a/ethereumj-core/src/main/java/org/ethereum/datasource/AsyncWriteCache.java b/ethereumj-core/src/main/java/org/ethereum/datasource/AsyncWriteCache.java index da4ea818a9..02af2637ca 100644 --- a/ethereumj-core/src/main/java/org/ethereum/datasource/AsyncWriteCache.java +++ b/ethereumj-core/src/main/java/org/ethereum/datasource/AsyncWriteCache.java @@ -126,15 +126,12 @@ public synchronized void flipStorage() throws InterruptedException { public synchronized ListenableFuture flushAsync() throws InterruptedException { logger.debug("AsyncWriteCache (" + name + "): flush submitted"); - lastFlush = flushExecutor.submit(new Callable() { - @Override - public Boolean call() { - logger.debug("AsyncWriteCache (" + name + "): flush started"); - long s = System.currentTimeMillis(); - boolean ret = flushingCache.flush(); - logger.debug("AsyncWriteCache (" + name + "): flush completed in " + (System.currentTimeMillis() - s) + " ms"); - return ret; - } + lastFlush = flushExecutor.submit(() -> { + logger.debug("AsyncWriteCache (" + name + "): flush started"); + long s = System.currentTimeMillis(); + boolean ret = flushingCache.flush(); + logger.debug("AsyncWriteCache (" + name + "): flush completed in " + (System.currentTimeMillis() - s) + " ms"); + return ret; }); return lastFlush; } diff --git a/ethereumj-core/src/main/java/org/ethereum/datasource/CountingQuotientFilter.java b/ethereumj-core/src/main/java/org/ethereum/datasource/CountingQuotientFilter.java new file mode 100644 index 0000000000..2a61430425 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/datasource/CountingQuotientFilter.java @@ -0,0 +1,98 @@ +package org.ethereum.datasource; + +import java.util.HashMap; +import java.util.Map; + +/** + * Supplies {@link QuotientFilter} with collisions counter map. + * + *

+ * Hence it can handle any number of hard and/or soft collisions without performance lack. + * While {@link QuotientFilter} experiencing performance problem when collision number tends to 10_000. + * + * @author Mikhail Kalinin + * @since 14.02.2018 + */ +public class CountingQuotientFilter extends QuotientFilter { + + long FINGERPRINT_MASK; + + private Map counters = new HashMap<>(); + + private CountingQuotientFilter(int quotientBits, int remainderBits) { + super(quotientBits, remainderBits); + this.FINGERPRINT_MASK = LOW_MASK(QUOTIENT_BITS + REMAINDER_BITS); + } + + public static CountingQuotientFilter create(long largestNumberOfElements, long startingElements) { + QuotientFilter filter = QuotientFilter.create(largestNumberOfElements, startingElements); + return new CountingQuotientFilter(filter.QUOTIENT_BITS, filter.REMAINDER_BITS); + } + + @Override + public synchronized void insert(long hash) { + if (super.maybeContains(hash)) { + addRef(hash); + } else { + super.insert(hash); + } + } + + @Override + public synchronized void remove(long hash) { + if (super.maybeContains(hash) && delRef(hash) < 0) { + super.remove(hash); + } + } + + @Override + protected long hash(byte[] bytes) { + long hash = 1; + for (byte b : bytes) { + hash = 31 * hash + b; + } + return hash; + } + + public synchronized int getCollisionNumber() { + return counters.size(); + } + + public long getEntryNumber() { + return entries; + } + + public long getMaxInsertions() { + return MAX_INSERTIONS; + } + + private void addRef(long hash) { + long fp = fingerprint(hash); + Counter cnt = counters.get(fp); + if (cnt == null) { + counters.put(fp, new Counter()); + } else { + cnt.refs++; + } + } + + private int delRef(long hash) { + long fp = fingerprint(hash); + Counter cnt = counters.get(fp); + if (cnt == null) { + return -1; + } + if (--cnt.refs < 1) { + counters.remove(fp); + } + return cnt.refs; + } + + private long fingerprint(long hash) { + return hash & FINGERPRINT_MASK; + } + + private static class Counter { + int refs = 1; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/datasource/DbSource.java b/ethereumj-core/src/main/java/org/ethereum/datasource/DbSource.java index 5dbd411883..28c297b4bb 100644 --- a/ethereumj-core/src/main/java/org/ethereum/datasource/DbSource.java +++ b/ethereumj-core/src/main/java/org/ethereum/datasource/DbSource.java @@ -55,4 +55,20 @@ public interface DbSource extends BatchSource { * @throws RuntimeException if the method is not supported */ Set keys() throws RuntimeException; + + /** + * Closes database, destroys its data and finally runs init() + */ + void reset(); + + /** + * If supported, retrieves a value using a key prefix. + * Prefix extraction is meant to be done on the implementing side.
+ * + * @param key a key for the lookup + * @param prefixBytes prefix length in bytes + * @return first value picked by prefix lookup over DB or null if there is no match + * @throws RuntimeException if operation is not supported + */ + V prefixLookup(byte[] key, int prefixBytes); } diff --git a/ethereumj-core/src/main/java/org/ethereum/datasource/JournalSource.java b/ethereumj-core/src/main/java/org/ethereum/datasource/JournalSource.java index ad6783699e..3c62b6f158 100644 --- a/ethereumj-core/src/main/java/org/ethereum/datasource/JournalSource.java +++ b/ethereumj-core/src/main/java/org/ethereum/datasource/JournalSource.java @@ -18,10 +18,10 @@ package org.ethereum.datasource; import org.ethereum.datasource.inmem.HashMapDB; +import org.ethereum.db.prune.Pruner; import org.ethereum.util.RLP; import org.ethereum.util.RLPElement; import org.ethereum.util.RLPList; -import org.spongycastle.util.encoders.Hex; import java.util.ArrayList; import java.util.List; @@ -29,28 +29,23 @@ /** * The JournalSource records all the changes which were made before each commitUpdate * Unlike 'put' deletes are not propagated to the backing Source immediately but are - * delayed until 'persistUpdate' is called for the corresponding hash. - * Also 'revertUpdate' might be called for a hash, in this case all inserts are removed - * from the database. + * delayed until {@link Pruner} accepts and persists changes for the corresponding hash. * - * Normally this class is used for State pruning: we need all the state nodes for last N + * Normally this class is used together with State pruning: we need all the state nodes for last N * blocks to be able to get back to previous state for applying fork block * however we would like to delete 'zombie' nodes which are not referenced anymore by - * calling 'persistUpdate' for the block CurrentBlockNumber - N and we would + * persisting update for the block CurrentBlockNumber - N and we would * also like to remove the updates made by the blocks which weren't too lucky - * to remain on the main chain by calling revertUpdate for such blocks + * to remain on the main chain by reverting update for such blocks * - * NOTE: the backing Source should be counting for this class to work correctly - * if e.g. some key is deleted in block 100 then added in block 200 - * then pruning of the block 100 would delete this key from the backing store - * if it was non-counting + * @see Pruner * * Created by Anton Nashatyrev on 08.11.2016. */ public class JournalSource extends AbstractChainedSource implements HashedKeySource { - private static class Update { + public static class Update { byte[] updateHash; List insertedKeys = new ArrayList<>(); List deletedKeys = new ArrayList<>(); @@ -86,6 +81,14 @@ private void parse(byte[] encoded) { deletedKeys.add(aRDeleted.getRLPData()); } } + + public List getInsertedKeys() { + return insertedKeys; + } + + public List getDeletedKeys() { + return deletedKeys; + } } private Update currentUpdate = new Update(); @@ -94,8 +97,6 @@ private void parse(byte[] encoded) { /** * Constructs instance with the underlying backing Source - * @param src the Source must implement counting semantics - * see e.g. {@link CountingBytesSource} or {@link WriteCache.CacheType#COUNTING} */ public JournalSource(Source src) { super(src); @@ -112,7 +113,7 @@ public void setJournalStore(Source journalSource) { /** * Inserts are immediately propagated to the backing Source * though are still recorded to the current update - * The insert might later be reverted due to revertUpdate call + * The insert might later be reverted by {@link Pruner} */ @Override public synchronized void put(byte[] key, V val) { @@ -121,14 +122,14 @@ public synchronized void put(byte[] key, V val) { return; } - currentUpdate.insertedKeys.add(key); getSource().put(key, val); + currentUpdate.insertedKeys.add(key); } /** * Deletes are not propagated to the backing Source immediately * but instead they are recorded to the current Update and - * might be later persisted with persistUpdate call + * might be later persisted */ @Override public synchronized void delete(byte[] key) { @@ -144,45 +145,18 @@ public synchronized V get(byte[] key) { * Records all the changes made prior to this call to a single chunk * with supplied hash. * Later those updates could be either persisted to backing Source (deletes only) - * via persistUpdate call * or reverted from the backing Source (inserts only) - * via revertUpdate call */ - public synchronized void commitUpdates(byte[] updateHash) { + public synchronized Update commitUpdates(byte[] updateHash) { currentUpdate.updateHash = updateHash; journal.put(updateHash, currentUpdate); + Update committed = currentUpdate; currentUpdate = new Update(); + return committed; } - /** - * Checks if the update with this hash key exists - */ - public synchronized boolean hasUpdate(byte[] updateHash) { - return journal.get(updateHash) != null; - } - - /** - * Persists all deletes to the backing store made under this hash key - */ - public synchronized void persistUpdate(byte[] updateHash) { - Update update = journal.get(updateHash); - if (update == null) throw new RuntimeException("No update found: " + Hex.toHexString(updateHash)); - for (byte[] key : update.deletedKeys) { - getSource().delete(key); - } - journal.delete(updateHash); - } - - /** - * Deletes all inserts to the backing store made under this hash key - */ - public synchronized void revertUpdate(byte[] updateHash) { - Update update = journal.get(updateHash); - if (update == null) throw new RuntimeException("No update found: " + Hex.toHexString(updateHash)); - for (byte[] key : update.insertedKeys) { - getSource().delete(key); - } - journal.delete(updateHash); + public Source getJournal() { + return journal; } @Override diff --git a/ethereumj-core/src/main/java/org/ethereum/datasource/MemSizeEstimator.java b/ethereumj-core/src/main/java/org/ethereum/datasource/MemSizeEstimator.java index 94f947bcd9..0434bdb3e9 100644 --- a/ethereumj-core/src/main/java/org/ethereum/datasource/MemSizeEstimator.java +++ b/ethereumj-core/src/main/java/org/ethereum/datasource/MemSizeEstimator.java @@ -29,10 +29,9 @@ public interface MemSizeEstimator { /** * byte[] type size estimator */ - MemSizeEstimator ByteArrayEstimator = new MemSizeEstimator() { - @Override - public long estimateSize(byte[] bytes) { - return bytes == null ? 0 : bytes.length + 4; // 4 - compressed ref size - } + MemSizeEstimator ByteArrayEstimator = bytes -> { + return bytes == null ? 0 : bytes.length + 16; // 4 - compressed ref size, 12 - Object header }; + + } diff --git a/ethereumj-core/src/main/java/org/ethereum/datasource/NodeKeyCompositor.java b/ethereumj-core/src/main/java/org/ethereum/datasource/NodeKeyCompositor.java new file mode 100644 index 0000000000..5f0dc5bcf9 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/datasource/NodeKeyCompositor.java @@ -0,0 +1,71 @@ +package org.ethereum.datasource; + +import org.ethereum.config.CommonConfig; +import org.ethereum.db.RepositoryRoot; + +import static java.lang.System.arraycopy; +import static org.ethereum.crypto.HashUtil.sha3; + +/** + * Composes keys for contract storage nodes.

+ * + * Input: 32-bytes node key, 20-bytes contract address
+ * Output: 32-bytes composed key [first 16-bytes of node key : first 16-bytes of address hash]

+ * + * Example:
+ * Contract address hash a9539c810cc2e8fa20785bdd78ec36ccb25e1b5be78dbadf6c4e817c6d170bbb
+ * Key of one of the storage nodes bbbbbb5be78dbadf6c4e817c6d170bbb47e9916f8f6cc4607c5f3819ce98497b
+ * Composed key will be a9539c810cc2e8fa20785bdd78ec36ccbbbbbb5be78dbadf6c4e817c6d170bbb

+ * + * This mechanism is a part of flat storage source which is free from reference counting + * + * @see CommonConfig#trieNodeSource() + * @see RepositoryRoot#RepositoryRoot(Source, byte[]) + * + * @author Mikhail Kalinin + * @since 05.12.2017 + */ +public class NodeKeyCompositor implements Serializer { + + public static final int HASH_LEN = 32; + public static final int PREFIX_BYTES = 16; + private byte[] addrHash; + + public NodeKeyCompositor(byte[] addrOrHash) { + this.addrHash = addrHash(addrOrHash); + } + + @Override + public byte[] serialize(byte[] key) { + return composeInner(key, addrHash); + } + + @Override + public byte[] deserialize(byte[] stream) { + return stream; + } + + public static byte[] compose(byte[] key, byte[] addrOrHash) { + return composeInner(key, addrHash(addrOrHash)); + } + + private static byte[] composeInner(byte[] key, byte[] addrHash) { + + validateKey(key); + + byte[] derivative = new byte[key.length]; + arraycopy(key, 0, derivative, 0, PREFIX_BYTES); + arraycopy(addrHash, 0, derivative, PREFIX_BYTES, PREFIX_BYTES); + + return derivative; + } + + private static void validateKey(byte[] key) { + if (key.length != HASH_LEN) + throw new IllegalArgumentException("Key is not a hash code"); + } + + private static byte[] addrHash(byte[] addrOrHash) { + return addrOrHash.length == HASH_LEN ? addrOrHash : sha3(addrOrHash); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/datasource/PrefixLookupSource.java b/ethereumj-core/src/main/java/org/ethereum/datasource/PrefixLookupSource.java new file mode 100644 index 0000000000..256c4e2cfc --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/datasource/PrefixLookupSource.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.datasource; + +/** + * A kind of source which executes {@link #get(byte[])} query as + * a {@link DbSource#prefixLookup(byte[], int)} query of backing source.
+ * + * Other operations are simply propagated to backing {@link DbSource}. + * + * @author Mikhail Kalinin + * @since 01.12.2017 + */ +public class PrefixLookupSource implements Source { + + // prefix length in bytes + private int prefixBytes; + private DbSource source; + + public PrefixLookupSource(DbSource source, int prefixBytes) { + this.source = source; + this.prefixBytes = prefixBytes; + } + + @Override + public V get(byte[] key) { + return source.prefixLookup(key, prefixBytes); + } + + @Override + public void put(byte[] key, V val) { + source.put(key, val); + } + + @Override + public void delete(byte[] key) { + source.delete(key); + } + + @Override + public boolean flush() { + return source.flush(); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/datasource/QuotientFilter.java b/ethereumj-core/src/main/java/org/ethereum/datasource/QuotientFilter.java index e0019e08c0..54fa2b539a 100644 --- a/ethereumj-core/src/main/java/org/ethereum/datasource/QuotientFilter.java +++ b/ethereumj-core/src/main/java/org/ethereum/datasource/QuotientFilter.java @@ -135,7 +135,7 @@ static long LOW_MASK(long n) { } static int TABLE_SIZE(int quotientBits, int remainderBits) { - long bits = (1 << quotientBits) * (remainderBits + 3); + long bits = (1L << quotientBits) * (remainderBits + 3); long longs = bits / 64; return Ints.checkedCast((bits % 64) > 0 ? (longs + 1) : longs); } @@ -192,7 +192,7 @@ public QuotientFilter(int quotientBits, int remainderBits) { INDEX_MASK = LOW_MASK(QUOTIENT_BITS); REMAINDER_MASK = LOW_MASK(REMAINDER_BITS); ELEMENT_MASK = LOW_MASK(ELEMENT_BITS); - MAX_SIZE = 1 << QUOTIENT_BITS; + MAX_SIZE = 1L << QUOTIENT_BITS; MAX_INSERTIONS = (long) (MAX_SIZE * .75); table = new long[TABLE_SIZE(QUOTIENT_BITS, REMAINDER_BITS)]; entries = 0; @@ -364,7 +364,7 @@ public boolean overflowed() { // insert(hashFactory.hash64().hash(data, offset, length, 0)); // } - private long hash(byte[] bytes) { + protected long hash(byte[] bytes) { return (bytes[0] & 0xFFL) << 56 | (bytes[1] & 0xFFL) << 48 | (bytes[2] & 0xFFL) << 40 | diff --git a/ethereumj-core/src/main/java/org/ethereum/datasource/SourceCodec.java b/ethereumj-core/src/main/java/org/ethereum/datasource/SourceCodec.java index 34527e41e2..f21dc4ca16 100644 --- a/ethereumj-core/src/main/java/org/ethereum/datasource/SourceCodec.java +++ b/ethereumj-core/src/main/java/org/ethereum/datasource/SourceCodec.java @@ -72,6 +72,15 @@ public ValueOnly(Source src, Serializer va } } + /** + * Shortcut class when only key conversion is required + */ + public static class KeyOnly extends SourceCodec { + public KeyOnly(Source src, Serializer keySerializer) { + super(src, keySerializer, new Serializers.Identity()); + } + } + /** * Shortcut class when only value conversion is required and keys are of byte[] type */ diff --git a/ethereumj-core/src/main/java/org/ethereum/datasource/inmem/HashMapDB.java b/ethereumj-core/src/main/java/org/ethereum/datasource/inmem/HashMapDB.java index 088605cdf0..5aeeaa5b86 100644 --- a/ethereumj-core/src/main/java/org/ethereum/datasource/inmem/HashMapDB.java +++ b/ethereumj-core/src/main/java/org/ethereum/datasource/inmem/HashMapDB.java @@ -20,6 +20,7 @@ import org.ethereum.datasource.DbSource; import org.ethereum.util.ALock; import org.ethereum.util.ByteArrayMap; +import org.ethereum.util.FastByteComparisons; import java.util.Map; import java.util.Set; @@ -102,6 +103,25 @@ public Set keys() { } } + @Override + public void reset() { + try (ALock l = writeLock.lock()) { + storage.clear(); + } + } + + @Override + public V prefixLookup(byte[] key, int prefixBytes) { + try (ALock l = readLock.lock()) { + for (Map.Entry e : storage.entrySet()) + if (FastByteComparisons.compareTo(key, 0, prefixBytes, e.getKey(), 0, prefixBytes) == 0) { + return e.getValue(); + } + + return null; + } + } + @Override public void updateBatch(Map rows) { try (ALock l = writeLock.lock()) { diff --git a/ethereumj-core/src/main/java/org/ethereum/datasource/inmem/HashMapDBSimple.java b/ethereumj-core/src/main/java/org/ethereum/datasource/inmem/HashMapDBSimple.java index 8ac544d7ed..44e667248e 100644 --- a/ethereumj-core/src/main/java/org/ethereum/datasource/inmem/HashMapDBSimple.java +++ b/ethereumj-core/src/main/java/org/ethereum/datasource/inmem/HashMapDBSimple.java @@ -20,6 +20,7 @@ import org.ethereum.datasource.DbSource; import org.ethereum.util.ALock; import org.ethereum.util.ByteArrayMap; +import org.ethereum.util.FastByteComparisons; import java.util.Map; import java.util.Set; @@ -89,6 +90,22 @@ public Set keys() { return getStorage().keySet(); } + @Override + public void reset() { + storage.clear(); + } + + @Override + public V prefixLookup(byte[] key, int prefixBytes) { + + for (Map.Entry e : storage.entrySet()) + if (FastByteComparisons.compareTo(key, 0, prefixBytes, e.getKey(), 0, prefixBytes) == 0) { + return e.getValue(); + } + + return null; + } + @Override public void updateBatch(Map rows) { for (Map.Entry entry : rows.entrySet()) { diff --git a/ethereumj-core/src/main/java/org/ethereum/datasource/leveldb/LevelDbDataSource.java b/ethereumj-core/src/main/java/org/ethereum/datasource/leveldb/LevelDbDataSource.java index 0494c00f1c..1c96296298 100644 --- a/ethereumj-core/src/main/java/org/ethereum/datasource/leveldb/LevelDbDataSource.java +++ b/ethereumj-core/src/main/java/org/ethereum/datasource/leveldb/LevelDbDataSource.java @@ -131,12 +131,18 @@ private Path getPath() { return Paths.get(config.databaseDir(), name); } + @Override public void reset() { close(); FileUtil.recursiveDelete(getPath().toString()); init(); } + @Override + public byte[] prefixLookup(byte[] key, int prefixBytes) { + throw new RuntimeException("LevelDbDataSource.prefixLookup() is not supported"); + } + @Override public boolean isAlive() { return alive; diff --git a/ethereumj-core/src/main/java/org/ethereum/datasource/rocksdb/RocksDbDataSource.java b/ethereumj-core/src/main/java/org/ethereum/datasource/rocksdb/RocksDbDataSource.java new file mode 100644 index 0000000000..fb0c7289d6 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/datasource/rocksdb/RocksDbDataSource.java @@ -0,0 +1,368 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.datasource.rocksdb; + +import org.ethereum.config.SystemProperties; +import org.ethereum.datasource.DbSource; +import org.ethereum.datasource.NodeKeyCompositor; +import org.ethereum.util.FileUtil; +import org.rocksdb.*; +import org.rocksdb.CompressionType; +import org.rocksdb.Options; +import org.rocksdb.WriteBatch; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; +import org.springframework.beans.factory.annotation.Autowired; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import static java.lang.System.arraycopy; + +/** + * @author Mikhail Kalinin + * @since 28.11.2017 + */ +public class RocksDbDataSource implements DbSource { + + private static final Logger logger = LoggerFactory.getLogger("db"); + + @Autowired + SystemProperties config = SystemProperties.getDefault(); // initialized for standalone test + + String name; + RocksDB db; + ReadOptions readOpts; + boolean alive; + + // The native RocksDB insert/update/delete are normally thread-safe + // However close operation is not thread-safe. + // This ReadWriteLock still permits concurrent execution of insert/delete/update operations + // however blocks them on init/close/delete operations + private ReadWriteLock resetDbLock = new ReentrantReadWriteLock(); + + static { + RocksDB.loadLibrary(); + } + + public RocksDbDataSource() { + } + + public RocksDbDataSource(String name) { + this.name = name; + logger.debug("New RocksDbDataSource: " + name); + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public void init() { + resetDbLock.writeLock().lock(); + try { + logger.debug("~> RocksDbDataSource.init(): " + name); + + if (isAlive()) return; + + if (name == null) throw new NullPointerException("no name set to the db"); + + try (Options options = new Options()) { + + // most of these options are suggested by https://github.com/facebook/rocksdb/wiki/Set-Up-Options + + // general options + options.setCreateIfMissing(true); + options.setCompressionType(CompressionType.LZ4_COMPRESSION); + options.setBottommostCompressionType(CompressionType.ZSTD_COMPRESSION); + options.setLevelCompactionDynamicLevelBytes(true); + options.setMaxBackgroundCompactions(4); + options.setMaxBackgroundFlushes(2); + options.setMaxOpenFiles(32); + + // key prefix for state node lookups + options.useFixedLengthPrefixExtractor(NodeKeyCompositor.PREFIX_BYTES); + + // table options + final BlockBasedTableConfig tableCfg; + options.setTableFormatConfig(tableCfg = new BlockBasedTableConfig()); + tableCfg.setBlockSize(16 * 1024); + tableCfg.setBlockCacheSize(32 * 1024 * 1024); + tableCfg.setCacheIndexAndFilterBlocks(true); + tableCfg.setPinL0FilterAndIndexBlocksInCache(true); + tableCfg.setFilter(new BloomFilter(10, false)); + + // read options + readOpts = new ReadOptions().setPrefixSameAsStart(true) + .setVerifyChecksums(false); + + try { + logger.debug("Opening database"); + final Path dbPath = getPath(); + if (!Files.isSymbolicLink(dbPath.getParent())) Files.createDirectories(dbPath.getParent()); + + if (config.databaseFromBackup() && backupPath().toFile().canWrite()) { + logger.debug("Restoring database from backup: '{}'", name); + try (BackupableDBOptions backupOptions = new BackupableDBOptions(backupPath().toString()); + RestoreOptions restoreOptions = new RestoreOptions(false); + BackupEngine backups = BackupEngine.open(Env.getDefault(), backupOptions)) { + + if (!backups.getBackupInfo().isEmpty()) { + backups.restoreDbFromLatestBackup(getPath().toString(), getPath().toString(), + restoreOptions); + } + + } catch (RocksDBException e) { + logger.error("Failed to restore database '{}' from backup", name, e); + } + } + + logger.debug("Initializing new or existing database: '{}'", name); + try { + db = RocksDB.open(options, dbPath.toString()); + } catch (RocksDBException e) { + logger.error(e.getMessage(), e); + throw new RuntimeException("Failed to initialize database", e); + } + + alive = true; + + } catch (IOException ioe) { + logger.error(ioe.getMessage(), ioe); + throw new RuntimeException("Failed to initialize database", ioe); + } + + logger.debug("<~ RocksDbDataSource.init(): " + name); + } + } finally { + resetDbLock.writeLock().unlock(); + } + } + + public void backup() { + resetDbLock.readLock().lock(); + if (logger.isTraceEnabled()) logger.trace("~> RocksDbDataSource.backup(): " + name); + Path path = backupPath(); + path.toFile().mkdirs(); + try (BackupableDBOptions backupOptions = new BackupableDBOptions(path.toString()); + BackupEngine backups = BackupEngine.open(Env.getDefault(), backupOptions)) { + + backups.createNewBackup(db, true); + + if (logger.isTraceEnabled()) logger.trace("<~ RocksDbDataSource.backup(): " + name + " done"); + } catch (RocksDBException e) { + logger.error("Failed to backup database '{}'", name, e); + throw new RuntimeException(e); + } finally { + resetDbLock.readLock().unlock(); + } + } + + private Path backupPath() { + return Paths.get(config.databaseDir(), "backup", name); + } + + @Override + public boolean isAlive() { + return alive; + } + + @Override + public void close() { + resetDbLock.writeLock().lock(); + try { + if (!isAlive()) return; + + logger.debug("Close db: {}", name); + db.close(); + + alive = false; + + } catch (Exception e) { + logger.error("Error closing db '{}'", name, e); + } finally { + resetDbLock.writeLock().unlock(); + } + } + + @Override + public Set keys() throws RuntimeException { + resetDbLock.readLock().lock(); + try { + if (logger.isTraceEnabled()) logger.trace("~> RocksDbDataSource.keys(): " + name); + try (RocksIterator iterator = db.newIterator()) { + Set result = new HashSet<>(); + for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) { + result.add(iterator.key()); + } + if (logger.isTraceEnabled()) logger.trace("<~ RocksDbDataSource.keys(): " + name + ", " + result.size()); + return result; + } catch (Exception e) { + logger.error("Error iterating db '{}'", name, e); + throw new RuntimeException(e); + } + } finally { + resetDbLock.readLock().unlock(); + } + } + + @Override + public void reset() { + close(); + FileUtil.recursiveDelete(getPath().toString()); + init(); + } + + private Path getPath() { + return Paths.get(config.databaseDir(), name); + } + + @Override + public void updateBatch(Map rows) { + resetDbLock.readLock().lock(); + try { + if (logger.isTraceEnabled()) logger.trace("~> RocksDbDataSource.updateBatch(): " + name + ", " + rows.size()); + try { + + try (WriteBatch batch = new WriteBatch(); + WriteOptions writeOptions = new WriteOptions()) { + for (Map.Entry entry : rows.entrySet()) { + if (entry.getValue() == null) { + batch.remove(entry.getKey()); + } else { + batch.put(entry.getKey(), entry.getValue()); + } + } + db.write(writeOptions, batch); + } + + if (logger.isTraceEnabled()) logger.trace("<~ RocksDbDataSource.updateBatch(): " + name + ", " + rows.size()); + } catch (RocksDBException e) { + logger.error("Error in batch update on db '{}'", name, e); + throw new RuntimeException(e); + } + } finally { + resetDbLock.readLock().unlock(); + } + } + + @Override + public void put(byte[] key, byte[] val) { + resetDbLock.readLock().lock(); + try { + if (logger.isTraceEnabled()) logger.trace("~> RocksDbDataSource.put(): " + name + ", key: " + Hex.toHexString(key) + ", " + (val == null ? "null" : val.length)); + if (val != null) { + db.put(key, val); + } else { + db.delete(key); + } + if (logger.isTraceEnabled()) logger.trace("<~ RocksDbDataSource.put(): " + name + ", key: " + Hex.toHexString(key) + ", " + (val == null ? "null" : val.length)); + } catch (RocksDBException e) { + logger.error("Failed to put into db '{}'", name, e); + throw new RuntimeException(e); + } finally { + resetDbLock.readLock().unlock(); + } + } + + @Override + public byte[] get(byte[] key) { + resetDbLock.readLock().lock(); + try { + if (logger.isTraceEnabled()) logger.trace("~> RocksDbDataSource.get(): " + name + ", key: " + Hex.toHexString(key)); + byte[] ret = db.get(readOpts, key); + if (logger.isTraceEnabled()) logger.trace("<~ RocksDbDataSource.get(): " + name + ", key: " + Hex.toHexString(key) + ", " + (ret == null ? "null" : ret.length)); + return ret; + } catch (RocksDBException e) { + logger.error("Failed to get from db '{}'", name, e); + throw new RuntimeException(e); + } finally { + resetDbLock.readLock().unlock(); + } + } + + @Override + public void delete(byte[] key) { + resetDbLock.readLock().lock(); + try { + if (logger.isTraceEnabled()) logger.trace("~> RocksDbDataSource.delete(): " + name + ", key: " + Hex.toHexString(key)); + db.delete(key); + if (logger.isTraceEnabled()) logger.trace("<~ RocksDbDataSource.delete(): " + name + ", key: " + Hex.toHexString(key)); + } catch (RocksDBException e) { + logger.error("Failed to delete from db '{}'", name, e); + throw new RuntimeException(e); + } finally { + resetDbLock.readLock().unlock(); + } + } + + @Override + public byte[] prefixLookup(byte[] key, int prefixBytes) { + + if (prefixBytes != NodeKeyCompositor.PREFIX_BYTES) + throw new RuntimeException("RocksDbDataSource.prefixLookup() supports only " + prefixBytes + "-bytes prefix"); + + resetDbLock.readLock().lock(); + try { + + if (logger.isTraceEnabled()) logger.trace("~> RocksDbDataSource.prefixLookup(): " + name + ", key: " + Hex.toHexString(key)); + + // RocksDB sets initial position of iterator to the first key which is greater or equal to the seek key + // since keys in RocksDB are ordered in asc order iterator must be initiated with the lowest key + // thus bytes with indexes greater than PREFIX_BYTES must be nullified + byte[] prefix = new byte[NodeKeyCompositor.PREFIX_BYTES]; + arraycopy(key, 0, prefix, 0, NodeKeyCompositor.PREFIX_BYTES); + + byte[] ret = null; + try (RocksIterator it = db.newIterator(readOpts)) { + + it.seek(prefix); + if (it.isValid()) + ret = it.value(); + + } catch (Exception e) { + logger.error("Failed to seek by prefix in db '{}'", name, e); + throw new RuntimeException(e); + } + + if (logger.isTraceEnabled()) logger.trace("<~ RocksDbDataSource.prefixLookup(): " + name + ", key: " + Hex.toHexString(key) + ", " + (ret == null ? "null" : ret.length)); + + return ret; + + } finally { + resetDbLock.readLock().unlock(); + } + } + + @Override + public boolean flush() { + return false; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/db/DbFlushManager.java b/ethereumj-core/src/main/java/org/ethereum/db/DbFlushManager.java index 4bbceac368..be544eb05f 100644 --- a/ethereumj-core/src/main/java/org/ethereum/db/DbFlushManager.java +++ b/ethereumj-core/src/main/java/org/ethereum/db/DbFlushManager.java @@ -23,10 +23,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.ethereum.config.CommonConfig; import org.ethereum.config.SystemProperties; -import org.ethereum.datasource.AbstractCachedSource; -import org.ethereum.datasource.AsyncFlushable; -import org.ethereum.datasource.DbSource; -import org.ethereum.datasource.WriteCache; +import org.ethereum.datasource.*; import org.ethereum.listener.CompositeEthereumListener; import org.ethereum.listener.EthereumListenerAdapter; import org.slf4j.Logger; @@ -45,7 +42,8 @@ public class DbFlushManager { private static final Logger logger = LoggerFactory.getLogger("db"); - List> writeCaches = new ArrayList<>(); + List> writeCaches = new ArrayList<>(); + List> sources = new ArrayList<>(); Set dbSources = new HashSet<>(); AbstractCachedSource stateDbCache; @@ -90,13 +88,17 @@ public void setSizeThreshold(long sizeThreshold) { this.sizeThreshold = sizeThreshold; } - public void addCache(AbstractCachedSource cache) { + public void addCache(AbstractCachedSource cache) { writeCaches.add(cache); } + public void addSource(Source src) { + sources.add(src); + } + public long getCacheSize() { long ret = 0; - for (AbstractCachedSource writeCache : writeCaches) { + for (AbstractCachedSource writeCache : writeCaches) { ret += writeCache.estimateCacheSize(); } return ret; @@ -141,7 +143,7 @@ public synchronized Future flush() { } } logger.debug("Flipping async storages"); - for (AbstractCachedSource writeCache : writeCaches) { + for (AbstractCachedSource writeCache : writeCaches) { try { if (writeCache instanceof AsyncFlushable) { ((AsyncFlushable) writeCache).flipStorage(); @@ -152,32 +154,31 @@ public synchronized Future flush() { } logger.debug("Submitting flush task"); - return lastFlush = flushThread.submit(new Callable() { - @Override - public Boolean call() throws Exception { - boolean ret = false; - long s = System.nanoTime(); - logger.info("Flush started"); - - for (AbstractCachedSource writeCache : writeCaches) { - if (writeCache instanceof AsyncFlushable) { - try { - ret |= ((AsyncFlushable) writeCache).flushAsync().get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } else { - ret |= writeCache.flush(); + return lastFlush = flushThread.submit(() -> { + boolean ret = false; + long s = System.nanoTime(); + logger.info("Flush started"); + + sources.forEach(Source::flush); + + for (AbstractCachedSource writeCache : writeCaches) { + if (writeCache instanceof AsyncFlushable) { + try { + ret |= ((AsyncFlushable) writeCache).flushAsync().get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); } + } else { + ret |= writeCache.flush(); } - if (stateDbCache != null) { - logger.debug("Flushing to DB"); - stateDbCache.flush(); - } - logger.info("Flush completed in " + (System.nanoTime() - s) / 1000000 + " ms"); - - return ret; } + if (stateDbCache != null) { + logger.debug("Flushing to DB"); + stateDbCache.flush(); + } + logger.info("Flush completed in " + (System.nanoTime() - s) / 1000000 + " ms"); + + return ret; }); } diff --git a/ethereumj-core/src/main/java/org/ethereum/db/PeerSource.java b/ethereumj-core/src/main/java/org/ethereum/db/PeerSource.java index cf94879a68..4147b48c0f 100644 --- a/ethereumj-core/src/main/java/org/ethereum/db/PeerSource.java +++ b/ethereumj-core/src/main/java/org/ethereum/db/PeerSource.java @@ -19,11 +19,12 @@ import org.apache.commons.lang3.tuple.Pair; import org.ethereum.datasource.DataSourceArray; +import org.ethereum.datasource.DbSource; import org.ethereum.datasource.ObjectDataSource; import org.ethereum.datasource.Serializer; import org.ethereum.datasource.Source; -import org.ethereum.datasource.leveldb.LevelDbDataSource; import org.ethereum.net.rlpx.Node; +import org.ethereum.util.ByteUtil; import org.ethereum.util.RLP; import org.ethereum.util.RLPList; import org.slf4j.Logger; @@ -65,11 +66,10 @@ public Pair deserialize(byte[] bytes) { Node node = new Node(nodeRlp); node.setDiscoveryNode(nodeIsDiscovery != null); - return Pair.of(node, savedReputation == null ? 0 : (new BigInteger(1, savedReputation)).intValue()); + return Pair.of(node, ByteUtil.byteArrayToInt(savedReputation)); } }; - public PeerSource(Source src) { this.src = src; INST = this; @@ -82,8 +82,8 @@ public DataSourceArray> getNodes() { } public void clear() { - if (src instanceof LevelDbDataSource) { - ((LevelDbDataSource) src).reset(); + if (src instanceof DbSource) { + ((DbSource) src).reset(); this.nodes = new DataSourceArray<>( new ObjectDataSource<>(src, NODE_SERIALIZER, 512)); } else { diff --git a/ethereumj-core/src/main/java/org/ethereum/db/PruneManager.java b/ethereumj-core/src/main/java/org/ethereum/db/PruneManager.java index 4f186afaca..6cf04dafc7 100644 --- a/ethereumj-core/src/main/java/org/ethereum/db/PruneManager.java +++ b/ethereumj-core/src/main/java/org/ethereum/db/PruneManager.java @@ -21,55 +21,136 @@ import org.ethereum.core.Block; import org.ethereum.core.BlockHeader; import org.ethereum.datasource.JournalSource; +import org.ethereum.datasource.Source; +import org.ethereum.db.prune.Segment; +import org.ethereum.db.prune.Pruner; import org.springframework.beans.factory.annotation.Autowired; +import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; /** + * Manages state pruning part of block processing. + * + *

+ * Constructs chain segments and prune them when they are complete + * * Created by Anton Nashatyrev on 10.11.2016. + * + * @see Segment + * @see Pruner */ public class PruneManager { - private JournalSource journal; + private static final int LONGEST_CHAIN = 192; + + private JournalSource journalSource; @Autowired private IndexedBlockStore blockStore; private int pruneBlocksCnt; + private Segment segment; + private Pruner pruner; + @Autowired private PruneManager(SystemProperties config) { pruneBlocksCnt = config.databasePruneDepth(); } - public PruneManager(IndexedBlockStore blockStore, JournalSource journal, int pruneBlocksCnt) { + public PruneManager(IndexedBlockStore blockStore, JournalSource journalSource, + Source pruneStorage, int pruneBlocksCnt) { this.blockStore = blockStore; - this.journal = journal; + this.journalSource = journalSource; this.pruneBlocksCnt = pruneBlocksCnt; + + if (journalSource != null && pruneStorage != null) + this.pruner = new Pruner(journalSource.getJournal(), pruneStorage); } @Autowired public void setStateSource(StateSource stateSource) { - journal = stateSource.getJournalSource(); + journalSource = stateSource.getJournalSource(); + if (journalSource != null) + pruner = new Pruner(journalSource.getJournal(), stateSource.getNoJournalSource()); } public void blockCommitted(BlockHeader block) { if (pruneBlocksCnt < 0) return; // pruning disabled - journal.commitUpdates(block.getHash()); - long pruneBlockNum = block.getNumber() - pruneBlocksCnt; - if (pruneBlockNum < 0) return; - - List pruneBlocks = blockStore.getBlocksByNumber(pruneBlockNum); - Block chainBlock = blockStore.getChainBlockByNumber(pruneBlockNum); - for (Block pruneBlock : pruneBlocks) { - if (journal.hasUpdate(pruneBlock.getHash())) { - if (chainBlock.isEqual(pruneBlock)) { - journal.persistUpdate(pruneBlock.getHash()); - } else { - journal.revertUpdate(pruneBlock.getHash()); + JournalSource.Update update = journalSource.commitUpdates(block.getHash()); + pruner.feed(update); + + long forkBlockNum = block.getNumber() - getForkBlocksCnt(); + if (forkBlockNum < 0) return; + + List pruneBlocks = blockStore.getBlocksByNumber(forkBlockNum); + Block chainBlock = blockStore.getChainBlockByNumber(forkBlockNum); + + if (segment == null) { + if (pruneBlocks.size() == 1) // wait for a single chain + segment = new Segment(chainBlock); + return; + } + + Segment.Tracker tracker = segment.startTracking(); + tracker.addMain(chainBlock); + tracker.addAll(pruneBlocks); + tracker.commit(); + + if (segment.isComplete()) { + if (!pruner.isReady()) { + List forkWindow = getAllChainsHashes(segment.getRootNumber() + 1, blockStore.getMaxNumber()); + pruner.init(forkWindow, getForkBlocksCnt()); + + int mainChainWindowSize = pruneBlocksCnt - getForkBlocksCnt(); + if (mainChainWindowSize > 0) { + List mainChainWindow = getMainChainHashes(Math.max(1, segment.getRootNumber() - mainChainWindowSize + 1), + segment.getRootNumber()); + pruner.withSecondStep(mainChainWindow, mainChainWindowSize); } } + pruner.prune(segment); + segment = new Segment(chainBlock); + } + + long mainBlockNum = block.getNumber() - getMainBlocksCnt(); + if (mainBlockNum < 0) return; + + byte[] hash = blockStore.getBlockHashByNumber(mainBlockNum); + pruner.persist(hash); + } + + private int getForkBlocksCnt() { + return Math.min(pruneBlocksCnt, 2 * LONGEST_CHAIN); + } + + private int getMainBlocksCnt() { + if (pruneBlocksCnt <= 2 * LONGEST_CHAIN) { + return Integer.MAX_VALUE; + } else { + return pruneBlocksCnt; + } + } + + private List getAllChainsHashes(long fromBlock, long toBlock) { + List ret = new ArrayList<>(); + for (long num = fromBlock; num <= toBlock; num++) { + List blocks = blockStore.getBlocksByNumber(num); + List hashes = blocks.stream().map(Block::getHash).collect(Collectors.toList()); + ret.addAll(hashes); + } + return ret; + } + + private List getMainChainHashes(long fromBlock, long toBlock) { + List ret = new ArrayList<>(); + for (long num = fromBlock; num <= toBlock; num++) { + byte[] hash = blockStore.getBlockHashByNumber(num); + ret.add(hash); } + return ret; } } 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 20e0f6d03a..4dba1e06be 100644 --- a/ethereumj-core/src/main/java/org/ethereum/db/RepositoryImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/db/RepositoryImpl.java @@ -127,7 +127,7 @@ public synchronized boolean hasContractDetails(byte[] addr) { @Override public synchronized void saveCode(byte[] addr, byte[] code) { byte[] codeHash = HashUtil.sha3(code); - codeCache.put(codeHash, code); + codeCache.put(codeKey(codeHash, addr), code); AccountState accountState = getOrCreateAccountState(addr); accountStateCache.put(addr, accountState.withCodeHash(codeHash)); } @@ -136,7 +136,12 @@ public synchronized void saveCode(byte[] addr, byte[] code) { public synchronized byte[] getCode(byte[] addr) { byte[] codeHash = getCodeHash(addr); return FastByteComparisons.equal(codeHash, HashUtil.EMPTY_DATA_HASH) ? - ByteUtil.EMPTY_BYTE_ARRAY : codeCache.get(codeHash); + ByteUtil.EMPTY_BYTE_ARRAY : codeCache.get(codeKey(codeHash, addr)); + } + + // composing a key as there can be several contracts with the same code + private byte[] codeKey(byte[] codeHash, byte[] addr) { + return NodeKeyCompositor.compose(codeHash, addr); } @Override diff --git a/ethereumj-core/src/main/java/org/ethereum/db/RepositoryRoot.java b/ethereumj-core/src/main/java/org/ethereum/db/RepositoryRoot.java index 8eb09d07fe..f6902e8c5b 100644 --- a/ethereumj-core/src/main/java/org/ethereum/db/RepositoryRoot.java +++ b/ethereumj-core/src/main/java/org/ethereum/db/RepositoryRoot.java @@ -44,7 +44,9 @@ public MultiStorageCache() { @Override protected synchronized StorageCache create(byte[] key, StorageCache srcCache) { AccountState accountState = accountStateCache.get(key); - TrieImpl storageTrie = createTrie(trieCache, accountState == null ? null : accountState.getStateRoot()); + Serializer keyCompositor = new NodeKeyCompositor(key); + Source composingSrc = new SourceCodec.KeyOnly<>(trieCache, keyCompositor); + TrieImpl storageTrie = createTrie(composingSrc, accountState == null ? null : accountState.getStateRoot()); return new StorageCache(storageTrie); } @@ -80,9 +82,9 @@ public RepositoryRoot(Source stateDS) { /** * Building the following structure for snapshot Repository: * - * stateDS --> trieCacheCodec --> trieCache --> stateTrie --> accountStateCodec --> accountStateCache - * \ \ - * \ \-->>> contractStorageTrie --> storageCodec --> StorageCache + * stateDS --> trieCache --> stateTrie --> accountStateCodec --> accountStateCache + * \ \ + * \ \-->>> storageKeyCompositor --> contractStorageTrie --> storageCodec --> storageCache * \--> codeCache * * @@ -142,7 +144,7 @@ public synchronized void syncToRoot(byte[] root) { stateTrie.setRoot(root); } - protected TrieImpl createTrie(CachedSource.BytesKey trieCache, byte[] root) { + protected TrieImpl createTrie(Source trieCache, byte[] root) { return new SecureTrie(trieCache, root); } diff --git a/ethereumj-core/src/main/java/org/ethereum/db/StateSource.java b/ethereumj-core/src/main/java/org/ethereum/db/StateSource.java index 77459e592e..415cf05f0b 100644 --- a/ethereumj-core/src/main/java/org/ethereum/db/StateSource.java +++ b/ethereumj-core/src/main/java/org/ethereum/db/StateSource.java @@ -20,8 +20,6 @@ import org.ethereum.config.CommonConfig; import org.ethereum.config.SystemProperties; import org.ethereum.datasource.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** @@ -29,7 +27,6 @@ */ public class StateSource extends SourceChainBox implements HashedKeySource { - private static final Logger logger = LoggerFactory.getLogger("db"); // for debug purposes public static StateSource INST; @@ -37,28 +34,18 @@ public class StateSource extends SourceChainBox JournalSource journalSource; NoDeleteSource noDeleteSource; - CountingBytesSource countingSource; ReadCache readCache; AbstractCachedSource writeCache; - BloomedSource bloomedSource; public StateSource(Source src, boolean pruningEnabled) { - this(src, pruningEnabled, 0); - } - - public StateSource(Source src, boolean pruningEnabled, int maxBloomSize) { super(src); INST = this; - add(bloomedSource = new BloomedSource(src, maxBloomSize)); - bloomedSource.setFlushSource(false); - add(readCache = new ReadCache.BytesKey<>(bloomedSource).withMaxCapacity(16 * 1024 * 1024 / 512)); // 512 - approx size of a node + add(readCache = new ReadCache.BytesKey<>(src).withMaxCapacity(16 * 1024 * 1024 / 512)); // 512 - approx size of a node readCache.setFlushSource(true); - add(countingSource = new CountingBytesSource(readCache, true)); - countingSource.setFlushSource(true); - writeCache = new AsyncWriteCache(countingSource) { + writeCache = new AsyncWriteCache(readCache) { @Override protected WriteCache createCache(Source source) { - WriteCache.BytesKey ret = new WriteCache.BytesKey(source, WriteCache.CacheType.COUNTING); + WriteCache.BytesKey ret = new WriteCache.BytesKey(source, WriteCache.CacheType.SIMPLE); ret.withSizeEstimators(MemSizeEstimator.ByteArrayEstimator, MemSizeEstimator.ByteArrayEstimator); ret.setFlushSource(true); return ret; @@ -91,10 +78,6 @@ public JournalSource getJournalSource() { return journalSource; } - public BloomedSource getBloomedSource() { - return bloomedSource; - } - /** * Returns the source behind JournalSource */ diff --git a/ethereumj-core/src/main/java/org/ethereum/db/prune/Chain.java b/ethereumj-core/src/main/java/org/ethereum/db/prune/Chain.java new file mode 100644 index 0000000000..49ff2df469 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/db/prune/Chain.java @@ -0,0 +1,110 @@ +package org.ethereum.db.prune; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * A single chain in a blockchain {@link Segment}. + * It could represent either fork or the main chain. + * + *

+ * Chain consists of certain number of {@link ChainItem} + * connected to each other with inheritance + * + * @author Mikhail Kalinin + * @since 24.01.2018 + */ +public class Chain { + + static final Chain NULL = new Chain() { + @Override + boolean connect(ChainItem item) { + throw new RuntimeException("Not supported for null chain"); + } + }; + + List items = new ArrayList<>(); + + public List getHashes() { + return items.stream().map(item -> item.hash).collect(Collectors.toList()); + } + + private Chain() { + } + + Chain(ChainItem item) { + this.items.add(item); + } + + ChainItem top() { + return items.size() > 0 ? items.get(items.size() - 1) : null; + } + + long topNumber() { + return top() != null ? top().number : 0; + } + + long startNumber() { + return items.isEmpty() ? 0 : items.get(0).number; + } + + boolean isHigher(Chain other) { + return other.topNumber() < this.topNumber(); + } + + boolean contains(ChainItem other) { + for (ChainItem item : items) { + if (item.equals(other)) + return true; + } + return false; + } + + boolean connect(ChainItem item) { + if (top().isParentOf(item)) { + items.add(item); + return true; + } + + return false; + } + + static Chain fromItems(ChainItem ... items) { + if (items.length == 0) { + return NULL; + } + + Chain chain = null; + for (ChainItem item : items) { + if (chain == null) { + chain = new Chain(item); + } else { + chain.connect(item); + } + } + + return chain; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Chain chain = (Chain) o; + + return !(items != null ? !items.equals(chain.items) : chain.items != null); + } + + @Override + public String toString() { + if (items.isEmpty()) { + return "(empty)"; + } + return "[" + items.get(0) + + " ~> " + items.get(items.size() - 1) + + ']'; + } + +} diff --git a/ethereumj-core/src/main/java/org/ethereum/db/prune/ChainItem.java b/ethereumj-core/src/main/java/org/ethereum/db/prune/ChainItem.java new file mode 100644 index 0000000000..9d00778bb7 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/db/prune/ChainItem.java @@ -0,0 +1,52 @@ +package org.ethereum.db.prune; + +import org.ethereum.core.Block; +import org.ethereum.util.FastByteComparisons; + +import java.util.Arrays; + +/** + * Represents a block in the {@link Chain} + * + * @author Mikhail Kalinin + * @since 26.01.2018 + */ +class ChainItem { + long number; + byte[] hash; + byte[] parentHash; + + ChainItem(Block block) { + this.number = block.getNumber(); + this.hash = block.getHash(); + this.parentHash = block.getParentHash(); + } + + ChainItem(long number, byte[] hash, byte[] parentHash) { + this.number = number; + this.hash = hash; + this.parentHash = parentHash; + } + + boolean isParentOf(ChainItem that) { + return FastByteComparisons.equal(hash, that.parentHash); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ChainItem that = (ChainItem) o; + return FastByteComparisons.equal(hash, that.hash); + } + + @Override + public int hashCode() { + return hash != null ? Arrays.hashCode(hash) : 0; + } + + @Override + public String toString() { + return String.valueOf(number); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/db/prune/Pruner.java b/ethereumj-core/src/main/java/org/ethereum/db/prune/Pruner.java new file mode 100644 index 0000000000..bd9d994249 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/db/prune/Pruner.java @@ -0,0 +1,370 @@ +package org.ethereum.db.prune; + +import org.ethereum.crypto.HashUtil; +import org.ethereum.datasource.CountingQuotientFilter; +import org.ethereum.datasource.JournalSource; +import org.ethereum.datasource.QuotientFilter; +import org.ethereum.datasource.Source; +import org.ethereum.util.ByteArraySet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * This class is responsible for state pruning. + * + *

+ * Taking the information supplied by {@link #journal} (check {@link JournalSource} for details) + * removes unused nodes from the {@link #storage}. + * There are two types of unused nodes: + * nodes not references in the trie after N blocks from the current one and + * nodes which were inserted in the forks that finally were not accepted + * + *

+ * Each prune session uses a certain chain {@link Segment} + * which is going to be 'pruned'. To be confident that live nodes won't be removed, + * pruner must be initialized with the top of the chain, see {@link #init(List, int)}}. + * And after that it must be fed with each newly processed block, see {@link #feed(JournalSource.Update)}. + * {@link QuotientFilter} ({@link CountingQuotientFilter} implementation in particular) instance is used to + * efficiently keep upcoming inserts in memory and protect newly inserted nodes from being deleted during + * prune session. The filter is constantly recycled in {@link #prune(Segment)} method. + * + *

+ * When 'prune.maxDepth' param is quite big, it becomes not efficient to keep reverted nodes until prune block number has come. + * Hence Pruner has two step mode to mitigate memory consumption, second step is initiated by {@link #withSecondStep(List, int)}. + * In that mode nodes from not accepted forks are deleted from storage immediately but main chain deletions are + * postponed for the second step. + * Second step uses another one instance of QuotientFilter with less memory impact, check {@link #instantiateFilter(int, int)}. + * + *

+ * Basically, prune session initiated by {@link #prune(Segment)} method + * consists of 3 steps: first, it reverts forks, then it persists main chain, + * after that it recycles {@link #journal} by removing processed updates from it. + * During the session reverted and deleted nodes are propagated to the {@link #storage} immediately. + * + * @author Mikhail Kalinin + * @since 25.01.2018 + */ +public class Pruner { + + private static final Logger logger = LoggerFactory.getLogger("prune"); + + Source journal; + Source storage; + QuotientFilter filter; + QuotientFilter distantFilter; + boolean ready = false; + + private static class Stats { + int collisions = 0; + int deleted = 0; + double load = 0; + @Override + public String toString() { + return String.format("load %.4f, collisions %d, deleted %d", load, collisions, deleted); + } + } + Stats maxLoad = new Stats(); + Stats maxCollisions = new Stats(); + int maxKeysInMemory = 0; + int statsTracker = 0; + + Stats distantMaxLoad = new Stats(); + Stats distantMaxCollisions = new Stats(); + + public Pruner(Source journal, Source storage) { + this.storage = storage; + this.journal = journal; + } + + public boolean isReady() { + return ready; + } + + public boolean init(List forkWindow, int sizeInBlocks) { + if (ready) return true; + + if (!forkWindow.isEmpty() && journal.get(forkWindow.get(0)) == null) { + logger.debug("pruner init aborted: can't fetch update " + Hex.toHexString(forkWindow.get(0))); + return false; + } + + QuotientFilter filter = instantiateFilter(sizeInBlocks, FILTER_ENTRIES_FORK); + for (byte[] hash : forkWindow) { + JournalSource.Update update = journal.get(hash); + if (update == null) { + logger.debug("pruner init aborted: can't fetch update " + Hex.toHexString(hash)); + return false; + } + update.getInsertedKeys().forEach(filter::insert); + } + + this.filter = filter; + return ready = true; + } + + public boolean withSecondStep() { + return distantFilter != null; + } + + public void withSecondStep(List mainChainWindow, int sizeInBlocks) { + if (!ready) return; + + QuotientFilter filter = instantiateFilter(sizeInBlocks, FILTER_ENTRIES_DISTANT); + + if (!mainChainWindow.isEmpty()) { + int i = mainChainWindow.size() - 1; + for (; i >= 0; i--) { + byte[] hash = mainChainWindow.get(i); + JournalSource.Update update = journal.get(hash); + if (update == null) { + break; + } + update.getInsertedKeys().forEach(filter::insert); + } + logger.debug("distant filter initialized with set of " + (i < 0 ? mainChainWindow.size() : mainChainWindow.size() - i) + + " hashes, last hash " + Hex.toHexString(mainChainWindow.get(i < 0 ? 0 : i))); + } else { + logger.debug("distant filter initialized with empty set"); + } + + this.distantFilter = filter; + } + + private static final int FILTER_ENTRIES_FORK = 1 << 13; // approximate number of nodes per block + private static final int FILTER_ENTRIES_DISTANT = 1 << 11; + private static final int FILTER_MAX_SIZE = Integer.MAX_VALUE >> 1; // that filter will consume ~3g of mem + private QuotientFilter instantiateFilter(int blocksCnt, int entries) { + int size = Math.min(entries * blocksCnt, FILTER_MAX_SIZE); + return CountingQuotientFilter.create(size, size); + } + + public boolean init(byte[] ... upcoming) { + return init(Arrays.asList(upcoming), 192); + } + + public void feed(JournalSource.Update update) { + if (ready) + update.getInsertedKeys().forEach(filter::insert); + } + + public void prune(Segment segment) { + if (!ready) return; + assert segment.isComplete(); + + logger.trace("prune " + segment); + + long t = System.currentTimeMillis(); + Pruning pruning = new Pruning(); + // important for fork management, check Pruning#insertedInMainChain and Pruning#insertedInForks for details + segment.forks.sort((f1, f2) -> Long.compare(f1.startNumber(), f2.startNumber())); + segment.forks.forEach(pruning::revert); + + // delete updates + for (Chain chain : segment.forks) { + chain.getHashes().forEach(journal::delete); + } + + int nodesPostponed = 0; + if (withSecondStep()) { + nodesPostponed = postpone(segment.main); + } else { + pruning.nodesDeleted += persist(segment.main); + segment.main.getHashes().forEach(journal::delete); + } + + if (logger.isTraceEnabled()) logger.trace("nodes {}, keys in mem: {}, filter load: {}/{}: {}, distinct collisions: {}", + (withSecondStep() ? "postponed: " + nodesPostponed : "deleted: " + pruning.nodesDeleted), + pruning.insertedInForks.size() + pruning.insertedInMainChain.size(), + ((CountingQuotientFilter) filter).getEntryNumber(), ((CountingQuotientFilter) filter).getMaxInsertions(), + String.format("%.4f", (double) ((CountingQuotientFilter) filter).getEntryNumber() / + ((CountingQuotientFilter) filter).getMaxInsertions()), + ((CountingQuotientFilter) filter).getCollisionNumber()); + + if (logger.isDebugEnabled()) { + int collisions = ((CountingQuotientFilter) filter).getCollisionNumber(); + double load = (double) ((CountingQuotientFilter) filter).getEntryNumber() / + ((CountingQuotientFilter) filter).getMaxInsertions(); + if (collisions > maxCollisions.collisions) { + maxCollisions.collisions = collisions; + maxCollisions.load = load; + maxCollisions.deleted = pruning.nodesDeleted; + } + if (load > maxLoad.load) { + maxLoad.load = load; + maxLoad.collisions = collisions; + maxLoad.deleted = pruning.nodesDeleted; + } + maxKeysInMemory = Math.max(maxKeysInMemory, pruning.insertedInForks.size() + pruning.insertedInMainChain.size()); + + if (++statsTracker % 100 == 0) { + logger.debug("fork filter: max load: " + maxLoad); + logger.debug("fork filter: max collisions: " + maxCollisions); + logger.debug("fork filter: max keys in mem: " + maxKeysInMemory); + } + } + + logger.trace(segment + " pruned in {}ms", System.currentTimeMillis() - t); + } + + public void persist(byte[] hash) { + if (!ready || !withSecondStep()) return; + + logger.trace("persist [{}]", Hex.toHexString(hash)); + + long t = System.currentTimeMillis(); + JournalSource.Update update = journal.get(hash); + if (update == null) { + logger.debug("skip [{}]: can't fetch update", HashUtil.shortHash(hash)); + return; + } + + // persist deleted keys + int nodesDeleted = 0; + for (byte[] key : update.getDeletedKeys()) { + if (!filter.maybeContains(key) && !distantFilter.maybeContains(key)) { + ++nodesDeleted; + storage.delete(key); + } + } + // clean up filter + update.getInsertedKeys().forEach(distantFilter::remove); + // delete update + journal.delete(hash); + + if (logger.isDebugEnabled()) { + int collisions = ((CountingQuotientFilter) distantFilter).getCollisionNumber(); + double load = (double) ((CountingQuotientFilter) distantFilter).getEntryNumber() / + ((CountingQuotientFilter) distantFilter).getMaxInsertions(); + if (collisions > distantMaxCollisions.collisions) { + distantMaxCollisions.collisions = collisions; + distantMaxCollisions.load = load; + distantMaxCollisions.deleted = nodesDeleted; + } + if (load > distantMaxLoad.load) { + distantMaxLoad.load = load; + distantMaxLoad.collisions = collisions; + distantMaxLoad.deleted = nodesDeleted; + } + if (statsTracker % 100 == 0) { + logger.debug("distant filter: max load: " + distantMaxLoad); + logger.debug("distant filter: max collisions: " + distantMaxCollisions); + } + } + + if (logger.isTraceEnabled()) logger.trace("[{}] persisted in {}ms: {}/{} ({}%) nodes deleted, filter load: {}/{}: {}, distinct collisions: {}", + HashUtil.shortHash(hash), System.currentTimeMillis() - t, nodesDeleted, update.getDeletedKeys().size(), + nodesDeleted * 100 / update.getDeletedKeys().size(), + ((CountingQuotientFilter) distantFilter).getEntryNumber(), + ((CountingQuotientFilter) distantFilter).getMaxInsertions(), + String.format("%.4f", (double) ((CountingQuotientFilter) distantFilter).getEntryNumber() / + ((CountingQuotientFilter) distantFilter).getMaxInsertions()), + ((CountingQuotientFilter) distantFilter).getCollisionNumber()); + } + + private int postpone(Chain chain) { + if (logger.isTraceEnabled()) + logger.trace("<~ postponing " + chain + ": " + strSample(chain.getHashes())); + + int nodesPostponed = 0; + for (byte[] hash : chain.getHashes()) { + JournalSource.Update update = journal.get(hash); + if (update == null) { + logger.debug("postponing: can't fetch update " + Hex.toHexString(hash)); + continue; + } + // feed distant filter + update.getInsertedKeys().forEach(distantFilter::insert); + // clean up fork filter + update.getInsertedKeys().forEach(filter::remove); + + nodesPostponed += update.getDeletedKeys().size(); + } + + return nodesPostponed; + } + + private int persist(Chain chain) { + if (logger.isTraceEnabled()) + logger.trace("<~ persisting " + chain + ": " + strSample(chain.getHashes())); + + int nodesDeleted = 0; + for (byte[] hash : chain.getHashes()) { + JournalSource.Update update = journal.get(hash); + if (update == null) { + logger.debug("pruning aborted: can't fetch update of main chain " + Hex.toHexString(hash)); + return 0; + } + // persist deleted keys + for (byte[] key : update.getDeletedKeys()) { + if (!filter.maybeContains(key)) { + ++nodesDeleted; + storage.delete(key); + } + } + // clean up filter + update.getInsertedKeys().forEach(filter::remove); + } + + return nodesDeleted; + } + + private String strSample(Collection hashes) { + String sample = hashes.stream().limit(3) + .map(HashUtil::shortHash).collect(Collectors.joining(", ")); + if (hashes.size() > 3) { + sample += ", ... (" + hashes.size() + " total)"; + } + return sample; + } + + private class Pruning { + + // track nodes inserted and deleted in forks + // to avoid deletion of those nodes which were originally inserted in the main chain + Set insertedInMainChain = new ByteArraySet(); + Set insertedInForks = new ByteArraySet(); + int nodesDeleted = 0; + + private void revert(Chain chain) { + if (logger.isTraceEnabled()) + logger.trace("<~ reverting " + chain + ": " + strSample(chain.getHashes())); + + for (byte[] hash : chain.getHashes()) { + JournalSource.Update update = journal.get(hash); + if (update == null) { + logger.debug("reverting chain " + chain + " aborted: can't fetch update " + Hex.toHexString(hash)); + return; + } + // clean up filter + update.getInsertedKeys().forEach(filter::remove); + + // node that was deleted in fork considered as a node that had earlier been inserted in main chain + update.getDeletedKeys().forEach(key -> { + if (!insertedInForks.contains(key)) { + insertedInMainChain.add(key); + } + }); + update.getInsertedKeys().forEach(key -> { + if (!insertedInMainChain.contains(key)) { + insertedInForks.add(key); + } + }); + + // revert inserted keys + for (byte[] key : update.getInsertedKeys()) { + if (!filter.maybeContains(key) && !insertedInMainChain.contains(key)) { + ++nodesDeleted; + storage.delete(key); + } + } + } + } + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/db/prune/Segment.java b/ethereumj-core/src/main/java/org/ethereum/db/prune/Segment.java new file mode 100644 index 0000000000..7cb3623324 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/db/prune/Segment.java @@ -0,0 +1,168 @@ +package org.ethereum.db.prune; + +import org.ethereum.core.Block; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Provides an interface for building and tracking chain segment. + * + *

+ * Chain segment is a fragment of the blockchain, it includes both forks and main chain. + * Segment always has a 'root' item which must belong to the main chain, + * anyway 'root' item itself is not treated as a part of the segment. + * + *

+ * Segment is complete when its main chain top item is the highest (fork tops have lower numbers). + * Whether segment is complete or not can be checked by call to {@link #isComplete()} + * + *

+ * Segment has a {@link Tracker} class which helps to update segment with new blocks. + * Its Usage is simple: add all blocks with {@link Tracker#addAll(List)}, + * add main chain blocks with {@link Tracker#addMain(Block)}, + * then when all blocks are added {@link Tracker#commit()} should be fired + * to connect added blocks to the segment + * + * @author Mikhail Kalinin + * @since 24.01.2018 + * + * @see Chain + * @see ChainItem + */ +public class Segment { + + List forks = new ArrayList<>(); + Chain main = Chain.NULL; + ChainItem root; + + public Segment(Block root) { + this.root = new ChainItem(root); + } + + public Segment(long number, byte[] hash, byte[] parentHash) { + this.root = new ChainItem(number, hash, parentHash); + } + + public boolean isComplete() { + if (main == Chain.NULL) + return false; + + for (Chain fork : forks) { + if (!main.isHigher(fork)) + return false; + } + return true; + } + + public long getRootNumber() { + return root.number; + } + + public long getMaxNumber() { + return main.topNumber(); + } + + public Tracker startTracking() { + return new Tracker(this); + } + + public int size() { + return main.items.size(); + } + + private void branch(ChainItem item) { + forks.add(new Chain(item)); + } + + private void connectMain(ChainItem item) { + if (main == Chain.NULL) { + if (root.isParentOf(item)) + main = new Chain(item); // start new + } else { + main.connect(item); + } + } + + private void connectFork(ChainItem item) { + + for (Chain fork : forks) { + if (fork.contains(item)) + return; + } + + if (root.isParentOf(item)) { + branch(item); + } else { + for (ChainItem mainItem : main.items) { + if (mainItem.isParentOf(item)) { + branch(item); + } + } + + for (Chain fork : forks) { + if (fork.connect(item)) { + return; + } + } + + List branchedForks = new ArrayList<>(); + for (Chain fork : forks) { + for (ChainItem forkItem : fork.items) { + if (forkItem.isParentOf(item)) { + branchedForks.add(new Chain(item)); + } + } + } + forks.addAll(branchedForks); + } + } + + @Override + public String toString() { + return "" + main; + } + + public static final class Tracker { + + Segment segment; + List main = new ArrayList<>(); + List items = new ArrayList<>(); + + Tracker(Segment segment) { + this.segment = segment; + } + + public void addMain(Block block) { + main.add(new ChainItem(block)); + } + + public void addAll(List blocks) { + items.addAll(blocks.stream() + .map(ChainItem::new) + .collect(Collectors.toList())); + } + + public Tracker addMain(long number, byte[] hash, byte[] parentHash) { + main.add(new ChainItem(number, hash, parentHash)); + return this; + } + + public Tracker addItem(long number, byte[] hash, byte[] parentHash) { + items.add(new ChainItem(number, hash, parentHash)); + return this; + } + + public void commit() { + + items.removeAll(main); + + main.sort((i1, i2) -> Long.compare(i1.number, i2.number)); + items.sort((i1, i2) -> Long.compare(i1.number, i2.number)); + + main.forEach(segment::connectMain); + items.forEach(segment::connectFork); + } + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/jsonrpc/JsonRpc.java b/ethereumj-core/src/main/java/org/ethereum/jsonrpc/JsonRpc.java deleted file mode 100644 index bf8f5a9fd7..0000000000 --- a/ethereumj-core/src/main/java/org/ethereum/jsonrpc/JsonRpc.java +++ /dev/null @@ -1,341 +0,0 @@ -/* - * Copyright (c) [2016] [ ] - * This file is part of the ethereumJ library. - * - * The ethereumJ library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The ethereumJ library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the ethereumJ library. If not, see . - */ -package org.ethereum.jsonrpc; - -import org.ethereum.core.Block; -import org.ethereum.core.CallTransaction; -import org.ethereum.core.Transaction; -import org.ethereum.vm.LogInfo; - -import java.util.Arrays; - -import static org.ethereum.jsonrpc.TypeConverter.toJsonHex; - -/** - * Created by Anton Nashatyrev on 25.11.2015. - */ -public interface JsonRpc { - - class SyncingResult { - public String startingBlock; - public String currentBlock; - public String highestBlock; - - @Override - public String toString() { - return "SyncingResult{" + - "startingBlock='" + startingBlock + '\'' + - ", currentBlock='" + currentBlock + '\'' + - ", highestBlock='" + highestBlock + '\'' + - '}'; - } - } - - class CallArguments { - public String from; - public String to; - public String gas; - public String gasPrice; - public String value; - public String data; // compiledCode - public String nonce; - - @Override - public String toString() { - return "CallArguments{" + - "from='" + from + '\'' + - ", to='" + to + '\'' + - ", gasLimit='" + gas + '\'' + - ", gasPrice='" + gasPrice + '\'' + - ", value='" + value + '\'' + - ", data='" + data + '\'' + - ", nonce='" + nonce + '\'' + - '}'; - } - } - - class BlockResult { - public String number; // QUANTITY - the block number. null when its pending block. - public String hash; // DATA, 32 Bytes - hash of the block. null when its pending block. - public String parentHash; // DATA, 32 Bytes - hash of the parent block. - public String nonce; // DATA, 8 Bytes - hash of the generated proof-of-work. null when its pending block. - public String sha3Uncles; // DATA, 32 Bytes - SHA3 of the uncles data in the block. - public String logsBloom; // DATA, 256 Bytes - the bloom filter for the logs of the block. null when its pending block. - public String transactionsRoot; // DATA, 32 Bytes - the root of the transaction trie of the block. - public String stateRoot; // DATA, 32 Bytes - the root of the final state trie of the block. - public String receiptsRoot; // DATA, 32 Bytes - the root of the receipts trie of the block. - public String miner; // DATA, 20 Bytes - the address of the beneficiary to whom the mining rewards were given. - public String difficulty; // QUANTITY - integer of the difficulty for this block. - public String totalDifficulty; // QUANTITY - integer of the total difficulty of the chain until this block. - public String extraData; // DATA - the "extra data" field of this block - public String size;//QUANTITY - integer the size of this block in bytes. - public String gasLimit;//: QUANTITY - the maximum gas allowed in this block. - public String gasUsed; // QUANTITY - the total used gas by all transactions in this block. - public String timestamp; //: QUANTITY - the unix timestamp for when the block was collated. - public Object[] transactions; //: Array - Array of transaction objects, or 32 Bytes transaction hashes depending on the last given parameter. - public String[] uncles; //: Array - Array of uncle hashes. - - @Override - public String toString() { - return "BlockResult{" + - "number='" + number + '\'' + - ", hash='" + hash + '\'' + - ", parentHash='" + parentHash + '\'' + - ", nonce='" + nonce + '\'' + - ", sha3Uncles='" + sha3Uncles + '\'' + - ", logsBloom='" + logsBloom + '\'' + - ", transactionsRoot='" + transactionsRoot + '\'' + - ", stateRoot='" + stateRoot + '\'' + - ", receiptsRoot='" + receiptsRoot + '\'' + - ", miner='" + miner + '\'' + - ", difficulty='" + difficulty + '\'' + - ", totalDifficulty='" + totalDifficulty + '\'' + - ", extraData='" + extraData + '\'' + - ", size='" + size + '\'' + - ", gasLimit='" + gasLimit + '\'' + - ", gasUsed='" + gasUsed + '\'' + - ", timestamp='" + timestamp + '\'' + - ", transactions=" + Arrays.toString(transactions) + - ", uncles=" + Arrays.toString(uncles) + - '}'; - } - } - - class CompilationResult { - public String code; - public CompilationInfo info; - - @Override - public String toString() { - return "CompilationResult{" + - "code='" + code + '\'' + - ", info=" + info + - '}'; - } - } - - class CompilationInfo { - public String source; - public String language; - public String languageVersion; - public String compilerVersion; - public CallTransaction.Function[] abiDefinition; - public String userDoc; - public String developerDoc; - - @Override - public String toString() { - return "CompilationInfo{" + - "source='" + source + '\'' + - ", language='" + language + '\'' + - ", languageVersion='" + languageVersion + '\'' + - ", compilerVersion='" + compilerVersion + '\'' + - ", abiDefinition=" + abiDefinition + - ", userDoc='" + userDoc + '\'' + - ", developerDoc='" + developerDoc + '\'' + - '}'; - } - } - - class FilterRequest { - public String fromBlock; - public String toBlock; - public Object address; - public Object[] topics; - - @Override - public String toString() { - return "FilterRequest{" + - "fromBlock='" + fromBlock + '\'' + - ", toBlock='" + toBlock + '\'' + - ", address=" + address + - ", topics=" + Arrays.toString(topics) + - '}'; - } - } - - class LogFilterElement { - public String logIndex; - public String blockNumber; - public String blockHash; - public String transactionHash; - public String transactionIndex; - public String address; - public String data; - public String[] topics; - - public LogFilterElement(LogInfo logInfo, Block b, int txIndex, Transaction tx, int logIdx) { - logIndex = toJsonHex(logIdx); - blockNumber = b == null ? null : toJsonHex(b.getNumber()); - blockHash = b == null ? null : toJsonHex(b.getHash()); - transactionIndex = b == null ? null : toJsonHex(txIndex); - transactionHash = toJsonHex(tx.getHash()); - address = toJsonHex(tx.getReceiveAddress()); - data = toJsonHex(logInfo.getData()); - topics = new String[logInfo.getTopics().size()]; - for (int i = 0; i < topics.length; i++) { - topics[i] = toJsonHex(logInfo.getTopics().get(i).getData()); - } - } - - @Override - public String toString() { - return "LogFilterElement{" + - "logIndex='" + logIndex + '\'' + - ", blockNumber='" + blockNumber + '\'' + - ", blockHash='" + blockHash + '\'' + - ", transactionHash='" + transactionHash + '\'' + - ", transactionIndex='" + transactionIndex + '\'' + - ", address='" + address + '\'' + - ", data='" + data + '\'' + - ", topics=" + Arrays.toString(topics) + - '}'; - } - } - - String web3_clientVersion(); - String web3_sha3(String data) throws Exception; - String net_version(); - String net_peerCount(); - boolean net_listening(); - String eth_protocolVersion(); - SyncingResult eth_syncing(); - String eth_coinbase(); - boolean eth_mining(); - String eth_hashrate(); - String eth_gasPrice(); - String[] eth_accounts(); - String eth_blockNumber(); - String eth_getBalance(String address, String block) throws Exception; - String eth_getBalance(String address) throws Exception; - - String eth_getStorageAt(String address, String storageIdx, String blockId) throws Exception; - - String eth_getTransactionCount(String address, String blockId) throws Exception; - - String eth_getBlockTransactionCountByHash(String blockHash)throws Exception; - String eth_getBlockTransactionCountByNumber(String bnOrId)throws Exception; - String eth_getUncleCountByBlockHash(String blockHash)throws Exception; - String eth_getUncleCountByBlockNumber(String bnOrId)throws Exception; - String eth_getCode(String addr, String bnOrId)throws Exception; - String eth_sign(String addr,String data) throws Exception; - String eth_sendTransaction(CallArguments transactionArgs) throws Exception; - // TODO: Remove, obsolete with this params - String eth_sendTransaction(String from,String to, String gas, - String gasPrice, String value,String data,String nonce) throws Exception; - String eth_sendRawTransaction(String rawData) throws Exception; - String eth_call(CallArguments args, String bnOrId) throws Exception; - String eth_estimateGas(CallArguments args) throws Exception; - BlockResult eth_getBlockByHash(String blockHash,Boolean fullTransactionObjects) throws Exception; - BlockResult eth_getBlockByNumber(String bnOrId,Boolean fullTransactionObjects) throws Exception; - TransactionResultDTO eth_getTransactionByHash(String transactionHash) throws Exception; - TransactionResultDTO eth_getTransactionByBlockHashAndIndex(String blockHash,String index) throws Exception; - TransactionResultDTO eth_getTransactionByBlockNumberAndIndex(String bnOrId,String index) throws Exception; - TransactionReceiptDTO eth_getTransactionReceipt(String transactionHash) throws Exception; - - TransactionReceiptDTOExt ethj_getTransactionReceipt(String transactionHash) throws Exception; - - BlockResult eth_getUncleByBlockHashAndIndex(String blockHash, String uncleIdx) throws Exception; - - BlockResult eth_getUncleByBlockNumberAndIndex(String blockId, String uncleIdx) throws Exception; - - String[] eth_getCompilers(); - CompilationResult eth_compileLLL(String contract); - CompilationResult eth_compileSolidity(String contract) throws Exception; - CompilationResult eth_compileSerpent(String contract); - String eth_resend(); - String eth_pendingTransactions(); - - String eth_newFilter(FilterRequest fr) throws Exception; - -// String eth_newFilter(String fromBlock, String toBlock, String address, String[] topics) throws Exception; - - String eth_newBlockFilter(); - String eth_newPendingTransactionFilter(); - boolean eth_uninstallFilter(String id); - Object[] eth_getFilterChanges(String id); - - Object[] eth_getFilterLogs(String id); - - Object[] eth_getLogs(FilterRequest fr) throws Exception; - - String eth_getWork(); - String eth_submitWork(); - String eth_submitHashrate(); - String db_putString(); - String db_getString(); - String db_putHex(); - String db_getHex(); - String shh_post(); - String shh_version(); - String shh_newIdentity(); - String shh_hasIdentity(); - String shh_newGroup(); - String shh_addToGroup(); - String shh_newFilter(); - String shh_uninstallFilter(); - String shh_getFilterChanges(); - String shh_getMessages(); - - - boolean admin_addPeer(String s); - - String admin_exportChain(); - String admin_importChain(); - String admin_sleepBlocks(); - String admin_verbosity(); - String admin_setSolc(); - String admin_startRPC(); - String admin_stopRPC(); - String admin_setGlobalRegistrar(); - String admin_setHashReg(); - String admin_setUrlHint(); - String admin_saveInfo(); - String admin_register(); - String admin_registerUrl(); - String admin_startNatSpec(); - String admin_stopNatSpec(); - String admin_getContractInfo(); - String admin_httpGet(); - String admin_nodeInfo(); - String admin_peers(); - String admin_datadir(); - String net_addPeer(); - boolean miner_start(); - boolean miner_stop(); - boolean miner_setEtherbase(String coinBase) throws Exception; - boolean miner_setExtra(String data) throws Exception; - boolean miner_setGasPrice(String newMinGasPrice); - boolean miner_startAutoDAG(); - boolean miner_stopAutoDAG(); - boolean miner_makeDAG(); - String miner_hashrate(); - String debug_printBlock(); - String debug_getBlockRlp(); - String debug_setHead(); - String debug_processBlock(); - String debug_seedHash(); - String debug_dumpBlock(); - String debug_metrics(); - - String personal_newAccount(String seed); - - boolean personal_unlockAccount(String addr, String pass, String duration); - - String[] personal_listAccounts(); -} diff --git a/ethereumj-core/src/main/java/org/ethereum/jsonrpc/JsonRpcImpl.java b/ethereumj-core/src/main/java/org/ethereum/jsonrpc/JsonRpcImpl.java deleted file mode 100644 index 789cedbfb7..0000000000 --- a/ethereumj-core/src/main/java/org/ethereum/jsonrpc/JsonRpcImpl.java +++ /dev/null @@ -1,1524 +0,0 @@ -/* - * Copyright (c) [2016] [ ] - * This file is part of the ethereumJ library. - * - * The ethereumJ library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The ethereumJ library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the ethereumJ library. If not, see . - */ -package org.ethereum.jsonrpc; - -import org.apache.commons.collections4.map.LRUMap; -import org.ethereum.config.CommonConfig; -import org.ethereum.config.SystemProperties; -import org.ethereum.core.*; -import org.ethereum.crypto.ECKey; -import org.ethereum.crypto.HashUtil; -import org.ethereum.db.BlockStore; -import org.ethereum.db.ByteArrayWrapper; -import org.ethereum.core.TransactionInfo; -import org.ethereum.db.TransactionStore; -import org.ethereum.facade.Ethereum; -import org.ethereum.listener.CompositeEthereumListener; -import org.ethereum.listener.EthereumListenerAdapter; -import org.ethereum.manager.WorldManager; -import org.ethereum.mine.BlockMiner; -import org.ethereum.net.client.Capability; -import org.ethereum.net.client.ConfigCapabilities; -import org.ethereum.net.rlpx.Node; -import org.ethereum.net.server.ChannelManager; -import org.ethereum.net.server.PeerServer; -import org.ethereum.solidity.compiler.SolidityCompiler; -import org.ethereum.sync.SyncManager; -import org.ethereum.util.BuildInfo; -import org.ethereum.util.ByteUtil; -import org.ethereum.util.RLP; -import org.ethereum.vm.DataWord; -import org.ethereum.vm.LogInfo; -import org.ethereum.vm.program.invoke.ProgramInvokeFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Component; - -import java.math.BigInteger; -import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; - -import static java.lang.Math.max; -import static org.ethereum.crypto.HashUtil.sha3; -import static org.ethereum.jsonrpc.TypeConverter.*; -import static org.ethereum.jsonrpc.TypeConverter.StringHexToByteArray; -import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY; -import static org.ethereum.util.ByteUtil.bigIntegerToBytes; - -/** - * Created by Anton Nashatyrev on 25.11.2015. - */ -@Component -@Lazy -public class JsonRpcImpl implements JsonRpc { - private static final Logger logger = LoggerFactory.getLogger("jsonrpc"); - - - - public class BinaryCallArguments { - public long nonce; - public long gasPrice; - public long gasLimit; - public String toAddress; - public long value; - public byte[] data; - public void setArguments(CallArguments args) throws Exception { - nonce = 0; - if (args.nonce != null && args.nonce.length() != 0) - nonce = JSonHexToLong(args.nonce); - - gasPrice = 0; - if (args.gasPrice != null && args.gasPrice.length()!=0) - gasPrice = JSonHexToLong(args.gasPrice); - - gasLimit = 4_000_000; - if (args.gas != null && args.gas.length()!=0) - gasLimit = JSonHexToLong(args.gas); - - toAddress = null; - if (args.to != null && !args.to.isEmpty()) - toAddress = JSonHexToHex(args.to); - - value=0; - if (args.value != null && args.value.length()!=0) - value = JSonHexToLong(args.value); - - data = null; - - if (args.data != null && args.data.length()!=0) - data = TypeConverter.StringHexToByteArray(args.data); - } - } - - @Autowired - SystemProperties config; - - @Autowired - ConfigCapabilities configCapabilities; - - @Autowired - public WorldManager worldManager; - - @Autowired - public Repository repository; - - @Autowired - Ethereum eth; - - @Autowired - PeerServer peerServer; - - @Autowired - SyncManager syncManager; - - @Autowired - TransactionStore txStore; - - @Autowired - ChannelManager channelManager; - - @Autowired - BlockMiner blockMiner; - - @Autowired - TransactionStore transactionStore; - - @Autowired - PendingStateImpl pendingState; - - @Autowired - SolidityCompiler solidityCompiler; - - @Autowired - ProgramInvokeFactory programInvokeFactory; - - @Autowired - CommonConfig commonConfig = CommonConfig.getDefault(); - - BlockchainImpl blockchain; - - CompositeEthereumListener compositeEthereumListener; - - - long initialBlockNumber; - - Map accounts = new HashMap<>(); - AtomicInteger filterCounter = new AtomicInteger(1); - Map installedFilters = new Hashtable<>(); - Map pendingReceipts = Collections.synchronizedMap(new LRUMap(1024)); - - @Autowired - public JsonRpcImpl(final BlockchainImpl blockchain, final CompositeEthereumListener compositeEthereumListener) { - this.blockchain = blockchain; - this.compositeEthereumListener = compositeEthereumListener; - initialBlockNumber = blockchain.getBestBlock().getNumber(); - - compositeEthereumListener.addListener(new EthereumListenerAdapter() { - @Override - public void onBlock(Block block, List receipts) { - for (Filter filter : installedFilters.values()) { - filter.newBlockReceived(block); - } - } - - @Override - public void onPendingTransactionsReceived(List transactions) { - for (Filter filter : installedFilters.values()) { - for (Transaction tx : transactions) { - filter.newPendingTx(tx); - } - } - } - - @Override - public void onPendingTransactionUpdate(TransactionReceipt txReceipt, PendingTransactionState state, Block block) { - ByteArrayWrapper txHashW = new ByteArrayWrapper(txReceipt.getTransaction().getHash()); - if (state.isPending() || state == PendingTransactionState.DROPPED) { - pendingReceipts.put(txHashW, txReceipt); - } else { - pendingReceipts.remove(txHashW); - } - } - }); - - } - - public long JSonHexToLong(String x) throws Exception { - if (!x.startsWith("0x")) - throw new Exception("Incorrect hex syntax"); - x = x.substring(2); - return Long.parseLong(x, 16); - } - - public int JSonHexToInt(String x) throws Exception { - if (!x.startsWith("0x")) - throw new Exception("Incorrect hex syntax"); - x = x.substring(2); - return Integer.parseInt(x, 16); - } - - public String JSonHexToHex(String x) throws Exception { - if (!x.startsWith("0x")) - throw new Exception("Incorrect hex syntax"); - x = x.substring(2); - return x; - } - - public Block getBlockByJSonHash(String blockHash) throws Exception { - byte[] bhash = TypeConverter.StringHexToByteArray(blockHash); - return worldManager.getBlockchain().getBlockByHash(bhash); - } - - private Block getByJsonBlockId(String id) { - if ("earliest".equalsIgnoreCase(id)) { - return blockchain.getBlockByNumber(0); - } else if ("latest".equalsIgnoreCase(id)) { - return blockchain.getBestBlock(); - } else if ("pending".equalsIgnoreCase(id)) { - return null; - } else { - long blockNumber = StringHexToBigInteger(id).longValue(); - return blockchain.getBlockByNumber(blockNumber); - } - } - - private Repository getRepoByJsonBlockId(String id) { - if ("pending".equalsIgnoreCase(id)) { - return pendingState.getRepository(); - } else { - Block block = getByJsonBlockId(id); - return this.repository.getSnapshotTo(block.getStateRoot()); - } - } - - private List getTransactionsByJsonBlockId(String id) { - if ("pending".equalsIgnoreCase(id)) { - return pendingState.getPendingTransactions(); - } else { - Block block = getByJsonBlockId(id); - return block != null ? block.getTransactionsList() : null; - } - } - - protected Account getAccount(String address) throws Exception { - return accounts.get(new ByteArrayWrapper(StringHexToByteArray(address))); - } - - protected Account addAccount(String seed) { - return addAccount(ECKey.fromPrivate(sha3(seed.getBytes()))); - } - - protected Account addAccount(ECKey key) { - Account account = new Account(); - account.init(key); - accounts.put(new ByteArrayWrapper(account.getAddress()), account); - return account; - } - - public String web3_clientVersion() { - - String s = "EthereumJ" + "/v" + config.projectVersion() + "/" + - System.getProperty("os.name") + "/Java1.7/" + config.projectVersionModifier() + "-" + BuildInfo.buildHash; - if (logger.isDebugEnabled()) logger.debug("web3_clientVersion(): " + s); - return s; - }; - - public String web3_sha3(String data) throws Exception { - String s = null; - try { - byte[] result = HashUtil.sha3(TypeConverter.StringHexToByteArray(data)); - return s = TypeConverter.toJsonHex(result); - } finally { - if (logger.isDebugEnabled()) logger.debug("web3_sha3(" + data + "): " + s); - } - } - - public String net_version() { - String s = null; - try { - return s = eth_protocolVersion(); - } finally { - if (logger.isDebugEnabled()) logger.debug("net_version(): " + s); - } - } - - public String net_peerCount(){ - String s = null; - try { - int n = channelManager.getActivePeers().size(); - return s = TypeConverter.toJsonHex(n); - } finally { - if (logger.isDebugEnabled()) logger.debug("net_peerCount(): " + s); - } - } - - public boolean net_listening() { - Boolean s = null; - try { - return s = peerServer.isListening(); - }finally { - if (logger.isDebugEnabled()) logger.debug("net_listening(): " + s); - } - } - - public String eth_protocolVersion(){ - String s = null; - try { - int version = 0; - for (Capability capability : configCapabilities.getConfigCapabilities()) { - if (capability.isEth()) { - version = max(version, capability.getVersion()); - } - } - return s = Integer.toString(version); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_protocolVersion(): " + s); - } - } - - public SyncingResult eth_syncing(){ - SyncingResult s = new SyncingResult(); - try { - s.startingBlock = TypeConverter.toJsonHex(initialBlockNumber); - s.currentBlock = TypeConverter.toJsonHex(blockchain.getBestBlock().getNumber()); - s.highestBlock = TypeConverter.toJsonHex(syncManager.getLastKnownBlockNumber()); - - return s; - }finally { - if (logger.isDebugEnabled()) logger.debug("eth_syncing(): " + s); - } - }; - - public String eth_coinbase() { - String s = null; - try { - return s = toJsonHex(blockchain.getMinerCoinbase()); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_coinbase(): " + s); - } - } - - public boolean eth_mining() { - Boolean s = null; - try { - return s = blockMiner.isMining(); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_mining(): " + s); - } - } - - - public String eth_hashrate() { - String s = null; - try { - return s = null; - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_hashrate(): " + s); - } - } - - public String eth_gasPrice(){ - String s = null; - try { - return s = TypeConverter.toJsonHex(eth.getGasPrice()); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_gasPrice(): " + s); - } - } - - public String[] eth_accounts() { - String[] s = null; - try { - return s = personal_listAccounts(); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_accounts(): " + Arrays.toString(s)); - } - } - - public String eth_blockNumber(){ - String s = null; - try { - Block bestBlock = blockchain.getBestBlock(); - long b = 0; - if (bestBlock != null) { - b = bestBlock.getNumber(); - } - return s = TypeConverter.toJsonHex(b); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_blockNumber(): " + s); - } - } - - - public String eth_getBalance(String address, String blockId) throws Exception { - String s = null; - try { - byte[] addressAsByteArray = TypeConverter.StringHexToByteArray(address); - BigInteger balance = getRepoByJsonBlockId(blockId).getBalance(addressAsByteArray); - return s = TypeConverter.toJsonHex(balance); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_getBalance(" + address + ", " + blockId + "): " + s); - } - } - - public String eth_getBalance(String address) throws Exception { - String s = null; - try { - return s = eth_getBalance(address, "latest"); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_getBalance(" + address + "): " + s); - } - } - - @Override - public String eth_getStorageAt(String address, String storageIdx, String blockId) throws Exception { - String s = null; - try { - byte[] addressAsByteArray = StringHexToByteArray(address); - DataWord storageValue = getRepoByJsonBlockId(blockId). - getStorageValue(addressAsByteArray, new DataWord(StringHexToByteArray(storageIdx))); - return s = TypeConverter.toJsonHex(storageValue.getData()); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_getStorageAt(" + address + ", " + storageIdx + ", " + blockId + "): " + s); - } - } - - @Override - public String eth_getTransactionCount(String address, String blockId) throws Exception { - String s = null; - try { - byte[] addressAsByteArray = TypeConverter.StringHexToByteArray(address); - BigInteger nonce = getRepoByJsonBlockId(blockId).getNonce(addressAsByteArray); - return s = TypeConverter.toJsonHex(nonce); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_getTransactionCount(" + address + ", " + blockId + "): " + s); - } - } - - public String eth_getBlockTransactionCountByHash(String blockHash) throws Exception { - String s = null; - try { - Block b = getBlockByJSonHash(blockHash); - if (b == null) return null; - long n = b.getTransactionsList().size(); - return s = TypeConverter.toJsonHex(n); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_getBlockTransactionCountByHash(" + blockHash + "): " + s); - } - } - - public String eth_getBlockTransactionCountByNumber(String bnOrId) throws Exception { - String s = null; - try { - List list = getTransactionsByJsonBlockId(bnOrId); - if (list == null) return null; - long n = list.size(); - return s = TypeConverter.toJsonHex(n); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_getBlockTransactionCountByNumber(" + bnOrId + "): " + s); - } - } - - public String eth_getUncleCountByBlockHash(String blockHash) throws Exception { - String s = null; - try { - Block b = getBlockByJSonHash(blockHash); - if (b == null) return null; - long n = b.getUncleList().size(); - return s = TypeConverter.toJsonHex(n); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_getUncleCountByBlockHash(" + blockHash + "): " + s); - } - } - - public String eth_getUncleCountByBlockNumber(String bnOrId) throws Exception { - String s = null; - try { - Block b = getByJsonBlockId(bnOrId); - if (b == null) return null; - long n = b.getUncleList().size(); - return s = TypeConverter.toJsonHex(n); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_getUncleCountByBlockNumber(" + bnOrId + "): " + s); - } - } - - public String eth_getCode(String address, String blockId) throws Exception { - String s = null; - try { - byte[] addressAsByteArray = TypeConverter.StringHexToByteArray(address); - byte[] code = getRepoByJsonBlockId(blockId).getCode(addressAsByteArray); - return s = TypeConverter.toJsonHex(code); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_getCode(" + address + ", " + blockId + "): " + s); - } - } - - public String eth_sign(String addr,String data) throws Exception { - String s = null; - try { - String ha = JSonHexToHex(addr); - Account account = getAccount(ha); - - if (account==null) - throw new Exception("Inexistent account"); - - // Todo: is not clear from the spec what hash function must be used to sign - byte[] masgHash= HashUtil.sha3(TypeConverter.StringHexToByteArray(data)); - ECKey.ECDSASignature signature = account.getEcKey().sign(masgHash); - // Todo: is not clear if result should be RlpEncoded or serialized by other means - byte[] rlpSig = RLP.encode(signature); - return s = TypeConverter.toJsonHex(rlpSig); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_sign(" + addr + ", " + data + "): " + s); - } - } - - public String eth_sendTransaction(CallArguments args) throws Exception { - - String s = null; - try { - Account account = getAccount(JSonHexToHex(args.from)); - - if (account == null) - throw new Exception("From address private key could not be found in this node"); - - if (args.data != null && args.data.startsWith("0x")) - args.data = args.data.substring(2); - - Transaction tx = new Transaction( - args.nonce != null ? StringHexToByteArray(args.nonce) : bigIntegerToBytes(pendingState.getRepository().getNonce(account.getAddress())), - args.gasPrice != null ? StringHexToByteArray(args.gasPrice) : ByteUtil.longToBytesNoLeadZeroes(eth.getGasPrice()), - args.gas != null ? StringHexToByteArray(args.gas) : ByteUtil.longToBytes(90_000), - args.to != null ? StringHexToByteArray(args.to) : EMPTY_BYTE_ARRAY, - args.value != null ? StringHexToByteArray(args.value) : EMPTY_BYTE_ARRAY, - args.data != null ? StringHexToByteArray(args.data) : EMPTY_BYTE_ARRAY, - eth.getChainIdForNextBlock()); - tx.sign(account.getEcKey().getPrivKeyBytes()); - - eth.submitTransaction(tx); - - return s = TypeConverter.toJsonHex(tx.getHash()); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_sendTransaction(" + args + "): " + s); - } - } - - public String eth_sendTransaction(String from, String to, String gas, - String gasPrice, String value,String data,String nonce) throws Exception { - String s = null; - try { - Transaction tx = new Transaction( - TypeConverter.StringHexToByteArray(nonce), - TypeConverter.StringHexToByteArray(gasPrice), - TypeConverter.StringHexToByteArray(gas), - TypeConverter.StringHexToByteArray(to), /*receiveAddress*/ - TypeConverter.StringHexToByteArray(value), - TypeConverter.StringHexToByteArray(data), - eth.getChainIdForNextBlock()); - - Account account = getAccount(from); - if (account == null) throw new RuntimeException("No account " + from); - - tx.sign(account.getEcKey()); - - eth.submitTransaction(tx); - - return s = TypeConverter.toJsonHex(tx.getHash()); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_sendTransaction(" + - "from = [" + from + "], to = [" + to + "], gas = [" + gas + "], gasPrice = [" + gasPrice + - "], value = [" + value + "], data = [" + data + "], nonce = [" + nonce + "]" + "): " + s); - } - } - - public String eth_sendRawTransaction(String rawData) throws Exception { - String s = null; - try { - Transaction tx = new Transaction(StringHexToByteArray(rawData)); - tx.verify(); - - eth.submitTransaction(tx); - - return s = TypeConverter.toJsonHex(tx.getHash()); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_sendRawTransaction(" + rawData + "): " + s); - } - } - - public TransactionReceipt createCallTxAndExecute(CallArguments args, Block block) throws Exception { - Repository repository = ((Repository) worldManager.getRepository()) - .getSnapshotTo(block.getStateRoot()) - .startTracking(); - - return createCallTxAndExecute(args, block, repository, worldManager.getBlockStore()); - } - - public TransactionReceipt createCallTxAndExecute(CallArguments args, Block block, Repository repository, BlockStore blockStore) throws Exception { - BinaryCallArguments bca = new BinaryCallArguments(); - bca.setArguments(args); - Transaction tx = CallTransaction.createRawTransaction(0, - bca.gasPrice, - bca.gasLimit, - bca.toAddress, - bca.value, - bca.data); - - // put mock signature if not present - if (tx.getSignature() == null) { - tx.sign(ECKey.fromPrivate(new byte[32])); - } - - try { - TransactionExecutor executor = new TransactionExecutor - (tx, block.getCoinbase(), repository, blockStore, - programInvokeFactory, block, new EthereumListenerAdapter(), 0) - .withCommonConfig(commonConfig) - .setLocalCall(true); - - executor.init(); - executor.execute(); - executor.go(); - executor.finalization(); - - return executor.getReceipt(); - } finally { - repository.rollback(); - } - } - - public String eth_call(CallArguments args, String bnOrId) throws Exception { - - String s = null; - try { - TransactionReceipt res; - if ("pending".equals(bnOrId)) { - Block pendingBlock = blockchain.createNewBlock(blockchain.getBestBlock(), pendingState.getPendingTransactions(), Collections.emptyList()); - res = createCallTxAndExecute(args, pendingBlock, pendingState.getRepository(), worldManager.getBlockStore()); - } else { - res = createCallTxAndExecute(args, getByJsonBlockId(bnOrId)); - } - return s = TypeConverter.toJsonHex(res.getExecutionResult()); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_call(" + args + "): " + s); - } - } - - public String eth_estimateGas(CallArguments args) throws Exception { - String s = null; - try { - TransactionReceipt res = createCallTxAndExecute(args, blockchain.getBestBlock()); - return s = TypeConverter.toJsonHex(res.getGasUsed()); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_estimateGas(" + args + "): " + s); - } - } - - - public BlockResult getBlockResult(Block b, boolean fullTx) { - if (b==null) - return null; - boolean isPending = ByteUtil.byteArrayToLong(b.getNonce()) == 0; - BlockResult br = new BlockResult(); - br.number = isPending ? null : TypeConverter.toJsonHex(b.getNumber()); - br.hash = isPending ? null : TypeConverter.toJsonHex(b.getHash()); - br.parentHash = TypeConverter.toJsonHex(b.getParentHash()); - br.nonce = isPending ? null : TypeConverter.toJsonHex(b.getNonce()); - br.sha3Uncles= TypeConverter.toJsonHex(b.getUnclesHash()); - br.logsBloom = isPending ? null : TypeConverter.toJsonHex(b.getLogBloom()); - br.transactionsRoot =TypeConverter.toJsonHex(b.getTxTrieRoot()); - br.stateRoot = TypeConverter.toJsonHex(b.getStateRoot()); - br.receiptsRoot =TypeConverter.toJsonHex(b.getReceiptsRoot()); - br.miner = isPending ? null : TypeConverter.toJsonHex(b.getCoinbase()); - br.difficulty = TypeConverter.toJsonHex(b.getDifficulty()); - br.totalDifficulty = TypeConverter.toJsonHex(blockchain.getTotalDifficulty()); - if (b.getExtraData() != null) - br.extraData =TypeConverter.toJsonHex(b.getExtraData()); - br.size = TypeConverter.toJsonHex(b.getEncoded().length); - br.gasLimit =TypeConverter.toJsonHex(b.getGasLimit()); - br.gasUsed =TypeConverter.toJsonHex(b.getGasUsed()); - br.timestamp =TypeConverter.toJsonHex(b.getTimestamp()); - - List txes = new ArrayList<>(); - if (fullTx) { - for (int i = 0; i < b.getTransactionsList().size(); i++) { - txes.add(new TransactionResultDTO(b, i, b.getTransactionsList().get(i))); - } - } else { - for (Transaction tx : b.getTransactionsList()) { - txes.add(toJsonHex(tx.getHash())); - } - } - br.transactions = txes.toArray(); - - List ul = new ArrayList<>(); - for (BlockHeader header : b.getUncleList()) { - ul.add(toJsonHex(header.getHash())); - } - br.uncles = ul.toArray(new String[ul.size()]); - - return br; - } - - public BlockResult eth_getBlockByHash(String blockHash,Boolean fullTransactionObjects) throws Exception { - BlockResult s = null; - try { - Block b = getBlockByJSonHash(blockHash); - return getBlockResult(b, fullTransactionObjects); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_getBlockByHash(" + blockHash + ", " + fullTransactionObjects + "): " + s); - } - } - - public BlockResult eth_getBlockByNumber(String bnOrId,Boolean fullTransactionObjects) throws Exception { - BlockResult s = null; - try { - Block b; - if ("pending".equalsIgnoreCase(bnOrId)) { - b = blockchain.createNewBlock(blockchain.getBestBlock(), pendingState.getPendingTransactions(), Collections.emptyList()); - } else { - b = getByJsonBlockId(bnOrId); - } - return s = (b == null ? null : getBlockResult(b, fullTransactionObjects)); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_getBlockByNumber(" + bnOrId + ", " + fullTransactionObjects + "): " + s); - } - } - - public TransactionResultDTO eth_getTransactionByHash(String transactionHash) throws Exception { - TransactionResultDTO s = null; - try { - byte[] txHash = StringHexToByteArray(transactionHash); - Block block = null; - - TransactionInfo txInfo = blockchain.getTransactionInfo(txHash); - - if (txInfo == null) { - TransactionReceipt receipt = pendingReceipts.get(new ByteArrayWrapper(txHash)); - - if (receipt == null) { - return null; - } - txInfo = new TransactionInfo(receipt); - } else { - block = blockchain.getBlockByHash(txInfo.getBlockHash()); - // need to return txes only from main chain - Block mainBlock = blockchain.getBlockByNumber(block.getNumber()); - if (!Arrays.equals(block.getHash(), mainBlock.getHash())) { - return null; - } - txInfo.setTransaction(block.getTransactionsList().get(txInfo.getIndex())); - } - - return s = new TransactionResultDTO(block, txInfo.getIndex(), txInfo.getReceipt().getTransaction()); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_getTransactionByHash(" + transactionHash + "): " + s); - } - } - - public TransactionResultDTO eth_getTransactionByBlockHashAndIndex(String blockHash,String index) throws Exception { - TransactionResultDTO s = null; - try { - Block b = getBlockByJSonHash(blockHash); - if (b == null) return null; - int idx = JSonHexToInt(index); - if (idx >= b.getTransactionsList().size()) return null; - Transaction tx = b.getTransactionsList().get(idx); - return s = new TransactionResultDTO(b, idx, tx); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_getTransactionByBlockHashAndIndex(" + blockHash + ", " + index + "): " + s); - } - } - - public TransactionResultDTO eth_getTransactionByBlockNumberAndIndex(String bnOrId, String index) throws Exception { - TransactionResultDTO s = null; - try { - Block b = getByJsonBlockId(bnOrId); - List txs = getTransactionsByJsonBlockId(bnOrId); - if (txs == null) return null; - int idx = JSonHexToInt(index); - if (idx >= txs.size()) return null; - Transaction tx = txs.get(idx); - return s = new TransactionResultDTO(b, idx, tx); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_getTransactionByBlockNumberAndIndex(" + bnOrId + ", " + index + "): " + s); - } - } - - public TransactionReceiptDTO eth_getTransactionReceipt(String transactionHash) throws Exception { - TransactionReceiptDTO s = null; - try { - byte[] hash = TypeConverter.StringHexToByteArray(transactionHash); - - TransactionReceipt pendingReceipt = pendingReceipts.get(new ByteArrayWrapper(hash)); - - TransactionInfo txInfo; - Block block; - - if (pendingReceipt != null) { - txInfo = new TransactionInfo(pendingReceipt); - block = null; - } else { - txInfo = blockchain.getTransactionInfo(hash); - - if (txInfo == null) - return null; - - block = blockchain.getBlockByHash(txInfo.getBlockHash()); - - // need to return txes only from main chain - Block mainBlock = blockchain.getBlockByNumber(block.getNumber()); - if (!Arrays.equals(block.getHash(), mainBlock.getHash())) { - return null; - } - } - - return s = new TransactionReceiptDTO(block, txInfo); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_getTransactionReceipt(" + transactionHash + "): " + s); - } - } - - @Override - public TransactionReceiptDTOExt ethj_getTransactionReceipt(String transactionHash) throws Exception { - TransactionReceiptDTOExt s = null; - try { - byte[] hash = TypeConverter.StringHexToByteArray(transactionHash); - - TransactionReceipt pendingReceipt = pendingReceipts.get(new ByteArrayWrapper(hash)); - - TransactionInfo txInfo; - Block block; - - if (pendingReceipt != null) { - txInfo = new TransactionInfo(pendingReceipt); - block = null; - } else { - txInfo = blockchain.getTransactionInfo(hash); - - if (txInfo == null) - return null; - - block = blockchain.getBlockByHash(txInfo.getBlockHash()); - - // need to return txes only from main chain - Block mainBlock = blockchain.getBlockByNumber(block.getNumber()); - if (!Arrays.equals(block.getHash(), mainBlock.getHash())) { - return null; - } - } - - return s = new TransactionReceiptDTOExt(block, txInfo); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_getTransactionReceipt(" + transactionHash + "): " + s); - } - } - - @Override - public BlockResult eth_getUncleByBlockHashAndIndex(String blockHash, String uncleIdx) throws Exception { - BlockResult s = null; - try { - Block block = blockchain.getBlockByHash(StringHexToByteArray(blockHash)); - if (block == null) return null; - int idx = JSonHexToInt(uncleIdx); - if (idx >= block.getUncleList().size()) return null; - BlockHeader uncleHeader = block.getUncleList().get(idx); - Block uncle = blockchain.getBlockByHash(uncleHeader.getHash()); - if (uncle == null) { - uncle = new Block(uncleHeader, Collections.emptyList(), Collections.emptyList()); - } - return s = getBlockResult(uncle, false); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_getUncleByBlockHashAndIndex(" + blockHash + ", " + uncleIdx + "): " + s); - } - } - - @Override - public BlockResult eth_getUncleByBlockNumberAndIndex(String blockId, String uncleIdx) throws Exception { - BlockResult s = null; - try { - Block block = getByJsonBlockId(blockId); - return s = block == null ? null : - eth_getUncleByBlockHashAndIndex(toJsonHex(block.getHash()), uncleIdx); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_getUncleByBlockNumberAndIndex(" + blockId + ", " + uncleIdx + "): " + s); - } - } - - @Override - public String[] eth_getCompilers() { - String[] s = null; - try { - return s = new String[] {"solidity"}; - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_getCompilers(): " + Arrays.toString(s)); - } - } - - @Override - public CompilationResult eth_compileLLL(String contract) { - throw new UnsupportedOperationException("LLL compiler not supported"); - } - - @Override - public CompilationResult eth_compileSolidity(String contract) throws Exception { - CompilationResult s = null; - try { - SolidityCompiler.Result res = solidityCompiler.compileSrc( - contract.getBytes(), true, true, SolidityCompiler.Options.ABI, SolidityCompiler.Options.BIN); - if (res.isFailed()) { - throw new RuntimeException("Compilation error: " + res.errors); - } - org.ethereum.solidity.compiler.CompilationResult result = org.ethereum.solidity.compiler.CompilationResult.parse(res.output); - CompilationResult ret = new CompilationResult(); - org.ethereum.solidity.compiler.CompilationResult.ContractMetadata contractMetadata = result.contracts.values().iterator().next(); - ret.code = toJsonHex(contractMetadata.bin); - ret.info = new CompilationInfo(); - ret.info.source = contract; - ret.info.language = "Solidity"; - ret.info.languageVersion = "0"; - ret.info.compilerVersion = result.version; - ret.info.abiDefinition = new CallTransaction.Contract(contractMetadata.abi).functions; - return s = ret; - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_compileSolidity(" + contract + ")" + s); - } - } - - @Override - public CompilationResult eth_compileSerpent(String contract){ - throw new UnsupportedOperationException("Serpent compiler not supported"); - } - - @Override - public String eth_resend() { - throw new UnsupportedOperationException("JSON RPC method eth_resend not implemented yet"); - } - - @Override - public String eth_pendingTransactions() { - throw new UnsupportedOperationException("JSON RPC method eth_pendingTransactions not implemented yet"); - } - - static class Filter { - static final int MAX_EVENT_COUNT = 1024; // prevent OOM when Filers are forgotten - static abstract class FilterEvent { - public abstract Object getJsonEventObject(); - } - List events = new LinkedList<>(); - - public synchronized boolean hasNew() { return !events.isEmpty();} - - public synchronized Object[] poll() { - Object[] ret = new Object[events.size()]; - for (int i = 0; i < ret.length; i++) { - ret[i] = events.get(i).getJsonEventObject(); - } - this.events.clear(); - return ret; - } - - protected synchronized void add(FilterEvent evt) { - events.add(evt); - if (events.size() > MAX_EVENT_COUNT) events.remove(0); - } - - public void newBlockReceived(Block b) {} - public void newPendingTx(Transaction tx) {} - } - - static class NewBlockFilter extends Filter { - class NewBlockFilterEvent extends FilterEvent { - public final Block b; - NewBlockFilterEvent(Block b) {this.b = b;} - - @Override - public String getJsonEventObject() { - return toJsonHex(b.getHash()); - } - } - - public void newBlockReceived(Block b) { - add(new NewBlockFilterEvent(b)); - } - } - - static class PendingTransactionFilter extends Filter { - class PendingTransactionFilterEvent extends FilterEvent { - private final Transaction tx; - - PendingTransactionFilterEvent(Transaction tx) {this.tx = tx;} - - @Override - public String getJsonEventObject() { - return toJsonHex(tx.getHash()); - } - } - - public void newPendingTx(Transaction tx) { - add(new PendingTransactionFilterEvent(tx)); - } - } - - class JsonLogFilter extends Filter { - class LogFilterEvent extends FilterEvent { - private final LogFilterElement el; - - LogFilterEvent(LogFilterElement el) { - this.el = el; - } - - @Override - public LogFilterElement getJsonEventObject() { - return el; - } - } - - LogFilter logFilter; - boolean onNewBlock; - boolean onPendingTx; - - public JsonLogFilter(LogFilter logFilter) { - this.logFilter = logFilter; - } - - void onLogMatch(LogInfo logInfo, Block b, int txIndex, Transaction tx, int logIdx) { - add(new LogFilterEvent(new LogFilterElement(logInfo, b, txIndex, tx, logIdx))); - } - - void onTransactionReceipt(TransactionReceipt receipt, Block b, int txIndex) { - if (logFilter.matchBloom(receipt.getBloomFilter())) { - int logIdx = 0; - for (LogInfo logInfo : receipt.getLogInfoList()) { - if (logFilter.matchBloom(logInfo.getBloom()) && logFilter.matchesExactly(logInfo)) { - onLogMatch(logInfo, b, txIndex, receipt.getTransaction(), logIdx); - } - logIdx++; - } - } - } - - void onTransaction(Transaction tx, Block b, int txIndex) { - if (logFilter.matchesContractAddress(tx.getReceiveAddress())) { - TransactionInfo txInfo = blockchain.getTransactionInfo(tx.getHash()); - onTransactionReceipt(txInfo.getReceipt(), b, txIndex); - } - } - - void onBlock(Block b) { - if (logFilter.matchBloom(new Bloom(b.getLogBloom()))) { - int txIdx = 0; - for (Transaction tx : b.getTransactionsList()) { - onTransaction(tx, b, txIdx); - txIdx++; - } - } - } - - @Override - public void newBlockReceived(Block b) { - if (onNewBlock) onBlock(b); - } - - @Override - public void newPendingTx(Transaction tx) { - // TODO add TransactionReceipt for PendingTx -// if (onPendingTx) - } - } - - @Override - public String eth_newFilter(FilterRequest fr) throws Exception { - String str = null; - try { - LogFilter logFilter = new LogFilter(); - - if (fr.address instanceof String) { - logFilter.withContractAddress(StringHexToByteArray((String) fr.address)); - } else if (fr.address instanceof String[]) { - List addr = new ArrayList<>(); - for (String s : ((String[]) fr.address)) { - addr.add(StringHexToByteArray(s)); - } - logFilter.withContractAddress(addr.toArray(new byte[0][])); - } - - if (fr.topics != null) { - for (Object topic : fr.topics) { - if (topic == null) { - logFilter.withTopic(null); - } else if (topic instanceof String) { - logFilter.withTopic(new DataWord(StringHexToByteArray((String) topic)).getData()); - } else if (topic instanceof String[]) { - List t = new ArrayList<>(); - for (String s : ((String[]) topic)) { - t.add(new DataWord(StringHexToByteArray(s)).getData()); - } - logFilter.withTopic(t.toArray(new byte[0][])); - } - } - } - - JsonLogFilter filter = new JsonLogFilter(logFilter); - int id = filterCounter.getAndIncrement(); - installedFilters.put(id, filter); - - Block blockFrom = fr.fromBlock == null ? null : getByJsonBlockId(fr.fromBlock); - Block blockTo = fr.toBlock == null ? null : getByJsonBlockId(fr.toBlock); - - if (blockFrom != null) { - // need to add historical data - blockTo = blockTo == null ? blockchain.getBestBlock() : blockTo; - for (long blockNum = blockFrom.getNumber(); blockNum <= blockTo.getNumber(); blockNum++) { - filter.onBlock(blockchain.getBlockByNumber(blockNum)); - } - } - - // the following is not precisely documented - if ("pending".equalsIgnoreCase(fr.fromBlock) || "pending".equalsIgnoreCase(fr.toBlock)) { - filter.onPendingTx = true; - } else if ("latest".equalsIgnoreCase(fr.fromBlock) || "latest".equalsIgnoreCase(fr.toBlock)) { - filter.onNewBlock = true; - } - - return str = toJsonHex(id); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_newFilter(" + fr + "): " + str); - } - } - - @Override - public String eth_newBlockFilter() { - String s = null; - try { - int id = filterCounter.getAndIncrement(); - installedFilters.put(id, new NewBlockFilter()); - return s = toJsonHex(id); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_newBlockFilter(): " + s); - } - } - - @Override - public String eth_newPendingTransactionFilter() { - String s = null; - try { - int id = filterCounter.getAndIncrement(); - installedFilters.put(id, new PendingTransactionFilter()); - return s = toJsonHex(id); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_newPendingTransactionFilter(): " + s); - } - } - - @Override - public boolean eth_uninstallFilter(String id) { - Boolean s = null; - try { - if (id == null) return false; - return s = installedFilters.remove(StringHexToBigInteger(id).intValue()) != null; - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_uninstallFilter(" + id + "): " + s); - } - } - - @Override - public Object[] eth_getFilterChanges(String id) { - Object[] s = null; - try { - Filter filter = installedFilters.get(StringHexToBigInteger(id).intValue()); - if (filter == null) return null; - return s = filter.poll(); - } finally { - if (logger.isDebugEnabled()) logger.debug("eth_getFilterChanges(" + id + "): " + Arrays.toString(s)); - } - } - - @Override - public Object[] eth_getFilterLogs(String id) { - logger.debug("eth_getFilterLogs ..."); - return eth_getFilterChanges(id); - } - - @Override - public Object[] eth_getLogs(FilterRequest fr) throws Exception { - logger.debug("eth_getLogs ..."); - String id = eth_newFilter(fr); - Object[] ret = eth_getFilterChanges(id); - eth_uninstallFilter(id); - return ret; - } - - @Override - public String eth_getWork() { - throw new UnsupportedOperationException("JSON RPC method eth_getWork not implemented yet"); - } - - @Override - public String eth_submitWork() { - throw new UnsupportedOperationException("JSON RPC method eth_submitWork not implemented yet"); - } - - @Override - public String eth_submitHashrate() { - throw new UnsupportedOperationException("JSON RPC method eth_submitHashrate not implemented yet"); - } - - @Override - public String db_putString() { - throw new UnsupportedOperationException("JSON RPC method db_putString not implemented yet"); - } - - @Override - public String db_getString() { - throw new UnsupportedOperationException("JSON RPC method db_getString not implemented yet"); - } - - @Override - public String db_putHex() { - throw new UnsupportedOperationException("JSON RPC method db_putHex not implemented yet"); - } - - @Override - public String db_getHex() { - throw new UnsupportedOperationException("JSON RPC method db_getHex not implemented yet"); - } - - @Override - public String shh_post() { - throw new UnsupportedOperationException("JSON RPC method shh_post not implemented yet"); - } - - @Override - public String shh_version() { - throw new UnsupportedOperationException("JSON RPC method shh_version not implemented yet"); - } - - @Override - public String shh_newIdentity() { - throw new UnsupportedOperationException("JSON RPC method shh_newIdentity not implemented yet"); - } - - @Override - public String shh_hasIdentity() { - throw new UnsupportedOperationException("JSON RPC method shh_hasIdentity not implemented yet"); - } - - @Override - public String shh_newGroup() { - throw new UnsupportedOperationException("JSON RPC method shh_newGroup not implemented yet"); - } - - @Override - public String shh_addToGroup() { - throw new UnsupportedOperationException("JSON RPC method shh_addToGroup not implemented yet"); - } - - @Override - public String shh_newFilter() { - throw new UnsupportedOperationException("JSON RPC method shh_newFilter not implemented yet"); - } - - @Override - public String shh_uninstallFilter() { - throw new UnsupportedOperationException("JSON RPC method shh_uninstallFilter not implemented yet"); - } - - @Override - public String shh_getFilterChanges() { - throw new UnsupportedOperationException("JSON RPC method shh_getFilterChanges not implemented yet"); - } - - @Override - public String shh_getMessages() { - throw new UnsupportedOperationException("JSON RPC method shh_getMessages not implemented yet"); - } - - @Override - public boolean admin_addPeer(String s) { - eth.connect(new Node(s)); - return true; - } - - @Override - public String admin_exportChain() { - throw new UnsupportedOperationException("JSON RPC method admin_exportChain not implemented yet"); - } - - @Override - public String admin_importChain() { - throw new UnsupportedOperationException("JSON RPC method admin_importChain not implemented yet"); - } - - @Override - public String admin_sleepBlocks() { - throw new UnsupportedOperationException("JSON RPC method admin_sleepBlocks not implemented yet"); - } - - @Override - public String admin_verbosity() { - throw new UnsupportedOperationException("JSON RPC method admin_verbosity not implemented yet"); - } - - @Override - public String admin_setSolc() { - throw new UnsupportedOperationException("JSON RPC method admin_setSolc not implemented yet"); - } - - @Override - public String admin_startRPC() { - throw new UnsupportedOperationException("JSON RPC method admin_startRPC not implemented yet"); - } - - @Override - public String admin_stopRPC() { - throw new UnsupportedOperationException("JSON RPC method admin_stopRPC not implemented yet"); - } - - @Override - public String admin_setGlobalRegistrar() { - throw new UnsupportedOperationException("JSON RPC method admin_setGlobalRegistrar not implemented yet"); - } - - @Override - public String admin_setHashReg() { - throw new UnsupportedOperationException("JSON RPC method admin_setHashReg not implemented yet"); - } - - @Override - public String admin_setUrlHint() { - throw new UnsupportedOperationException("JSON RPC method admin_setUrlHint not implemented yet"); - } - - @Override - public String admin_saveInfo() { - throw new UnsupportedOperationException("JSON RPC method admin_saveInfo not implemented yet"); - } - - @Override - public String admin_register() { - throw new UnsupportedOperationException("JSON RPC method admin_register not implemented yet"); - } - - @Override - public String admin_registerUrl() { - throw new UnsupportedOperationException("JSON RPC method admin_registerUrl not implemented yet"); - } - - @Override - public String admin_startNatSpec() { - throw new UnsupportedOperationException("JSON RPC method admin_startNatSpec not implemented yet"); - } - - @Override - public String admin_stopNatSpec() { - throw new UnsupportedOperationException("JSON RPC method admin_stopNatSpec not implemented yet"); - } - - @Override - public String admin_getContractInfo() { - throw new UnsupportedOperationException("JSON RPC method admin_getContractInfo not implemented yet"); - } - - @Override - public String admin_httpGet() { - throw new UnsupportedOperationException("JSON RPC method admin_httpGet not implemented yet"); - } - - @Override - public String admin_nodeInfo() { - throw new UnsupportedOperationException("JSON RPC method admin_nodeInfo not implemented yet"); - } - - @Override - public String admin_peers() { - throw new UnsupportedOperationException("JSON RPC method admin_peers not implemented yet"); - } - - @Override - public String admin_datadir() { - throw new UnsupportedOperationException("JSON RPC method admin_datadir not implemented yet"); - } - - @Override - public String net_addPeer() { - throw new UnsupportedOperationException("JSON RPC method net_addPeer not implemented yet"); - } - - @Override - public boolean miner_start() { - blockMiner.startMining(); - return true; - } - - @Override - public boolean miner_stop() { - blockMiner.stopMining(); - return true; - } - - @Override - public boolean miner_setEtherbase(String coinBase) throws Exception { - blockchain.setMinerCoinbase(TypeConverter.StringHexToByteArray(coinBase)); - return true; - } - - @Override - public boolean miner_setExtra(String data) throws Exception { - blockchain.setMinerExtraData(TypeConverter.StringHexToByteArray(data)); - return true; - } - - @Override - public boolean miner_setGasPrice(String newMinGasPrice) { - blockMiner.setMinGasPrice(TypeConverter.StringHexToBigInteger(newMinGasPrice)); - return true; - } - - @Override - public boolean miner_startAutoDAG() { - return false; - } - - @Override - public boolean miner_stopAutoDAG() { - return false; - } - - @Override - public boolean miner_makeDAG() { - return false; - } - - @Override - public String miner_hashrate() { - return "0x01"; - } - - @Override - public String debug_printBlock() { - throw new UnsupportedOperationException("JSON RPC method debug_printBlock not implemented yet"); - } - - @Override - public String debug_getBlockRlp() { - throw new UnsupportedOperationException("JSON RPC method debug_getBlockRlp not implemented yet"); - } - - @Override - public String debug_setHead() { - throw new UnsupportedOperationException("JSON RPC method debug_setHead not implemented yet"); - } - - @Override - public String debug_processBlock() { - throw new UnsupportedOperationException("JSON RPC method debug_processBlock not implemented yet"); - } - - @Override - public String debug_seedHash() { - throw new UnsupportedOperationException("JSON RPC method debug_seedHash not implemented yet"); - } - - @Override - public String debug_dumpBlock() { - throw new UnsupportedOperationException("JSON RPC method debug_dumpBlock not implemented yet"); - } - - @Override - public String debug_metrics() { - throw new UnsupportedOperationException("JSON RPC method debug_metrics not implemented yet"); - } - - @Override - public String personal_newAccount(String seed) { - String s = null; - try { - Account account = addAccount(seed); - return s = toJsonHex(account.getAddress()); - } finally { - if (logger.isDebugEnabled()) logger.debug("personal_newAccount(*****): " + s); - } - } - - @Override - public boolean personal_unlockAccount(String addr, String pass, String duration) { - String s = null; - try { - return true; - } finally { - if (logger.isDebugEnabled()) logger.debug("personal_unlockAccount(" + addr + ", ***, " + duration + "): " + s); - } - } - - @Override - public String[] personal_listAccounts() { - String[] ret = new String[accounts.size()]; - try { - int i = 0; - for (ByteArrayWrapper addr : accounts.keySet()) { - ret[i++] = toJsonHex(addr.getData()); - } - return ret; - } finally { - if (logger.isDebugEnabled()) logger.debug("personal_listAccounts(): " + Arrays.toString(ret)); - } - } -} diff --git a/ethereumj-core/src/main/java/org/ethereum/jsonrpc/TransactionReceiptDTO.java b/ethereumj-core/src/main/java/org/ethereum/jsonrpc/TransactionReceiptDTO.java deleted file mode 100644 index c235ea5c69..0000000000 --- a/ethereumj-core/src/main/java/org/ethereum/jsonrpc/TransactionReceiptDTO.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) [2016] [ ] - * This file is part of the ethereumJ library. - * - * The ethereumJ library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The ethereumJ library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the ethereumJ library. If not, see . - */ -package org.ethereum.jsonrpc; - -import org.ethereum.core.Block; -import org.ethereum.core.TransactionReceipt; -import org.ethereum.core.TransactionInfo; -import org.ethereum.util.ByteUtil; -import org.ethereum.vm.LogInfo; - -import static org.ethereum.jsonrpc.TypeConverter.toJsonHex; - -/** - * Created by Ruben on 5/1/2016. - */ -public class TransactionReceiptDTO { - - public String transactionHash; // hash of the transaction. - public int transactionIndex; // integer of the transactions index position in the block. - public String blockHash; // hash of the block where this transaction was in. - public long blockNumber; // block number where this transaction was in. - public long cumulativeGasUsed; // The total amount of gas used when this transaction was executed in the block. - public long gasUsed; //The amount of gas used by this specific transaction alone. - public String contractAddress; // The contract address created, if the transaction was a contract creation, otherwise null . - public JsonRpc.LogFilterElement[] logs; // Array of log objects, which this transaction generated. - - public TransactionReceiptDTO(Block block, TransactionInfo txInfo){ - TransactionReceipt receipt = txInfo.getReceipt(); - - transactionHash = toJsonHex(receipt.getTransaction().getHash()); - transactionIndex = txInfo.getIndex(); - cumulativeGasUsed = ByteUtil.byteArrayToLong(receipt.getCumulativeGas()); - gasUsed = ByteUtil.byteArrayToLong(receipt.getGasUsed()); - if (receipt.getTransaction().getContractAddress() != null) - contractAddress = toJsonHex(receipt.getTransaction().getContractAddress()); - logs = new JsonRpc.LogFilterElement[receipt.getLogInfoList().size()]; - if (block != null) { - blockNumber = block.getNumber(); - blockHash = toJsonHex(txInfo.getBlockHash()); - for (int i = 0; i < logs.length; i++) { - LogInfo logInfo = receipt.getLogInfoList().get(i); - logs[i] = new JsonRpc.LogFilterElement(logInfo, block, txInfo.getIndex(), - txInfo.getReceipt().getTransaction(), i); - } - } - } -} diff --git a/ethereumj-core/src/main/java/org/ethereum/jsonrpc/TransactionResultDTO.java b/ethereumj-core/src/main/java/org/ethereum/jsonrpc/TransactionResultDTO.java deleted file mode 100644 index caaa404386..0000000000 --- a/ethereumj-core/src/main/java/org/ethereum/jsonrpc/TransactionResultDTO.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) [2016] [ ] - * This file is part of the ethereumJ library. - * - * The ethereumJ library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The ethereumJ library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the ethereumJ library. If not, see . - */ -package org.ethereum.jsonrpc; - -import org.ethereum.core.Block; -import org.ethereum.core.Transaction; - -/** - * Created by Ruben on 8/1/2016. - */ -public class TransactionResultDTO { - - public String hash; - public String nonce; - public String blockHash; - public String blockNumber; - public String transactionIndex; - - public String from; - public String to; - public String gas; - public String gasPrice; - public String value; - public String input; - public String v; - public String r; - public String s; - - public TransactionResultDTO() { - } - - public TransactionResultDTO (Block b, int index, Transaction tx) { - hash = TypeConverter.toJsonHex(tx.getHash()); - nonce = TypeConverter.toJsonHex(tx.getNonce()); - blockHash = b == null ? null : TypeConverter.toJsonHex(b.getHash()); - blockNumber = b == null ? null : TypeConverter.toJsonHex(b.getNumber()); - transactionIndex = b == null ? null : TypeConverter.toJsonHex(index); - from= TypeConverter.toJsonHex(tx.getSender()); - to = tx.getReceiveAddress() == null ? null : TypeConverter.toJsonHex(tx.getReceiveAddress()); - gas = TypeConverter.toJsonHex(tx.getGasLimit()); - gasPrice = TypeConverter.toJsonHex(tx.getGasPrice()); - value = TypeConverter.toJsonHex(tx.getValue()); - input = tx.getData() != null ? TypeConverter.toJsonHex(tx.getData()) : null; - } - - @Override - public String toString() { - return "TransactionResultDTO{" + - "hash='" + hash + '\'' + - ", nonce='" + nonce + '\'' + - ", blockHash='" + blockHash + '\'' + - ", blockNumber='" + blockNumber + '\'' + - ", transactionIndex='" + transactionIndex + '\'' + - ", from='" + from + '\'' + - ", to='" + to + '\'' + - ", gas='" + gas + '\'' + - ", gasPrice='" + gasPrice + '\'' + - ", value='" + value + '\'' + - ", input='" + input + '\'' + - '}'; - } -} \ No newline at end of file diff --git a/ethereumj-core/src/main/java/org/ethereum/jsonrpc/TypeConverter.java b/ethereumj-core/src/main/java/org/ethereum/jsonrpc/TypeConverter.java deleted file mode 100644 index 1bd77757da..0000000000 --- a/ethereumj-core/src/main/java/org/ethereum/jsonrpc/TypeConverter.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) [2016] [ ] - * This file is part of the ethereumJ library. - * - * The ethereumJ library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The ethereumJ library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the ethereumJ library. If not, see . - */ -package org.ethereum.jsonrpc; - -import org.ethereum.util.ByteUtil; -import org.spongycastle.util.encoders.Hex; - -import java.math.BigInteger; - -/** - * Created by Ruben on 19/11/2015. - */ -public class TypeConverter { - - public static byte[] StringNumberAsBytes(String input) { - return ByteUtil.bigIntegerToBytes(StringDecimalToBigInteger(input)); - } - - public static BigInteger StringNumberAsBigInt(String input) throws Exception { - if (input.startsWith("0x")) - return TypeConverter.StringHexToBigInteger(input); - else - return TypeConverter.StringDecimalToBigInteger(input); - } - - public static BigInteger StringHexToBigInteger(String input) { - String hexa = input.startsWith("0x") ? input.substring(2) : input; - return new BigInteger(hexa, 16); - } - - private static BigInteger StringDecimalToBigInteger(String input) { - return new BigInteger(input); - } - - public static byte[] StringHexToByteArray(String x) throws Exception { - if (x.startsWith("0x")) { - x = x.substring(2); - } - if (x.length() % 2 != 0) x = "0" + x; - return Hex.decode(x); - } - - public static String toJsonHex(byte[] x) { - return "0x"+Hex.toHexString(x); - } - - public static String toJsonHex(String x) { - return x.startsWith("0x") ? x : "0x" + x; - } - - public static String toJsonHex(long n) { - return "0x"+Long.toHexString(n); - } - - public static String toJsonHex(BigInteger n) { - return "0x"+ n.toString(16); - } -} diff --git a/ethereumj-core/src/main/java/org/ethereum/listener/BlockReplay.java b/ethereumj-core/src/main/java/org/ethereum/listener/BlockReplay.java new file mode 100644 index 0000000000..fbb25ad1db --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/listener/BlockReplay.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.listener; + +import org.apache.commons.collections4.queue.CircularFifoQueue; +import org.ethereum.core.*; +import org.ethereum.db.BlockStore; +import org.ethereum.db.TransactionStore; +import org.ethereum.net.eth.message.StatusMessage; +import org.ethereum.net.message.Message; +import org.ethereum.net.p2p.HelloMessage; +import org.ethereum.net.rlpx.Node; +import org.ethereum.net.server.Channel; +import org.ethereum.util.FastByteComparisons; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import static org.ethereum.sync.BlockDownloader.MAX_IN_REQUEST; + +/** + * Class capable of replaying stored blocks prior to 'going online' and + * notifying on newly imported blocks + * + * All other EthereumListener events are just forwarded to the supplied listener. + * + * For example of usage, look at {@link org.ethereum.samples.EventListenerSample} + * + * Created by Anton Nashatyrev on 18.07.2016. + */ +public class BlockReplay extends EthereumListenerAdapter { + private static final Logger logger = LoggerFactory.getLogger("events"); + private static final int HALF_BUFFER = MAX_IN_REQUEST; + + BlockStore blockStore; + TransactionStore transactionStore; + + EthereumListener listener; + + long firstBlock; + + boolean replayComplete = false; + Block lastReplayedBlock; + CircularFifoQueue onBlockBuffer = new CircularFifoQueue<>(HALF_BUFFER * 2); + + public BlockReplay(BlockStore blockStore, TransactionStore transactionStore, EthereumListener listener, long firstBlock) { + this.blockStore = blockStore; + this.transactionStore = transactionStore; + this.listener = listener; + this.firstBlock = firstBlock; + } + + /** + * Replay blocks asynchronously + */ + public void replayAsync() { + new Thread(this::replay).start(); + } + + /** + * Replay blocks synchronously + */ + public void replay() { + long lastBlock = blockStore.getMaxNumber(); + logger.info("Replaying blocks from " + firstBlock + ", current best block: " + lastBlock); + int cnt = 0; + long num = firstBlock; + while(!replayComplete) { + for (; num <= lastBlock; num++) { + replayBlock(num); + cnt++; + if (cnt % 1000 == 0) { + logger.info("Replayed " + cnt + " blocks so far. Current block: " + num); + } + } + + synchronized (this) { + if (onBlockBuffer.size() < onBlockBuffer.maxSize()) { + replayComplete = true; + } else { + // So we'll have half of the buffer for new blocks until not synchronized replay finish + long newLastBlock = blockStore.getMaxNumber() - HALF_BUFFER; + if (lastBlock >= newLastBlock) { + replayComplete = true; + } else { + lastBlock = newLastBlock; + } + } + } + } + logger.info("Replay complete."); + } + + private void replayBlock(long num) { + Block block = blockStore.getChainBlockByNumber(num); + lastReplayedBlock = block; + List receipts = new ArrayList<>(); + for (Transaction tx : block.getTransactionsList()) { + TransactionInfo info = transactionStore.get(tx.getHash(), block.getHash()); + TransactionReceipt receipt = info.getReceipt(); + receipt.setTransaction(tx); + receipts.add(receipt); + } + BlockSummary blockSummary = new BlockSummary(block, null, receipts, null); + blockSummary.setTotalDifficulty(BigInteger.valueOf(num)); + listener.onBlock(blockSummary); + } + + @Override + public synchronized void onBlock(BlockSummary blockSummary) { + if (replayComplete) { + if (onBlockBuffer.isEmpty()) { + listener.onBlock(blockSummary); + } else { + logger.info("Replaying cached " + onBlockBuffer.size() + " blocks..."); + boolean lastBlockFound = lastReplayedBlock == null || onBlockBuffer.size() < onBlockBuffer.maxSize(); + for (BlockSummary block : onBlockBuffer) { + if (!lastBlockFound) { + lastBlockFound = FastByteComparisons.equal(block.getBlock().getHash(), lastReplayedBlock.getHash()); + } else { + listener.onBlock(block); + } + } + onBlockBuffer.clear(); + listener.onBlock(blockSummary); + logger.info("Cache replay complete. Switching to online mode."); + } + } else { + onBlockBuffer.add(blockSummary); + } + } + + @Override + public void onPendingTransactionUpdate(TransactionReceipt transactionReceipt, PendingTransactionState pendingTransactionState, Block block) { + listener.onPendingTransactionUpdate(transactionReceipt, pendingTransactionState, block); + } + + @Override + public void onPeerDisconnect(String s, long l) { + listener.onPeerDisconnect(s, l); + } + + @Override + public void onPendingTransactionsReceived(List list) { + listener.onPendingTransactionsReceived(list); + } + + @Override + public void onPendingStateChanged(PendingState pendingState) { + listener.onPendingStateChanged(pendingState); + } + + @Override + public void onSyncDone(SyncState state) { + listener.onSyncDone(state); + } + + @Override + public void onNoConnections() { + listener.onNoConnections(); + } + + @Override + public void onVMTraceCreated(String s, String s1) { + listener.onVMTraceCreated(s, s1); + } + + @Override + public void onTransactionExecuted(TransactionExecutionSummary transactionExecutionSummary) { + listener.onTransactionExecuted(transactionExecutionSummary); + } + + @Override + public void onPeerAddedToSyncPool(Channel channel) { + listener.onPeerAddedToSyncPool(channel); + } + + @Override + public void trace(String s) { + listener.trace(s); + } + + @Override + public void onNodeDiscovered(Node node) { + listener.onNodeDiscovered(node); + } + + @Override + public void onHandShakePeer(Channel channel, HelloMessage helloMessage) { + listener.onHandShakePeer(channel, helloMessage); + } + + @Override + public void onEthStatusUpdated(Channel channel, StatusMessage statusMessage) { + listener.onEthStatusUpdated(channel, statusMessage); + } + + @Override + public void onRecvMessage(Channel channel, Message message) { + listener.onRecvMessage(channel, message); + } + + @Override + public void onSendMessage(Channel channel, Message message) { + listener.onSendMessage(channel, message); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/listener/EventListener.java b/ethereumj-core/src/main/java/org/ethereum/listener/EventListener.java new file mode 100644 index 0000000000..0248f79124 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/listener/EventListener.java @@ -0,0 +1,332 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.listener; + +import org.ethereum.core.Block; +import org.ethereum.core.BlockSummary; +import org.ethereum.core.Bloom; +import org.ethereum.core.CallTransaction; +import org.ethereum.core.PendingStateImpl; +import org.ethereum.core.TransactionReceipt; +import org.ethereum.db.ByteArrayWrapper; +import org.ethereum.listener.EthereumListener.PendingTransactionState; +import org.ethereum.util.ByteArrayMap; +import org.ethereum.util.Utils; +import org.ethereum.vm.LogInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static org.ethereum.sync.BlockDownloader.MAX_IN_REQUEST; + +/** + * The base class for tracking events generated by a Solidity contract + * For initializing the contract with its address use the [initContractAddress] method + * For initializing the contract with some topic and any address use the [initContractTopic] method + * + * Alternatively you could override initialization and [onLogMatch] method for very specific parsing + * + * For example of usage, look at {@link org.ethereum.samples.EventListenerSample} + * + * @param this is the data generated by the implementing class for + * a Solidity Event. It is created by the [onEvent] implementation + */ +public abstract class EventListener { + private static final Logger logger = LoggerFactory.getLogger("events"); + + /** + * The container class for [EventData] instance(s) with the respect + * to the transaction which generated events. + * The transaction pending state is tracked via + * - onPendingTransactionUpdate callback: to handle Tx state changes on block inclusion, rebranches and rejects + * - onBlock callback: to handle confirming blocks after the Tx is included + */ + protected class PendingEvent { + // The latest transaction receipt either pending/rejected or best branch included + public TransactionReceipt receipt; + + // The Solidity Events (represented as EventData) generated by transaction + public List eventData; + // null if pending/rejected + public Block includedTo; + // the latest block from the main branch + public Block bestConfirmingBlock; + // if came from a block we take block time, it pending we take current time + public long created; + // status of the Ethereum Tx + public TxStatus txStatus; + + public PendingEvent(long created) { + this.created = created; + } + + public void update(TransactionReceipt receipt, List txs, PendingTransactionState state, Block includedTo) { + this.receipt = receipt; + this.eventData = txs; + this.bestConfirmingBlock = state == PendingTransactionState.INCLUDED ? includedTo : null; + this.includedTo = state == PendingTransactionState.INCLUDED ? includedTo : null; + txStatus = state.isPending() ? TxStatus.PENDING : + (state == PendingTransactionState.DROPPED ? TxStatus.REJECTED : TxStatus.getConfirmed(1)); + } + + public boolean setBestConfirmingBlock(Block bestConfirmingBlock) { + if (txStatus == TxStatus.REJECTED || txStatus == TxStatus.PENDING) return false; + if (this.bestConfirmingBlock.isEqual(bestConfirmingBlock)) return false; + this.bestConfirmingBlock = bestConfirmingBlock; + txStatus = TxStatus.getConfirmed((int) (bestConfirmingBlock.getNumber() - includedTo.getNumber() + 1)); + return true; + } + + @Override + public String toString() { + return "PendingEvent{" + + "eventData=" + eventData + + ", includedTo=" + (includedTo == null ? "null" : includedTo.getShortDescr()) + + ", bestConfirmingBlock=" + (bestConfirmingBlock == null ? "null" : bestConfirmingBlock.getShortDescr()) + + ", created=" + created + + ", txStatus=" + txStatus + + ", tx=" + receipt.getTransaction() + + '}'; + } + } + + protected LogFilter logFilter; + protected CallTransaction.Contract contract; + protected PendingStateImpl pendingState; + private boolean initialized = false; + + // txHash => PendingEvent + protected ByteArrayMap pendings = new ByteArrayMap<>(new LinkedHashMap()); + protected Block bestBlock; + BigInteger lastTotDiff = BigInteger.ZERO; + // executing EthereumListener callbacks on a separate thread to avoid long running + // handlers (die to DB access) messing up core + ExecutorService executor = Executors.newSingleThreadExecutor(r -> new Thread(r, EventListener.this.getClass().getSimpleName() + "-exec")); + + public final EthereumListener listener = new EthereumListenerAdapter() { + @Override + public void onBlock(BlockSummary blockSummary) { + executor.submit(() -> onBlockImpl(blockSummary)); + } + + @Override + public void onPendingTransactionUpdate(TransactionReceipt txReceipt, PendingTransactionState state, Block block) { + executor.submit(() -> onPendingTransactionUpdateImpl(txReceipt, state, block)); + } + }; + + public EventListener(PendingStateImpl pendingState) { + this.pendingState = pendingState; + } + + public void onBlockImpl(BlockSummary blockSummary) { + if (!initialized) throw new RuntimeException("Event listener should be initialized"); + try { + logger.debug("onBlock: " + blockSummary.getBlock().getShortDescr()); + + // ignoring spurious old blocks + if (bestBlock != null && blockSummary.getBlock().getNumber() < bestBlock.getNumber() - MAX_IN_REQUEST) { + logger.debug("Ignoring block as too old: " + blockSummary.getBlock().getShortDescr()); + return; + } + + if (logFilter.matchBloom(new Bloom(blockSummary.getBlock().getLogBloom()))) { + for (int i = 0; i < blockSummary.getReceipts().size(); i++) { + TransactionReceipt receipt = blockSummary.getReceipts().get(i); + if (logFilter.matchBloom(receipt.getBloomFilter())) { + if (!pendings.containsKey(receipt.getTransaction().getHash())) { + // ask PendingState to track candidate transactions from blocks since there is + // no guarantee the transaction of interest received as a pending + // on included transaction PendingState should call onPendingTransactionUpdateImpl + // with INCLUDED state + // if Tx was included into fork block PendingState should call onPendingTransactionUpdateImpl + // with PENDING state + pendingState.trackTransaction(receipt.getTransaction()); + } + } + } + } + + if (blockSummary.betterThan(lastTotDiff)) { + lastTotDiff = blockSummary.getTotalDifficulty(); + bestBlock = blockSummary.getBlock(); + // we need to update pendings bestConfirmingBlock + newBestBlock(blockSummary.getBlock()); + } + } catch (Exception e) { + logger.error("Unexpected error while processing onBlock", e); + } + } + + public void onPendingTransactionUpdateImpl(TransactionReceipt txReceipt, PendingTransactionState state, Block block) { + try { + if (state != PendingTransactionState.DROPPED || pendings.containsKey(txReceipt.getTransaction().getHash())) { + logger.debug("onPendingTransactionUpdate: " + Hex.toHexString(txReceipt.getTransaction().getHash()) + ", " + state); + } + onReceipt(txReceipt, block, state); + } catch (Exception e) { + logger.error("Unexpected error while processing onPendingTransactionUpdate", e); + } + } + + /** + * Initialize listener with the contract of interest and its address + */ + public synchronized void initContractAddress(String abi, byte[] contractAddress) { + if (initialized) throw new RuntimeException("Already initialized"); + contract = new CallTransaction.Contract(abi); + logFilter = new LogFilter().withContractAddress(contractAddress); + initialized = true; + } + + /** + * Initialize listener with the contract of interest and topic to search for + */ + public synchronized void initContractTopic(String abi, byte[] topic) { + if (initialized) throw new RuntimeException("Already initialized"); + contract = new CallTransaction.Contract(abi); + logFilter = new LogFilter().withTopic(topic); + initialized = true; + } + + // checks the Tx receipt for the contract LogEvents + // initiated on [onPendingTransactionUpdateImpl] callback only + private synchronized void onReceipt(TransactionReceipt receipt, Block block, PendingTransactionState state) { + if (!initialized) throw new RuntimeException("Event listener should be initialized"); + byte[] txHash = receipt.getTransaction().getHash(); + if (logFilter.matchBloom(receipt.getBloomFilter())) { + int txCount = 0; // several transactions are possible withing a single Ethereum Tx + List matchedTxs = new ArrayList<>(); + for (LogInfo logInfo : receipt.getLogInfoList()) { + if (logFilter.matchBloom(logInfo.getBloom()) && + logFilter.matchesExactly(logInfo)) { + // This is our contract event, asking implementing class to process it + EventData matchedTx = onLogMatch(logInfo, block, receipt, txCount, state); + // implementing class may return null if the event is not interesting + if (matchedTx != null) { + txCount++; + matchedTxs.add(matchedTx); + } + } + } + if (!matchedTxs.isEmpty()) { + // cool, we've got some Events from this Tx, let's track further Tx lifecycle + onEventData(receipt, block, state, matchedTxs); + } + } else if (state == PendingTransactionState.DROPPED && pendings.containsKey(txHash)) { + PendingEvent event = pendings.get(txHash); + onEventData(receipt, block, state, event.eventData); + } + } + + // process the list of [EventData] generated by the Tx + // initiated on [onPendingTransactionUpdateImpl] callback only + private void onEventData(TransactionReceipt receipt, Block block, + PendingTransactionState state, List matchedTxs) { + byte[] txHash = receipt.getTransaction().getHash(); + PendingEvent event = pendings.get(txHash); + boolean newEvent = false; + if (event == null) { + // new Tx + event = new PendingEvent(state.isPending() ? Utils.toUnixTime(System.currentTimeMillis()) : block.getTimestamp()); + pendings.put(txHash, event); + newEvent = true; + } + // If the Tx is not new, then update its data with the latest results + // Tx may change its state (and thus results) several times if it was included + // to block(s) from different fork branches + event.update(receipt, matchedTxs, state, block); + logger.debug("Event " + (newEvent ? "created" : "updated") + ": " + event); + + if (pendingTransactionUpdated(event)) { + pendings.remove(txHash); + } + pendingTransactionsUpdated(); + } + + protected EventData onLogMatch(LogInfo logInfo, Block block, TransactionReceipt receipt, int txCount, PendingTransactionState state) { + CallTransaction.Invocation event = contract.parseEvent(logInfo); + + if (event == null) { + logger.error("Can't parse log: " + logInfo); + return null; + } + + return onEvent(event, block, receipt, txCount, state); + } + + /** + * The implementing subclass should create an EventData instance with the data extracted from + * Solidity [event] + * @param event Representation of the Solidity event which contains ABI to parse Event arguments and the + * actual Event arguments + * @param block Either block where Tx was included, or PENDING block + * @param receipt + * @param txCount The sequence number of this event generated by this Tx. A single Tx might produce + * several Events of interest, so the unique key of an Event is TxHash + SeqNumber + * @param state The state of Transaction (Pending/Rejected/Included) + * @return Either null if this [event] is not interesting for implementation class, or [event] representation + */ + protected abstract EventData onEvent(CallTransaction.Invocation event, Block block, TransactionReceipt receipt, + int txCount, PendingTransactionState state); + + /** + * Called after one or more transactions updated + * (normally on a new best block all the included Txs are updated with confirmed block) + */ + protected abstract void pendingTransactionsUpdated(); + + /** + * Called on a single transaction update + * @return true if the implementation is not interested in further tracking of this Tx + * (i.e. the transaction is assumed 100% confirmed or it was rejected) + */ + protected abstract boolean pendingTransactionUpdated(PendingEvent evt); + + // Updates included [pendings] with a new confirming block + // and removes those we are not interested in anymore + private synchronized void newBestBlock(Block newBlock) { + List toRemove = new ArrayList<>(); + + boolean updated = false; + for (PendingEvent event : pendings.values()) { + if (event.setBestConfirmingBlock(newBlock)) { + boolean remove = pendingTransactionUpdated(event); + if (remove) { + logger.info("Removing event from pending: " + event); + toRemove.add(event.receipt.getTransaction().getHash()); + } + updated = true; + } + } + + pendings.keySet().removeAll(toRemove); + + if (updated) { + pendingTransactionsUpdated(); + } + } + +} diff --git a/ethereumj-core/src/main/java/org/ethereum/jsonrpc/LogFilter.java b/ethereumj-core/src/main/java/org/ethereum/listener/LogFilter.java similarity index 97% rename from ethereumj-core/src/main/java/org/ethereum/jsonrpc/LogFilter.java rename to ethereumj-core/src/main/java/org/ethereum/listener/LogFilter.java index 135e78eeec..f75b386d20 100644 --- a/ethereumj-core/src/main/java/org/ethereum/jsonrpc/LogFilter.java +++ b/ethereumj-core/src/main/java/org/ethereum/listener/LogFilter.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU Lesser General Public License * along with the ethereumJ library. If not, see . */ -package org.ethereum.jsonrpc; +package org.ethereum.listener; import org.ethereum.core.Bloom; import org.ethereum.vm.DataWord; @@ -81,7 +81,7 @@ public boolean matchBloom(Bloom blockBloom) { return true; } - boolean matchesContractAddress(byte[] toAddr) { + public boolean matchesContractAddress(byte[] toAddr) { initBlooms(); for (byte[] address : contractAddresses) { if (Arrays.equals(address, toAddr)) return true; diff --git a/ethereumj-core/src/main/java/org/ethereum/jsonrpc/TransactionReceiptDTOExt.java b/ethereumj-core/src/main/java/org/ethereum/listener/TxStatus.java similarity index 56% rename from ethereumj-core/src/main/java/org/ethereum/jsonrpc/TransactionReceiptDTOExt.java rename to ethereumj-core/src/main/java/org/ethereum/listener/TxStatus.java index 99790cd84e..0e0d404671 100644 --- a/ethereumj-core/src/main/java/org/ethereum/jsonrpc/TransactionReceiptDTOExt.java +++ b/ethereumj-core/src/main/java/org/ethereum/listener/TxStatus.java @@ -15,24 +15,29 @@ * You should have received a copy of the GNU Lesser General Public License * along with the ethereumJ library. If not, see . */ -package org.ethereum.jsonrpc; - -import org.ethereum.core.Block; -import org.ethereum.core.TransactionInfo; - -import static org.ethereum.jsonrpc.TypeConverter.toJsonHex; +package org.ethereum.listener; /** - * Created by Anton Nashatyrev on 05.08.2016. + * Created by Anton Nashatyrev on 15.07.2016. */ -public class TransactionReceiptDTOExt extends TransactionReceiptDTO { +public class TxStatus { + + public static final TxStatus REJECTED = new TxStatus(0); + public static final TxStatus PENDING = new TxStatus(0); + public static TxStatus getConfirmed(int blocks) { + return new TxStatus(blocks); + } + + public final int confirmed; - public String returnData; - public String error; + private TxStatus(int confirmed) { + this.confirmed = confirmed; + } - public TransactionReceiptDTOExt(Block block, TransactionInfo txInfo) { - super(block, txInfo); - returnData = toJsonHex(txInfo.getReceipt().getExecutionResult()); - error = txInfo.getReceipt().getError(); + @Override + public String toString() { + if (this == REJECTED) return "REJECTED"; + if (this == PENDING) return "PENDING"; + return "CONFIRMED_" + confirmed; } } 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 2566ae1024..555c795f21 100644 --- a/ethereumj-core/src/main/java/org/ethereum/manager/BlockLoader.java +++ b/ethereumj-core/src/main/java/org/ethereum/manager/BlockLoader.java @@ -30,14 +30,13 @@ import org.springframework.stereotype.Component; import java.io.FileInputStream; -import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; -import java.util.concurrent.*; +import java.util.function.Function; @Component public class BlockLoader { @@ -86,31 +85,21 @@ private void blockWork(Block block) { ExecutorPipeline exec2; public void loadBlocks() { - exec1 = new ExecutorPipeline(8, 1000, true, new Functional.Function() { - @Override - public Block apply(Block b) { - if (b.getNumber() >= blockchain.getBlockStore().getBestBlock().getNumber()) { - for (Transaction tx : b.getTransactionsList()) { - tx.getSender(); - } + exec1 = new ExecutorPipeline(8, 1000, true, (Function) b -> { + if (b.getNumber() >= blockchain.getBlockStore().getBestBlock().getNumber()) { + for (Transaction tx : b.getTransactionsList()) { + tx.getSender(); } - return b; } - }, new Functional.Consumer() { - @Override - public void accept(Throwable throwable) { - logger.error("Unhandled exception: ", throwable); - } - }); - - exec2 = exec1.add(1, 1000, new Functional.Consumer() { - @Override - public void accept(Block block) { - try { - blockWork(block); - } catch (Exception e) { - e.printStackTrace(); - } + return b; + }, throwable -> logger.error("Unhandled exception: ", throwable) + ); + + exec2 = exec1.add(1, 1000, block -> { + try { + blockWork(block); + } catch (Exception e) { + e.printStackTrace(); } }); diff --git a/ethereumj-core/src/main/java/org/ethereum/mine/AnyFuture.java b/ethereumj-core/src/main/java/org/ethereum/mine/AnyFuture.java index 3a93053002..b240c41fd9 100644 --- a/ethereumj-core/src/main/java/org/ethereum/mine/AnyFuture.java +++ b/ethereumj-core/src/main/java/org/ethereum/mine/AnyFuture.java @@ -37,11 +37,7 @@ public class AnyFuture extends AbstractFuture { public synchronized void add(final ListenableFuture f) { if (isCancelled() || isDone()) return; - f.addListener(new Runnable() { - public void run() { - futureCompleted(f); - } - }, MoreExecutors.sameThreadExecutor()); + f.addListener(() -> futureCompleted(f), MoreExecutors.sameThreadExecutor()); futures.add(f); } diff --git a/ethereumj-core/src/main/java/org/ethereum/mine/BlockMiner.java b/ethereumj-core/src/main/java/org/ethereum/mine/BlockMiner.java index 44993e034c..ed36f9fc12 100644 --- a/ethereumj-core/src/main/java/org/ethereum/mine/BlockMiner.java +++ b/ethereumj-core/src/main/java/org/ethereum/mine/BlockMiner.java @@ -269,18 +269,15 @@ protected void restartMining() { } for (final ListenableFuture task : currentMiningTasks) { - task.addListener(new Runnable() { - @Override - public void run() { - try { - // wow, block mined! - final Block minedBlock = task.get().block; - blockMined(minedBlock); - } catch (InterruptedException | CancellationException e) { - // OK, we've been cancelled, just exit - } catch (Exception e) { - logger.warn("Exception during mining: ", e); - } + task.addListener(() -> { + try { + // wow, block mined! + final Block minedBlock = task.get().block; + blockMined(minedBlock); + } catch (InterruptedException | CancellationException e) { + // OK, we've been cancelled, just exit + } catch (Exception e) { + logger.warn("Exception during mining: ", e); } }, MoreExecutors.sameThreadExecutor()); } diff --git a/ethereumj-core/src/main/java/org/ethereum/mine/Ethash.java b/ethereumj-core/src/main/java/org/ethereum/mine/Ethash.java index bac4ac951d..463be6ba30 100644 --- a/ethereumj-core/src/main/java/org/ethereum/mine/Ethash.java +++ b/ethereumj-core/src/main/java/org/ethereum/mine/Ethash.java @@ -52,7 +52,7 @@ public class Ethash { private static long cachedBlockEpoch = 0; // private static ExecutorService executor = Executors.newSingleThreadExecutor(); private static ListeningExecutorService executor = MoreExecutors.listeningDecorator( - new ThreadPoolExecutor(8, 8, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue(), + new ThreadPoolExecutor(8, 8, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new ThreadFactoryBuilder().setNameFormat("ethash-pool-%d").build())); public static boolean fileCacheEnabled = true; diff --git a/ethereumj-core/src/main/java/org/ethereum/net/MessageQueue.java b/ethereumj-core/src/main/java/org/ethereum/net/MessageQueue.java index 106a98a725..6c98c47e21 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/MessageQueue.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/MessageQueue.java @@ -83,13 +83,11 @@ public MessageQueue() { public void activate(ChannelHandlerContext ctx) { this.ctx = ctx; - timerTask = timer.scheduleAtFixedRate(new Runnable() { - public void run() { - try { - nudgeQueue(); - } catch (Throwable t) { - logger.error("Unhandled exception", t); - } + timerTask = timer.scheduleAtFixedRate(() -> { + try { + nudgeQueue(); + } catch (Throwable t) { + logger.error("Unhandled exception", t); } }, 10, 10, TimeUnit.MILLISECONDS); } diff --git a/ethereumj-core/src/main/java/org/ethereum/net/eth/handler/Eth63.java b/ethereumj-core/src/main/java/org/ethereum/net/eth/handler/Eth63.java index 43385d003b..16d2f2b955 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/eth/handler/Eth63.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/eth/handler/Eth63.java @@ -23,8 +23,8 @@ import org.apache.commons.lang3.tuple.Pair; import org.ethereum.config.SystemProperties; import org.ethereum.core.*; +import org.ethereum.datasource.Source; import org.ethereum.db.BlockStore; -import org.ethereum.db.StateSource; import org.ethereum.listener.CompositeEthereumListener; import org.ethereum.net.eth.EthVersion; import org.ethereum.net.eth.message.EthMessage; @@ -58,8 +58,8 @@ public class Eth63 extends Eth62 { private static final EthVersion version = V63; - @Autowired - private StateSource stateSource; + @Autowired @Qualifier("trieNodeSource") + private Source trieNodeSource; private List requestedReceipts; private SettableFuture>> requestReceiptsFuture; @@ -110,7 +110,7 @@ protected synchronized void processGetNodeData(GetNodeDataMessage msg) { List nodeValues = new ArrayList<>(); for (byte[] nodeKey : msg.getNodeKeys()) { - byte[] rawNode = stateSource.get(nodeKey); + byte[] rawNode = trieNodeSource.get(nodeKey); if (rawNode != null) { Value value = new Value(rawNode); nodeValues.add(value); diff --git a/ethereumj-core/src/main/java/org/ethereum/net/eth/message/EthMessageCodes.java b/ethereumj-core/src/main/java/org/ethereum/net/eth/message/EthMessageCodes.java index 72a103f21d..5d729f758e 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/eth/message/EthMessageCodes.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/eth/message/EthMessageCodes.java @@ -214,6 +214,16 @@ public static EthMessageCodes[] values(EthVersion v) { return versionToValuesMap.get(v); } + public static int maxCode(EthVersion v) { + + int max = 0; + for (EthMessageCodes cd : versionToValuesMap.get(v)) + if (max < cd.asByte()) + max = cd.asByte(); + + return max; + } + public static EthMessageCodes fromByte(byte i, EthVersion v) { Map map = intToTypeMap.get(v); return map.get((int) i); diff --git a/ethereumj-core/src/main/java/org/ethereum/net/eth/message/NodeDataMessage.java b/ethereumj-core/src/main/java/org/ethereum/net/eth/message/NodeDataMessage.java index 02a86b598f..7c18a94cb3 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/eth/message/NodeDataMessage.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/eth/message/NodeDataMessage.java @@ -59,7 +59,7 @@ private void encode() { List dataListRLP = new ArrayList<>(); for (Value value: dataList) { if (value == null) continue; // Bad sign - dataListRLP.add(RLP.encodeElement(value.getData())); + dataListRLP.add(value.getData()); } byte[][] encodedElementArray = dataListRLP.toArray(new byte[dataListRLP.size()][]); this.encoded = RLP.encodeList(encodedElementArray); diff --git a/ethereumj-core/src/main/java/org/ethereum/net/p2p/P2pHandler.java b/ethereumj-core/src/main/java/org/ethereum/net/p2p/P2pHandler.java index ce15b88e55..37fefab397 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/p2p/P2pHandler.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/p2p/P2pHandler.java @@ -73,16 +73,12 @@ @Scope("prototype") public class P2pHandler extends SimpleChannelInboundHandler { - public final static byte VERSION = 4; + public final static byte VERSION = 5; private final static Logger logger = LoggerFactory.getLogger("net"); private static ScheduledExecutorService pingTimer = - Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { - public Thread newThread(Runnable r) { - return new Thread(r, "P2pPingTimer"); - } - }); + Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "P2pPingTimer")); private MessageQueue msgQueue; @@ -279,16 +275,13 @@ public HelloMessage getHandshakeHelloMessage() { private void startTimers() { // sample for pinging in background - pingTask = pingTimer.scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - try { - msgQueue.sendMessage(PING_MESSAGE); - } catch (Throwable t) { - logger.error("Unhandled exception", t); - } + pingTask = pingTimer.scheduleAtFixedRate(() -> { + try { + msgQueue.sendMessage(PING_MESSAGE); + } catch (Throwable t) { + logger.error("Unhandled exception", t); } - }, 2, config.getProperty("peer.p2p.pingInterval", 5L), TimeUnit.SECONDS); + }, 2, config.getProperty("peer.p2p.pingInterval", 5), TimeUnit.SECONDS); } public void killTimers() { @@ -338,4 +331,4 @@ public List getSupportedCapabilities(HelloMessage hello) { return supported; } -} \ No newline at end of file +} diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/FrameCodec.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/FrameCodec.java index 116d588278..022cc2c33d 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/FrameCodec.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/FrameCodec.java @@ -208,7 +208,7 @@ public List readFrames(DataInput inp) throws IOException { ingressMac.update(buffer, 0, frameSize); dec.processBytes(buffer, 0, frameSize, buffer, 0); int pos = 0; - long type = RLP.decodeInt(buffer, pos); // FIXME long + long type = RLP.decodeLong(buffer, pos); pos = RLP.getNextElementIndex(buffer, pos); InputStream payload = new ByteArrayInputStream(buffer, pos, totalBodySize - pos); int size = totalBodySize - pos; diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/MessageCodesResolver.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/MessageCodesResolver.java index 4f5b8c845b..d825fecf73 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/MessageCodesResolver.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/MessageCodesResolver.java @@ -54,7 +54,7 @@ public void init(List caps) { if (capability.getName().equals(Capability.ETH)) { setEthOffset(offset); EthVersion v = fromCode(capability.getVersion()); - offset += EthMessageCodes.values(v).length; + offset += EthMessageCodes.maxCode(v) + 1; // +1 is essential cause STATUS code starts from 0x0 } if (capability.getName().equals(Capability.SHH)) { diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/SnappyCodec.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/SnappyCodec.java new file mode 100644 index 0000000000..9def7a83c2 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/SnappyCodec.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.net.rlpx; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageCodec; +import org.ethereum.net.message.ReasonCode; +import org.ethereum.net.server.Channel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xerial.snappy.Snappy; + +import java.io.IOException; +import java.util.List; + +/** + * Snappy compression codec.
+ * + * Check EIP-706 for details + * + * @author Mikhail Kalinin + * @since 31.10.2017 + */ +public class SnappyCodec extends MessageToMessageCodec { + + private static final Logger logger = LoggerFactory.getLogger("net"); + + private final static int SNAPPY_P2P_VERSION = 5; + private final static int MAX_SIZE = 16 * 1024 * 1024; // 16 mb + + Channel channel; + + public SnappyCodec(Channel channel) { + this.channel = channel; + } + + public static boolean isSupported(int p2pVersion) { + return p2pVersion >= SNAPPY_P2P_VERSION; + } + + @Override + protected void encode(ChannelHandlerContext ctx, FrameCodec.Frame msg, List out) throws Exception { + + // stay consistent with decoding party + if (msg.size > MAX_SIZE) { + logger.info("{}: outgoing frame size exceeds the limit ({} bytes), disconnect", channel, msg.size); + channel.getNodeStatistics().nodeDisconnectedLocal(ReasonCode.USELESS_PEER); + channel.disconnect(ReasonCode.USELESS_PEER); + return; + } + + byte[] in = new byte[msg.size]; + msg.payload.read(in); + + byte[] compressed = Snappy.rawCompress(in, in.length); + + out.add(new FrameCodec.Frame((int) msg.type, compressed)); + } + + @Override + protected void decode(ChannelHandlerContext ctx, FrameCodec.Frame msg, List out) throws Exception { + + byte[] in = new byte[msg.size]; + msg.payload.read(in); + + long uncompressedLength = Snappy.uncompressedLength(in) & 0xFFFFFFFFL; + if (uncompressedLength > MAX_SIZE) { + logger.info("{}: uncompressed frame size exceeds the limit ({} bytes), drop the peer", channel, uncompressedLength); + channel.getNodeStatistics().nodeDisconnectedLocal(ReasonCode.BAD_PROTOCOL); + channel.disconnect(ReasonCode.BAD_PROTOCOL); + return; + } + + byte[] uncompressed = new byte[(int) uncompressedLength]; + try { + Snappy.rawUncompress(in, 0, in.length, uncompressed, 0); + } catch (IOException e) { + String detailMessage = e.getMessage(); + // 5 - error code for framed snappy + if (detailMessage.startsWith("FAILED_TO_UNCOMPRESS") && detailMessage.contains("5")) { + logger.info("{}: Snappy frames are not allowed in DEVp2p protocol, drop the peer", channel); + channel.getNodeStatistics().nodeDisconnectedLocal(ReasonCode.BAD_PROTOCOL); + channel.disconnect(ReasonCode.BAD_PROTOCOL); + return; + } else { + throw e; + } + } + + out.add(new FrameCodec.Frame((int) msg.type, uncompressed)); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (channel.isDiscoveryMode()) { + logger.trace("SnappyCodec failed: " + cause); + } else { + if (cause instanceof IOException) { + logger.debug("SnappyCodec failed: " + ctx.channel().remoteAddress() + ": " + cause); + } else { + logger.warn("SnappyCodec failed: ", cause); + } + } + ctx.close(); + } +} 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 index 0497356915..f6d30ac42a 100644 --- 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 @@ -23,20 +23,14 @@ import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.socket.DatagramPacket; import io.netty.channel.socket.nio.NioDatagramChannel; -import org.ethereum.crypto.ECKey; -import org.ethereum.net.rlpx.*; -import org.ethereum.net.rlpx.discover.table.NodeTable; -import org.ethereum.util.Functional; import org.slf4j.LoggerFactory; import java.net.InetSocketAddress; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.function.Consumer; public class MessageHandler extends SimpleChannelInboundHandler - implements Functional.Consumer { + implements Consumer { static final org.slf4j.Logger logger = LoggerFactory.getLogger("discover"); public Channel channel; diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/NodeHandler.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/NodeHandler.java index e7f8460e88..7fb18eca3c 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/NodeHandler.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/NodeHandler.java @@ -290,16 +290,14 @@ void sendPing() { getNodeStatistics().discoverOutPing.add(); if (nodeManager.getPongTimer().isShutdown()) return; - nodeManager.getPongTimer().schedule(new Runnable() { - public void run() { - try { - if (waitForPong) { - waitForPong = false; - handleTimedOut(); - } - } catch (Throwable t) { - logger.error("Unhandled exception", t); + nodeManager.getPongTimer().schedule(() -> { + try { + if (waitForPong) { + waitForPong = false; + handleTimedOut(); } + } catch (Throwable t) { + logger.error("Unhandled exception", t); } }, PingTimeout, TimeUnit.MILLISECONDS); } diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/NodeManager.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/NodeManager.java index 80c53838eb..4ec7c73957 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/NodeManager.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/NodeManager.java @@ -25,7 +25,6 @@ import org.ethereum.net.rlpx.*; import org.ethereum.net.rlpx.discover.table.NodeTable; import org.ethereum.util.CollectionUtils; -import org.ethereum.util.Functional; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; @@ -36,6 +35,8 @@ import java.util.*; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Consumer; +import java.util.function.Predicate; import static java.lang.Math.min; @@ -49,7 +50,7 @@ * Created by Anton Nashatyrev on 16.07.2015. */ @Component -public class NodeManager implements Functional.Consumer{ +public class NodeManager implements Consumer{ static final org.slf4j.Logger logger = LoggerFactory.getLogger("discover"); private final boolean PERSIST; @@ -64,7 +65,7 @@ public class NodeManager implements Functional.Consumer{ EthereumListener ethereumListener; SystemProperties config = SystemProperties.getDefault(); - Functional.Consumer messageSender; + Consumer messageSender; NodeTable table; private Map nodeHandlerMap = new HashMap<>(); @@ -174,7 +175,7 @@ private void dbWrite() { logger.info("Write Node statistics to DB: " + peerSource.getNodes().size() + " nodes."); } - public void setMessageSender(Functional.Consumer messageSender) { + public void setMessageSender(Consumer messageSender) { this.messageSender = messageSender; } @@ -217,12 +218,7 @@ private void trimTable() { List sorted = new ArrayList<>(nodeHandlerMap.values()); // reverse sort by reputation - Collections.sort(sorted, new Comparator() { - @Override - public int compare(NodeHandler o1, NodeHandler o2) { - return o1.getNodeStatistics().getReputation() - o2.getNodeStatistics().getReputation(); - } - }); + sorted.sort((o1, o2) -> o1.getNodeStatistics().getReputation() - o2.getNodeStatistics().getReputation()); for (NodeHandler handler : sorted) { nodeHandlerMap.remove(getKey(handler.getNode())); @@ -314,7 +310,7 @@ public synchronized List getNodes(int minReputation) { * @return list of nodes matching criteria */ public List getNodes( - Functional.Predicate predicate, + Predicate predicate, int limit ) { ArrayList filtered = new ArrayList<>(); synchronized (this) { @@ -324,13 +320,8 @@ public List getNodes( } } } - Collections.sort(filtered, new Comparator() { - @Override - public int compare(NodeHandler o1, NodeHandler o2) { - return o2.getNodeStatistics().getEthTotalDifficulty().compareTo( - o1.getNodeStatistics().getEthTotalDifficulty()); - } - }); + filtered.sort((o1, o2) -> o2.getNodeStatistics().getEthTotalDifficulty().compareTo( + o1.getNodeStatistics().getEthTotalDifficulty())); return CollectionUtils.truncate(filtered, limit); } @@ -348,7 +339,7 @@ private synchronized void processListeners() { * Add a listener which is notified when the node statistics starts or stops meeting * the criteria specified by [filter] param. */ - public synchronized void addDiscoverListener(DiscoverListener listener, Functional.Predicate filter) { + public synchronized void addDiscoverListener(DiscoverListener listener, Predicate filter) { listeners.put(listener, new ListenerHandler(listener, filter)); } @@ -358,11 +349,7 @@ public synchronized void removeDiscoverListener(DiscoverListener listener) { public synchronized String dumpAllStatistics() { List l = new ArrayList<>(nodeHandlerMap.values()); - Collections.sort(l, new Comparator() { - public int compare(NodeHandler o1, NodeHandler o2) { - return -(o1.getNodeStatistics().getReputation() - o2.getNodeStatistics().getReputation()); - } - }); + l.sort((o1, o2) -> -(o1.getNodeStatistics().getReputation() - o2.getNodeStatistics().getReputation())); StringBuilder sb = new StringBuilder(); int zeroReputCount = 0; @@ -371,7 +358,7 @@ public int compare(NodeHandler o1, NodeHandler o2) { sb.append(nodeHandler).append("\t").append(nodeHandler.getNodeStatistics()).append("\n"); } else { zeroReputCount++; - } + } } sb.append("0 reputation: ").append(zeroReputCount).append(" nodes.\n"); return sb.toString(); @@ -419,9 +406,9 @@ public void close() { private class ListenerHandler { Map discoveredNodes = new IdentityHashMap<>(); DiscoverListener listener; - Functional.Predicate filter; + Predicate filter; - ListenerHandler(DiscoverListener listener, Functional.Predicate filter) { + ListenerHandler(DiscoverListener listener, Predicate filter) { this.listener = listener; this.filter = filter; } diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/NodeStatistics.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/NodeStatistics.java index 5296a42996..23ab074620 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/NodeStatistics.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/NodeStatistics.java @@ -160,7 +160,9 @@ private boolean isReputationPenalized() { rlpxLastLocalDisconnectReason == ReasonCode.INCOMPATIBLE_PROTOCOL || rlpxLastRemoteDisconnectReason == ReasonCode.INCOMPATIBLE_PROTOCOL || rlpxLastLocalDisconnectReason == ReasonCode.USELESS_PEER || - rlpxLastRemoteDisconnectReason == ReasonCode.USELESS_PEER; + rlpxLastRemoteDisconnectReason == ReasonCode.USELESS_PEER || + rlpxLastLocalDisconnectReason == ReasonCode.BAD_PROTOCOL || + rlpxLastRemoteDisconnectReason == ReasonCode.BAD_PROTOCOL; } public void nodeDisconnectedRemote(ReasonCode reason) { diff --git a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/PeerConnectionTester.java b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/PeerConnectionTester.java index 913c0438de..d902b08548 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/PeerConnectionTester.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/rlpx/discover/PeerConnectionTester.java @@ -105,13 +105,10 @@ public PeerConnectionTester(final SystemProperties config) { ReconnectMaxPeers = config.peerDiscoveryTouchMaxNodes(); peerConnectionPool = new ThreadPoolExecutor(ConnectThreads, ConnectThreads, 0L, TimeUnit.SECONDS, - new MutablePriorityQueue(new Comparator() { - @Override - public int compare(ConnectTask h1, ConnectTask h2) { - return h2.nodeHandler.getNodeStatistics().getReputation() - - h1.nodeHandler.getNodeStatistics().getReputation(); - } - }), new ThreadFactoryBuilder().setDaemon(true).setNameFormat("discovery-tester-%d").build()); + new MutablePriorityQueue<>((Comparator) (h1, h2) -> + h2.nodeHandler.getNodeStatistics().getReputation() - + h1.nodeHandler.getNodeStatistics().getReputation()), + new ThreadFactoryBuilder().setDaemon(true).setNameFormat("discovery-tester-%d").build()); } public void close() { 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 index 034b30c55d..acd80a4967 100644 --- 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 @@ -73,17 +73,14 @@ public UDPListener(final SystemProperties config, final NodeManager nodeManager) if (port == 0) { logger.error("Discovery can't be started while listen port == 0"); } else { - new Thread("UDPListener") { - @Override - public void run() { - try { - UDPListener.this.start(bootPeers); - } catch (Exception e) { - e.printStackTrace(); - throw new RuntimeException(e); - } + new Thread(() -> { + try { + UDPListener.this.start(bootPeers); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(e); } - }.start(); + }, "UDPListener").start(); } } } diff --git a/ethereumj-core/src/main/java/org/ethereum/net/server/Channel.java b/ethereumj-core/src/main/java/org/ethereum/net/server/Channel.java index 391f6de6cf..10f8996f8c 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/server/Channel.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/server/Channel.java @@ -165,6 +165,12 @@ public void publicRLPxHandshakeFinished(ChannelHandlerContext ctx, FrameCodec fr FrameCodecHandler frameCodecHandler = new FrameCodecHandler(frameCodec, this); ctx.pipeline().addLast("medianFrameCodec", frameCodecHandler); + + if (SnappyCodec.isSupported(Math.min(config.defaultP2PVersion(), helloRemote.getP2PVersion()))) { + ctx.pipeline().addLast("snappyCodec", new SnappyCodec(this)); + logger.debug("{}: use snappy compression", ctx.channel()); + } + ctx.pipeline().addLast("messageCodec", messageCodec); ctx.pipeline().addLast(Capability.P2P, p2pHandler); diff --git a/ethereumj-core/src/main/java/org/ethereum/net/server/ChannelManager.java b/ethereumj-core/src/main/java/org/ethereum/net/server/ChannelManager.java index 07bb8eb0c1..dd079a3247 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/server/ChannelManager.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/server/ChannelManager.java @@ -102,42 +102,25 @@ private ChannelManager(final SystemProperties config, final SyncManager syncMana this.peerServer = peerServer; maxActivePeers = config.maxActivePeers(); trustedPeers = config.peerTrusted(); - mainWorker.scheduleWithFixedDelay(new Runnable() { - @Override - public void run() { - try { - processNewPeers(); - } catch (Throwable t) { - logger.error("Error", t); - } + mainWorker.scheduleWithFixedDelay(() -> { + try { + processNewPeers(); + } catch (Throwable t) { + logger.error("Error", t); } }, 0, 1, TimeUnit.SECONDS); if (config.listenPort() > 0) { - new Thread(new Runnable() { - public void run() { - peerServer.start(config.listenPort()); - } - }, + new Thread(() -> peerServer.start(config.listenPort()), "PeerServerThread").start(); } // Resending new blocks to network in loop - this.blockDistributeThread = new Thread(new Runnable() { - @Override - public void run() { - newBlocksDistributeLoop(); - } - }, "NewSyncThreadBlocks"); + this.blockDistributeThread = new Thread(this::newBlocksDistributeLoop, "NewSyncThreadBlocks"); this.blockDistributeThread.start(); // Resending pending txs to newly connected peers - this.txDistributeThread = new Thread(new Runnable() { - @Override - public void run() { - newTxDistributeLoop(); - } - }, "NewPeersThread"); + this.txDistributeThread = new Thread(this::newTxDistributeLoop, "NewPeersThread"); this.txDistributeThread.start(); } diff --git a/ethereumj-core/src/main/java/org/ethereum/net/server/EthereumChannelInitializer.java b/ethereumj-core/src/main/java/org/ethereum/net/server/EthereumChannelInitializer.java index 99b2b53559..6f7f527b41 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/server/EthereumChannelInitializer.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/server/EthereumChannelInitializer.java @@ -77,12 +77,9 @@ public void initChannel(NioSocketChannel ch) throws Exception { ch.config().setOption(ChannelOption.SO_BACKLOG, 1024); // be aware of channel closing - ch.closeFuture().addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) throws Exception { - if (!peerDiscoveryMode) { - channelManager.notifyDisconnect(channel); - } + ch.closeFuture().addListener((ChannelFutureListener) future -> { + if (!peerDiscoveryMode) { + channelManager.notifyDisconnect(channel); } }); diff --git a/ethereumj-core/src/main/java/org/ethereum/net/shh/JsonRpcWhisper.java b/ethereumj-core/src/main/java/org/ethereum/net/shh/JsonRpcWhisper.java index 82141761bd..798f8ad893 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/shh/JsonRpcWhisper.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/shh/JsonRpcWhisper.java @@ -55,14 +55,11 @@ public class JsonRpcWhisper extends Whisper { public JsonRpcWhisper(URL rpcUrl) { this.rpcUrl = rpcUrl; - poller.scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - try { - pollFilters(); - } catch (Exception e) { - logger.error("Unhandled exception", e); - } + poller.scheduleAtFixedRate(() -> { + try { + pollFilters(); + } catch (Exception e) { + logger.error("Unhandled exception", e); } }, 1, 1, TimeUnit.SECONDS); } @@ -173,23 +170,24 @@ private String sendPost(String urlParams) { // Send post request con.setDoOutput(true); - DataOutputStream wr = new DataOutputStream(con.getOutputStream()); - wr.writeBytes(urlParams); - wr.flush(); - wr.close(); + try (DataOutputStream wr = new DataOutputStream(con.getOutputStream())) { + wr.writeBytes(urlParams); + wr.flush(); + } int responseCode = con.getResponseCode(); if (responseCode != 200) { throw new RuntimeException("HTTP Response: " + responseCode); } - BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); - String inputLine; - StringBuffer response = new StringBuffer(); + final StringBuffer response; + try (BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()))) { + String inputLine; + response = new StringBuffer(); - while ((inputLine = in.readLine()) != null) { - response.append(inputLine); + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } } - in.close(); return response.toString(); } catch (IOException e) { throw new RuntimeException("Error sending POST to " + rpcUrl, e); diff --git a/ethereumj-core/src/main/java/org/ethereum/net/swarm/bzz/BzzHandler.java b/ethereumj-core/src/main/java/org/ethereum/net/swarm/bzz/BzzHandler.java index 060a880d60..0dfc40426d 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/swarm/bzz/BzzHandler.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/swarm/bzz/BzzHandler.java @@ -22,20 +22,21 @@ import org.ethereum.listener.EthereumListener; import org.ethereum.net.MessageQueue; import org.ethereum.net.swarm.NetStore; -import org.ethereum.util.Functional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; +import java.util.function.Consumer; + /** * Process the messages between peers with 'bzz' capability on the network. */ @Component @Scope("prototype") public class BzzHandler extends SimpleChannelInboundHandler - implements Functional.Consumer { + implements Consumer { public final static byte VERSION = 0; private MessageQueue msgQueue = null; diff --git a/ethereumj-core/src/main/java/org/ethereum/net/swarm/bzz/BzzProtocol.java b/ethereumj-core/src/main/java/org/ethereum/net/swarm/bzz/BzzProtocol.java index adbc621726..721dd53f42 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/swarm/bzz/BzzProtocol.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/swarm/bzz/BzzProtocol.java @@ -1,27 +1,26 @@ -/* - * Copyright (c) [2016] [ ] - * This file is part of the ethereumJ library. - * - * The ethereumJ library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The ethereumJ library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the ethereumJ library. If not, see . - */ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ package org.ethereum.net.swarm.bzz; import org.ethereum.net.client.Capability; import org.ethereum.net.swarm.Key; import org.ethereum.net.swarm.NetStore; import org.ethereum.net.swarm.Util; -import org.ethereum.util.Functional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spongycastle.util.encoders.Hex; @@ -30,6 +29,7 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; import static java.lang.Math.min; @@ -40,7 +40,7 @@ * * Created by Anton Nashatyrev on 18.06.2015. */ -public class BzzProtocol implements Functional.Consumer { +public class BzzProtocol implements Consumer { private final static Logger LOG = LoggerFactory.getLogger("net.bzz"); private final static AtomicLong idGenerator = new AtomicLong(0); @@ -52,7 +52,7 @@ public class BzzProtocol implements Functional.Consumer { public final static int Strategy = 0; private NetStore netStore; - private Functional.Consumer messageSender; + private Consumer messageSender; private PeerAddress node; private boolean handshaken = false; @@ -70,7 +70,7 @@ public BzzProtocol(NetStore netStore) { * In the testing environment this could be a special handler which delivers the message * without network stack */ - public void setMessageSender(Functional.Consumer messageSender) { + public void setMessageSender(Consumer messageSender) { this.messageSender = messageSender; } diff --git a/ethereumj-core/src/main/java/org/ethereum/samples/CreateContractSample.java b/ethereumj-core/src/main/java/org/ethereum/samples/CreateContractSample.java index 9ad6bbd028..5e51e6bab0 100644 --- a/ethereumj-core/src/main/java/org/ethereum/samples/CreateContractSample.java +++ b/ethereumj-core/src/main/java/org/ethereum/samples/CreateContractSample.java @@ -75,10 +75,10 @@ public void onBlock(Block block, List receipts) { throw new RuntimeException("Contract compilation failed:\n" + result.errors); } CompilationResult res = CompilationResult.parse(result.output); - if (res.contracts.isEmpty()) { + if (res.getContracts().isEmpty()) { throw new RuntimeException("Compilation failed, no contracts returned:\n" + result.errors); } - CompilationResult.ContractMetadata metadata = res.contracts.values().iterator().next(); + CompilationResult.ContractMetadata metadata = res.getContracts().iterator().next(); if (metadata.bin == null || metadata.bin.isEmpty()) { throw new RuntimeException("Compilation failed, no binary returned:\n" + result.errors); } diff --git a/ethereumj-core/src/main/java/org/ethereum/samples/EventListenerSample.java b/ethereumj-core/src/main/java/org/ethereum/samples/EventListenerSample.java new file mode 100644 index 0000000000..8ba0e397c4 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/samples/EventListenerSample.java @@ -0,0 +1,440 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.samples; + +import org.ethereum.core.Block; +import org.ethereum.core.CallTransaction; +import org.ethereum.core.PendingStateImpl; +import org.ethereum.core.Transaction; +import org.ethereum.core.TransactionReceipt; +import org.ethereum.crypto.ECKey; +import org.ethereum.db.BlockStore; +import org.ethereum.db.ByteArrayWrapper; +import org.ethereum.db.TransactionStore; +import org.ethereum.facade.EthereumFactory; +import org.ethereum.listener.BlockReplay; +import org.ethereum.listener.EthereumListener; +import org.ethereum.listener.EthereumListenerAdapter; +import org.ethereum.listener.EventListener; +import org.ethereum.listener.TxStatus; +import org.ethereum.solidity.compiler.CompilationResult; +import org.ethereum.solidity.compiler.SolidityCompiler; +import org.ethereum.util.ByteUtil; +import org.ethereum.vm.program.ProgramResult; +import org.spongycastle.util.encoders.Hex; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Scanner; + +import static org.ethereum.crypto.HashUtil.sha3; + +/** + * Sample usage of events listener API. + * {@link EventListener} Contract events listener + * {@link BlockReplay} Listener wrapper for pushing old blocks to any listener in addition to live data + * + * - getting free Ether assuming we are running in test network + * - deploying contract with event, which we are going to track + * - calling contract and catching corresponding events + * - alternatively you could provide address of already deployed contract and + * replay any number of blocks in the past to process old events + */ +public class EventListenerSample extends TestNetSample { + + @Autowired + SolidityCompiler compiler; + + @Autowired + BlockStore blockStore; + + @Autowired + TransactionStore transactionStore; + + @Autowired + PendingStateImpl pendingState; + + // Change seed phrases + protected final byte[] senderPrivateKey = sha3("cat".getBytes()); + protected final byte[] sender2PrivateKey = sha3("goat".getBytes()); + + // If no contractAddress provided, deploys new contract, otherwise + // replays events from already deployed contract + String contractAddress = null; +// String contractAddress = "cedf27de170a05cf1d1736f21e1f5ffc1cf22eef"; + + String contract = + "contract Sample {\n" + + " int i;\n" + + " event Inc(\n" + + " address _from,\n" + + " int _inc,\n" + + " int _total\n" + + " ); \n" + + " \n" + + " function inc(int n) {\n" + + " i = i + n;\n" + + " Inc(msg.sender, n, i); \n" + + " } \n" + + " \n" + + " function get() returns (int) {\n" + + " return i; \n" + + " }\n" + + "} "; + + private Map txWaiters = + Collections.synchronizedMap(new HashMap()); + + class IncEvent { + IncEvent(String address, Long inc, Long total) { + this.address = address; + this.inc = inc; + this.total = total; + } + + String address; + Long inc; + Long total; + + @Override + public String toString() { + return "IncEvent{" + + "address='" + address + '\'' + + ", inc=" + inc + + ", total=" + total + + '}'; + } + } + + class IncEventListener extends EventListener { + /** + * Minimum required Tx block confirmations for the events + * from this Tx to be confirmed + * After this number of confirmations, event will fire {@link #processConfirmed(PendingEvent, IncEvent)} + * on each confirmation + */ + protected int blocksToConfirm = 32; + /** + * Minimum required Tx block confirmations for this Tx to be purged + * from the tracking list + * After this number of confirmations, event will not fire {@link #processConfirmed(PendingEvent, IncEvent)} + */ + protected int purgeFromPendingsConfirmations = 40; + + public IncEventListener(PendingStateImpl pendingState) { + super(pendingState); + } + + public IncEventListener(PendingStateImpl pendingState, String contractABI, byte[] contractAddress) { + super(pendingState); + initContractAddress(contractABI, contractAddress); + // Instead you can init with topic search, + // so you could get events from all contracts with the same code + // You could init listener only once +// initContractTopic(contractABI, sha3("Inc(address,int256,int256)".getBytes())); + } + + @Override + protected IncEvent onEvent(CallTransaction.Invocation event, Block block, TransactionReceipt receipt, int txCount, EthereumListener.PendingTransactionState state) { + // Processing raw event data to fill our model IncEvent + if ("Inc".equals(event.function.name)) { + String address = Hex.toHexString((byte[]) event.args[0]); + Long inc = ((BigInteger) event.args[1]).longValue(); + Long total = ((BigInteger) event.args[2]).longValue(); + + IncEvent incEvent = new IncEvent(address, inc, total); + logger.info("Pending event: {}", incEvent); + return incEvent; + } else { + logger.error("Unknown event: " + event); + } + return null; + } + + @Override + protected void pendingTransactionsUpdated() { + } + + /** + * Events are fired here on every block since blocksToConfirm to purgeFromPendingsConfirmations + */ + void processConfirmed(PendingEvent evt, IncEvent event) { + // +1 because on included block we have 1 confirmation + long numberOfConfirmations = evt.bestConfirmingBlock.getNumber() - evt.includedTo.getNumber() + 1; + logger.info("Confirmed event: {}, confirmations: {}", event, numberOfConfirmations); + } + + @Override + protected boolean pendingTransactionUpdated(PendingEvent evt) { + if (evt.txStatus == TxStatus.REJECTED || evt.txStatus.confirmed >= blocksToConfirm) { + evt.eventData.forEach(d -> processConfirmed(evt, d)); + } + return evt.txStatus == TxStatus.REJECTED || evt.txStatus.confirmed >= purgeFromPendingsConfirmations; + } + } + + /** + * Sample logic starts here when sync is done + */ + @Override + public void onSyncDone() throws Exception { + ethereum.addListener(new EthereumListenerAdapter() { + @Override + public void onPendingTransactionUpdate(TransactionReceipt txReceipt, PendingTransactionState state, Block block) { + ByteArrayWrapper txHashW = new ByteArrayWrapper(txReceipt.getTransaction().getHash()); + // Catching transaction errors + if (txWaiters.containsKey(txHashW) && !txReceipt.isSuccessful()) { + txWaiters.put(txHashW, txReceipt); + } + } + }); + requestFreeEther(ECKey.fromPrivate(senderPrivateKey).getAddress()); + requestFreeEther(ECKey.fromPrivate(sender2PrivateKey).getAddress()); + if (contractAddress == null) { + deployContractAndTest(); + } else { + replayOnly(); + } + } + + public void requestFreeEther(byte[] addressBytes) { + String address = "0x" + Hex.toHexString(addressBytes); + logger.info("Checking address {} for available ether.", address); + BigInteger balance = ethereum.getRepository().getBalance(addressBytes); + logger.info("Address {} balance: {} wei", address, balance); + BigInteger requiredBalance = BigInteger.valueOf(3_000_000 * ethereum.getGasPrice()); + if (balance.compareTo(requiredBalance) < 0) { + logger.info("Insufficient funds for address {}, requesting free ether", address); + try { + String result = postQuery("https://ropsten.faucet.b9lab.com/tap", "{\"toWhom\":\"" + address + "\"}"); + logger.info("Answer from free Ether API: {}", result); + waitForEther(addressBytes, requiredBalance); + } catch (Exception ex) { + logger.error("Error during request of free Ether,", ex); + } + } + } + + private String postQuery(String endPoint, String json) throws IOException { + URL url = new URL(endPoint); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setConnectTimeout(5000); + conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); + conn.setDoOutput(true); + conn.setDoInput(true); + conn.setRequestMethod("POST"); + + OutputStream os = conn.getOutputStream(); + os.write(json.getBytes("UTF-8")); + os.close(); + + // read the response + InputStream in = new BufferedInputStream(conn.getInputStream()); + String result = null; + try (Scanner scanner = new Scanner(in, "UTF-8")) { + result = scanner.useDelimiter("\\A").next(); + } + + in.close(); + conn.disconnect(); + + return result; + } + + private void waitForEther(byte[] address, BigInteger requiredBalance) throws InterruptedException { + while(true) { + BigInteger balance = ethereum.getRepository().getBalance(address); + if (balance.compareTo(requiredBalance) > 0) { + logger.info("Address {} successfully funded. Balance: {} wei", "0x" + Hex.toHexString(address), balance); + break; + } + synchronized (this) { + wait(20000); + } + } + } + + /** + * - Deploys contract + * - Adds events listener + * - Calls contract from 2 different addresses + */ + private void deployContractAndTest() throws Exception { + ethereum.addListener(new EthereumListenerAdapter() { + // when block arrives look for our included transactions + @Override + public void onBlock(Block block, List receipts) { + EventListenerSample.this.onBlock(block, receipts); + } + }); + + CompilationResult.ContractMetadata metadata = compileContract(); + + logger.info("Sending contract to net and waiting for inclusion"); + TransactionReceipt receipt = sendTxAndWait(new byte[0], Hex.decode(metadata.bin), senderPrivateKey); + + if (!receipt.isSuccessful()) { + logger.error("Some troubles creating a contract: " + receipt.getError()); + return; + } + + byte[] address = receipt.getTransaction().getContractAddress(); + logger.info("Contract created: " + Hex.toHexString(address)); + + IncEventListener eventListener = new IncEventListener(pendingState, metadata.abi, address); + ethereum.addListener(eventListener.listener); + + CallTransaction.Contract contract = new CallTransaction.Contract(metadata.abi); + contractIncCall(senderPrivateKey, 777, metadata.abi, address); + contractIncCall(sender2PrivateKey, 555, metadata.abi, address); + + ProgramResult r = ethereum.callConstantFunction(Hex.toHexString(address), + contract.getByName("get")); + Object[] ret = contract.getByName("get").decodeResult(r.getHReturn()); + logger.info("Current contract data member value: " + ret[0]); + } + + /** + * Replays contract events for old blocks + * using {@link BlockReplay} with {@link EventListener} + */ + private void replayOnly() throws Exception { + logger.info("Contract already deployed to address 0x{}, using it", contractAddress); + CompilationResult.ContractMetadata metadata = compileContract(); + byte[] address = Hex.decode(contractAddress); + IncEventListener eventListener = new IncEventListener(pendingState, metadata.abi, address); + BlockReplay blockReplay = new BlockReplay(blockStore, transactionStore, eventListener.listener, + blockStore.getMaxNumber() - 5000); + ethereum.addListener(blockReplay); + blockReplay.replayAsync(); + } + + private CompilationResult.ContractMetadata compileContract() throws IOException { + logger.info("Compiling contract..."); + SolidityCompiler.Result result = compiler.compileSrc(contract.getBytes(), true, true, + SolidityCompiler.Options.ABI, SolidityCompiler.Options.BIN); + if (result.isFailed()) { + throw new RuntimeException("Contract compilation failed:\n" + result.errors); + } + CompilationResult res = CompilationResult.parse(result.output); + if (res.getContracts().isEmpty()) { + throw new RuntimeException("Compilation failed, no contracts returned:\n" + result.errors); + } + CompilationResult.ContractMetadata metadata = res.getContracts().iterator().next(); + if (metadata.bin == null || metadata.bin.isEmpty()) { + throw new RuntimeException("Compilation failed, no binary returned:\n" + result.errors); + } + + return metadata; + } + + private void contractIncCall(byte[] privateKey, int incAmount, + String contractABI, byte[] contractAddress) throws InterruptedException { + logger.info("Calling the contract function 'inc'"); + CallTransaction.Contract contract = new CallTransaction.Contract(contractABI); + CallTransaction.Function inc = contract.getByName("inc"); + byte[] functionCallBytes = inc.encode(incAmount); + TransactionReceipt receipt = sendTxAndWait(contractAddress, functionCallBytes, privateKey); + if (!receipt.isSuccessful()) { + logger.error("Some troubles invoking the contract: " + receipt.getError()); + return; + } + logger.info("Contract modified!"); + } + + protected TransactionReceipt sendTxAndWait(byte[] receiveAddress, + byte[] data, byte[] privateKey) throws InterruptedException { + BigInteger nonce = ethereum.getRepository().getNonce(ECKey.fromPrivate(privateKey).getAddress()); + Transaction tx = new Transaction( + ByteUtil.bigIntegerToBytes(nonce), + ByteUtil.longToBytesNoLeadZeroes(ethereum.getGasPrice()), + ByteUtil.longToBytesNoLeadZeroes(3_000_000), + receiveAddress, + ByteUtil.longToBytesNoLeadZeroes(0), + data, + ethereum.getChainIdForNextBlock()); + tx.sign(ECKey.fromPrivate(privateKey)); + + logger.info("<=== Sending transaction: " + tx); + ByteArrayWrapper txHashW = new ByteArrayWrapper(tx.getHash()); + txWaiters.put(txHashW, null); + ethereum.submitTransaction(tx); + + return waitForTx(txHashW); + } + + private void onBlock(Block block, List receipts) { + for (TransactionReceipt receipt : receipts) { + ByteArrayWrapper txHashW = new ByteArrayWrapper(receipt.getTransaction().getHash()); + if (txWaiters.containsKey(txHashW)) { + txWaiters.put(txHashW, receipt); + synchronized (this) { + notifyAll(); + } + } + } + } + + protected TransactionReceipt waitForTx(ByteArrayWrapper txHashW) throws InterruptedException { + long startBlock = ethereum.getBlockchain().getBestBlock().getNumber(); + while(true) { + TransactionReceipt receipt = txWaiters.get(txHashW); + if (receipt != null) { + return receipt; + } else { + long curBlock = ethereum.getBlockchain().getBestBlock().getNumber(); + if (curBlock > startBlock + 16) { + throw new RuntimeException("The transaction was not included during last 16 blocks: " + txHashW.toString().substring(0,8)); + } else { + logger.info("Waiting for block with transaction 0x" + txHashW.toString().substring(0,8) + + " included (" + (curBlock - startBlock) + " blocks received so far) ..."); + } + } + synchronized (this) { + wait(20000); + } + } + } + + public static void main(String[] args) throws Exception { + sLogger.info("Starting EthereumJ!"); + + class Config extends TestNetConfig{ + @Override + @Bean + public TestNetSample sampleBean() { + return new EventListenerSample(); + } + } + + // Based on Config class the BasicSample would be created by Spring + // and its springInit() method would be called as an entry point + EthereumFactory.createEthereum(Config.class); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/samples/PendingStateSample.java b/ethereumj-core/src/main/java/org/ethereum/samples/PendingStateSample.java index 56c275996c..16a40095d2 100644 --- a/ethereumj-core/src/main/java/org/ethereum/samples/PendingStateSample.java +++ b/ethereumj-core/src/main/java/org/ethereum/samples/PendingStateSample.java @@ -79,16 +79,13 @@ public void onBlock(Block block, List receipts) { } }); - new Thread("PendingStateSampleThread") { - @Override - public void run() { - try { - sendTransactions(); - } catch (Exception e) { - logger.error("Error while sending transactions", e); - } + new Thread(() -> { + try { + sendTransactions(); + } catch (Exception e) { + logger.error("Error while sending transactions", e); } - }.start(); + }, "PendingStateSampleThread").start(); } /** diff --git a/ethereumj-core/src/main/java/org/ethereum/samples/PrivateMinerSample.java b/ethereumj-core/src/main/java/org/ethereum/samples/PrivateMinerSample.java index 27769c9f1a..dca68670aa 100644 --- a/ethereumj-core/src/main/java/org/ethereum/samples/PrivateMinerSample.java +++ b/ethereumj-core/src/main/java/org/ethereum/samples/PrivateMinerSample.java @@ -187,14 +187,11 @@ public RegularNode() { @Override public void onSyncDone() { - new Thread(new Runnable() { - @Override - public void run() { - try { - generateTransactions(); - } catch (Exception e) { - logger.error("Error generating tx: ", e); - } + new Thread(() -> { + try { + generateTransactions(); + } catch (Exception e) { + logger.error("Error generating tx: ", e); } }).start(); } diff --git a/ethereumj-core/src/main/java/org/ethereum/samples/PrivateNetworkDiscoverySample.java b/ethereumj-core/src/main/java/org/ethereum/samples/PrivateNetworkDiscoverySample.java index 74b6191a47..5ae9e32fd2 100644 --- a/ethereumj-core/src/main/java/org/ethereum/samples/PrivateNetworkDiscoverySample.java +++ b/ethereumj-core/src/main/java/org/ethereum/samples/PrivateNetworkDiscoverySample.java @@ -93,40 +93,36 @@ public BasicSample node() { NodeManager nodeManager; { - new Thread(new Runnable() { - - @Override - public void run() { - try { - Thread.sleep(5000); - while (true) { - if (logger != null) { - Thread.sleep(15000); - if (channelManager != null) { - final Collection activePeers = channelManager.getActivePeers(); - final ArrayList ports = new ArrayList<>(); - for (Channel channel: activePeers) { - ports.add(channel.getInetSocketAddress().getHostName() + ":" + channel.getInetSocketAddress().getPort()); - } - - final Collection nodes = nodeManager.getTable().getAllNodes(); - final ArrayList nodesString = new ArrayList<>(); - for (NodeEntry node: nodes) { - nodesString.add(node.getNode().getHost() + ":" + node.getNode().getPort() + "@" + node.getNode().getHexId().substring(0, 6) ); - } - - logger.info("channelManager.getActivePeers() " + activePeers.size() + " " + Joiner.on(", ").join(ports)); - logger.info("nodeManager.getTable().getAllNodes() " + nodesString.size() + " " + Joiner.on(", ").join(nodesString)); - } else { - logger.info("Channel manager is null"); + new Thread(() -> { + try { + Thread.sleep(5000); + while (true) { + if (logger != null) { + Thread.sleep(15000); + if (channelManager != null) { + final Collection activePeers = channelManager.getActivePeers(); + final ArrayList ports = new ArrayList<>(); + for (Channel channel: activePeers) { + ports.add(channel.getInetSocketAddress().getHostName() + ":" + channel.getInetSocketAddress().getPort()); } + + final Collection nodes = nodeManager.getTable().getAllNodes(); + final ArrayList nodesString = new ArrayList<>(); + for (NodeEntry node: nodes) { + nodesString.add(node.getNode().getHost() + ":" + node.getNode().getPort() + "@" + node.getNode().getHexId().substring(0, 6) ); + } + + logger.info("channelManager.getActivePeers() " + activePeers.size() + " " + Joiner.on(", ").join(ports)); + logger.info("nodeManager.getTable().getAllNodes() " + nodesString.size() + " " + Joiner.on(", ").join(nodesString)); } else { - System.err.println("Logger is null for " + nodeIndex); + logger.info("Channel manager is null"); } + } else { + System.err.println("Logger is null for " + nodeIndex); } - } catch (Exception e) { - logger.error("Error checking peers count: ", e); } + } catch (Exception e) { + logger.error("Error checking peers count: ", e); } }).start(); } diff --git a/ethereumj-core/src/main/java/org/ethereum/samples/TestNetSample.java b/ethereumj-core/src/main/java/org/ethereum/samples/TestNetSample.java index 504e4f973d..7602488597 100644 --- a/ethereumj-core/src/main/java/org/ethereum/samples/TestNetSample.java +++ b/ethereumj-core/src/main/java/org/ethereum/samples/TestNetSample.java @@ -42,21 +42,19 @@ public class TestNetSample extends BasicSample { protected abstract static class TestNetConfig { private final String config = - // network has no discovery, peers are connected directly - "peer.discovery.enabled = false \n" + - // set port to 0 to disable accident inbound connections - "peer.listen.port = 0 \n" + - "peer.networkId = 161 \n" + + // Ropsten revive network configuration + "peer.discovery.enabled = true \n" + + "peer.listen.port = 30303 \n" + + "peer.networkId = 3 \n" + // a number of public peers for this network (not all of then may be functioning) "peer.active = [" + - " { url = 'enode://9bcff30ea776ebd28a9424d0ac7aa500d372f918445788f45a807d83186bd52c4c0afaf504d77e2077e5a99f1f264f75f8738646c1ac3673ccc652b65565c3bb@peer-1.ether.camp:30303' }," + - " { url = 'enode://c2b35ed63f5d79c7f160d05c54dd60b3ba32d455dbb10a5fe6fde44854073db02f9a538423a63a480126c74c7f650d77066ae446258e3d00388401d419b99f88@peer-2.ether.camp:30303' }," + - " { url = 'enode://8246787f8d57662b850b354f0b526251eafee1f077fc709460dc8788fa640a597e49ffc727580f3ebbbc5eacb34436a66ea40415fab9d73563481666090a6cf0@peer-3.ether.camp:30303' }" + + " {url = 'enode://6ce05930c72abc632c58e2e4324f7c7ea478cec0ed4fa2528982cf34483094e9cbc9216e7aa349691242576d552a2a56aaeae426c5303ded677ce455ba1acd9d@13.84.180.240:30303'}," + + " {url = 'enode://20c9ad97c081d63397d7b685a412227a40e23c8bdc6688c6f37e97cfbc22d2b4d1db1510d8f61e6a8866ad7f0e17c02b14182d37ea7c3c8b9c2683aeb6b733a1@52.169.14.227:30303'}" + "] \n" + "sync.enabled = true \n" + // special genesis for this test network - "genesis = frontier-test.json \n" + - "blockchain.config.name = 'testnet' \n" + + "genesis = ropsten.json \n" + + "blockchain.config.name = 'ropsten' \n" + "database.dir = testnetSampleDb \n" + "cache.flush.memory = 0"; diff --git a/ethereumj-core/src/main/java/org/ethereum/solidity/Abi.java b/ethereumj-core/src/main/java/org/ethereum/solidity/Abi.java index a59e9baba5..d795602fe4 100644 --- a/ethereumj-core/src/main/java/org/ethereum/solidity/Abi.java +++ b/ethereumj-core/src/main/java/org/ethereum/solidity/Abi.java @@ -63,12 +63,7 @@ public String toJson() { } private T find(Class resultClass, final Abi.Entry.Type type, final Predicate searchPredicate) { - return (T) CollectionUtils.find(this, new Predicate() { - @Override - public boolean evaluate(Abi.Entry entry) { - return entry.type == type && searchPredicate.evaluate((T) entry); - } - }); + return (T) CollectionUtils.find(this, entry -> entry.type == type && searchPredicate.evaluate((T) entry)); } public Function findFunction(Predicate searchPredicate) { @@ -80,12 +75,7 @@ public Event findEvent(Predicate searchPredicate) { } public Abi.Constructor findConstructor() { - return find(Constructor.class, Entry.Type.constructor, new Predicate() { - @Override - public boolean evaluate(Constructor object) { - return true; - } - }); + return find(Constructor.class, Entry.Type.constructor, object -> true); } @Override @@ -308,12 +298,7 @@ public List decode(byte[] data, byte[][] topics) { } private List filteredInputs(final boolean indexed) { - return select(inputs, new Predicate() { - @Override - public boolean evaluate(Param param) { - return param.indexed == indexed; - } - }); + return select(inputs, param -> param.indexed == indexed); } @Override diff --git a/ethereumj-core/src/main/java/org/ethereum/solidity/compiler/CompilationResult.java b/ethereumj-core/src/main/java/org/ethereum/solidity/compiler/CompilationResult.java index 02b7bca3ef..c5dd28453c 100644 --- a/ethereumj-core/src/main/java/org/ethereum/solidity/compiler/CompilationResult.java +++ b/ethereumj-core/src/main/java/org/ethereum/solidity/compiler/CompilationResult.java @@ -17,20 +17,26 @@ */ package org.ethereum.solidity.compiler; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import java.util.Map; @JsonIgnoreProperties(ignoreUnknown = true) public class CompilationResult { - public Map contracts; - public String version; + @JsonProperty("contracts") private Map contracts; + @JsonProperty("version") public String version; - public static CompilationResult parse(String rawJson) throws IOException { + @JsonIgnore public static CompilationResult parse(String rawJson) throws IOException { if(rawJson == null || rawJson.isEmpty()){ CompilationResult empty = new CompilationResult(); empty.contracts = Collections.emptyMap(); @@ -42,6 +48,73 @@ public static CompilationResult parse(String rawJson) throws IOException { } } + /** + * @return the contract's path given this compilation result contains exactly one contract + */ + @JsonIgnore public Path getContractPath() { + if (contracts.size() > 1) { + throw new UnsupportedOperationException("Source contains more than 1 contact. Please specify the contract name. Available keys (" + getContractKeys() + ")."); + } else { + String key = contracts.keySet().iterator().next(); + return Paths.get(key.substring(0, key.lastIndexOf(':'))); + } + } + + /** + * @return the contract's name given this compilation result contains exactly one contract + */ + @JsonIgnore public String getContractName() { + if (contracts.size() > 1) { + throw new UnsupportedOperationException("Source contains more than 1 contact. Please specify the contract name. Available keys (" + getContractKeys() + ")."); + } else { + String key = contracts.keySet().iterator().next(); + return key.substring(key.lastIndexOf(':') + 1); + } + } + + /** + * @param contractName The contract name + * @return the first contract found for a given contract name; use {@link #getContract(Path, String)} if this compilation result contains more than one contract with the same name + */ + @JsonIgnore public ContractMetadata getContract(String contractName) { + if (contractName == null && contracts.size() == 1) { + return contracts.values().iterator().next(); + } else if (contractName == null || contractName.isEmpty()) { + throw new UnsupportedOperationException("Source contains more than 1 contact. Please specify the contract name. Available keys (" + getContractKeys() + ")."); + } + for (Map.Entry entry : contracts.entrySet()) { + String key = entry.getKey(); + String name = key.substring(key.lastIndexOf(':') + 1); + if (contractName.equals(name)) { + return entry.getValue(); + } + } + throw new UnsupportedOperationException("No contract found with name '" + contractName + "'. Please specify a valid contract name. Available keys (" + getContractKeys() + ")."); + } + + /** + * @param contractPath The contract path + * @param contractName The contract name + * @return the contract with key {@code contractPath:contractName} if it exists; {@code null} otherwise + */ + @JsonIgnore public ContractMetadata getContract(Path contractPath, String contractName) { + return contracts.get(contractPath.toAbsolutePath().toString() + ':' + contractName); + } + + /** + * @return all contracts from this compilation result + */ + @JsonIgnore public List getContracts() { + return new ArrayList<>(contracts.values()); + } + + /** + * @return all keys from this compilation result + */ + @JsonIgnore public List getContractKeys() { + return new ArrayList<>(contracts.keySet()); + } + @JsonIgnoreProperties(ignoreUnknown = true) public static class ContractMetadata { public String abi; diff --git a/ethereumj-core/src/main/java/org/ethereum/solidity/compiler/SolidityCompiler.java b/ethereumj-core/src/main/java/org/ethereum/solidity/compiler/SolidityCompiler.java index 6b67723279..303a97ace6 100644 --- a/ethereumj-core/src/main/java/org/ethereum/solidity/compiler/SolidityCompiler.java +++ b/ethereumj-core/src/main/java/org/ethereum/solidity/compiler/SolidityCompiler.java @@ -17,15 +17,24 @@ */ package org.ethereum.solidity.compiler; -import com.google.common.base.Joiner; import org.ethereum.config.SystemProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.io.*; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Serializable; +import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import static java.util.stream.Collectors.toList; + @Component public class SolidityCompiler { @@ -38,30 +47,106 @@ public SolidityCompiler(SystemProperties config) { solc = new Solc(config); } - public static Result compile(File sourceDirectory, boolean combinedJson, Options... options) throws IOException { + public static Result compile(File sourceDirectory, boolean combinedJson, Option... options) throws IOException { return getInstance().compileSrc(sourceDirectory, false, combinedJson, options); } - public enum Options { + /** + * This class is mainly here for backwards compatibility; however we are now reusing it making it the solely public + * interface listing all the supported options. + */ + public static final class Options { + public static final OutputOption AST = OutputOption.AST; + public static final OutputOption BIN = OutputOption.BIN; + public static final OutputOption INTERFACE = OutputOption.INTERFACE; + public static final OutputOption ABI = OutputOption.ABI; + public static final OutputOption METADATA = OutputOption.METADATA; + public static final OutputOption ASTJSON = OutputOption.ASTJSON; + + private static final NameOnlyOption OPTIMIZE = NameOnlyOption.OPTIMIZE; + private static final NameOnlyOption VERSION = NameOnlyOption.VERSION; + + private static class CombinedJson extends ListOption { + private CombinedJson(List values) { + super("combined-json", values); + } + } + public static class AllowPaths extends ListOption { + public AllowPaths(List values) { + super("allow-paths", values); + } + } + } + + public interface Option extends Serializable { + String getValue(); + String getName(); + } + + private static class ListOption implements Option { + private String name; + private List values; + + private ListOption(String name, List values) { + this.name = name; + this.values = values; + } + + @Override public String getValue() { + StringBuilder result = new StringBuilder(); + for (Object value : values) { + if (OutputOption.class.isAssignableFrom(value.getClass())) { + result.append((result.length() == 0) ? ((OutputOption) value).getName() : ',' + ((OutputOption) value).getName()); + } else if (Path.class.isAssignableFrom(value.getClass())) { + result.append((result.length() == 0) ? ((Path) value).toAbsolutePath().toString() : ',' + ((Path) value).toAbsolutePath().toString()); + } else if (File.class.isAssignableFrom(value.getClass())) { + result.append((result.length() == 0) ? ((File) value).getAbsolutePath() : ',' + ((File) value).getAbsolutePath()); + } else if (String.class.isAssignableFrom(value.getClass())) { + result.append((result.length() == 0) ? value : "," + value); + } else { + throw new UnsupportedOperationException("Unexpected type, value '" + value + "' cannot be retrieved."); + } + } + return result.toString(); + } + @Override public String getName() { return name; } + @Override public String toString() { return name; } + } + + private enum NameOnlyOption implements Option { + OPTIMIZE("optimize"), + VERSION("version"); + + private String name; + + NameOnlyOption(String name) { + this.name = name; + } + + @Override public String getValue() { return ""; } + @Override public String getName() { return name; } + @Override public String toString() { + return name; + } + } + + private enum OutputOption implements Option { AST("ast"), BIN("bin"), INTERFACE("interface"), ABI("abi"), - METADATA("metadata"), + METADATA("metadata"), ASTJSON("ast-json"); private String name; - Options(String name) { + OutputOption(String name) { this.name = name; } - public String getName() { - return name; - } - - @Override - public String toString() { + @Override public String getValue() { return ""; } + @Override public String getName() { return name; } + @Override public String toString() { return name; } } @@ -69,7 +154,7 @@ public String toString() { public static class Result { public String errors; public String output; - private boolean success = false; + private boolean success; public Result(String errors, String output, boolean success) { this.errors = errors; @@ -125,11 +210,11 @@ public void run() { } } - public static Result compile(byte[] source, boolean combinedJson, Options... options) throws IOException { + public static Result compile(byte[] source, boolean combinedJson, Option... options) throws IOException { return getInstance().compileSrc(source, false, combinedJson, options); } - public Result compileSrc(File source, boolean optimize, boolean combinedJson, Options... options) throws IOException { + public Result compileSrc(File source, boolean optimize, boolean combinedJson, Option... options) throws IOException { List commandParts = prepareCommandOptions(optimize, combinedJson, options); commandParts.add(source.getAbsolutePath()); @@ -156,24 +241,33 @@ public Result compileSrc(File source, boolean optimize, boolean combinedJson, Op return new Result(error.getContent(), output.getContent(), success); } - private List prepareCommandOptions(boolean optimize, boolean combinedJson, Options[] options) throws IOException { + private List prepareCommandOptions(boolean optimize, boolean combinedJson, Option... options) throws IOException { List commandParts = new ArrayList<>(); commandParts.add(solc.getExecutable().getCanonicalPath()); if (optimize) { - commandParts.add("--optimize"); + commandParts.add("--" + Options.OPTIMIZE.getName()); } if (combinedJson) { - commandParts.add("--combined-json"); - commandParts.add(Joiner.on(',').join(options)); + Option combinedJsonOption = new Options.CombinedJson(getElementsOf(OutputOption.class, options)); + commandParts.add("--" + combinedJsonOption.getName()); + commandParts.add(combinedJsonOption.getValue()); } else { - for (Options option : options) { + for (Option option : getElementsOf(OutputOption.class, options)) { commandParts.add("--" + option.getName()); } } + for (Option option : getElementsOf(ListOption.class, options)) { + commandParts.add("--" + option.getName()); + commandParts.add(option.getValue()); + } return commandParts; } - public Result compileSrc(byte[] source, boolean optimize, boolean combinedJson, Options... options) throws IOException { + private static List getElementsOf(Class clazz, Option... options) { + return Arrays.stream(options).filter(clazz::isInstance).map(clazz::cast).collect(toList()); + } + + public Result compileSrc(byte[] source, boolean optimize, boolean combinedJson, Option... options) throws IOException { List commandParts = prepareCommandOptions(optimize, combinedJson, options); ProcessBuilder processBuilder = new ProcessBuilder(commandParts) @@ -205,7 +299,7 @@ public Result compileSrc(byte[] source, boolean optimize, boolean combinedJson, public static String runGetVersionOutput() throws IOException { List commandParts = new ArrayList<>(); commandParts.add(getInstance().solc.getExecutable().getCanonicalPath()); - commandParts.add("--version"); + commandParts.add("--" + Options.VERSION.getName()); ProcessBuilder processBuilder = new ProcessBuilder(commandParts) .directory(getInstance().solc.getExecutable().getParentFile()); @@ -231,12 +325,10 @@ public static String runGetVersionOutput() throws IOException { throw new RuntimeException("Problem getting solc version: " + error.getContent()); } - - public static SolidityCompiler getInstance() { if (INSTANCE == null) { INSTANCE = new SolidityCompiler(SystemProperties.getDefault()); } return INSTANCE; } -} +} \ No newline at end of file diff --git a/ethereumj-core/src/main/java/org/ethereum/sync/BlockBodiesDownloader.java b/ethereumj-core/src/main/java/org/ethereum/sync/BlockBodiesDownloader.java index 08a30ff399..29d5ee4f6b 100644 --- a/ethereumj-core/src/main/java/org/ethereum/sync/BlockBodiesDownloader.java +++ b/ethereumj-core/src/main/java/org/ethereum/sync/BlockBodiesDownloader.java @@ -17,6 +17,7 @@ */ package org.ethereum.sync; +import org.ethereum.config.SystemProperties; import org.ethereum.core.*; import org.ethereum.crypto.HashUtil; import org.ethereum.datasource.DataSourceArray; @@ -68,9 +69,12 @@ public class BlockBodiesDownloader extends BlockDownloader { Thread headersThread; int downloadCnt = 0; + private long blockBytesLimit = 32 * 1024 * 1024; + @Autowired - public BlockBodiesDownloader(BlockHeaderValidator headerValidator) { + public BlockBodiesDownloader(final SystemProperties config, BlockHeaderValidator headerValidator) { super(headerValidator); + blockBytesLimit = config.blockQueueSize(); } public void startImporting() { @@ -78,24 +82,19 @@ public void startImporting() { syncQueue = new SyncQueueImpl(Collections.singletonList(genesis)); curTotalDiff = genesis.getDifficultyBI(); - headersThread = new Thread("FastsyncHeadersFetchThread") { - @Override - public void run() { - headerLoop(); - } - }; + headersThread = new Thread(this::headerLoop, "FastsyncHeadersFetchThread"); headersThread.start(); setHeadersDownload(false); - init(syncQueue, syncPool); + init(syncQueue, syncPool, "BlockBodiesDownloader"); } private void headerLoop() { while (curBlockIdx < headerStore.size() && !Thread.currentThread().isInterrupted()) { List wrappers = new ArrayList<>(); List emptyBodyHeaders = new ArrayList<>(); - for (int i = 0; i < 10000 - syncQueue.getHeadersCount() && curBlockIdx < headerStore.size(); i++) { + for (int i = 0; i < getTotalHeadersToRequest() - syncQueue.getHeadersCount() && curBlockIdx < headerStore.size(); i++) { BlockHeader header = headerStore.get(curBlockIdx++); wrappers.add(new BlockHeaderWrapper(header, new byte[0])); @@ -154,6 +153,10 @@ protected void pushBlocks(List blockWrappers) { } dbFlushManager.commit(); + estimateBlockSize(blockWrappers); + logger.debug("{}: header queue size {} (~{}mb)", name, syncQueue.getHeadersCount(), + syncQueue.getHeadersCount() * getEstimatedBlockSize() / 1024 / 1024); + long c = System.currentTimeMillis(); if (c - t > 5000) { t = c; @@ -180,6 +183,16 @@ protected int getBlockQueueFreeSize() { return Integer.MAX_VALUE; } + @Override + protected int getTotalHeadersToRequest() { + if (getEstimatedBlockSize() == 0) { + return getHeaderQueueLimit(); + } + + int slotsLeft = Math.max((int) (blockBytesLimit / getEstimatedBlockSize()), MAX_IN_REQUEST); + return Math.min(slotsLeft + MAX_IN_REQUEST, getHeaderQueueLimit()); + } + public int getDownloadedCount() { return downloadCnt; } diff --git a/ethereumj-core/src/main/java/org/ethereum/sync/BlockDownloader.java b/ethereumj-core/src/main/java/org/ethereum/sync/BlockDownloader.java index a196ddb9d6..771629815d 100644 --- a/ethereumj-core/src/main/java/org/ethereum/sync/BlockDownloader.java +++ b/ethereumj-core/src/main/java/org/ethereum/sync/BlockDownloader.java @@ -20,9 +20,9 @@ import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import org.apache.commons.collections4.queue.CircularFifoQueue; import org.ethereum.core.*; import org.ethereum.net.server.Channel; -import org.ethereum.util.ByteArrayMap; import org.ethereum.validator.BlockHeaderValidator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,7 +46,7 @@ public abstract class BlockDownloader { private int headerQueueLimit = 10000; // Max number of Blocks / Headers in one request - private static int MAX_IN_REQUEST = 192; + public static int MAX_IN_REQUEST = 192; private static int REQUESTS = 32; private BlockHeaderValidator headerValidator; @@ -69,6 +69,11 @@ public abstract class BlockDownloader { private CountDownLatch stopLatch = new CountDownLatch(1); + protected String name = "BlockDownloader"; + + private long estimatedBlockSize = 0; + private final CircularFifoQueue lastBlockSizes = new CircularFifoQueue<>(10 * MAX_IN_REQUEST); + public BlockDownloader(BlockHeaderValidator headerValidator) { this.headerValidator = headerValidator; } @@ -76,6 +81,7 @@ public BlockDownloader(BlockHeaderValidator headerValidator) { protected abstract void pushBlocks(List blockWrappers); protected abstract void pushHeaders(List headers); protected abstract int getBlockQueueFreeSize(); + protected abstract int getTotalHeadersToRequest(); protected void finishDownload() {} @@ -91,29 +97,20 @@ public void setHeadersDownload(boolean headersDownload) { this.headersDownload = headersDownload; } - public void init(SyncQueueIfc syncQueue, final SyncPool pool) { + public void init(SyncQueueIfc syncQueue, final SyncPool pool, String name) { this.syncQueue = syncQueue; this.pool = pool; + this.name = name; - logger.info("Initializing BlockDownloader."); + logger.info("{}: Initializing BlockDownloader.", name); if (headersDownload) { - getHeadersThread = new Thread(new Runnable() { - @Override - public void run() { - headerRetrieveLoop(); - } - }, "SyncThreadHeaders"); + getHeadersThread = new Thread(this::headerRetrieveLoop, "SyncThreadHeaders"); getHeadersThread.start(); } if (blockBodiesDownload) { - getBodiesThread = new Thread(new Runnable() { - @Override - public void run() { - blockRetrieveLoop(); - } - }, "SyncThreadBlocks"); + getBodiesThread = new Thread(this::blockRetrieveLoop, "SyncThreadBlocks"); getBodiesThread.start(); } } @@ -140,6 +137,10 @@ public int getBlockQueueLimit() { return blockQueueLimit; } + public int getHeaderQueueLimit() { + return headerQueueLimit; + } + public void setBlockQueueLimit(int blockQueueLimit) { this.blockQueueLimit = blockQueueLimit; } @@ -150,9 +151,9 @@ private void headerRetrieveLoop() { try { if (hReq.isEmpty()) { synchronized (this) { - hReq = syncQueue.requestHeaders(MAX_IN_REQUEST, 128, headerQueueLimit); + hReq = syncQueue.requestHeaders(MAX_IN_REQUEST, 128, getTotalHeadersToRequest()); if (hReq == null) { - logger.info("Headers download complete."); + logger.info("{}: Headers download complete.", name); headersDownloadComplete = true; if (!blockBodiesDownload) { finishDownload(); @@ -160,7 +161,7 @@ private void headerRetrieveLoop() { } return; } - String l = "########## New header requests (" + hReq.size() + "):\n"; + String l = "########## " + name + ": New header requests (" + hReq.size() + "):\n"; for (SyncQueueIfc.HeadersRequest request : hReq) { l += " " + request + "\n"; } @@ -174,10 +175,10 @@ private void headerRetrieveLoop() { final Channel any = getAnyPeer(); if (any == null) { - logger.debug("headerRetrieveLoop: No IDLE peers found"); + logger.debug("{} headerRetrieveLoop: No IDLE peers found", name); break; } else { - logger.debug("headerRetrieveLoop: request headers (" + headersRequest.getStart() + ") from " + any.getNode()); + logger.debug("{} headerRetrieveLoop: request headers (" + headersRequest.getStart() + ") from " + any.getNode(), name); ListenableFuture> futureHeaders = headersRequest.getHash() == null ? any.getEthHandler().sendGetBlockHeaders(headersRequest.getStart(), headersRequest.getCount(), headersRequest.isReverse()) : any.getEthHandler().sendGetBlockHeaders(headersRequest.getHash(), headersRequest.getCount(), headersRequest.getStep(), headersRequest.isReverse()); @@ -192,7 +193,7 @@ public void onSuccess(List result) { @Override public void onFailure(Throwable t) { - logger.debug("Error receiving headers. Dropping the peer.", t); + logger.debug("{}: Error receiving headers. Dropping the peer.", name, t); any.getEthHandler().dropConnection(); } }); @@ -228,7 +229,7 @@ public void onSuccess(List result) { @Override public void onFailure(Throwable t) { - logger.debug("Error receiving Blocks. Dropping the peer.", t); + logger.debug("{}: Error receiving Blocks. Dropping the peer.", name, t); peer.getEthHandler().dropConnection(); } } @@ -241,14 +242,14 @@ public void onFailure(Throwable t) { } if (bReqs.isEmpty() && headersDownloadComplete) { - logger.info("Block download complete."); + logger.info("{}: Block download complete.", name); finishDownload(); downloadComplete = true; return; } int blocksToAsk = getBlockQueueFreeSize(); - if (blocksToAsk > MAX_IN_REQUEST) { + if (blocksToAsk >= MAX_IN_REQUEST) { // SyncQueueIfc.BlocksRequest bReq = syncQueue.requestBlocks(maxBlocks); if (bReqs.size() == 1 && bReqs.get(0).getBlockHeaders().size() <= 3) { @@ -276,10 +277,10 @@ public void onFailure(Throwable t) { SyncQueueIfc.BlocksRequest blocksRequest = it.next(); Channel any = getAnyPeer(); if (any == null) { - logger.debug("blockRetrieveLoop: No IDLE peers found"); + logger.debug("{} blockRetrieveLoop: No IDLE peers found", name); break; } else { - logger.debug("blockRetrieveLoop: Requesting " + blocksRequest.getBlockHeaders().size() + " blocks from " + any.getNode()); + logger.debug("{} blockRetrieveLoop: Requesting " + blocksRequest.getBlockHeaders().size() + " blocks from " + any.getNode(), name); ListenableFuture> futureBlocks = any.getEthHandler().sendGetBlockBodies(blocksRequest.getBlockHeaders()); blocksRequested += blocksRequest.getBlockHeaders().size(); @@ -292,7 +293,7 @@ public void onFailure(Throwable t) { } receivedBlocksLatch = new CountDownLatch(max(reqBlocksCounter - 2, 1)); } else { - logger.debug("blockRetrieveLoop: BlockQueue is full"); + logger.debug("{} blockRetrieveLoop: BlockQueue is full", name); receivedBlocksLatch = new CountDownLatch(1); } receivedBlocksLatch.await(200, TimeUnit.MILLISECONDS); @@ -317,8 +318,8 @@ private void addBlocks(List blocks, byte[] nodeId) { } synchronized (this) { - logger.debug("Adding new " + blocks.size() + " blocks to sync queue: " + - blocks.get(0).getShortDescr() + " ... " + blocks.get(blocks.size() - 1).getShortDescr()); + logger.debug("{}: Adding new " + blocks.size() + " blocks to sync queue: " + + blocks.get(0).getShortDescr() + " ... " + blocks.get(blocks.size() - 1).getShortDescr(), name); List newBlocks = syncQueue.addBlocks(blocks); @@ -328,8 +329,8 @@ private void addBlocks(List blocks, byte[] nodeId) { } - logger.debug("Pushing " + wrappers.size() + " blocks to import queue: " + (wrappers.isEmpty() ? "" : - wrappers.get(0).getBlock().getShortDescr() + " ... " + wrappers.get(wrappers.size() - 1).getBlock().getShortDescr())); + logger.debug("{}: Pushing " + wrappers.size() + " blocks to import queue: " + (wrappers.isEmpty() ? "" : + wrappers.get(0).getBlock().getShortDescr() + " ... " + wrappers.get(wrappers.size() - 1).getBlock().getShortDescr()), name); pushBlocks(wrappers); } @@ -337,7 +338,8 @@ private void addBlocks(List blocks, byte[] nodeId) { receivedBlocksLatch.countDown(); if (logger.isDebugEnabled()) logger.debug( - "Blocks waiting to be proceed: lastBlock.number: [{}]", + "{}: Blocks waiting to be proceed: lastBlock.number: [{}]", + name, blocks.get(blocks.size() - 1).getNumber() ); } @@ -364,7 +366,7 @@ private boolean validateAndAddHeaders(List headers, byte[] nodeId) if (!isValid(header)) { if (logger.isDebugEnabled()) { - logger.debug("Invalid header RLP: {}", Hex.toHexString(header.getEncoded())); + logger.debug("{}: Invalid header RLP: {}", Hex.toHexString(header.getEncoded()), name); } return false; @@ -382,7 +384,7 @@ private boolean validateAndAddHeaders(List headers, byte[] nodeId) receivedHeadersLatch.countDown(); - logger.debug("{} headers added", headers.size()); + logger.debug("{}: {} headers added", name, headers.size()); return true; } @@ -416,4 +418,32 @@ public void close() { } } + /** + * Estimates block size in bytes. + * Block memory size can depend on the underlying logic, + * hence ancestors should call this method on their own, + * preferably after actions that impact on block memory size (like RLP parsing, signature recover) are done + */ + protected void estimateBlockSize(BlockWrapper blockWrapper) { + synchronized (lastBlockSizes) { + lastBlockSizes.add(blockWrapper.estimateMemSize()); + estimatedBlockSize = lastBlockSizes.stream().mapToLong(Long::longValue).sum() / lastBlockSizes.size(); + } + logger.debug("{}: estimated block size: {}", name, estimatedBlockSize); + } + + protected void estimateBlockSize(Collection blockWrappers) { + if (blockWrappers.isEmpty()) + return; + + synchronized (lastBlockSizes) { + blockWrappers.forEach(b -> lastBlockSizes.add(b.estimateMemSize())); + estimatedBlockSize = lastBlockSizes.stream().mapToLong(Long::longValue).sum() / lastBlockSizes.size(); + } + logger.debug("{}: estimated block size: {}", name, estimatedBlockSize); + } + + public long getEstimatedBlockSize() { + return estimatedBlockSize; + } } diff --git a/ethereumj-core/src/main/java/org/ethereum/sync/FastSyncDownloader.java b/ethereumj-core/src/main/java/org/ethereum/sync/FastSyncDownloader.java index 0fbd122058..236357301b 100644 --- a/ethereumj-core/src/main/java/org/ethereum/sync/FastSyncDownloader.java +++ b/ethereumj-core/src/main/java/org/ethereum/sync/FastSyncDownloader.java @@ -19,15 +19,12 @@ import org.ethereum.core.BlockHeaderWrapper; import org.ethereum.core.BlockWrapper; -import org.ethereum.core.Blockchain; -import org.ethereum.db.DbFlushManager; import org.ethereum.db.IndexedBlockStore; -import org.ethereum.util.ByteUtil; import org.ethereum.validator.BlockHeaderValidator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import java.math.BigInteger; @@ -37,7 +34,7 @@ * Created by Anton Nashatyrev on 27.10.2016. */ @Component -@Lazy +@Scope("prototype") public class FastSyncDownloader extends BlockDownloader { private final static Logger logger = LoggerFactory.getLogger("sync"); @@ -57,9 +54,12 @@ public FastSyncDownloader(BlockHeaderValidator headerValidator) { } public void startImporting(byte[] fromHash, int count) { - SyncQueueReverseImpl syncQueueReverse = new SyncQueueReverseImpl(fromHash); - init(syncQueueReverse, syncPool); this.maxCount = count <= 0 ? Integer.MAX_VALUE : count; + setHeaderQueueLimit(maxCount); + setBlockQueueLimit(maxCount); + + SyncQueueReverseImpl syncQueueReverse = new SyncQueueReverseImpl(fromHash); + init(syncQueueReverse, syncPool, "FastSync"); } @Override @@ -70,7 +70,7 @@ protected void pushBlocks(List blockWrappers) { blockStore.saveBlock(blockWrapper.getBlock(), BigInteger.ZERO, true); counter++; if (counter >= maxCount) { - logger.info("All requested " + counter + " blocks are downloaded. (last " + blockWrapper.getBlock().getShortDescr() + ")"); + logger.info("FastSync: All requested " + counter + " blocks are downloaded. (last " + blockWrapper.getBlock().getShortDescr() + ")"); stop(); break; } @@ -90,7 +90,12 @@ protected void pushHeaders(List headers) {} @Override protected int getBlockQueueFreeSize() { - return Integer.MAX_VALUE; + return getBlockQueueLimit(); + } + + @Override + protected int getTotalHeadersToRequest() { + return getHeaderQueueLimit(); } // TODO: receipts loading here diff --git a/ethereumj-core/src/main/java/org/ethereum/sync/FastSyncManager.java b/ethereumj-core/src/main/java/org/ethereum/sync/FastSyncManager.java index 828090d378..42c1d514ed 100644 --- a/ethereumj-core/src/main/java/org/ethereum/sync/FastSyncManager.java +++ b/ethereumj-core/src/main/java/org/ethereum/sync/FastSyncManager.java @@ -24,8 +24,9 @@ import org.ethereum.config.SystemProperties; import org.ethereum.core.*; import org.ethereum.crypto.HashUtil; -import org.ethereum.datasource.BloomFilter; import org.ethereum.datasource.DbSource; +import org.ethereum.datasource.NodeKeyCompositor; +import org.ethereum.datasource.rocksdb.RocksDbDataSource; import org.ethereum.db.DbFlushManager; import org.ethereum.db.IndexedBlockStore; import org.ethereum.db.StateSource; @@ -36,8 +37,8 @@ import org.ethereum.net.client.Capability; import org.ethereum.net.eth.handler.Eth63; import org.ethereum.net.message.ReasonCode; -import org.ethereum.net.rlpx.discover.NodeHandler; import org.ethereum.net.server.Channel; +import org.ethereum.trie.TrieKey; import org.ethereum.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,10 +51,12 @@ import java.math.BigInteger; import java.util.*; import java.util.concurrent.*; +import java.util.stream.Collectors; import static org.ethereum.listener.EthereumListener.SyncState.COMPLETE; import static org.ethereum.listener.EthereumListener.SyncState.SECURE; import static org.ethereum.listener.EthereumListener.SyncState.UNSECURE; +import static org.ethereum.trie.TrieKey.fromPacked; import static org.ethereum.util.CompactEncoder.hasTerminator; /** @@ -101,9 +104,6 @@ public class FastSyncManager { @Autowired DbFlushManager dbFlushManager; - @Autowired - FastSyncDownloader downloader; - @Autowired CompositeEthereumListener listener; @@ -140,42 +140,37 @@ private void waitDbQueueSizeBelow(int size) { void init() { - dbWriterThread = new Thread("FastSyncDBWriter") { - @Override - public void run() { - try { - while (!Thread.currentThread().isInterrupted()) { - synchronized (FastSyncManager.this) { - if (dbQueueSizeMonitor >= 0 && dbWriteQueue.size() <= dbQueueSizeMonitor) { - FastSyncManager.this.notifyAll(); - } - } - TrieNodeRequest request = dbWriteQueue.take(); - nodesInserted++; - stateSource.getNoJournalSource().put(request.nodeHash, request.response); - if (nodesInserted % 1000 == 0) { - dbFlushManager.commit(); - logger.debug("FastSyncDBWriter: commit: dbWriteQueue.size = " + dbWriteQueue.size()); + dbWriterThread = new Thread(() -> { + try { + while (!Thread.currentThread().isInterrupted()) { + synchronized (FastSyncManager.this) { + if (dbQueueSizeMonitor >= 0 && dbWriteQueue.size() <= dbQueueSizeMonitor) { + FastSyncManager.this.notifyAll(); } } - } catch (InterruptedException e) { - } catch (Exception e) { - logger.error("Fatal FastSync error while writing data", e); + TrieNodeRequest request = dbWriteQueue.take(); + nodesInserted++; + request.storageHashes().forEach(hash -> stateSource.getNoJournalSource().put(hash, request.response)); + + if (nodesInserted % 1000 == 0) { + dbFlushManager.commit(); + logger.debug("FastSyncDBWriter: commit: dbWriteQueue.size = " + dbWriteQueue.size()); + } } + } catch (InterruptedException e) { + } catch (Exception e) { + logger.error("Fatal FastSync error while writing data", e); } - }; + }, "FastSyncDBWriter"); dbWriterThread.start(); - fastSyncThread = new Thread("FastSyncLoop") { - @Override - public void run() { - try { - main(); - } catch (Exception e) { - logger.error("Fatal FastSync loop error", e); - } + fastSyncThread = new Thread(() -> { + try { + main(); + } catch (Exception e) { + logger.error("Fatal FastSync loop error", e); } - }; + }, "FastSyncLoop"); fastSyncThread.start(); } @@ -193,17 +188,21 @@ public SyncStatus getSyncState() { return new SyncStatus(SyncStatus.SyncStage.StateNodes, nodesInserted, nodesQueue.size() + pendingNodes.size() + nodesInserted); case SECURE: - return new SyncStatus(SyncStatus.SyncStage.Headers, headersDownloader.getHeadersLoaded(), - pivot.getNumber()); + if (headersDownloader != null) { + return new SyncStatus(SyncStatus.SyncStage.Headers, headersDownloader.getHeadersLoaded(), + pivot.getNumber()); + } else { + return new SyncStatus(SyncStatus.SyncStage.Headers, pivot.getNumber(), pivot.getNumber()); + } case COMPLETE: if (receiptsDownloader != null) { return new SyncStatus(SyncStatus.SyncStage.Receipts, receiptsDownloader.getDownloadedBlocksCount(), pivot.getNumber()); - } else if (blockBodiesDownloader!= null) { + } else if (blockBodiesDownloader != null) { return new SyncStatus(SyncStatus.SyncStage.BlockBodies, blockBodiesDownloader.getDownloadedCount(), pivot.getNumber()); } else { - return new SyncStatus(SyncStatus.SyncStage.BlockBodies, 0, pivot.getNumber()); + return new SyncStatus(SyncStatus.SyncStage.Receipts, pivot.getNumber(), pivot.getNumber()); } } return new SyncStatus(SyncStatus.SyncStage.Complete, 0, 0); @@ -224,6 +223,9 @@ private class TrieNodeRequest { byte[] nodeHash; byte[] response; final Map requestSent = new HashMap<>(); + TrieKey nodePath = TrieKey.empty(false); + + private final Set accounts = new ByteArraySet(); TrieNodeRequest(TrieNodeType type, byte[] nodeHash) { this.type = type; @@ -236,6 +238,17 @@ private class TrieNodeRequest { } } + TrieNodeRequest(TrieNodeType type, byte[] nodeHash, byte[] accountKey) { + this(type, nodeHash); + this.accounts.add(accountKey); + } + + TrieNodeRequest(TrieNodeType type, byte[] nodeHash, TrieKey nodePath, Set accounts) { + this(type, nodeHash); + this.nodePath = nodePath; + this.accounts.addAll(accounts); + } + List createChildRequests() { if (type == TrieNodeType.CODE) { return Collections.emptyList(); @@ -248,20 +261,34 @@ List createChildRequests() { byte[] nodeValue = (byte[]) node.get(1); AccountState state = new AccountState(nodeValue); + TrieKey accountKey = nodePath.concat(fromPacked((byte[]) node.get(0))); + if (!FastByteComparisons.equal(HashUtil.EMPTY_DATA_HASH, state.getCodeHash())) { - ret.add(new TrieNodeRequest(TrieNodeType.CODE, state.getCodeHash())); + ret.add(new TrieNodeRequest(TrieNodeType.CODE, state.getCodeHash(), accountKey.toNormal())); } if (!FastByteComparisons.equal(HashUtil.EMPTY_TRIE_HASH, state.getStateRoot())) { - ret.add(new TrieNodeRequest(TrieNodeType.STORAGE, state.getStateRoot())); + ret.add(new TrieNodeRequest(TrieNodeType.STORAGE, state.getStateRoot(), accountKey.toNormal())); } return ret; } } - List childHashes = getChildHashes(node); - for (byte[] childHash : childHashes) { - ret.add(new TrieNodeRequest(type, childHash)); + if (node.size() == 2) { + Value val = new Value(node.get(1)); + if (val.isHashCode() && !hasTerminator((byte[]) node.get(0))) { + TrieKey childPath = nodePath.concat(fromPacked((byte[]) node.get(0))); + ret.add(new TrieNodeRequest(type, val.asBytes(), childPath, accountsSnapshot())); + } + } else { + for (int j = 0; j < 16; ++j) { + Value val = new Value(node.get(j)); + if (val.isHashCode()) { + TrieKey childPath = nodePath.concat(TrieKey.singleHex(j)); + ret.add(new TrieNodeRequest(type, val.asBytes(), childPath, accountsSnapshot())); + } + } } + return ret; } @@ -278,31 +305,37 @@ public Set requestIdsSnapshot() { } } + public List storageHashes() { + if (type == TrieNodeType.STATE) { + return Collections.singletonList(nodeHash); + } else { + return accountsSnapshot().stream().map(key -> NodeKeyCompositor.compose(nodeHash, key)) + .collect(Collectors.toList()); + } + } + + public Set accountsSnapshot() { + synchronized (FastSyncManager.this) { + return new HashSet<>(accounts); + } + } + + public void merge(TrieNodeRequest other) { + synchronized (FastSyncManager.this) { + accounts.addAll(other.accounts); + } + } + @Override public String toString() { return "TrieNodeRequest{" + "type=" + type + ", nodeHash=" + Hex.toHexString(nodeHash) + + ", nodePath=" + nodePath + '}'; } } - private static List getChildHashes(List siblings) { - List ret = new ArrayList<>(); - if (siblings.size() == 2) { - Value val = new Value(siblings.get(1)); - if (val.isHashCode() && !hasTerminator((byte[]) siblings.get(0))) - ret.add(val.asBytes()); - } else { - for (int j = 0; j < 16; ++j) { - Value val = new Value(siblings.get(j)); - if (val.isHashCode()) - ret.add(val.asBytes()); - } - } - return ret; - } - Deque nodesQueue = new LinkedBlockingDeque<>(); ByteArrayMap pendingNodes = new ByteArrayMap<>(); Long requestId = 0L; @@ -350,11 +383,14 @@ boolean requestNextNodes(int cnt) { synchronized (this) { for (int i = 0; i < cnt && !nodesQueue.isEmpty(); i++) { TrieNodeRequest req = nodesQueue.poll(); + hashes.add(req.nodeHash); TrieNodeRequest request = pendingNodes.get(req.nodeHash); if (request == null) { pendingNodes.put(req.nodeHash, req); request = req; + } else { + request.merge(req); } sentRequestIds.add(requestId); request.reqSent(requestId); @@ -503,6 +539,7 @@ private void syncUnsecure(BlockHeader pivot) { logStat(); logger.info("FastSync: downloading 256 blocks prior to pivot block (" + pivot.getShortDescr() + ")"); + FastSyncDownloader downloader = applicationContext.getBean(FastSyncDownloader.class); downloader.startImporting(pivot.getHash(), 260); downloader.waitForStop(); @@ -542,6 +579,14 @@ private void syncSecure() { headersDownloader = applicationContext.getBean(HeadersDownloader.class); headersDownloader.init(pivot.getHash()); setSyncStage(EthereumListener.SyncState.SECURE); + + if (config.fastSyncBackupState()) { + if (blockchainDB instanceof RocksDbDataSource) { + dbFlushManager.flushSync(); + ((RocksDbDataSource) blockchainDB).backup(); + } + } + headersDownloader.waitForStop(); if (!FastByteComparisons.equal(headersDownloader.getGenesisHash(), config.getGenesis().getHash())) { logger.error("FASTSYNC FATAL ERROR: after downloading header chain starting from the pivot block (" + @@ -551,29 +596,40 @@ private void syncSecure() { } dbFlushManager.commit(); dbFlushManager.flush(); + headersDownloader = null; logger.info("FastSync: all headers downloaded. The state is SECURE now."); } private void syncBlocksReceipts() { pivot = new BlockHeader(blockchainDB.get(FASTSYNC_DB_KEY_PIVOT)); - logger.info("FastSync: Downloading Block bodies up to pivot block (" + pivot.getShortDescr() + ")..."); + if (!config.fastSyncSkipHistory()) { + logger.info("FastSync: Downloading Block bodies up to pivot block (" + pivot.getShortDescr() + ")..."); - blockBodiesDownloader = applicationContext.getBean(BlockBodiesDownloader.class); - setSyncStage(EthereumListener.SyncState.COMPLETE); - blockBodiesDownloader.startImporting(); - blockBodiesDownloader.waitForStop(); + blockBodiesDownloader = applicationContext.getBean(BlockBodiesDownloader.class); + setSyncStage(EthereumListener.SyncState.COMPLETE); + blockBodiesDownloader.startImporting(); + blockBodiesDownloader.waitForStop(); + blockBodiesDownloader = null; - logger.info("FastSync: Block bodies downloaded"); + logger.info("FastSync: Block bodies downloaded"); + } else { + logger.info("FastSync: skip bodies downloading"); + } - logger.info("FastSync: Downloading receipts..."); + if (!config.fastSyncSkipHistory()) { + logger.info("FastSync: Downloading receipts..."); - receiptsDownloader = applicationContext.getBean - (ReceiptsDownloader.class, 1, pivot.getNumber() + 1); - receiptsDownloader.startImporting(); - receiptsDownloader.waitForStop(); + receiptsDownloader = applicationContext.getBean + (ReceiptsDownloader.class, 1, pivot.getNumber() + 1); + receiptsDownloader.startImporting(); + receiptsDownloader.waitForStop(); + receiptsDownloader = null; - logger.info("FastSync: receipts downloaded"); + logger.info("FastSync: receipts downloaded"); + } else { + logger.info("FastSync: skip receipts downloading"); + } logger.info("FastSync: updating totDifficulties starting from the pivot block..."); blockchain.updateBlockTotDifficulties((int) pivot.getNumber()); @@ -595,14 +651,7 @@ public void main() { // or we have incomplete headers/blocks/receipts download fastSyncInProgress = true; - pool.setNodesSelector(new Functional.Predicate() { - @Override - public boolean test(NodeHandler handler) { - if (!handler.getNodeStatistics().capabilities.contains(ETH63_CAPABILITY)) - return false; - return true; - } - }); + pool.setNodesSelector(handler -> handler.getNodeStatistics().capabilities.contains(ETH63_CAPABILITY)); try { EthereumListener.SyncState origSyncStage = getSyncStage(); diff --git a/ethereumj-core/src/main/java/org/ethereum/sync/HeadersDownloader.java b/ethereumj-core/src/main/java/org/ethereum/sync/HeadersDownloader.java index f36c2c3ccc..64d9642ab0 100644 --- a/ethereumj-core/src/main/java/org/ethereum/sync/HeadersDownloader.java +++ b/ethereumj-core/src/main/java/org/ethereum/sync/HeadersDownloader.java @@ -20,6 +20,7 @@ import org.ethereum.core.BlockHeader; import org.ethereum.core.BlockHeaderWrapper; import org.ethereum.core.BlockWrapper; +import org.ethereum.core.Blockchain; import org.ethereum.datasource.DataSourceArray; import org.ethereum.db.DbFlushManager; import org.ethereum.db.IndexedBlockStore; @@ -59,6 +60,9 @@ public class HeadersDownloader extends BlockDownloader { @Autowired DbFlushManager dbFlushManager; + @Autowired + Blockchain blockchain; + byte[] genesisHash; int headersLoaded = 0; @@ -74,8 +78,8 @@ public HeadersDownloader(BlockHeaderValidator headerValidator) { public void init(byte[] startFromBlockHash) { logger.info("HeaderDownloader init: startHash = " + Hex.toHexString(startFromBlockHash)); SyncQueueReverseImpl syncQueue = new SyncQueueReverseImpl(startFromBlockHash, true); - super.init(syncQueue, syncPool); - syncPool.init(channelManager); + super.init(syncQueue, syncPool, "HeadersDownloader"); + syncPool.init(channelManager, blockchain); } @Override @@ -89,7 +93,7 @@ protected void pushHeaders(List headers) { if (headers.get(headers.size() - 1).getNumber() == 1) { genesisHash = headers.get(headers.size() - 1).getHeader().getParentHash(); } - logger.info(headers.size() + " headers loaded: " + headers.get(0).getNumber() + " - " + headers.get(headers.size() - 1).getNumber()); + logger.info(name + ": " + headers.size() + " headers loaded: " + headers.get(0).getNumber() + " - " + headers.get(headers.size() - 1).getNumber()); for (BlockHeaderWrapper header : headers) { headerStore.set((int) header.getNumber(), header.getHeader()); headersLoaded++; @@ -112,6 +116,11 @@ protected int getBlockQueueFreeSize() { return Integer.MAX_VALUE; } + @Override + protected int getTotalHeadersToRequest() { + return getHeaderQueueLimit(); + } + public int getHeadersLoaded() { return headersLoaded; } diff --git a/ethereumj-core/src/main/java/org/ethereum/sync/ReceiptsDownloader.java b/ethereumj-core/src/main/java/org/ethereum/sync/ReceiptsDownloader.java index f321086342..54d03937c9 100644 --- a/ethereumj-core/src/main/java/org/ethereum/sync/ReceiptsDownloader.java +++ b/ethereumj-core/src/main/java/org/ethereum/sync/ReceiptsDownloader.java @@ -20,9 +20,12 @@ import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import org.apache.commons.collections4.queue.CircularFifoQueue; +import org.ethereum.config.SystemProperties; import org.ethereum.core.*; import org.ethereum.crypto.HashUtil; import org.ethereum.datasource.DataSourceArray; +import org.ethereum.db.ByteArrayWrapper; import org.ethereum.db.DbFlushManager; import org.ethereum.db.IndexedBlockStore; import org.ethereum.db.TransactionStore; @@ -38,6 +41,10 @@ import java.util.*; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +import static java.lang.Math.max; +import static java.lang.Math.min; /** * Created by Anton Nashatyrev on 27.10.2016. @@ -47,6 +54,11 @@ public class ReceiptsDownloader { private final static Logger logger = LoggerFactory.getLogger("sync"); + private static final long REQUEST_TIMEOUT = 5 * 1000; + private static final int MAX_IN_REQUEST = 100; + private static final int MIN_IN_REQUEST = 10; + private int requestLimit = 2000; + @Autowired SyncPool syncPool; @@ -63,7 +75,8 @@ public class ReceiptsDownloader { DataSourceArray headerStore; long fromBlock, toBlock; - Set completedBlocks = new HashSet<>(); + LinkedHashMap queuedBlocks = new LinkedHashMap<>(); + AtomicInteger blocksInMem = new AtomicInteger(0); long t; int cnt; @@ -71,70 +84,76 @@ public class ReceiptsDownloader { Thread retrieveThread; private CountDownLatch stopLatch = new CountDownLatch(1); + private long blockBytesLimit = 32 * 1024 * 1024; + private long estimatedBlockSize = 0; + private final CircularFifoQueue lastBlockSizes = new CircularFifoQueue<>(requestLimit); + public ReceiptsDownloader(long fromBlock, long toBlock) { this.fromBlock = fromBlock; this.toBlock = toBlock; } public void startImporting() { - retrieveThread = new Thread("FastsyncReceiptsFetchThread") { - @Override - public void run() { - retrieveLoop(); - } - }; + retrieveThread = new Thread(this::retrieveLoop, "FastsyncReceiptsFetchThread"); retrieveThread.start(); } - private List> getToDownload(int maxAskSize, int maxAsks) { - List toDownload = getToDownload(maxAskSize * maxAsks); - List> ret = new ArrayList<>(); - for (int i = 0; i < toDownload.size(); i += maxAskSize) { - ret.add(toDownload.subList(i, Math.min(toDownload.size(), i + maxAskSize))); - } - return ret; - } - - private synchronized List getToDownload(int maxSize) { + private synchronized List getHashesForRequest(int maxSize) { List ret = new ArrayList<>(); - for (long i = fromBlock; i < toBlock && maxSize > 0; i++) { - if (!completedBlocks.contains(i)) { - BlockHeader header = headerStore.get((int) i); - - // Skipping download for blocks with no transactions - if (FastByteComparisons.equal(header.getReceiptsRoot(), HashUtil.EMPTY_TRIE_HASH)) { - finalizeBlock(header.getNumber()); - continue; - } + for (; fromBlock < toBlock && maxSize > 0; fromBlock++) { + BlockHeader header = headerStore.get((int) fromBlock); - ret.add(header.getHash()); - maxSize--; + // Skipping download for blocks with no transactions + if (FastByteComparisons.equal(header.getReceiptsRoot(), HashUtil.EMPTY_TRIE_HASH)) { + finalizeBlock(); + continue; } + + ret.add(header.getHash()); + maxSize--; } return ret; } - private void processDownloaded(byte[] blockHash, List receipts) { - Block block = blockStore.getBlockByHash(blockHash); - if (block.getNumber() >= fromBlock && validate(block, receipts) && !completedBlocks.contains(block.getNumber())) { - for (int i = 0; i < receipts.size(); i++) { - TransactionReceipt receipt = receipts.get(i); - TransactionInfo txInfo = new TransactionInfo(receipt, block.getHash(), i); - txInfo.setTransaction(block.getTransactionsList().get(i)); - txStore.put(txInfo); + private synchronized void processQueue() { + Iterator it = queuedBlocks.values().iterator(); + while (it.hasNext()) { + QueuedBlock queuedBlock = it.next(); + List receipts = queuedBlock.receipts; + if (receipts != null) { + Block block = blockStore.getBlockByHash(queuedBlock.hash); + if (validate(block, receipts)) { + for (int i = 0; i < queuedBlock.receipts.size(); i++) { + TransactionReceipt receipt = receipts.get(i); + TransactionInfo txInfo = new TransactionInfo(receipt, block.getHash(), i); + txInfo.setTransaction(block.getTransactionsList().get(i)); + txStore.put(txInfo); + } + + estimateBlockSize(receipts, block.getNumber()); + + it.remove(); + blocksInMem.decrementAndGet(); + + finalizeBlock(); + } else { + queuedBlock.reset(); + } } + } + } - finalizeBlock(block.getNumber()); + private synchronized void processDownloaded(byte[] blockHash, List receipts) { + QueuedBlock block = queuedBlocks.get(new ByteArrayWrapper(blockHash)); + if (block != null) { + block.receipts = receipts; } } - private void finalizeBlock(Long blockNumber) { + private void finalizeBlock() { synchronized (this) { - completedBlocks.add(blockNumber); - - while (fromBlock < toBlock && completedBlocks.remove(fromBlock)) fromBlock++; - - if (fromBlock >= toBlock) finishDownload(); + if (fromBlock >= toBlock && queuedBlocks.isEmpty()) + finishDownload(); cnt++; if (cnt % 1000 == 0) logger.info("FastSync: downloaded receipts for " + cnt + " blocks."); @@ -149,15 +168,20 @@ private boolean validate(Block block, List receipts) { private void retrieveLoop() { List> toDownload = Collections.emptyList(); + long t = 0; while (!Thread.currentThread().isInterrupted()) { try { + if (toDownload.isEmpty()) { - toDownload = getToDownload(100, 20); + if (fillBlockQueue() > 0 || System.currentTimeMillis() - t > REQUEST_TIMEOUT) { + toDownload = getToDownload(); + t = System.currentTimeMillis(); + } } Channel idle = getAnyPeer(); - if (idle != null) { - final List list = toDownload.remove(0); + if (idle != null && !toDownload.isEmpty()) { + List list = toDownload.remove(0); ListenableFuture>> future = ((Eth63) idle.getEthHandler()).requestReceipts(list); if (future != null) { @@ -167,6 +191,7 @@ public void onSuccess(List> result) { for (int i = 0; i < result.size(); i++) { processDownloaded(list.get(i), result.get(i)); } + processQueue(); } @Override public void onFailure(Throwable t) {} @@ -174,9 +199,9 @@ public void onFailure(Throwable t) {} } } else { try { - Thread.sleep(100); + Thread.sleep(200); } catch (InterruptedException e) { - break; + Thread.currentThread().interrupt(); } } } catch (Exception e) { @@ -185,6 +210,69 @@ public void onFailure(Throwable t) {} } } + private List> getToDownload() { + List> ret = new ArrayList<>(); + + int reqSize = getRequestSize(); + synchronized (this) { + List req = new ArrayList<>(); + for (QueuedBlock b : queuedBlocks.values()) { + if (!b.hasResponse()) { + req.add(b.hash); + if (req.size() >= reqSize) { + ret.add(req); + req = new ArrayList<>(); + } + } + } + if (!req.isEmpty()) { + ret.add(req); + } + } + + logger.debug("ReceiptsDownloader: queue broke down to {} requests, {} blocks in each", ret.size(), reqSize); + return ret; + } + + private int getRequestSize() { + int reqCnt = max(syncPool.getActivePeersCount() * 3 / 4, 1); + int optimalReqSz = queuedBlocks.size() / reqCnt; + if (optimalReqSz <= MIN_IN_REQUEST) { + return MIN_IN_REQUEST; + } else if (optimalReqSz >= MAX_IN_REQUEST) { + return MAX_IN_REQUEST; + } else { + return optimalReqSz; + } + } + + private int fillBlockQueue() { + int blocksToAdd = getTargetBlocksInMem() - blocksInMem.get(); + if (blocksToAdd < MAX_IN_REQUEST) + return 0; + + List blockHashes = getHashesForRequest(blocksToAdd); + synchronized (this) { + blockHashes.forEach(hash -> queuedBlocks.put(new ByteArrayWrapper(hash), new QueuedBlock(hash))); + } + blocksInMem.addAndGet(blockHashes.size()); + + logger.debug("ReceiptsDownloader: blocks added {}, in queue {}, in memory {} (~{}mb)", + blockHashes.size(), queuedBlocks.size(), blocksInMem.get(), + blocksInMem.get() * estimatedBlockSize / 1024 / 1024); + + return blockHashes.size(); + } + + private int getTargetBlocksInMem() { + if (estimatedBlockSize == 0) { + return requestLimit; + } + + int slotsInMem = max((int) (blockBytesLimit / estimatedBlockSize), MAX_IN_REQUEST); + return min(slotsInMem, requestLimit); + } + /** * Download could block chain synchronization occupying all peers * Prevents this by leaving one peer without work @@ -214,4 +302,40 @@ public void waitForStop() { protected void finishDownload() { stop(); } + + private void estimateBlockSize(List receipts, long number) { + if (receipts.isEmpty()) + return; + + long blockSize = receipts.stream().mapToLong(TransactionReceipt::estimateMemSize).sum(); + synchronized (lastBlockSizes) { + lastBlockSizes.add(blockSize); + estimatedBlockSize = lastBlockSizes.stream().mapToLong(Long::longValue).sum() / lastBlockSizes.size(); + } + + if (number % 1000 == 0) + logger.debug("ReceiptsDownloader: estimated block size: {}", estimatedBlockSize); + } + + @Autowired + public void setSystemProperties(final SystemProperties config) { + this.blockBytesLimit = config.blockQueueSize(); + } + + private static class QueuedBlock { + byte[] hash; + List receipts; + + public QueuedBlock(byte[] hash) { + this.hash = hash; + } + + public boolean hasResponse() { + return receipts != null; + } + + public void reset() { + receipts = null; + } + } } diff --git a/ethereumj-core/src/main/java/org/ethereum/sync/SyncManager.java b/ethereumj-core/src/main/java/org/ethereum/sync/SyncManager.java index 5dbe065912..ff7c494a60 100644 --- a/ethereumj-core/src/main/java/org/ethereum/sync/SyncManager.java +++ b/ethereumj-core/src/main/java/org/ethereum/sync/SyncManager.java @@ -26,7 +26,6 @@ import org.ethereum.net.server.Channel; import org.ethereum.net.server.ChannelManager; import org.ethereum.util.ExecutorPipeline; -import org.ethereum.util.Functional; import org.ethereum.validator.BlockHeaderValidator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,7 +40,9 @@ import java.util.List; import java.util.Locale; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; import static java.lang.Math.max; import static java.util.Collections.singletonList; @@ -57,30 +58,21 @@ public class SyncManager extends BlockDownloader { private final static Logger logger = LoggerFactory.getLogger("sync"); - private final static AtomicLong blockQueueByteSize = new AtomicLong(0); - private final static int BLOCK_BYTES_ADDON = 4; - // Transaction.getSender() is quite heavy operation so we are prefetching this value on several threads // to unload the main block importing cycle private ExecutorPipeline exec1 = new ExecutorPipeline<> - (4, 1000, true, new Functional.Function() { - public BlockWrapper apply(BlockWrapper blockWrapper) { - for (Transaction tx : blockWrapper.getBlock().getTransactionsList()) { - tx.getSender(); - } - return blockWrapper; - } - }, new Functional.Consumer() { - public void accept(Throwable throwable) { - logger.error("Unexpected exception: ", throwable); + (4, 1000, true, blockWrapper -> { + for (Transaction tx : blockWrapper.getBlock().getTransactionsList()) { + tx.getSender(); } - }); + return blockWrapper; + }, throwable -> logger.error("Unexpected exception: ", throwable)); - private ExecutorPipeline exec2 = exec1.add(1, 1, new Functional.Consumer() { + private ExecutorPipeline exec2 = exec1.add(1, 1, new Consumer() { @Override public void accept(BlockWrapper blockWrapper) { - blockQueueByteSize.addAndGet(estimateBlockSize(blockWrapper)); blockQueue.add(blockWrapper); + estimateBlockSize(blockWrapper); } }); @@ -116,6 +108,8 @@ public void accept(BlockWrapper blockWrapper) { private EthereumListener.SyncState syncDoneType = EthereumListener.SyncState.COMPLETE; private ScheduledExecutorService logExecutor = Executors.newSingleThreadScheduledExecutor(); + private AtomicInteger blocksInMem = new AtomicInteger(0); + public SyncManager() { super(null); } @@ -132,15 +126,13 @@ public void init(final ChannelManager channelManager, final SyncPool pool) { if (this.channelManager == null) { // First init this.pool = pool; this.channelManager = channelManager; - logExecutor.scheduleAtFixedRate(new Runnable() { - public void run() { - try { - logger.info("Sync state: " + getSyncStatus() + - (isSyncDone() || importStart == 0 ? "" : "; Import idle time " + - longToTimePeriod(importIdleTime.get()) + " of total " + longToTimePeriod(System.currentTimeMillis() - importStart))); - } catch (Exception e) { - logger.error("Unexpected", e); - } + logExecutor.scheduleAtFixedRate(() -> { + try { + logger.info("Sync state: " + getSyncStatus() + + (isSyncDone() || importStart == 0 ? "" : "; Import idle time " + + longToTimePeriod(importIdleTime.get()) + " of total " + longToTimePeriod(System.currentTimeMillis() - importStart))); + } catch (Exception e) { + logger.error("Unexpected", e); } }, 10, 10, TimeUnit.SECONDS); } @@ -153,7 +145,7 @@ public void run() { if (pool.getChannelManager() == null) { // Never were on this stage of init logger.info("Initializing SyncManager."); - pool.init(channelManager); + pool.init(channelManager, blockchain); if (config.isFastSyncEnabled()) { fastSyncManager.init(); @@ -168,15 +160,9 @@ void initRegularSync(EthereumListener.SyncState syncDoneType) { this.syncDoneType = syncDoneType; syncQueue = new SyncQueueImpl(blockchain); - super.init(syncQueue, pool); + super.init(syncQueue, pool, "RegularSync"); - Runnable queueProducer = new Runnable(){ - - @Override - public void run() { - produceQueue(); - } - }; + Runnable queueProducer = this::produceQueue; syncQueueThread = new Thread (queueProducer, "SyncQueueThread"); syncQueueThread.start(); @@ -208,6 +194,7 @@ private SyncStatus getSyncStateImpl() { protected void pushBlocks(List blockWrappers) { if (!exec1.isShutdown()) { exec1.pushAll(blockWrappers); + blocksInMem.addAndGet(blockWrappers.size()); } } @@ -216,23 +203,29 @@ protected void pushHeaders(List headers) {} @Override protected int getBlockQueueFreeSize() { - int blockQueueSize = blockQueue.size(); - long blockByteSize = blockQueueByteSize.get(); - int availableBlockSpace = Math.max(0, getBlockQueueLimit() - blockQueueSize); - long availableBytesSpace = Math.max(0, blockBytesLimit - blockByteSize); - - int bytesSpaceInBlocks; - if (blockByteSize == 0 || blockQueueSize == 0) { - bytesSpaceInBlocks = Integer.MAX_VALUE; - } else { - bytesSpaceInBlocks = (int) Math.floor(availableBytesSpace / (blockByteSize / blockQueueSize)); + return getBlockQueueLimit(); + } + + @Override + protected int getTotalHeadersToRequest() { + if (getEstimatedBlockSize() == 0) { + // accurately exploring the net + if (syncQueue.getHeadersCount() < 2 * MAX_IN_REQUEST) { + return 2 * MAX_IN_REQUEST; + } else { + return 0; + } } - return Math.min(bytesSpaceInBlocks, availableBlockSpace); - } + int inMem = blocksInMem.get(); + int slotsLeft = Math.max(0, (int) (blockBytesLimit / getEstimatedBlockSize()) - inMem); - private long estimateBlockSize(BlockWrapper blockWrapper) { - return blockWrapper.getEncoded().length + BLOCK_BYTES_ADDON; + if (slotsLeft + inMem < MAX_IN_REQUEST) { + slotsLeft = MAX_IN_REQUEST; + } + + // adding MAX_IN_REQUEST to overcome dark zone buffer + return Math.min(slotsLeft + MAX_IN_REQUEST, getHeaderQueueLimit()); } /** @@ -250,14 +243,17 @@ private void produceQueue() { long stale = !isSyncDone() && importStart > 0 && blockQueue.isEmpty() ? System.nanoTime() : 0; wrapper = blockQueue.take(); - blockQueueByteSize.addAndGet(-estimateBlockSize(wrapper)); + + blocksInMem.decrementAndGet(); if (stale > 0) { importIdleTime.addAndGet((System.nanoTime() - stale) / 1_000_000); } if (importStart == 0) importStart = System.currentTimeMillis(); - logger.debug("BlockQueue size: {}, headers queue size: {}", blockQueue.size(), syncQueue.getHeadersCount()); + logger.debug("BlockQueue size: {}, headers queue size: {}, blocks in mem: {} (~{}mb)", + blockQueue.size(), syncQueue.getHeadersCount(), blocksInMem.get(), + blocksInMem.get() * getEstimatedBlockSize() / 1024 / 1024); long s = System.nanoTime(); long sl; @@ -335,6 +331,11 @@ public boolean validateAndAddNewBlock(Block block, byte[] nodeId) { lastKnownBlockNumber = block.getNumber(); + // skip too distant blocks + if (block.getNumber() > syncQueue.maxNum + MAX_IN_REQUEST * 2) { + return true; + } + logger.debug("Adding new block to sync queue: " + block.getShortDescr()); syncQueue.addHeaders(singletonList(new BlockHeaderWrapper(block.getHeader(), nodeId))); diff --git a/ethereumj-core/src/main/java/org/ethereum/sync/SyncPool.java b/ethereumj-core/src/main/java/org/ethereum/sync/SyncPool.java index d5bda89a16..cd3440256f 100644 --- a/ethereumj-core/src/main/java/org/ethereum/sync/SyncPool.java +++ b/ethereumj-core/src/main/java/org/ethereum/sync/SyncPool.java @@ -25,7 +25,6 @@ import org.ethereum.net.rlpx.discover.NodeManager; import org.ethereum.net.server.Channel; import org.ethereum.net.server.ChannelManager; -import org.ethereum.util.Functional; import org.ethereum.util.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,6 +38,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; import static java.lang.Math.min; import static org.ethereum.util.BIUtil.isIn20PercentRange; @@ -79,51 +79,43 @@ public class SyncPool { private ScheduledExecutorService poolLoopExecutor = Executors.newSingleThreadScheduledExecutor(); - private Functional.Predicate nodesSelector; + private Predicate nodesSelector; private ScheduledExecutorService logExecutor = Executors.newSingleThreadScheduledExecutor(); @Autowired - public SyncPool(final SystemProperties config, final Blockchain blockchain) { + public SyncPool(final SystemProperties config) { this.config = config; - this.blockchain = blockchain; } - public void init(final ChannelManager channelManager) { + public void init(final ChannelManager channelManager, final Blockchain blockchain) { if (this.channelManager != null) return; // inited already this.channelManager = channelManager; + this.blockchain = blockchain; updateLowerUsefulDifficulty(); - poolLoopExecutor.scheduleWithFixedDelay( - new Runnable() { - @Override - public void run() { - try { - heartBeat(); - updateLowerUsefulDifficulty(); - fillUp(); - prepareActive(); - cleanupActive(); - } catch (Throwable t) { - logger.error("Unhandled exception", t); - } - } - }, WORKER_TIMEOUT, WORKER_TIMEOUT, TimeUnit.SECONDS - ); - logExecutor.scheduleWithFixedDelay(new Runnable() { - @Override - public void run() { - try { - logActivePeers(); - logger.info("\n"); - } catch (Throwable t) { - t.printStackTrace(); - logger.error("Exception in log worker", t); - } + poolLoopExecutor.scheduleWithFixedDelay(() -> { + try { + heartBeat(); + updateLowerUsefulDifficulty(); + fillUp(); + prepareActive(); + cleanupActive(); + } catch (Throwable t) { + logger.error("Unhandled exception", t); + } + }, WORKER_TIMEOUT, WORKER_TIMEOUT, TimeUnit.SECONDS); + logExecutor.scheduleWithFixedDelay(() -> { + try { + logActivePeers(); + logger.info("\n"); + } catch (Throwable t) { + t.printStackTrace(); + logger.error("Exception in log worker", t); } }, 30, 30, TimeUnit.SECONDS); } - public void setNodesSelector(Functional.Predicate nodesSelector) { + public void setNodesSelector(Predicate nodesSelector) { this.nodesSelector = nodesSelector; } @@ -232,7 +224,7 @@ synchronized void logActivePeers() { } } - class NodeSelector implements Functional.Predicate { + class NodeSelector implements Predicate { BigInteger lowerDifficulty; Set nodesInUse; @@ -303,12 +295,7 @@ private synchronized void prepareActive() { if (active.isEmpty()) return; // filtering by 20% from top difficulty - Collections.sort(active, new Comparator() { - @Override - public int compare(Channel c1, Channel c2) { - return c2.getTotalDifficulty().compareTo(c1.getTotalDifficulty()); - } - }); + active.sort((c1, c2) -> c2.getTotalDifficulty().compareTo(c1.getTotalDifficulty())); BigInteger highestDifficulty = active.get(0).getTotalDifficulty(); int thresholdIdx = min(config.syncPeerCount(), active.size()) - 1; @@ -323,12 +310,7 @@ public int compare(Channel c1, Channel c2) { List filtered = active.subList(0, thresholdIdx + 1); // sorting by latency in asc order - Collections.sort(filtered, new Comparator() { - @Override - public int compare(Channel c1, Channel c2) { - return Double.valueOf(c1.getPeerStats().getAvgLatency()).compareTo(c2.getPeerStats().getAvgLatency()); - } - }); + filtered.sort(Comparator.comparingDouble(c -> c.getPeerStats().getAvgLatency())); for (Channel channel : filtered) { if (!activePeers.contains(channel)) { diff --git a/ethereumj-core/src/main/java/org/ethereum/sync/SyncQueueImpl.java b/ethereumj-core/src/main/java/org/ethereum/sync/SyncQueueImpl.java index 4915520f93..fe1c244cf2 100644 --- a/ethereumj-core/src/main/java/org/ethereum/sync/SyncQueueImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/sync/SyncQueueImpl.java @@ -23,18 +23,19 @@ import org.ethereum.core.Blockchain; import org.ethereum.db.ByteArrayWrapper; import org.ethereum.util.ByteArrayMap; -import org.ethereum.util.Functional; import org.spongycastle.util.encoders.Hex; import java.util.*; +import java.util.function.Function; import static java.lang.Math.min; +import static org.ethereum.sync.BlockDownloader.MAX_IN_REQUEST; /** * Created by Anton Nashatyrev on 27.05.2016. */ public class SyncQueueImpl implements SyncQueueIfc { - static int MAX_CHAIN_LEN = 192; + static int MAX_CHAIN_LEN = MAX_IN_REQUEST; static class HeadersRequestImpl implements HeadersRequest { public HeadersRequestImpl(long start, int count, boolean reverse) { @@ -188,7 +189,7 @@ public SyncQueueImpl(List initBlocks) { public SyncQueueImpl(Blockchain bc) { Block bestBlock = bc.getBestBlock(); - long start = bestBlock.getNumber() - MAX_CHAIN_LEN; + long start = bestBlock.getNumber() - MAX_CHAIN_LEN + 1; start = start < 0 ? 0 : start; List initBlocks = new ArrayList<>(); for (long i = start; i <= bestBlock.getNumber(); i++) { @@ -446,7 +447,7 @@ class ChildVisitor { private Visitor handler; boolean downUp = true; - public ChildVisitor(Functional.Function> handler) { + public ChildVisitor(Function> handler) { // this.handler = handler; } 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 1050b852c1..8720622073 100644 --- a/ethereumj-core/src/main/java/org/ethereum/trie/TrieImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/trie/TrieImpl.java @@ -27,6 +27,8 @@ import org.ethereum.util.FastByteComparisons; import org.ethereum.util.RLP; import org.ethereum.util.Value; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.spongycastle.util.encoders.Hex; import java.util.ArrayList; @@ -48,6 +50,8 @@ public class TrieImpl implements Trie { private final static int MIN_BRANCHES_CONCURRENTLY = 3; private static ExecutorService executor; + private static final Logger logger = LoggerFactory.getLogger("state"); + public static ExecutorService getExecutor() { if (executor == null) { executor = Executors.newFixedThreadPool(4, @@ -108,6 +112,7 @@ public boolean resolveCheck() { private void resolve() { if (!resolveCheck()) { + logger.error("Invalid Trie state, can't resolve hash " + Hex.toHexString(hash)); throw new RuntimeException("Invalid Trie state, can't resolve hash " + Hex.toHexString(hash)); } } @@ -141,12 +146,7 @@ private byte[] encode(final int depth, boolean forceHash) { if (encoded[i] == null) { final Node child = branchNodeGetChild(i); if (encodeCnt >= MIN_BRANCHES_CONCURRENTLY) { - encoded[i] = getExecutor().submit(new Callable() { - @Override - public byte[] call() throws Exception { - return child.encode(depth + 1, false); - } - }); + encoded[i] = getExecutor().submit(() -> child.encode(depth + 1, false)); } else { encoded[i] = child.encode(depth + 1, false); } @@ -639,6 +639,7 @@ private Node delete(Node n, TrieKey k) { TrieKey newKey = newKvNode.kvNodeGetKey().concat(newChild.kvNodeGetKey()); Node newNode = new Node(newKey, newChild.kvNodeGetValueOrNode()); newChild.dispose(); + newKvNode.dispose(); return newNode; } else { // no compaction needed diff --git a/ethereumj-core/src/main/java/org/ethereum/trie/TrieKey.java b/ethereumj-core/src/main/java/org/ethereum/trie/TrieKey.java index 309c551297..91a0ae7864 100644 --- a/ethereumj-core/src/main/java/org/ethereum/trie/TrieKey.java +++ b/ethereumj-core/src/main/java/org/ethereum/trie/TrieKey.java @@ -92,9 +92,9 @@ public TrieKey shift(int hexCnt) { public TrieKey getCommonPrefix(TrieKey k) { // TODO can be optimized int prefixLen = 0; - int thisLenght = getLength(); + int thisLength = getLength(); int kLength = k.getLength(); - while (prefixLen < thisLenght && prefixLen < kLength && getHex(prefixLen) == k.getHex(prefixLen)) + while (prefixLen < thisLength && prefixLen < kLength && getHex(prefixLen) == k.getHex(prefixLen)) prefixLen++; byte[] prefixKey = new byte[(prefixLen + 1) >> 1]; TrieKey ret = new TrieKey(prefixKey, (prefixLen & 1) == 0 ? 0 : 1, diff --git a/ethereumj-core/src/main/java/org/ethereum/util/ByteArraySet.java b/ethereumj-core/src/main/java/org/ethereum/util/ByteArraySet.java index 6b79ab2b3c..f6bcaddf85 100644 --- a/ethereumj-core/src/main/java/org/ethereum/util/ByteArraySet.java +++ b/ethereumj-core/src/main/java/org/ethereum/util/ByteArraySet.java @@ -23,6 +23,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; /** * Created by Anton Nashatyrev on 06.10.2016. @@ -122,7 +123,11 @@ public boolean retainAll(Collection c) { @Override public boolean removeAll(Collection c) { - throw new RuntimeException("Not implemented"); + boolean changed = false; + for (Object el : c) { + changed |= remove(el); + } + return changed; } @Override 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 9de75f90bf..16386bfc57 100644 --- a/ethereumj-core/src/main/java/org/ethereum/util/ByteUtil.java +++ b/ethereumj-core/src/main/java/org/ethereum/util/ByteUtil.java @@ -103,8 +103,15 @@ public static byte[] bigIntegerToBytes(BigInteger value) { return data; } + /** + * Cast hex encoded value from byte[] to BigInteger + * null is parsed like byte[0] + * + * @param bb byte array contains the values + * @return unsigned positive BigInteger value. + */ public static BigInteger bytesToBigInteger(byte[] bb) { - return bb.length == 0 ? BigInteger.ZERO : new BigInteger(1, bb); + return (bb == null || bb.length == 0) ? BigInteger.ZERO : new BigInteger(1, bb); } /** @@ -133,7 +140,7 @@ public static int matchingNibbleLength(byte[] a, byte[] b) { * @return byte[] of length 8, representing the long value */ public static byte[] longToBytes(long val) { - return ByteBuffer.allocate(8).putLong(val).array(); + return ByteBuffer.allocate(Long.BYTES).putLong(val).array(); } /** @@ -147,7 +154,7 @@ public static byte[] longToBytesNoLeadZeroes(long val) { // todo: improve performance by while strip numbers until (long >> 8 == 0) if (val == 0) return EMPTY_BYTE_ARRAY; - byte[] data = ByteBuffer.allocate(8).putLong(val).array(); + byte[] data = ByteBuffer.allocate(Long.BYTES).putLong(val).array(); return stripLeadingZeroes(data); } @@ -159,7 +166,7 @@ public static byte[] longToBytesNoLeadZeroes(long val) { * @return byte[] of length 4, representing the int value */ public static byte[] intToBytes(int val){ - return ByteBuffer.allocate(4).putInt(val).array(); + return ByteBuffer.allocate(Integer.BYTES).putInt(val).array(); } /** @@ -226,6 +233,7 @@ public static byte[] calcPacketLength(byte[] msg) { /** * Cast hex encoded value from byte[] to int + * null is parsed like byte[0] * * Limited to Integer.MAX_VALUE: 2^32-1 (4 bytes) * @@ -239,9 +247,10 @@ public static int byteArrayToInt(byte[] b) { } /** - * Cast hex encoded value from byte[] to int + * Cast hex encoded value from byte[] to long + * null is parsed like byte[0] * - * Limited to Integer.MAX_VALUE: 2^32-1 (4 bytes) + * Limited to Long.MAX_VALUE: 263-1 (8 bytes) * * @param b array contains the values * @return unsigned positive long value. @@ -495,11 +504,9 @@ public static byte[] xorAlignRight(byte[] b1, byte[] b2) { */ public static byte[] merge(byte[]... arrays) { - int arrCount = 0; int count = 0; for (byte[] array: arrays) { - arrCount++; count += array.length; } diff --git a/ethereumj-core/src/main/java/org/ethereum/util/CollectionUtils.java b/ethereumj-core/src/main/java/org/ethereum/util/CollectionUtils.java index 597fe2431f..4d30b520bd 100644 --- a/ethereumj-core/src/main/java/org/ethereum/util/CollectionUtils.java +++ b/ethereumj-core/src/main/java/org/ethereum/util/CollectionUtils.java @@ -18,6 +18,8 @@ package org.ethereum.util; import java.util.*; +import java.util.function.Function; +import java.util.function.Predicate; /** * @author Mikhail Kalinin @@ -25,7 +27,7 @@ */ public class CollectionUtils { - public static List collectList(Collection items, Functional.Function collector) { + public static List collectList(Collection items, Function collector) { List collected = new ArrayList<>(items.size()); for(K item : items) { collected.add(collector.apply(item)); @@ -33,7 +35,7 @@ public static List collectList(Collection items, Functional.Functio return collected; } - public static Set collectSet(Collection items, Functional.Function collector) { + public static Set collectSet(Collection items, Function collector) { Set collected = new HashSet<>(); for(K item : items) { collected.add(collector.apply(item)); @@ -55,7 +57,7 @@ public static List truncate(List items, int limit) { return truncated; } - public static List selectList(Collection items, Functional.Predicate predicate) { + public static List selectList(Collection items, Predicate predicate) { List selected = new ArrayList<>(); for(T item : items) { if(predicate.test(item)) { @@ -65,7 +67,7 @@ public static List selectList(Collection items, Functional.Predicate Set selectSet(Collection items, Functional.Predicate predicate) { + public static Set selectSet(Collection items, Predicate predicate) { Set selected = new HashSet<>(); for(T item : items) { if(predicate.test(item)) { diff --git a/ethereumj-core/src/main/java/org/ethereum/util/ExecutorPipeline.java b/ethereumj-core/src/main/java/org/ethereum/util/ExecutorPipeline.java index 3c0ca5de14..6a286136f0 100644 --- a/ethereumj-core/src/main/java/org/ethereum/util/ExecutorPipeline.java +++ b/ethereumj-core/src/main/java/org/ethereum/util/ExecutorPipeline.java @@ -17,7 +17,6 @@ */ package org.ethereum.util; -import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -25,6 +24,8 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; +import java.util.function.Function; /** * Queues execution tasks into a single pipeline where some tasks can be executed in parallel @@ -38,8 +39,8 @@ public class ExecutorPipeline { private BlockingQueue queue; private ThreadPoolExecutor exec; private boolean preserveOrder = false; - private Functional.Function processor; - private Functional.Consumer exceptionHandler; + private Function processor; + private Consumer exceptionHandler; private ExecutorPipeline next; private AtomicLong orderCounter = new AtomicLong(); @@ -51,33 +52,27 @@ public class ExecutorPipeline { private static AtomicInteger pipeNumber = new AtomicInteger(1); private AtomicInteger threadNumber = new AtomicInteger(1); - public ExecutorPipeline(int threads, int queueSize, boolean preserveOrder, Functional.Function processor, - Functional.Consumer exceptionHandler) { + public ExecutorPipeline(int threads, int queueSize, boolean preserveOrder, Function processor, + Consumer exceptionHandler) { queue = new LimitedQueue<>(queueSize); - exec = new ThreadPoolExecutor(threads, threads, 0L, TimeUnit.MILLISECONDS, queue, new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - return new Thread(r, threadPoolName + "-" + threadNumber.getAndIncrement()); - } - }); + exec = new ThreadPoolExecutor(threads, threads, 0L, TimeUnit.MILLISECONDS, queue, r -> + new Thread(r, threadPoolName + "-" + threadNumber.getAndIncrement()) + ); this.preserveOrder = preserveOrder; this.processor = processor; this.exceptionHandler = exceptionHandler; this.threadPoolName = "pipe-" + pipeNumber.getAndIncrement(); } - public ExecutorPipeline add(int threads, int queueSize, final Functional.Consumer consumer) { - return add(threads, queueSize, false, new Functional.Function() { - @Override - public Void apply(Out out) { - consumer.accept(out); - return null; - } + public ExecutorPipeline add(int threads, int queueSize, final Consumer consumer) { + return add(threads, queueSize, false, out -> { + consumer.accept(out); + return null; }); } public ExecutorPipeline add(int threads, int queueSize, boolean preserveOrder, - Functional.Function processor) { + Function processor) { ExecutorPipeline ret = new ExecutorPipeline<>(threads, queueSize, preserveOrder, processor, exceptionHandler); next = ret; return ret; @@ -110,14 +105,11 @@ private void pushNext(long order, Out res) { public void push(final In in) { final long order = orderCounter.getAndIncrement(); - exec.execute(new Runnable() { - @Override - public void run() { - try { - pushNext(order, processor.apply(in)); - } catch (Throwable e) { - exceptionHandler.accept(e); - } + exec.execute(() -> { + try { + pushNext(order, processor.apply(in)); + } catch (Throwable e) { + exceptionHandler.accept(e); } }); } diff --git a/ethereumj-core/src/main/java/org/ethereum/util/Functional.java b/ethereumj-core/src/main/java/org/ethereum/util/Functional.java deleted file mode 100644 index c893fd5098..0000000000 --- a/ethereumj-core/src/main/java/org/ethereum/util/Functional.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) [2016] [ ] - * This file is part of the ethereumJ library. - * - * The ethereumJ library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The ethereumJ library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the ethereumJ library. If not, see . - */ -package org.ethereum.util; - - -public interface Functional { - - /** - * Represents an operation that accepts a single input argument and returns no - * result. Unlike most other functional interfaces, {@code Consumer} is expected - * to operate via side-effects. - * - * @param the type of the input to the operation - */ - interface Consumer { - - /** - * Performs this operation on the given argument. - * - * @param t the input argument - */ - void accept(T t); - } - - /** - * Represents an operation that accepts two input arguments and returns no - * result. This is the two-arity specialization of {@link java.util.function.Consumer}. - * Unlike most other functional interfaces, {@code BiConsumer} is expected - * to operate via side-effects. - * - * @param the type of the first argument to the operation - * @param the type of the second argument to the operation - * - * @see org.ethereum.util.Functional.Consumer - */ - interface BiConsumer { - - /** - * Performs this operation on the given arguments. - * - * @param t the first input argument - * @param u the second input argument - */ - void accept(T t, U u); - } - - - /** - * Represents a function that accepts one argument and produces a result. - * - * @param the type of the input to the function - * @param the type of the result of the function - */ - interface Function { - - /** - * Applies this function to the given argument. - * - * @param t the function argument - * @return the function result - */ - R apply(T t); - } - - interface Supplier { - - /** - * Gets a result. - * - * @return a result - */ - T get(); - } - - interface InvokeWrapper { - - void invoke(); - } - - interface InvokeWrapperWithResult { - - R invoke(); - } - - interface Predicate { - boolean test(T t); - } - -} 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 7f4c1f28c2..859530b85f 100644 --- a/ethereumj-core/src/main/java/org/ethereum/util/RLP.java +++ b/ethereumj-core/src/main/java/org/ethereum/util/RLP.java @@ -23,7 +23,6 @@ import org.spongycastle.util.encoders.Hex; import java.math.BigInteger; -import java.nio.ByteBuffer; import java.util.*; import static java.util.Arrays.copyOfRange; @@ -151,125 +150,152 @@ private static byte decodeOneByteItem(byte[] data, int index) { } public static int decodeInt(byte[] data, int index) { + int value = 0; - // NOTE: there are two ways zero can be encoded - 0x00 and OFFSET_SHORT_ITEM + // NOTE: From RLP doc: + // Ethereum integers must be represented in big endian binary form + // with no leading zeroes (thus making the integer value zero be + // equivalent to the empty byte array) + + if (data[index] == 0x00) { + throw new RuntimeException("not a number"); + } else if ((data[index] & 0xFF) < OFFSET_SHORT_ITEM) { - if ((data[index] & 0xFF) < OFFSET_SHORT_ITEM) { return data[index]; - } else if ((data[index] & 0xFF) >= OFFSET_SHORT_ITEM - && (data[index] & 0xFF) < OFFSET_LONG_ITEM) { + + } else if ((data[index] & 0xFF) <= OFFSET_SHORT_ITEM + Integer.BYTES) { byte length = (byte) (data[index] - OFFSET_SHORT_ITEM); byte pow = (byte) (length - 1); for (int i = 1; i <= length; ++i) { + // << (8 * pow) == bit shift to 0 (*1), 8 (*256) , 16 (*65..).. value += (data[index + i] & 0xFF) << (8 * pow); pow--; } } else { + + // If there are more than 4 bytes, it is not going + // to decode properly into an int. throw new RuntimeException("wrong decode attempt"); } return value; } - private static short decodeShort(byte[] data, int index) { - if ((data[index] & 0xFF) > OFFSET_SHORT_ITEM - && (data[index] & 0xFF) < OFFSET_LONG_ITEM) { - byte length = (byte) (data[index] - OFFSET_SHORT_ITEM); - return ByteBuffer.wrap(data, index, length).getShort(); - } else { - return data[index]; - } - } + static short decodeShort(byte[] data, int index) { - private static long decodeLong(byte[] data, int index) { + short value = 0; - long value = 0; + if (data[index] == 0x00) { + throw new RuntimeException("not a number"); + } else if ((data[index] & 0xFF) < OFFSET_SHORT_ITEM) { - if ((data[index] & 0xFF) > OFFSET_SHORT_ITEM - && (data[index] & 0xFF) < OFFSET_LONG_ITEM) { + return data[index]; + + } else if ((data[index] & 0xFF) <= OFFSET_SHORT_ITEM + Short.BYTES) { byte length = (byte) (data[index] - OFFSET_SHORT_ITEM); byte pow = (byte) (length - 1); for (int i = 1; i <= length; ++i) { + // << (8 * pow) == bit shift to 0 (*1), 8 (*256) , 16 (*65..) value += (data[index + i] & 0xFF) << (8 * pow); pow--; } } else { + + // If there are more than 2 bytes, it is not going + // to decode properly into a short. throw new RuntimeException("wrong decode attempt"); } return value; } - private static String decodeStringItem(byte[] data, int index) { + public static long decodeLong(byte[] data, int index) { - if ((data[index] & 0xFF) >= OFFSET_LONG_ITEM - && (data[index] & 0xFF) < OFFSET_SHORT_LIST) { + long value = 0; - byte lengthOfLength = (byte) (data[index] - OFFSET_LONG_ITEM); - int length = calcLength(lengthOfLength, data, index); - return new String(data, index + lengthOfLength + 1, length); + if (data[index] == 0x00) { + throw new RuntimeException("not a number"); + } else if ((data[index] & 0xFF) < OFFSET_SHORT_ITEM) { - } else if ((data[index] & 0xFF) > OFFSET_SHORT_ITEM - && (data[index] & 0xFF) < OFFSET_LONG_ITEM) { + return data[index]; - byte length = (byte) ((data[index] & 0xFF) - OFFSET_SHORT_ITEM); - return new String(data, index + 1, length); + } else if ((data[index] & 0xFF) <= OFFSET_SHORT_ITEM + Long.BYTES) { + byte length = (byte) (data[index] - OFFSET_SHORT_ITEM); + byte pow = (byte) (length - 1); + for (int i = 1; i <= length; ++i) { + // << (8 * pow) == bit shift to 0 (*1), 8 (*256) , 16 (*65..).. + value += (long) (data[index + i] & 0xFF) << (8 * pow); + pow--; + } } else { + + // If there are more than 8 bytes, it is not going + // to decode properly into a long. throw new RuntimeException("wrong decode attempt"); } + return value; } - private static byte[] decodeItemBytes(byte[] data, int index) { + private static String decodeStringItem(byte[] data, int index) { + + final byte[] valueBytes = decodeItemBytes(data, index); - final int length = calculateLength(data, index); - byte[] valueBytes = new byte[length]; - System.arraycopy(data, index, valueBytes, 0, length); - return valueBytes; + if (valueBytes.length == 0) { + // shortcut + return ""; + } else { + return new String(valueBytes); + } } public static BigInteger decodeBigInteger(byte[] data, int index) { - final int length = calculateLength(data, index); - byte[] valueBytes = new byte[length]; - System.arraycopy(data, index, valueBytes, 0, length); - return new BigInteger(1, valueBytes); + final byte[] valueBytes = decodeItemBytes(data, index); + + if (valueBytes.length == 0) { + // shortcut + return BigInteger.ZERO; + } else { + BigInteger res = new BigInteger(1, valueBytes); + return res; + } } private static byte[] decodeByteArray(byte[] data, int index) { - final int length = calculateLength(data, index); - byte[] valueBytes = new byte[length]; - System.arraycopy(data, index, valueBytes, 0, length); - return valueBytes; + return decodeItemBytes(data, index); } private static int nextItemLength(byte[] data, int index) { if (index >= data.length) return -1; - - if ((data[index] & 0xFF) >= OFFSET_LONG_LIST) { + // [0xf8, 0xff] + if ((data[index] & 0xFF) > OFFSET_LONG_LIST) { byte lengthOfLength = (byte) (data[index] - OFFSET_LONG_LIST); return calcLength(lengthOfLength, data, index); } + // [0xc0, 0xf7] if ((data[index] & 0xFF) >= OFFSET_SHORT_LIST - && (data[index] & 0xFF) < OFFSET_LONG_LIST) { + && (data[index] & 0xFF) <= OFFSET_LONG_LIST) { return (byte) ((data[index] & 0xFF) - OFFSET_SHORT_LIST); } + // [0xb8, 0xbf] if ((data[index] & 0xFF) > OFFSET_LONG_ITEM && (data[index] & 0xFF) < OFFSET_SHORT_LIST) { byte lengthOfLength = (byte) (data[index] - OFFSET_LONG_ITEM); return calcLength(lengthOfLength, data, index); } + // [0x81, 0xb7] if ((data[index] & 0xFF) > OFFSET_SHORT_ITEM && (data[index] & 0xFF) <= OFFSET_LONG_ITEM) { return (byte) ((data[index] & 0xFF) - OFFSET_SHORT_ITEM); } - + // [0x00, 0x80] if ((data[index] & 0xFF) <= OFFSET_SHORT_ITEM) { return 1; } @@ -298,15 +324,18 @@ public static int getFirstListElement(byte[] payload, int pos) { if (pos >= payload.length) return -1; - if ((payload[pos] & 0xFF) >= OFFSET_LONG_LIST) { + // [0xf8, 0xff] + if ((payload[pos] & 0xFF) > OFFSET_LONG_LIST) { byte lengthOfLength = (byte) (payload[pos] - OFFSET_LONG_LIST); return pos + lengthOfLength + 1; } + // [0xc0, 0xf7] if ((payload[pos] & 0xFF) >= OFFSET_SHORT_LIST - && (payload[pos] & 0xFF) < OFFSET_LONG_LIST) { + && (payload[pos] & 0xFF) <= OFFSET_LONG_LIST) { return pos + 1; } - if ((payload[pos] & 0xFF) >= OFFSET_LONG_ITEM + // [0xb8, 0xbf] + if ((payload[pos] & 0xFF) > OFFSET_LONG_ITEM && (payload[pos] & 0xFF) < OFFSET_SHORT_LIST) { byte lengthOfLength = (byte) (payload[pos] - OFFSET_LONG_ITEM); return pos + lengthOfLength + 1; @@ -319,33 +348,39 @@ public static int getNextElementIndex(byte[] payload, int pos) { if (pos >= payload.length) return -1; - if ((payload[pos] & 0xFF) >= OFFSET_LONG_LIST) { + // [0xf8, 0xff] + if ((payload[pos] & 0xFF) > OFFSET_LONG_LIST) { byte lengthOfLength = (byte) (payload[pos] - OFFSET_LONG_LIST); int length = calcLength(lengthOfLength, payload, pos); return pos + lengthOfLength + length + 1; } + // [0xc0, 0xf7] if ((payload[pos] & 0xFF) >= OFFSET_SHORT_LIST - && (payload[pos] & 0xFF) < OFFSET_LONG_LIST) { + && (payload[pos] & 0xFF) <= OFFSET_LONG_LIST) { byte length = (byte) ((payload[pos] & 0xFF) - OFFSET_SHORT_LIST); return pos + 1 + length; } - if ((payload[pos] & 0xFF) >= OFFSET_LONG_ITEM + // [0xb8, 0xbf] + if ((payload[pos] & 0xFF) > OFFSET_LONG_ITEM && (payload[pos] & 0xFF) < OFFSET_SHORT_LIST) { byte lengthOfLength = (byte) (payload[pos] - OFFSET_LONG_ITEM); int length = calcLength(lengthOfLength, payload, pos); return pos + lengthOfLength + length + 1; } + // [0x81, 0xb7] if ((payload[pos] & 0xFF) > OFFSET_SHORT_ITEM - && (payload[pos] & 0xFF) < OFFSET_LONG_ITEM) { + && (payload[pos] & 0xFF) <= OFFSET_LONG_ITEM) { byte length = (byte) ((payload[pos] & 0xFF) - OFFSET_SHORT_ITEM); return pos + 1 + length; } + // []0x80] if ((payload[pos] & 0xFF) == OFFSET_SHORT_ITEM) { return pos + 1; } + // [0x00, 0x7f] if ((payload[pos] & 0xFF) < OFFSET_SHORT_ITEM) { return pos + 1; } @@ -372,7 +407,8 @@ public static void fullTraverse(byte[] msgData, int level, int startPos, // It's a list with a payload more than 55 bytes // data[0] - 0xF7 = how many next bytes allocated // for the length of the list - if ((msgData[pos] & 0xFF) >= OFFSET_LONG_LIST) { + // [0xf8, 0xff] + if ((msgData[pos] & 0xFF) > OFFSET_LONG_LIST) { byte lengthOfLength = (byte) (msgData[pos] - OFFSET_LONG_LIST); int length = calcLength(lengthOfLength, msgData, pos); @@ -387,9 +423,10 @@ public static void fullTraverse(byte[] msgData, int level, int startPos, pos += lengthOfLength + length + 1; continue; } - // It's a list with a payload less than 55 bytes + // It's a list with a payload less than or equal to 55 bytes + // [0xc0, 0xf7] if ((msgData[pos] & 0xFF) >= OFFSET_SHORT_LIST - && (msgData[pos] & 0xFF) < OFFSET_LONG_LIST) { + && (msgData[pos] & 0xFF) <= OFFSET_LONG_LIST) { byte length = (byte) ((msgData[pos] & 0xFF) - OFFSET_SHORT_LIST); @@ -405,7 +442,8 @@ public static void fullTraverse(byte[] msgData, int level, int startPos, // It's an item with a payload more than 55 bytes // data[0] - 0xB7 = how much next bytes allocated for // the length of the string - if ((msgData[pos] & 0xFF) >= OFFSET_LONG_ITEM + // [0xb8, 0xbf] + if ((msgData[pos] & 0xFF) > OFFSET_LONG_ITEM && (msgData[pos] & 0xFF) < OFFSET_SHORT_LIST) { byte lengthOfLength = (byte) (msgData[pos] - OFFSET_LONG_ITEM); @@ -420,8 +458,9 @@ public static void fullTraverse(byte[] msgData, int level, int startPos, } // It's an item less than 55 bytes long, // data[0] - 0x80 == length of the item + // [0x81, 0xb7] if ((msgData[pos] & 0xFF) > OFFSET_SHORT_ITEM - && (msgData[pos] & 0xFF) < OFFSET_LONG_ITEM) { + && (msgData[pos] & 0xFF) <= OFFSET_LONG_ITEM) { byte length = (byte) ((msgData[pos] & 0xFF) - OFFSET_SHORT_ITEM); @@ -431,6 +470,7 @@ public static void fullTraverse(byte[] msgData, int level, int startPos, continue; } // null item + // [0x80] if ((msgData[pos] & 0xFF) == OFFSET_SHORT_ITEM) { System.out.println("-- level: [" + level + "] Found null item: "); @@ -438,6 +478,7 @@ public static void fullTraverse(byte[] msgData, int level, int startPos, continue; } // single byte item + // [0x00, 0x7f] if ((msgData[pos] & 0xFF) < OFFSET_SHORT_ITEM) { System.out.println("-- level: [" + level + "] Found single item: "); @@ -602,10 +643,6 @@ private static void fullTraverse(byte[] msgData, int level, int startPos, System.arraycopy(msgData, pos + lengthOfLength + 1, item, 0, length); - byte[] rlpPrefix = new byte[lengthOfLength + 1]; - System.arraycopy(msgData, pos, rlpPrefix, 0, - lengthOfLength + 1); - RLPItem rlpItem = new RLPItem(item); rlpList.add(rlpItem); pos += lengthOfLength + length + 1; @@ -626,9 +663,6 @@ private static void fullTraverse(byte[] msgData, int level, int startPos, throw new RuntimeException("Single byte has been encoded as byte string"); } - byte[] rlpPrefix = new byte[2]; - System.arraycopy(msgData, pos, rlpPrefix, 0, 2); - RLPItem rlpItem = new RLPItem(item); rlpList.add(rlpItem); pos += 1 + length; @@ -870,6 +904,7 @@ public static byte[] encodeByte(byte singleByte) { } public static byte[] encodeShort(short singleShort) { + if ((singleShort & 0xFF) == singleShort) return encodeByte((byte) singleShort); else { @@ -880,6 +915,7 @@ public static byte[] encodeShort(short singleShort) { } public static byte[] encodeInt(int singleInt) { + if ((singleInt & 0xFF) == singleInt) return encodeByte((byte) singleInt); else if ((singleInt & 0xFFFF) == singleInt) @@ -903,6 +939,8 @@ public static byte[] encodeString(String srcString) { } public static byte[] encodeBigInteger(BigInteger srcBigInteger) { + if (srcBigInteger.compareTo(BigInteger.ZERO) < 0) throw new RuntimeException("negative numbers are not allowed"); + if (srcBigInteger.equals(BigInteger.ZERO)) return encodeByte((byte) 0); else @@ -911,12 +949,19 @@ public static byte[] encodeBigInteger(BigInteger srcBigInteger) { public static byte[] encodeElement(byte[] srcData) { - if (isNullOrZeroArray(srcData)) + // [0x80] + if (isNullOrZeroArray(srcData)) { return new byte[]{(byte) OFFSET_SHORT_ITEM}; - else if (isSingleZero(srcData)) - return srcData; - else if (srcData.length == 1 && (srcData[0] & 0xFF) < 0x80) { + + // [0x00] + } else if (isSingleZero(srcData)) { + return srcData; + + // [0x01, 0x7f] - single byte, that byte is its own RLP encoding + } else if (srcData.length == 1 && (srcData[0] & 0xFF) < 0x80) { return srcData; + + // [0x80, 0xb7], 0 - 55 bytes } else if (srcData.length < SIZE_THRESHOLD) { // length = 8X byte length = (byte) (OFFSET_SHORT_ITEM + srcData.length); @@ -925,24 +970,30 @@ else if (srcData.length == 1 && (srcData[0] & 0xFF) < 0x80) { data[0] = length; return data; + // [0xb8, 0xbf], 56+ bytes } else { // length of length = BX // prefix = [BX, [length]] int tmpLength = srcData.length; - byte byteNum = 0; + byte lengthOfLength = 0; while (tmpLength != 0) { - ++byteNum; + ++lengthOfLength; tmpLength = tmpLength >> 8; } - byte[] lenBytes = new byte[byteNum]; - for (int i = 0; i < byteNum; ++i) { - lenBytes[byteNum - 1 - i] = (byte) ((srcData.length >> (8 * i)) & 0xFF); + + // set length Of length at first byte + byte[] data = new byte[1 + lengthOfLength + srcData.length]; + data[0] = (byte) (OFFSET_LONG_ITEM + lengthOfLength); + + // copy length after first byte + tmpLength = srcData.length; + for (int i = lengthOfLength; i > 0; --i) { + data[i] = (byte) (tmpLength & 0xFF); + tmpLength = tmpLength >> 8; } - // first byte = F7 + bytes.length - byte[] data = Arrays.copyOf(srcData, srcData.length + 1 + byteNum); - System.arraycopy(data, 0, data, 1 + byteNum, srcData.length); - data[0] = (byte) (OFFSET_LONG_ITEM + byteNum); - System.arraycopy(lenBytes, 0, data, 1, lenBytes.length); + + // at last copy the number bytes after its length + System.arraycopy(srcData, 0, data, 1 + lengthOfLength, srcData.length); return data; } @@ -1144,22 +1195,69 @@ private static byte[] toBytes(Object input) { } - private static int calculateLength(byte[] data, int index) { - if ((data[index] & 0xFF) >= OFFSET_LONG_ITEM + private static byte[] decodeItemBytes(byte[] data, int index) { + + final int length = calculateItemLength(data, index); + // [0x80] + if (length == 0) { + + return new byte[0]; + + // [0x00, 0x7f] - single byte with item + } else if ((data[index] & 0xFF) < OFFSET_SHORT_ITEM) { + + byte[] valueBytes = new byte[1]; + System.arraycopy(data, index, valueBytes, 0, 1); + return valueBytes; + + // [0x01, 0xb7] - 1-55 bytes item + } else if ((data[index] & 0xFF) <= OFFSET_LONG_ITEM) { + + byte[] valueBytes = new byte[length]; + System.arraycopy(data, index+1, valueBytes, 0, length); + return valueBytes; + + // [0xb8, 0xbf] - 56+ bytes item + } else if ((data[index] & 0xFF) > OFFSET_LONG_ITEM + && (data[index] & 0xFF) < OFFSET_SHORT_LIST) { + + byte lengthOfLength = (byte) (data[index] - OFFSET_LONG_ITEM); + byte[] valueBytes = new byte[length]; + System.arraycopy(data, index + 1 + lengthOfLength, valueBytes, 0, length); + return valueBytes; + } else { + throw new RuntimeException("wrong decode attempt"); + } + } + + + private static int calculateItemLength(byte[] data, int index) { + + // [0xb8, 0xbf] - 56+ bytes item + if ((data[index] & 0xFF) > OFFSET_LONG_ITEM && (data[index] & 0xFF) < OFFSET_SHORT_LIST) { byte lengthOfLength = (byte) (data[index] - OFFSET_LONG_ITEM); return calcLength(lengthOfLength, data, index); + // [0x81, 0xb7] - 0-55 bytes item } else if ((data[index] & 0xFF) > OFFSET_SHORT_ITEM - && (data[index] & 0xFF) < OFFSET_LONG_ITEM) { + && (data[index] & 0xFF) <= OFFSET_LONG_ITEM) { return (byte) (data[index] - OFFSET_SHORT_ITEM); + // [0x80] - item = 0 itself + } else if ((data[index] & 0xFF) == OFFSET_SHORT_ITEM) { + + return (byte) 0; + + // [0x00, 0x7f] - 1 byte item, no separate length representation + } else if ((data[index] & 0xFF) < OFFSET_SHORT_ITEM) { + + return (byte) 1; + } else { throw new RuntimeException("wrong decode attempt"); } } - - } diff --git a/ethereumj-core/src/main/java/org/ethereum/util/Utils.java b/ethereumj-core/src/main/java/org/ethereum/util/Utils.java index 96ca254575..5b1822024f 100644 --- a/ethereumj-core/src/main/java/org/ethereum/util/Utils.java +++ b/ethereumj-core/src/main/java/org/ethereum/util/Utils.java @@ -76,7 +76,7 @@ public static String longToDateTime(long timestamp) { public static String longToTimePeriod(long msec) { if (msec < 1000) return msec + "ms"; - if (msec < 3000) return String.format("%.2f", msec / 1000d); + if (msec < 3000) return String.format("%.2fs", msec / 1000d); if (msec < 60 * 1000) return (msec / 1000) + "s"; long sec = msec / 1000; if (sec < 5 * 60) return (sec / 60) + "m" + (sec % 60) + "s"; @@ -257,6 +257,24 @@ public static void showErrorAndExit(String message, String... messages) { throw new RuntimeException(message); } + /** + * Show std warning messages in red. + */ + public static void showWarn(String message, String... messages) { + LoggerFactory.getLogger("general").warn(message); + final String ANSI_RED = "\u001B[31m"; + final String ANSI_RESET = "\u001B[0m"; + + System.err.println(ANSI_RED); + System.err.println(""); + System.err.println(" " + message); + for (String msg : messages) { + System.err.println(" " + msg); + } + System.err.println(""); + System.err.println(ANSI_RESET); + } + public static String sizeToStr(long size) { if (size < 2 * (1L << 10)) return size + "b"; if (size < 2 * (1L << 20)) return String.format("%dKb", size / (1L << 10)); diff --git a/ethereumj-core/src/main/java/org/ethereum/util/blockchain/StandaloneBlockchain.java b/ethereumj-core/src/main/java/org/ethereum/util/blockchain/StandaloneBlockchain.java index 3cd49ed270..818d63d759 100644 --- a/ethereumj-core/src/main/java/org/ethereum/util/blockchain/StandaloneBlockchain.java +++ b/ethereumj-core/src/main/java/org/ethereum/util/blockchain/StandaloneBlockchain.java @@ -381,11 +381,7 @@ private SolidityContractImpl createContract(String soliditySrc, String contractN private SolidityContractImpl createContractFromJson(String contractName, String json) throws IOException { CompilationResult result = CompilationResult.parse(json); if (contractName == null) { - if (result.contracts.size() > 1) { - throw new RuntimeException("Source contains more than 1 contact (" + result.contracts.keySet() + "). Please specify the contract name"); - } else { - contractName = result.contracts.keySet().iterator().next(); - } + contractName = result.getContractName(); } return createContract(contractName, result); @@ -397,10 +393,10 @@ private SolidityContractImpl createContractFromJson(String contractName, String * @return */ private SolidityContractImpl createContract(String contractName, CompilationResult result) { - ContractMetadata cMetaData = result.contracts.get(contractName); + ContractMetadata cMetaData = result.getContract(contractName); SolidityContractImpl contract = createContract(cMetaData); - for (CompilationResult.ContractMetadata metadata : result.contracts.values()) { + for (CompilationResult.ContractMetadata metadata : result.getContracts()) { contract.addRelatedContract(metadata.abi); } return contract; @@ -477,8 +473,9 @@ private BlockchainImpl createBlockchain(Genesis genesis) { blockStore.init(new HashMapDB(), new HashMapDB()); stateDS = new HashMapDB<>(); - pruningStateDS = new JournalSource<>(new CountingBytesSource(stateDS)); - pruneManager = new PruneManager(blockStore, pruningStateDS, SystemProperties.getDefault().databasePruneDepth()); + pruningStateDS = new JournalSource<>(stateDS); + pruneManager = new PruneManager(blockStore, pruningStateDS, + stateDS, SystemProperties.getDefault().databasePruneDepth()); final RepositoryRoot repository = new RepositoryRoot(pruningStateDS); 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 a644ed5750..3709db071b 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/DataWord.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/DataWord.java @@ -43,6 +43,8 @@ public class DataWord implements Comparable { public static final DataWord ZERO = new DataWord(new byte[32]); // don't push it in to the stack public static final DataWord ZERO_EMPTY_ARRAY = new DataWord(new byte[0]); // don't push it in to the stack + public static final long MEM_SIZE = 32 + 16 + 16; + private byte[] data = new byte[32]; public DataWord() { @@ -110,9 +112,8 @@ public BigInteger value() { public int intValue() { int intVal = 0; - for (int i = 0; i < data.length; i++) - { - intVal = (intVal << 8) + (data[i] & 0xff); + for (byte aData : data) { + intVal = (intVal << 8) + (aData & 0xff); } return intVal; @@ -140,9 +141,8 @@ public int intValueSafe() { public long longValue() { long longVal = 0; - for (int i = 0; i < data.length; i++) - { - longVal = (longVal << 8) + (data[i] & 0xff); + for (byte aData : data) { + longVal = (longVal << 8) + (aData & 0xff); } return longVal; diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/LogInfo.java b/ethereumj-core/src/main/java/org/ethereum/vm/LogInfo.java index 36815a47f4..89feb0af9f 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/LogInfo.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/LogInfo.java @@ -19,6 +19,7 @@ import org.ethereum.core.Bloom; import org.ethereum.crypto.HashUtil; +import org.ethereum.datasource.MemSizeEstimator; import org.ethereum.util.RLP; import org.ethereum.util.RLPElement; import org.ethereum.util.RLPItem; @@ -29,6 +30,8 @@ import java.util.ArrayList; import java.util.List; +import static org.ethereum.datasource.MemSizeEstimator.ByteArrayEstimator; + /** * @author Roman Mandeleil * @since 19.11.2014 @@ -39,9 +42,6 @@ public class LogInfo { List topics = new ArrayList<>(); byte[] data = new byte[]{}; - /* Log info in encoded form */ - private byte[] rlpEncoded; - public LogInfo(byte[] rlp) { RLPList params = RLP.decode2(rlp); @@ -58,8 +58,6 @@ public LogInfo(byte[] rlp) { byte[] topic = topic1.getRLPData(); this.topics.add(new DataWord(topic)); } - - rlpEncoded = rlp; } public LogInfo(byte[] address, List topics, byte[] data) { @@ -129,5 +127,8 @@ public String toString() { '}'; } - + public static final MemSizeEstimator MemEstimator = log -> + ByteArrayEstimator.estimateSize(log.address) + + ByteArrayEstimator.estimateSize(log.data) + + log.topics.size() * DataWord.MEM_SIZE + 16; } diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/program/Program.java b/ethereumj-core/src/main/java/org/ethereum/vm/program/Program.java index 5e8764c95a..b99667155e 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/program/Program.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/program/Program.java @@ -844,13 +844,17 @@ public void fullTrace() { getContractDetails(getOwnerAddress().getLast20Bytes()); StringBuilder storageData = new StringBuilder(); if (contractDetails != null) { - List storageKeys = new ArrayList<>(contractDetails.getStorage().keySet()); - Collections.sort(storageKeys); - for (DataWord key : storageKeys) { - storageData.append(" ").append(key).append(" -> "). - append(contractDetails.getStorage().get(key)).append("\n"); + try { + List storageKeys = new ArrayList<>(contractDetails.getStorage().keySet()); + Collections.sort(storageKeys); + for (DataWord key : storageKeys) { + storageData.append(" ").append(key).append(" -> "). + append(contractDetails.getStorage().get(key)).append("\n"); + } + if (storageData.length() > 0) storageData.insert(0, "\n"); + } catch (java.lang.Exception e) { + storageData.append("Failed to print storage: ").append(e.getMessage()); } - if (storageData.length() > 0) storageData.insert(0, "\n"); } StringBuilder memoryData = new StringBuilder(); @@ -1178,6 +1182,9 @@ public void callToPrecompiledAddress(MessageCall msg, PrecompiledContract contra track.rollback(); } else { + if (logger.isDebugEnabled()) + logger.debug("Call {}(data = {})", contract.getClass().getSimpleName(), Hex.toHexString(data)); + Pair out = contract.execute(data); if (out.getLeft()) { // success diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeFactoryImpl.java b/ethereumj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeFactoryImpl.java index 32a0800c78..010a2aed2f 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeFactoryImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeFactoryImpl.java @@ -98,6 +98,7 @@ public ProgramInvoke createProgramInvoke(Transaction tx, Block block, Repository if (logger.isInfoEnabled()) { logger.info("Top level call: \n" + + "tx.hash={}\n" + "address={}\n" + "origin={}\n" + "caller={}\n" + @@ -113,6 +114,7 @@ public ProgramInvoke createProgramInvoke(Transaction tx, Block block, Repository "difficulty={}\n" + "gaslimit={}\n", + Hex.toHexString(tx.getHash()), Hex.toHexString(address), Hex.toHexString(origin), Hex.toHexString(caller), diff --git a/ethereumj-core/src/main/resources/ethereumj.conf b/ethereumj-core/src/main/resources/ethereumj.conf index b74ab03d87..54e5ca59d9 100644 --- a/ethereumj-core/src/main/resources/ethereumj.conf +++ b/ethereumj-core/src/main/resources/ethereumj.conf @@ -186,6 +186,11 @@ database { # Set to 0 for a 'full' reset. resetBlock = 0 + # every time the application starts + # existing database will be restored from latest backup + # this option is supported by RocksDB only + fromBackup = false + # handling incompatible database version: # * EXIT - (default) show error in std out and exit by throwing Error # * RESET - clear database directory and continue working @@ -203,6 +208,14 @@ database { # controls how much last block states are not pruned # it is not recommneded to set this value below 192 # as it can prevent rebranching from long fork chains + # + # NOTE: the bigger this value is the larger memory footprint pruning has, + # here are some milstones to get the idea of how large it can be: + # 192: 10 Mb + # 1_000: 16 Mb + # 10_000: 58 Mb + # 100_000: 718 Mb + # 1_000_000: 5658 Mb maxDepth = 192 } } @@ -238,6 +251,7 @@ cache { # when reaching this threshold the bloom filter # is turned off forever # 128M can manage approx up to 50M of db entries + # DEPRECATED, states source is no more backed with bloom filter maxStateBloomSize = 128 } @@ -259,6 +273,14 @@ sync { # this is the fast and secure option to do fastsync # if not specified the block is selected like [peerBestBlockNumber - 1000] // pivotBlockHash = 6149ddfd7f52b2aa34a65b15ae117c269b5ff2dc58aa839dd015790553269411 + + # create a backup when the state has been downloaded + # this option is supported by RocksDB only + backupState = false + + # skip bodies and receipts downloading for blocks prior to pivot + # this option doesn't affect block headers + skipHistory = false } # minimal peers count @@ -314,7 +336,7 @@ mine { # minimal timeout between mined blocks minBlockTimeoutMsec = 0 - # start mining with specific nonce (might be usefult for testing) + # start mining with specific nonce (might be useful for testing) # null for random start nonce startNonce = null } @@ -408,8 +430,9 @@ hello.phrase = Dev # [hex hash 32 bytes] root hash root.hash.start = null -# Key value data source values: [leveldb/inmem] -keyvalue.datasource = leveldb +# Key value data source values: [rocksdb/leveldb/inmem] +# 'leveldb' option is meant to be DEPRECATED +keyvalue.datasource = rocksdb record.blocks=false blockchain.only=false diff --git a/ethereumj-core/src/main/resources/logback-detailed.xml b/ethereumj-core/src/main/resources/logback-detailed.xml index fce5b5fa2b..46762b75d1 100644 --- a/ethereumj-core/src/main/resources/logback-detailed.xml +++ b/ethereumj-core/src/main/resources/logback-detailed.xml @@ -48,6 +48,7 @@ + diff --git a/ethereumj-core/src/main/resources/logback.xml b/ethereumj-core/src/main/resources/logback.xml index 143011fa60..e37112568a 100644 --- a/ethereumj-core/src/main/resources/logback.xml +++ b/ethereumj-core/src/main/resources/logback.xml @@ -70,6 +70,7 @@ + diff --git a/ethereumj-core/src/main/resources/morden.conf b/ethereumj-core/src/main/resources/morden.conf deleted file mode 100644 index c98941c39e..0000000000 --- a/ethereumj-core/src/main/resources/morden.conf +++ /dev/null @@ -1,30 +0,0 @@ -peer.discovery = { - - # List of the seed peers to start - # the search for online peers - # values: [ip:port, ip:port, ip:port ...] - ip.list = [ - "94.242.229.4:40404", - "94.242.229.203:30303" - ] -} - -# Network id -peer.networkId = 2 - -# the folder resources/genesis -# contains several versions of -# genesis configuration according -# to the network the peer will run on -genesis = frontier-morden.json - -# Blockchain settings (constants and algorithms) which are -# not described in the genesis file (like MINIMUM_DIFFICULTY or Mining algorithm) -blockchain.config.name = "morden" - -database { - # place to save physical storage files - dir = database-morden -} - -//sync.fast.pivotBlockHash = 9ada56317a6fe36e8c92d82b06957c32170b71c1f1bae0012e67985b2685011c diff --git a/ethereumj-core/src/main/resources/test.conf b/ethereumj-core/src/main/resources/test.conf deleted file mode 100644 index 61a027b939..0000000000 --- a/ethereumj-core/src/main/resources/test.conf +++ /dev/null @@ -1,38 +0,0 @@ -peer.discovery = { - - # if peer discovery is off - # the peer window will show - # only what retrieved by active - # peer [true/false] - enabled = false -} - -peer { - - # Boot node list - active = [ - { url = "enode://9bcff30ea776ebd28a9424d0ac7aa500d372f918445788f45a807d83186bd52c4c0afaf504d77e2077e5a99f1f264f75f8738646c1ac3673ccc652b65565c3bb@peer-1.ether.camp:30303" }, - { url = "enode://c2b35ed63f5d79c7f160d05c54dd60b3ba32d455dbb10a5fe6fde44854073db02f9a538423a63a480126c74c7f650d77066ae446258e3d00388401d419b99f88@peer-2.ether.camp:30303" }, - { url = "enode://8246787f8d57662b850b354f0b526251eafee1f077fc709460dc8788fa640a597e49ffc727580f3ebbbc5eacb34436a66ea40415fab9d73563481666090a6cf0@peer-3.ether.camp:30303" } - ] -} - -# Network id -peer.networkId = 161 - -# the folder resources/genesis -# contains several versions of -# genesis configuration according -# to the network the peer will run on -genesis = frontier-test.json - -# Blockchain settings (constants and algorithms) which are -# not described in the genesis file (like MINIMUM_DIFFICULTY or Mining algorithm) -blockchain.config.name = "testnet" - -database { - # place to save physical storage files - dir = database-test -} - -//sync.fast.pivotBlockHash = 6eed2ab66a5edab75ad5f1b62498e328e0fd71391e0f08d0e2890531141fce0e diff --git a/ethereumj-core/src/main/resources/version.properties b/ethereumj-core/src/main/resources/version.properties index b931b2a72c..d5e0595301 100644 --- a/ethereumj-core/src/main/resources/version.properties +++ b/ethereumj-core/src/main/resources/version.properties @@ -1,2 +1,2 @@ -versionNumber='1.6.3' -databaseVersion=5 \ No newline at end of file +versionNumber='1.7.0' +databaseVersion=6 diff --git a/ethereumj-core/src/test/java/org/ethereum/config/DefaultConfigTest.java b/ethereumj-core/src/test/java/org/ethereum/config/DefaultConfigTest.java new file mode 100644 index 0000000000..16fe7d5376 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/config/DefaultConfigTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) [2017] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + * + * + */ + +package org.ethereum.config; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.IThrowableProxy; +import ch.qos.logback.core.read.ListAppender; +import org.ethereum.db.PruneManager; +import org.junit.Test; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.concurrent.Executors; + +import static org.junit.Assert.*; + +public class DefaultConfigTest { + /** + * TODO: For better testability, consider making setDefaultUncaughtExceptionHandler pluggable or Spring configurable as an autowired list + */ + @Test + public void testConstruction() throws InterruptedException { + ListAppender inMemoryAppender = new ListAppender<>(); + inMemoryAppender.start(); + + Logger logger = (Logger) LoggerFactory.getLogger("general"); + try { + logger.setLevel(Level.DEBUG); + logger.addAppender(inMemoryAppender); + + // Registers the safety net + new DefaultConfig(); + + // Trigger an exception in the background + Executors.newSingleThreadExecutor().execute(new ExceptionThrower()); + Thread.sleep(600); + + ILoggingEvent firstException = inMemoryAppender.list.get(0); + assertEquals("Uncaught exception", firstException.getMessage()); + + IThrowableProxy cause = firstException.getThrowableProxy(); + assertEquals("Unit test throwing an exception", cause.getMessage()); + } finally { + inMemoryAppender.stop(); + logger.detachAppender(inMemoryAppender); + } + } + + @Test + public void testNoopPruneManager() throws Exception { + DefaultConfig defaultConfig = new DefaultConfig(); + defaultConfig.config = new SystemProperties(); + defaultConfig.config.overrideParams("database.prune.enabled", "false"); + + PruneManager noopPruneManager = defaultConfig.pruneManager(); + // Should throw exception unless this is a NOOP prune manager + noopPruneManager.blockCommitted(null); + } + + private static class ExceptionThrower implements Runnable { + @Override + public void run() { + throw new IllegalStateException("Unit test throwing an exception"); + } + } +} \ No newline at end of file diff --git a/ethereumj-core/src/test/java/org/ethereum/config/InitializerTest.java b/ethereumj-core/src/test/java/org/ethereum/config/InitializerTest.java index d01d696320..98b04bc2ef 100644 --- a/ethereumj-core/src/test/java/org/ethereum/config/InitializerTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/config/InitializerTest.java @@ -17,6 +17,10 @@ */ package org.ethereum.config; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; import com.google.common.io.Files; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; @@ -25,25 +29,26 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; - -import static org.junit.Assert.*; +import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; +import java.util.List; import static org.ethereum.config.Initializer.DatabaseVersionHandler.Behavior; import static org.ethereum.config.Initializer.DatabaseVersionHandler.Behavior.*; +import static org.junit.Assert.*; /** * Created by Stan Reshetnyk on 11.09.16. */ public class InitializerTest { - final Initializer.DatabaseVersionHandler resetHelper = new Initializer.DatabaseVersionHandler(); + private final Initializer.DatabaseVersionHandler resetHelper = new Initializer.DatabaseVersionHandler(); - File tempFile; - String databaseDir; - File versionFile; + private File tempFile; + private String databaseDir; + private File versionFile; @Before public void before() { @@ -92,6 +97,7 @@ public void helper_shouldCreateVersionFile_whenOldVersion() { // create database without version SystemProperties props1 = withConfig(1, null); resetHelper.process(props1); + //noinspection ResultOfMethodCallIgnored versionFile.renameTo(new File(versionFile.getAbsoluteFile() + ".renamed")); SystemProperties props2 = withConfig(2, IGNORE); @@ -107,6 +113,7 @@ public void helper_shouldStop_whenNoVersionFileAndNotFirstVersion() throws IOExc resetHelper.process(props); // database is assumed to exist if dir is not empty + //noinspection ResultOfMethodCallIgnored versionFile.renameTo(new File(versionFile.getAbsoluteFile() + ".renamed")); resetHelper.process(props); @@ -171,9 +178,42 @@ public void helper_shouldPutVersion_afterDatabaseReset() throws IOException { assertFalse(testFile.exists()); // reset should have cleared file } + @Test + public void helper_shouldPrintCapabilityEthVersion_whenInfoEnabled() { + SystemProperties system = new SystemProperties(); + Initializer initializer = new Initializer(); + + ListAppender inMemoryAppender = new ListAppender<>(); + inMemoryAppender.start(); + + Logger logger = (Logger) LoggerFactory.getLogger("general"); + try { + logger.setLevel(Level.DEBUG); + logger.addAppender(inMemoryAppender); + + initializer.postProcessBeforeInitialization(system, "initializerBean"); + assertContainsLogLine(inMemoryAppender.list, "capability eth version: [62, 63]"); + assertContainsLogLine(inMemoryAppender.list, "capability shh version: [3]"); + assertContainsLogLine(inMemoryAppender.list, "capability bzz version: [0]"); + } finally { + inMemoryAppender.stop(); + logger.detachAppender(inMemoryAppender); + } + } + + private void assertContainsLogLine(List lines, final String line) { + for (ILoggingEvent loggingEvent : lines) { + if (loggingEvent.getFormattedMessage().equals(line)) { + return; + } + } + fail("Could not find log line that matches: " + line); + } + // HELPERS + @SuppressWarnings("ResultOfMethodCallIgnored") private File createFile() { final File testFile = new File(databaseDir + "/empty.file"); testFile.getParentFile().mkdirs(); @@ -204,11 +244,11 @@ private SystemProperties withConfig(int databaseVersion, Behavior behavior) { public static class SPO extends SystemProperties { - public SPO(Config config) { + SPO(Config config) { super(config); } - public void setDatabaseVersion(Integer databaseVersion) { + void setDatabaseVersion(Integer databaseVersion) { this.databaseVersion = databaseVersion; } } diff --git a/ethereumj-core/src/test/java/org/ethereum/config/NodeFilterTest.java b/ethereumj-core/src/test/java/org/ethereum/config/NodeFilterTest.java new file mode 100644 index 0000000000..97b5719326 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/config/NodeFilterTest.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) [2017] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + * + * + */ + +package org.ethereum.config; + +import org.ethereum.net.rlpx.Node; +import org.junit.Test; +import org.spongycastle.util.encoders.Hex; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +import static junit.framework.TestCase.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class NodeFilterTest { + + private static final byte[] NODE_1 = "node-1".getBytes(); + private static final byte[] NODE_2 = "node-2".getBytes(); + + @Test + public void addByHostIpPattern() throws Exception { + NodeFilter filter = new NodeFilter(); + + filter.add(NODE_1, "1.2.3.4"); + assertTrue(filter.accept(createTestNode("node-1", "1.2.3.4"))); + } + + @Test + public void doNotAcceptDifferentNodeNameButSameIp() throws Exception { + NodeFilter filter = new NodeFilter(); + filter.add(NODE_1, "1.2.3.4"); + assertTrue(filter.accept(createTestNode("node-1", "1.2.3.4"))); + assertFalse(filter.accept(createTestNode("node-2", "1.2.3.4"))); + } + + @Test + public void acceptDifferentNodeWithoutNameButSameIp() throws Exception { + NodeFilter filter = new NodeFilter(); + filter.add(null, "1.2.3.4"); + assertTrue(filter.accept(createTestNode("node-1", "1.2.3.4"))); + assertTrue(filter.accept(createTestNode("node-2", "1.2.3.4"))); + } + + @Test + public void acceptDuplicateFilter() { + NodeFilter filter = new NodeFilter(); + filter.add(NODE_1, "1.2.3.4"); + filter.add(NODE_1, "1.2.3.4"); + assertTrue(filter.accept(createTestNode("node-1", "1.2.3.4"))); + } + + @Test + public void acceptDifferentNodeNameButOverlappingIp() { + NodeFilter filter = new NodeFilter(); + filter.add(NODE_1, "1.2.3.4"); + filter.add(NODE_2, "1.2.3.*"); + assertTrue(filter.accept(createTestNode("node-1", "1.2.3.4"))); + assertTrue(filter.accept(createTestNode("node-2", "1.2.3.4"))); + assertTrue(filter.accept(createTestNode("node-2", "1.2.3.99"))); + + assertFalse(filter.accept(createTestNode("invalid-1", "1.2.4.1"))); + } + + @Test + public void acceptMultipleWildcards() { + NodeFilter filter = new NodeFilter(); + filter.add(NODE_1, "1.2.3.*"); + filter.add(NODE_2, "1.*.3.*"); + assertTrue(filter.accept(createTestNode("node-1", "1.2.3.4"))); + assertTrue(filter.accept(createTestNode("node-2", "1.2.3.4"))); + assertTrue(filter.accept(createTestNode("node-2", "1.2.3.99"))); + assertTrue(filter.accept(createTestNode("node-2", "1.2.99.99"))); + assertTrue(filter.accept(createTestNode("node-2", "1.99.99.99"))); + assertFalse(filter.accept(createTestNode("node-2", "2.99.99.99"))); + } + + @Test + public void addInvalidNodeShouldThrowException() throws Exception { + NodeFilter filter = new NodeFilter(); + assertFalse(filter.accept(createTestNode("invalid-1", null))); + } + + @Test + public void neverAcceptOnEmptyFilter() throws Exception { + NodeFilter filter = new NodeFilter(); + assertFalse(filter.accept(createTestNode("node-1", "1.2.3.4"))); + assertFalse(filter.accept(createTestNode("node-2", "1.2.3.4"))); + assertFalse(filter.accept(createTestNode("node-2", "1.2.3.99"))); + assertFalse(filter.accept(createTestNode("node-2", "1.2.99.99"))); + assertFalse(filter.accept(createTestNode("node-2", "1.99.99.99"))); + assertFalse(filter.accept(createTestNode("node-2", "2.99.99.99"))); + } + + @Test + public void acceptByInetAddress() throws Exception { + NodeFilter filter = new NodeFilter(); + filter.add(NODE_1, "8.*"); + assertTrue(filter.accept(InetAddress.getByName("8.8.8.8"))); + } + + @Test + public void doNotAcceptTheCatchAllWildcard() throws Exception { + NodeFilter filter = new NodeFilter(); + filter.add(NODE_1, "*"); + assertFalse(filter.accept(InetAddress.getByName("1.2.3.4"))); + assertFalse(filter.accept(createTestNode("node-1", "255.255.255.255"))); + } + + @Test + public void acceptNullIpPatternAsCatchAllForNodes() throws Exception { + NodeFilter filter = new NodeFilter(); + filter.add(NODE_1, null); + assertTrue(filter.accept(createTestNode("node-1", "1.2.3.4"))); + assertTrue(filter.accept(createTestNode("node-1", "255.255.255.255"))); + } + + @Test + public void doNotAcceptNullIpPatternAsCatchAllForInetAddresses() throws Exception { + NodeFilter filter = new NodeFilter(); + filter.add(NODE_1, null); + assertFalse(filter.accept(InetAddress.getByName("1.2.3.4"))); + assertFalse(filter.accept(InetAddress.getByName("255.255.255.255"))); + } + + @Test + public void acceptLoopback() throws Exception { + NodeFilter filter = new NodeFilter(); + filter.add(NODE_1, "127.0.0.1"); + assertTrue(filter.accept(InetAddress.getByName("localhost"))); + } + + @Test + public void doNotAcceptInvalidNodeHostnameWhenUsingPattern() throws Exception { + NodeFilter filter = new NodeFilter(); + filter.add(null, "1.2.3.4"); + + Node nodeWithInvalidHostname = new Node( + "enode://" + Hex.toHexString(NODE_1) + "@unknown:30303"); + assertFalse(filter.accept(nodeWithInvalidHostname)); + } + + @Test + public void acceptInvalidNodeHostnameWhenUsingWildcard() throws Exception { + NodeFilter filter = new NodeFilter(); + filter.add(null, null); + + Node nodeWithInvalidHostname = new Node( + "enode://" + Hex.toHexString(NODE_1) + "@unknown:30303"); + assertTrue(filter.accept(nodeWithInvalidHostname)); + } + + private static Node createTestNode(String nodeName, String hostIpPattern) { + return new Node("enode://" + Hex.toHexString(nodeName.getBytes()) + "@" + hostIpPattern + ":30303"); + } + +} \ No newline at end of file diff --git a/ethereumj-core/src/test/java/org/ethereum/config/SystemPropertiesTest.java b/ethereumj-core/src/test/java/org/ethereum/config/SystemPropertiesTest.java index 3b07fffd29..59cf748971 100644 --- a/ethereumj-core/src/test/java/org/ethereum/config/SystemPropertiesTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/config/SystemPropertiesTest.java @@ -17,43 +17,566 @@ */ package org.ethereum.config; +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.RandomUtils; +import org.ethereum.config.blockchain.OlympicConfig; +import org.ethereum.config.net.*; +import org.ethereum.core.AccountState; +import org.ethereum.core.Genesis; +import org.ethereum.db.ByteArrayWrapper; +import org.ethereum.net.rlpx.Node; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.util.FileCopyUtils; +import org.springframework.util.StringUtils; + +import javax.annotation.concurrent.NotThreadSafe; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.math.BigInteger; +import java.net.InetAddress; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static com.google.common.collect.Lists.newArrayList; +import static junit.framework.TestCase.assertNotNull; +import static org.junit.Assert.*; /** - * Created by Anton Nashatyrev on 26.08.2015. + * Not thread safe - testUseOnlySprintConfig temporarily sets a static flag that may influence other tests. + * Not thread safe - testGeneratedNodePrivateKey temporarily removes the nodeId.properties file which may influence other tests. */ +@SuppressWarnings("ConstantConditions") +@NotThreadSafe public class SystemPropertiesTest { + private final static Logger logger = LoggerFactory.getLogger(SystemPropertiesTest.class); + @Test - public void punchBindIpTest() { + public void testPunchBindIp() { SystemProperties.getDefault().overrideParams("peer.bind.ip", ""); long st = System.currentTimeMillis(); String ip = SystemProperties.getDefault().bindIp(); long t = System.currentTimeMillis() - st; - System.out.println(ip + " in " + t + " msec"); + logger.info(ip + " in " + t + " msec"); Assert.assertTrue(t < 10 * 1000); Assert.assertFalse(ip.isEmpty()); } @Test - public void externalIpTest() { + public void testExternalIp() { SystemProperties.getDefault().overrideParams("peer.discovery.external.ip", ""); long st = System.currentTimeMillis(); String ip = SystemProperties.getDefault().externalIp(); long t = System.currentTimeMillis() - st; - System.out.println(ip + " in " + t + " msec"); + logger.info(ip + " in " + t + " msec"); Assert.assertTrue(t < 10 * 1000); Assert.assertFalse(ip.isEmpty()); } @Test - public void blockchainNetConfigTest() { - SystemProperties systemProperties1 = new SystemProperties(); - systemProperties1.overrideParams("blockchain.config.name", "olympic"); - BlockchainNetConfig blockchainConfig1 = systemProperties1.getBlockchainConfig(); - SystemProperties systemProperties2 = new SystemProperties(); - systemProperties2.overrideParams("blockchain.config.name", "morden"); - BlockchainNetConfig blockchainConfig2= systemProperties2.getBlockchainConfig(); - Assert.assertNotEquals(blockchainConfig1.getClass(), blockchainConfig2.getClass()); + public void testExternalIpWhenSpecificallyConfigured() { + SystemProperties props = SystemProperties.getDefault(); + props.overrideParams("peer.discovery.external.ip", "1.1.1.1"); + assertEquals("1.1.1.1", props.externalIp()); + + props.overrideParams("peer.discovery.external.ip", "no_validation_rules_on_this_value"); + assertEquals("no_validation_rules_on_this_value", props.externalIp()); + } + + @Test + public void testBlockchainNetConfig() { + assertConfigNameResolvesToType("main", MainNetConfig.class); + assertConfigNameResolvesToType("olympic", OlympicConfig.class); + assertConfigNameResolvesToType("morden", MordenNetConfig.class); + assertConfigNameResolvesToType("ropsten", RopstenNetConfig.class); + assertConfigNameResolvesToType("testnet", TestNetConfig.class); + } + + private void assertConfigNameResolvesToType(String configName, Class expectedConfigType) { + SystemProperties props = new SystemProperties(); + props.overrideParams("blockchain.config.name", configName); + BlockchainNetConfig blockchainConfig = props.getBlockchainConfig(); + assertTrue(blockchainConfig.getClass().isAssignableFrom(expectedConfigType)); + } + + @Test + public void testConfigNamesAreCaseSensitive() { + assertConfigNameIsUnsupported("mAin"); + assertConfigNameIsUnsupported("Main"); + } + + @Test + public void testGarbageConfigNamesTriggerExceptions() { + assertConfigNameIsUnsupported("\t"); + assertConfigNameIsUnsupported("\n"); + assertConfigNameIsUnsupported(""); + assertConfigNameIsUnsupported("fake"); + } + + private void assertConfigNameIsUnsupported(String configName) { + try { + SystemProperties props = new SystemProperties(); + props.overrideParams("blockchain.config.name", configName); + props.getBlockchainConfig(); + fail("Should throw error for unsupported config name " + configName); + } catch (Exception e) { + assertEquals("Unknown value for 'blockchain.config.name': '" + configName + "'", e.getMessage()); + } + } + + @Test + public void testUseOnlySprintConfig() { + boolean originalValue = SystemProperties.isUseOnlySpringConfig(); + + try { + SystemProperties.setUseOnlySpringConfig(false); + assertNotNull(SystemProperties.getDefault()); + + SystemProperties.setUseOnlySpringConfig(true); + assertNull(SystemProperties.getDefault()); + } finally { + SystemProperties.setUseOnlySpringConfig(originalValue); + } + } + + @Test + public void testValidateMeAnnotatedGetters() { + assertIncorrectValueTriggersConfigException("peer.discovery.enabled", "not_a_boolean"); + assertIncorrectValueTriggersConfigException("peer.discovery.persist", "not_a_boolean"); + assertIncorrectValueTriggersConfigException("peer.discovery.workers", "not_a_number"); + assertIncorrectValueTriggersConfigException("peer.discovery.touchPeriod", "not_a_number"); + assertIncorrectValueTriggersConfigException("peer.connection.timeout", "not_a_number"); + assertIncorrectValueTriggersConfigException("peer.p2p.version", "not_a_number"); + assertIncorrectValueTriggersConfigException("peer.p2p.framing.maxSize", "not_a_number"); + assertIncorrectValueTriggersConfigException("transaction.approve.timeout", "not_a_number"); + assertIncorrectValueTriggersConfigException("peer.discovery.ip.list", "not_a_ip"); + assertIncorrectValueTriggersConfigException("database.reset", "not_a_boolean"); + assertIncorrectValueTriggersConfigException("database.resetBlock", "not_a_number"); + assertIncorrectValueTriggersConfigException("database.prune.enabled", "not_a_boolean"); + assertIncorrectValueTriggersConfigException("database.resetBlock", "not_a_number"); + } + + private void assertIncorrectValueTriggersConfigException(String... keyValuePairs) { + try { + new SystemProperties().overrideParams(keyValuePairs); + fail("Should've thrown ConfigException"); + } catch (Exception ignore) { + } + } + + @Test + public void testDatabasePruneShouldAdhereToMax() { + SystemProperties props = new SystemProperties(); + props.overrideParams("database.prune.enabled", "false"); + assertEquals("When database.prune.maxDepth is not set, defaults to -1", -1, props.databasePruneDepth()); + + props.overrideParams("database.prune.enabled", "true", "database.prune.maxDepth", "42"); + assertEquals(42, props.databasePruneDepth()); + } + + @Test + public void testRequireEitherNameOrClassConfiguration() { + try { + SystemProperties props = new SystemProperties(); + props.overrideParams("blockchain.config.name", "test", "blockchain.config.class", "org.ethereum.config.net.TestNetConfig"); + props.getBlockchainConfig(); + fail("Should've thrown exception because not 'Only one of two options should be defined'"); + } catch (RuntimeException e) { + assertEquals("Only one of two options should be defined: 'blockchain.config.name' and 'blockchain.config.class'", e.getMessage()); + } + } + + @Test + public void testRequireTypeBlockchainNetConfigOnManualClass() { + SystemProperties props = new SystemProperties(); + props.overrideParams("blockchain.config.name", null, "blockchain.config.class", "org.ethereum.config.net.TestNetConfig"); + assertTrue(props.getBlockchainConfig().getClass().isAssignableFrom(TestNetConfig.class)); + } + + @Test + public void testNonExistentBlockchainNetConfigClass() { + SystemProperties props = new SystemProperties(); + try { + props.overrideParams("blockchain.config.name", null, "blockchain.config.class", "org.ethereum.config.net.NotExistsConfig"); + props.getBlockchainConfig(); + fail("Should throw exception for invalid class"); + } catch (RuntimeException expected) { + assertEquals("The class specified via blockchain.config.class 'org.ethereum.config.net.NotExistsConfig' not found", expected.getMessage()); + } + } + + @Test + public void testNotInstanceOfBlockchainForkConfig() { + SystemProperties props = new SystemProperties(ConfigFactory.empty(), getClass().getClassLoader()); + try { + props.overrideParams("blockchain.config.name", null, "blockchain.config.class", "org.ethereum.config.NodeFilter"); + props.getBlockchainConfig(); + fail("Should throw exception for invalid class"); + } catch (RuntimeException expected) { + assertEquals("The class specified via blockchain.config.class 'org.ethereum.config.NodeFilter' is not instance of org.ethereum.config.BlockchainForkConfig", expected.getMessage()); + } + } + + @Test + public void testEmptyListOnEmptyPeerActiveConfiguration() { + SystemProperties props = new SystemProperties(); + props.overrideParams("peer.active", null); + assertEquals(newArrayList(), props.peerActive()); + } + + @Test + public void testPeerActive() { + ActivePeer node1 = ActivePeer.asEnodeUrl("node-1", "1.1.1.1"); + ActivePeer node2 = ActivePeer.asNode("node-2", "2.2.2.2"); + Config config = createActivePeersConfig(node1, node2); + + SystemProperties props = new SystemProperties(); + props.overrideParams(config); + + List activePeers = props.peerActive(); + assertEquals(2, activePeers.size()); + } + + @Test + public void testRequire64CharsNodeId() { + assertInvalidNodeId(RandomStringUtils.randomAlphanumeric(63)); + assertInvalidNodeId(RandomStringUtils.randomAlphanumeric(1)); + assertInvalidNodeId(RandomStringUtils.randomAlphanumeric(65)); + } + + private void assertInvalidNodeId(String nodeId) { + String hexEncodedNodeId = Hex.toHexString(nodeId.getBytes()); + + ActivePeer nodeWithInvalidNodeId = ActivePeer.asNodeWithId("node-1", "1.1.1.1", hexEncodedNodeId); + Config config = createActivePeersConfig(nodeWithInvalidNodeId); + + SystemProperties props = new SystemProperties(); + try { + props.overrideParams(config); + fail("Should've thrown exception for invalid node id"); + } catch (RuntimeException ignore) { } + } + + @Test + public void testUnexpectedElementInNodeConfigThrowsException() { + String nodeWithUnexpectedElement = "peer = {" + + "active = [{\n" + + " port = 30303\n" + + " nodeName = Test\n" + + " unexpectedElement = 12345\n" + + "}]}"; + + Config invalidConfig = ConfigFactory.parseString(nodeWithUnexpectedElement); + + SystemProperties props = new SystemProperties(); + try { + props.overrideParams(invalidConfig); + } catch (RuntimeException ignore) { } + } + + @Test + public void testActivePeersUsingNodeName() { + ActivePeer node = ActivePeer.asNodeWithName("node-1", "1.1.1.1", "peer-1"); + Config config = createActivePeersConfig(node); + + SystemProperties props = new SystemProperties(); + props.overrideParams(config); + + List activePeers = props.peerActive(); + assertEquals(1, activePeers.size()); + + Node peer = activePeers.get(0); + String expectedKeccak512HashOfNodeName = "fcaf073315aa0fe284dd6d76200ede5cc9277f3cb1fd7649ddab3b6a61e96ee91e957" + + "0b14932be6d6cd837027d50d9521923962909e5a9fdcdcabc3fe29408bb"; + String actualHexEncodedId = Hex.toHexString(peer.getId()); + assertEquals(expectedKeccak512HashOfNodeName, actualHexEncodedId); + } + + @Test + public void testRequireEitherNodeNameOrNodeId() { + ActivePeer node = ActivePeer.asNodeWithName("node-1", "1.1.1.1", null); + Config config = createActivePeersConfig(node); + + SystemProperties props = new SystemProperties(); + try { + props.overrideParams(config); + fail("Should require either nodeName or nodeId"); + } catch (RuntimeException ignore) { } + } + + private static Config createActivePeersConfig(ActivePeer... activePeers) { + StringBuilder config = new StringBuilder("peer = {"); + config.append(" active =["); + + for (int i = 0; i < activePeers.length; i++) { + ActivePeer activePeer = activePeers[i]; + config.append(activePeer.toString()); + if (i < activePeers.length - 1) { + config.append(","); + } + } + + config.append(" ]"); + config.append("}"); + + return ConfigFactory.parseString(config.toString()); + } + + @Test + public void testPeerTrusted() throws Exception{ + TrustedPeer peer1 = TrustedPeer.asNode("node-1", "1.1.1.1"); + TrustedPeer peer2 = TrustedPeer.asNode("node-2", "2.1.1.*"); + TrustedPeer peer3 = TrustedPeer.asNode("node-2", "3.*"); + Config config = createTrustedPeersConfig(peer1, peer2, peer3); + + SystemProperties props = new SystemProperties(); + props.overrideParams(config); + + NodeFilter filter = props.peerTrusted(); + assertTrue(filter.accept(InetAddress.getByName("1.1.1.1"))); + assertTrue(filter.accept(InetAddress.getByName("2.1.1.1"))); + assertTrue(filter.accept(InetAddress.getByName("2.1.1.9"))); + assertTrue(filter.accept(InetAddress.getByName("3.1.1.1"))); + assertTrue(filter.accept(InetAddress.getByName("3.1.1.9"))); + assertTrue(filter.accept(InetAddress.getByName("3.9.1.9"))); + assertFalse(filter.accept(InetAddress.getByName("4.1.1.1"))); + } + + private static Config createTrustedPeersConfig(TrustedPeer... trustedPeers) { + StringBuilder config = new StringBuilder("peer = {"); + config.append(" trusted =["); + + for (int i = 0; i < trustedPeers.length; i++) { + TrustedPeer activePeer = trustedPeers[i]; + config.append(activePeer.toString()); + if (i < trustedPeers.length - 1) { + config.append(","); + } + } + + config.append(" ]"); + config.append("}"); + + return ConfigFactory.parseString(config.toString()); + } + + @Test + public void testRequire64CharsPrivateKey() { + assertInvalidPrivateKey(RandomUtils.nextBytes(1)); + assertInvalidPrivateKey(RandomUtils.nextBytes(31)); + assertInvalidPrivateKey(RandomUtils.nextBytes(33)); + assertInvalidPrivateKey(RandomUtils.nextBytes(64)); + assertInvalidPrivateKey(RandomUtils.nextBytes(0)); + + String validPrivateKey = Hex.toHexString(RandomUtils.nextBytes(32)); + SystemProperties props = new SystemProperties(); + props.overrideParams("peer.privateKey", validPrivateKey); + assertEquals(validPrivateKey, props.privateKey()); + } + + private void assertInvalidPrivateKey(byte[] privateKey) { + String hexEncodedPrivateKey = Hex.toHexString(privateKey); + + SystemProperties props = new SystemProperties(); + try { + props.overrideParams("peer.privateKey", hexEncodedPrivateKey); + props.privateKey(); + fail("Should've thrown exception for invalid private key"); + } catch (RuntimeException ignore) { } + } + + @Ignore + @Test + public void testExposeBugWhereNonHexEncodedIsAcceptedWithoutValidation() { + SystemProperties props = new SystemProperties(); + String nonHexEncoded = RandomStringUtils.randomAlphanumeric(64); + try { + props.overrideParams("peer.privateKey", nonHexEncoded); + props.privateKey(); + fail("Should've thrown exception for invalid private key"); + } catch (RuntimeException ignore) { } + } + + /** + * TODO: Consider using a strategy interface for #getGeneratedNodePrivateKey(). + * Anything 'File' and 'random' generation are difficult to test and assert + */ + @Test + public void testGeneratedNodePrivateKeyThroughECKey() throws Exception { + File outputFile = new File("database-test/nodeId.properties"); + //noinspection ResultOfMethodCallIgnored + outputFile.delete(); + + SystemProperties props = new SystemProperties(); + props.privateKey(); + + assertTrue(outputFile.exists()); + String contents = FileCopyUtils.copyToString(new FileReader(outputFile)); + String[] lines = StringUtils.tokenizeToStringArray(contents, "\n"); + assertEquals(4, lines.length); + assertTrue(lines[0].startsWith("#Generated NodeID.")); + assertTrue(lines[1].startsWith("#")); + assertTrue(lines[2].startsWith("nodeIdPrivateKey=")); + assertEquals("nodeIdPrivateKey=".length() + 64, lines[2].length()); + assertTrue(lines[3].startsWith("nodeId=")); + assertEquals("nodeId=".length() + 128, lines[3].length()); + } + + @Test + public void testFastSyncPivotBlockHash() { + SystemProperties props = new SystemProperties(); + assertNull(props.getFastSyncPivotBlockHash()); + + byte[] validPivotBlockHash = RandomUtils.nextBytes(32); + props.overrideParams("sync.fast.pivotBlockHash", Hex.toHexString(validPivotBlockHash)); + assertTrue(Arrays.equals(validPivotBlockHash, props.getFastSyncPivotBlockHash())); + + assertInvalidPivotBlockHash(RandomUtils.nextBytes(0)); + assertInvalidPivotBlockHash(RandomUtils.nextBytes(1)); + assertInvalidPivotBlockHash(RandomUtils.nextBytes(31)); + assertInvalidPivotBlockHash(RandomUtils.nextBytes(33)); + } + + private void assertInvalidPivotBlockHash(byte[] pivotBlockHash) { + String hexEncodedPrivateKey = Hex.toHexString(pivotBlockHash); + + SystemProperties props = new SystemProperties(); + try { + props.overrideParams("sync.fast.pivotBlockHash", hexEncodedPrivateKey); + props.getFastSyncPivotBlockHash(); + fail("Should've thrown exception for invalid private key"); + } catch (RuntimeException ignore) { } + } + + @Test + public void testUseGenesis() throws IOException { + BigInteger mordenInitialNonse = BigInteger.valueOf(0x100000); + SystemProperties props = new SystemProperties() { + @Override + public BlockchainNetConfig getBlockchainConfig() { + return new MordenNetConfig(); + } + }; + Resource sampleGenesisBlock = new ClassPathResource("/config/genesis-sample.json"); + Genesis genesis = props.useGenesis(sampleGenesisBlock.getInputStream()); + + /* + * Assert that MordenNetConfig is used when generating the + * premine state. + */ + Map premine = genesis.getPremine(); + assertEquals(1, premine.size()); + + Genesis.PremineAccount account = premine.values().iterator().next(); + AccountState state = account.accountState; + assertEquals(state.getNonce(), mordenInitialNonse); + //noinspection SpellCheckingInspection + assertEquals("#0 (4addb5 <~ 000000) Txs:0, Unc: 0", genesis.getShortDescr()); + } + + @Test + public void testDump() { + SystemProperties props = new SystemProperties(); + /* + * No intend to test TypeSafe's render functionality. Perform + * some high-level asserts to verify that: + * - it's probably a config + * - it's probably fairly sized + * - it didn't break + */ + String dump = props.dump().trim(); + assertTrue(dump.startsWith("{")); + assertTrue(dump.endsWith("}")); + assertTrue(dump.length() > 5 * 1024); + assertTrue(StringUtils.countOccurrencesOf(dump, "{") > 50); + assertTrue(StringUtils.countOccurrencesOf(dump, "{") > 50); + } + + @SuppressWarnings("SameParameterValue") + static class ActivePeer { + boolean asEnodeUrl; + String node; + String host; + String nodeId; + String nodeName; + + static ActivePeer asEnodeUrl(String node, String host) { + return new ActivePeer(true, node, host, "e437a4836b77ad9d9ffe73ee782ef2614e6d8370fcf62191a6e488276e23717147073a7ce0b444d485fff5a0c34c4577251a7a990cf80d8542e21b95aa8c5e6c", null); + } + + static ActivePeer asNode(String node, String host) { + return asNodeWithId(node, host, "e437a4836b77ad9d9ffe73ee782ef2614e6d8370fcf62191a6e488276e23717147073a7ce0b444d485fff5a0c34c4577251a7a990cf80d8542e21b95aa8c5e6c"); + } + + static ActivePeer asNodeWithId(String node, String host, String nodeId) { + return new ActivePeer(false, node, host, nodeId, null); + } + + static ActivePeer asNodeWithName(String node, String host, String name) { + return new ActivePeer(false, node, host, null, name); + } + + private ActivePeer(boolean asEnodeUrl, String node, String host, String nodeId, String nodeName) { + this.asEnodeUrl = asEnodeUrl; + this.node = node; + this.host = host; + this.nodeId = nodeId; + this.nodeName = nodeName; + } + + public String toString() { + String hexEncodedNode = Hex.toHexString(node.getBytes()); + if (asEnodeUrl) { + return "{\n" + + " url = \"enode://" + hexEncodedNode + "@" + host + ".com:30303\" \n" + + "}"; + } + + if (StringUtils.hasText(nodeName)) { + return "{\n" + + " ip = " + host + "\n" + + " port = 30303\n" + + " nodeName = " + nodeName + "\n" + + "}\n"; + } + + return "{\n" + + " ip = " + host + "\n" + + " port = 30303\n" + + " nodeId = " + nodeId + "\n" + + "}\n"; + } + } + + @SuppressWarnings("SameParameterValue") + static class TrustedPeer { + String ip; + String nodeId; + + static TrustedPeer asNode(String nodeId, String ipPattern) { + return new TrustedPeer(nodeId, ipPattern); + } + + private TrustedPeer(String nodeId, String ipPattern) { + this.ip = ipPattern; + this.nodeId = Hex.toHexString(nodeId.getBytes()); + } + + public String toString() { + return "{\n" + + " ip = \"" + ip + "\"\n" + + " nodeId = " + nodeId + "\n" + + "}\n"; + } } } diff --git a/ethereumj-core/src/test/java/org/ethereum/config/blockchain/BlockHeaderBuilder.java b/ethereumj-core/src/test/java/org/ethereum/config/blockchain/BlockHeaderBuilder.java new file mode 100644 index 0000000000..f846e6bf70 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/config/blockchain/BlockHeaderBuilder.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) [2017] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + * + * + */ +package org.ethereum.config.blockchain; + +import org.apache.commons.lang3.StringUtils; +import org.ethereum.core.BlockHeader; + +import java.math.BigInteger; + +class BlockHeaderBuilder { + private byte[] EMPTY_ARRAY = new byte[0]; + + private byte[] parentHash; + private long blockNumber; + private BigInteger difficulty = BigInteger.ZERO; + private long timestamp = 2L; + private byte[] unclesHash = EMPTY_ARRAY; + + BlockHeaderBuilder(byte[] parentHash, long blockNumber, String difficulty) { + this(parentHash, blockNumber, parse(difficulty)); + } + + BlockHeaderBuilder(byte[] parentHash, long blockNumber, int difficulty) { + this(parentHash, blockNumber, BigInteger.valueOf(difficulty)); + } + + BlockHeaderBuilder(byte[] parentHash, long blockNumber, BigInteger difficulty) { + this.parentHash = parentHash; + this.blockNumber = blockNumber; + this.difficulty = difficulty; + } + + BlockHeaderBuilder withTimestamp(long timestamp) { + this.timestamp = timestamp; + return this; + } + + BlockHeaderBuilder withUncles(byte[] unclesHash) { + this.unclesHash = unclesHash; + return this; + } + + BlockHeader build() { + return new BlockHeader(parentHash, unclesHash, EMPTY_ARRAY, EMPTY_ARRAY, + difficulty.toByteArray(), blockNumber, EMPTY_ARRAY, 1L, timestamp, EMPTY_ARRAY, EMPTY_ARRAY, EMPTY_ARRAY); + } + + public static BigInteger parse(String val) { + return new BigInteger(StringUtils.replace(val, ",", "")); + } + +} diff --git a/ethereumj-core/src/test/java/org/ethereum/config/blockchain/ByzantiumConfigTest.java b/ethereumj-core/src/test/java/org/ethereum/config/blockchain/ByzantiumConfigTest.java new file mode 100644 index 0000000000..76eadbe520 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/config/blockchain/ByzantiumConfigTest.java @@ -0,0 +1,224 @@ +/* + * Copyright (c) [2017] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + * + * + */ + +package org.ethereum.config.blockchain; + +import org.ethereum.config.Constants; +import org.ethereum.config.ConstantsAdapter; +import org.ethereum.core.BlockHeader; +import org.junit.Ignore; +import org.junit.Test; +import org.spongycastle.util.encoders.Hex; + +import java.math.BigInteger; + +import static org.junit.Assert.*; + +@SuppressWarnings("SameParameterValue") +public class ByzantiumConfigTest { + + private static final byte[] FAKE_HASH = {11, 12}; + + @Test + public void testPredefinedChainId() throws Exception { + ByzantiumConfig byzantiumConfig = new ByzantiumConfig(new TestBlockchainConfig()); + assertEquals(1, (int) byzantiumConfig.getChainId()); + } + + @Test + public void testRelatedEip() throws Exception { + TestBlockchainConfig parent = new TestBlockchainConfig(); + + ByzantiumConfig byzantiumConfig = new ByzantiumConfig(parent); + // Inherited from parent + assertTrue(byzantiumConfig.eip198()); + assertTrue(byzantiumConfig.eip206()); + assertTrue(byzantiumConfig.eip211()); + assertTrue(byzantiumConfig.eip212()); + assertTrue(byzantiumConfig.eip213()); + assertTrue(byzantiumConfig.eip214()); + assertTrue(byzantiumConfig.eip658()); + + // Always false + assertTrue(byzantiumConfig.eip161()); + } + + + @Test + public void testDifficultyWithoutExplosion() throws Exception { + ByzantiumConfig byzantiumConfig = new ByzantiumConfig(new TestBlockchainConfig()); + + BlockHeader parent = new BlockHeaderBuilder(new byte[]{11, 12}, 0L, 1_000_000).build(); + BlockHeader current = new BlockHeaderBuilder(parent.getHash(), 1L, -1).build(); + + BigInteger difficulty = byzantiumConfig.calcDifficulty(current, parent); + assertEquals(BigInteger.valueOf(1_000_976), difficulty); + } + + @Test + public void testDifficultyAdjustedForParentBlockHavingUncles() throws Exception { + ByzantiumConfig byzantiumConfig = new ByzantiumConfig(new TestBlockchainConfig()); + BlockHeader parent = new BlockHeaderBuilder(new byte[]{11, 12}, 0L, 0) + .withTimestamp(0L) + .withUncles(new byte[]{1, 2}) + .build(); + BlockHeader current = new BlockHeaderBuilder(parent.getHash(), 1L, 0) + .withTimestamp(9L) + .build(); + assertEquals(1, byzantiumConfig.getCalcDifficultyMultiplier(current, parent).intValue()); + } + + @Test + @Ignore + public void testEtherscanIoBlock4490790() throws Exception { + ByzantiumConfig byzantiumConfig = new ByzantiumConfig(new TestBlockchainConfig()); + + // https://etherscan.io/block/4490788 + String parentHash = "fd9d7467e933ff2975c33ea3045ddf8773c87c4cec4e7da8de1bcc015361b38b"; + BlockHeader parent = new BlockHeaderBuilder(parentHash.getBytes(), 4490788, "1,377,255,445,606,146") + .withTimestamp(1509827488) + // Actually an empty list hash, so _no_ uncles + .withUncles(Hex.decode("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347")) + .build(); + + // https://etherscan.io/block/4490789 + BlockHeader current = new BlockHeaderBuilder(parent.getHash(), 4490789, BigInteger.ZERO) + .withTimestamp(1509827494) + .build(); + + BigInteger minimumDifficulty = byzantiumConfig.calcDifficulty(current, parent); + assertEquals(BlockHeaderBuilder.parse("1,378,600,421,631,340"), minimumDifficulty); + + BigInteger actualDifficultyOnEtherscan = BlockHeaderBuilder.parse("1,377,927,933,620,791"); + assertTrue(actualDifficultyOnEtherscan.compareTo(minimumDifficulty) > -1); + } + + @Test + public void testDifficultyWithExplosionShouldBeImpactedByBlockTimestamp() throws Exception { + ByzantiumConfig byzantiumConfig = new ByzantiumConfig(new TestBlockchainConfig()); + + BlockHeader parent = new BlockHeaderBuilder(new byte[]{11, 12}, 2_500_000, 8_388_608) + .withTimestamp(0) + .build(); + BlockHeader current = new BlockHeaderBuilder(parent.getHash(), 2_500_001, 8_388_608) + .withTimestamp(10 * 60) // 10 minutes later, longer time: lowers difficulty + .build(); + + BigInteger difficulty = byzantiumConfig.calcDifficulty(current, parent); + assertEquals(BigInteger.valueOf(8126464), difficulty); + + + parent = new BlockHeaderBuilder(new byte[]{11, 12}, 2_500_000, 8_388_608) + .withTimestamp(0) + .build(); + current = new BlockHeaderBuilder(parent.getHash(), 2_500_001, 8_388_608) + .withTimestamp(5) // 5 seconds later, shorter time: higher difficulty + .build(); + + difficulty = byzantiumConfig.calcDifficulty(current, parent); + assertEquals(BigInteger.valueOf(8396800), difficulty); + } + + @Test + public void testDifficultyAboveBlock3MShouldTriggerExplosion() throws Exception { + ByzantiumConfig byzantiumConfig = new ByzantiumConfig(new TestBlockchainConfig()); + + int parentDifficulty = 268_435_456; + BlockHeader parent = new BlockHeaderBuilder(FAKE_HASH, 4_000_000, parentDifficulty).build(); + BlockHeader current = new BlockHeaderBuilder(parent.getHash(), 4_000_001, -1).build(); + int actualDifficulty = byzantiumConfig.calcDifficulty(current, parent).intValue(); + int differenceWithoutExplosion = actualDifficulty - parentDifficulty; + assertEquals(262_400, differenceWithoutExplosion); + + parent = new BlockHeaderBuilder(FAKE_HASH, 5_000_000, parentDifficulty).build(); + current = new BlockHeaderBuilder(parent.getHash(), 5_000_001, -1).build(); + actualDifficulty = byzantiumConfig.calcDifficulty(current, parent).intValue(); + differenceWithoutExplosion = actualDifficulty - parentDifficulty; + assertEquals(524_288, differenceWithoutExplosion); + + parent = new BlockHeaderBuilder(FAKE_HASH, 6_000_000, parentDifficulty).build(); + current = new BlockHeaderBuilder(parent.getHash(), 6_000_001, -1).build(); + actualDifficulty = byzantiumConfig.calcDifficulty(current, parent).intValue(); + differenceWithoutExplosion = actualDifficulty - parentDifficulty; + assertEquals(268_697_600, differenceWithoutExplosion); + } + + @Test + @SuppressWarnings("PointlessArithmeticExpression") + public void testCalcDifficultyMultiplier() throws Exception { + // Note; timestamps are in seconds + assertCalcDifficultyMultiplier(0L, 1L, 2); + assertCalcDifficultyMultiplier(0L, 5, 2); // 5 seconds + assertCalcDifficultyMultiplier(0L, 1 * 10, 1); // 10 seconds + assertCalcDifficultyMultiplier(0L, 2 * 10, -0); // 20 seconds + assertCalcDifficultyMultiplier(0L, 10 * 10, -9); // 100 seconds + assertCalcDifficultyMultiplier(0L, 60 * 10, -64); // 10 mins + assertCalcDifficultyMultiplier(0L, 60 * 12, -78); // 12 mins + } + + private void assertCalcDifficultyMultiplier(long parentBlockTimestamp, long curBlockTimestamp, int expectedMultiplier) { + ByzantiumConfig byzantiumConfig = new ByzantiumConfig(new TestBlockchainConfig()); + BlockHeader parent = new BlockHeaderBuilder(new byte[]{11, 12}, 0L, 0) + .withTimestamp(parentBlockTimestamp) + .build(); + BlockHeader current = new BlockHeaderBuilder(parent.getHash(), 1L, 0) + .withTimestamp(curBlockTimestamp) + .build(); + assertEquals(expectedMultiplier, byzantiumConfig.getCalcDifficultyMultiplier(current, parent).intValue()); + } + + + @Test + public void testExplosionChanges() throws Exception { + ByzantiumConfig byzantiumConfig = new ByzantiumConfig(new TestBlockchainConfig()); + + BlockHeader beforePauseBlock = new BlockHeaderBuilder(FAKE_HASH, 2_000_000, 0).build(); + assertEquals(-2, byzantiumConfig.getExplosion(beforePauseBlock, null)); + + BlockHeader endOfIceAge = new BlockHeaderBuilder(FAKE_HASH, 3_000_000, 0).build(); + assertEquals(-2, byzantiumConfig.getExplosion(endOfIceAge, null)); + + BlockHeader startExplodingBlock = new BlockHeaderBuilder(FAKE_HASH, 3_200_000, 0).build(); + assertEquals(0, byzantiumConfig.getExplosion(startExplodingBlock, null)); + + startExplodingBlock = new BlockHeaderBuilder(FAKE_HASH, 4_000_000, 0).build(); + assertEquals(8, byzantiumConfig.getExplosion(startExplodingBlock, null)); + + startExplodingBlock = new BlockHeaderBuilder(FAKE_HASH, 6_000_000, 0).build(); + assertEquals(28, byzantiumConfig.getExplosion(startExplodingBlock, null)); + } + + @Test + public void testBlockReward() throws Exception { + ByzantiumConfig byzantiumConfig = new ByzantiumConfig(new TestBlockchainConfig() { + @Override + public Constants getConstants() { + return new ConstantsAdapter(super.getConstants()) { + @Override + public BigInteger getBLOCK_REWARD() { + // Make sure ByzantiumConfig is not using parent's block reward + return BigInteger.TEN; + } + }; + } + }); + assertEquals(new BigInteger("3000000000000000000"), byzantiumConfig.getConstants().getBLOCK_REWARD()); + } +} \ No newline at end of file diff --git a/ethereumj-core/src/test/java/org/ethereum/config/blockchain/ETCFork3MTest.java b/ethereumj-core/src/test/java/org/ethereum/config/blockchain/ETCFork3MTest.java new file mode 100644 index 0000000000..93235c1b95 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/config/blockchain/ETCFork3MTest.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) [2017] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + * + * + */ + +package org.ethereum.config.blockchain; + +import org.apache.commons.lang3.StringUtils; +import org.ethereum.core.BlockHeader; +import org.junit.Test; + +import java.math.BigInteger; + +import static org.junit.Assert.*; + +@SuppressWarnings("SameParameterValue") +public class ETCFork3MTest { + /** + * Ethereum Classic's Chain ID should be '61' according to + * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md + */ + @Test + public void textPredefinedChainId() throws Exception { + ETCFork3M etcFork3M = new ETCFork3M(new TestBlockchainConfig()); + assertEquals(61, (int) etcFork3M.getChainId()); + } + + @Test + public void testRelatedEip() throws Exception { + TestBlockchainConfig parent = new TestBlockchainConfig(); + + ETCFork3M etcFork3M = new ETCFork3M(parent); + // Inherited from parent + assertFalse(etcFork3M.eip198()); + assertFalse(etcFork3M.eip206()); + assertFalse(etcFork3M.eip211()); + assertFalse(etcFork3M.eip212()); + assertFalse(etcFork3M.eip213()); + assertFalse(etcFork3M.eip214()); + assertFalse(etcFork3M.eip658()); + + // Always false + assertFalse(etcFork3M.eip161()); + + /* + * By flipping parent's eip values, we assert that + * ETCFork3M delegates respective eip calls to parent. + */ + parent.enableAllEip(); + + // Inherited from parent + assertTrue(etcFork3M.eip198()); + assertFalse(etcFork3M.eip206()); + assertFalse(etcFork3M.eip211()); + assertTrue(etcFork3M.eip212()); + assertTrue(etcFork3M.eip213()); + assertFalse(etcFork3M.eip214()); + assertFalse(etcFork3M.eip658()); + + // Always false + assertFalse(etcFork3M.eip161()); + } + + + @Test + public void testDifficultyWithoutExplosion() throws Exception { + ETCFork3M etcFork3M = new ETCFork3M(new TestBlockchainConfig()); + + BlockHeader parent = new BlockHeaderBuilder(new byte[]{11, 12}, 0L, 1_000_000).build(); + BlockHeader current = new BlockHeaderBuilder(parent.getHash(), 1L, -1).build(); + + BigInteger difficulty = etcFork3M.calcDifficulty(current, parent); + assertEquals(BigInteger.valueOf(269435944), difficulty); + } + + @Test + public void testDifficultyWithExplosionShouldBeImpactedByBlockTimestamp() throws Exception { + ETCFork3M etcFork3M = new ETCFork3M(new TestBlockchainConfig()); + + BlockHeader parent = new BlockHeaderBuilder(new byte[]{11, 12}, 2_500_000, 8_388_608) + .withTimestamp(0) + .build(); + BlockHeader current = new BlockHeaderBuilder(parent.getHash(), 2_500_001, -1) + .withTimestamp(10 * 60) // 10 minutes later, longer time: lowers difficulty + .build(); + + BigInteger difficulty = etcFork3M.calcDifficulty(current, parent); + assertEquals(BigInteger.valueOf(276582400), difficulty); + + + parent = new BlockHeaderBuilder(new byte[]{11, 12}, 2_500_000, 8_388_608) + .withTimestamp(0) + .build(); + current = new BlockHeaderBuilder(parent.getHash(), 2_500_001, -1) + .withTimestamp(5) // 5 seconds later, shorter time: higher difficulty + .build(); + + difficulty = etcFork3M.calcDifficulty(current, parent); + assertEquals(BigInteger.valueOf(276828160), difficulty); + } + + @Test + public void testDifficultyAboveBlock5MShouldTriggerExplosion() throws Exception { + ETCFork3M etcFork3M = new ETCFork3M(new TestBlockchainConfig()); + + BlockHeader parent = new BlockHeaderBuilder(new byte[]{11, 12}, 5_000_000, 268_435_456).build(); + BlockHeader current = new BlockHeaderBuilder(parent.getHash(), 5_000_001, -1).build(); + assertEquals(BigInteger.valueOf(537_001_984), etcFork3M.calcDifficulty(current, parent)); + + parent = new BlockHeaderBuilder(new byte[]{11, 12}, 5_199_999, 1_073_872_896).build(); + current = new BlockHeaderBuilder(parent.getHash(), 5_200_000, 1_073_872_896).build(); + assertEquals(BlockHeaderBuilder.parse("2,148,139,072"), etcFork3M.calcDifficulty(current, parent)); + } + + @Test + @SuppressWarnings("PointlessArithmeticExpression") + public void testCalcDifficultyMultiplier() throws Exception { + // Note; timestamps are in seconds + assertCalcDifficultyMultiplier(0L, 1L, 1); + assertCalcDifficultyMultiplier(0L, 5, 1); // 5 seconds + assertCalcDifficultyMultiplier(0L, 1 * 10, 0); // 10 seconds + assertCalcDifficultyMultiplier(0L, 2 * 10, -1); // 20 seconds + assertCalcDifficultyMultiplier(0L, 10 * 10, -9); // 100 seconds + assertCalcDifficultyMultiplier(0L, 60 * 10, -59); // 10 mins + assertCalcDifficultyMultiplier(0L, 60 * 12, -71); // 12 mins + } + + private void assertCalcDifficultyMultiplier(long parentBlockTimestamp, long curBlockTimestamp, int expectedMultiplier) { + ETCFork3M etcFork3M = new ETCFork3M(new TestBlockchainConfig()); + BlockHeader parent = new BlockHeaderBuilder(new byte[]{11, 12}, 0L, 0) + .withTimestamp(parentBlockTimestamp) + .build(); + BlockHeader current = new BlockHeaderBuilder(parent.getHash(), 1L, 0) + .withTimestamp(curBlockTimestamp) + .build(); + assertEquals(expectedMultiplier, etcFork3M.getCalcDifficultyMultiplier(current, parent).intValue()); + } + + + /** + * https://github.com/ethereumproject/ECIPs/blob/master/ECIPs/ECIP-1010.md + * +
+     if (block.number < pause_block) {
+        explosion = (block.number / 100000) - 2
+     } else if (block.number < cont_block) {
+        explosion = fixed_diff
+     } else { // block.number >= cont_block
+        explosion = (block.number / 100000) - delay - 2
+     }
+     
+ + * Expected explosion values would be: +
+     Block 3,000,000 == 2**28 == 268,435,456
+     Block 4,000,000 == 2**28 == 268,435,456
+     Block 5,000,000 == 2**28 == 268,435,456
+     Block 5,200,000 == 2**30 == 1 TH
+     Block 6,000,000 == 2**38 == 274 TH
+     
+ Where the explosion is the value after '**'. + */ + @Test + public void testEcip1010ExplosionChanges() throws Exception { + ETCFork3M etcFork3M = new ETCFork3M(new TestBlockchainConfig()); + + /* + * Technically, a block number < 3_000_000 should result in an explosion < fixed_diff, or explosion < 28 + * + * Block number 3_000_000 occurred on Jan 15, 2017. The ETCFork3M configuration was committed a day after. It + * is therefor not necessary to have block.number < pause_block be implemented + */ + BlockHeader beforePauseBlock = new BlockHeaderBuilder(new byte[]{11, 12}, 2_500_000, 0).build(); + int unimplementedPrePauseBlockExplosion = 28; + assertEquals(unimplementedPrePauseBlockExplosion, etcFork3M.getExplosion(beforePauseBlock, null)); + + BlockHeader endOfIceAge = new BlockHeaderBuilder(new byte[]{11, 12}, 5_000_000, 0).build(); + assertEquals(28, etcFork3M.getExplosion(endOfIceAge, null)); + + BlockHeader startExplodingBlock = new BlockHeaderBuilder(new byte[]{11, 12}, 5_200_000, 0).build(); + assertEquals(30, etcFork3M.getExplosion(startExplodingBlock, null)); + + startExplodingBlock = new BlockHeaderBuilder(new byte[]{11, 12}, 6_000_000, 0).build(); + assertEquals(38, etcFork3M.getExplosion(startExplodingBlock, null)); + } +} \ No newline at end of file diff --git a/ethereumj-core/src/test/java/org/ethereum/config/blockchain/Eip150HFConfigTest.java b/ethereumj-core/src/test/java/org/ethereum/config/blockchain/Eip150HFConfigTest.java new file mode 100644 index 0000000000..e16bcff793 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/config/blockchain/Eip150HFConfigTest.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) [2017] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + * + * + */ + +package org.ethereum.config.blockchain; + +import org.ethereum.config.SystemProperties; +import org.ethereum.core.Transaction; +import org.ethereum.vm.DataWord; +import org.ethereum.vm.OpCode; +import org.junit.Test; + +import java.math.BigInteger; + +import static org.junit.Assert.*; + +/** + *

This unit test covers EIP-150:

+ * + *
+ * EIP: 150
+ * Title: Gas cost changes for IO-heavy operations
+ * Author: Vitalik Buterin
+ * Type: Standard Track
+ * Category: Core
+ * Status: Final
+ * Created: 2016-09-24
+ * 
+ * + *
If block.number >= FORK_BLKNUM, then:
+ *
    + *
  • Increase the gas cost of EXTCODESIZE to 700
  • + *
  • Increase the base gas cost of EXTCODECOPY to 700
  • + *
  • Increase the gas cost of BALANCE to 400
  • + *
  • Increase the gas cost of SLOAD to 200
  • + *
  • Increase the gas cost of CALL, DELEGATECALL, CALLCODE to 700
  • + *
  • Increase the gas cost of SELFDESTRUCT to 5000
  • + *
  • If SELFDESTRUCT hits a newly created account, it triggers an additional gas cost of 25000 (similar to CALLs)
  • + *
  • Increase the recommended gas limit target to 5.5 million
  • + *
  • Define "all but one 64th" of N as N - floor(N / 64)
  • + *
  • If a call asks for more gas than the maximum allowed amount (ie. total amount of gas remaining + * in the parent after subtracting the gas cost of the call and memory expansion), do not return an + * OOG error; instead, if a call asks for more gas than all but one 64th of the maximum allowed amount, + * call with all but one 64th of the maximum allowed amount of gas (this is equivalent to a version of + * #90 plus #114). CREATE only provides all but one 64th of the parent gas to the child call.
  • + *
+ *

Source -- https://github.com/ethereum/EIPs/blob/master/EIPS/eip-150.md

+ */ +public class Eip150HFConfigTest { + + @Test + public void testNewGasCost() { + Eip150HFConfig.GasCostEip150HF gasCostEip150HF = new Eip150HFConfig.GasCostEip150HF(); + assertEquals(700, gasCostEip150HF.getEXT_CODE_SIZE()); + assertEquals(700, gasCostEip150HF.getEXT_CODE_COPY()); + assertEquals(400, gasCostEip150HF.getBALANCE()); + assertEquals(200, gasCostEip150HF.getSLOAD()); + assertEquals(700, gasCostEip150HF.getCALL()); + assertEquals(5000, gasCostEip150HF.getSUICIDE()); + assertEquals(25000, gasCostEip150HF.getNEW_ACCT_SUICIDE()); + // TODO: Verify recommended gas limit target of 5.5 million. This may no longer apply though. + } + + @Test + public void testGetCreateGas() { + Eip150HFConfig eip150 = new Eip150HFConfig(null); + + DataWord createGas = eip150.getCreateGas(new DataWord(64_000)); + assertEquals(BigInteger.valueOf(63_000), createGas.value()); + } + + @Test + public void testAllButOne64thToUseFloor() { + Eip150HFConfig eip150 = new Eip150HFConfig(new DaoHFConfig()); + + assertCallGas(eip150, 63, 64, 63); + assertCallGas(eip150, 100, 64, 63); + assertCallGas(eip150, 10, 64, 10); + assertCallGas(eip150, 0, 64, 0); + assertCallGas(eip150, -10, 64, 63); + assertCallGas(eip150, 11, 10, 10); + } + + private void assertCallGas(Eip150HFConfig eip150, long requestedGas, long availableGas, long expectedCallGas) { + DataWord callGas = eip150.getCallGas(OpCode.CALL, new DataWord(requestedGas), new DataWord(availableGas)); + assertEquals(BigInteger.valueOf(expectedCallGas), callGas.value()); + } + + @Test + public void testRelatedEip() { + TestBlockchainConfig parentAllDependentEipTrue = new TestBlockchainConfig() + .enableEip161() + .enableEip198() + .enableEip212() + .enableEip213(); + Eip150HFConfig eip150 = new Eip150HFConfig(parentAllDependentEipTrue); + // Inherited from parent + assertTrue(eip150.eip161()); + assertTrue(eip150.eip198()); + assertTrue(eip150.eip212()); + assertTrue(eip150.eip213()); + + // Always false + assertFalse(eip150.eip206()); + assertFalse(eip150.eip211()); + assertFalse(eip150.eip214()); + assertFalse(eip150.eip658()); + + /* + * By flipping parent's eip values, we assert that + * Eip150 delegates respective eip calls to parent. + */ + parentAllDependentEipTrue = new TestBlockchainConfig(); + eip150 = new Eip150HFConfig(parentAllDependentEipTrue); + // Inherited from parent + assertFalse(eip150.eip161()); + assertFalse(eip150.eip198()); + assertFalse(eip150.eip212()); + assertFalse(eip150.eip213()); + + // Always false + assertFalse(eip150.eip206()); + assertFalse(eip150.eip211()); + assertFalse(eip150.eip214()); + assertFalse(eip150.eip658()); + } + + @Test + public void testParentInheritedProperties() { + byte[] emptyBytes = new byte[]{}; + SystemProperties props = new SystemProperties(); + + TestBlockchainConfig parent = new TestBlockchainConfig(); + Eip150HFConfig eip150 = new Eip150HFConfig(parent); + assertEquals(parent.constants, eip150.getConstants()); + assertEquals(parent.minerIfc, eip150.getMineAlgorithm(props)); + assertEquals(parent.difficulty, eip150.calcDifficulty(null, null)); + assertEquals(parent.difficultyMultiplier, eip150.getCalcDifficultyMultiplier(null, null)); + assertEquals(parent.transactionCost, eip150.getTransactionCost(null)); + assertEquals(parent.validateTransactionChanges, eip150.validateTransactionChanges(null, null, null, null)); + assertEquals(parent.extraData, eip150.getExtraData(emptyBytes, 0L)); + assertEquals(parent.headerValidators, eip150.headerValidators()); + assertEquals(parent.constants, eip150.getCommonConstants()); + assertSame(eip150, eip150.getConfigForBlock(0)); + assertSame(eip150, eip150.getConfigForBlock(1)); + assertSame(eip150, eip150.getConfigForBlock(5_000_000)); + assertTrue(eip150.getGasCost() instanceof Eip150HFConfig.GasCostEip150HF); + + Transaction txWithoutChainId = new Transaction(emptyBytes, emptyBytes, emptyBytes, emptyBytes, emptyBytes, emptyBytes, null); + assertTrue(eip150.acceptTransactionSignature(txWithoutChainId)); + Transaction txWithChainId = new Transaction(emptyBytes, emptyBytes, emptyBytes, emptyBytes, emptyBytes, emptyBytes, 1); + assertFalse(eip150.acceptTransactionSignature(txWithChainId)); + } +} \ No newline at end of file diff --git a/ethereumj-core/src/test/java/org/ethereum/config/blockchain/Eip160HFConfigTest.java b/ethereumj-core/src/test/java/org/ethereum/config/blockchain/Eip160HFConfigTest.java new file mode 100644 index 0000000000..3116510538 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/config/blockchain/Eip160HFConfigTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) [2017] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + * + * + */ + +package org.ethereum.config.blockchain; + +import org.ethereum.config.Constants; +import org.ethereum.core.Transaction; +import org.ethereum.crypto.ECKey; +import org.ethereum.util.ByteUtil; +import org.junit.Test; +import org.spongycastle.util.BigIntegers; +import org.spongycastle.util.encoders.Hex; + +import java.math.BigInteger; +import java.util.Arrays; + +import static java.math.BigInteger.valueOf; +import static org.junit.Assert.*; + +/** +
+ Specification
+
+ If block.number >= FORK_BLKNUM, increase the gas cost of EXP from 10 + 10 per byte in the
+ exponent to 10 + 50 per byte in the exponent.
+ 
+ * + * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-160.md + */ +public class Eip160HFConfigTest { + private byte[] emptyBytes = new byte[]{}; + + @Test + public void testGetGasCost() throws Exception { + TestBlockchainConfig parent = new TestBlockchainConfig(); + Eip160HFConfig config = new Eip160HFConfig(parent); + assertTrue(config.getGasCost() instanceof Eip160HFConfig.GasCostEip160HF); + assertEquals(50, config.getGasCost().getEXP_BYTE_GAS()); + } + + @Test + public void testMaxContractSizeIsOnlyChangeInEip160() throws Exception { + TestBlockchainConfig parent = new TestBlockchainConfig(); + Eip160HFConfig config = new Eip160HFConfig(parent); + assertEquals(0x6000, config.getConstants().getMAX_CONTRACT_SZIE()); + + Constants expected = parent.getConstants(); + Constants actual = config.getConstants(); + assertEquals(expected.getInitialNonce(), actual.getInitialNonce()); + assertEquals(expected.getMINIMUM_DIFFICULTY(), actual.getMINIMUM_DIFFICULTY()); + assertEquals(expected.getDIFFICULTY_BOUND_DIVISOR(), actual.getDIFFICULTY_BOUND_DIVISOR()); + assertEquals(expected.getMAXIMUM_EXTRA_DATA_SIZE(), actual.getMAXIMUM_EXTRA_DATA_SIZE()); + assertEquals(expected.getBLOCK_REWARD(), actual.getBLOCK_REWARD()); + assertEquals(expected.getEXP_DIFFICULTY_PERIOD(), actual.getEXP_DIFFICULTY_PERIOD()); + assertEquals(expected.getGAS_LIMIT_BOUND_DIVISOR(), actual.getGAS_LIMIT_BOUND_DIVISOR()); + assertEquals(expected.getUNCLE_GENERATION_LIMIT(), actual.getUNCLE_GENERATION_LIMIT()); + assertEquals(expected.getUNCLE_LIST_LIMIT(), actual.getUNCLE_LIST_LIMIT()); + assertEquals(expected.getDURATION_LIMIT(), actual.getDURATION_LIMIT()); + assertEquals(expected.getBEST_NUMBER_DIFF_LIMIT(), actual.getBEST_NUMBER_DIFF_LIMIT()); + assertEquals(expected.createEmptyContractOnOOG(), actual.createEmptyContractOnOOG()); + assertEquals(expected.hasDelegateCallOpcode(), actual.hasDelegateCallOpcode()); + } + + /** + * See also {@link org.ethereum.core.TransactionTest#afterEIP158Test} which tests the + * EIP-155 'signature.v' specification changes. + */ + @Test + public void testAcceptTransactionWithSignature() throws Exception { + Transaction tx = Transaction.create( + "3535353535353535353535353535353535353535", + new BigInteger("1000000000000000000"), + new BigInteger("9"), + new BigInteger("20000000000"), + new BigInteger("21000"), + 1); + + ECKey ecKey = ECKey.fromPrivate(Hex.decode("4646464646464646464646464646464646464646464646464646464646464646")); + tx.sign(ecKey); + + TestBlockchainConfig parent = new TestBlockchainConfig(); + Eip160HFConfig config = new Eip160HFConfig(parent); + assertTrue(config.acceptTransactionSignature(tx)); + } + + @Test + public void testDenyTransactionWithInvalidSignature() throws Exception { + Transaction tx = Transaction.create( + "3535353535353535353535353535353535353535", + new BigInteger("1000000000000000000"), + new BigInteger("9"), + new BigInteger("20000000000"), + new BigInteger("21000"), + 1); + + ECKey ecKey = ECKey.fromPrivate(Hex.decode("4646464646464646464646464646464646464646464646464646464646464646")); + tx.sign(ecKey); + + TestBlockchainConfig parent = new TestBlockchainConfig(); + Eip160HFConfig config = new Eip160HFConfig(parent); + + // Corrupt the signature and assert it's *not* accepted + tx.getSignature().v = 99; + assertFalse(config.acceptTransactionSignature(tx)); + } + + @Test + public void testDenyTransactionWithoutSignature() throws Exception { + TestBlockchainConfig parent = new TestBlockchainConfig(); + Eip160HFConfig config = new Eip160HFConfig(parent); + Transaction txWithoutSignature = new Transaction(emptyBytes, emptyBytes, emptyBytes, emptyBytes, emptyBytes, emptyBytes, null); + assertFalse(config.acceptTransactionSignature(txWithoutSignature)); + } + +} \ No newline at end of file diff --git a/ethereumj-core/src/test/java/org/ethereum/config/blockchain/TestBlockchainConfig.java b/ethereumj-core/src/test/java/org/ethereum/config/blockchain/TestBlockchainConfig.java new file mode 100644 index 0000000000..9dc1d2ce20 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/config/blockchain/TestBlockchainConfig.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) [2017] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + * + * + */ + +package org.ethereum.config.blockchain; + +import com.google.common.util.concurrent.ListenableFuture; +import org.apache.commons.lang3.tuple.Pair; +import org.ethereum.config.Constants; +import org.ethereum.config.SystemProperties; +import org.ethereum.core.Block; +import org.ethereum.core.BlockHeader; +import org.ethereum.core.Repository; +import org.ethereum.core.Transaction; +import org.ethereum.db.BlockStore; +import org.ethereum.mine.MinerIfc; +import org.ethereum.validator.BlockHeaderValidator; + +import java.math.BigInteger; +import java.util.List; + +import static com.google.common.collect.Lists.newArrayList; + +@SuppressWarnings("WeakerAccess") +class TestBlockchainConfig extends AbstractConfig { + boolean eip161 = false; + boolean eip198 = false; + boolean eip206 = false; + boolean eip211 = false; + boolean eip212 = false; + boolean eip213 = false; + boolean eip214 = false; + boolean eip658 = false; + + Constants constants = new Constants(); + MinerIfc minerIfc = new TestMinerIfc(); + BigInteger difficulty = BigInteger.valueOf(100); + BigInteger difficultyMultiplier = BigInteger.valueOf(20); + long transactionCost = 200L; + boolean acceptTransactionSignature = true; + String validateTransactionChanges = "test"; + byte[] extraData = new byte[]{}; + List> headerValidators = newArrayList(); + + public TestBlockchainConfig enableAllEip() { + return this + .enableEip161() + .enableEip198() + .enableEip206() + .enableEip211() + .enableEip212() + .enableEip213() + .enableEip214() + .enableEip658(); + } + + public TestBlockchainConfig enableEip161() { + this.eip161 = true; + return this; + } + + public TestBlockchainConfig enableEip198() { + this.eip198 = true; + return this; + } + + public TestBlockchainConfig enableEip206() { + this.eip206 = true; + return this; + } + + public TestBlockchainConfig enableEip211() { + this.eip211 = true; + return this; + } + + public TestBlockchainConfig enableEip212() { + this.eip212 = true; + return this; + } + + public TestBlockchainConfig enableEip213() { + this.eip213 = true; + return this; + } + + public TestBlockchainConfig enableEip214() { + this.eip214 = true; + return this; + } + + public TestBlockchainConfig enableEip658() { + this.eip658 = true; + return this; + } + + @Override + public Constants getConstants() { + return constants; + } + + @Override + public MinerIfc getMineAlgorithm(SystemProperties config) { + return minerIfc; + } + + @Override + public BigInteger calcDifficulty(BlockHeader curBlock, BlockHeader parent) { + return difficulty; + } + + @Override + public BigInteger getCalcDifficultyMultiplier(BlockHeader curBlock, BlockHeader parent) { + return difficultyMultiplier; + } + + @Override + public long getTransactionCost(Transaction tx) { + return transactionCost; + } + + @Override + public boolean acceptTransactionSignature(Transaction tx) { + return acceptTransactionSignature; + } + + @Override + public String validateTransactionChanges(BlockStore blockStore, Block curBlock, Transaction tx, Repository repository) { + return validateTransactionChanges; + } + + @Override + public byte[] getExtraData(byte[] minerExtraData, long blockNumber) { + return extraData; + } + + @Override + public List> headerValidators() { + return headerValidators; + } + + @Override + public boolean eip161() { + return eip161; + } + + @Override + public boolean eip198() { + return eip198; + } + + @Override + public boolean eip212() { + return eip212; + } + + @Override + public boolean eip213() { + return eip213; + } + + + public class TestMinerIfc implements MinerIfc { + @Override + public ListenableFuture mine(Block block) { + return null; + } + + @Override + public boolean validate(BlockHeader blockHeader) { + return false; + } + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/config/net/BaseNetConfigTest.java b/ethereumj-core/src/test/java/org/ethereum/config/net/BaseNetConfigTest.java new file mode 100644 index 0000000000..426da2d7cf --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/config/net/BaseNetConfigTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.config.net; + +import org.ethereum.config.BlockchainConfig; +import org.ethereum.config.blockchain.AbstractConfig; +import org.ethereum.core.BlockHeader; +import org.ethereum.core.Transaction; +import org.junit.Test; + +import java.math.BigInteger; + +import static org.junit.Assert.*; + +public class BaseNetConfigTest { + @Test(expected = RuntimeException.class) + public void addedBlockShouldHaveIncrementedBlockNumber() throws Exception { + BlockchainConfig blockchainConfig = new TestBlockchainConfig(); + + BaseNetConfig config = new BaseNetConfig(); + config.add(0, blockchainConfig); + config.add(1, blockchainConfig); + config.add(0, blockchainConfig); + } + + @Test + public void toStringShouldCaterForNulls() throws Exception { + BaseNetConfig config = new BaseNetConfig(); + assertEquals("BaseNetConfig{blockNumbers=[], configs=[], count=0}", config.toString()); + + BlockchainConfig blockchainConfig = new TestBlockchainConfig() { + @Override + public String toString() { + return "TestBlockchainConfig"; + } + }; + config.add(0, blockchainConfig); + assertEquals("BaseNetConfig{blockNumbers=[0], configs=[TestBlockchainConfig], count=1}", config.toString()); + + config.add(1, blockchainConfig); + assertEquals("BaseNetConfig{blockNumbers=[0, 1], configs=[TestBlockchainConfig, TestBlockchainConfig], count=2}", config.toString()); + } + + private static class TestBlockchainConfig extends AbstractConfig { + @Override + public BigInteger getCalcDifficultyMultiplier(BlockHeader curBlock, BlockHeader parent) { + return BigInteger.ZERO; + } + + @Override + public long getTransactionCost(Transaction tx) { + return 0; + } + } +} \ No newline at end of file diff --git a/ethereumj-core/src/test/java/org/ethereum/config/net/ETCNetConfigTest.java b/ethereumj-core/src/test/java/org/ethereum/config/net/ETCNetConfigTest.java new file mode 100644 index 0000000000..7121c3a309 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/config/net/ETCNetConfigTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.config.net; + +import org.ethereum.config.BlockchainConfig; +import org.ethereum.config.BlockchainNetConfig; +import org.ethereum.config.blockchain.Eip150HFConfig; +import org.ethereum.config.blockchain.Eip160HFConfig; +import org.ethereum.config.blockchain.FrontierConfig; +import org.ethereum.config.blockchain.HomesteadConfig; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +public class ETCNetConfigTest { + + @Test + public void verifyETCNetConfigConstruction() { + ETCNetConfig config = new ETCNetConfig(); + + assertBlockchainConfigExistsAt(config, 0, FrontierConfig.class); + assertBlockchainConfigExistsAt(config, 1_150_000, HomesteadConfig.class); + assertBlockchainConfigExistsAt(config, 2_500_000, Eip150HFConfig.class); + assertBlockchainConfigExistsAt(config, 3_000_000, Eip160HFConfig.class); + } + + private void assertBlockchainConfigExistsAt(BlockchainNetConfig netConfig, long blockNumber, Class configType) { + BlockchainConfig block = netConfig.getConfigForBlock(blockNumber); + assertTrue(configType.isAssignableFrom(block.getClass())); + } +} \ No newline at end of file diff --git a/ethereumj-core/src/test/java/org/ethereum/config/net/JsonNetConfigTest.java b/ethereumj-core/src/test/java/org/ethereum/config/net/JsonNetConfigTest.java new file mode 100644 index 0000000000..6e4ac87e8e --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/config/net/JsonNetConfigTest.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) [2017] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + * + * + */ + +package org.ethereum.config.net; + +import org.ethereum.config.BlockchainConfig; +import org.ethereum.config.BlockchainNetConfig; +import org.ethereum.config.blockchain.*; +import org.ethereum.core.genesis.GenesisConfig; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class JsonNetConfigTest { + @Test + public void testCreationBasedOnGenesis() { + GenesisConfig genesisConfig = new GenesisConfig(); + genesisConfig.eip155Block = 10; + JsonNetConfig config = new JsonNetConfig(genesisConfig); + assertBlockchainConfigExistsAt(config, 0, FrontierConfig.class); + assertBlockchainConfigExistsAt(config, 10, Eip150HFConfig.class); + } + + @Test + public void testCreationBasedOnDaoForkAndEip150Blocks_noHardFork() { + GenesisConfig genesisConfig = new GenesisConfig(); + genesisConfig.daoForkBlock = 10; + genesisConfig.eip150Block = 20; + genesisConfig.daoForkSupport = false; + JsonNetConfig config = new JsonNetConfig(genesisConfig); + assertBlockchainConfigExistsAt(config, 0, FrontierConfig.class); + assertBlockchainConfigExistsAt(config, 10, DaoNoHFConfig.class); + assertBlockchainConfigExistsAt(config, 20, Eip150HFConfig.class); + } + + @Test + public void testCreationBasedOnDaoHardFork() { + GenesisConfig genesisConfig = new GenesisConfig(); + genesisConfig.daoForkBlock = 10; + genesisConfig.daoForkSupport = true; + JsonNetConfig config = new JsonNetConfig(genesisConfig); + assertBlockchainConfigExistsAt(config, 0, FrontierConfig.class); + assertBlockchainConfigExistsAt(config, 10, DaoHFConfig.class); + } + + @Test + public void testEip158WithoutEip155CreatesEip160HFConfig() { + GenesisConfig genesisConfig = new GenesisConfig(); + genesisConfig.eip158Block = 10; + + JsonNetConfig config = new JsonNetConfig(genesisConfig); + assertBlockchainConfigExistsAt(config, 10, Eip160HFConfig.class); + } + + @Test + public void testEip155WithoutEip158CreatesEip160HFConfig() { + GenesisConfig genesisConfig = new GenesisConfig(); + genesisConfig.eip155Block = 10; + + JsonNetConfig config = new JsonNetConfig(genesisConfig); + assertBlockchainConfigExistsAt(config, 10, Eip160HFConfig.class); + } + + @Test + public void testChainIdIsCorrectlySetOnEip160HFConfig() { + GenesisConfig genesisConfig = new GenesisConfig(); + genesisConfig.eip155Block = 10; + + JsonNetConfig config = new JsonNetConfig(genesisConfig); + BlockchainConfig eip160 = config.getConfigForBlock(10); + assertEquals("Default chainId must be '1'", new Integer(1), eip160.getChainId()); + + genesisConfig.chainId = 99; + + config = new JsonNetConfig(genesisConfig); + eip160 = config.getConfigForBlock(10); + assertEquals("chainId should be copied from genesis config", new Integer(99), eip160.getChainId()); + } + + @Test + public void testEip155MustMatchEip158IfBothExist() { + GenesisConfig genesisConfig = new GenesisConfig(); + genesisConfig.eip155Block = 10; + genesisConfig.eip158Block = 10; + JsonNetConfig config = new JsonNetConfig(genesisConfig); + assertBlockchainConfigExistsAt(config, 10, Eip160HFConfig.class); + + try { + genesisConfig.eip158Block = 13; + new JsonNetConfig(genesisConfig); + fail("Must fail. EIP155 and EIP158 must have same blocks"); + } catch (RuntimeException e) { + assertEquals("Unable to build config with different blocks for EIP155 (10) and EIP158 (13)", e.getMessage()); + } + } + + @Test + public void testByzantiumBlock() { + GenesisConfig genesisConfig = new GenesisConfig(); + genesisConfig.byzantiumBlock = 50; + + JsonNetConfig config = new JsonNetConfig(genesisConfig); + assertBlockchainConfigExistsAt(config, 50, ByzantiumConfig.class); + + BlockchainConfig eip160 = config.getConfigForBlock(50); + assertEquals("Default chainId must be '1'", new Integer(1), eip160.getChainId()); + + genesisConfig.chainId = 99; + + config = new JsonNetConfig(genesisConfig); + eip160 = config.getConfigForBlock(50); + assertEquals("chainId should be copied from genesis config", new Integer(99), eip160.getChainId()); + } + + private void assertBlockchainConfigExistsAt(BlockchainNetConfig netConfig, long blockNumber, Class configType) { + BlockchainConfig block = netConfig.getConfigForBlock(blockNumber); + if (!configType.isAssignableFrom(block.getClass())) { + fail(block.getClass().getName() + " is not of type " + configType); + } + } +} \ No newline at end of file diff --git a/ethereumj-core/src/test/java/org/ethereum/config/net/TestNetConfigTest.java b/ethereumj-core/src/test/java/org/ethereum/config/net/TestNetConfigTest.java new file mode 100644 index 0000000000..9e45211f6e --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/config/net/TestNetConfigTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) [2017] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + * + * + */ + +package org.ethereum.config.net; + +import org.ethereum.config.BlockchainConfig; +import org.ethereum.config.BlockchainNetConfig; +import org.ethereum.config.blockchain.FrontierConfig; +import org.ethereum.config.blockchain.HomesteadConfig; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +public class TestNetConfigTest { + @Test + public void verifyETCNetConfigConstruction() { + TestNetConfig config = new TestNetConfig(); + + assertBlockchainConfigExistsAt(config, 0, FrontierConfig.class); + assertBlockchainConfigExistsAt(config, 1_150_000, HomesteadConfig.class); + } + + private void assertBlockchainConfigExistsAt(BlockchainNetConfig netConfig, long blockNumber, Class configType) { + BlockchainConfig block = netConfig.getConfigForBlock(blockNumber); + assertTrue(configType.isAssignableFrom(block.getClass())); + } + +} \ No newline at end of file diff --git a/ethereumj-core/src/test/java/org/ethereum/core/BlockTest.java b/ethereumj-core/src/test/java/org/ethereum/core/BlockTest.java index 6e1d357aa1..39ebf45bf1 100644 --- a/ethereumj-core/src/test/java/org/ethereum/core/BlockTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/core/BlockTest.java @@ -201,4 +201,14 @@ public void testZeroPrecedingDifficultyGenesis(){ assertEquals("8028c28b55eab8be08883e921f20d1b6cc9f2aa02cc6cd90cfaa9b0462ff6d3e", root); assertEquals("05b2dc41ade973d26db921052bcdaf54e2e01b308c9e90723b514823a0923592", hash); } + + @Test + public void testMemEstimator() { + Block b = new Block(Hex.decode("f964daf9020da0a7f0248bfbb49ba21302f9a6dbfabda21de4a59d9ac33e832f406501373aa893a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479452bc44d5378309ee2abf1539bf71de1b7d7be3b5a093f9be92b8a699a8ab8f72b8539b307f150e230aab3cf106bc40041cd472a35ea0d13172adf6cab541a651710427c88a92a47f51bb58c8705ca8c2bf6adace555ca003ff1726fd8f2b3b213cf47dafb927253858dfe47bc16060549561f774b8afdeb90100502260304042380280110c240b140002180040200aa4017002998ca01a38a128070a0c04040841494836c0015d8200108b020304810502409a8088000abb8201004000c1044ac093221046ac092c04880600000600080060120a80408a49a40215213020120040022582b98442025a00484802240804006800810219b411201020030028142c00400062002194008442442401c00008813451099620002000120a0051809844320000040250d006c20320a0440d11088000090000000054000201848a0ac08c1011a88092042880a74684a044070008100001815004005860130450690000000005449000800005ca96e0253016025440004200000800030000870a4cc6a8954c6e834e428e837a121d837842ea845a8d16158c6e616e6f706f6f6c2e6f7267a034438c68867b67ef20beef55844105289c21f97f1abd4b0a0f9f49b4f71ad1728896f95450073af434f962c6f8ad8305844a8514f46b04008301a22c944470bb87d77b963a013db939be332f927f2b992e80b844a9059cbb0000000000000000000000009aa154abbeed087fc637b02220f6f9ca1e839afb000000000000000000000000000000000000000000000000000000000009cfb826a0233b1f37a3198f3bedf03c294c41513fe2f755057d50552b8b88cd7b11c19b74a019fe8c087e8261b1258a2a792a450cacedf7d51c72651cf81b1404f4d357be45f86c01850dbcac8e0082520894e46013f9ac84c0c92e597fc77d1b6f5a12d051f58801434c04928f80008026a0b07eb57ffe0f169754266958fe9016ef4b72337f03b95dd8736501963da68220a01e384c1a34b17ab42ef0f0aa3e54834b41561f4b09355c8fad8e13a3ed6a1155f8708304f6b9850ba43b7400830668a094b3408f57077bb2dde0c5515af76bdc33878c975c880ff59ee833b300008025a01ce0f9e7e30e24a883512df526724d7ab87332b554b5f4f609862b234b327b7ca003f5c63bc8e64c13ec4483769425fade4a353b76d9a6c40fd91eb3b2b270bb4af86c04850ba43b740082520894236f9f97e0e62388479bf9e5ba4889e46b0273c3880eab03262a30e0008025a07b859da40ce711bf005d028dc3e642f02f1b8a840ece4779461434060a1bea13a062e59be0a3f252358bce0f8f667c17c3ebacb0a91f9f31ace0cef25f429ad77ef8653285098bca5a0083014c089412fb5d5802c3b284761d76c3e723ea913877afba808026a0e5e1b99e0028be6f807f8eeba50dc2a88d9ae03b2c6e90877a69f7e54c66a3d7a0561055a4cbcdc9d7ffa9b366da54dc942164c2029e59bc416b75e50f12a98f71f86d0485098bca5a008303d0909456eb83949fdf543e8cee4569739d21f07e4eafa9880326774c1c0380008025a0ca5fe5e0ff90124a77e0f4516cb9c46ee80633787c06079bd4d3211fb0e3d330a05ae9853ff257d8bfa6d5f54f5d297588d2acf3174d37de44c044f21edfcf6c35f8a92585098bca5a0082ca609478b039921e84e726eb72e7b1212bb35504c645ca80b844a9059cbb0000000000000000000000009c3e7e5a0e072eb394d75c0d18e10cec5851117600000000000000000000000000000000000000000000000b2ad30490b278000026a09c12914b07c23f1694b9c835e4bcccda9dda0a49377bc8690df1ee199f1944dba037f30add470ca323a924ef085778dcd38ae37e3f86f067fe971e40d4a16f04bef8a91685098bca5a0082cc9594dd974d5c2e2928dea5f71b9825b8b646686bd20080b844a9059cbb0000000000000000000000008401e4983ca4a4a1f1cddeb89c4085b69169e6c9000000000000000000000000000000000000000000000002ce1b2b57de96800026a05e57d944a3049060276c562f8cf704dee9aa2b91da007717e848cc0f012de8aaa007a8fbd6ab346e6478b43dfd573acdf9c166b1f914c81614757a8cb4630b54f8f870830df28e8509502f9000830186a094933bc4e63edfe770f962459b66da5388a32263c2880531d5b3461364008026a04db7659cf5e3db86701be5d204d4229c232d8bc34bd6fe60c282b3e1a670df22a013b5069962e32c568e602571434db0d85b77a44697355d8af54fcb0a81b3ffbdf8718307288d8509502f900083015f9094009c40e9589129974a515ea90c2ab414ccde79ca89056ba3d73af34f00008026a0e75c1b66ca1dfd848668959e4cac392b0c1f35870dbeb2cb6996eedc2f19a898a072764cfb083400808d0d8fbdf5277bb5c5ae42e7d757b0e7e8712ccc6694c11ef871158509502f90008303d090942a0c0dbecc7e4d658f48e01e3fa353f44050c208880f9db9ca32d7600084d0e30db01ca05c73421d0c3bbfc765cad8fb8ac759d5eb8107ef1919d85824eddbd7f02ecea7a077dd3b92923663c8030d0a9eb1c540bad33b9f13c857a801d718b12d5d821650f86b823548850737be76008303d09094c7f7ec818f65459c3a692065fc5f1c52129c893480849ac8441425a0d593908326699956dc84bd35aaa6e86cd4a8b824ece525d817048ff5e4d31ccca0603ca0397148bcbd236cfc0251b3dba50400ae0050d7b050960503a51668caf3f8aa808506fc23ac008301d4c09486fa049857e0209aa7d9e616f7eb3b3b78ecfdb080b844a9059cbb000000000000000000000000692da4782d996dac7d66b5822f3c504f67da849300000000000000000000000000000000000000000000001dd0ed9ba758e9e4001ba0ce3a7a5d0ef258a8518b7cfa4f505415b47d9f311577599235e0a40d243964d1a02ccb5aa80f3fb93f551759237f139c6a77668594d386c52260b54a64ecd700aff86b018506fc23ac0082520894e65a88f487f5d26469cfd37ce7ef763d6d9be45487304a920b74dc00801ba0ca7aa102e2c1edf6c12491f7eeca2763135b43e4efb280566d7fd381cb2a31fda036624504908c0545ef8e9f17ede76b4e41a54a55112ace60528329566ee28880f8708301098f850684ee180083015f90945ca08c42f0dfd09cba09fd94320bbdcf4d50586c881b9de674df0700008026a0707232fb5b62a713210b5829ba560f516c130ed48710504fdca6b68ea64f4183a05a0100cc3b8cc5d07324be52d47172b5ec146dc86ac6f7784bad2ccce8cfccc7f86c07850684ee18008252089486e6df2933f88a00caa11ad19413123c3abef3de881adb9d6629d7e6008026a081f6c734fb3523566f78adb1569c95bef37e2e659335c723c35259812ca0a32ca078dd5577804b470bc295b854155c3e2c156711d11bede02e6223cc6455966345f8a90e850668fd895082f56b945ca9a71b1d01849c0a95490cc00559717fcf0d1d80b844a9059cbb000000000000000000000000e7bbdd9e22e4763272e417cc247219b72422a04e0000000000000000000000000000000000000000000001c9966829aa5044000026a0853b5c66e2a77a074ca76ac16f092a0e2f9cf4c4bb29deb126be7deb6df415f9a015491c274e59b9cc4f8fb909a58ef086bf6879ef71256a5d4df488beef46d72af86c0a85055ae8260082520894053b270864d6a858c809a68c0b00280dff2bc7eb880118c146a62a46f4801ca0afc989860bf3301f538690dc138533eebd3d239f7fb75cd6f7773323af2b543ea02673f53178299e28eb1c5a8be2cfe951121439b272f2622cdb027481a5e1d332f9016e8304ea1685051f4d5c008303f7a0942a0c0dbecc7e4d658f48e01e3fa353f44050c20880b901042295115b000000000000000000000000aa7a9ca87d3694b5755f213b5d04094b8d0f0a6f00000000000000000000000000000000000000000000012976ee0708e4c8241c000000000000000000000000a75fcdf436c9713b3757819f1fb235918749c3ba000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000001c43dc24648536c130879e8927f3adb53c3c9a01cee9379fa14b004846385265306c9fc438db8b8e1e72441e7fa5c463e3eb62c9f5c0457d339961e52309e448650000000000000000000000000000000000000000000000000007cb57bdce51231ba03f4b39ae10286e5b01bc0f6e0eb29498498640327980932cc2e0767c7acf1ae3a04d3ce562a78eeb60ce835a4ddc6228ee104d3edaf9ffc4678586488c888f211cf86d5c8504e3b2920082520894737d60f2490210da0f6ce401e2dadff995ca80058902b5e3af16b18800008026a0bfc3f176f41c6e53135a66b13113b8fcc3e12be96354809475fc719bf4a8d990a07e34b40c5c24a02d7e1e26a43981e30d0a94bc5eb3518268dd4cfe783931bad1f864048504e3b2920082cbd89445555629aabfea138ead1c1e5f2ac3cce2add830808026a029856a81c5fc062e8e2835836c3c57511ec5d7a9869e4a827fe1e1d0392b3f90a02aa4333475aff5acee9deaf748bfee321ecb664da8a9070f56d4162a021adfc1f86c4c8504a817c80082a410943f5ce5fbfe3e9af3971dd833d26ba9b5c936f0be88456213856625c0008026a0bbc22c5a7c90b7a9069d1cb7d9d033013ae28ccb0b60666ded5eefe11d00ff21a07f528d89ca785104becf15f59ff48ee048df35dff3860c05e89156e1b45b9b58f86c048504a817c8008255f09415e310ebf90b07bc44817c4f6c312e0220b81e4e880f8b0a10e4700000001ca04ff7de42037f13e0345617295dcba17c765d5725680414b035756c4ece6a2b00a03bbbcef9953a172e9f3837b3c8052ea3a34726e3a4db17d3e95b9367c6814066f8ac8205888504a817c800830f42409464cdf819d3e75ac8ec217b3496d7ce167be42e8080b844a9059cbb000000000000000000000000ee13c3012681e2d758ea6d098b73fbad1b1ae850000000000000000000000000000000000000000000000292993e7bd6dcb4000026a0e7b18eb3f25ff7ac57ea90cbc32dff69fb93c086ab2d8118b16efa3129baa3afa00f4e426723dc595bbcbfbec3236af2b6ecda9ba1b275b2186fa97f7f8e6d9b87f86c078504a817c8008252089405ee546c1a62f90d7acbffd6d846c9c54c7cf94c881d178d98cc2cf8e08025a0a5b3a3335f8df2d6171c0e559ca44644d64cd8a0073d5d5a580caa0ba8be4f1ca06de00817dff5ce59bcbe3fe34963367ef3192f3285bc975208c13f590627fb2ff8ad8307ddee8504a817c80083030d4094cb97e65f07da24d46bcdd078ebebd7c6e6e3d75080b844a9059cbb0000000000000000000000002571586392956e467b4124def92cf3da22a3096500000000000000000000000000000000000000000000000000000072dbf46a2025a0b3d069846501c4a9d2f6724c41041b5d98fa2fe2edc615145fcfbd5c54605b0fa016ad5eed174c506a1d1c05c4ac168de99459c8febe3477fd3cb47f122c4df1b6f86f8301f6c485045d964b8082c3509437632b659660988d99acb59575880410e2f43dc6881efc523ffa056c008025a085d1e9e7831a902cecbd73820a705e0058f8700e326056de9c58952baa880d73a05326a3d89da4ed1e0a775bcd4830517052de8b10451c3d8bd59d3e0a216b3da9f8ac8207c58503f5476a00831e848094d780ae2bf04cd96e577d3d014762f831d97129d080b844a9059cbb000000000000000000000000fab4b2081dac9fc307dd4eaefe9e92268562a658000000000000000000000000000000000000000000000001ec93f1f8126800001ba0dc8b5bb15c0d6e84ccb4dbbaa690e6199e35260bada02147b77a023e9abe9caea03d736e049525904f3e7bed060a4990a6bf28875f7d27e51c5c33547bac8bb41ef8ac821b218502cb417800831e84809490335e6f8cf5b4b3cc28217b6b2ece290439e49280b844a9059cbb000000000000000000000000ba702b32e37d3537209babad75ee7d318c7e7769000000000000000000000000000000000000000000000006fe3c10875964000026a0f55ec9369d983969c6989184720db22f2a265f2fceff7f774947b0db3bf9d22fa0617af4f0a210df4a599cd5149dfb1d34b5848a5ce6b5b4e1a07d6197f94b900df9033083059cee8502540be400830e57e08080b902da6060604052341561000f57600080fd5b5b60008054600160a060020a03191633600160a060020a03161790555b5b61029e8061003c6000396000f300606060405236156100465763ffffffff60e060020a600035041663395ede4d811461004a57806383197ef01461006b5780638da5cb5b14610080578063e5225381146100af575b5b5b005b341561005557600080fd5b610046600160a060020a03600435166100c4565b005b341561007657600080fd5b6100466101df565b005b341561008b57600080fd5b61009361020b565b604051600160a060020a03909116815260200160405180910390f35b34156100ba57600080fd5b61004661021a565b005b60008054819033600160a060020a039081169116146100e257600080fd5b82915081600160a060020a03166370a082313060006040516020015260405160e060020a63ffffffff8416028152600160a060020a039091166004820152602401602060405180830381600087803b151561013c57600080fd5b6102c65a03f1151561014d57600080fd5b505050604051805160008054919350600160a060020a03808616935063a9059cbb92169084906040516020015260405160e060020a63ffffffff8516028152600160a060020a0390921660048301526024820152604401602060405180830381600087803b15156101bd57600080fd5b6102c65a03f115156101ce57600080fd5b505050604051805150505b5b505050565b60005433600160a060020a039081169116146101fa57600080fd5b600054600160a060020a0316ff5b5b565b600054600160a060020a031681565b60005433600160a060020a0390811691161461023557600080fd5b600054600160a060020a039081169030163180156108fc0290604051600060405180830381858888f19350505050151561020857600080fd5b5b5b5600a165627a7a7230582046378ee80aabd231215e1636373ac5eccd4f45b88b2a03d6fc70c9671e4802a0002926a012ebb3326445e6342f20a9a2e8e44d0b4430feacafca98bb5e46944a1dd8c309a07a0dd3a18c9d0cc1927c7d4757c0522567b7af9e2432714b548bee948fe948c7f8ac8205158502540be400830e57e0942c974b2d0ba1716e644c1fc59982a89ddd2ff72480b844a9059cbb0000000000000000000000005cea26d5c431e6d2c88b32f7b5bf48ccbe4cee20000000000000000000000000000000000000000000000007e651f8215e16a00026a08b2d5a76c394bb10c542bf009724107d0b32f83f5647f030e7664bdb97ab9d88a03473c68cdd3d4d63b9336c49a417894d8e0a69d3959c28062fde00f89d21c7e2f86d168502540be40082627094db5b2ab29b624ac3644e437393ffecf1ff5818028901e5b8fa8fe2ac00008026a0a0c207b23d0990615901a3f4bbc20550b6809b73dbc6f28ec9c63b62f7224df7a05ab4f627471594013be68b19a1ea5956d797571dc6bb2fbb84b01643e0422e0bf86d808502540be40083015f909495153c5e1d9e1c6754e95e92d164373020088d278802f93026b03590008026a08779b66ed23d164aca7765af843a8bc12df59cb83c24795430aca993bc096741a008dd09661fa350e68213b061d88d7fe057a142edb71222a8cd00058dfc02b47ef86d8202848502540be40082520894fa3d425d4e09ec8431f881593f45fccdbcfa4488870bfbd981743d478025a023d60fc1e19fbb54d19a85daa626ca48d3994822f0830606ffac7db72baac9f5a0107a6c1e4cf9a909f8f87b57985d219ea9d8ce7f0ec382c989e2f176e4a600a6f8ac8261538502540be400830186a09493e682107d1e9defb0b5ee701c71707a4b2e46bc80b844a9059cbb000000000000000000000000860c7f15df4165a23fbd10587f30bb5c850bf796000000000000000000000000000000000000000000000000000000009b9c45f025a0c1e5c33fa4cf45c519dd044e0d9b525130201352f04af4da9edf0da71f99312fa04fcc6f9de62b6e009a49bc75e715dd217bcc5ff4a54e0405948b0565d9f2618ff865808502540be400830188eb941530df3e1c69501d4ecb7e58eb045b90de158873808026a0d00295afd845cd81c1259e2e17b5c8d0d2e1e9ae8eb6738f42096aecc89d9aeea064f6cfebdce252978736eebad6ed3b371416b662f6229e25f886c0fcd8bdeb12f8a9018502540be40082ea60941530df3e1c69501d4ecb7e58eb045b90de15887380b844a9059cbb00000000000000000000000004923c5c744653c78a41a26c443b1e78b9c4961200000000000000000000000000000000000000000000000000000036a7b76cd025a0b0a643ed5ab4138344c4177dd946d50cc060d4a3cfedcfd8642c46197cbbae83a07e93439db7761c5863cede3beffc2e2649e64bc6b3631c0aa61f05e0d98a01a5f86d808502540be40083015f90942c71e8491862e24bf5217673aea600468301245b88031156963dd028008025a0ded7d8edd2562f3165d845df049dffad61e27318a264623522aa6e7e448b1b32a00d76d2248ddc97a4b18874f444c0a34b83c2259ed7ae317a3e3071baf92d51d2f86c808502540be40083015f909416bb92ea0de563a97d2a9252dda4764d06c3e89b870c04c66d0afc008025a0f08ca8cfc85c5bb19cf9765de3e38d154e0c8d63629d58b481556a7dda0bb971a057d9e7f09903922727df0e13fc22889710f3dc7eeb991ec2a3b90596f436d1e3f8a9028502540be40082ea6094b98660a7784c494f34ec6ee343499e2d2c47b12880b844a9059cbb0000000000000000000000005296eeabd756f156eecb68a6a87e046be56f91cf00000000000000000000000000000000000000000000001e162c177be5cc000026a02b846ffc5dedfb0d60b177e5b310deade11aaed4d463884764726d2a52613168a06bc4381887b0dcc86de234c12ceaa248d231a618f02445adb3812a4cc7b4c807f86e822d348502540be40082520894f37910f86d5a1fbd3de7f7d6089c54c0390f8b628804a03d81c58f60008025a0ee4e35bc83212404e299ab15919858db1bcbc7ddf2d6a3753e2f701062ace231a02d979b9de92aa251642c300a0ef7dd78a5803a355b7f92efab8628812cd29ed0f86d808502540be40083015f9094bea97ea3ff568c553cdd4fe98eaf5a08e4fc484088119fa980a91e1c008026a00ccf7a44ff4aed3052f8067bce0abe5be8a340ef81f52ff6fed78802175243eaa051e39021e9cc5e5d8edfdd0bc77f03b74fdedf1c811d5b044d3faf9b6f14ece5f86d81938502540be400825208944fe6fc250e21e29820462c856891d5cbb1a5984c880e92596fd62900008025a0450e58ff000fd6993db264e38921b2d89d0a8f12162b6b74471a857eeb152ce1a076a046d293bfaa1e6d525154ab43dd0f3a64aef9ce1b8f779c96223436248626f86b098502540be40082520894be7bac1c616df2bfec24dd1e083c47cc03ab319887038d7ea4c680008026a037f0f9248fbfb2f9977f1ad7f2794c13670e6c68892f20175b11292b82a2e471a048a886b22bbabe8f8aee9d72b4d6775a149d7b6c8cde5def4f5448b3a36f7c18f86b808502540be400825208943b61cd9539c7adb09029590eb2134f50264c0fee87030f132f26d0008025a03f3b29668d917062255742627381a3be53789c93b82a0b1787dfda49c1ce0e7fa06be0bd05ff3e09bb86453f20c814088ec00af3c7445c4d756a082a3bb4eccdf5f86d73850218711a008301117094137822b91c6451762e036c529c33c64e94e9c625884563918244f400008026a01d1c69c6f1ab3a13d4c31d380c104c5f265878adaf0ad9c19b53682924941adba064a85d344d9c6c3aada6b69c60eb4004d8a6892f5adfdfffe389c69879b97459f8ab820a1b8501dcd6500082ca8f946f259637dcd74c767781e37bc6133cd6a68aa16180b844a9059cbb0000000000000000000000002ee8c7ace08ba528c0a17426d5b32af7bb59efdf0000000000000000000000000000000000000000000000000de0b6b3a764000025a0825d5a8b86f3c83cd075318c8e09eb560e8fee2c9ed2c17c2e11550855f38bafa070b8e4432c714377df5bc1779d12c019203456c8066a2b0fac72f8a495963becf865178501dcd6500083011ea29445555629aabfea138ead1c1e5f2ac3cce2add830808025a05f91c347847d091eb093680a922170cc5594fe1572c60fd313d4310a7b3b2234a07d961331105de03d1320f9bc06dd4781bceec8d12e77fa2b3155776d8f6304fff8a9018501dcd6500082ea60941530df3e1c69501d4ecb7e58eb045b90de15887380b844a9059cbb000000000000000000000000f9ac17f4eda48b0ac0bf7baf2d09eaff37a62ed6000000000000000000000000000000000000000000000000000000000000000026a065bf2cfee714276b351ee713cfd119d6235b771a4e10ff97990f8fe54ed9d7f2a056b36888c9f37e63df49c06290937047c5a6526de637f4df5eb23cad53475262f865808501dcd65000830188eb941530df3e1c69501d4ecb7e58eb045b90de158873808025a0767806eb1a58d2bc390531310376628b533d156c19c89791d5e8a387b5cebc8aa071fd413b5052ef60ece9d474d0ba88f9d94d5b2ae47ad6d0d638b83a38353aecf865808501dcd6500083015422949c821d1eed859080c307e326fcc888c230d2bf86808026a0df03333e11fee3c55609648b6a7bc2507b33678938f23b87d278435666c9d586a010fd91a15a0512c05f34c24399445d0e60af1bf581d8a31b64b7ff986dccbec5f86b188501dcd6500082627094878d7954c16f6f0aafdda7aa5dbd825cb50281ba8725449973b1a0008026a026ffa3b09d46f37987debcb5ec81d27e317735c28d0bf1774bc4da33e20e7642a014fcedf54797ed1051afaa488cce6b0295703bf158585604953dfe63cfbd4119f86b378501dcd650008262709480f8662b859e2ad3b5e7feaaca716ab3ecec800a87044364c5bb00008026a0bb20d28877d03f4f623d09fcc7d4ddb2e522e5670bb70300ce857140e275351da06c298ae62ba525c4864472dfa0cb005ee7f408056facfffcfee53624a839678bf86d82041f8501dcd65000826270946e92d5b6f866986bb38ae1d6d8d749c437c158b08703e871b540c0008025a0f8381ce353bc914caf0aa8b8443d09bf5873510d90d0f3f7b6a5eefe7f5629eda01bc402fdf8e667e91fdadfd8470e1f83acdecfe7c3732ce59bb15ef6642aae7ff86b498501dcd6500082627094defffb1eed8571ffc601e7114f626bb1ce8e01b987044364c5bb00008026a04994d82ae686511bde97390411ad41aaa431da7b9010b88897bb31ec8cd4b9aca07b426f7d9dfa2e67a96c48ab9975715ccc9a09fe230ebb5d444a5f307268a45bf865808501dcd65000830188eb941530df3e1c69501d4ecb7e58eb045b90de158873808026a07d2577aadd023057ef2ec01b997390c3fa2dc22429d1903f13b700347024c32ca06f37824c896fab4ae721e87d85735ee0b673f361e86468cb778cc547af2b003bf86b058501dcd6500082627094d550552430a5a9c2c7e9dd3c2f042b72e0181cb487044364c5bb00008025a07841ed68678aabd69c13ca18d9d2fd7fd15f8a24a81ea1019c9b3f7db1d3c44ca014e076bc30b3e67a702d2a707b4816da89654a8ea48f58c326bc896e2c5608d3f865018501dcd6500083011ea29445555629aabfea138ead1c1e5f2ac3cce2add830808026a03a78781565c6deb9db3256151c0d4ee6fd9e9c6167738b77c4ac5054b65bc26ca0724cedc0d77ba1971c77b096131f79127bf5b17d77c329493bbce342049bd190f865808501dcd65000830188eb941530df3e1c69501d4ecb7e58eb045b90de158873808025a022a0e74b66fe5435d6863f4f54e3e818ccb6a1fad444d6547434189ac93a76bfa010223854280edca930e916ac8a5063a3eddf9fa827426535e2da306eddfc139ef865018501dcd6500083011ea29445555629aabfea138ead1c1e5f2ac3cce2add830808026a08658358028c1daa5af9ccc69ddb8907eaf141f1cf85af74bfc335c50e46fdff0a0615fa95dda08f087d2e0be3696c4add62fbc3c601b3aeb480a0418f6842eac98f8a9018501dcd6500082ea60941530df3e1c69501d4ecb7e58eb045b90de15887380b844a9059cbb00000000000000000000000085b1af16abf053364284207a8b4180b9bcce54850000000000000000000000000000000000000000000000000000002e24ccfef026a0931463dc408eed8a294e3a9cc5b4e07c07b595e9c1ef9741e203929e72fd2672a04be66179d4fd07b8265d03085885c541ee546e0c8cdba030d34f85958813fe28f86b028501dcd6500082627094131f4d14341e7ac99ee99af4d0af03ae6b1e11a48729ec09985980008025a0ad4da4ad9f2f3f50dac6fbab36198f3e77aaa44c5089829dcc2ad63067377fdba0050fb9099ff87702520e9f40e39825ca9af42db837988ac301bc6a5f04df1d80f9010a3a8501dcd65000832dc6c09456cd53067e5acd557dbbfb4c66580dfb9722962a80b8a453047154000000000000000000000000eec23ec894f2fe3879472861da6064e4a4dd715a0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000016345785d8a000000000000000000000000000000000000000000000000000000000000000000094a6f686e205769636b000000000000000000000000000000000000000000000025a01c4c38cc4bd08b460cffdf0c77218d10ef90e7eceb0456c6d1d18dbcb6dda981a05e0d3f06396e2c89ae000e8ba228774054888a52aa3b65e4b45a83c7b052b7e5f865808501dcd65000830188eb941530df3e1c69501d4ecb7e58eb045b90de158873808026a0848b074de4ffcbea27e2ea29632a5188bb4a9475928b184a509b5f7f32d7e336a0239b7abe6e53cce99b6ecb5d2e2fb179fac53b9750158b563895414a3139db37f86a058501dcd65000826270948d94432f09d47b07f16409dcdc89668d06c80f3c865af3107a40008025a08db32e28dfb01e7f4b78d7e97ade00db194dad14d992854fe2873a5a074b2160a010408bba59e242162a276945185f0cfb2e2e9679a7f29493399df8eea3b86419f86c058501dcd65000826270943c3c3ca45ad57b24b64f72d570f1bd0e46424ee0881bc16d674ec800008025a03914b8eb0a53afbbc1c4f52c5d8d902a83025818b3149fda8460b9fcc5eedd24a05d6acf8b6f45aa723d1bdcc6f8fde115bc4d4ccff0e1d1e0ae43d368df8e94d9f865018501dcd65000830188ba94041fe8df8b4aaa868941eb877952f17babe57da5808026a0fb92d03e8f6c565e099cf9f8ea9b425ed30b800c8c98a60a75dc21e017e85f51a01602956fb3dfe04def18e8ac26cd538aa2fabd1581accf17e7c4685899ce8f4cf8a9048501dcd6500082ea6094041fe8df8b4aaa868941eb877952f17babe57da580b844a9059cbb0000000000000000000000001ecbbf170219e1a6855d0529850b5e518c78e2380000000000000000000000000000000000000000000000000000003a184861e026a01a69934d2d8466e196f8a5e366d444d8729730f57e426d7f8347b6ad46e4abcca01ff262d01953845c12f89d7ac1b12b29241b18d81e6d0c75e5eb68b98621a4a0f8a9018501dcd6500082ea60941530df3e1c69501d4ecb7e58eb045b90de15887380b844a9059cbb0000000000000000000000006943adb55f9849366cc51741b1535d596c91784e0000000000000000000000000000000000000000000000000000002e1e3e877025a02dbd07ad96b2b02d7c73973a2ec45dcea415d01155c54d1e490fb70d13e0e3b7a06c93882133cbccd591ed06376f4f8dacaa478f884d243ae26bca23779f1d4915f86b028501dcd65000826270947d2337f89b641d9c5ef8eb9ce686750f3a20dd64870d8014722580008026a07a0a88e6bef92235d56c733fe9c7b497212e6dd37bc299d52d13d28c62fcc0c8a03467a9dea1c4f482d047f9ca5f3531e41fbe8e51f083168761a996bf0f8ebe6cf8a9808501dcd6500082ea609420e94867794dba030ee287f1406e100d03c84cd380b844a9059cbb00000000000000000000000061652ac9f52bad1b9d2b9833d54d88182a5073530000000000000000000000000000000000000000000000821ab0d4414980000025a03e55b84b41093367bb8bf03590a58365b8340c1e9fa8e5bc20848a362fdd1dcba03697e776bfaac2d870ebca903daaccff004f4692226d9c54d368222e126fac94f8680d8501dcd650008301a1bf943618516f45cd3c913f81f9987af41077932bc40d808439055172259f8fdb3b77253d6f7cf3f36ee0ead450c39aa3044b37e01539b6efa13f6aee82a05fc31294d0bcff616477caa91faa54ec9538a0d50eafa95dae08a4c62acd4811f865038501dcd65000830188ba94041fe8df8b4aaa868941eb877952f17babe57da5808026a0e6e1a06e4bb30fe466bc7c5ed1470d6093c9cd18a4f1b0f17e4939fd7079e734a07673bb62b3129caee22a8fb4e26dd7f727061a6ac845833ccbb23add328f9c83f865018501dcd65000830188ba94041fe8df8b4aaa868941eb877952f17babe57da5808025a0f17d8db4cb69da7e00a5c8c543b3b403602a9b78f458615cfbead6317360340da0793929655871a0d81d5f64151526deb1b769970160f9554457652baaa589967bf8678201448501dcd65000830249f09445555629aabfea138ead1c1e5f2ac3cce2add830808026a0ab499b2473b857f9a40f4ec90bc5c04b16825e7a2d92ee445534ff4eeabd3e0ba060c7bcf537b0d55c14e968449a3eb938f871dba0a78e4fcb1dce91ba09205334f8a9018501dcd6500082ea60941530df3e1c69501d4ecb7e58eb045b90de15887380b844a9059cbb000000000000000000000000297e4a2a65b53d57659b250ae2fef7b57851f5d00000000000000000000000000000000000000000000000000000002e211af37026a03060b1f66c92579a8abbbc9fd84531586909c6666e385479726fb16e88348f72a069ed6630d96e1e9bd133ccc3929e1471e60375b1ca9b27fc30da44e4906a6063f865048501dcd65000830188ba94041fe8df8b4aaa868941eb877952f17babe57da5808025a0c04ff31100078fea393437bc326421ca10b85c3a3712d15c4e4fafb278ac7107a049d1f082e0d3627e56630657f0dd2bbbe5a30ae6e05d441b0f732445966bb430f865808501dcd65000830188eb941530df3e1c69501d4ecb7e58eb045b90de158873808026a0b18e5ab1569e09a1b51e0615d98fc67ed475859fa7e37808d34963e316fa581ca0644d546af52528fa6cca893895a059a2c41829afc6590c83aaa30b18f84bcf63f8aa078501dcd650008303d0909470a72833d6bf7f508c8224ce59ea1ef3d0ea3a3880b844095ea7b30000000000000000000000008d12a197cb00d4747a1fe03395095ce2a5cc681900000000000000000000000000000000000000000000003102eb135ac856b8001ba0824921bdf2549600ca3e4926b6b6118ec838e80e61d25f01d606192c0cbcdd97a04c1531ba1db290020244d1f0efd026c401b468eacbee78565d270129614df997f865808501dcd6500083015422949c821d1eed859080c307e326fcc888c230d2bf86808025a014eda6b30d89a40fe9cacd1f3f0bc18ca50428dddb974e9b5dc1619e86efbd84a04fe40e4abdbe8a97e08a52a2dc11c985889ad97e89d1b2154ddfc2075a4be5c4f8a9058501dcd6500082ea6094041fe8df8b4aaa868941eb877952f17babe57da580b844a9059cbb0000000000000000000000005ffd96366c0d3ea6d8fa2f91223d65d52cedbd710000000000000000000000000000000000000000000000000000003a243423e025a081715992bf76a845033dd4df3ba659e714a6335f217738027bb6142cddf33c32a006196b965820971a50d35065a27ea3f37799f1b0f6196f14d44a5d0961b1579bf8a9018501dcd6500082ea60941530df3e1c69501d4ecb7e58eb045b90de15887380b844a9059cbb000000000000000000000000285049ff70c9acd36b0829b1364762e25df830120000000000000000000000000000000000000000000000000000002e1d4bc30025a0054c26bdd55b89cae30fdae5446bb6b479ea3b4c3e89376940b39dcea1450f05a03b24ce62aeb16c20b1f4eb35d04b6caedeaf3442c6939e91687e000b1fce1f5af86c81c58501dcd650008262709420021f82ebe035557eb44b42a4ed201cfa046d3b870775f05a0740008026a0b828a3b0c3f6fbe26f53f7484994dfc32f2a46db65d45f2f3e18e3689d067646a075218ac434f9b3df83ec4ab75f4a13cac02b8f3482e970c7e00dac765c8e43f7f865048501dcd65000830188eb941530df3e1c69501d4ecb7e58eb045b90de158873808025a0d73c2c97b88a1214bfa6b94997d019de6e6509db165523f11da5d5a85ddb0453a0448dd5e8509b51effe195c4ac7a932c48b8b2afcfaf1ca9904418184da41cabff8a9018501dcd6500082ea60941530df3e1c69501d4ecb7e58eb045b90de15887380b844a9059cbb00000000000000000000000094e6dffb38cade4f8946176479b32c9444969ebf0000000000000000000000000000000000000000000000000000002e28f91c7025a03d34dda851947973c1fcaadf818f2c564ec442471ef5847b9a8112f2eca53543a06da49c7d9e58546244449a88854070c33b02f47991cc3b38600a35f9a4f09b1bf865808501dcd65000830188eb941530df3e1c69501d4ecb7e58eb045b90de158873808025a0d41c9093a124f7d43b98e63b57da3319f0b3684c7a4ef477f13b95d21a99c9fca016698d94f95f351d985746134b54299b568bc1ee9485bc417bcf0ae1f3027874f865808501dcd65000830188eb941530df3e1c69501d4ecb7e58eb045b90de158873808026a056670ea85d93fc362d2d97817976a4c09ebf45df30fa408994805fd7f3364311a0325607212adaa85e51d0a525b637339a7155279646c2c455367508f302a1eeeaf865808501dcd65000830188eb941530df3e1c69501d4ecb7e58eb045b90de158873808026a084c6ca7c20efd06f116e4195e57773fa95254ba65d8db3d940b6cdb04c32fb98a03d62df0104c0be2aab0eaadafc0a15bec508ab4471ec607734338a0665a92ffdf86b028501dcd6500082627094e8a92b7f5b1ca356e42af878ba0a3804f30da5fc87c85667fee260008026a0cc179d1682f27ba3462fc467c3aae7d84f414dc933d04dddde24fc6f164ea17aa06620fa60bf3645f85c6f8338751d71cc093a69bbb88bffe60bfa18798d4fdd4bf8a9048501dcd6500082ea6094041fe8df8b4aaa868941eb877952f17babe57da580b844a9059cbb000000000000000000000000a26606347b1ca55695781fdf9bb97b1bd5e7f7a60000000000000000000000000000000000000000000000000000003a20c6c28025a030fd5263601d0614e11f3368f0f53b5c3b17fa3cda2ae3140792d45675d5bfeda01a129961517a13116fb249d0e0d2108bdac5e955bb84fddd939cafea1e34226df865258501dcd650008301adb09445555629aabfea138ead1c1e5f2ac3cce2add830808026a0adfd88dfbbae0604764fb248362dda15ff70251b2e9dc196953fcbc9a2a6aacaa06f253e0b0438c495571e02b315cbebf18c2b654aa63fab0942a82640a1009226f864028501dcd6500082627094275b69aa7c8c1d648a0557656bce1c286e69a29d808026a0ce76e53b280e96e399a694251b01cafed48890a22099239f75e9724c934af85fa043f2a2c351003aec48a6a80786a5d955be65ebbd5e965cfcb90e72b2c321cffbf865038501dcd65000830188eb941530df3e1c69501d4ecb7e58eb045b90de158873808026a0477492d12369fbb1d979b20ee4ec6b65b2bc609dca95bf40f091d77d63d795e2a05f6c9cf205a20a1d3e8a67f05f0a7ad04f37d4cfe69292cdb475213a3ac166d1f865018501dcd6500083011ea29445555629aabfea138ead1c1e5f2ac3cce2add830808025a08e35620e56bd5246b14d30955a30d60284ccf52ab3bdcb2595a14e344317417ca017f78d258350cbff103d92053f932c432af31e03748a11646ded8b2d13cfe95cf86c0a8501dcd6500082627094e9dd33253476a52f68036b03c5fe19465999d639880de0b6b3a76400008026a0e480e36da4c11371298833fd2edab0c4ee2aa8acc6bdd14f9daf0d211e24a5a7a04ef1d2f6e98ccc0935bf190aec550cc63af5162ceb9f869d8876181ff0e2b456f86b058501dcd6500082627094a3543a59ef459d512ee17c663024f3f89dffbcac87470de4df8200008025a0ccc009c9e617851612cd4ede7a5012c3163b6961815ec24a3d2edc76553725dca04296d1f5c92f78fdc27429c6fcae5e8d884d714c83067f2b71ae863a29bf371cf86b2a8501dcd6500082627094480ae00f320a90b7f0e79521cc4e8bf5527ded7787044364c5bb00008025a06e4630fa99b13df833b075ee4ceebe287bc1cdd3d95357053eeba503655337dea03b4bc3e6fe72d77f6dd2400e24945ffc24017ba362785511986733e2b736d5f9f865218501dcd65000830188ba94041fe8df8b4aaa868941eb877952f17babe57da5808025a0ee20d7430fd44bfd6edebcc005cfdb157edeb435dbce04c0f3978721c1eb450ba007637fa44c4925203536b02f46df354f29c56d92015d920b9d8e97bfb2f5bd93f865038501dcd65000830188eb94f7dca20054469a1d548b20859a100b9ec6ff1f61808025a062a7a1611b105ed26090edf659fa1784fd04da136b54d55f34abf4fd1547903ba06443c369a2ada0edfcb706d2e672867e418eeaebbbb2524ef0eed4903c2c1f9df86a028501dcd650008262709471a1a75889278820ea73ac45930ae4ebe5013d4b8612309ce540008026a0625d20ae39aea4cc31665511d5423b7ed6692465307e18b19a625b6d9dd70518a01905fdf82657f8c9ae9e986e0d1695dd691e08993079141015f90bee5d9953a0f8a9048501dcd6500082ea60941530df3e1c69501d4ecb7e58eb045b90de15887380b844a9059cbb000000000000000000000000c328f30cad4f14fd2e80cb7fabb444ab36521d9c0000000000000000000000000000000000000000000000000000002e5b47606026a02090b02053fecb987465a656106817f76e3989bf3bc90882e57a9ae885247f38a07d5ca3fbf0a9451e12c70f14cd9de45a3c327a5bf176b2a297311463f2d416a7f8a9018501a13b8600828dd5943a26746ddb79b1b8e4450e3f4ffe3285a307387e80b844a9059cbb0000000000000000000000002b6401fbf71effb2edc0d419271b7e3466130d01000000000000000000000000000000000000000000000000000000009502f90025a0cadba085ea9d933fba063d0bccc0f8d4a041cb4cab0aed0a8382dcb1adea4753a051ac59ec94067c37b4f997163c95af8c986dcab316e06e946c6bf295bfb2c880f86d0c8501a13b8600830186a094fe7fb69b43e5d912c0af2547d3291864254d4b2b881774160bc66900008025a015827600b51aabff9eaefafb5b9e1cf5386b9a14044b6d0663c8ca098de5cf97a07bc7a99107ea82d31e32c9cf56479b6a9e2d1d8c71ff1c52ea70a21f096fcaedf8652e8501a13b8600830249f0949c821d1eed859080c307e326fcc888c230d2bf86018026a068098f560e4c38f05c88a37d3f3008bb350380f117e292b983e2404bf02b5f67a00178e4644acb2d3c2d7be66fc318917c6dea23f10cc26432d0e0dae64c67a3f8f891818a850165a0bc008303d09094b1690c08e213a35ed9bab7b318de14420fb57d8c8711c37937e08000a4454a2ab3000000000000000000000000000000000000000000000000000000000004453d26a0904e0bc877f87df103e4c71ce23d689b80e585d803c982a384871d3dc2ac574ea00e1aa5df6ba0af2f3f5a8feec6d9181947291d80c3cef19cc74f722e38bc21b8f9018b0c850165a0bc008303d090941ce7ae555139c5ef5a57cc8d814a867ee6ee33d880b90124278b8c0e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c4f89310dcc0000000000000000000000000004c251de85ad3498c5b89388d8efc696ddd0b0fe700000000000000000000000000000000000000000000000821ab0d441498000000000000000000000000000000000000000000000000000000000000004e63540000000000000000000000000000000000000000000000000000000037535952000000000000000000000000000000000000000000000000000000000000001c589e03657e41e05a0e386be971c3ff3f24e9a9ecefb176e29ce26c68bdb298a33fa7414cb54cb89afb8d40f04a5a69ba4aa14bdef016fd68d56355c0d54364561ba0c8c60ae751a38116679045d8b1760b3dcb0f6223a9d69b51114f4f63dad4d7dba04a1084ba5790b08892a8339afe0cf20332cd0c99252b94cc12eeda5970934259f8b3820b72850165a0bc008301b54f9406012c8cf97bead5deae237070f9587f8e7a266d871c6bf526340000b844f7d8c8830000000000000000000000000000000000000000000000000000000000081531000000000000000000000000000000000000000000000000000000000004a6b825a01e6eecb6467cfd4cec83c349392e5cba97170cc20d89cece0bd0156e307f3789a074926b4aa0a2d8b3ed69d4a7028e84066f02e9fe65be3a659d4ec35b65e8a909f870830df28f8509502f9000830186a094fefb07a2f0f161994bcad68393e96772c21794be88020091c1e30f44008026a0d43f9003b351e5f08ee41034d158d9916bf41325ed3ee8e2e4d23282a10ac069a00419c3fb210210530880b357cd267d4c4865f3374c999fd046b84452398998e4f8ad8307288e8509502f9000830193869441e5560054824ea6b0732e656e3ad64e20e94e4580b844a9059cbb00000000000000000000000069964ad8dd235535288df785e8f7a450f5912552000000000000000000000000000000000000000000000000000000000bebc20025a0210b8732c3b847d870fa47c16d7e1d9b267ee1e46cdeaa1e7314bf4ee624dfa8a0566c5a6cb04a74af926a812e000eb875e425960e9b07371005754a2539b8e992f87083010990850684ee180083015f9094811a27c1b32a1c265a909ba826a6408f2a35c5c8883782dace9d9000008025a05854558634b7bdf9fe0346c6adb7b9aed883f9e14ce5265d0c28d05bcff99853a05d7e572f5e045aa2f2c9857aa9c21f270f2598856a6860b536e7974f9603bbdbf902ae8304ea1785051f4d5c008303f7a0942a0c0dbecc7e4d658f48e01e3fa353f44050c20880b90244ef34358800000000000000000000000000000000000000000000003ba389df387ff6c4800000000000000000000000000000000000000000000000001627c93273beb2640000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000004c31756a00000000000000000000000000000000000000000000003ba389df387ff6c48000000000000000000000000000000000000000000000000000000000000000af00000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000f6da3bc686c810000000000000000000000009992ec3cf6a55b00978cddf2b27bc6882d88d1ec0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c91674d315cc89ba9d6ab194bd0b812bf750cae5000000000000000000000000c6b7d0789d9dc186735d83024c635b0e9a8548b8000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001b35ed1bdc59fe1f965e48cc2be1c48442115d8c0bd15f1133d39986b34769e891062f86dbaef0f0f52d4b37c14a5da2eb1d85d60546a39086c277f87a31c1d39bc6bee6576b09fc4de1954b6fdb31e5b6d5fef0710960977c824447a6175e40c42f4321568697ddfef8cc1b49448e764e9fbb304686ed294dc0ddf970b61afbe31ca0a0f7194cb51c0ba628c05e9fd6413a2b73f0251884d7f286bd5c3329b8545bf4a0537ade4b71462b9608f429b33c905f8e304ad3c5609051c5894fb6c059c213daf9033083059cef8502540be400830e57e08080b902da6060604052341561000f57600080fd5b5b60008054600160a060020a03191633600160a060020a03161790555b5b61029e8061003c6000396000f300606060405236156100465763ffffffff60e060020a600035041663395ede4d811461004a57806383197ef01461006b5780638da5cb5b14610080578063e5225381146100af575b5b5b005b341561005557600080fd5b610046600160a060020a03600435166100c4565b005b341561007657600080fd5b6100466101df565b005b341561008b57600080fd5b61009361020b565b604051600160a060020a03909116815260200160405180910390f35b34156100ba57600080fd5b61004661021a565b005b60008054819033600160a060020a039081169116146100e257600080fd5b82915081600160a060020a03166370a082313060006040516020015260405160e060020a63ffffffff8416028152600160a060020a039091166004820152602401602060405180830381600087803b151561013c57600080fd5b6102c65a03f1151561014d57600080fd5b505050604051805160008054919350600160a060020a03808616935063a9059cbb92169084906040516020015260405160e060020a63ffffffff8516028152600160a060020a0390921660048301526024820152604401602060405180830381600087803b15156101bd57600080fd5b6102c65a03f115156101ce57600080fd5b505050604051805150505b5b505050565b60005433600160a060020a039081169116146101fa57600080fd5b600054600160a060020a0316ff5b5b565b600054600160a060020a031681565b60005433600160a060020a0390811691161461023557600080fd5b600054600160a060020a039081169030163180156108fc0290604051600060405180830381858888f19350505050151561020857600080fd5b5b5b5600a165627a7a7230582046378ee80aabd231215e1636373ac5eccd4f45b88b2a03d6fc70c9671e4802a0002926a0642924aedbabe87fbc3f9189c0926313425aab11144fce53f6410eb8302225b8a049723bf79ede4f45182466b34bd5a5979a269a1b098ca352d14e4ee583155ea1f8ab820a1c8501dcd6500082ca8f946f259637dcd74c767781e37bc6133cd6a68aa16180b844a9059cbb00000000000000000000000053587ba1e1144d03a5819d5101f94ab0600fb4f20000000000000000000000000000000000000000000000000de0b6b3a764000025a030b21caaf789d4d6fccf7b2b504e9fe28d78621e1c3c97c843447a1da0e6fc0fa07aac66eb4346ecb120b2ac262b7a00e8a296d79870d3d65d424bcef3fafabab3f8aa088501dcd650008303d090948d12a197cb00d4747a1fe03395095ce2a5cc681980b844338b5dea00000000000000000000000070a72833d6bf7f508c8224ce59ea1ef3d0ea3a3800000000000000000000000000000000000000000000003102eb135ac856b8001ca0fabe31e791c83093ba302cc0f0245015ab58c68bdfebf18adce487e9769a142ca053599a2ddb513f5e004c6acd2e76b5ddbd6994757cf979e87c0a4b8ae94a7362f870830df2908509502f9000830186a09403c779e9b0277310f5347674f2f7fe1897fd3b0c8806ed3fd58500d0008026a0172f976a8fac2ded91b87ea888c81933efd9197069556bcc89bef3fdebde444ca00a1a5dbca5f7c5fb5769ec49be7066ac347295cd48802e37585b2f23e5ec21a9f9033083059cf08502540be400830e57e08080b902da6060604052341561000f57600080fd5b5b60008054600160a060020a03191633600160a060020a03161790555b5b61029e8061003c6000396000f300606060405236156100465763ffffffff60e060020a600035041663395ede4d811461004a57806383197ef01461006b5780638da5cb5b14610080578063e5225381146100af575b5b5b005b341561005557600080fd5b610046600160a060020a03600435166100c4565b005b341561007657600080fd5b6100466101df565b005b341561008b57600080fd5b61009361020b565b604051600160a060020a03909116815260200160405180910390f35b34156100ba57600080fd5b61004661021a565b005b60008054819033600160a060020a039081169116146100e257600080fd5b82915081600160a060020a03166370a082313060006040516020015260405160e060020a63ffffffff8416028152600160a060020a039091166004820152602401602060405180830381600087803b151561013c57600080fd5b6102c65a03f1151561014d57600080fd5b505050604051805160008054919350600160a060020a03808616935063a9059cbb92169084906040516020015260405160e060020a63ffffffff8516028152600160a060020a0390921660048301526024820152604401602060405180830381600087803b15156101bd57600080fd5b6102c65a03f115156101ce57600080fd5b505050604051805150505b5b505050565b60005433600160a060020a039081169116146101fa57600080fd5b600054600160a060020a0316ff5b5b565b600054600160a060020a031681565b60005433600160a060020a0390811691161461023557600080fd5b600054600160a060020a039081169030163180156108fc0290604051600060405180830381858888f19350505050151561020857600080fd5b5b5b5600a165627a7a7230582046378ee80aabd231215e1636373ac5eccd4f45b88b2a03d6fc70c9671e4802a0002926a0b96d8d3220fa13515f9c9ef3dec544e42fdb5f541602f12bf4eff1fab17e13a9a051c7b6723b49b6f4a3ba196ff57c324455fd5dfc9497c2a96b6a569aacd07231f8ab820a1d8501dcd6500082ca8f946f259637dcd74c767781e37bc6133cd6a68aa16180b844a9059cbb0000000000000000000000004949da879392266ab412dc0897115db66aeb20dd0000000000000000000000000000000000000000000000000de0b6b3a764000025a08bad8678c23c3f580b4215ab67668d071898ac96a018b7ee0576fa8994868894a06b44f01a4947bcb7cc2461c73c663448347c5369e1c982d07a9e612fed669b56f86f830df2918509502f9000830186a09422091806f89d266afbbb1c22fdf6a5354d649075872008bdad0ea4008025a0bf95b3c1308097200fb39558b44721a2525fcb417ce4e78cf49e8c462802045fa059ad4c267320eba469d92b25a5e6f9b28350fadd44ea37fffc53233675a203b1f9033083059cf18502540be400830e57e08080b902da6060604052341561000f57600080fd5b5b60008054600160a060020a03191633600160a060020a03161790555b5b61029e8061003c6000396000f300606060405236156100465763ffffffff60e060020a600035041663395ede4d811461004a57806383197ef01461006b5780638da5cb5b14610080578063e5225381146100af575b5b5b005b341561005557600080fd5b610046600160a060020a03600435166100c4565b005b341561007657600080fd5b6100466101df565b005b341561008b57600080fd5b61009361020b565b604051600160a060020a03909116815260200160405180910390f35b34156100ba57600080fd5b61004661021a565b005b60008054819033600160a060020a039081169116146100e257600080fd5b82915081600160a060020a03166370a082313060006040516020015260405160e060020a63ffffffff8416028152600160a060020a039091166004820152602401602060405180830381600087803b151561013c57600080fd5b6102c65a03f1151561014d57600080fd5b505050604051805160008054919350600160a060020a03808616935063a9059cbb92169084906040516020015260405160e060020a63ffffffff8516028152600160a060020a0390921660048301526024820152604401602060405180830381600087803b15156101bd57600080fd5b6102c65a03f115156101ce57600080fd5b505050604051805150505b5b505050565b60005433600160a060020a039081169116146101fa57600080fd5b600054600160a060020a0316ff5b5b565b600054600160a060020a031681565b60005433600160a060020a0390811691161461023557600080fd5b600054600160a060020a039081169030163180156108fc0290604051600060405180830381858888f19350505050151561020857600080fd5b5b5b5600a165627a7a7230582046378ee80aabd231215e1636373ac5eccd4f45b88b2a03d6fc70c9671e4802a0002925a087de2873f51df7667488f118710e6e31e8f94a950662b17486baabb0843c6f74a01db4acea166f0621d89e5096ac96b0abc561ada3c38c304f757d066d4afea672f8ab820a1e8501dcd6500082ca8f946f259637dcd74c767781e37bc6133cd6a68aa16180b844a9059cbb000000000000000000000000af733e15beace714ee335cc1b81ee7a5153def620000000000000000000000000000000000000000000000000de0b6b3a764000026a0951f9ba9f22863ba96483a1e72bcdfebfd6cb282a88b32bc25d9991c1155b5c9a04f6b3005b1fb6b84e15880561a45aa7426aa6aabc0acff286f92fde899556e43f86f830df2928509502f9000830186a0942fe8283062fc30cb0bed18408ace49d8adccdfa7872030d285c0a0008026a0fc5b060da34701b884dbfaf04d78f49599cc1fc9d5256f15f39c101c1488f192a03ee91184d7ed01242456bfecb892ac45240c083fea7e11ae46cae8db00c01764f9033083059cf28502540be400830e57e08080b902da6060604052341561000f57600080fd5b5b60008054600160a060020a03191633600160a060020a03161790555b5b61029e8061003c6000396000f300606060405236156100465763ffffffff60e060020a600035041663395ede4d811461004a57806383197ef01461006b5780638da5cb5b14610080578063e5225381146100af575b5b5b005b341561005557600080fd5b610046600160a060020a03600435166100c4565b005b341561007657600080fd5b6100466101df565b005b341561008b57600080fd5b61009361020b565b604051600160a060020a03909116815260200160405180910390f35b34156100ba57600080fd5b61004661021a565b005b60008054819033600160a060020a039081169116146100e257600080fd5b82915081600160a060020a03166370a082313060006040516020015260405160e060020a63ffffffff8416028152600160a060020a039091166004820152602401602060405180830381600087803b151561013c57600080fd5b6102c65a03f1151561014d57600080fd5b505050604051805160008054919350600160a060020a03808616935063a9059cbb92169084906040516020015260405160e060020a63ffffffff8516028152600160a060020a0390921660048301526024820152604401602060405180830381600087803b15156101bd57600080fd5b6102c65a03f115156101ce57600080fd5b505050604051805150505b5b505050565b60005433600160a060020a039081169116146101fa57600080fd5b600054600160a060020a0316ff5b5b565b600054600160a060020a031681565b60005433600160a060020a0390811691161461023557600080fd5b600054600160a060020a039081169030163180156108fc0290604051600060405180830381858888f19350505050151561020857600080fd5b5b5b5600a165627a7a7230582046378ee80aabd231215e1636373ac5eccd4f45b88b2a03d6fc70c9671e4802a0002925a005974e86452eb8a23e1f89fafce732d7dd1aea9060d31e5517d47c42d0630acca07447ce67592b3d29dd5736186f4c5a509013710976fe78e43e765e36b6e6441ff8ab820a1f8501dcd6500082ca8f946f259637dcd74c767781e37bc6133cd6a68aa16180b844a9059cbb0000000000000000000000009ec8b099d4b766295e8fa44423f228c2c293dcd00000000000000000000000000000000000000000000000000de0b6b3a764000026a0b2b960146500680200383e219d612efa1886e36c49147fe8a414b1db1636cb62a067c85fd8971110c87961ba1c96887efa838e7acef3ded44b1232fc0f3a168afaf86f830df2938509502f9000830186a0946cc2e200c58d2280ac0d0c2f50350bb1f466659987200f32c608fc008025a0676717fc06167d628b197c80c4b558ce597856ba3260caec436bb9e25e762b89a0710c755ba691e3b04fe20e85f644633affc30514ca4f2a9109261a834187f790f9033083059cf38502540be400830e57e08080b902da6060604052341561000f57600080fd5b5b60008054600160a060020a03191633600160a060020a03161790555b5b61029e8061003c6000396000f300606060405236156100465763ffffffff60e060020a600035041663395ede4d811461004a57806383197ef01461006b5780638da5cb5b14610080578063e5225381146100af575b5b5b005b341561005557600080fd5b610046600160a060020a03600435166100c4565b005b341561007657600080fd5b6100466101df565b005b341561008b57600080fd5b61009361020b565b604051600160a060020a03909116815260200160405180910390f35b34156100ba57600080fd5b61004661021a565b005b60008054819033600160a060020a039081169116146100e257600080fd5b82915081600160a060020a03166370a082313060006040516020015260405160e060020a63ffffffff8416028152600160a060020a039091166004820152602401602060405180830381600087803b151561013c57600080fd5b6102c65a03f1151561014d57600080fd5b505050604051805160008054919350600160a060020a03808616935063a9059cbb92169084906040516020015260405160e060020a63ffffffff8516028152600160a060020a0390921660048301526024820152604401602060405180830381600087803b15156101bd57600080fd5b6102c65a03f115156101ce57600080fd5b505050604051805150505b5b505050565b60005433600160a060020a039081169116146101fa57600080fd5b600054600160a060020a0316ff5b5b565b600054600160a060020a031681565b60005433600160a060020a0390811691161461023557600080fd5b600054600160a060020a039081169030163180156108fc0290604051600060405180830381858888f19350505050151561020857600080fd5b5b5b5600a165627a7a7230582046378ee80aabd231215e1636373ac5eccd4f45b88b2a03d6fc70c9671e4802a0002925a06ed8fbbe361c6b3664d8d20ff5ba00f6ca20de8a5a5629d90e6dc102ff4b8d58a048ebfb81610f9256a975c6ea1774c043a5e0251084a890b1fe9fad626129b694f8ab820a208501dcd6500082ca4f946f259637dcd74c767781e37bc6133cd6a68aa16180b844a9059cbb00000000000000000000000016b50005e6e463107abd5c1342f06590cd871ff50000000000000000000000000000000000000000000000000de0b6b3a764000025a0e134eb6eb936034bef58afc25fa0775556cef2032dce73e34d30f7a8a136d381a017a1dd92b25da2d1927809ddaef71b2d9cc7deb7e556ad2bd85804bc8299af7bf870830df2948509502f9000830186a094b21ae44f4136a04ca7d0c9ede7a5a5bccf3197898829a192fa2d014c008026a0151021ae31f035402eaef55ec6aaf10c6b7a6131338cfd0c35ed6d323f80787aa07167bbee65f1fab34ee097fe5798cd6a497a9be35eac4ec81de9ed404706ffe1f9033083059cf48502540be400830e57e08080b902da6060604052341561000f57600080fd5b5b60008054600160a060020a03191633600160a060020a03161790555b5b61029e8061003c6000396000f300606060405236156100465763ffffffff60e060020a600035041663395ede4d811461004a57806383197ef01461006b5780638da5cb5b14610080578063e5225381146100af575b5b5b005b341561005557600080fd5b610046600160a060020a03600435166100c4565b005b341561007657600080fd5b6100466101df565b005b341561008b57600080fd5b61009361020b565b604051600160a060020a03909116815260200160405180910390f35b34156100ba57600080fd5b61004661021a565b005b60008054819033600160a060020a039081169116146100e257600080fd5b82915081600160a060020a03166370a082313060006040516020015260405160e060020a63ffffffff8416028152600160a060020a039091166004820152602401602060405180830381600087803b151561013c57600080fd5b6102c65a03f1151561014d57600080fd5b505050604051805160008054919350600160a060020a03808616935063a9059cbb92169084906040516020015260405160e060020a63ffffffff8516028152600160a060020a0390921660048301526024820152604401602060405180830381600087803b15156101bd57600080fd5b6102c65a03f115156101ce57600080fd5b505050604051805150505b5b505050565b60005433600160a060020a039081169116146101fa57600080fd5b600054600160a060020a0316ff5b5b565b600054600160a060020a031681565b60005433600160a060020a0390811691161461023557600080fd5b600054600160a060020a039081169030163180156108fc0290604051600060405180830381858888f19350505050151561020857600080fd5b5b5b5600a165627a7a7230582046378ee80aabd231215e1636373ac5eccd4f45b88b2a03d6fc70c9671e4802a0002926a084927134839f43240baf45f18c42938ed61e2d9de4adc0dc8f37bff01e0993e6a02a9db9d8b8062a6227c74a3cbd3344e789c0e7a15573a34008057253bd18c90bf8ab820a218501dcd6500082ca8f946f259637dcd74c767781e37bc6133cd6a68aa16180b844a9059cbb000000000000000000000000dd0ea1397cc744c49e26f2ce569e968569cfd4610000000000000000000000000000000000000000000000000de0b6b3a764000026a0b876183777d985188bfc2634f5122f474ba1ff64cbdd7e9f15a0eccd4d501dd0a0539ef3bb823780bb5284e3d2795e477b40bee2331c014f7849c289f7c1e2e388f9033083059cf58502540be400830e57e08080b902da6060604052341561000f57600080fd5b5b60008054600160a060020a03191633600160a060020a03161790555b5b61029e8061003c6000396000f300606060405236156100465763ffffffff60e060020a600035041663395ede4d811461004a57806383197ef01461006b5780638da5cb5b14610080578063e5225381146100af575b5b5b005b341561005557600080fd5b610046600160a060020a03600435166100c4565b005b341561007657600080fd5b6100466101df565b005b341561008b57600080fd5b61009361020b565b604051600160a060020a03909116815260200160405180910390f35b34156100ba57600080fd5b61004661021a565b005b60008054819033600160a060020a039081169116146100e257600080fd5b82915081600160a060020a03166370a082313060006040516020015260405160e060020a63ffffffff8416028152600160a060020a039091166004820152602401602060405180830381600087803b151561013c57600080fd5b6102c65a03f1151561014d57600080fd5b505050604051805160008054919350600160a060020a03808616935063a9059cbb92169084906040516020015260405160e060020a63ffffffff8516028152600160a060020a0390921660048301526024820152604401602060405180830381600087803b15156101bd57600080fd5b6102c65a03f115156101ce57600080fd5b505050604051805150505b5b505050565b60005433600160a060020a039081169116146101fa57600080fd5b600054600160a060020a0316ff5b5b565b600054600160a060020a031681565b60005433600160a060020a0390811691161461023557600080fd5b600054600160a060020a039081169030163180156108fc0290604051600060405180830381858888f19350505050151561020857600080fd5b5b5b5600a165627a7a7230582046378ee80aabd231215e1636373ac5eccd4f45b88b2a03d6fc70c9671e4802a0002926a0b2e65cd39820b9450cba6b6a52e5d68231bd6b7c75c3fc0542009fdc29ca092aa0317632816e88e1c8c25af20b86e374251b0ad3dfc3d59829e56ddacd0ca2da2ff8ab820a228501dcd6500082ca8f946f259637dcd74c767781e37bc6133cd6a68aa16180b844a9059cbb00000000000000000000000047089369db79cde40279ec52a77370e1e23c35be0000000000000000000000000000000000000000000000000de0b6b3a764000026a0e4b5971a9589874ecb421774c9c8cc9160ab8dbb1087bc0535dd75b41848e22aa059305c7c2eb6bd5637933af1da6dc9cd270ad764b9e0edb2becfc517228274b4f8ab820a238501dcd6500082ca8f946f259637dcd74c767781e37bc6133cd6a68aa16180b844a9059cbb000000000000000000000000f4ae63922475f9e7fefc03c788a0acf6c9788c770000000000000000000000000000000000000000000000000de0b6b3a764000025a03c630f0858b8e658e942bc5e86e91a988ce18bda2828cc33f219a8f3a3c93342a0256971ff6264ccb39e5e49ef9a873d30a0079841fdb9cf155a58da0bc6cd84f5f8ab820a248501dcd6500082ca4f946f259637dcd74c767781e37bc6133cd6a68aa16180b844a9059cbb0000000000000000000000004de06deaa4f48753167229b258fb9b8400126d0f0000000000000000000000000000000000000000000000000de0b6b3a764000026a0ab72b91539ee3a2e7b0de0357784c78c38e82b4361e5d3af082c2185304c787fa00b16bbd40f5b7b32e71842b57fbe7829501161c62b3b24da4f87022ec8fc221df8ab820a258501dcd6500082ca8f946f259637dcd74c767781e37bc6133cd6a68aa16180b844a9059cbb000000000000000000000000d57a83e33c37316b49296d19fa2877c734198eee0000000000000000000000000000000000000000000000000de0b6b3a764000026a0d57829b1dbf307cefc07a287961c5116c807b5f81216cac4e5425381a95fd75ea00758d5963b1de16e6f872457d77c27128559cb4ceb977e92b11890d5a8dfeedff8ab820a268501dcd6500082ca8f946f259637dcd74c767781e37bc6133cd6a68aa16180b844a9059cbb00000000000000000000000089997319c1350af9cd24a84fd41f6786bdd985b00000000000000000000000000000000000000000000000000de0b6b3a764000026a03891a150e5488b9c16e14d7bb66ea550029f43db91310ac5d8e1c5a416448e5da023be5ead4a1d12114f3fb6d9f58271903281df05f7002f07dfb0fc262ea00dadf8ab820a278501dcd6500082ca8f946f259637dcd74c767781e37bc6133cd6a68aa16180b844a9059cbb0000000000000000000000009fea9854d48655d830d66819983a1793430b2b590000000000000000000000000000000000000000000000000de0b6b3a764000025a02e53fc2de677cd051e954d9ebec9ca7db7b248d39e7432bc397546fca21e438aa0750010f13b411b3d5bf80f936c2201ffe075d7d6755dbec68489ca2f6fe6084ef8ab820a288501dcd6500082ca4f946f259637dcd74c767781e37bc6133cd6a68aa16180b844a9059cbb0000000000000000000000002bd4af55f12b86e1f2f00068d92ee34fe1d2f6c10000000000000000000000000000000000000000000000000de0b6b3a764000025a067d7b75c838db2ea807e1060ffedd61bfefaa8de5a84ebb2a586a7442f4ca190a07128e74bbb58a97b857fd2cf364c2885d6e35cbc8d0df9027d08198694a6e4d0f8ab820a298501dcd6500082ca8f946f259637dcd74c767781e37bc6133cd6a68aa16180b844a9059cbb000000000000000000000000238ad34dcd7f049a8a85aecdb6b6a18d094220be0000000000000000000000000000000000000000000000000de0b6b3a764000026a079fcafbeeedbecae31a70e56c386c2da78556b6e8d4370eed1da85c2c3da8130a00e685d3a5afda668a9e89764eb1225de3ee4c510ae0fc550d3aaac5d635ed65af8ab820a2a8501dcd6500082ca8f946f259637dcd74c767781e37bc6133cd6a68aa16180b844a9059cbb00000000000000000000000006381059d6b5db97a8c73ad668973d6e85a6de840000000000000000000000000000000000000000000000000de0b6b3a764000025a08748f823480daf9ebfd9ec01463ec22b825e3c37ebba48876005c01d30450428a005fbfdec03fc8114b10ed4eb319e0d2d6fb9b43fb7578c2dbefb13c4d3419099f8ab820a2b8501dcd6500082ca8f946f259637dcd74c767781e37bc6133cd6a68aa16180b844a9059cbb00000000000000000000000014f8392a7fedcf8e40cbde99dc3b5629ae6a887c0000000000000000000000000000000000000000000000000de0b6b3a764000026a0da1e5fee3bab46f642b1e0c33b13f9b017ed967917969a6f278dac4f8d01a85da002ea288e9f0dd7138c2c2602b3f1836c0bcc379eb3662e45d0737a809bce1e0bf8ab820a2c8501dcd6500082ca8f946f259637dcd74c767781e37bc6133cd6a68aa16180b844a9059cbb000000000000000000000000fef76c4de9a9d591056cd26ed58478e392c9e1660000000000000000000000000000000000000000000000000de0b6b3a764000025a09609b8ab63a2648b016cb475a3b334b716e1eeb59637fd627348ad92f8e44157a07a27c3484330f4175636e598d1c6c449c786ee0ce56c49bacdfc2946ad85d7c7c0")); + System.out.println("Size of encoded block: " + Block.MemEstimator.estimateSize(b)); + b.getNumber(); + System.out.println("Size of parsed block: " + Block.MemEstimator.estimateSize(b)); + b.getTransactionsList().forEach(Transaction::getSender); + System.out.println("Size of parsed block with parsed txes: " + Block.MemEstimator.estimateSize(b)); + } } \ No newline at end of file diff --git a/ethereumj-core/src/test/java/org/ethereum/core/ImportLightTest.java b/ethereumj-core/src/test/java/org/ethereum/core/ImportLightTest.java index 6319ff8d10..d77774d7a6 100644 --- a/ethereumj-core/src/test/java/org/ethereum/core/ImportLightTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/core/ImportLightTest.java @@ -19,13 +19,11 @@ import org.ethereum.config.CommonConfig; import org.ethereum.config.SystemProperties; -import org.ethereum.config.blockchain.FrontierConfig; import org.ethereum.core.genesis.GenesisLoader; import org.ethereum.crypto.ECKey; import org.ethereum.crypto.HashUtil; import org.ethereum.datasource.inmem.HashMapDB; import org.ethereum.datasource.NoDeleteSource; -import org.ethereum.db.ByteArrayWrapper; import org.ethereum.db.IndexedBlockStore; import org.ethereum.db.RepositoryRoot; import org.ethereum.listener.EthereumListenerAdapter; @@ -514,7 +512,7 @@ public void operateNotExistingContractTest() throws IOException, InterruptedExce // checking balance of not existed address should take // less that gas limit - Assert.assertEquals(21508, spent); + Assert.assertEquals(21532, spent); } { @@ -681,22 +679,19 @@ public void threadRacePendingTest() throws Exception { int cnt = 1; final CallTransaction.Function function = a.contract.getByName("set"); - new Thread(new Runnable() { - @Override - public void run() { - int cnt = 1; - while (cnt++ > 0) { - try { - bc.generatePendingTransactions(); + new Thread(() -> { + int cnt1 = 1; + while (cnt1++ > 0) { + try { + bc.generatePendingTransactions(); // byte[] encode = function.encode(cnt % 32, cnt); // Transaction callTx1 = bc.createTransaction(new ECKey(), 0, a.getAddress(), BigInteger.ZERO, encode); // bc.getPendingState().addPendingTransaction(callTx1); // Transaction callTx2 = bc.createTransaction(, 0, a.getAddress(), BigInteger.ZERO, encode); // bc.getPendingState().addPendingTransaction(callTx); - Thread.sleep(10); - } catch (Exception e) { - e.printStackTrace(); - } + Thread.sleep(10); + } catch (Exception e) { + e.printStackTrace(); } } }).start(); @@ -834,7 +829,7 @@ public void ecRecoverTest() throws Exception { " mstore(0x120, v)" + " mstore(0x140, r)" + " mstore(0x160, s)" + - " callcode(0x50000, 0x01, 0x0, 0x100, 0x80, 0x200, 0x220)" + // call ecrecover + " let ret := callcode(0x50000, 0x01, 0x0, 0x100, 0x80, 0x200, 0x220)" + // call ecrecover " return(0x200, 0x20)" + " }" + " }" + diff --git a/ethereumj-core/src/test/java/org/ethereum/core/PruneTest.java b/ethereumj-core/src/test/java/org/ethereum/core/PruneTest.java index 4c17aae7d5..d471ee9dbd 100644 --- a/ethereumj-core/src/test/java/org/ethereum/core/PruneTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/core/PruneTest.java @@ -23,6 +23,8 @@ import org.ethereum.datasource.*; import org.ethereum.datasource.inmem.HashMapDB; import org.ethereum.db.ByteArrayWrapper; +import org.ethereum.db.prune.Pruner; +import org.ethereum.db.prune.Segment; import org.ethereum.trie.SecureTrie; import org.ethereum.trie.TrieImpl; import org.ethereum.util.FastByteComparisons; @@ -37,7 +39,6 @@ import static org.ethereum.util.ByteUtil.intToBytes; import static org.ethereum.util.blockchain.EtherUtil.Unit.ETHER; import static org.ethereum.util.blockchain.EtherUtil.convert; -import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertTrue; /** @@ -53,50 +54,60 @@ public static void cleanup() { @Test public void testJournal1() throws Exception { HashMapDB db = new HashMapDB<>(); - CountingBytesSource countDB = new CountingBytesSource(db); - JournalSource journalDB = new JournalSource<>(countDB); + JournalSource journalDB = new JournalSource<>(db); + Pruner pruner = new Pruner(journalDB.getJournal(), db); + pruner.init(); put(journalDB, "11"); put(journalDB, "22"); put(journalDB, "33"); - journalDB.commitUpdates(intToBytes(1)); + pruner.feed(journalDB.commitUpdates(intToBytes(1))); checkKeys(db.getStorage(), "11", "22", "33"); put(journalDB, "22"); delete(journalDB, "33"); put(journalDB, "44"); - journalDB.commitUpdates(intToBytes(2)); + pruner.feed(journalDB.commitUpdates(intToBytes(2))); checkKeys(db.getStorage(), "11", "22", "33", "44"); - journalDB.persistUpdate(intToBytes(1)); - checkKeys(db.getStorage(), "11", "22", "33", "44"); + pruner.feed(journalDB.commitUpdates(intToBytes(12))); + + Segment segment = new Segment(0, intToBytes(0), intToBytes(0)); + segment.startTracking() + .addMain(1, intToBytes(1), intToBytes(0)) + .addItem(1, intToBytes(2), intToBytes(0)) + .addMain(2, intToBytes(12), intToBytes(1)) + .commit(); + pruner.prune(segment); - journalDB.revertUpdate(intToBytes(2)); checkKeys(db.getStorage(), "11", "22", "33"); put(journalDB, "22"); delete(journalDB, "33"); put(journalDB, "44"); - journalDB.commitUpdates(intToBytes(3)); + pruner.feed(journalDB.commitUpdates(intToBytes(3))); checkKeys(db.getStorage(), "11", "22", "33", "44"); delete(journalDB, "22"); put(journalDB, "33"); delete(journalDB, "44"); - journalDB.commitUpdates(intToBytes(4)); + pruner.feed(journalDB.commitUpdates(intToBytes(4))); checkKeys(db.getStorage(), "11", "22", "33", "44"); - journalDB.persistUpdate(intToBytes(3)); - checkKeys(db.getStorage(), "11", "22", "33", "44"); + segment = new Segment(0, intToBytes(0), intToBytes(0)); + segment.startTracking() + .addMain(1, intToBytes(3), intToBytes(0)) + .commit(); + pruner.prune(segment); - journalDB.persistUpdate(intToBytes(4)); - checkKeys(db.getStorage(), "11", "22", "33"); + checkKeys(db.getStorage(), "11", "22", "33", "44"); - delete(journalDB, "22"); - journalDB.commitUpdates(intToBytes(5)); - checkKeys(db.getStorage(), "11", "22", "33"); + segment = new Segment(0, intToBytes(0), intToBytes(0)); + segment.startTracking() + .addMain(1, intToBytes(4), intToBytes(0)) + .commit(); + pruner.prune(segment); - journalDB.persistUpdate(intToBytes(5)); checkKeys(db.getStorage(), "11", "33"); } @@ -104,6 +115,7 @@ public void testJournal1() throws Exception { private static void put(Source db, String key) { db.put(Hex.decode(key), Hex.decode(key)); } + private static void delete(Source db, String key) { db.delete(Hex.decode(key)); } @@ -193,7 +205,7 @@ public void simpleTest() throws Exception { { // this state should be pruned already - Block b1 = bc.getBlockchain().getBlockByNumber(bestBlockNum - 4); + Block b1 = bc.getBlockchain().getBlockByNumber(bestBlockNum - 6); Repository r1 = bc.getBlockchain().getRepository().getSnapshotTo(b1.getStateRoot()); Assert.assertEquals(BigInteger.ZERO, r1.getBalance(alice.getAddress())); Assert.assertEquals(BigInteger.ZERO, r1.getBalance(bob.getAddress())); @@ -238,6 +250,10 @@ public void contractTest() throws Exception { bc.createBlock(); bc.createBlock(); bc.createBlock(); + bc.createBlock(); + bc.createBlock(); + bc.createBlock(); + bc.createBlock(); Assert.assertEquals(BigInteger.valueOf(0xaaaaaaaaaaaaL), contr.callConstFunction("n")[0]); for (int i = 1; i < 4; i++) { @@ -390,6 +406,10 @@ public void branchTest() throws Exception { bc.createBlock(); bc.createBlock(); bc.createBlock(); + bc.createBlock(); + bc.createBlock(); + bc.createBlock(); + bc.createBlock(); Assert.assertEquals(BigInteger.valueOf(0xaaaaaaaaaaaaL), contr.callConstFunction("n")[0]); } @@ -521,7 +541,8 @@ public void doOnValue(byte[] nodeHash, TrieImpl.Node node, byte[] key, byte[] va ret.add(new ByteArrayWrapper(accountState.getCodeHash())); } if (!FastByteComparisons.equal(accountState.getStateRoot(), HashUtil.EMPTY_TRIE_HASH)) { - ret.addAll(getReferencedTrieNodes(stateDS, false, accountState.getStateRoot())); + NodeKeyCompositor nodeKeyCompositor = new NodeKeyCompositor(key); + ret.addAll(getReferencedTrieNodes(new SourceCodec.KeyOnly<>(stateDS, nodeKeyCompositor), false, accountState.getStateRoot())); } } } @@ -548,7 +569,8 @@ public void doOnValue(byte[] nodeHash, TrieImpl.Node node, byte[] key, byte[] va ret.append(" CodeHash: " + Hex.toHexString(accountState.getCodeHash()) + "\n"); } if (!FastByteComparisons.equal(accountState.getStateRoot(), HashUtil.EMPTY_TRIE_HASH)) { - ret.append(dumpState(stateDS, false, accountState.getStateRoot())); + NodeKeyCompositor nodeKeyCompositor = new NodeKeyCompositor(key); + ret.append(dumpState(new SourceCodec.KeyOnly<>(stateDS, nodeKeyCompositor), false, accountState.getStateRoot())); } } else { ret.append(" " + Hex.toHexString(nodeHash) + ": " + Hex.toHexString(key) + " = " + Hex.toHexString(value) + "\n"); diff --git a/ethereumj-core/src/test/java/org/ethereum/core/TransactionTest.java b/ethereumj-core/src/test/java/org/ethereum/core/TransactionTest.java index 79b89ebd28..94fe0ff5cb 100644 --- a/ethereumj-core/src/test/java/org/ethereum/core/TransactionTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/core/TransactionTest.java @@ -35,6 +35,8 @@ import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.spongycastle.util.BigIntegers; import org.spongycastle.util.encoders.Hex; @@ -47,12 +49,14 @@ import java.util.Arrays; import java.util.List; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.ethereum.solidity.SolidityType.*; +import static org.slf4j.LoggerFactory.getLogger; public class TransactionTest { - + private final static Logger logger = getLogger(TransactionTest.class); @Test /* sign transaction https://tools.ietf.org/html/rfc6979 */ public void test1() throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException, IOException { @@ -71,7 +75,7 @@ public void test1() throws NoSuchProviderException, NoSuchAlgorithmException, In byte[] txHash = HashUtil.sha3(data); String signature = key.doSign(txHash).toBase64(); - System.out.println(signature); + logger.info(signature); } @Ignore @@ -98,25 +102,25 @@ public void test2() throws Exception { tx.sign(ECKey.fromPrivate(senderPrivKey)); - System.out.println("v\t\t\t: " + Hex.toHexString(new byte[]{tx.getSignature().v})); - System.out.println("r\t\t\t: " + Hex.toHexString(BigIntegers.asUnsignedByteArray(tx.getSignature().r))); - System.out.println("s\t\t\t: " + Hex.toHexString(BigIntegers.asUnsignedByteArray(tx.getSignature().s))); + logger.info("v\t\t\t: " + Hex.toHexString(new byte[]{tx.getSignature().v})); + logger.info("r\t\t\t: " + Hex.toHexString(BigIntegers.asUnsignedByteArray(tx.getSignature().r))); + logger.info("s\t\t\t: " + Hex.toHexString(BigIntegers.asUnsignedByteArray(tx.getSignature().s))); - System.out.println("RLP encoded tx\t\t: " + Hex.toHexString(tx.getEncoded())); + logger.info("RLP encoded tx\t\t: " + Hex.toHexString(tx.getEncoded())); // retrieve the signer/sender of the transaction ECKey key = ECKey.signatureToKey(tx.getHash(), tx.getSignature()); - System.out.println("Tx unsigned RLP\t\t: " + Hex.toHexString(tx.getEncodedRaw())); - System.out.println("Tx signed RLP\t\t: " + Hex.toHexString(tx.getEncoded())); + logger.info("Tx unsigned RLP\t\t: " + Hex.toHexString(tx.getEncodedRaw())); + logger.info("Tx signed RLP\t\t: " + Hex.toHexString(tx.getEncoded())); - System.out.println("Signature public key\t: " + Hex.toHexString(key.getPubKey())); - System.out.println("Sender is\t\t: " + Hex.toHexString(key.getAddress())); + logger.info("Signature public key\t: " + Hex.toHexString(key.getPubKey())); + logger.info("Sender is\t\t: " + Hex.toHexString(key.getAddress())); assertEquals("cd2a3d9f938e13cd947ec05abc7fe734df8dd826", Hex.toHexString(key.getAddress())); - System.out.println(tx.toString()); + logger.info(tx.toString()); } @@ -140,20 +144,20 @@ public void test3() throws Exception { tx.sign(ECKey.fromPrivate(senderPrivKey)); - System.out.println("v\t\t\t: " + Hex.toHexString(new byte[]{tx.getSignature().v})); - System.out.println("r\t\t\t: " + Hex.toHexString(BigIntegers.asUnsignedByteArray(tx.getSignature().r))); - System.out.println("s\t\t\t: " + Hex.toHexString(BigIntegers.asUnsignedByteArray(tx.getSignature().s))); + logger.info("v\t\t\t: " + Hex.toHexString(new byte[]{tx.getSignature().v})); + logger.info("r\t\t\t: " + Hex.toHexString(BigIntegers.asUnsignedByteArray(tx.getSignature().r))); + logger.info("s\t\t\t: " + Hex.toHexString(BigIntegers.asUnsignedByteArray(tx.getSignature().s))); - System.out.println("RLP encoded tx\t\t: " + Hex.toHexString(tx.getEncoded())); + logger.info("RLP encoded tx\t\t: " + Hex.toHexString(tx.getEncoded())); // retrieve the signer/sender of the transaction ECKey key = ECKey.signatureToKey(tx.getHash(), tx.getSignature()); - System.out.println("Tx unsigned RLP\t\t: " + Hex.toHexString(tx.getEncodedRaw())); - System.out.println("Tx signed RLP\t\t: " + Hex.toHexString(tx.getEncoded())); + logger.info("Tx unsigned RLP\t\t: " + Hex.toHexString(tx.getEncodedRaw())); + logger.info("Tx signed RLP\t\t: " + Hex.toHexString(tx.getEncoded())); - System.out.println("Signature public key\t: " + Hex.toHexString(key.getPubKey())); - System.out.println("Sender is\t\t: " + Hex.toHexString(key.getAddress())); + logger.info("Signature public key\t: " + Hex.toHexString(key.getPubKey())); + logger.info("Sender is\t\t: " + Hex.toHexString(key.getAddress())); assertEquals("cd2a3d9f938e13cd947ec05abc7fe734df8dd826", Hex.toHexString(key.getAddress())); @@ -291,7 +295,7 @@ public void testTransactionCreateContract() { byte[] payload = tx1.getEncoded(); - System.out.println(Hex.toHexString(payload)); + logger.info(Hex.toHexString(payload)); Transaction tx2 = new Transaction(payload); // tx2.getSender(); @@ -300,13 +304,13 @@ public void testTransactionCreateContract() { // Transaction tx = new Transaction(Hex.decode(rlp)); - System.out.println("tx1.hash: " + Hex.toHexString(tx1.getHash())); - System.out.println("tx2.hash: " + Hex.toHexString(tx2.getHash())); - System.out.println(); - System.out.println("plainTx1: " + plainTx1); - System.out.println("plainTx2: " + plainTx2); + logger.info("tx1.hash: " + Hex.toHexString(tx1.getHash())); + logger.info("tx2.hash: " + Hex.toHexString(tx2.getHash())); + logger.info(""); + logger.info("plainTx1: " + plainTx1); + logger.info("plainTx2: " + plainTx2); - System.out.println(Hex.toHexString(tx2.getSender())); + logger.info(Hex.toHexString(tx2.getSender())); } @@ -449,7 +453,7 @@ protected ProgramResult executeTransaction(Transaction tx) { track.rollback(); - System.out.println("Return value: " + new IntType("uint").decode(executor.getResult().getHReturn())); + logger.info("Return value: " + new IntType("uint").decode(executor.getResult().getHReturn())); } // now executing the JSON test transaction @@ -537,7 +541,7 @@ function get() returns (uint) { StateTestSuite stateTestSuite = new StateTestSuite(json.replaceAll("'", "\"")); - System.out.println(json.replaceAll("'", "\"")); + logger.info(json.replaceAll("'", "\"")); try { SystemProperties.getDefault().setBlockchainConfig(new HomesteadConfig()); @@ -567,23 +571,23 @@ public void multiSuicideTest() throws IOException, InterruptedException { "}"; SolidityCompiler.Result res = SolidityCompiler.compile( contract.getBytes(), true, SolidityCompiler.Options.ABI, SolidityCompiler.Options.BIN); - System.out.println(res.errors); + logger.info(res.errors); CompilationResult cres = CompilationResult.parse(res.output); BlockchainImpl blockchain = ImportLightTest.createBlockchain(GenesisLoader.loadGenesis( getClass().getResourceAsStream("/genesis/genesis-light.json"))); ECKey sender = ECKey.fromPrivate(Hex.decode("3ec771c31cac8c0dba77a69e503765701d3c2bb62435888d4ffa38fed60c445c")).compress(); - System.out.println("address: " + Hex.toHexString(sender.getAddress())); + logger.info("address: " + Hex.toHexString(sender.getAddress())); - if (cres.contracts.get("PsychoKiller") != null) { + if (cres.getContract("PsychoKiller") != null) { Transaction tx = createTx(blockchain, sender, new byte[0], - Hex.decode(cres.contracts.get("PsychoKiller").bin)); + Hex.decode(cres.getContract("PsychoKiller").bin)); executeTransaction(blockchain, tx); byte[] contractAddress = tx.getContractAddress(); - CallTransaction.Contract contract1 = new CallTransaction.Contract(cres.contracts.get("PsychoKiller").abi); + CallTransaction.Contract contract1 = new CallTransaction.Contract(cres.getContract("PsychoKiller").abi); byte[] callData = contract1.getByName("multipleHomocide").encode(); Transaction tx1 = createTx(blockchain, sender, contractAddress, callData, 0l); @@ -615,7 +619,7 @@ public void receiptErrorTest() throws Exception { Transaction tx = createTx(blockchain, sender, new byte[32], new byte[0], 100); TransactionReceipt receipt = executeTransaction(blockchain, tx).getReceipt(); - System.out.println(Hex.toHexString(receipt.getEncoded())); + logger.info(Hex.toHexString(receipt.getEncoded())); receipt = new TransactionReceipt(receipt.getEncoded()); @@ -660,9 +664,9 @@ public void receiptErrorTest() throws Exception { "}"; SolidityCompiler.Result res = SolidityCompiler.compile( contract.getBytes(), true, SolidityCompiler.Options.ABI, SolidityCompiler.Options.BIN); - System.out.println(res.errors); + logger.info(res.errors); CompilationResult cres = CompilationResult.parse(res.output); - Transaction tx = createTx(blockchain, sender, new byte[0], Hex.decode(cres.contracts.get("GasConsumer").bin), 0); + Transaction tx = createTx(blockchain, sender, new byte[0], Hex.decode(cres.getContract("GasConsumer").bin), 0); TransactionReceipt receipt = executeTransaction(blockchain, tx).getReceipt(); receipt = new TransactionReceipt(receipt.getEncoded()); @@ -672,11 +676,11 @@ public void receiptErrorTest() throws Exception { } } - protected Transaction createTx(BlockchainImpl blockchain, ECKey sender, byte[] receiveAddress, byte[] data) { + private Transaction createTx(BlockchainImpl blockchain, ECKey sender, byte[] receiveAddress, byte[] data) { return createTx(blockchain, sender, receiveAddress, data, 0); } - protected Transaction createTx(BlockchainImpl blockchain, ECKey sender, byte[] receiveAddress, - byte[] data, long value) { + private Transaction createTx(BlockchainImpl blockchain, ECKey sender, byte[] receiveAddress, + byte[] data, long value) { BigInteger nonce = blockchain.getRepository().getNonce(sender.getAddress()); Transaction tx = new Transaction( ByteUtil.bigIntegerToBytes(nonce), @@ -689,7 +693,7 @@ protected Transaction createTx(BlockchainImpl blockchain, ECKey sender, byte[] r return tx; } - public TransactionExecutor executeTransaction(BlockchainImpl blockchain, Transaction tx) { + private TransactionExecutor executeTransaction(BlockchainImpl blockchain, Transaction tx) { Repository track = blockchain.getRepository().startTracking(); TransactionExecutor executor = new TransactionExecutor(tx, new byte[32], blockchain.getRepository(), blockchain.getBlockStore(), blockchain.getProgramInvokeFactory(), blockchain.getBestBlock()); @@ -723,36 +727,39 @@ public void afterEIP158Test() throws Exception { ); // Checking RLP of unsigned transaction and its hash - assert Arrays.equals(Hex.decode(rlpUnsigned), tx.getEncoded()); - assert Arrays.equals(Hex.decode(unsignedHash), tx.getHash()); + assertArrayEquals(Hex.decode(rlpUnsigned), tx.getEncoded()); + assertArrayEquals(Hex.decode(unsignedHash), tx.getHash()); + assertNull(tx.getSignature()); ECKey ecKey = ECKey.fromPrivate(Hex.decode(privateKey)); tx.sign(ecKey); // Checking modified signature - assert tx.getSignature().r.equals(signatureR); - assert tx.getSignature().s.equals(signatureS); + assertEquals(signatureR, tx.getSignature().r); + assertEquals(signatureS, tx.getSignature().s); + // TODO: Strange, it's still 27. Why is signature used for getEncoded() never assigned to signature field? + assertEquals(27, tx.getSignature().v); // Checking that we get correct TX in the end - assert Arrays.equals(Hex.decode(signedTxRlp), tx.getEncoded()); + assertArrayEquals(Hex.decode(signedTxRlp), tx.getEncoded()); // Check that we could correctly extract tx from new RLP Transaction txSigned = new Transaction(Hex.decode(signedTxRlp)); - assert txSigned.getChainId() == chainId; + assertEquals((int)txSigned.getChainId(), chainId); } @Test public void etcChainIdTest() { Transaction tx = new Transaction(Hex.decode("f871830617428504a817c80083015f90940123286bd94beecd40905321f5c3202c7628d685880ecab7b2bae2c27080819ea021355678b1aa704f6ad4706fb8647f5125beadd1d84c6f9cf37dda1b62f24b1aa06b4a64fd29bb6e54a2c5107e8be42ac039a8ffb631e16e7bcbd15cdfc0015ee2")); Integer chainId = tx.getChainId(); - assert 61 == chainId; + assertEquals(61, chainId.intValue()); } @Test public void longChainIdTest() { Transaction tx = new Transaction(Hex.decode("f8ae82477b8504a817c80083015f9094977ddf44438d540892d1b8618fea65395399971680b844eceb6e3e57696e6454757262696e655f30310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007827e19a025f55532f5cebec362f3f750a3b9c47ab76322622eb3a26ad24c80f9c388c15ba02dcc7ebcfb6ad6ae09f56a29d710cc4115e960a83b98405cf98f7177c14d8a51")); Integer chainId = tx.getChainId(); - assert 16123 == chainId; + assertEquals(16123, chainId.intValue()); Transaction tx1 = Transaction.create( "3535353535353535353535353535353535353535", @@ -767,8 +774,8 @@ public void longChainIdTest() { tx1.sign(key); Transaction tx2 = new Transaction(tx1.getEncoded()); - assert 333333 == tx2.getChainId(); - assert Arrays.equals(tx2.getSender(), key.getAddress()); + assertEquals(333333, tx2.getChainId().intValue()); + assertArrayEquals(tx2.getSender(), key.getAddress()); } } diff --git a/ethereumj-core/src/test/java/org/ethereum/crypto/ECKeyTest.java b/ethereumj-core/src/test/java/org/ethereum/crypto/ECKeyTest.java index 7e88fec37c..dc313ee224 100644 --- a/ethereumj-core/src/test/java/org/ethereum/crypto/ECKeyTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/crypto/ECKeyTest.java @@ -261,12 +261,7 @@ public void testSValue() throws Exception { final ECKey key = new ECKey(); for (byte i = 0; i < ITERATIONS; i++) { final byte[] hash = HashUtil.sha3(new byte[]{i}); - sigFutures.add(executor.submit(new Callable() { - @Override - public ECKey.ECDSASignature call() throws Exception { - return key.doSign(hash); - } - })); + sigFutures.add(executor.submit(() -> key.doSign(hash))); } List sigs = Futures.allAsList(sigFutures).get(); for (ECKey.ECDSASignature signature : sigs) { @@ -302,7 +297,8 @@ public void testSunECRoundTrip() throws Exception { if (provider != null) { testProviderRoundTrip(provider); } else { - System.out.println("Skip test as provider doesn't exist. Must be OpenJDK 1.7 which ships without 'SunEC'"); + System.out.println("Skip test as provider doesn't exist. " + + "Must be OpenJDK which could be shipped without 'SunEC'"); } } diff --git a/ethereumj-core/src/test/java/org/ethereum/datasource/CountingQuotientFilterTest.java b/ethereumj-core/src/test/java/org/ethereum/datasource/CountingQuotientFilterTest.java new file mode 100644 index 0000000000..33615d5968 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/datasource/CountingQuotientFilterTest.java @@ -0,0 +1,157 @@ +package org.ethereum.datasource; + +import org.ethereum.datasource.CountingQuotientFilter; +import org.junit.Ignore; +import org.junit.Test; + +import static org.ethereum.crypto.HashUtil.sha3; +import static org.ethereum.util.ByteUtil.intToBytes; + +/** + * @author Mikhail Kalinin + * @since 15.02.2018 + */ +public class CountingQuotientFilterTest { + + @Ignore + @Test + public void perfTest() { + CountingQuotientFilter f = CountingQuotientFilter.create(50_000_000, 100_000); + long s = System.currentTimeMillis(); + for (int i = 0; i < 5_000_000; i++) { + f.insert(sha3(intToBytes(i))); + + if (i % 10 == 0) f.insert(sha3(intToBytes(0))); + + if (i > 100_000 && i % 2 == 0) { + f.remove(sha3(intToBytes(i - 100_000))); + } + if (i % 10000 == 0) { + System.out.println(i + ": " + (System.currentTimeMillis() - s)); + s = System.currentTimeMillis(); + } + } + } + + @Test + public void simpleTest() { + CountingQuotientFilter f = CountingQuotientFilter.create(1_000_000, 1_000_000); + + f.insert(sha3(intToBytes(0))); + assert f.maybeContains(sha3(intToBytes(0))); + + f.insert(sha3(intToBytes(1))); + f.remove(sha3(intToBytes(0))); + assert f.maybeContains(sha3(intToBytes(1))); + assert !f.maybeContains(sha3(intToBytes(0))); + + for (int i = 0; i < 10; i++) { + f.insert(sha3(intToBytes(2))); + } + + assert f.maybeContains(sha3(intToBytes(2))); + + for (int i = 0; i < 8; i++) { + f.remove(sha3(intToBytes(2))); + } + assert f.maybeContains(sha3(intToBytes(2))); + + f.remove(sha3(intToBytes(2))); + assert f.maybeContains(sha3(intToBytes(2))); + + f.remove(sha3(intToBytes(2))); + assert !f.maybeContains(sha3(intToBytes(2))); + + f.remove(sha3(intToBytes(2))); // check that it breaks nothing + assert !f.maybeContains(sha3(intToBytes(2))); + } + + @Test // elements have same fingerprint, but different hash + public void softCollisionTest() { + CountingQuotientFilter f = CountingQuotientFilter.create(1_000_000, 1_000_000); + + f.insert(-1L); + f.insert(Long.MAX_VALUE); + f.insert(Long.MAX_VALUE - 1); + + assert f.maybeContains(-1L); + assert f.maybeContains(Long.MAX_VALUE); + assert f.maybeContains(Long.MAX_VALUE - 1); + + f.remove(-1L); + assert f.maybeContains(Long.MAX_VALUE); + assert f.maybeContains(Long.MAX_VALUE - 1); + + f.remove(Long.MAX_VALUE); + assert f.maybeContains(Long.MAX_VALUE - 1); + + f.remove(Long.MAX_VALUE - 1); + assert !f.maybeContains(-1L); + assert !f.maybeContains(Long.MAX_VALUE); + assert !f.maybeContains(Long.MAX_VALUE - 1); + } + + @Test // elements have same fingerprint, but different hash + public void softCollisionTest2() { + CountingQuotientFilter f = CountingQuotientFilter.create(1_000_000, 1_000_000); + + f.insert(0xE0320F4F9B35343FL); + f.insert(0xFF2D4CCA9B353435L); + f.insert(0xFF2D4CCA9B353435L); + + f.remove(0xE0320F4F9B35343FL); + f.remove(0xFF2D4CCA9B353435L); + + assert f.maybeContains(0xFF2D4CCA9B353435L); + } + + @Test // elements have same hash + public void hardCollisionTest() { + CountingQuotientFilter f = CountingQuotientFilter.create(10_000_000, 10_000_000); + + f.insert(Long.MAX_VALUE); + f.insert(Long.MAX_VALUE); + f.insert(Long.MAX_VALUE); + assert f.maybeContains(Long.MAX_VALUE); + + f.remove(Long.MAX_VALUE); + f.remove(Long.MAX_VALUE); + assert f.maybeContains(Long.MAX_VALUE); + + f.remove(-1L); + assert !f.maybeContains(-1L); + } + + @Test + public void resizeTest() { + CountingQuotientFilter f = CountingQuotientFilter.create(1_000, 1_000); + + f.insert(Long.MAX_VALUE); + f.insert(Long.MAX_VALUE); + f.insert(Long.MAX_VALUE); + f.insert(Long.MAX_VALUE - 1); + + for (int i = 100_000; i < 200_000; i++) { + f.insert(intToBytes(i)); + } + + assert f.maybeContains(Long.MAX_VALUE); + for (int i = 100_000; i < 200_000; i++) { + assert f.maybeContains(intToBytes(i)); + } + + assert f.maybeContains(Long.MAX_VALUE); + assert f.maybeContains(Long.MAX_VALUE - 1); + + f.remove(Long.MAX_VALUE); + f.remove(Long.MAX_VALUE); + f.remove(Long.MAX_VALUE - 1); + assert f.maybeContains(Long.MAX_VALUE); + + f.remove(Long.MAX_VALUE); + assert !f.maybeContains(Long.MAX_VALUE); + + f.remove(Long.MAX_VALUE - 1); + assert !f.maybeContains(Long.MAX_VALUE - 1); + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/datasource/JournalPruneTest.java b/ethereumj-core/src/test/java/org/ethereum/datasource/JournalPruneTest.java index 666c013a7d..c8ee124322 100644 --- a/ethereumj-core/src/test/java/org/ethereum/datasource/JournalPruneTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/datasource/JournalPruneTest.java @@ -19,9 +19,13 @@ import org.ethereum.crypto.HashUtil; import org.ethereum.datasource.inmem.HashMapDB; +import org.ethereum.db.prune.Pruner; +import org.ethereum.db.prune.Segment; import org.ethereum.util.ByteUtil; import org.junit.Test; +import java.util.Collections; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -39,9 +43,8 @@ public StringJDS() { } private StringJDS(HashMapDB mapDB) { - this(mapDB, new CountingBytesSource(mapDB)); + this(mapDB, mapDB); } - private StringJDS(HashMapDB mapDB, Source db) { super(db); this.db = db; @@ -77,25 +80,42 @@ private void putKeys(StringJDS db, String ... keys) { @Test public void simpleTest() { StringJDS jds = new StringJDS(); + Pruner pruner = new Pruner(jds.getJournal(), jds.db); + pruner.init(); putKeys(jds, "a1", "a2"); jds.put("a3"); jds.delete("a2"); - jds.commitUpdates(hashInt(1)); + pruner.feed(jds.commitUpdates(hashInt(1))); jds.put("a2"); jds.delete("a3"); - jds.commitUpdates(hashInt(2)); + pruner.feed(jds.commitUpdates(hashInt(2))); jds.delete("a2"); - jds.commitUpdates(hashInt(3)); + pruner.feed(jds.commitUpdates(hashInt(3))); + + Segment segment = new Segment(0, hashInt(0), hashInt(0)); + segment.startTracking() + .addMain(1, hashInt(1), hashInt(0)) + .commit(); + pruner.prune(segment); - jds.persistUpdate(hashInt(1)); checkDb(jds, "a1", "a2", "a3"); - jds.persistUpdate(hashInt(2)); + segment = new Segment(1, hashInt(1), hashInt(0)); + segment.startTracking() + .addMain(2, hashInt(2), hashInt(1)) + .commit(); + pruner.prune(segment); + checkDb(jds, "a1", "a2"); - jds.persistUpdate(hashInt(3)); + segment = new Segment(2, hashInt(2), hashInt(1)); + segment.startTracking() + .addMain(3, hashInt(3), hashInt(2)) + .commit(); + pruner.prune(segment); + checkDb(jds, "a1"); assertEquals(0, ((HashMapDB) jds.journal).getStorage().size()); @@ -104,23 +124,39 @@ public void simpleTest() { @Test public void forkTest1() { StringJDS jds = new StringJDS(); + Pruner pruner = new Pruner(jds.getJournal(), jds.db); + pruner.init(); putKeys(jds, "a1", "a2", "a3"); + pruner.feed(jds.commitUpdates(hashInt(0))); jds.put("a4"); jds.put("a1"); jds.delete("a2"); - jds.commitUpdates(hashInt(1)); + pruner.feed(jds.commitUpdates(hashInt(1))); jds.put("a5"); jds.delete("a3"); jds.put("a2"); jds.put("a1"); - jds.commitUpdates(hashInt(2)); + pruner.feed(jds.commitUpdates(hashInt(2))); + pruner.feed(jds.commitUpdates(hashInt(3))); // complete segment checkDb(jds, "a1", "a2", "a3", "a4", "a5"); - jds.persistUpdate(hashInt(1)); - jds.revertUpdate(hashInt(2)); + Segment segment = new Segment(0, hashInt(0), hashInt(0)); + segment.startTracking() + .addMain(0, hashInt(0), hashInt(0)) + .commit(); + pruner.prune(segment); + + segment = new Segment(0, hashInt(0), hashInt(-1)); + segment.startTracking() + .addMain(1, hashInt(1), hashInt(0)) + .addItem(1, hashInt(2), hashInt(0)) + .addMain(2, hashInt(3), hashInt(1)) + .commit(); + pruner.prune(segment); + checkDb(jds, "a1", "a3", "a4"); assertEquals(0, ((HashMapDB) jds.journal).getStorage().size()); @@ -129,35 +165,56 @@ public void forkTest1() { @Test public void forkTest2() { StringJDS jds = new StringJDS(); + Pruner pruner = new Pruner(jds.getJournal(), jds.db); + pruner.init(); putKeys(jds, "a1", "a2", "a3"); jds.delete("a1"); jds.delete("a3"); - jds.commitUpdates(hashInt(1)); + pruner.feed(jds.commitUpdates(hashInt(1))); jds.put("a4"); - jds.commitUpdates(hashInt(2)); - jds.commitUpdates(hashInt(3)); + pruner.feed(jds.commitUpdates(hashInt(2))); + pruner.feed(jds.commitUpdates(hashInt(3))); jds.put("a1"); jds.delete("a2"); - jds.commitUpdates(hashInt(4)); + pruner.feed(jds.commitUpdates(hashInt(4))); jds.put("a4"); - jds.commitUpdates(hashInt(5)); + pruner.feed(jds.commitUpdates(hashInt(5))); + pruner.feed(jds.commitUpdates(hashInt(6))); + pruner.feed(jds.commitUpdates(hashInt(7))); jds.put("a3"); - jds.commitUpdates(hashInt(6)); + pruner.feed(jds.commitUpdates(hashInt(8))); checkDb(jds, "a1", "a2", "a3", "a4"); - jds.persistUpdate(hashInt(1)); - jds.revertUpdate(hashInt(2)); + Segment segment = new Segment(0, hashInt(0), hashInt(0)); + segment.startTracking() + .addMain(1, hashInt(1), hashInt(0)) + .addItem(1, hashInt(2), hashInt(0)) + .addMain(2, hashInt(3), hashInt(1)) + .commit(); + pruner.prune(segment); + checkDb(jds, "a1", "a2", "a3", "a4"); - jds.persistUpdate(hashInt(3)); - jds.revertUpdate(hashInt(4)); - jds.revertUpdate(hashInt(5)); + segment = new Segment(0, hashInt(0), hashInt(0)); + segment.startTracking() + .addMain(1, hashInt(6), hashInt(0)) + .addItem(1, hashInt(4), hashInt(0)) + .addItem(1, hashInt(5), hashInt(0)) + .addMain(2, hashInt(7), hashInt(6)) + .commit(); + pruner.prune(segment); + checkDb(jds, "a2", "a3"); - jds.persistUpdate(hashInt(6)); + segment = new Segment(0, hashInt(0), hashInt(0)); + segment.startTracking() + .addMain(1, hashInt(8), hashInt(0)) + .commit(); + pruner.prune(segment); + checkDb(jds, "a2", "a3"); assertEquals(0, ((HashMapDB) jds.journal).getStorage().size()); @@ -166,30 +223,208 @@ public void forkTest2() { @Test public void forkTest3() { StringJDS jds = new StringJDS(); + Pruner pruner = new Pruner(jds.getJournal(), jds.db); + pruner.init(); putKeys(jds, "a1"); jds.put("a2"); - jds.commitUpdates(hashInt(1)); + pruner.feed(jds.commitUpdates(hashInt(1))); jds.put("a1"); jds.put("a2"); jds.put("a3"); - jds.commitUpdates(hashInt(2)); + pruner.feed(jds.commitUpdates(hashInt(2))); jds.put("a1"); jds.put("a2"); jds.put("a3"); - jds.commitUpdates(hashInt(3)); + pruner.feed(jds.commitUpdates(hashInt(3))); + pruner.feed(jds.commitUpdates(hashInt(4))); checkDb(jds, "a1", "a2", "a3"); - jds.persistUpdate(hashInt(1)); - jds.revertUpdate(hashInt(2)); - jds.revertUpdate(hashInt(3)); + Segment segment = new Segment(0, hashInt(0), hashInt(0)); + segment.startTracking() + .addMain(1, hashInt(1), hashInt(0)) + .addItem(1, hashInt(2), hashInt(0)) + .addItem(1, hashInt(3), hashInt(0)) + .addMain(2, hashInt(4), hashInt(1)) + .commit(); + pruner.prune(segment); + checkDb(jds, "a1", "a2"); assertEquals(0, ((HashMapDB) jds.journal).getStorage().size()); } + @Test + public void twoStepTest1() { + StringJDS jds = new StringJDS(); + Pruner pruner = new Pruner(jds.getJournal(), jds.db); + pruner.init(); + pruner.withSecondStep(Collections.emptyList(), 100); + + putKeys(jds, "a1", "a2", "a3"); + pruner.feed(jds.commitUpdates(hashInt(0))); + + jds.put("a4"); + jds.put("a1"); + jds.delete("a2"); + pruner.feed(jds.commitUpdates(hashInt(1))); + jds.put("a5"); + jds.delete("a3"); + jds.put("a1"); + pruner.feed(jds.commitUpdates(hashInt(2))); + pruner.feed(jds.commitUpdates(hashInt(3))); // complete segment + + checkDb(jds, "a1", "a2", "a3", "a4", "a5"); + + Segment segment = new Segment(0, hashInt(0), hashInt(0)); + segment.startTracking() + .addMain(0, hashInt(0), hashInt(0)) + .commit(); + pruner.prune(segment); + + segment = new Segment(0, hashInt(0), hashInt(-1)); + segment.startTracking() + .addMain(1, hashInt(1), hashInt(0)) + .addItem(1, hashInt(2), hashInt(0)) + .addMain(2, hashInt(3), hashInt(1)) + .commit(); + pruner.prune(segment); + + pruner.persist(hashInt(0)); + checkDb(jds, "a1", "a2", "a3", "a4"); + + pruner.persist(hashInt(1)); + checkDb(jds, "a1", "a3", "a4"); + + pruner.persist(hashInt(3)); + assertEquals(0, ((HashMapDB) jds.journal).getStorage().size()); + } + + @Test + public void twoStepTest2() { + StringJDS jds = new StringJDS(); + Pruner pruner = new Pruner(jds.getJournal(), jds.db); + pruner.init(); + pruner.withSecondStep(Collections.emptyList(), 100); + + putKeys(jds, "a1", "a2", "a3"); + pruner.feed(jds.commitUpdates(hashInt(0))); + + jds.put("a4"); + jds.delete("a2"); + jds.delete("a1"); + pruner.feed(jds.commitUpdates(hashInt(1))); + jds.put("a2"); + jds.delete("a3"); + pruner.feed(jds.commitUpdates(hashInt(2))); + jds.put("a5"); + jds.delete("a2"); + pruner.feed(jds.commitUpdates(hashInt(3))); + jds.put("a5"); + jds.put("a6"); + jds.delete("a4"); + pruner.feed(jds.commitUpdates(hashInt(31))); + pruner.feed(jds.commitUpdates(hashInt(4))); + + checkDb(jds, "a1", "a2", "a3", "a4", "a5", "a6"); + + Segment segment = new Segment(0, hashInt(0), hashInt(0)); + segment.startTracking() + .addMain(0, hashInt(0), hashInt(0)) + .addMain(1, hashInt(1), hashInt(0)) + .addMain(2, hashInt(2), hashInt(1)) + .commit(); + pruner.prune(segment); + + pruner.persist(hashInt(0)); + checkDb(jds, "a1", "a2", "a3", "a4", "a5", "a6"); + + pruner.persist(hashInt(1)); + checkDb(jds, "a2", "a3", "a4", "a5", "a6"); + + pruner.persist(hashInt(2)); + checkDb(jds, "a2", "a4", "a5", "a6"); + + segment = new Segment(2, hashInt(2), hashInt(1)); + segment.startTracking() + .addMain(3, hashInt(3), hashInt(2)) + .addItem(3, hashInt(31), hashInt(2)) + .addMain(4, hashInt(4), hashInt(3)) + .commit(); + pruner.prune(segment); + + pruner.persist(hashInt(3)); + checkDb(jds, "a4", "a5"); + + pruner.persist(hashInt(4)); + assertEquals(0, ((HashMapDB) jds.journal).getStorage().size()); + } + + + @Test + public void twoStepTest3() { + StringJDS jds = new StringJDS(); + Pruner pruner = new Pruner(jds.getJournal(), jds.db); + pruner.init(); + pruner.withSecondStep(Collections.emptyList(), 100); + + putKeys(jds, "a1", "a2", "a3"); + pruner.feed(jds.commitUpdates(hashInt(0))); + + jds.put("a4"); + jds.delete("a2"); + jds.delete("a1"); + pruner.feed(jds.commitUpdates(hashInt(1))); + jds.put("a2"); + jds.delete("a3"); + pruner.feed(jds.commitUpdates(hashInt(2))); + jds.put("a5"); + jds.put("a1"); + jds.delete("a2"); + pruner.feed(jds.commitUpdates(hashInt(3))); + jds.put("a5"); + jds.put("a6"); + jds.delete("a4"); + pruner.feed(jds.commitUpdates(hashInt(31))); + pruner.feed(jds.commitUpdates(hashInt(4))); + + checkDb(jds, "a1", "a2", "a3", "a4", "a5", "a6"); + + Segment segment = new Segment(0, hashInt(0), hashInt(0)); + segment.startTracking() + .addMain(0, hashInt(0), hashInt(0)) + .addMain(1, hashInt(1), hashInt(0)) + .addMain(2, hashInt(2), hashInt(1)) + .commit(); + pruner.prune(segment); + + pruner.persist(hashInt(0)); + checkDb(jds, "a1", "a2", "a3", "a4", "a5", "a6"); + + pruner.persist(hashInt(1)); + checkDb(jds, "a1", "a2", "a3", "a4", "a5", "a6"); + + pruner.persist(hashInt(2)); + checkDb(jds, "a1", "a2", "a4", "a5", "a6"); + + segment = new Segment(2, hashInt(2), hashInt(1)); + segment.startTracking() + .addMain(3, hashInt(3), hashInt(2)) + .addItem(3, hashInt(31), hashInt(2)) + .addMain(4, hashInt(4), hashInt(3)) + .commit(); + pruner.prune(segment); + + pruner.persist(hashInt(3)); + checkDb(jds, "a1", "a4", "a5"); + + pruner.persist(hashInt(4)); + assertEquals(0, ((HashMapDB) jds.journal).getStorage().size()); + } + + public byte[] hashInt(int i) { return HashUtil.sha3(ByteUtil.intToBytes(i)); } diff --git a/ethereumj-core/src/test/java/org/ethereum/datasource/MultiThreadSourcesTest.java b/ethereumj-core/src/test/java/org/ethereum/datasource/MultiThreadSourcesTest.java index 448e04d1f7..2edf39ed98 100644 --- a/ethereumj-core/src/test/java/org/ethereum/datasource/MultiThreadSourcesTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/datasource/MultiThreadSourcesTest.java @@ -89,99 +89,87 @@ public void setNoDelete(boolean noDelete) { this.noDelete = noDelete; } - final Thread readThread = new Thread(new Runnable() { - @Override - public void run() { - try { - while(running) { - int curMax = putCnt.get() - 1; - if (checkCnt.get() >= curMax) { - sleep(10); - continue; - } - assertEquals(str(intToValue(curMax)), str(cache.get(intToKey(curMax)))); - checkCnt.set(curMax); + final Thread readThread = new Thread(() -> { + try { + while(running) { + int curMax = putCnt.get() - 1; + if (checkCnt.get() >= curMax) { + sleep(10); + continue; } - } catch (Throwable e) { - e.printStackTrace(); - failSema.countDown(); + assertEquals(str(intToValue(curMax)), str(cache.get(intToKey(curMax)))); + checkCnt.set(curMax); } + } catch (Throwable e) { + e.printStackTrace(); + failSema.countDown(); } }); - final Thread delThread = new Thread(new Runnable() { - @Override - public void run() { - try { - while(running) { - int toDelete = delCnt.get(); - int curMax = putCnt.get() - 1; + final Thread delThread = new Thread(() -> { + try { + while(running) { + int toDelete = delCnt.get(); + int curMax = putCnt.get() - 1; - if (toDelete > checkCnt.get() || toDelete >= curMax) { - sleep(10); - continue; - } - assertEquals(str(intToValue(toDelete)), str(cache.get(intToKey(toDelete)))); + if (toDelete > checkCnt.get() || toDelete >= curMax) { + sleep(10); + continue; + } + assertEquals(str(intToValue(toDelete)), str(cache.get(intToKey(toDelete)))); - if (isCounting) { - for (int i = 0; i < (toDelete % 5); ++i) { - cache.delete(intToKey(toDelete)); - assertEquals(str(intToValue(toDelete)), str(cache.get(intToKey(toDelete)))); - } + if (isCounting) { + for (int i = 0; i < (toDelete % 5); ++i) { + cache.delete(intToKey(toDelete)); + assertEquals(str(intToValue(toDelete)), str(cache.get(intToKey(toDelete)))); } - - cache.delete(intToKey(toDelete)); - if (isCounting) cache.flush(); - assertNull(cache.get(intToKey(toDelete))); - delCnt.getAndIncrement(); } - } catch (Throwable e) { - e.printStackTrace(); - failSema.countDown(); + + cache.delete(intToKey(toDelete)); + if (isCounting) cache.flush(); + assertNull(cache.get(intToKey(toDelete))); + delCnt.getAndIncrement(); } + } catch (Throwable e) { + e.printStackTrace(); + failSema.countDown(); } }); public void run(long timeout) { - new Thread(new Runnable() { - @Override - public void run() { - try { - while(running) { - int curCnt = putCnt.get(); - cache.put(intToKey(curCnt), intToValue(curCnt)); - if (isCounting) { - for (int i = 0; i < (curCnt % 5); ++i) { - cache.put(intToKey(curCnt), intToValue(curCnt)); - } + new Thread(() -> { + try { + while(running) { + int curCnt = putCnt.get(); + cache.put(intToKey(curCnt), intToValue(curCnt)); + if (isCounting) { + for (int i = 0; i < (curCnt % 5); ++i) { + cache.put(intToKey(curCnt), intToValue(curCnt)); } - putCnt.getAndIncrement(); - if (curCnt == 1) { - readThread.start(); - if (!noDelete) { - delThread.start(); - } + } + putCnt.getAndIncrement(); + if (curCnt == 1) { + readThread.start(); + if (!noDelete) { + delThread.start(); } } - } catch (Throwable e) { - e.printStackTrace(); - failSema.countDown(); } + } catch (Throwable e) { + e.printStackTrace(); + failSema.countDown(); } }).start(); - new Thread(new Runnable() { - @Override - public void run() { - try { - while(running) { - sleep(10); - cache.flush(); - } - } catch (Throwable e) { - e.printStackTrace(); - failSema.countDown(); + new Thread(() -> { + try { + while(running) { + sleep(10); + cache.flush(); } + } catch (Throwable e) { + e.printStackTrace(); + failSema.countDown(); } }).start(); diff --git a/ethereumj-core/src/test/java/org/ethereum/datasource/RocksDbDataSourceTest.java b/ethereumj-core/src/test/java/org/ethereum/datasource/RocksDbDataSourceTest.java new file mode 100644 index 0000000000..fe5da34237 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/datasource/RocksDbDataSourceTest.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.datasource; + +import org.ethereum.datasource.rocksdb.RocksDbDataSource; +import org.junit.Ignore; +import org.junit.Test; +import org.spongycastle.util.encoders.Hex; + +import java.util.HashMap; +import java.util.Map; + +import static org.ethereum.TestUtils.randomBytes; +import static org.junit.Assert.*; + +@Ignore +public class RocksDbDataSourceTest { + + @Test + public void testBatchUpdating() { + RocksDbDataSource dataSource = new RocksDbDataSource("test"); + dataSource.reset(); + + final int batchSize = 100; + Map batch = createBatch(batchSize); + + dataSource.updateBatch(batch); + + assertEquals(batchSize, dataSource.keys().size()); + + for (Map.Entry e : batch.entrySet()) { + assertArrayEquals(e.getValue(), dataSource.get(e.getKey())); + assertArrayEquals(e.getValue(), dataSource.prefixLookup(e.getKey(), NodeKeyCompositor.PREFIX_BYTES)); + } + + + dataSource.close(); + } + + @Test + public void testPutting() { + RocksDbDataSource dataSource = new RocksDbDataSource("test"); + dataSource.reset(); + + byte[] key = randomBytes(32); + dataSource.put(key, randomBytes(32)); + + assertNotNull(dataSource.get(key)); + assertEquals(1, dataSource.keys().size()); + + dataSource.close(); + } + + @Test + public void testPrefixLookup() { + + RocksDbDataSource dataSource = new RocksDbDataSource("test"); + dataSource.reset(); + + byte[] k1 = Hex.decode("a9539c810cc2e8fa20785bdd78ec36cc1dab4b41f0d531e80a5e5fd25c3037ee"); + byte[] k2 = Hex.decode("b25e1b5be78dbadf6c4e817c6d170bbb47e9916f8f6cc4607c5f3819ce98497b"); + + byte[] v1, v2, v3; + v3 = v1 = "v1".getBytes(); + v2 = "v2".getBytes(); + + dataSource.put(k1, v1); + assertArrayEquals(v1, dataSource.get(k1)); + + assertArrayEquals(v1, dataSource.prefixLookup(k1, NodeKeyCompositor.PREFIX_BYTES)); + + dataSource.put(k2, v2); + assertArrayEquals(v2, dataSource.get(k2)); + + assertArrayEquals(v1, dataSource.prefixLookup(k1, NodeKeyCompositor.PREFIX_BYTES)); + assertArrayEquals(v2, dataSource.prefixLookup(k2, NodeKeyCompositor.PREFIX_BYTES)); + + byte[] k3 = Hex.decode("a9539c810cc2e8fa20785bdd78ec36ccb25e1b5be78dbadf6c4e817c6d170bbb"); + byte[] k4 = Hex.decode("a9539c810cc2e8fa20785bdd78ec36cdb25e1b5be78dbadf6c4e817c6d170bbb"); + dataSource.put(k3, v3); + dataSource.put(k4, v3); + assertArrayEquals(v3, dataSource.get(k3)); + assertArrayEquals(v3, dataSource.get(k4)); + + assertArrayEquals(v1, dataSource.prefixLookup(k1, NodeKeyCompositor.PREFIX_BYTES)); + assertArrayEquals(v2, dataSource.prefixLookup(k2, NodeKeyCompositor.PREFIX_BYTES)); + assertArrayEquals(v3, dataSource.prefixLookup(k3, NodeKeyCompositor.PREFIX_BYTES)); + + assertArrayEquals(v3, dataSource.prefixLookup(Hex.decode("a9539c810cc2e8fa20785bdd78ec36cc00000000000000000000000000000000"), NodeKeyCompositor.PREFIX_BYTES)); + assertArrayEquals(v3, dataSource.prefixLookup(Hex.decode("a9539c810cc2e8fa20785bdd78ec36ccb25e1b5be78dbadf6c4e817c6d170bb0"), NodeKeyCompositor.PREFIX_BYTES)); + + assertNull(dataSource.prefixLookup(Hex.decode("a9539c810cc2e8fa20785bdd78ec36c000000000000000000000000000000000"), NodeKeyCompositor.PREFIX_BYTES)); + + dataSource.delete(k2); + assertNull(dataSource.prefixLookup(k2, NodeKeyCompositor.PREFIX_BYTES)); + assertArrayEquals(v3, dataSource.get(k3)); + + dataSource.delete(k3); + assertNull(dataSource.prefixLookup(k2, NodeKeyCompositor.PREFIX_BYTES)); + assertArrayEquals(v1, dataSource.get(k1)); + + dataSource.delete(k1); + assertNull(dataSource.prefixLookup(k1, NodeKeyCompositor.PREFIX_BYTES)); + assertNull(dataSource.prefixLookup(k2, NodeKeyCompositor.PREFIX_BYTES)); + assertNull(dataSource.prefixLookup(k3, NodeKeyCompositor.PREFIX_BYTES)); + + assertNull(dataSource.get(k1)); + assertNull(dataSource.get(k2)); + assertNull(dataSource.get(k3)); + + assertArrayEquals(v3, dataSource.get(k4)); + + dataSource.put(Hex.decode("df92d643f6f19067a6a1cac3c37332d1631be8a462f0c2c41efb60078515ed50"), v1); + assertArrayEquals(dataSource.prefixLookup(Hex.decode("df92d643f6f19067a6a1cac3c37332d1d1b3ede7e2015c259e493a1bff2ed58c"), NodeKeyCompositor.PREFIX_BYTES), v1); + + dataSource.close(); + } + + private static Map createBatch(int batchSize) { + HashMap result = new HashMap<>(); + for (int i = 0; i < batchSize; i++) { + result.put(randomBytes(32), randomBytes(32)); + } + return result; + } + +} \ No newline at end of file diff --git a/ethereumj-core/src/test/java/org/ethereum/datasource/TrieNodeSourceIntegrationTest.java b/ethereumj-core/src/test/java/org/ethereum/datasource/TrieNodeSourceIntegrationTest.java new file mode 100644 index 0000000000..832e8f6194 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/datasource/TrieNodeSourceIntegrationTest.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.datasource; + +import com.typesafe.config.ConfigFactory; +import org.ethereum.config.NoAutoscan; +import org.ethereum.config.SystemProperties; +import org.ethereum.core.Repository; +import org.ethereum.crypto.HashUtil; +import org.ethereum.db.DbFlushManager; +import org.ethereum.db.StateSource; +import org.ethereum.db.prune.Pruner; +import org.ethereum.db.prune.Segment; +import org.ethereum.vm.DataWord; +import org.junit.AfterClass; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.spongycastle.util.encoders.Hex; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.*; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +import java.util.concurrent.ExecutionException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +/** + * @author Mikhail Kalinin + * @since 05.12.2017 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(loader = AnnotationConfigContextLoader.class) +@NoAutoscan +@Ignore +public class TrieNodeSourceIntegrationTest { + + @Autowired @Qualifier("trieNodeSource") + Source trieNodeSource; + + @Autowired + StateSource stateSource; + + @Autowired @Qualifier("defaultRepository") + Repository repository; + + @Autowired + DbFlushManager dbFlushManager; + + @Configuration + @ComponentScan(basePackages = "org.ethereum") + @NoAutoscan + static class ContextConfiguration { + + private final String config = + // no need for discovery in that small network + "peer.discovery.enabled = false \n" + + "sync.enabled = false \n" + + "genesis = genesis-low-difficulty.json \n"; + + @Bean + public SystemProperties systemProperties() { + SystemProperties props = new SystemProperties(); + props.overrideParams(ConfigFactory.parseString(config.replaceAll("'", "\""))); + return props; + } + } + + @AfterClass + public static void cleanup() { + SystemProperties.resetToDefault(); + } + + + @Test + public void testContractStorage() throws ExecutionException, InterruptedException { + + byte[] addr1 = Hex.decode("5c543e7ae0a1104f78406c340e9c64fd9fce5170"); + byte[] addr2 = Hex.decode("8bccc9ba2e5706e24a36dda02ca2a846e39a7bbf"); + + byte[] k1 = HashUtil.sha3("key1".getBytes()), v1 = "value1".getBytes(); + + assertNull(trieNodeSource.get(Hex.decode("4b7fc4d98630bae2133ad002f743124f2ef5d8167f094af0c2b82d3476604055"))); + + repository.addStorageRow(addr1, new DataWord(k1), new DataWord(v1)); + repository.addStorageRow(addr2, new DataWord(k1), new DataWord(v1)); + flushChanges(); + + // Logically we must get a node with key 4b7fc4d98630bae2133ad002f743124f2ef5d8167f094af0c2b82d3476604055 + // and reference counter equal 2, cause both addresses have that node in their storage. + + // Technically we get two nodes with keys produced by NodeKeyCompositor: + // 4b7fc4d98630bae2133ad002f743124f01bb4fbefc0a1a699cd4c20899a2877f + // 4b7fc4d98630bae2133ad002f743124fafe15fa1326e1c0fcd4b67a53eee9537 + // values they hold are equal, logically it is the same value + + assertNull(stateSource.get(Hex.decode("4b7fc4d98630bae2133ad002f743124f2ef5d8167f094af0c2b82d3476604055"))); + + assertEquals("eaa1202cc45dfd615ef5099974383a7c13606186f629a558453bdb2985d005ad174e73878676616c756531", + Hex.toHexString(stateSource.get(Hex.decode("4b7fc4d98630bae2133ad002f743124f01bb4fbefc0a1a699cd4c20899a2877f")))); + assertEquals("eaa1202cc45dfd615ef5099974383a7c13606186f629a558453bdb2985d005ad174e73878676616c756531", + Hex.toHexString(stateSource.get(Hex.decode("4b7fc4d98630bae2133ad002f743124fafe15fa1326e1c0fcd4b67a53eee9537")))); + + // trieNodeSource must return that value supplied with origin key 4b7fc4d98630bae2133ad002f743124f2ef5d8167f094af0c2b82d3476604055 + // it does a prefix search by only first 16 bytes of the key + assertEquals("eaa1202cc45dfd615ef5099974383a7c13606186f629a558453bdb2985d005ad174e73878676616c756531", + Hex.toHexString(trieNodeSource.get(Hex.decode("4b7fc4d98630bae2133ad002f743124f2ef5d8167f094af0c2b82d3476604055")))); + + // one of that storage rows is gonna be removed + repository.addStorageRow(addr1, new DataWord(k1), DataWord.ZERO); + flushChanges(); + + // state doesn't contain a copy of node that belongs to addr1 + assertNull(stateSource.get(Hex.decode("4b7fc4d98630bae2133ad002f743124f01bb4fbefc0a1a699cd4c20899a2877f"))); + // but still able to pick addr2 value + assertEquals("eaa1202cc45dfd615ef5099974383a7c13606186f629a558453bdb2985d005ad174e73878676616c756531", + Hex.toHexString(stateSource.get(Hex.decode("4b7fc4d98630bae2133ad002f743124fafe15fa1326e1c0fcd4b67a53eee9537")))); + // trieNodeSource still able to pick a value by origin key + assertEquals("eaa1202cc45dfd615ef5099974383a7c13606186f629a558453bdb2985d005ad174e73878676616c756531", + Hex.toHexString(trieNodeSource.get(Hex.decode("4b7fc4d98630bae2133ad002f743124f2ef5d8167f094af0c2b82d3476604055")))); + + // remove a copy of value stick to addr2 + repository.addStorageRow(addr2, new DataWord(k1), DataWord.ZERO); + flushChanges(); + + // no source can resolve any of those keys + assertNull(stateSource.get(Hex.decode("4b7fc4d98630bae2133ad002f743124f01bb4fbefc0a1a699cd4c20899a2877f"))); + assertNull(stateSource.get(Hex.decode("4b7fc4d98630bae2133ad002f743124fafe15fa1326e1c0fcd4b67a53eee9537"))); + assertNull(trieNodeSource.get(Hex.decode("4b7fc4d98630bae2133ad002f743124f2ef5d8167f094af0c2b82d3476604055"))); + } + + @Test + public void testContractCode() throws ExecutionException, InterruptedException { + + byte[] addr1 = Hex.decode("5c543e7ae0a1104f78406c340e9c64fd9fce5170"); + byte[] addr2 = Hex.decode("8bccc9ba2e5706e24a36dda02ca2a846e39a7bbf"); + + byte[] code = "contract Foo {}".getBytes(); + byte[] codeHash = HashUtil.sha3(code); + + repository.saveCode(addr1, code); + flushChanges(); + + assertNull(stateSource.get(codeHash)); + assertEquals(Hex.toHexString(code), + Hex.toHexString(stateSource.get(Hex.decode("0827ccfec1b70192ffadbc46e945a9af01bb4fbefc0a1a699cd4c20899a2877f")))); + assertNull(stateSource.get(Hex.decode("0827ccfec1b70192ffadbc46e945a9afafe15fa1326e1c0fcd4b67a53eee9537"))); + assertEquals(Hex.toHexString(code), Hex.toHexString(trieNodeSource.get(codeHash))); + + repository.saveCode(addr2, code); + flushChanges(); + + assertNull(stateSource.get(codeHash)); + assertEquals(Hex.toHexString(code), + Hex.toHexString(stateSource.get(Hex.decode("0827ccfec1b70192ffadbc46e945a9af01bb4fbefc0a1a699cd4c20899a2877f")))); + assertEquals(Hex.toHexString(code), + Hex.toHexString(stateSource.get(Hex.decode("0827ccfec1b70192ffadbc46e945a9afafe15fa1326e1c0fcd4b67a53eee9537")))); + assertEquals(Hex.toHexString(code), Hex.toHexString(trieNodeSource.get(codeHash))); + } + + private void flushChanges() throws ExecutionException, InterruptedException { + repository.commit(); + stateSource.getJournalSource().commitUpdates(HashUtil.EMPTY_DATA_HASH); + + Pruner pruner = new Pruner(stateSource.getJournalSource().getJournal(), stateSource.getNoJournalSource()); + pruner.init(HashUtil.EMPTY_DATA_HASH); + Segment segment = new Segment(0, HashUtil.EMPTY_DATA_HASH, HashUtil.EMPTY_DATA_HASH); + segment.startTracking() + .addMain(1, HashUtil.EMPTY_DATA_HASH, HashUtil.EMPTY_DATA_HASH) + .commit(); + pruner.prune(segment); + + dbFlushManager.flush().get(); + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/datasource/TrieNodeSourceTest.java b/ethereumj-core/src/test/java/org/ethereum/datasource/TrieNodeSourceTest.java new file mode 100644 index 0000000000..afb7d6ca78 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/datasource/TrieNodeSourceTest.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.datasource; + +import org.ethereum.core.Repository; +import org.ethereum.crypto.HashUtil; +import org.ethereum.datasource.inmem.HashMapDB; +import org.ethereum.db.RepositoryRoot; +import org.ethereum.db.StateSource; +import org.ethereum.db.prune.Pruner; +import org.ethereum.db.prune.Segment; +import org.ethereum.vm.DataWord; +import org.junit.Before; +import org.junit.Test; +import org.spongycastle.util.encoders.Hex; + +import java.util.concurrent.ExecutionException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +/** + * @author Mikhail Kalinin + * @since 05.12.2017 + */ +public class TrieNodeSourceTest { + + Source trieNodeSource; + StateSource stateSource; + Repository repository; + HashMapDB db; + AsyncWriteCache stateWriteCache; + + @Before + public void setup() { + db = new HashMapDB<>(); + + stateSource = new StateSource(db, true); + stateWriteCache = (AsyncWriteCache) stateSource.getWriteCache(); + + trieNodeSource = new PrefixLookupSource<>(db, NodeKeyCompositor.PREFIX_BYTES); + + repository = new RepositoryRoot(stateSource); + } + + @Test + public void testContractStorage() throws ExecutionException, InterruptedException { + + byte[] addr1 = Hex.decode("5c543e7ae0a1104f78406c340e9c64fd9fce5170"); + byte[] addr2 = Hex.decode("8bccc9ba2e5706e24a36dda02ca2a846e39a7bbf"); + + byte[] k1 = HashUtil.sha3("key1".getBytes()), v1 = "value1".getBytes(); + + assertNull(trieNodeSource.get(Hex.decode("4b7fc4d98630bae2133ad002f743124f2ef5d8167f094af0c2b82d3476604055"))); + + repository.addStorageRow(addr1, new DataWord(k1), new DataWord(v1)); + repository.addStorageRow(addr2, new DataWord(k1), new DataWord(v1)); + flushChanges(); + + // Logically we must get a node with key 4b7fc4d98630bae2133ad002f743124f2ef5d8167f094af0c2b82d3476604055 + // and reference counter equal 2, cause both addresses have that node in their storage. + + // Technically we get two nodes with keys produced by NodeKeyCompositor: + // 4b7fc4d98630bae2133ad002f743124f01bb4fbefc0a1a699cd4c20899a2877f + // 4b7fc4d98630bae2133ad002f743124fafe15fa1326e1c0fcd4b67a53eee9537 + // values they hold are equal, logically it is the same value + + assertNull(stateSource.get(Hex.decode("4b7fc4d98630bae2133ad002f743124f2ef5d8167f094af0c2b82d3476604055"))); + + assertEquals("eaa1202cc45dfd615ef5099974383a7c13606186f629a558453bdb2985d005ad174e73878676616c756531", + Hex.toHexString(stateSource.get(Hex.decode("4b7fc4d98630bae2133ad002f743124f01bb4fbefc0a1a699cd4c20899a2877f")))); + assertEquals("eaa1202cc45dfd615ef5099974383a7c13606186f629a558453bdb2985d005ad174e73878676616c756531", + Hex.toHexString(stateSource.get(Hex.decode("4b7fc4d98630bae2133ad002f743124fafe15fa1326e1c0fcd4b67a53eee9537")))); + + // trieNodeSource must return that value supplied with origin key 4b7fc4d98630bae2133ad002f743124f2ef5d8167f094af0c2b82d3476604055 + // it does a prefix search by only first 16 bytes of the key + assertEquals("eaa1202cc45dfd615ef5099974383a7c13606186f629a558453bdb2985d005ad174e73878676616c756531", + Hex.toHexString(trieNodeSource.get(Hex.decode("4b7fc4d98630bae2133ad002f743124f2ef5d8167f094af0c2b82d3476604055")))); + + // one of that storage rows is gonna be removed + repository.addStorageRow(addr1, new DataWord(k1), DataWord.ZERO); + flushChanges(); + + // state doesn't contain a copy of node that belongs to addr1 + assertNull(stateSource.get(Hex.decode("4b7fc4d98630bae2133ad002f743124f01bb4fbefc0a1a699cd4c20899a2877f"))); + // but still able to pick addr2 value + assertEquals("eaa1202cc45dfd615ef5099974383a7c13606186f629a558453bdb2985d005ad174e73878676616c756531", + Hex.toHexString(stateSource.get(Hex.decode("4b7fc4d98630bae2133ad002f743124fafe15fa1326e1c0fcd4b67a53eee9537")))); + // trieNodeSource still able to pick a value by origin key + assertEquals("eaa1202cc45dfd615ef5099974383a7c13606186f629a558453bdb2985d005ad174e73878676616c756531", + Hex.toHexString(trieNodeSource.get(Hex.decode("4b7fc4d98630bae2133ad002f743124f2ef5d8167f094af0c2b82d3476604055")))); + + // remove a copy of value stick to addr2 + repository.addStorageRow(addr2, new DataWord(k1), DataWord.ZERO); + flushChanges(); + + // no source can resolve any of those keys + assertNull(stateSource.get(Hex.decode("4b7fc4d98630bae2133ad002f743124f01bb4fbefc0a1a699cd4c20899a2877f"))); + assertNull(stateSource.get(Hex.decode("4b7fc4d98630bae2133ad002f743124fafe15fa1326e1c0fcd4b67a53eee9537"))); + assertNull(trieNodeSource.get(Hex.decode("4b7fc4d98630bae2133ad002f743124f2ef5d8167f094af0c2b82d3476604055"))); + } + + @Test + public void testContractCode() throws ExecutionException, InterruptedException { + + byte[] addr1 = Hex.decode("5c543e7ae0a1104f78406c340e9c64fd9fce5170"); + byte[] addr2 = Hex.decode("8bccc9ba2e5706e24a36dda02ca2a846e39a7bbf"); + + byte[] code = "contract Foo {}".getBytes(); + byte[] codeHash = HashUtil.sha3(code); + + repository.saveCode(addr1, code); + flushChanges(); + + assertNull(stateSource.get(codeHash)); + assertEquals(Hex.toHexString(code), + Hex.toHexString(stateSource.get(Hex.decode("0827ccfec1b70192ffadbc46e945a9af01bb4fbefc0a1a699cd4c20899a2877f")))); + assertNull(stateSource.get(Hex.decode("0827ccfec1b70192ffadbc46e945a9afafe15fa1326e1c0fcd4b67a53eee9537"))); + assertEquals(Hex.toHexString(code), Hex.toHexString(trieNodeSource.get(codeHash))); + + repository.saveCode(addr2, code); + flushChanges(); + + assertNull(stateSource.get(codeHash)); + assertEquals(Hex.toHexString(code), + Hex.toHexString(stateSource.get(Hex.decode("0827ccfec1b70192ffadbc46e945a9af01bb4fbefc0a1a699cd4c20899a2877f")))); + assertEquals(Hex.toHexString(code), + Hex.toHexString(stateSource.get(Hex.decode("0827ccfec1b70192ffadbc46e945a9afafe15fa1326e1c0fcd4b67a53eee9537")))); + assertEquals(Hex.toHexString(code), Hex.toHexString(trieNodeSource.get(codeHash))); + } + + private void flushChanges() throws ExecutionException, InterruptedException { + repository.commit(); + stateSource.getJournalSource().commitUpdates(HashUtil.EMPTY_DATA_HASH); + + Pruner pruner = new Pruner(stateSource.getJournalSource().getJournal(), stateSource.getNoJournalSource()); + pruner.init(HashUtil.EMPTY_DATA_HASH); + Segment segment = new Segment(0, HashUtil.EMPTY_DATA_HASH, HashUtil.EMPTY_DATA_HASH); + segment.startTracking() + .addMain(1, HashUtil.EMPTY_DATA_HASH, HashUtil.EMPTY_DATA_HASH) + .commit(); + pruner.prune(segment); + + stateWriteCache.flipStorage(); + stateWriteCache.flushAsync().get(); + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/db/FlushDbManagerTest.java b/ethereumj-core/src/test/java/org/ethereum/db/FlushDbManagerTest.java index 7caf9cf5b4..76b1863c9a 100644 --- a/ethereumj-core/src/test/java/org/ethereum/db/FlushDbManagerTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/db/FlushDbManagerTest.java @@ -55,76 +55,64 @@ public void testConcurrentCommit() throws Throwable { final CountDownLatch latch = new CountDownLatch(1); final Throwable[] exception = new Throwable[1]; - new Thread() { - @Override - public void run() { - try { - for (int i = 0; i < 300; i++) { - final int i_ = i; - dbFlushManager.commit(new Runnable() { - @Override - public void run() { - cache1.put(intToBytes(i_), intToBytes(i_)); - try { - Thread.sleep(5); - } catch (InterruptedException e) {} - cache2.put(intToBytes(i_), intToBytes(i_)); - } - }); - } - } catch (Throwable e) { - exception[0] = e; - e.printStackTrace(); - } finally { - latch.countDown(); + new Thread(() -> { + try { + for (int i = 0; i < 300; i++) { + final int i_ = i; + dbFlushManager.commit(() -> { + cache1.put(intToBytes(i_), intToBytes(i_)); + try { + Thread.sleep(5); + } catch (InterruptedException e) {} + cache2.put(intToBytes(i_), intToBytes(i_)); + }); } + } catch (Throwable e) { + exception[0] = e; + e.printStackTrace(); + } finally { + latch.countDown(); } - }.start(); + }).start(); - new Thread() { - @Override - public void run() { - try { - while (true) { - dbFlushManager.commit(); - Thread.sleep(5); - } - } catch (Exception e) { - exception[0] = e; - e.printStackTrace(); - } finally { - latch.countDown(); + new Thread(() -> { + try { + while (true) { + dbFlushManager.commit(); + Thread.sleep(5); } + } catch (Exception e) { + exception[0] = e; + e.printStackTrace(); + } finally { + latch.countDown(); } - }.start(); + }).start(); - new Thread() { - @Override - public void run() { - try { - int cnt = 0; - while (true) { - synchronized (dbFlushManager) { - for (; cnt < 300; cnt++) { - byte[] val1 = db1.get(intToBytes(cnt)); - byte[] val2 = db2.get(intToBytes(cnt)); - if (val1 == null) { - Assert.assertNull(val2); - break; - } else { - Assert.assertNotNull(val2); - } + new Thread(() -> { + try { + int cnt = 0; + while (true) { + synchronized (dbFlushManager) { + for (; cnt < 300; cnt++) { + byte[] val1 = db1.get(intToBytes(cnt)); + byte[] val2 = db2.get(intToBytes(cnt)); + if (val1 == null) { + Assert.assertNull(val2); + break; + } else { + Assert.assertNotNull(val2); } } } - } catch (Exception e) { - exception[0] = e; - e.printStackTrace(); - } finally { - latch.countDown(); } + } catch (Exception e) { + exception[0] = e; + e.printStackTrace(); + } finally { + latch.countDown(); } - }.start(); + }).start(); diff --git a/ethereumj-core/src/test/java/org/ethereum/db/IndexedBlockStoreTest.java b/ethereumj-core/src/test/java/org/ethereum/db/IndexedBlockStoreTest.java index 0818b2e9dc..b4b423512e 100644 --- a/ethereumj-core/src/test/java/org/ethereum/db/IndexedBlockStoreTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/db/IndexedBlockStoreTest.java @@ -23,8 +23,8 @@ import org.ethereum.core.Block; import org.ethereum.core.Genesis; import org.ethereum.datasource.DbSource; -import org.ethereum.datasource.leveldb.LevelDbDataSource; import org.ethereum.datasource.inmem.HashMapDB; +import org.ethereum.datasource.rocksdb.RocksDbDataSource; import org.ethereum.util.FileUtil; import org.ethereum.util.blockchain.StandaloneBlockchain; import org.junit.*; @@ -427,10 +427,10 @@ public void test4() throws IOException { String testDir = "test_db_" + bi; SystemProperties.getDefault().setDataBaseDir(testDir); - LevelDbDataSource indexDB = new LevelDbDataSource("index"); + RocksDbDataSource indexDB = new RocksDbDataSource("index"); indexDB.init(); - DbSource blocksDB = new LevelDbDataSource("blocks"); + DbSource blocksDB = new RocksDbDataSource("blocks"); blocksDB.init(); IndexedBlockStore indexedBlockStore = new IndexedBlockStore(); @@ -541,10 +541,10 @@ public void test4() throws IOException { // testing after: REOPEN - indexDB = new LevelDbDataSource("index"); + indexDB = new RocksDbDataSource("index"); indexDB.init(); - blocksDB = new LevelDbDataSource("blocks"); + blocksDB = new RocksDbDataSource("blocks"); blocksDB.init(); indexedBlockStore = new IndexedBlockStore(); @@ -573,10 +573,10 @@ public void test5() throws IOException { String testDir = "test_db_" + bi; SystemProperties.getDefault().setDataBaseDir(testDir); - LevelDbDataSource indexDB = new LevelDbDataSource("index"); + RocksDbDataSource indexDB = new RocksDbDataSource("index"); indexDB.init(); - DbSource blocksDB = new LevelDbDataSource("blocks"); + DbSource blocksDB = new RocksDbDataSource("blocks"); blocksDB.init(); try { @@ -703,10 +703,10 @@ public void test5() throws IOException { // testing after: REOPEN - indexDB = new LevelDbDataSource("index"); + indexDB = new RocksDbDataSource("index"); indexDB.init(); - blocksDB = new LevelDbDataSource("blocks"); + blocksDB = new RocksDbDataSource("blocks"); blocksDB.init(); indexedBlockStore = new IndexedBlockStore(); @@ -737,10 +737,10 @@ public void test6() throws IOException { String testDir = "test_db_" + bi; SystemProperties.getDefault().setDataBaseDir(testDir); - DbSource indexDB = new LevelDbDataSource("index"); + DbSource indexDB = new RocksDbDataSource("index"); indexDB.init(); - DbSource blocksDB = new LevelDbDataSource("blocks"); + DbSource blocksDB = new RocksDbDataSource("blocks"); blocksDB.init(); try { @@ -846,10 +846,10 @@ public void test7() throws IOException { String testDir = "test_db_" + bi; SystemProperties.getDefault().setDataBaseDir(testDir); - DbSource indexDB = new LevelDbDataSource("index"); + DbSource indexDB = new RocksDbDataSource("index"); indexDB.init(); - DbSource blocksDB = new LevelDbDataSource("blocks"); + DbSource blocksDB = new RocksDbDataSource("blocks"); blocksDB.init(); try { @@ -917,10 +917,10 @@ public void test8() throws IOException { String testDir = "test_db_" + bi; SystemProperties.getDefault().setDataBaseDir(testDir); - DbSource indexDB = new LevelDbDataSource("index"); + DbSource indexDB = new RocksDbDataSource("index"); indexDB.init(); - DbSource blocksDB = new LevelDbDataSource("blocks"); + DbSource blocksDB = new RocksDbDataSource("blocks"); blocksDB.init(); try { diff --git a/ethereumj-core/src/test/java/org/ethereum/db/RepositoryTest.java b/ethereumj-core/src/test/java/org/ethereum/db/RepositoryTest.java index 6ff2cc1a90..7995e58f41 100644 --- a/ethereumj-core/src/test/java/org/ethereum/db/RepositoryTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/db/RepositoryTest.java @@ -963,54 +963,48 @@ public void testMultiThread() throws InterruptedException { final CountDownLatch failSema = new CountDownLatch(1); for (int i = 0; i < 10; ++i) { - new Thread(new Runnable() { - @Override - public void run() { - try { - int cnt = 1; - while (running) { - Repository snap = repository.getSnapshotTo(repository.getRoot()).startTracking(); - snap.addBalance(cow, BigInteger.TEN); - snap.addStorageRow(cow, cowKey1, new DataWord(cnt)); - snap.rollback(); - cnt++; - } - } catch (Throwable e) { - e.printStackTrace(); - failSema.countDown(); - } - } - }).start(); - } - - new Thread(new Runnable() { - @Override - public void run() { - int cnt = 1; + new Thread(() -> { try { - while(running) { - Repository track2 = repository.startTracking(); //track - DataWord cVal = new DataWord(cnt); - track2.addStorageRow(cow, cowKey1, cVal); - track2.addBalance(cow, BigInteger.ONE); - track2.commit(); - - repository.flush(); - - assertEquals(BigInteger.valueOf(cnt), repository.getBalance(cow)); - assertEquals(cVal, repository.getStorageValue(cow, cowKey1)); - assertEquals(cowVal0, repository.getStorageValue(cow, cowKey2)); + int cnt = 1; + while (running) { + Repository snap = repository.getSnapshotTo(repository.getRoot()).startTracking(); + snap.addBalance(cow, BigInteger.TEN); + snap.addStorageRow(cow, cowKey1, new DataWord(cnt)); + snap.rollback(); cnt++; } } catch (Throwable e) { e.printStackTrace(); - try { - repository.addStorageRow(cow, cowKey1, new DataWord(123)); - } catch (Exception e1) { - e1.printStackTrace(); - } failSema.countDown(); } + }).start(); + } + + new Thread(() -> { + int cnt = 1; + try { + while(running) { + Repository track21 = repository.startTracking(); //track + DataWord cVal = new DataWord(cnt); + track21.addStorageRow(cow, cowKey1, cVal); + track21.addBalance(cow, BigInteger.ONE); + track21.commit(); + + repository.flush(); + + assertEquals(BigInteger.valueOf(cnt), repository.getBalance(cow)); + assertEquals(cVal, repository.getStorageValue(cow, cowKey1)); + assertEquals(cowVal0, repository.getStorageValue(cow, cowKey2)); + cnt++; + } + } catch (Throwable e) { + e.printStackTrace(); + try { + repository.addStorageRow(cow, cowKey1, new DataWord(123)); + } catch (Exception e1) { + e1.printStackTrace(); + } + failSema.countDown(); } }).start(); diff --git a/ethereumj-core/src/test/java/org/ethereum/db/prune/SegmentTest.java b/ethereumj-core/src/test/java/org/ethereum/db/prune/SegmentTest.java new file mode 100644 index 0000000000..89c38dc4aa --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/db/prune/SegmentTest.java @@ -0,0 +1,247 @@ +package org.ethereum.db.prune; + +import org.junit.Test; + +import static org.ethereum.util.ByteUtil.intToBytes; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Mikhail Kalinin + * @since 31.01.2018 + */ +public class SegmentTest { + + @Test + public void simpleTest() { + Segment s = new Segment(1, intToBytes(1), intToBytes(0)); + + assertEquals(Chain.NULL, s.main); + assertFalse(s.isComplete()); + assertEquals(0, s.getMaxNumber()); + assertEquals(0, s.size()); + + Segment.Tracker t = s.startTracking(); + t.addMain(2, intToBytes(2), intToBytes(1)); + t.commit(); + + assertTrue(s.isComplete()); + assertEquals(2, s.getMaxNumber()); + assertEquals(1, s.size()); + + t = s.startTracking(); + t.addItem(2, intToBytes(21), intToBytes(1)); + t.addItem(2, intToBytes(22), intToBytes(1)); + t.addItem(3, intToBytes(3), intToBytes(2)); + t.addMain(3, intToBytes(3), intToBytes(2)); // should process double adding + t.commit(); + + assertTrue(s.isComplete()); + assertEquals(3, s.getMaxNumber()); + assertEquals(2, s.size()); + assertEquals(2, s.forks.size()); + + t = s.startTracking(); + t.addItem(3, intToBytes(31), intToBytes(21)); + t.commit(); + + assertFalse(s.isComplete()); + assertEquals(3, s.getMaxNumber()); + assertEquals(2, s.size()); + assertEquals(2, s.forks.size()); + } + + @Test // short forks + public void testFork1() { + Segment s = new Segment(1, intToBytes(1), intToBytes(0)); + + s.startTracking() + .addItem(2, intToBytes(2), intToBytes(1)) + .addMain(2, intToBytes(2), intToBytes(1)) + .commit(); + + assertTrue(s.isComplete()); + + s.startTracking() + .addItem(3, intToBytes(31), intToBytes(2)) + .addItem(3, intToBytes(32), intToBytes(2)) + .addItem(3, intToBytes(3), intToBytes(2)) + .addMain(3, intToBytes(3), intToBytes(2)) + .commit(); + + assertFalse(s.isComplete()); + assertEquals(2, s.size()); + assertEquals(2, s.forks.size()); + + s.startTracking() + .addItem(4, intToBytes(41), intToBytes(31)) + .addItem(4, intToBytes(4), intToBytes(3)) + .addMain(4, intToBytes(4), intToBytes(3)) + .commit(); + + assertFalse(s.isComplete()); + assertEquals(3, s.size()); + assertEquals(2, s.forks.size()); + assertEquals(4, s.getMaxNumber()); + + s.startTracking() + .addItem(5, intToBytes(53), intToBytes(4)) + .addItem(5, intToBytes(5), intToBytes(4)) + .addMain(5, intToBytes(5), intToBytes(4)) + .commit(); + + s.startTracking() + .addItem(6, intToBytes(6), intToBytes(5)) + .addMain(6, intToBytes(6), intToBytes(5)) + .commit(); + + assertTrue(s.isComplete()); + assertEquals(5, s.size()); + assertEquals(3, s.forks.size()); + assertEquals(6, s.getMaxNumber()); + } + + @Test // long fork with short forks + public void testFork2() { + Segment s = new Segment(1, intToBytes(1), intToBytes(0)); + + s.startTracking() + .addItem(2, intToBytes(2), intToBytes(1)) + .addMain(2, intToBytes(2), intToBytes(1)) + .commit(); + + assertTrue(s.isComplete()); + + s.startTracking() + .addItem(3, intToBytes(30), intToBytes(2)) + .addItem(3, intToBytes(31), intToBytes(2)) + .addItem(3, intToBytes(3), intToBytes(2)) + .addMain(3, intToBytes(3), intToBytes(2)) + .commit(); + + assertFalse(s.isComplete()); + assertEquals(2, s.size()); + assertEquals(2, s.forks.size()); + + s.startTracking() + .addItem(4, intToBytes(40), intToBytes(30)) + .addItem(4, intToBytes(41), intToBytes(31)) + .addItem(4, intToBytes(42), intToBytes(3)) + .addItem(4, intToBytes(4), intToBytes(3)) + .addMain(4, intToBytes(4), intToBytes(3)) + .commit(); + + assertFalse(s.isComplete()); + assertEquals(3, s.size()); + assertEquals(3, s.forks.size()); + assertEquals(4, s.getMaxNumber()); + + s.startTracking() + .addItem(5, intToBytes(50), intToBytes(40)) + .addItem(5, intToBytes(53), intToBytes(4)) + .addItem(5, intToBytes(5), intToBytes(4)) + .addMain(5, intToBytes(5), intToBytes(4)) + .commit(); + + s.startTracking() + .addItem(6, intToBytes(60), intToBytes(50)) + .addItem(6, intToBytes(6), intToBytes(5)) + .addMain(6, intToBytes(6), intToBytes(5)) + .commit(); + + assertFalse(s.isComplete()); + assertEquals(5, s.size()); + assertEquals(4, s.forks.size()); + assertEquals(6, s.getMaxNumber()); + + s.startTracking() + .addItem(7, intToBytes(7), intToBytes(6)) + .addMain(7, intToBytes(7), intToBytes(6)) + .commit(); + + assertTrue(s.isComplete()); + assertEquals(6, s.size()); + assertEquals(4, s.forks.size()); + assertEquals(7, s.getMaxNumber()); + } + + @Test // several branches started at fork block + public void testFork3() { + /* + 2: 2 -> 3: 3 -> 4: 4 -> 5: 5 -> 6: 6 <-- Main + \ + -> 3: 31 -> 4: 41 + \ -> 5: 53 + \ / + -> 4: 42 -> 5: 52 + \ + -> 5: 54 + */ + + Segment s = new Segment(1, intToBytes(1), intToBytes(0)); + + s.startTracking() + .addItem(2, intToBytes(2), intToBytes(1)) + .addMain(2, intToBytes(2), intToBytes(1)) + .commit(); + + s.startTracking() + .addItem(3, intToBytes(3), intToBytes(2)) + .addItem(3, intToBytes(31), intToBytes(2)) + .addMain(3, intToBytes(3), intToBytes(2)) + .commit(); + + s.startTracking() + .addItem(4, intToBytes(4), intToBytes(3)) + .addItem(4, intToBytes(41), intToBytes(31)) + .addItem(4, intToBytes(42), intToBytes(31)) + .addMain(4, intToBytes(4), intToBytes(3)) + .commit(); + + s.startTracking() + .addItem(5, intToBytes(5), intToBytes(4)) + .addItem(5, intToBytes(52), intToBytes(42)) + .addItem(5, intToBytes(53), intToBytes(42)) + .addItem(5, intToBytes(54), intToBytes(42)) + .addMain(5, intToBytes(5), intToBytes(4)) + .commit(); + + s.startTracking() + .addItem(6, intToBytes(6), intToBytes(5)) + .addMain(6, intToBytes(6), intToBytes(5)) + .commit(); + + Chain main = Chain.fromItems( + new ChainItem(2, intToBytes(2), intToBytes(1)), + new ChainItem(3, intToBytes(3), intToBytes(2)), + new ChainItem(4, intToBytes(4), intToBytes(3)), + new ChainItem(5, intToBytes(5), intToBytes(4)), + new ChainItem(6, intToBytes(6), intToBytes(5)) + ); + + Chain fork1 = Chain.fromItems( + new ChainItem(3, intToBytes(31), intToBytes(2)), + new ChainItem(4, intToBytes(41), intToBytes(31)) + ); + + Chain fork2 = Chain.fromItems( + new ChainItem(4, intToBytes(42), intToBytes(31)), + new ChainItem(5, intToBytes(52), intToBytes(42)) + ); + Chain fork3 = Chain.fromItems( + new ChainItem(5, intToBytes(53), intToBytes(42)) + ); + Chain fork4 = Chain.fromItems( + new ChainItem(5, intToBytes(54), intToBytes(42)) + ); + + assertEquals(main, s.main); + + assertEquals(4, s.forks.size()); + assertEquals(fork1, s.forks.get(0)); + assertEquals(fork2, s.forks.get(1)); + assertEquals(fork3, s.forks.get(2)); + assertEquals(fork4, s.forks.get(3)); + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/jsonrpc/JsonRpcTest.java b/ethereumj-core/src/test/java/org/ethereum/jsonrpc/JsonRpcTest.java deleted file mode 100644 index 64ccc69643..0000000000 --- a/ethereumj-core/src/test/java/org/ethereum/jsonrpc/JsonRpcTest.java +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright (c) [2016] [ ] - * This file is part of the ethereumJ library. - * - * The ethereumJ library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The ethereumJ library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the ethereumJ library. If not, see . - */ -package org.ethereum.jsonrpc; - -import com.typesafe.config.ConfigFactory; -import org.ethereum.config.SystemProperties; -import org.ethereum.config.blockchain.FrontierConfig; -import org.ethereum.core.CallTransaction; -import org.ethereum.core.Transaction; -import org.ethereum.datasource.DbSource; -import org.ethereum.datasource.inmem.HashMapDB; -import org.ethereum.facade.Ethereum; -import org.ethereum.facade.EthereumFactory; -import org.ethereum.facade.EthereumImpl; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Scope; - -import java.math.BigInteger; - -import static java.math.BigInteger.valueOf; -import static org.ethereum.crypto.HashUtil.sha3; -import static org.junit.Assert.*; - -/** - * Created by Anton Nashatyrev on 19.04.2016. - */ -public class JsonRpcTest { - - private static class TestConfig { - - private final String config = - // no need for discovery in that small network - "peer.discovery.enabled = false \n" + - "peer.listen.port = 0 \n" + - // need to have different nodeId's for the peers - "peer.privateKey = 6ef8da380c27cea8fdf7448340ea99e8e2268fc2950d79ed47cbf6f85dc977ec \n" + - // our private net ID - "peer.networkId = 555 \n" + - // we have no peers to sync with - "sync.enabled = false \n" + - // genesis with a lower initial difficulty and some predefined known funded accounts - "genesis = genesis-light.json \n" + - // two peers need to have separate database dirs - "database.dir = sampleDB-1 \n" + - "keyvalue.datasource = inmem \n" + - // when more than 1 miner exist on the network extraData helps to identify the block creator - "mine.extraDataHex = cccccccccccccccccccc \n" + - "mine.fullDataSet = false \n" + - "mine.cpuMineThreads = 2"; - - /** - * Instead of supplying properties via config file for the peer - * we are substituting the corresponding bean which returns required - * config for this instance. - */ - @Bean - public SystemProperties systemProperties() { - SystemProperties props = new SystemProperties(); - props.overrideParams(ConfigFactory.parseString(config.replaceAll("'", "\""))); - FrontierConfig config = new FrontierConfig(new FrontierConfig.FrontierConstants() { - @Override - public BigInteger getMINIMUM_DIFFICULTY() { - return BigInteger.ONE; - } - }); - SystemProperties.getDefault().setBlockchainConfig(config); - props.setBlockchainConfig(config); - return props; - } - - @Bean - public TestRunner test() { - return new TestRunner(); - } - } - - static class TestRunner { - @Autowired - JsonRpc jsonRpc; - - @Autowired - Ethereum ethereum; - -// @PostConstruct - public void runTests() throws Exception { - String cowAcct = jsonRpc.personal_newAccount("cow"); - String bal0 = jsonRpc.eth_getBalance(cowAcct); - System.out.println("Balance: " + bal0); - assertTrue(TypeConverter.StringHexToBigInteger(bal0).compareTo(BigInteger.ZERO) > 0); - - String pendingTxFilterId = jsonRpc.eth_newPendingTransactionFilter(); - Object[] changes = jsonRpc.eth_getFilterChanges(pendingTxFilterId); - assertEquals(0, changes.length); - - JsonRpc.CallArguments ca = new JsonRpc.CallArguments(); - ca.from = cowAcct; - ca.to = "0x0000000000000000000000000000000000001234"; - ca.gas = "0x300000"; - ca.gasPrice = "0x10000000000"; - ca.value = "0x7777"; - ca.data = "0x"; - long sGas = TypeConverter.StringHexToBigInteger(jsonRpc.eth_estimateGas(ca)).longValue(); - - String txHash1 = jsonRpc.eth_sendTransaction(cowAcct, "0x0000000000000000000000000000000000001234", "0x300000", - "0x10000000000", "0x7777", "0x", "0x00"); - System.out.println("Tx hash: " + txHash1); - assertTrue(TypeConverter.StringHexToBigInteger(txHash1).compareTo(BigInteger.ZERO) > 0); - - for (int i = 0; i < 50 && changes.length == 0; i++) { - changes = jsonRpc.eth_getFilterChanges(pendingTxFilterId); - Thread.sleep(200); - } - assertEquals(1, changes.length); - changes = jsonRpc.eth_getFilterChanges(pendingTxFilterId); - assertEquals(0, changes.length); - - JsonRpc.BlockResult blockResult = jsonRpc.eth_getBlockByNumber("pending", true); - System.out.println(blockResult); - assertEquals(txHash1, ((TransactionResultDTO) blockResult.transactions[0]).hash); - - String hash1 = mineBlock(); - - JsonRpc.BlockResult blockResult1 = jsonRpc.eth_getBlockByHash(hash1, true); - assertEquals(hash1, blockResult1.hash); - assertEquals(txHash1, ((TransactionResultDTO) blockResult1.transactions[0]).hash); - TransactionReceiptDTO receipt1 = jsonRpc.eth_getTransactionReceipt(txHash1); - assertEquals(1, receipt1.blockNumber); - assertTrue(receipt1.gasUsed > 0); - assertEquals(sGas, receipt1.gasUsed); - - String bal1 = jsonRpc.eth_getBalance(cowAcct); - System.out.println("Balance: " + bal0); - assertTrue(TypeConverter.StringHexToBigInteger(bal0).compareTo(TypeConverter.StringHexToBigInteger(bal1)) > 0); - - JsonRpc.CompilationResult compRes = jsonRpc.eth_compileSolidity( - "contract A { " + - "uint public num; " + - "function set(uint a) {" + - " num = a; " + - " log1(0x1111, 0x2222);" + - "}}"); - assertEquals(compRes.info.abiDefinition[0].name, "num"); - assertEquals(compRes.info.abiDefinition[1].name, "set"); - assertTrue(compRes.code.length() > 10); - - JsonRpc.CallArguments callArgs = new JsonRpc.CallArguments(); - callArgs.from = cowAcct; - callArgs.data = compRes.code; - callArgs.gasPrice = "0x10000000000"; - callArgs.gas = "0x1000000"; - String txHash2 = jsonRpc.eth_sendTransaction(callArgs); - sGas = TypeConverter.StringHexToBigInteger(jsonRpc.eth_estimateGas(callArgs)).longValue(); - - String hash2 = mineBlock(); - - JsonRpc.BlockResult blockResult2 = jsonRpc.eth_getBlockByHash(hash2, true); - assertEquals(hash2, blockResult2.hash); - assertEquals(txHash2, ((TransactionResultDTO) blockResult2.transactions[0]).hash); - TransactionReceiptDTO receipt2 = jsonRpc.eth_getTransactionReceipt(txHash2); - assertTrue(receipt2.blockNumber > 1); - assertTrue(receipt2.gasUsed > 0); - assertEquals(sGas, receipt2.gasUsed); - assertTrue(TypeConverter.StringHexToByteArray(receipt2.contractAddress).length == 20); - - JsonRpc.FilterRequest filterReq = new JsonRpc.FilterRequest(); - filterReq.topics = new Object[]{"0x2222"}; - filterReq.fromBlock = "latest"; - filterReq.toBlock = "latest"; - String filterId = jsonRpc.eth_newFilter(filterReq); - - CallTransaction.Function function = CallTransaction.Function.fromSignature("set", "uint"); - Transaction rawTx = ethereum.createTransaction(valueOf(2), - valueOf(50_000_000_000L), - valueOf(3_000_000), - TypeConverter.StringHexToByteArray(receipt2.contractAddress), - valueOf(0), function.encode(0x777)); - rawTx.sign(sha3("cow".getBytes())); - - String txHash3 = jsonRpc.eth_sendRawTransaction(TypeConverter.toJsonHex(rawTx.getEncoded())); - - JsonRpc.CallArguments callArgs2= new JsonRpc.CallArguments(); - callArgs2.to = receipt2.contractAddress; - callArgs2.data = TypeConverter.toJsonHex(CallTransaction.Function.fromSignature("num").encode()); - - String ret3 = jsonRpc.eth_call(callArgs2, "pending"); - String ret4 = jsonRpc.eth_call(callArgs2, "latest"); - - String hash3 = mineBlock(); - - JsonRpc.BlockResult blockResult3 = jsonRpc.eth_getBlockByHash(hash3, true); - assertEquals(hash3, blockResult3.hash); - assertEquals(txHash3, ((TransactionResultDTO) blockResult3.transactions[0]).hash); - TransactionReceiptDTO receipt3 = jsonRpc.eth_getTransactionReceipt(txHash3); - assertTrue(receipt3.blockNumber > 2); - assertTrue(receipt3.gasUsed > 0); - - Object[] logs = jsonRpc.eth_getFilterLogs(filterId); - assertEquals(1, logs.length); - assertEquals("0x0000000000000000000000000000000000000000000000000000000000001111", - ((JsonRpc.LogFilterElement)logs[0]).data); - assertEquals(0, jsonRpc.eth_getFilterLogs(filterId).length); - - String ret1 = jsonRpc.eth_call(callArgs2, blockResult2.number); - String ret2 = jsonRpc.eth_call(callArgs2, "latest"); - - assertEquals("0x0000000000000000000000000000000000000000000000000000000000000000", ret1); - assertEquals("0x0000000000000000000000000000000000000000000000000000000000000777", ret2); - assertEquals("0x0000000000000000000000000000000000000000000000000000000000000777", ret3); - assertEquals("0x0000000000000000000000000000000000000000000000000000000000000000", ret4); - } - - String mineBlock() throws InterruptedException { - String blockFilterId = jsonRpc.eth_newBlockFilter(); - jsonRpc.miner_start(); - int cnt = 0; - String hash1; - while (true) { - Object[] blocks = jsonRpc.eth_getFilterChanges(blockFilterId); - cnt += blocks.length; - if (cnt > 0) { - hash1 = (String) blocks[0]; - break; - } - Thread.sleep(100); - } - jsonRpc.miner_stop(); - Thread.sleep(100); - Object[] blocks = jsonRpc.eth_getFilterChanges(blockFilterId); - cnt += blocks.length; - System.out.println(cnt + " blocks mined"); - boolean b = jsonRpc.eth_uninstallFilter(blockFilterId); - assertTrue(b); - return hash1; - } - } - - @Test - public void complexTest() throws Exception { - System.out.println("Starting Ethereum..."); - Ethereum ethereum = EthereumFactory.createEthereum(TestConfig.class); - System.out.println("Ethereum started"); - TestRunner testRunner = ((EthereumImpl) ethereum).getApplicationContext().getBean(TestRunner.class); - System.out.println("Starting test..."); - testRunner.runTests(); - System.out.println("Test complete."); - } -} diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubBasicTest.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubBasicTest.java index eb51cb21de..82ef8a21ae 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubBasicTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubBasicTest.java @@ -38,7 +38,7 @@ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class GitHubBasicTest { - String commitSHA = "182dfacc9c72b014224dc1fac3b9c3331037d7d5"; + String commitSHA = "7f638829311dfc1d341c1db85d8a891f57fa4da7"; @Test public void btCrypto() throws IOException { diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubBlockStateTest.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubBlockStateTest.java index 49787b2edc..3f3f6483d3 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubBlockStateTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubBlockStateTest.java @@ -32,8 +32,8 @@ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class GitHubBlockStateTest { - static String commitSHA = "883d2de442781548d178824db7cfbe0b3fbc0b9a"; - static String treeSHA = "ba612d18c378eacb2709bd19debc883e17d9a998"; // https://github.com/ethereum/tests/tree/develop/BlockchainTests/GeneralStateTests/ + static String commitSHA = "7f638829311dfc1d341c1db85d8a891f57fa4da7"; + static String treeSHA = "9b96943196bfbb8b49651eab5e479956d7dabcc7"; // https://github.com/ethereum/tests/tree/develop/BlockchainTests/GeneralStateTests/ static GitHubJSONTestSuite.Network[] targetNets = { GitHubJSONTestSuite.Network.Frontier, @@ -147,6 +147,11 @@ public void bcStPreCompiledContracts() throws IOException { suite.runAll("stPreCompiledContracts"); } + @Test + public void bcStPreCompiledContracts2() throws IOException { + suite.runAll("stPreCompiledContracts2"); + } + @Test @Ignore public void bcStMemoryStressTest() throws IOException { @@ -261,6 +266,11 @@ public void bcStZeroKnowledge() throws IOException { suite.runAll("stZeroKnowledge"); } + @Test + public void bcStZeroKnowledge2() throws IOException { + suite.runAll("stZeroKnowledge2"); + } + @Test public void bcStCodeSizeLimit() throws IOException { suite.runAll("stCodeSizeLimit"); @@ -271,6 +281,11 @@ public void bcStRandom() throws IOException { suite.runAll("stRandom"); } + @Test + public void bcStRandom2() throws IOException { + suite.runAll("stRandom2"); + } + @Test public void stBadOpcode() throws IOException { suite.runAll("stBadOpcode"); diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubBlockTest.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubBlockTest.java index 3504b8034c..373b6e3e70 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubBlockTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubBlockTest.java @@ -31,8 +31,8 @@ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class GitHubBlockTest { - static String commitSHA = "883d2de442781548d178824db7cfbe0b3fbc0b9a"; - static String treeSHA = "2993be360f64cfb30c82078d4ea296e1fb44f072"; // https://github.com/ethereum/tests/tree/develop/BlockchainTests/ + static String commitSHA = "7f638829311dfc1d341c1db85d8a891f57fa4da7"; + static String treeSHA = "3f6a1117be5c0d6f801875118c7c580dc4200712"; // https://github.com/ethereum/tests/tree/develop/BlockchainTests/ static GitHubJSONTestSuite.Network[] targetNets = { GitHubJSONTestSuite.Network.Frontier, GitHubJSONTestSuite.Network.Homestead, diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubStateTest.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubStateTest.java index cfa5078e52..b51d4566a7 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubStateTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubStateTest.java @@ -29,8 +29,8 @@ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class GitHubStateTest { - static String commitSHA = "883d2de442781548d178824db7cfbe0b3fbc0b9a"; - static String treeSHA = "d08710643dad05f056002b7df492ecb28d943d7c"; // https://github.com/ethereum/tests/tree/develop/GeneralStateTests/ + static String commitSHA = "7f638829311dfc1d341c1db85d8a891f57fa4da7"; + static String treeSHA = "d1ece13ebfb2adb27061ae5a6155bd9ed9773d8f"; // https://github.com/ethereum/tests/tree/develop/GeneralStateTests/ static GitHubJSONTestSuite.Network[] targetNets = { GitHubJSONTestSuite.Network.Frontier, GitHubJSONTestSuite.Network.Homestead, @@ -59,7 +59,7 @@ public static void clean() { // it reduces impact on GitHub API public void stSingleTest() throws IOException { GeneralStateTestSuite.runSingle( - "stCreateTest/TransactionCollisionToEmpty.json", commitSHA, GitHubJSONTestSuite.Network.Byzantium); + "stPreCompiledContracts2/modexpRandomInput.json", commitSHA, GitHubJSONTestSuite.Network.Byzantium); } @Test @@ -146,6 +146,11 @@ public void stPreCompiledContracts() throws IOException { suite.runAll("stPreCompiledContracts"); } + @Test + public void stPreCompiledContracts2() throws IOException { + suite.runAll("stPreCompiledContracts2"); + } + @Test @Ignore public void stMemoryStressTest() throws IOException { @@ -262,6 +267,11 @@ public void stZeroKnowledge() throws IOException { suite.runAll("stZeroKnowledge"); } + @Test + public void stZeroKnowledge2() throws IOException { + suite.runAll("stZeroKnowledge2"); + } + @Test public void stCodeSizeLimit() throws IOException { suite.runAll("stCodeSizeLimit"); @@ -272,6 +282,11 @@ public void stRandom() throws IOException { suite.runAll("stRandom"); } + @Test + public void stRandom2() throws IOException { + suite.runAll("stRandom2"); + } + @Test public void stBadOpcode() throws IOException { suite.runAll("stBadOpcode"); diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubTestNetTest.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubTestNetTest.java index d0324adc66..a4010ef7c9 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubTestNetTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubTestNetTest.java @@ -26,8 +26,8 @@ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class GitHubTestNetTest { - static String commitSHA = "d07aedd65b2f0d9bc26959a67834b4fc7f3dcbbe"; - static String treeSHA = "038011258981cb76bc54c89a538f45078e3decc1"; // https://github.com/ethereum/tests/tree/develop/BlockchainTests/TransitionTests + static String commitSHA = "7f638829311dfc1d341c1db85d8a891f57fa4da7"; + static String treeSHA = "12ee51045ace4a3075e39fe58128fdaa74b3fbd0"; // https://github.com/ethereum/tests/tree/develop/BlockchainTests/TransitionTests static BlockchainTestSuite suite; diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubTransactionTest.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubTransactionTest.java index 1389acbb7b..8ae5d8e725 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubTransactionTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubTransactionTest.java @@ -31,14 +31,15 @@ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class GitHubTransactionTest { - static String commitSHA = "f32ed84a66d4c3c15ececc67e76ebb40c148b478"; - static String treeSHA = "ffa3acff38492d1e53cdf99749a410b4c7f5f285"; // https://github.com/ethereum/tests/tree/develop/TransactionTests/ + static String commitSHA = "7f638829311dfc1d341c1db85d8a891f57fa4da7"; + static String treeSHA = "1405e8a09a6f695e843259b9029b04a3fc4da3fa"; // https://github.com/ethereum/tests/tree/develop/TransactionTests/ static TxTestSuite suite; @BeforeClass public static void setup() throws IOException { suite = new TxTestSuite(treeSHA, commitSHA); + SystemProperties.getDefault().setBlockchainConfig(new MainNetConfig()); } @After diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubVMTest.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubVMTest.java index cfb818ca4d..3d56abac7d 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubVMTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubVMTest.java @@ -29,8 +29,8 @@ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class GitHubVMTest { - static String commitSHA = "ddba26a8c7381345f035d9428d721f5ddba7aaec"; - static String treeSHA = "c676f05f1558da6bad978072a3a779309ef54758"; // https://github.com/ethereum/tests/tree/develop/VMTests/ + static String commitSHA = "7f638829311dfc1d341c1db85d8a891f57fa4da7"; + static String treeSHA = "7429078cc0ae66034bbede7c9666c234dc76bc67"; // https://github.com/ethereum/tests/tree/develop/VMTests/ static VMTestSuite suite; diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GithubABITest.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GithubABITest.java index 4c62cc9a35..47fd6ab28d 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GithubABITest.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GithubABITest.java @@ -1,3 +1,20 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ package org.ethereum.jsontestsuite; import org.junit.FixMethodOrder; diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GithubTrieTest.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GithubTrieTest.java index 221a805327..15fb45c1c1 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GithubTrieTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GithubTrieTest.java @@ -1,3 +1,20 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ package org.ethereum.jsontestsuite; import org.junit.FixMethodOrder; @@ -15,7 +32,7 @@ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class GithubTrieTest { - String commitSHA = "5ab5e0cac99e982d27f42300364c3d085ed9172d"; + String commitSHA = "6581e1fc35b1d0fdec86bd4eea552a7c742d6bca"; @Test public void hexEncodedSecureTrieTest() throws IOException { diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/ABITestSuite.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/ABITestSuite.java index fe6ad9f67d..fcfae41fac 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/ABITestSuite.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/ABITestSuite.java @@ -43,12 +43,7 @@ public ABITestSuite(String json) throws IOException { testCases.add(e.getValue()); } - Collections.sort(testCases, new Comparator() { - @Override - public int compare(ABITestCase t1, ABITestCase t2) { - return t1.getName().compareTo(t2.getName()); - } - }); + testCases.sort(Comparator.comparing(ABITestCase::getName)); } public List getTestCases() { diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/BlockchainTestSuite.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/BlockchainTestSuite.java index f5bd60616b..c1fbc823b0 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/BlockchainTestSuite.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/BlockchainTestSuite.java @@ -57,6 +57,7 @@ public BlockchainTestSuite(String treeSHA, String commitSHA) throws IOException } public void setSubDir(String subDir) { + if (!subDir.endsWith("/")) subDir = subDir + "/"; this.subDir = subDir; } @@ -137,7 +138,11 @@ public void runAll(String testCaseRoot, Set excludedCases, GitHubJSONTes List testCaseFiles = new ArrayList<>(); for (String file : files) { - if (file.startsWith(testCaseRoot + "/")) testCaseFiles.add(subDir + file); + if (file.startsWith(testCaseRoot + "/")) { + testCaseFiles.add(subDir + file); + } else if (file.startsWith(subDir + testCaseRoot + "/")) { + testCaseFiles.add(file); + } } Set toExclude = new HashSet<>(); diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/DifficultyTestSuite.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/DifficultyTestSuite.java index a37c4d63ad..457b2dde82 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/DifficultyTestSuite.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/DifficultyTestSuite.java @@ -43,12 +43,7 @@ public DifficultyTestSuite(String json) throws IOException { testCases.add(e.getValue()); } - Collections.sort(testCases, new Comparator() { - @Override - public int compare(DifficultyTestCase t1, DifficultyTestCase t2) { - return t1.getName().compareTo(t2.getName()); - } - }); + testCases.sort(Comparator.comparing(DifficultyTestCase::getName)); } public List getTestCases() { diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/JSONReader.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/JSONReader.java index 1da9a40e7c..624df357e6 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/JSONReader.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/JSONReader.java @@ -44,21 +44,18 @@ public class JSONReader { static ExecutorService threadPool; + private static int MAX_RETRIES = 3; + public static List loadJSONsFromCommit(List filenames, final String shacommit) { - int threads = 64; + int threads = 16; if (threadPool == null) { threadPool = Executors.newFixedThreadPool(threads); } List> retF = new ArrayList<>(); for (final String filename : filenames) { - Future f = threadPool.submit(new Callable() { - @Override - public String call() throws Exception { - return loadJSONFromCommit(filename, shacommit); - } - }); + Future f = threadPool.submit(() -> loadJSONFromCommit(filename, shacommit)); retF.add(f); } @@ -67,7 +64,8 @@ public String call() throws Exception { try { ret.add(f.get()); } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); + throw new RuntimeException(String.format("Failed to retrieve %d files from commit %s", + filenames.size(), shacommit), e); } } @@ -93,6 +91,27 @@ public static String getFromLocal(String filename) throws IOException { } public static String getFromUrl(String urlToRead) { + String result = null; + for (int i = 0; i < MAX_RETRIES; ++i) { + try { + result = getFromUrlImpl(urlToRead); + break; + } catch (Exception ex) { + logger.debug(String.format("Failed to retrieve %s, retry %d/%d", urlToRead, (i + 1), MAX_RETRIES), ex); + if (i < (MAX_RETRIES - 1)) { + try { + Thread.sleep(2000); // adding delay after fail + } catch (InterruptedException e) { + } + } + } + } + if (result == null) throw new RuntimeException(String.format("Failed to retrieve file from url %s", urlToRead)); + + return result; + } + + private static String getFromUrlImpl(String urlToRead) throws Exception { URL url; HttpURLConnection conn; BufferedReader rd; @@ -113,7 +132,8 @@ public static String getFromUrl(String urlToRead) { } rd.close(); } catch (Throwable e) { - e.printStackTrace(); + logger.debug("Failed to retrieve file.", e); + throw e; } return result.toString(); } diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/StateTestData.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/StateTestData.java index 5a798b06a2..9817aa4a80 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/StateTestData.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/StateTestData.java @@ -1,3 +1,20 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ package org.ethereum.jsontestsuite.suite; import org.codehaus.jackson.map.ObjectMapper; diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/StateTestDataEntry.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/StateTestDataEntry.java index 38a662551a..6956a47e02 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/StateTestDataEntry.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/StateTestDataEntry.java @@ -1,3 +1,20 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ package org.ethereum.jsontestsuite.suite; import org.codehaus.jackson.annotate.JsonIgnoreProperties; diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/TrieTestSuite.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/TrieTestSuite.java index be1d88bdb3..52a5bef305 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/TrieTestSuite.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/TrieTestSuite.java @@ -43,12 +43,7 @@ public TrieTestSuite(String json) throws IOException { testCases.add(e.getValue()); } - Collections.sort(testCases, new Comparator() { - @Override - public int compare(TrieTestCase t1, TrieTestCase t2) { - return t1.getName().compareTo(t2.getName()); - } - }); + testCases.sort(Comparator.comparing(TrieTestCase::getName)); } public List getTestCases() { diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/model/PostDataTck.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/model/PostDataTck.java index f4ee0d6480..3d74350180 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/model/PostDataTck.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/model/PostDataTck.java @@ -1,3 +1,20 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ package org.ethereum.jsontestsuite.suite.model; import org.codehaus.jackson.annotate.JsonIgnoreProperties; diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/model/TransactionDataTck.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/model/TransactionDataTck.java index ef9dcf87ef..29be0eb831 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/model/TransactionDataTck.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/model/TransactionDataTck.java @@ -1,3 +1,20 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ package org.ethereum.jsontestsuite.suite.model; import org.codehaus.jackson.annotate.JsonIgnoreProperties; diff --git a/ethereumj-core/src/test/java/org/ethereum/longrun/BlockchainValidation.java b/ethereumj-core/src/test/java/org/ethereum/longrun/BlockchainValidation.java index 43c5cfebf9..2df8524b41 100644 --- a/ethereumj-core/src/test/java/org/ethereum/longrun/BlockchainValidation.java +++ b/ethereumj-core/src/test/java/org/ethereum/longrun/BlockchainValidation.java @@ -28,7 +28,7 @@ import org.ethereum.core.TransactionReceipt; import org.ethereum.crypto.HashUtil; import org.ethereum.datasource.DataSourceArray; -import org.ethereum.datasource.Serializers; +import org.ethereum.datasource.NodeKeyCompositor; import org.ethereum.datasource.Source; import org.ethereum.datasource.SourceCodec; import org.ethereum.db.BlockStore; @@ -36,7 +36,6 @@ import org.ethereum.trie.SecureTrie; import org.ethereum.trie.TrieImpl; import org.ethereum.util.FastByteComparisons; -import org.ethereum.util.Value; import org.junit.Assert; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -74,7 +73,8 @@ public void doOnValue(byte[] nodeHash, TrieImpl.Node node, byte[] key, byte[] va ret.incrementAndGet(); } if (!FastByteComparisons.equal(accountState.getStateRoot(), HashUtil.EMPTY_TRIE_HASH)) { - ret.addAndGet(getReferencedTrieNodes(stateDS, false, accountState.getStateRoot())); + NodeKeyCompositor nodeKeyCompositor = new NodeKeyCompositor(key); + ret.addAndGet(getReferencedTrieNodes(new SourceCodec.KeyOnly<>(stateDS, nodeKeyCompositor), false, accountState.getStateRoot())); } } } @@ -87,7 +87,7 @@ public static void checkNodes(Ethereum ethereum, CommonConfig commonConfig, Atom try { Source stateDS = commonConfig.stateSource(); byte[] stateRoot = ethereum.getBlockchain().getBestBlock().getHeader().getStateRoot(); - Integer rootsSize = getReferencedTrieNodes(stateDS, true, stateRoot); + int rootsSize = TrieTraversal.ofState(stateDS, stateRoot, true).go(); testLogger.info("Node validation successful"); testLogger.info("Non-unique node size: {}", rootsSize); } catch (Exception | AssertionError ex) { diff --git a/ethereumj-core/src/test/java/org/ethereum/longrun/FastSyncSanityTest.java b/ethereumj-core/src/test/java/org/ethereum/longrun/FastSyncSanityTest.java index 2974c442e9..95b9dce26f 100644 --- a/ethereumj-core/src/test/java/org/ethereum/longrun/FastSyncSanityTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/longrun/FastSyncSanityTest.java @@ -23,15 +23,18 @@ import org.ethereum.config.SystemProperties; import org.ethereum.facade.Ethereum; import org.ethereum.facade.EthereumFactory; +import org.ethereum.listener.EthereumListener; +import org.ethereum.sync.SyncManager; import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; +import java.util.EnumSet; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -58,7 +61,7 @@ public class FastSyncSanityTest { private Ethereum regularNode; private static AtomicBoolean firstRun = new AtomicBoolean(true); - private static AtomicBoolean secondRun = new AtomicBoolean(true); + private static EnumSet statesCompleted = EnumSet.noneOf(EthereumListener.SyncState.class); private static final Logger testLogger = LoggerFactory.getLogger("TestLogger"); private static final MutableObject configPath = new MutableObject<>("longrun/conf/ropsten-fast.conf"); private static final MutableObject resetDBOnFirstRun = new MutableObject<>(null); @@ -75,16 +78,13 @@ public FastSyncSanityTest() throws Exception { } if (overrideConfigPath != null) configPath.setValue(overrideConfigPath); - statTimer.scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - try { - if (fatalErrors.get() > 0) { - statTimer.shutdownNow(); - } - } catch (Throwable t) { - FastSyncSanityTest.testLogger.error("Unhandled exception", t); + statTimer.scheduleAtFixedRate(() -> { + try { + if (fatalErrors.get() > 0) { + statTimer.shutdownNow(); } + } catch (Throwable t) { + FastSyncSanityTest.testLogger.error("Unhandled exception", t); } }, 0, 15, TimeUnit.SECONDS); } @@ -119,6 +119,10 @@ public SystemProperties systemProperties() { * Just regular EthereumJ node */ static class RegularNode extends BasicNode { + + @Autowired + SyncManager syncManager; + public RegularNode() { super("sampleNode"); } @@ -128,6 +132,27 @@ private void stopSync() { config.setDiscoveryEnabled(false); ethereum.getChannelManager().close(); syncPool.close(); + syncManager.close(); + } + + private void firstRunChecks() throws InterruptedException { + + if (!statesCompleted.containsAll(EnumSet.of(EthereumListener.SyncState.UNSECURE, + EthereumListener.SyncState.SECURE))) + return; + + sleep(60000); + stopSync(); + + testLogger.info("Validating nodes: Start"); + BlockchainValidation.checkNodes(ethereum, commonConfig, fatalErrors); + testLogger.info("Validating nodes: End"); + + testLogger.info("Validating block headers: Start"); + BlockchainValidation.checkFastHeaders(ethereum, commonConfig, fatalErrors); + testLogger.info("Validating block headers: End"); + + firstRun.set(false); } @Override @@ -139,20 +164,16 @@ public void waitForSync() throws Exception { switch (syncState) { case UNSECURE: - if (!firstRun.get()) break; + if (!firstRun.get() || statesCompleted.contains(EthereumListener.SyncState.UNSECURE)) break; testLogger.info("[v] Unsecure sync completed"); - sleep(60000); - stopSync(); - BlockchainValidation.checkNodes(ethereum, commonConfig, fatalErrors); - firstRun.set(false); + statesCompleted.add(EthereumListener.SyncState.UNSECURE); + firstRunChecks(); break; case SECURE: - if (!secondRun.get()) break; + if (!firstRun.get() || statesCompleted.contains(EthereumListener.SyncState.SECURE)) break; testLogger.info("[v] Secure sync completed"); - sleep(60000); - stopSync(); - BlockchainValidation.checkFastHeaders(ethereum, commonConfig, fatalErrors); - secondRun.set(false); + statesCompleted.add(EthereumListener.SyncState.SECURE); + firstRunChecks(); break; case COMPLETE: testLogger.info("[v] Sync complete! The best block: " + bestBlock.getShortDescr()); @@ -171,14 +192,10 @@ public void onSyncDone() throws Exception { private final static AtomicInteger fatalErrors = new AtomicInteger(0); - private final static long MAX_RUN_MINUTES = 180L; + private final static long MAX_RUN_MINUTES = 1440L; private static ScheduledExecutorService statTimer = - Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { - public Thread newThread(Runnable r) { - return new Thread(r, "StatTimer"); - } - }); + Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "StatTimer")); private static boolean logStats() { testLogger.info("---------====---------"); @@ -200,9 +217,7 @@ public void testTripleCheck() throws Exception { runEthereum(); - new Thread(new Runnable() { - @Override - public void run() { + new Thread(() -> { try { while(firstRun.get()) { sleep(1000); @@ -213,25 +228,15 @@ public void run() { sleep(10_000); testLogger.info("Starting second run"); runEthereum(); - while(secondRun.get()) { - sleep(1000); - } - testLogger.info("Stopping second run"); - regularNode.close(); - testLogger.info("Second run stopped"); - sleep(10_000); - testLogger.info("Starting third run"); - runEthereum(); while(!allChecksAreOver.get()) { sleep(1000); } - testLogger.info("Stopping third run"); + testLogger.info("Stopping second run"); regularNode.close(); testLogger.info("All checks are finished"); } catch (Throwable e) { e.printStackTrace(); } - } }).start(); if(statTimer.awaitTermination(MAX_RUN_MINUTES, TimeUnit.MINUTES)) { diff --git a/ethereumj-core/src/test/java/org/ethereum/longrun/HardExitSanityTest.java b/ethereumj-core/src/test/java/org/ethereum/longrun/HardExitSanityTest.java new file mode 100644 index 0000000000..b77d7be688 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/longrun/HardExitSanityTest.java @@ -0,0 +1,318 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.longrun; + +import com.typesafe.config.ConfigFactory; +import org.apache.commons.lang3.mutable.MutableObject; +import org.ethereum.config.CommonConfig; +import org.ethereum.config.SystemProperties; +import org.ethereum.facade.Ethereum; +import org.ethereum.facade.EthereumFactory; +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.util.Random; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import static java.lang.Thread.sleep; +import static org.junit.Assert.assertNotNull; + +/** + * Sync with sanity check + * + * Runs sync with defined config + * - checks that State Trie is not broken + * - checks whether all blocks are in blockstore, validates parent connection and bodies + * - checks and validates transaction receipts + * + * Stopped from time to time via process killing to replicate + * most complicated conditions for application + * + * Run with '-Dlogback.configurationFile=longrun/logback.xml' for proper logging + * *** NOTE: this test uses standard output for discovering node process pid, but if you run test using Gradle, + * it will put away standard streams, so test is unable to work. To solve the issue, you need to add + * "showStandardStreams = true" line and extend events with 'standard_out', 'standard_error' + * in test.testLogging section of build.gradle + * Also following flags are supported: + * -Doverride.config.res=longrun/conf/live.conf + * -Dnode.run.cmd="./gradlew run" + */ +@Ignore +public class HardExitSanityTest { + + private Ethereum checkNode; + private static AtomicBoolean checkInProgress = new AtomicBoolean(false); + private static final Logger testLogger = LoggerFactory.getLogger("TestLogger"); + // Database path and type of two following configurations should match, so check will run over the same DB + private static final MutableObject configPath = new MutableObject<>("longrun/conf/live.conf"); + private String nodeRunCmd = "./gradlew run"; // Test made to use configuration started from Gradle + private Process proc; + + private final static AtomicInteger fatalErrors = new AtomicInteger(0); + + private final static long MAX_RUN_MINUTES = 3 * 24 * 60L; // Maximum running time + + private static ScheduledExecutorService statTimer = + Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "StatTimer")); + + + public HardExitSanityTest() throws Exception { + String overrideNodeRunCmd = System.getProperty("node.run.cmd"); + if (overrideNodeRunCmd != null) { + nodeRunCmd = overrideNodeRunCmd; + } + testLogger.info("Test will run EthereumJ using command: {}", nodeRunCmd); + + String overrideConfigPath = System.getProperty("override.config.res"); + if (overrideConfigPath != null) { + configPath.setValue(overrideConfigPath); + } + + // Catching errors in separate thread + statTimer.scheduleAtFixedRate(() -> { + try { + if (fatalErrors.get() > 0) { + statTimer.shutdownNow(); + } + } catch (Throwable t) { + HardExitSanityTest.testLogger.error("Unhandled exception", t); + } + }, 0, 1, TimeUnit.SECONDS); + } + + private static boolean logStats() { + testLogger.info("---------====---------"); + testLogger.info("fatalErrors: {}", fatalErrors); + testLogger.info("---------====---------"); + + return fatalErrors.get() == 0; + } + + + /** + * Spring configuration class for the Regular peer + * - Peer will not sync + * - Peer will run sanity check + */ + private static class SanityCheckConfig { + + @Bean + public SyncSanityTest.RegularNode node() { + return new SyncSanityTest.RegularNode() { + @Override + public void run() { + testLogger.info("Begin sanity check for EthereumJ, best block [{}]", ethereum.getBlockchain().getBestBlock().getNumber()); + fullSanityCheck(ethereum, commonConfig); + checkInProgress.set(false); + } + }; + } + + /** + * Instead of supplying properties via config file for the peer + * we are substituting the corresponding bean which returns required + * config for this instance. + */ + @Bean + public SystemProperties systemProperties() { + SystemProperties props = new SystemProperties(); + props.overrideParams(ConfigFactory.parseResources(configPath.getValue())); + props.setDatabaseReset(false); + props.setSyncEnabled(false); + props.setDiscoveryEnabled(false); + return props; + } + } + + private static void fullSanityCheck(Ethereum ethereum, CommonConfig commonConfig) { + BlockchainValidation.fullCheck(ethereum, commonConfig, fatalErrors); + logStats(); + } + + @Test + public void testMain() throws Exception { + + System.out.println("Test started"); + Thread main = new Thread(() -> { + try { + while (true) { + Random rnd = new Random(); + int runDistance = 60 * 5 + rnd.nextInt(60 * 5); // 5 - 10 minutes + testLogger.info("Running EthereumJ node for {} seconds", runDistance); + startEthereumJ(); + TimeUnit.SECONDS.sleep(runDistance); + killEthereumJ(); + sleep(2000); + + checkInProgress.set(true); + testLogger.info("Starting EthereumJ sanity check instance"); + this.checkNode = EthereumFactory.createEthereum(SanityCheckConfig.class); + while (checkInProgress.get()) { + sleep(1000); + } + checkNode.close(); + testLogger.info("Sanity check is over", runDistance); + } + } catch (Throwable e) { + e.printStackTrace(); + } + }); + main.start(); + + if(statTimer.awaitTermination(MAX_RUN_MINUTES, TimeUnit.MINUTES)) { + if (!checkInProgress.get()) { + killEthereumJ(); + } + while (checkInProgress.get()) { + sleep(1000); + } + assert logStats(); + } + } + + private void startEthereumJ() { + try { + File dir = new File(System.getProperty("user.dir")); + if (dir.toString().contains("ethereumj-core")) { + dir = new File(dir.getParent()); + } + String javaHomePath = System.getenv("JAVA_HOME"); + proc = Runtime.getRuntime().exec(nodeRunCmd, new String[] {"JAVA_HOME=" + javaHomePath}, dir); + flushOutput(proc); + testLogger.info("EthereumJ started, pid {}", getUnixPID(proc)); + // Uncomment following line for debugging purposes +// System.out.print(getProcOutput(proc)); + } catch (Exception ex) { + testLogger.error("Error during starting of main EthereumJ using cmd: " + nodeRunCmd, ex); + fatalErrors.addAndGet(1); + } + } + + private int getChildPID(int processPID) throws Exception { + try { + ProcessBuilder builder = new ProcessBuilder("pgrep", "-P", "" + processPID); + builder.redirectErrorStream(true); + Process getPID = builder.start(); + String output = getProcOutput(getPID); + String pidPart = output.substring(0, output.indexOf('\n')); + Integer ret = new Integer(pidPart); + + if (ret <= 0) { + throw new RuntimeException("Incorrect child PID detected"); + } + + return ret; + } catch (Exception ex) { + testLogger.error("Failed to get child PID of gradle", ex); + throw new RuntimeException("Cannot get child PID for parent #" + processPID); + } + } + + + /** + * Just eats process output and does nothing with eat + * Some applications could hang if output buffer is not emptied from time to time, + * probably, when they have synchronized output + */ + private void flushOutput(Process process) throws Exception { + new Thread(() -> { + String line = null; + try (BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + while ((line = input.readLine()) != null) { + } + } catch (IOException e) { + e.printStackTrace(); + } + }).start(); + new Thread(() -> { + String line = null; + try (BufferedReader input = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { + while ((line = input.readLine()) != null) { + } + } catch (IOException e) { + e.printStackTrace(); + } + }).start(); + } + + private String getProcOutput(Process process) throws Exception { + StringBuilder buffer = new StringBuilder(); + Thread listener = new Thread(() -> { + String line = null; + try (BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + while ((line = input.readLine()) != null) { + buffer.append(line); + buffer.append('\n'); + } + + process.waitFor(); + } catch (Exception e) { + e.printStackTrace(); + } + }); + listener.start(); + listener.join(); + + return buffer.toString(); + } + + private void killEthereumJ() { + try { + if (!proc.isAlive()) { + testLogger.warn("Not killing EthereumJ, already finished."); + return; + } + testLogger.info("Killing EthereumJ"); + // Gradle (PID) -> Java app (another PID) + if (killUnixProcess(getChildPID(getUnixPID(proc))) != 0) { + throw new RuntimeException("Killing EthereunJ was not successful"); + } + } catch (Exception ex) { + testLogger.error("Error during shutting down of main EthereumJ", ex); + fatalErrors.addAndGet(1); + } + } + + private static int getUnixPID(Process process) throws Exception { + if (process.getClass().getName().equals("java.lang.UNIXProcess")) { + Class cl = process.getClass(); + Field field = cl.getDeclaredField("pid"); + field.setAccessible(true); + Object pidObject = field.get(process); + return (Integer) pidObject; + } else { + throw new IllegalArgumentException("Needs to be a UNIXProcess"); + } + } + + private static int killUnixProcess(int pid) throws Exception { + return Runtime.getRuntime().exec("kill -9 " + pid).waitFor(); + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/longrun/PendingTxMonitor.java b/ethereumj-core/src/test/java/org/ethereum/longrun/PendingTxMonitor.java deleted file mode 100644 index 8eb5d6e60a..0000000000 --- a/ethereumj-core/src/test/java/org/ethereum/longrun/PendingTxMonitor.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (c) [2016] [ ] - * This file is part of the ethereumJ library. - * - * The ethereumJ library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The ethereumJ library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the ethereumJ library. If not, see . - */ -package org.ethereum.longrun; - -import com.googlecode.jsonrpc4j.JsonRpcHttpClient; -import com.googlecode.jsonrpc4j.ProxyUtil; -import com.typesafe.config.ConfigFactory; -import org.apache.commons.lang3.tuple.Pair; -import org.apache.commons.lang3.tuple.Triple; -import org.ethereum.config.SystemProperties; -import org.ethereum.core.Block; -import org.ethereum.core.TransactionReceipt; -import org.ethereum.facade.EthereumFactory; -import org.ethereum.jsonrpc.JsonRpc; -import org.ethereum.jsonrpc.TransactionResultDTO; -import org.ethereum.listener.EthereumListener; -import org.ethereum.listener.EthereumListenerAdapter; -import org.ethereum.util.ByteArrayMap; -import org.junit.Ignore; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.spongycastle.util.encoders.Hex; -import org.springframework.context.annotation.Bean; - -import java.net.URL; -import java.util.HashSet; - -/** - * Matches pending transactions from EthereumJ and any other JSON-RPC client - * - * Created by Anton Nashatyrev on 15.02.2017. - */ -@Ignore -public class PendingTxMonitor extends BasicNode { - private static final Logger testLogger = LoggerFactory.getLogger("TestLogger"); - - /** - * Spring configuration class for the Regular peer - */ - private static class RegularConfig { - - @Bean - public PendingTxMonitor node() { - return new PendingTxMonitor(); - } - - /** - * Instead of supplying properties via config file for the peer - * we are substituting the corresponding bean which returns required - * config for this instance. - */ - @Bean - public SystemProperties systemProperties() { - SystemProperties props = new SystemProperties(); - props.overrideParams(ConfigFactory.parseString( - "peer.discovery.enabled = true\n" + - "sync.enabled = true\n" + - "sync.fast.enabled = true\n" + - "database.dir = database-test-ptx\n" + - "database.reset = false\n" - )); - return props; - } - } - - public PendingTxMonitor() { - super("sampleNode"); - } - - @Override - public void run() { - try { - setupRemoteRpc(); - } catch (Exception e) { - throw new RuntimeException(e); - } - super.run(); - } - - private void setupRemoteRpc() throws Exception { - System.out.println("Creating RPC interface..."); - JsonRpcHttpClient httpClient = new JsonRpcHttpClient(new URL("http://localhost:8545")); - final JsonRpc jsonRpc = ProxyUtil.createClientProxy(getClass().getClassLoader(), JsonRpc.class, httpClient); - System.out.println("Pinging remote RPC..."); - String protocolVersion = jsonRpc.eth_protocolVersion(); - System.out.println("Remote OK. Version: " + protocolVersion); - - final String pTxFilterId = jsonRpc.eth_newPendingTransactionFilter(); - - new Thread(new Runnable() { - @Override - public void run() { - try { - while (Boolean.TRUE) { - Object[] changes = jsonRpc.eth_getFilterChanges(pTxFilterId); - if (changes.length > 0) { - for (Object change : changes) { - TransactionResultDTO tx = jsonRpc.eth_getTransactionByHash((String) change); - newRemotePendingTx(tx); - } - } - Thread.sleep(100); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - }).start(); - } - - @Override - public void onSyncDoneImpl(EthereumListener.SyncState state) { - super.onSyncDoneImpl(state); - if (remoteTxs == null) { - remoteTxs = new ByteArrayMap<>(); - System.out.println("Sync Done!!!"); - ethereum.addListener(new EthereumListenerAdapter() { - @Override - public void onPendingTransactionUpdate(TransactionReceipt txReceipt, PendingTransactionState state, Block block) { - PendingTxMonitor.this.onPendingTransactionUpdate(txReceipt, state, block); - } - }); - } - } - - ByteArrayMap> localTxs = new ByteArrayMap<>(); - ByteArrayMap> remoteTxs; - - private void checkUnmatched() { - for (byte[] txHash : new HashSet<>(localTxs.keySet())) { - Triple tx = localTxs.get(txHash); - if (System.currentTimeMillis() - tx.getLeft() > 60_000) { - localTxs.remove(txHash); - System.err.println("Local tx doesn't match: " + tx.getMiddle().getTransaction()); - } - } - - for (byte[] txHash : new HashSet<>(remoteTxs.keySet())) { - Pair tx = remoteTxs.get(txHash); - if (System.currentTimeMillis() - tx.getLeft() > 60_000) { - remoteTxs.remove(txHash); - System.err.println("Remote tx doesn't match: " + tx.getRight()); - } - } - - } - - private void onPendingTransactionUpdate(TransactionReceipt txReceipt, EthereumListener.PendingTransactionState state, Block block) { - byte[] txHash = txReceipt.getTransaction().getHash(); - Pair removed = remoteTxs.remove(txHash); - if (state == EthereumListener.PendingTransactionState.DROPPED) { - if (localTxs.remove(txHash) != null) { - System.out.println("Dropped due to timeout (matchned: " + (removed != null) + "): " + Hex.toHexString(txHash)); - } else { - if (remoteTxs.containsKey(txHash)) { - System.err.println("Dropped but matching: " + Hex.toHexString(txHash) + ": \n" + txReceipt); - } - } - } else if (state == EthereumListener.PendingTransactionState.NEW_PENDING) { - System.out.println("Local: " + Hex.toHexString(txHash)); - if (removed == null) { - localTxs.put(txHash, Triple.of(System.currentTimeMillis(), txReceipt, state)); - } else { - System.out.println("Tx matched: " + Hex.toHexString(txHash)); - } - } - checkUnmatched(); - } - - public void newRemotePendingTx(TransactionResultDTO tx) { - byte[] txHash = Hex.decode(tx.hash.substring(2)); - if (remoteTxs == null) return; - System.out.println("Remote: " + Hex.toHexString(txHash)); - Triple removed = localTxs.remove(txHash); - if (removed == null) { - remoteTxs.put(txHash, Pair.of(System.currentTimeMillis(), tx)); - } else { - System.out.println("Tx matched: " + Hex.toHexString(txHash)); - } - checkUnmatched(); - } - - @Test - public void test() throws Exception { - testLogger.info("Starting EthereumJ regular instance!"); - EthereumFactory.createEthereum(RegularConfig.class); - - Thread.sleep(100000000000L); - } -} diff --git a/ethereumj-core/src/test/java/org/ethereum/longrun/SyncSanityTest.java b/ethereumj-core/src/test/java/org/ethereum/longrun/SyncSanityTest.java index 6de0f1c1c6..6d4c88b4b0 100644 --- a/ethereumj-core/src/test/java/org/ethereum/longrun/SyncSanityTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/longrun/SyncSanityTest.java @@ -73,16 +73,13 @@ public SyncSanityTest() throws Exception { } if (overrideConfigPath != null) configPath.setValue(overrideConfigPath); - statTimer.scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - try { - if (fatalErrors.get() > 0) { - statTimer.shutdownNow(); - } - } catch (Throwable t) { - SyncSanityTest.testLogger.error("Unhandled exception", t); + statTimer.scheduleAtFixedRate(() -> { + try { + if (fatalErrors.get() > 0) { + statTimer.shutdownNow(); } + } catch (Throwable t) { + SyncSanityTest.testLogger.error("Unhandled exception", t); } }, 0, 15, TimeUnit.SECONDS); } @@ -153,11 +150,7 @@ public void onSyncDone() throws Exception { private final static long MAX_RUN_MINUTES = 180L; private static ScheduledExecutorService statTimer = - Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { - public Thread newThread(Runnable r) { - return new Thread(r, "StatTimer"); - } - }); + Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "StatTimer")); private static boolean logStats() { testLogger.info("---------====---------"); @@ -185,9 +178,7 @@ public void testDoubleCheck() throws Exception { runEthereum(); - new Thread(new Runnable() { - @Override - public void run() { + new Thread(() -> { try { while(firstRun.get()) { sleep(1000); @@ -201,7 +192,6 @@ public void run() { } catch (Throwable e) { e.printStackTrace(); } - } }).start(); if(statTimer.awaitTermination(MAX_RUN_MINUTES, TimeUnit.MINUTES)) { diff --git a/ethereumj-core/src/test/java/org/ethereum/longrun/SyncWithLoadTest.java b/ethereumj-core/src/test/java/org/ethereum/longrun/SyncWithLoadTest.java index 4b115d8ed3..f37a607b48 100644 --- a/ethereumj-core/src/test/java/org/ethereum/longrun/SyncWithLoadTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/longrun/SyncWithLoadTest.java @@ -102,28 +102,25 @@ public SyncWithLoadTest() throws Exception { } if (overrideConfigPath != null) configPath.setValue(overrideConfigPath); - statTimer.scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - // Adds error if no successfully imported blocks for LAST_IMPORT_TIMEOUT - long currentMillis = System.currentTimeMillis(); - if (lastImport.get() != 0 && currentMillis - lastImport.get() > LAST_IMPORT_TIMEOUT) { - testLogger.error("No imported block for {} seconds", LAST_IMPORT_TIMEOUT / 1000); - fatalErrors.incrementAndGet(); - } + statTimer.scheduleAtFixedRate(() -> { + // Adds error if no successfully imported blocks for LAST_IMPORT_TIMEOUT + long currentMillis = System.currentTimeMillis(); + if (lastImport.get() != 0 && currentMillis - lastImport.get() > LAST_IMPORT_TIMEOUT) { + testLogger.error("No imported block for {} seconds", LAST_IMPORT_TIMEOUT / 1000); + fatalErrors.incrementAndGet(); + } - try { - if (fatalErrors.get() > 0) { - statTimer.shutdownNow(); - errorLatch.countDown(); - } - } catch (Throwable t) { - SyncWithLoadTest.testLogger.error("Unhandled exception", t); + try { + if (fatalErrors.get() > 0) { + statTimer.shutdownNow(); + errorLatch.countDown(); } - - if (lastImport.get() == 0 && isRunning.get()) lastImport.set(currentMillis); - if (lastImport.get() != 0 && !isRunning.get()) lastImport.set(0); + } catch (Throwable t) { + SyncWithLoadTest.testLogger.error("Unhandled exception", t); } + + if (lastImport.get() == 0 && isRunning.get()) lastImport.set(currentMillis); + if (lastImport.get() != 0 && !isRunning.get()) lastImport.set(0); }, 0, 15, TimeUnit.SECONDS); } @@ -263,11 +260,7 @@ public void run() { private final static AtomicInteger fatalErrors = new AtomicInteger(0); private static ScheduledExecutorService statTimer = - Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { - public Thread newThread(Runnable r) { - return new Thread(r, "StatTimer"); - } - }); + Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "StatTimer")); private static boolean logStats() { testLogger.info("---------====---------"); @@ -290,9 +283,7 @@ public void testDelayedCheck() throws Exception { runEthereum(); - new Thread(new Runnable() { - @Override - public void run() { + new Thread(() -> { try { while(firstRun.get()) { sleep(1000); @@ -313,7 +304,6 @@ public void run() { } catch (Throwable e) { e.printStackTrace(); } - } }).start(); errorLatch.await(); diff --git a/ethereumj-core/src/test/java/org/ethereum/longrun/TrieTraversal.java b/ethereumj-core/src/test/java/org/ethereum/longrun/TrieTraversal.java new file mode 100644 index 0000000000..3532806d60 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/longrun/TrieTraversal.java @@ -0,0 +1,314 @@ +package org.ethereum.longrun; + +import org.ethereum.core.AccountState; +import org.ethereum.crypto.HashUtil; +import org.ethereum.datasource.NodeKeyCompositor; +import org.ethereum.datasource.Source; +import org.ethereum.datasource.SourceCodec; +import org.ethereum.trie.SecureTrie; +import org.ethereum.trie.TrieImpl; +import org.ethereum.util.FastByteComparisons; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static java.lang.Runtime.getRuntime; + +/** + * @author Mikhail Kalinin + * @since 16.01.2018 + */ +public abstract class TrieTraversal { + + protected static final Logger logger = LoggerFactory.getLogger("TestLogger"); + + final byte[] root; + final Source src; + final TraversalStats stats; + final AtomicInteger nodesCount = new AtomicInteger(0); + + protected TrieTraversal(final Source src, final byte[] root, final TraversalStats stats) { + this.src = src; + this.root = root; + this.stats = stats; + } + + private static class TraversalStats { + long startedAt = System.currentTimeMillis(); + long updatedAt = System.currentTimeMillis(); + int checkpoint = 0; + int passed = 0; + } + + public static TrieTraversal ofState(final Source src, final byte[] root, boolean includeAccounts) { + return new StateTraversal(src, root, includeAccounts); + } + + public static TrieTraversal ofStorage(final Source src, final byte[] stateRoot, final byte[] address) { + + TrieImpl stateTrie = new SecureTrie(src, stateRoot); + byte[] encoded = stateTrie.get(address); + if (encoded == null) { + logger.error("Account {} does not exist", Hex.toHexString(address)); + throw new RuntimeException("Account does not exist"); + } + + AccountState state = new AccountState(encoded); + if (FastByteComparisons.equal(state.getStateRoot(), HashUtil.EMPTY_TRIE_HASH)) { + logger.error("Storage of account {} is empty", Hex.toHexString(address)); + throw new RuntimeException("Account storage is empty"); + } + + final NodeKeyCompositor nodeKeyCompositor = new NodeKeyCompositor(address); + return new StorageTraversal( + new SourceCodec.KeyOnly<>(src, nodeKeyCompositor), + state.getStateRoot(), + new TraversalStats(), + address, + true + ); + } + + public int go() { + + onStartImpl(); + stats.startedAt = System.currentTimeMillis(); + + SecureTrie trie = new SecureTrie(src, root); + trie.scanTree(new TrieImpl.ScanAction() { + @Override + public void doOnNode(byte[] hash, TrieImpl.Node node) { + nodesCount.incrementAndGet(); + ++stats.passed; + onNodeImpl(hash, node); + } + + @Override + public void doOnValue(byte[] nodeHash, TrieImpl.Node node, byte[] key, byte[] value) { + if (nodeHash == null) { // other value nodes are counted in doOnNode call + nodesCount.incrementAndGet(); + ++stats.passed; + } + onValueImpl(nodeHash, node, key, value); + } + }); + + onEndImpl(); + + return nodesCount.get(); + } + + protected abstract void onNodeImpl(byte[] hash, TrieImpl.Node node); + protected abstract void onValueImpl(byte[] nodeHash, TrieImpl.Node node, byte[] key, byte[] value); + protected abstract void onStartImpl(); + protected abstract void onEndImpl(); + + static class StateTraversal extends TrieTraversal { + + final boolean includeAccounts; + + private Thread statsLogger; + + private static final int MAX_THREADS = 20; + private BlockingQueue traversalQueue; + private ThreadPoolExecutor traversalExecutor; + private final Object mutex = new Object(); + + private void setupAsyncGroup() { + traversalQueue = new LinkedBlockingQueue<>(); + traversalExecutor = new ThreadPoolExecutor(MAX_THREADS, MAX_THREADS, + 0L, TimeUnit.MILLISECONDS, + traversalQueue); + } + + private void shutdownAsyncGroup() { + traversalQueue.clear(); + traversalExecutor.shutdownNow(); + try { + traversalExecutor.awaitTermination(60, TimeUnit.MINUTES); + } catch (InterruptedException e) { + logger.error("Validating nodes: traversal has been interrupted", e); + throw new RuntimeException("Traversal has been interrupted", e); + } + } + + private void waitForAsyncJobs() { + try { + synchronized (mutex) { + while (traversalExecutor.getActiveCount() > 0 || traversalQueue.size() > 0) { + logger.info("Validating nodes: waiting for incomplete jobs: running {}, pending {}", + traversalExecutor.getActiveCount(), traversalQueue.size()); + mutex.wait(); + } + } + } catch (InterruptedException e) { + logger.error("Validating nodes: traversal has been interrupted", e); + throw new RuntimeException("Traversal has been interrupted", e); + } + } + + private StateTraversal(final Source src, final byte[] root, boolean includeAccounts) { + super(src, root, new TraversalStats()); + this.includeAccounts = includeAccounts; + } + + @Override + protected void onNodeImpl(byte[] hash, TrieImpl.Node node) { + } + + @Override + protected void onValueImpl(byte[] nodeHash, TrieImpl.Node node, byte[] key, byte[] value) { + if (includeAccounts) { + final AccountState accountState = new AccountState(value); + if (!FastByteComparisons.equal(accountState.getCodeHash(), HashUtil.EMPTY_DATA_HASH)) { + nodesCount.incrementAndGet(); + ++stats.passed; + assert (null != src.get(NodeKeyCompositor.compose(accountState.getCodeHash(), key))); + } + if (!FastByteComparisons.equal(accountState.getStateRoot(), HashUtil.EMPTY_TRIE_HASH)) { + logger.trace("Validating nodes: new storage discovered {}", HashUtil.shortHash(key)); + final NodeKeyCompositor nodeKeyCompositor = new NodeKeyCompositor(key); + final StorageTraversal storage = new StorageTraversal(new SourceCodec.KeyOnly<>(src, nodeKeyCompositor), + accountState.getStateRoot(), stats, key); + + synchronized (mutex) { + try { + while (traversalExecutor.getActiveCount() > maxThreads()) mutex.wait(); + } catch (InterruptedException e) { + logger.error("Validating nodes: traversal has been interrupted", e); + throw new RuntimeException("Traversal has been interrupted", e); + } + traversalExecutor.submit(() -> { + nodesCount.addAndGet(storage.go()); + synchronized (mutex) { + mutex.notifyAll(); + } + }); + } + } + } + } + + private int maxThreads() { + double freeMemRatio = freeMemRatio(); + if (freeMemRatio < 0.1) { + return MAX_THREADS / 8; + } else if (freeMemRatio < 0.2) { + return MAX_THREADS / 4; + } else { + return MAX_THREADS; + } + } + + private double freeMemRatio() { + + long free = getRuntime().freeMemory(); + long total = getRuntime().totalMemory(); + long max = getRuntime().maxMemory(); + + if ((double) total / max < 0.9) { + return 1; + } else { + return (double) free / total; + } + } + + @Override + protected void onStartImpl() { + runStatsLogger(); + setupAsyncGroup(); + } + + private void runStatsLogger() { + statsLogger = new Thread(() -> { + while (!Thread.currentThread().isInterrupted()) { + try { + Thread.sleep(30000); + long cur = System.currentTimeMillis(); + logger.info("Validating nodes: running for " + ((cur - stats.startedAt) / 1000) + " sec, " + stats.passed + " passed, " + + String.format("%.2f nodes/sec", (double) (stats.passed - stats.checkpoint) / (cur - stats.updatedAt) * 1000) + + ", storage threads " + (traversalExecutor != null ? traversalExecutor.getActiveCount() : 0) + "/" + maxThreads()); + stats.checkpoint = stats.passed; + stats.updatedAt = cur; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + }); + statsLogger.start(); + } + + @Override + protected void onEndImpl() { + waitForAsyncJobs(); + shutdownAsyncGroup(); + statsLogger.interrupt(); + + logger.info("Validating nodes: traversal completed in " + ((System.currentTimeMillis() - stats.startedAt) / 1000) + " sec, " + + stats.passed + " nodes passed"); + } + } + + static class StorageTraversal extends TrieTraversal { + + final byte[] stateAddressOrHash; + boolean logStats = false; + + private StorageTraversal(final Source src, final byte[] root, + TraversalStats stats, final byte[] stateAddressOrHash) { + super(src, root, stats); + this.stateAddressOrHash = stateAddressOrHash; + } + + private StorageTraversal(final Source src, final byte[] root, + TraversalStats stats, final byte[] stateAddressOrHash, boolean logStats) { + super(src, root, stats); + this.stateAddressOrHash = stateAddressOrHash; + this.logStats = logStats; + } + + @Override + protected void onNodeImpl(byte[] hash, TrieImpl.Node node) { + logStatsImpl(); + } + + @Override + protected void onValueImpl(byte[] nodeHash, TrieImpl.Node node, byte[] key, byte[] value) { + logStatsImpl(); + } + + private void logStatsImpl() { + if (!logStats) return; + + if (stats.passed % 10000 == 0 && stats.passed > stats.checkpoint) { + long cur = System.currentTimeMillis(); + logger.info("Validating storage " + HashUtil.shortHash(stateAddressOrHash) + + ": running for " + ((cur - stats.startedAt) / 1000) + " sec, " + stats.passed + " passed, " + + String.format("%.2f nodes/sec", (double) (stats.passed - stats.checkpoint) / (cur - stats.updatedAt) * 1000)); + stats.checkpoint = stats.passed; + stats.updatedAt = cur; + } + } + + @Override + protected void onStartImpl() { + logger.trace("Validating nodes: start traversing {} storage", + stateAddressOrHash != null ? HashUtil.shortHash(stateAddressOrHash) : "unknown"); + } + + @Override + protected void onEndImpl() { + logger.trace("Validating nodes: end traversing {} storage", + stateAddressOrHash != null ? HashUtil.shortHash(stateAddressOrHash) : "unknown"); + if (logStats) + logger.info("Validating storage " + HashUtil.shortHash(stateAddressOrHash) + + ": completed in " + ((System.currentTimeMillis() - stats.startedAt) / 1000) + " sec, " + stats.passed + " nodes passed"); + } + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/mine/FutureTest.java b/ethereumj-core/src/test/java/org/ethereum/mine/FutureTest.java index 2626be5e34..a38bec47e1 100644 --- a/ethereumj-core/src/test/java/org/ethereum/mine/FutureTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/mine/FutureTest.java @@ -32,31 +32,25 @@ public class FutureTest { @Test public void interruptTest() throws InterruptedException { ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); - final ListenableFuture future = executor.submit(new Callable() { - @Override - public Object call() throws Exception { + final ListenableFuture future = executor.submit(() -> { // try { - System.out.println("Waiting"); - Thread.sleep(10000); - System.out.println("Complete"); - return null; + System.out.println("Waiting"); + Thread.sleep(10000); + System.out.println("Complete"); + return null; // } catch (Exception e) { // e.printStackTrace(); // throw e; // } - } }); - future.addListener(new Runnable() { - @Override - public void run() { - System.out.println("Listener: " + future.isCancelled() + ", " + future.isDone()); - try { - future.get(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throw new RuntimeException(e); - } + future.addListener(() -> { + System.out.println("Listener: " + future.isCancelled() + ", " + future.isDone()); + try { + future.get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw new RuntimeException(e); } }, MoreExecutors.sameThreadExecutor()); @@ -69,22 +63,19 @@ public void guavaExecutor() throws ExecutionException, InterruptedException { // ListeningExecutorService executor = MoreExecutors.listeningDecorator( // new ThreadPoolExecutor(2, 16, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue())); ExecutorService executor = - new ThreadPoolExecutor(16, 16, 1L, TimeUnit.SECONDS, new ArrayBlockingQueue(1)); + new ThreadPoolExecutor(16, 16, 1L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1)); Future future = null; for (int i = 0; i < 4; i++) { final int ii = i; - future = executor.submit(new Callable() { - @Override - public Object call() throws Exception { - try { - System.out.println("Waiting " + ii); - Thread.sleep(5000); - System.out.println("Complete " + ii); - return null; - } catch (Exception e) { - e.printStackTrace(); - throw e; - } + future = executor.submit(() -> { + try { + System.out.println("Waiting " + ii); + Thread.sleep(5000); + System.out.println("Complete " + ii); + return null; + } catch (Exception e) { + e.printStackTrace(); + throw e; } }); } @@ -94,7 +85,7 @@ public Object call() throws Exception { @Test public void anyFutureTest() throws ExecutionException, InterruptedException { ListeningExecutorService executor = MoreExecutors.listeningDecorator( - new ThreadPoolExecutor(16, 16, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue())); + new ThreadPoolExecutor(16, 16, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<>())); AnyFuture anyFuture = new AnyFuture() { @Override protected void postProcess(Integer integer) { @@ -103,14 +94,11 @@ protected void postProcess(Integer integer) { }; for (int i = 0; i < 4; i++) { final int ii = i; - ListenableFuture future = executor.submit(new Callable() { - @Override - public Integer call() throws Exception { - System.out.println("Waiting " + ii); - Thread.sleep(5000 - ii * 500); - System.out.println("Complete " + ii); - return ii; - } + ListenableFuture future = executor.submit(() -> { + System.out.println("Waiting " + ii); + Thread.sleep(5000 - ii * 500); + System.out.println("Complete " + ii); + return ii; }); anyFuture.add(future); } @@ -127,14 +115,11 @@ protected void postProcess(Integer integer) { }; for (int i = 0; i < 4; i++) { final int ii = i; - ListenableFuture future = executor.submit(new Callable() { - @Override - public Integer call() throws Exception { - System.out.println("Waiting " + ii); - Thread.sleep(5000 - ii * 500); - System.out.println("Complete " + ii); - return ii; - } + ListenableFuture future = executor.submit(() -> { + System.out.println("Waiting " + ii); + Thread.sleep(5000 - ii * 500); + System.out.println("Complete " + ii); + return ii; }); anyFuture.add(future); } diff --git a/ethereumj-core/src/test/java/org/ethereum/net/TwoPeerTest.java b/ethereumj-core/src/test/java/org/ethereum/net/TwoPeerTest.java index d07ea34904..5ebc95ec98 100644 --- a/ethereumj-core/src/test/java/org/ethereum/net/TwoPeerTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/net/TwoPeerTest.java @@ -66,7 +66,7 @@ public Eth62 eth62() { // return new Eth62(); } - static SystemProperties props = new SystemProperties();; + static SystemProperties props = new SystemProperties(); @Bean public SystemProperties systemProperties() { return props; diff --git a/ethereumj-core/src/test/java/org/ethereum/net/UdpTest.java b/ethereumj-core/src/test/java/org/ethereum/net/UdpTest.java index 9c35b5a4ae..5aad5a76bc 100644 --- a/ethereumj-core/src/test/java/org/ethereum/net/UdpTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/net/UdpTest.java @@ -30,7 +30,6 @@ import org.ethereum.net.rlpx.Message; import org.ethereum.net.rlpx.discover.DiscoveryEvent; import org.ethereum.net.rlpx.discover.PacketDecoder; -import org.ethereum.util.Functional; import org.junit.Ignore; import org.junit.Test; import org.spongycastle.util.encoders.Hex; @@ -39,6 +38,7 @@ import java.net.InetSocketAddress; import java.net.Socket; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import static org.ethereum.util.ByteUtil.longToBytesNoLeadZeroes; @@ -66,7 +66,7 @@ public class UdpTest { private final SimpleNodeManager nodeManager = new SimpleNodeManager(); private class SimpleMessageHandler extends SimpleChannelInboundHandler - implements Functional.Consumer { + implements Consumer { Channel channel; diff --git a/ethereumj-core/src/test/java/org/ethereum/net/eth/handler/LockBlockchainTest.java b/ethereumj-core/src/test/java/org/ethereum/net/eth/handler/LockBlockchainTest.java index 40f983b380..3b0c03011e 100644 --- a/ethereumj-core/src/test/java/org/ethereum/net/eth/handler/LockBlockchainTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/net/eth/handler/LockBlockchainTest.java @@ -143,22 +143,16 @@ public SystemProperties systemProperties() { @Test public synchronized void testHeadersWithoutSkip() throws FileNotFoundException, InterruptedException { ExecutorService executor1 = Executors.newSingleThreadExecutor(); - executor1.submit(new Runnable() { - @Override - public void run() { - blockchain.isBlockExist(null); - } - } + executor1.submit(() -> { + blockchain.isBlockExist(null); + } ); this.wait(DELAY); ExecutorService executor2 = Executors.newSingleThreadExecutor(); - executor2.submit(new Runnable() { - @Override - public void run() { - GetBlockHeadersMessage msg = new GetBlockHeadersMessage(1L, new byte[0], 10, 0, false); - SysPropConfig1.testHandler.processGetBlockHeaders(msg); - } - } + executor2.submit(() -> { + GetBlockHeadersMessage msg = new GetBlockHeadersMessage(1L, new byte[0], 10, 0, false); + SysPropConfig1.testHandler.processGetBlockHeaders(msg); + } ); this.wait(DELAY); assert result.get(); @@ -167,22 +161,16 @@ public void run() { @Test public synchronized void testHeadersWithSkip() throws FileNotFoundException, InterruptedException { ExecutorService executor1 = Executors.newSingleThreadExecutor(); - executor1.submit(new Runnable() { - @Override - public void run() { - blockchain.isBlockExist(null); - } - } + executor1.submit(() -> { + blockchain.isBlockExist(null); + } ); this.wait(DELAY); ExecutorService executor2 = Executors.newSingleThreadExecutor(); - executor2.submit(new Runnable() { - @Override - public void run() { - GetBlockHeadersMessage msg = new GetBlockHeadersMessage(1L, new byte[0], 10, 5, false); - SysPropConfig1.testHandler.processGetBlockHeaders(msg); - } - } + executor2.submit(() -> { + GetBlockHeadersMessage msg = new GetBlockHeadersMessage(1L, new byte[0], 10, 5, false); + SysPropConfig1.testHandler.processGetBlockHeaders(msg); + } ); this.wait(DELAY); assert result.get(); @@ -191,25 +179,19 @@ public void run() { @Test public synchronized void testBodies() throws FileNotFoundException, InterruptedException { ExecutorService executor1 = Executors.newSingleThreadExecutor(); - executor1.submit(new Runnable() { - @Override - public void run() { - blockchain.isBlockExist(null); - } - } + executor1.submit(() -> { + blockchain.isBlockExist(null); + } ); this.wait(DELAY); ExecutorService executor2 = Executors.newSingleThreadExecutor(); - executor2.submit(new Runnable() { - @Override - public void run() { - List hashes = new ArrayList<>(); - hashes.add(new byte[] {1, 2, 3}); - hashes.add(new byte[] {4, 5, 6}); - GetBlockBodiesMessage msg = new GetBlockBodiesMessage(hashes); - SysPropConfig1.testHandler.processGetBlockBodies(msg); - } - } + executor2.submit(() -> { + List hashes = new ArrayList<>(); + hashes.add(new byte[] {1, 2, 3}); + hashes.add(new byte[] {4, 5, 6}); + GetBlockBodiesMessage msg = new GetBlockBodiesMessage(hashes); + SysPropConfig1.testHandler.processGetBlockBodies(msg); + } ); this.wait(DELAY); assert result.get(); @@ -218,25 +200,19 @@ public void run() { @Test public synchronized void testStatus() throws FileNotFoundException, InterruptedException { ExecutorService executor1 = Executors.newSingleThreadExecutor(); - executor1.submit(new Runnable() { - @Override - public void run() { - blockchain.isBlockExist(null); - } - } + executor1.submit(() -> { + blockchain.isBlockExist(null); + } ); this.wait(DELAY); ExecutorService executor2 = Executors.newSingleThreadExecutor(); - executor2.submit(new Runnable() { - @Override - public void run() { - try { - SysPropConfig1.testHandler.sendStatus(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } + executor2.submit(() -> { + try { + SysPropConfig1.testHandler.sendStatus(); + } catch (Exception e) { + e.printStackTrace(); + } + } ); this.wait(DELAY); assert result.get(); diff --git a/ethereumj-core/src/test/java/org/ethereum/net/rlpx/SnappyCodecTest.java b/ethereumj-core/src/test/java/org/ethereum/net/rlpx/SnappyCodecTest.java new file mode 100644 index 0000000000..6111172a8d --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/net/rlpx/SnappyCodecTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) [2017] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + * + * + */ + +package org.ethereum.net.rlpx; + +import org.ethereum.net.rlpx.discover.NodeStatistics; +import org.ethereum.net.server.Channel; +import org.junit.Test; +import org.spongycastle.util.encoders.Hex; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.util.FileCopyUtils; +import java.util.List; + +import static com.google.common.collect.Lists.newArrayList; +import static org.ethereum.net.message.ReasonCode.BAD_PROTOCOL; +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * EIP-706 provides snappy samples. FrameCodec should be + * able to decode these samples. + */ +public class SnappyCodecTest { + @Test + public void testDecodeGoGeneratedSnappy() throws Exception { + Resource golangGeneratedSnappy = new ClassPathResource("/rlp/block.go.snappy"); + List result = snappyDecode(golangGeneratedSnappy); + assertEquals(1, result.size()); + } + + @Test + public void testDecodePythonGeneratedSnappy() throws Exception { + Resource pythonGeneratedSnappy = new ClassPathResource("/rlp/block.py.snappy"); + List result = snappyDecode(pythonGeneratedSnappy); + assertEquals(1, result.size()); + } + + @Test + public void testFramedDecodeDisconnect() throws Exception { + byte[] frameBytes = new byte[] {(byte) 0xff, 0x06, 0x00, 0x00, 0x73, 0x4e, 0x61, 0x50, 0x70, 0x59}; + Channel shouldBeDropped = mock(Channel.class); + when(shouldBeDropped.getNodeStatistics()) + .thenReturn(new NodeStatistics(new Node(new byte[0], "", 0))); + + snappyDecode(frameBytes, shouldBeDropped); + + assertTrue(shouldBeDropped.getNodeStatistics().wasDisconnected()); + String stats = shouldBeDropped.getNodeStatistics().toString(); + assertTrue(stats.contains(BAD_PROTOCOL.toString())); + } + + private void snappyDecode(byte[] payload, Channel channel) throws Exception { + SnappyCodec codec = new SnappyCodec(channel); + + FrameCodec.Frame msg = new FrameCodec.Frame(1, payload); + + List result = newArrayList(); + codec.decode(null, msg, result); + } + + private List snappyDecode(Resource hexEncodedSnappyResource) throws Exception { + Channel channel = new Channel(); + SnappyCodec codec = new SnappyCodec(channel); + + byte[] golangSnappyBytes = FileCopyUtils.copyToByteArray(hexEncodedSnappyResource.getInputStream()); + FrameCodec.Frame msg = new FrameCodec.Frame(1, Hex.decode(golangSnappyBytes)); + + List result = newArrayList(); + codec.decode(null, msg, result); + return result; + } +} \ No newline at end of file diff --git a/ethereumj-core/src/test/java/org/ethereum/net/rlpx/SnappyConnectionTest.java b/ethereumj-core/src/test/java/org/ethereum/net/rlpx/SnappyConnectionTest.java new file mode 100644 index 0000000000..082ff6dbc3 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/net/rlpx/SnappyConnectionTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.net.rlpx; + +import org.ethereum.config.NoAutoscan; +import org.ethereum.config.SystemProperties; +import org.ethereum.facade.Ethereum; +import org.ethereum.facade.EthereumFactory; +import org.ethereum.listener.EthereumListenerAdapter; +import org.ethereum.net.eth.message.StatusMessage; +import org.ethereum.net.server.Channel; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.io.FileNotFoundException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * @author Mikhail Kalinin + * @since 02.11.2017 + */ +@Ignore +public class SnappyConnectionTest { + + @Configuration + @NoAutoscan + public static class SysPropConfig1 { + static SystemProperties props; + @Bean + public SystemProperties systemProperties() { + return props; + } + } + + @Configuration + @NoAutoscan + public static class SysPropConfig2 { + static SystemProperties props; + @Bean + public SystemProperties systemProperties() { + return props; + } + } + + @Test + public void test4To4() throws FileNotFoundException, InterruptedException { + runScenario(4, 4); + } + + @Test + public void test4To5() throws FileNotFoundException, InterruptedException { + runScenario(4, 5); + } + + @Test + public void test5To4() throws FileNotFoundException, InterruptedException { + runScenario(5, 4); + } + + @Test + public void test5To5() throws FileNotFoundException, InterruptedException { + runScenario(5, 5); + } + + private void runScenario(int vOutbound, int vInbound) throws FileNotFoundException, InterruptedException { + SysPropConfig1.props = new SystemProperties(); + SysPropConfig1.props.overrideParams( + "peer.listen.port", "30334", + "peer.privateKey", "ba43d10d069f0c41a8914849c1abeeac2a681b21ae9b60a6a2362c06e6eb1bc8", + "database.dir", "test_db-1", + "peer.p2p.version", String.valueOf(vInbound)); + SysPropConfig2.props = new SystemProperties(); + SysPropConfig2.props.overrideParams( + "peer.listen.port", "30335", + "peer.privateKey", "d3a4a240b107ab443d46187306d0b947ce3d6b6ed95aead8c4941afcebde43d2", + "database.dir", "test_db-2", + "peer.p2p.version", String.valueOf(vOutbound)); + + Ethereum ethereum1 = EthereumFactory.createEthereum(SysPropConfig1.class); + Ethereum ethereum2 = EthereumFactory.createEthereum(SysPropConfig2.class); + + final CountDownLatch semaphore = new CountDownLatch(2); + + ethereum1.addListener(new EthereumListenerAdapter() { + @Override + public void onEthStatusUpdated(Channel channel, StatusMessage statusMessage) { + System.out.println("1: -> " + statusMessage); + semaphore.countDown(); + } + }); + ethereum2.addListener(new EthereumListenerAdapter() { + @Override + public void onEthStatusUpdated(Channel channel, StatusMessage statusMessage) { + System.out.println("2: -> " + statusMessage); + semaphore.countDown(); + } + }); + + ethereum2.connect(new Node("enode://a560c55a0a5b5d137c638eb6973812f431974e4398c6644fa0c19181954fab530bb2a1e2c4eec7cc855f6bab9193ea41d6cf0bf2b8b41ed6b8b9e09c072a1e5a" + + "@localhost:30334")); + + semaphore.await(60, TimeUnit.SECONDS); + + ethereum1.close(); + ethereum2.close(); + + if(semaphore.getCount() > 0) { + throw new RuntimeException("One or both StatusMessage was not received: " + semaphore.getCount()); + } + + System.out.println("Passed."); + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/net/rlpx/discover/QueueTest.java b/ethereumj-core/src/test/java/org/ethereum/net/rlpx/discover/QueueTest.java index 4a63c984f6..1d1a4dc71a 100644 --- a/ethereumj-core/src/test/java/org/ethereum/net/rlpx/discover/QueueTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/net/rlpx/discover/QueueTest.java @@ -32,30 +32,22 @@ public class QueueTest { @Test public void simple() throws Exception { final PeerConnectionTester.MutablePriorityQueue queue = - new PeerConnectionTester.MutablePriorityQueue<>(new Comparator() { - @Override - public int compare(String o1, String o2) { - return o1.compareTo(o2); - } - }); + new PeerConnectionTester.MutablePriorityQueue<>(String::compareTo); final int threadCnt = 8; final int elemCnt = 1000; - Runnable adder = new Runnable() { - @Override - public void run() { - try { - System.out.println("Adding..."); - for (int i = 0; i < elemCnt && !exception; i++) { - queue.add("aaa" + i); - if (i % 100 == 0) Thread.sleep(10); - } - System.out.println("Done."); - } catch (Exception e) { - exception = true; - e.printStackTrace(); + Runnable adder = () -> { + try { + System.out.println("Adding..."); + for (int i = 0; i < elemCnt && !exception; i++) { + queue.add("aaa" + i); + if (i % 100 == 0) Thread.sleep(10); } + System.out.println("Done."); + } catch (Exception e) { + exception = true; + e.printStackTrace(); } }; @@ -69,19 +61,16 @@ public void run() { } - Runnable taker = new Runnable() { - @Override - public void run() { - try { - System.out.println("Taking..."); - for (int i = 0; i < elemCnt && !exception; i++) { - queue.poll(1, TimeUnit.SECONDS); - } - System.out.println("OK: " + queue.size()); - } catch (Exception e) { - exception = true; - e.printStackTrace(); + Runnable taker = () -> { + try { + System.out.println("Taking..."); + for (int i = 0; i < elemCnt && !exception; i++) { + queue.poll(1, TimeUnit.SECONDS); } + System.out.println("OK: " + queue.size()); + } catch (Exception e) { + exception = true; + e.printStackTrace(); } }; Thread t2[] = new Thread[threadCnt]; diff --git a/ethereumj-core/src/test/java/org/ethereum/net/swarm/BzzProtocolTest.java b/ethereumj-core/src/test/java/org/ethereum/net/swarm/BzzProtocolTest.java index 8cad13f4c3..fa537b19aa 100644 --- a/ethereumj-core/src/test/java/org/ethereum/net/swarm/BzzProtocolTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/net/swarm/BzzProtocolTest.java @@ -1,20 +1,20 @@ -/* - * Copyright (c) [2016] [ ] - * This file is part of the ethereumJ library. - * - * The ethereumJ library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The ethereumJ library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the ethereumJ library. If not, see . - */ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ package org.ethereum.net.swarm; import org.ethereum.net.rlpx.Node; @@ -22,7 +22,6 @@ import org.ethereum.net.swarm.bzz.BzzProtocol; import org.ethereum.net.swarm.bzz.PeerAddress; import org.ethereum.util.ByteUtil; -import org.ethereum.util.Functional; import org.ethereum.util.Utils; import org.junit.Assert; import org.junit.Ignore; @@ -32,6 +31,7 @@ import java.io.PrintWriter; import java.util.*; import java.util.concurrent.*; +import java.util.function.Consumer; import static org.ethereum.crypto.HashUtil.sha3; @@ -58,12 +58,7 @@ public void println(String x) { } public void setFilter(final String filter) { - pFilter = new Predicate() { - @Override - public boolean test(String s) { - return s.contains(filter); - } - }; + pFilter = s -> s.contains(filter); } public void setFilter(Predicate pFilter) { @@ -74,11 +69,11 @@ public void setFilter(Predicate pFilter) { static FilterPrinter stdout = new FilterPrinter(System.out); public static class TestPipe { - protected Functional.Consumer out1; - protected Functional.Consumer out2; + protected Consumer out1; + protected Consumer out2; protected String name1, name2; - public TestPipe(Functional.Consumer out1, Functional.Consumer out2) { + public TestPipe(Consumer out1, Consumer out2) { this.out1 = out1; this.out2 = out2; } @@ -86,28 +81,22 @@ public TestPipe(Functional.Consumer out1, Functional.Consumer createIn1() { - return new Functional.Consumer() { - @Override - public void accept(BzzMessage bzzMessage) { - BzzMessage smsg = serialize(bzzMessage); - if (TestPeer.MessageOut) { - stdout.println("+ " + name1 + " => " + name2 + ": " + smsg); - } - out2.accept(smsg); + Consumer createIn1() { + return bzzMessage -> { + BzzMessage smsg = serialize(bzzMessage); + if (TestPeer.MessageOut) { + stdout.println("+ " + name1 + " => " + name2 + ": " + smsg); } + out2.accept(smsg); }; } - Functional.Consumer createIn2() { - return new Functional.Consumer() { - @Override - public void accept(BzzMessage bzzMessage) { - BzzMessage smsg = serialize(bzzMessage); - if (TestPeer.MessageOut) { - stdout.println("+ " + name2 + " => " + name1 + ": " + smsg); - } - out1.accept(smsg); + Consumer createIn2() { + return bzzMessage -> { + BzzMessage smsg = serialize(bzzMessage); + if (TestPeer.MessageOut) { + stdout.println("+ " + name2 + " => " + name1 + ": " + smsg); } + out1.accept(smsg); }; } @@ -124,11 +113,11 @@ private BzzMessage serialize(BzzMessage msg) { } } - public Functional.Consumer getOut1() { + public Consumer getOut1() { return out1; } - public Functional.Consumer getOut2() { + public Consumer getOut2() { return out2; } } @@ -138,35 +127,32 @@ public static class TestAsyncPipe extends TestPipe { static ScheduledExecutorService exec = Executors.newScheduledThreadPool(32); static Queue> tasks = new LinkedBlockingQueue<>(); - class AsyncConsumer implements Functional.Consumer { - Functional.Consumer delegate; + class AsyncConsumer implements Consumer { + Consumer delegate; boolean rev; - public AsyncConsumer(Functional.Consumer delegate, boolean rev) { + public AsyncConsumer(Consumer delegate, boolean rev) { this.delegate = delegate; this.rev = rev; } @Override public void accept(final BzzMessage bzzMessage) { - ScheduledFuture future = exec.schedule(new Runnable() { - @Override - public void run() { - try { - if (!rev) { - if (TestPeer.MessageOut) { - stdout.println("- " + name1 + " => " + name2 + ": " + bzzMessage); - } - } else { - if (TestPeer.MessageOut) { - stdout.println("- " + name2 + " => " + name1 + ": " + bzzMessage); - } + ScheduledFuture future = exec.schedule(() -> { + try { + if (!rev) { + if (TestPeer.MessageOut) { + stdout.println("- " + name1 + " => " + name2 + ": " + bzzMessage); + } + } else { + if (TestPeer.MessageOut) { + stdout.println("- " + name2 + " => " + name1 + ": " + bzzMessage); } - delegate.accept(bzzMessage); - } catch (Exception e) { - e.printStackTrace(); } + delegate.accept(bzzMessage); + } catch (Exception e) { + e.printStackTrace(); } }, channelLatencyMs, TimeUnit.MILLISECONDS); tasks.add(future); @@ -186,7 +172,7 @@ public static void waitForCompletion() { } } - public TestAsyncPipe(Functional.Consumer out1, Functional.Consumer out2) { + public TestAsyncPipe(Consumer out1, Consumer out2) { this.out1 = new AsyncConsumer(out1, false); this.out2 = new AsyncConsumer(out2, true); } @@ -245,15 +231,12 @@ public Collection getNodes(Key key, int max) { @Override public Collection getPeers(Key key, int maxCount) { if (thisPeer == null) return peers.keySet(); -// TreeMap sort = new TreeMap(new Comparator() { -// @Override -// public int compare(Key o1, Key o2) { -// for (int i = 0; i < o1.getBytes().length; i++) { -// if (o1.getBytes()[i] > o2.getBytes()[i]) return 1; -// if (o1.getBytes()[i] < o2.getBytes()[i]) return -1; -// } -// return 0; +// TreeMap sort = new TreeMap<>((o1, o2) -> { +// for (int i = 0; i < o1.getBytes().length; i++) { +// if (o1.getBytes()[i] > o2.getBytes()[i]) return 1; +// if (o1.getBytes()[i] < o2.getBytes()[i]) return -1; // } +// return 0; // }); // for (TestPeer testPeer : TestPeer.staticMap.values()) { // if (thisPeer != testPeer) { @@ -441,12 +424,7 @@ public void manyPeersTest() throws InterruptedException { System.out.println("Waiting for net idle "); TestAsyncPipe.waitForCompletion(); TestPeer.MessageOut = true; - stdout.setFilter(new Predicate() { - @Override - public boolean test(String s) { - return s.startsWith("+") && s.contains("BzzStoreReqMessage"); - } - }); + stdout.setFilter(s -> s.startsWith("+") && s.contains("BzzStoreReqMessage")); // System.out.println("==== Storage statistics:\n" + dumpPeers(allPeers, null)); // System.out.println("Sleeping..."); diff --git a/ethereumj-core/src/test/java/org/ethereum/solidity/CompilerTest.java b/ethereumj-core/src/test/java/org/ethereum/solidity/CompilerTest.java index 475c4843a6..535ae4e273 100644 --- a/ethereumj-core/src/test/java/org/ethereum/solidity/CompilerTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/solidity/CompilerTest.java @@ -23,8 +23,10 @@ import org.junit.Assert; import org.junit.Test; -import java.io.File; import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; import static org.ethereum.solidity.compiler.SolidityCompiler.Options.*; import static org.hamcrest.MatcherAssert.assertThat; @@ -66,8 +68,8 @@ public void simpleTest() throws IOException { System.out.println("Out: '" + res.output + "'"); System.out.println("Err: '" + res.errors + "'"); CompilationResult result = CompilationResult.parse(res.output); - if (result.contracts.get("a") != null) - System.out.println(result.contracts.get("a").bin); + if (result.getContract("a") != null) + System.out.println(result.getContract("a").bin); else Assert.fail(); } @@ -86,27 +88,91 @@ public void defaultFuncTest() throws IOException { System.out.println("Err: '" + res.errors + "'"); CompilationResult result = CompilationResult.parse(res.output); - CompilationResult.ContractMetadata a = result.contracts.get("a"); + CompilationResult.ContractMetadata a = result.getContract("a"); CallTransaction.Contract contract = new CallTransaction.Contract(a.abi); - System.out.printf(contract.functions[0].toString()); + System.out.print(contract.functions[0].toString()); } @Test public void compileFilesTest() throws IOException { - File source = new File("src/test/resources/solidity/file1.sol"); + Path source = Paths.get("src","test","resources","solidity","file1.sol"); - SolidityCompiler.Result res = SolidityCompiler.compile( - source, true, ABI, BIN, INTERFACE, METADATA); + SolidityCompiler.Result res = SolidityCompiler.compile(source.toFile(), true, ABI, BIN, INTERFACE, METADATA); + System.out.println("Out: '" + res.output + "'"); + System.out.println("Err: '" + res.errors + "'"); + CompilationResult result = CompilationResult.parse(res.output); + + Assert.assertEquals("test1", result.getContractName()); + Assert.assertEquals(source.toAbsolutePath(), result.getContractPath()); + + CompilationResult.ContractMetadata a = result.getContract(source, "test1"); + CallTransaction.Contract contract = new CallTransaction.Contract(a.abi); + System.out.print(contract.functions[0].toString()); + } + + @Test public void compileFilesWithImportTest() throws IOException { + + Path source = Paths.get("src","test","resources","solidity","file2.sol"); + + SolidityCompiler.Result res = SolidityCompiler.compile(source.toFile(), true, ABI, BIN, INTERFACE, METADATA); + System.out.println("Out: '" + res.output + "'"); + System.out.println("Err: '" + res.errors + "'"); + CompilationResult result = CompilationResult.parse(res.output); + + CompilationResult.ContractMetadata a = result.getContract(source, "test2"); + CallTransaction.Contract contract = new CallTransaction.Contract(a.abi); + System.out.print(contract.functions[0].toString()); + } + + @Test public void compileFilesWithImportFromParentFileTest() throws IOException { + + Path source = Paths.get("src","test","resources","solidity","foo","file3.sol"); + + SolidityCompiler.Option allowPathsOption = new SolidityCompiler.Options.AllowPaths(Collections.singletonList(source.getParent().getParent().toFile())); + SolidityCompiler.Result res = SolidityCompiler.compile(source.toFile(), true, ABI, BIN, INTERFACE, METADATA, allowPathsOption); System.out.println("Out: '" + res.output + "'"); System.out.println("Err: '" + res.errors + "'"); CompilationResult result = CompilationResult.parse(res.output); - CompilationResult.ContractMetadata a = result.contracts.get("test1"); + Assert.assertEquals(2, result.getContractKeys().size()); + Assert.assertEquals(result.getContract("test3"), result.getContract(source,"test3")); + Assert.assertNotNull(result.getContract("test1")); + + CompilationResult.ContractMetadata a = result.getContract(source, "test3"); CallTransaction.Contract contract = new CallTransaction.Contract(a.abi); - System.out.printf(contract.functions[0].toString()); + System.out.print(contract.functions[0].toString()); } + @Test public void compileFilesWithImportFromParentStringTest() throws IOException { + + Path source = Paths.get("src","test","resources","solidity","foo","file3.sol"); + + SolidityCompiler.Option allowPathsOption = new SolidityCompiler.Options.AllowPaths(Collections.singletonList(source.getParent().getParent().toAbsolutePath().toString())); + SolidityCompiler.Result res = SolidityCompiler.compile(source.toFile(), true, ABI, BIN, INTERFACE, METADATA, allowPathsOption); + System.out.println("Out: '" + res.output + "'"); + System.out.println("Err: '" + res.errors + "'"); + CompilationResult result = CompilationResult.parse(res.output); + + CompilationResult.ContractMetadata a = result.getContract(source, "test3"); + CallTransaction.Contract contract = new CallTransaction.Contract(a.abi); + System.out.print(contract.functions[0].toString()); + } + + @Test public void compileFilesWithImportFromParentPathTest() throws IOException { + + Path source = Paths.get("src","test","resources","solidity","foo","file3.sol"); + + SolidityCompiler.Option allowPathsOption = new SolidityCompiler.Options.AllowPaths(Collections.singletonList(source.getParent().getParent())); + SolidityCompiler.Result res = SolidityCompiler.compile(source.toFile(), true, ABI, BIN, INTERFACE, METADATA, allowPathsOption); + System.out.println("Out: '" + res.output + "'"); + System.out.println("Err: '" + res.errors + "'"); + CompilationResult result = CompilationResult.parse(res.output); + + CompilationResult.ContractMetadata a = result.getContract("test3"); + CallTransaction.Contract contract = new CallTransaction.Contract(a.abi); + System.out.print(contract.functions[0].toString()); + } public static void main(String[] args) throws Exception { new CompilerTest().simpleTest(); diff --git a/ethereumj-core/src/test/java/org/ethereum/sync/BlockTxForwardTest.java b/ethereumj-core/src/test/java/org/ethereum/sync/BlockTxForwardTest.java index 30a74f9ebf..0161794770 100644 --- a/ethereumj-core/src/test/java/org/ethereum/sync/BlockTxForwardTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/sync/BlockTxForwardTest.java @@ -417,14 +417,11 @@ public GeneratorNode() { @Override public void onSyncDone() { - new Thread(new Runnable() { - @Override - public void run() { - try { - generateTransactions(); - } catch (Exception e) { - logger.error("Error generating tx: ", e); - } + new Thread(() -> { + try { + generateTransactions(); + } catch (Exception e) { + logger.error("Error generating tx: ", e); } }).start(); } @@ -467,11 +464,7 @@ private void generateTransactions() throws Exception{ private final static int STOP_ON_BLOCK = 100; private static ScheduledExecutorService statTimer = - Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { - public Thread newThread(Runnable r) { - return new Thread(r, "StatTimer"); - } - }); + Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "StatTimer")); private boolean logStats() { testLogger.info("---------====---------"); @@ -502,17 +495,14 @@ private boolean logStats() { @Test public void testTest() throws Exception { - statTimer.scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - try { - logStats(); - if (fatalErrors.get() > 0 || blocks.size() >= STOP_ON_BLOCK) { - statTimer.shutdownNow(); - } - } catch (Throwable t) { - testLogger.error("Unhandled exception", t); + statTimer.scheduleAtFixedRate(() -> { + try { + logStats(); + if (fatalErrors.get() > 0 || blocks.size() >= STOP_ON_BLOCK) { + statTimer.shutdownNow(); } + } catch (Throwable t) { + testLogger.error("Unhandled exception", t); } }, 0, 15, TimeUnit.SECONDS); diff --git a/ethereumj-core/src/test/java/org/ethereum/trie/TrieTest.java b/ethereumj-core/src/test/java/org/ethereum/trie/TrieTest.java index 1a9be3cfca..f1fc0f92bf 100644 --- a/ethereumj-core/src/test/java/org/ethereum/trie/TrieTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/trie/TrieTest.java @@ -712,9 +712,8 @@ public void testMasiveUpdate() { } } - @Ignore @Test - public void testMasiveDetermenisticUpdate() throws IOException, URISyntaxException { + public void testMassiveDeterministicUpdate() throws IOException, URISyntaxException { // should be root: cfd77c0fcb037adefce1f4e2eb94381456a4746379d2896bb8f309c620436d30 @@ -773,7 +772,7 @@ public void testMasiveDetermenisticUpdate() throws IOException, URISyntaxExcepti System.out.println("root_2: => " + Hex.toHexString(trie2.getRootHash())); - assertEquals(trieSingle.getRootHash(), trie2.getRootHash()); + assertArrayEquals(trieSingle.getRootHash(), trie2.getRootHash()); } @@ -1106,6 +1105,31 @@ public void testBugFix2() throws ParseException, IOException, URISyntaxException assertEquals("36e350d9a1d9c02d5bc4539a05e51890784ea5d2b675a0b26725dbbdadb4d6e2", Hex.toHexString(trie.getRootHash())); } + @Test + public void testBugFix3() throws ParseException, IOException, URISyntaxException { + + HashMapDB src = new HashMapDB<>(); + + // Scenario: + // create trie with subtrie: ... -> kvNodeNode -> BranchNode() -> kvNodeValue1, kvNodeValue2 + // remove kvNodeValue2, in that way kvNodeNode and kvNodeValue1 are going to be merged in a new kvNodeValue3 + + // BUG: kvNodeNode is not deleted from storage after the merge + + TrieImpl trie = new TrieImpl(src); + trie.put(Hex.decode("0000000000000000000000000000000000000000000000000000000000011133"), + Hex.decode("0000000000000000000000000000000000000000000000000000000000000033")); + trie.put(Hex.decode("0000000000000000000000000000000000000000000000000000000000021244"), + Hex.decode("0000000000000000000000000000000000000000000000000000000000000044")); + trie.put(Hex.decode("0000000000000000000000000000000000000000000000000000000000011255"), + Hex.decode("0000000000000000000000000000000000000000000000000000000000000055")); + trie.flush(); + + trie.delete(Hex.decode("0000000000000000000000000000000000000000000000000000000000011255")); + + assertFalse(src.getStorage().containsKey(Hex.decode("5152f9274abb8e61f3956ccd08d31e38bfa2913afd23bc13b5e7bb709ce7f603"))); + } + @Ignore @Test public void perfTestGet() { diff --git a/ethereumj-core/src/test/java/org/ethereum/util/ByteUtilTest.java b/ethereumj-core/src/test/java/org/ethereum/util/ByteUtilTest.java index 6e4f9251d9..1e5b94086d 100644 --- a/ethereumj-core/src/test/java/org/ethereum/util/ByteUtilTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/util/ByteUtilTest.java @@ -534,4 +534,11 @@ public void testNumberOfLeadingZeros() { assertEquals(64, n7); } + + @Test + public void nullArrayToNumber() { + assertEquals(BigInteger.ZERO, ByteUtil.bytesToBigInteger(null)); + assertEquals(0L, ByteUtil.byteArrayToLong(null)); + assertEquals(0, ByteUtil.byteArrayToInt(null)); + } } diff --git a/ethereumj-core/src/test/java/org/ethereum/util/ExecutorPipelineTest.java b/ethereumj-core/src/test/java/org/ethereum/util/ExecutorPipelineTest.java index dbce12177b..d8ef6f3c0a 100644 --- a/ethereumj-core/src/test/java/org/ethereum/util/ExecutorPipelineTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/util/ExecutorPipelineTest.java @@ -30,31 +30,19 @@ public class ExecutorPipelineTest { @Test public void joinTest() throws InterruptedException { - ExecutorPipeline exec1 = new ExecutorPipeline<>(8, 100, true, new Functional.Function() { - @Override - public Integer apply(Integer integer) { - try { - Thread.sleep(2); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - return integer; + ExecutorPipeline exec1 = new ExecutorPipeline<>(8, 100, true, + integer -> { + try { + Thread.sleep(2); + } catch (InterruptedException e) { + throw new RuntimeException(e); } - }, new Functional.Consumer() { - @Override - public void accept(Throwable throwable) { - throwable.printStackTrace(); - } - }); + return integer; + }, Throwable::printStackTrace); final List consumed = new ArrayList<>(); - ExecutorPipeline exec2 = exec1.add(1, 100, new Functional.Consumer() { - @Override - public void accept(Integer integer) { - consumed.add(integer); - } - }); + ExecutorPipeline exec2 = exec1.add(1, 100, consumed::add); int cnt = 1000; for (int i = 0; i < cnt; i++) { 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 f9aeca55a8..8946594297 100644 --- a/ethereumj-core/src/test/java/org/ethereum/util/RLPTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/util/RLPTest.java @@ -112,7 +112,7 @@ public void test3() throws UnknownHostException { BigInteger peerId = decodeBigInteger(payload, nextIndex); BigInteger expectedPeerId = - new BigInteger("9650128800487972697726795438087510101805200020100629942070155319087371611597658887860952245483247188023303607186148645071838189546969115967896446355306572"); + new BigInteger("11356629247358725515654715129711890958861491612873043044752814241820167155109073064559464053586837011802513611263556758124445676272172838679152022396871088"); assertEquals(expectedPeerId, peerId); nextIndex = getNextElementIndex(payload, nextIndex); @@ -128,7 +128,7 @@ public void test3() throws UnknownHostException { peerId = decodeBigInteger(payload, nextIndex); expectedPeerId = - new BigInteger("9650128800487972697726795438087510101805200020100629942070155319087371611597658887860952245483247188023303607186148645071838189546969115967896446355306572"); + new BigInteger("11356629247358725515654715129711890958861491612873043044752814241820167155109073064559464053586837011802513611263556758124445676272172838679152022396871088"); assertEquals(expectedPeerId, peerId); @@ -177,10 +177,6 @@ public void test5() { byte[] expected5 = {(byte) 0x82, (byte) 0x4E, (byte) 0xEA}; data = encodeShort((short) 20202); assertArrayEquals(expected5, data); - - byte[] expected6 = {(byte) 0x82, (byte) 0x9D, (byte) 0x0A}; - data = encodeShort((short) 40202); - assertArrayEquals(expected6, data); } @Test @@ -225,21 +221,36 @@ public void testEncodeInt() { assertArrayEquals(expected6, data); assertEquals(65536, RLP.decodeInt(data, 0)); - byte[] expected7 = {(byte) 0x84, (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00}; - data = encodeInt(Integer.MIN_VALUE); - assertArrayEquals(expected7, data); - assertEquals(Integer.MIN_VALUE, RLP.decodeInt(data, 0)); - byte[] expected8 = {(byte) 0x84, (byte) 0x7F, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}; data = encodeInt(Integer.MAX_VALUE); assertArrayEquals(expected8, data); assertEquals(Integer.MAX_VALUE, RLP.decodeInt(data, 0)); + } - byte[] expected9 = {(byte) 0x84, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}; - data = encodeInt(0xFFFFFFFF); - assertArrayEquals(expected9, data); - assertEquals(0xFFFFFFFF, RLP.decodeInt(data, 0)); + @Test(expected = RuntimeException.class) + public void incorrectZero() { + RLP.decodeInt(new byte[]{0x00}, 0); + } + /** + * NOTE: While negative numbers are not used in RLP, we usually use RLP + * for encoding all data and sometime use -1 in primitive fields as null. + * So, currently negative numbers encoding is allowed + */ + @Ignore + @Test(expected = RuntimeException.class) + public void cannotEncodeNegativeNumbers() { + encodeInt(Integer.MIN_VALUE); + } + + @Test + public void testMaxNumerics() { + int expected1 = Integer.MAX_VALUE; + assertEquals(expected1, decodeInt(encodeInt(expected1), 0)); + short expected2 = Short.MAX_VALUE; + assertEquals(expected2, decodeShort(encodeShort(expected2), 0)); + long expected3 = Long.MAX_VALUE; + assertEquals(expected3, decodeLong(encodeBigInteger(BigInteger.valueOf(expected3)), 0)); } @Test @@ -1150,4 +1161,13 @@ public void shortStringRightBoundTest(){ String res = new String((byte[])decode(rlpEncoded, 0).getDecoded()); assertEquals(testString, res); //Fails } -} \ No newline at end of file + + @Test + public void encodeDecodeBigInteger() { + BigInteger expected = new BigInteger("9650128800487972697726795438087510101805200020100629942070155319087371611597658887860952245483247188023303607186148645071838189546969115967896446355306572"); + byte[] encoded = encodeBigInteger(expected); + BigInteger decoded = decodeBigInteger(encoded, 0); + assertNotNull(decoded); + assertEquals(expected, decoded); + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/util/UtilsTest.java b/ethereumj-core/src/test/java/org/ethereum/util/UtilsTest.java index 7624a07523..c77b672229 100644 --- a/ethereumj-core/src/test/java/org/ethereum/util/UtilsTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/util/UtilsTest.java @@ -18,7 +18,6 @@ package org.ethereum.util; import org.junit.Test; - import org.spongycastle.util.Arrays; import org.spongycastle.util.encoders.Hex; @@ -105,4 +104,9 @@ public void testAddressStringToBytes() { result = Utils.addressStringToBytes(HexStr); assertEquals(expected, result); } + + @Test + public void testLongToTimePeriod() { + assertEquals("2.99s", Utils.longToTimePeriod(3000 - 12)); + } } diff --git a/ethereumj-core/src/test/java/org/ethereum/vm/BytecodeCompiler.java b/ethereumj-core/src/test/java/org/ethereum/vm/BytecodeCompiler.java new file mode 100644 index 0000000000..ba58d67070 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/vm/BytecodeCompiler.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) [2018] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.vm; + +import org.ethereum.vm.OpCode; +import org.spongycastle.util.encoders.Hex; + +import java.util.ArrayList; +import java.util.List; + +public class BytecodeCompiler { + public byte[] compile(String code) { + return compile(code.split("\\s+")); + } + + private byte[] compile(String[] tokens) { + List bytecodes = new ArrayList<>(); + int ntokens = tokens.length; + + for (int i = 0; i < ntokens; i++) { + String token = tokens[i].trim().toUpperCase(); + + if (token.isEmpty()) + continue; + + if (isHexadecimal(token)) + compileHexadecimal(token, bytecodes); + else + bytecodes.add(OpCode.byteVal(token)); + } + + int nbytes = bytecodes.size(); + byte[] bytes = new byte[nbytes]; + + for (int k = 0; k < nbytes; k++) + bytes[k] = bytecodes.get(k).byteValue(); + + return bytes; + } + + private static boolean isHexadecimal(String token) { + return token.startsWith("0X"); + } + + private static void compileHexadecimal(String token, List bytecodes) { + byte[] bytes = Hex.decode(token.substring(2)); + + for (int k = 0; k < bytes.length; k++) + bytecodes.add(bytes[k]); + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/vm/BytecodeCompilerTest.java b/ethereumj-core/src/test/java/org/ethereum/vm/BytecodeCompilerTest.java new file mode 100644 index 0000000000..e7093d1d42 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/vm/BytecodeCompilerTest.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) [2018] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.vm; + +import org.junit.Assert; +import org.junit.Test; + +public class BytecodeCompilerTest { + @Test + public void compileSimpleOpcode() { + BytecodeCompiler compiler = new BytecodeCompiler(); + + byte[] result = compiler.compile("ADD"); + + Assert.assertNotNull(result); + Assert.assertEquals(1, result.length); + Assert.assertEquals(1, result[0]); + } + + @Test + public void compileSimpleOpcodeWithSpaces() { + BytecodeCompiler compiler = new BytecodeCompiler(); + + byte[] result = compiler.compile(" ADD "); + + Assert.assertNotNull(result); + Assert.assertEquals(1, result.length); + Assert.assertEquals(1, result[0]); + } + + @Test + public void compileTwoOpcodes() { + BytecodeCompiler compiler = new BytecodeCompiler(); + + byte[] result = compiler.compile("ADD SUB"); + + Assert.assertNotNull(result); + Assert.assertEquals(2, result.length); + Assert.assertEquals(1, result[0]); + Assert.assertEquals(3, result[1]); + } + + @Test + public void compileFourOpcodes() { + BytecodeCompiler compiler = new BytecodeCompiler(); + + byte[] result = compiler.compile("ADD MUL SUB DIV"); + + Assert.assertNotNull(result); + Assert.assertEquals(4, result.length); + Assert.assertEquals(1, result[0]); + Assert.assertEquals(2, result[1]); + Assert.assertEquals(3, result[2]); + Assert.assertEquals(4, result[3]); + } + + @Test + public void compileHexadecimalValueOneByte() { + BytecodeCompiler compiler = new BytecodeCompiler(); + + byte[] result = compiler.compile("0x01"); + + Assert.assertNotNull(result); + Assert.assertEquals(1, result.length); + Assert.assertEquals(1, result[0]); + } + + @Test + public void compileHexadecimalValueTwoByte() { + BytecodeCompiler compiler = new BytecodeCompiler(); + + byte[] result = compiler.compile("0x0102"); + + Assert.assertNotNull(result); + Assert.assertEquals(2, result.length); + Assert.assertEquals(1, result[0]); + Assert.assertEquals(2, result[1]); + } + + @Test + public void compileSimpleOpcodeInLowerCase() { + BytecodeCompiler compiler = new BytecodeCompiler(); + + byte[] result = compiler.compile("add"); + + Assert.assertNotNull(result); + Assert.assertEquals(1, result.length); + Assert.assertEquals(1, result[0]); + } + + @Test + public void compileSimpleOpcodeInMixedCase() { + BytecodeCompiler compiler = new BytecodeCompiler(); + + byte[] result = compiler.compile("Add"); + + Assert.assertNotNull(result); + Assert.assertEquals(1, result.length); + Assert.assertEquals(1, result[0]); + } +} diff --git a/ethereumj-core/src/test/java/org/ethereum/vm/VMTest.java b/ethereumj-core/src/test/java/org/ethereum/vm/VMTest.java index c2a95db8bd..fbcc71416c 100644 --- a/ethereumj-core/src/test/java/org/ethereum/vm/VMTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/vm/VMTest.java @@ -18,7 +18,6 @@ package org.ethereum.vm; import org.ethereum.core.Repository; -import org.ethereum.util.ByteUtil; import org.ethereum.vm.program.Program; import org.ethereum.vm.program.Program.BadJumpDestinationException; import org.ethereum.vm.program.Program.StackTooSmallException; @@ -58,7 +57,7 @@ public void tearDown() { public void testPUSH1() { VM vm = new VM(); - program = new Program(Hex.decode("60A0"), invoke); + program = new Program(compile("PUSH1 0xa0"), invoke); String expected = "00000000000000000000000000000000000000000000000000000000000000A0"; program.fullTrace(); @@ -71,7 +70,7 @@ public void testPUSH1() { public void testPUSH2() { VM vm = new VM(); - program = new Program(Hex.decode("61A0B0"), invoke); + program = new Program(compile("PUSH2 0xa0b0"), invoke); String expected = "000000000000000000000000000000000000000000000000000000000000A0B0"; program.fullTrace(); @@ -84,7 +83,7 @@ public void testPUSH2() { public void testPUSH3() { VM vm = new VM(); - program = new Program(Hex.decode("62A0B0C0"), invoke); + program = new Program(compile("PUSH3 0xA0B0C0"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000A0B0C0"; program.fullTrace(); @@ -97,7 +96,7 @@ public void testPUSH3() { public void testPUSH4() { VM vm = new VM(); - program = new Program(Hex.decode("63A0B0C0D0"), invoke); + program = new Program(compile("PUSH4 0xA0B0C0D0"), invoke); String expected = "00000000000000000000000000000000000000000000000000000000A0B0C0D0"; program.fullTrace(); @@ -110,7 +109,7 @@ public void testPUSH4() { public void testPUSH5() { VM vm = new VM(); - program = new Program(Hex.decode("64A0B0C0D0E0"), invoke); + program = new Program(compile("PUSH5 0xA0B0C0D0E0"), invoke); String expected = "000000000000000000000000000000000000000000000000000000A0B0C0D0E0"; program.fullTrace(); @@ -123,7 +122,7 @@ public void testPUSH5() { public void testPUSH6() { VM vm = new VM(); - program = new Program(Hex.decode("65A0B0C0D0E0F0"), invoke); + program = new Program(compile("PUSH6 0xA0B0C0D0E0F0"), invoke); String expected = "0000000000000000000000000000000000000000000000000000A0B0C0D0E0F0"; program.fullTrace(); @@ -136,7 +135,7 @@ public void testPUSH6() { public void testPUSH7() { VM vm = new VM(); - program = new Program(Hex.decode("66A0B0C0D0E0F0A1"), invoke); + program = new Program(compile("PUSH7 0xA0B0C0D0E0F0A1"), invoke); String expected = "00000000000000000000000000000000000000000000000000A0B0C0D0E0F0A1"; program.fullTrace(); @@ -149,7 +148,7 @@ public void testPUSH7() { public void testPUSH8() { VM vm = new VM(); - program = new Program(Hex.decode("67A0B0C0D0E0F0A1B1"), invoke); + program = new Program(compile("PUSH8 0xA0B0C0D0E0F0A1B1"), invoke); String expected = "000000000000000000000000000000000000000000000000A0B0C0D0E0F0A1B1"; program.fullTrace(); @@ -162,7 +161,7 @@ public void testPUSH8() { public void testPUSH9() { VM vm = new VM(); - program = new Program(Hex.decode("68A0B0C0D0E0F0A1B1C1"), invoke); + program = new Program(compile("PUSH9 0xA0B0C0D0E0F0A1B1C1"), invoke); String expected = "0000000000000000000000000000000000000000000000A0B0C0D0E0F0A1B1C1"; program.fullTrace(); @@ -176,7 +175,7 @@ public void testPUSH9() { public void testPUSH10() { VM vm = new VM(); - program = new Program(Hex.decode("69A0B0C0D0E0F0A1B1C1D1"), invoke); + program = new Program(compile("PUSH10 0xA0B0C0D0E0F0A1B1C1D1"), invoke); String expected = "00000000000000000000000000000000000000000000A0B0C0D0E0F0A1B1C1D1"; program.fullTrace(); @@ -189,7 +188,7 @@ public void testPUSH10() { public void testPUSH11() { VM vm = new VM(); - program = new Program(Hex.decode("6AA0B0C0D0E0F0A1B1C1D1E1"), invoke); + program = new Program(compile("PUSH11 0xA0B0C0D0E0F0A1B1C1D1E1"), invoke); String expected = "000000000000000000000000000000000000000000A0B0C0D0E0F0A1B1C1D1E1"; program.fullTrace(); @@ -202,7 +201,7 @@ public void testPUSH11() { public void testPUSH12() { VM vm = new VM(); - program = new Program(Hex.decode("6BA0B0C0D0E0F0A1B1C1D1E1F1"), invoke); + program = new Program(compile("PUSH12 0xA0B0C0D0E0F0A1B1C1D1E1F1"), invoke); String expected = "0000000000000000000000000000000000000000A0B0C0D0E0F0A1B1C1D1E1F1"; program.fullTrace(); @@ -215,7 +214,7 @@ public void testPUSH12() { public void testPUSH13() { VM vm = new VM(); - program = new Program(Hex.decode("6CA0B0C0D0E0F0A1B1C1D1E1F1A2"), invoke); + program = new Program(compile("PUSH13 0xA0B0C0D0E0F0A1B1C1D1E1F1A2"), invoke); String expected = "00000000000000000000000000000000000000A0B0C0D0E0F0A1B1C1D1E1F1A2"; program.fullTrace(); @@ -228,7 +227,7 @@ public void testPUSH13() { public void testPUSH14() { VM vm = new VM(); - program = new Program(Hex.decode("6DA0B0C0D0E0F0A1B1C1D1E1F1A2B2"), invoke); + program = new Program(compile("PUSH14 0xA0B0C0D0E0F0A1B1C1D1E1F1A2B2"), invoke); String expected = "000000000000000000000000000000000000A0B0C0D0E0F0A1B1C1D1E1F1A2B2"; program.fullTrace(); @@ -241,7 +240,7 @@ public void testPUSH14() { public void testPUSH15() { VM vm = new VM(); - program = new Program(Hex.decode("6EA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2"), invoke); + program = new Program(compile("PUSH15 0xA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2"), invoke); String expected = "0000000000000000000000000000000000A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2"; program.fullTrace(); @@ -254,7 +253,7 @@ public void testPUSH15() { public void testPUSH16() { VM vm = new VM(); - program = new Program(Hex.decode("6FA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2"), invoke); + program = new Program(compile("PUSH16 0xA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2"), invoke); String expected = "00000000000000000000000000000000A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2"; program.fullTrace(); @@ -267,7 +266,7 @@ public void testPUSH16() { public void testPUSH17() { VM vm = new VM(); - program = new Program(Hex.decode("70A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2"), invoke); + program = new Program(compile("PUSH17 0xA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2"), invoke); String expected = "000000000000000000000000000000A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2"; program.fullTrace(); @@ -280,7 +279,7 @@ public void testPUSH17() { public void testPUSH18() { VM vm = new VM(); - program = new Program(Hex.decode("71A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2"), invoke); + program = new Program(compile("PUSH18 0xA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2"), invoke); String expected = "0000000000000000000000000000A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2"; program.fullTrace(); @@ -293,7 +292,7 @@ public void testPUSH18() { public void testPUSH19() { VM vm = new VM(); - program = new Program(Hex.decode("72A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3"), invoke); + program = new Program(compile("PUSH19 0xA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3"), invoke); String expected = "00000000000000000000000000A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3"; program.fullTrace(); @@ -306,7 +305,7 @@ public void testPUSH19() { public void testPUSH20() { VM vm = new VM(); - program = new Program(Hex.decode("73A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3"), invoke); + program = new Program(compile("PUSH20 0xA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3"), invoke); String expected = "000000000000000000000000A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3"; program.fullTrace(); @@ -319,7 +318,7 @@ public void testPUSH20() { public void testPUSH21() { VM vm = new VM(); - program = new Program(Hex.decode("74A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3"), invoke); + program = new Program(compile("PUSH21 0xA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3"), invoke); String expected = "0000000000000000000000A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3"; program.fullTrace(); @@ -332,7 +331,7 @@ public void testPUSH21() { public void testPUSH22() { VM vm = new VM(); - program = new Program(Hex.decode("75A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3"), invoke); + program = new Program(compile("PUSH22 0xA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3"), invoke); String expected = "00000000000000000000A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3"; program.fullTrace(); @@ -345,7 +344,7 @@ public void testPUSH22() { public void testPUSH23() { VM vm = new VM(); - program = new Program(Hex.decode("76A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3"), invoke); + program = new Program(compile("PUSH23 0xA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3"), invoke); String expected = "000000000000000000A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3"; program.fullTrace(); @@ -358,7 +357,7 @@ public void testPUSH23() { public void testPUSH24() { VM vm = new VM(); - program = new Program(Hex.decode("77A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3"), invoke); + program = new Program(compile("PUSH24 0xA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3"), invoke); String expected = "0000000000000000A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3"; program.fullTrace(); @@ -371,7 +370,7 @@ public void testPUSH24() { public void testPUSH25() { VM vm = new VM(); - program = new Program(Hex.decode("78A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4"), invoke); + program = new Program(compile("PUSH25 0xA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4"), invoke); String expected = "00000000000000A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4"; program.fullTrace(); @@ -384,7 +383,7 @@ public void testPUSH25() { public void testPUSH26() { VM vm = new VM(); - program = new Program(Hex.decode("79A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4"), invoke); + program = new Program(compile("PUSH26 0xA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4"), invoke); String expected = "000000000000A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4"; program.fullTrace(); @@ -397,7 +396,7 @@ public void testPUSH26() { public void testPUSH27() { VM vm = new VM(); - program = new Program(Hex.decode("7AA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4"), invoke); + program = new Program(compile("PUSH27 0xA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4"), invoke); String expected = "0000000000A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4"; program.fullTrace(); @@ -410,7 +409,7 @@ public void testPUSH27() { public void testPUSH28() { VM vm = new VM(); - program = new Program(Hex.decode("7BA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4D4"), invoke); + program = new Program(compile("PUSH28 0xA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4D4"), invoke); String expected = "00000000A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4D4"; program.fullTrace(); @@ -423,7 +422,7 @@ public void testPUSH28() { public void testPUSH29() { VM vm = new VM(); - program = new Program(Hex.decode("7CA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4D4E4"), invoke); + program = new Program(compile("PUSH29 0xA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4D4E4"), invoke); String expected = "000000A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4D4E4"; program.fullTrace(); @@ -436,7 +435,7 @@ public void testPUSH29() { public void testPUSH30() { VM vm = new VM(); - program = new Program(Hex.decode("7DA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4D4E4F4"), invoke); + program = new Program(compile("PUSH30 0xA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4D4E4F4"), invoke); String expected = "0000A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4D4E4F4"; program.fullTrace(); @@ -449,7 +448,7 @@ public void testPUSH30() { public void testPUSH31() { VM vm = new VM(); - program = new Program(Hex.decode("7EA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4D4E4F4A1"), invoke); + program = new Program(compile("PUSH31 0xA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4D4E4F4A1"), invoke); String expected = "00A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4D4E4F4A1"; program.fullTrace(); @@ -462,7 +461,7 @@ public void testPUSH31() { public void testPUSH32() { VM vm = new VM(); - program = new Program(Hex.decode("7FA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4D4E4F4A1B1"), invoke); + program = new Program(compile("PUSH32 0xA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4D4E4F4A1B1"), invoke); String expected = "A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4D4E4F4A1B1"; program.fullTrace(); @@ -475,7 +474,7 @@ public void testPUSH32() { public void testPUSHN_1() { VM vm = new VM(); - program = new Program(Hex.decode("61AA"), invoke); + program = new Program(compile("PUSH2 0xAA"), invoke); String expected = "000000000000000000000000000000000000000000000000000000000000AA00"; program.fullTrace(); @@ -489,7 +488,7 @@ public void testPUSHN_1() { public void testPUSHN_2() { VM vm = new VM(); - program = new Program(Hex.decode("7fAABB"), invoke); + program = new Program(compile("PUSH32 0xAABB"), invoke); String expected = "AABB000000000000000000000000000000000000000000000000000000000000"; program.fullTrace(); @@ -503,7 +502,7 @@ public void testPUSHN_2() { public void testAND_1() { VM vm = new VM(); - program = new Program(Hex.decode("600A600A16"), invoke); + program = new Program(compile("PUSH1 0x0A PUSH1 0x0A AND"), invoke); String expected = "000000000000000000000000000000000000000000000000000000000000000A"; vm.step(program); @@ -517,7 +516,7 @@ public void testAND_1() { public void testAND_2() { VM vm = new VM(); - program = new Program(Hex.decode("60C0600A16"), invoke); + program = new Program(compile("PUSH1 0xC0 PUSH1 0x0A AND"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000000000"; vm.step(program); @@ -531,7 +530,7 @@ public void testAND_2() { public void testAND_3() { VM vm = new VM(); - program = new Program(Hex.decode("60C016"), invoke); + program = new Program(compile("PUSH1 0xC0 AND"), invoke); try { vm.step(program); vm.step(program); @@ -545,7 +544,7 @@ public void testAND_3() { public void testOR_1() { VM vm = new VM(); - program = new Program(Hex.decode("60F0600F17"), invoke); + program = new Program(compile("PUSH1 0xF0 PUSH1 0x0F OR"), invoke); String expected = "00000000000000000000000000000000000000000000000000000000000000FF"; vm.step(program); @@ -559,7 +558,7 @@ public void testOR_1() { public void testOR_2() { VM vm = new VM(); - program = new Program(Hex.decode("60C3603C17"), invoke); + program = new Program(compile("PUSH1 0xC3 PUSH1 0x3C OR"), invoke); String expected = "00000000000000000000000000000000000000000000000000000000000000FF"; vm.step(program); @@ -573,7 +572,7 @@ public void testOR_2() { public void testOR_3() { VM vm = new VM(); - program = new Program(Hex.decode("60C017"), invoke); + program = new Program(compile("PUSH1 0xC0 OR"), invoke); try { vm.step(program); vm.step(program); @@ -587,7 +586,7 @@ public void testOR_3() { public void testXOR_1() { VM vm = new VM(); - program = new Program(Hex.decode("60FF60FF18"), invoke); + program = new Program(compile("PUSH1 0xFF PUSH1 0xFF XOR"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000000000"; vm.step(program); @@ -601,7 +600,7 @@ public void testXOR_1() { public void testXOR_2() { VM vm = new VM(); - program = new Program(Hex.decode("600F60F018"), invoke); + program = new Program(compile("PUSH1 0x0F PUSH1 0xF0 XOR"), invoke); String expected = "00000000000000000000000000000000000000000000000000000000000000FF"; vm.step(program); @@ -616,7 +615,7 @@ public void testXOR_2() { public void testXOR_3() { VM vm = new VM(); - program = new Program(Hex.decode("60C018"), invoke); + program = new Program(compile("PUSH1 0xC0 XOR"), invoke); try { vm.step(program); vm.step(program); @@ -630,7 +629,7 @@ public void testXOR_3() { public void testBYTE_1() { VM vm = new VM(); - program = new Program(Hex.decode("65AABBCCDDEEFF601E1A"), invoke); + program = new Program(compile("PUSH6 0xAABBCCDDEEFF PUSH1 0x1E BYTE"), invoke); String expected = "00000000000000000000000000000000000000000000000000000000000000EE"; vm.step(program); @@ -644,7 +643,7 @@ public void testBYTE_1() { public void testBYTE_2() { VM vm = new VM(); - program = new Program(Hex.decode("65AABBCCDDEEFF60201A"), invoke); + program = new Program(compile("PUSH6 0xAABBCCDDEEFF PUSH1 0x20 BYTE"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000000000"; vm.step(program); @@ -658,7 +657,7 @@ public void testBYTE_2() { public void testBYTE_3() { VM vm = new VM(); - program = new Program(Hex.decode("65AABBCCDDEE3A601F1A"), invoke); + program = new Program(compile("PUSH6 0xAABBCCDDEE3A PUSH1 0x1F BYTE"), invoke); String expected = "000000000000000000000000000000000000000000000000000000000000003A"; vm.step(program); @@ -673,7 +672,7 @@ public void testBYTE_3() { public void testBYTE_4() { VM vm = new VM(); - program = new Program(Hex.decode("65AABBCCDDEE3A1A"), invoke); + program = new Program(compile("PUSH6 0xAABBCCDDEE3A BYTE"), invoke); try { vm.step(program); vm.step(program); @@ -687,7 +686,7 @@ public void testBYTE_4() { public void testISZERO_1() { VM vm = new VM(); - program = new Program(Hex.decode("600015"), invoke); + program = new Program(compile("PUSH1 0x00 ISZERO"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000000001"; vm.step(program); @@ -700,7 +699,7 @@ public void testISZERO_1() { public void testISZERO_2() { VM vm = new VM(); - program = new Program(Hex.decode("602A15"), invoke); + program = new Program(compile("PUSH1 0x2A ISZERO"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000000000"; vm.step(program); @@ -713,7 +712,7 @@ public void testISZERO_2() { public void testISZERO_3() { VM vm = new VM(); - program = new Program(Hex.decode("15"), invoke); + program = new Program(compile("ISZERO"), invoke); try { vm.step(program); vm.step(program); @@ -727,7 +726,7 @@ public void testISZERO_3() { public void testEQ_1() { VM vm = new VM(); - program = new Program(Hex.decode("602A602A14"), invoke); + program = new Program(compile("PUSH1 0x2A PUSH1 0x2A EQ"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000000001"; vm.step(program); @@ -741,7 +740,7 @@ public void testEQ_1() { public void testEQ_2() { VM vm = new VM(); - program = new Program(Hex.decode("622A3B4C622A3B4C14"), invoke); + program = new Program(compile("PUSH3 0x2A3B4C PUSH3 0x2A3B4C EQ"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000000001"; vm.step(program); @@ -755,7 +754,7 @@ public void testEQ_2() { public void testEQ_3() { VM vm = new VM(); - program = new Program(Hex.decode("622A3B5C622A3B4C14"), invoke); + program = new Program(compile("PUSH3 0x2A3B5C PUSH3 0x2A3B4C EQ"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000000000"; vm.step(program); @@ -769,7 +768,7 @@ public void testEQ_3() { public void testEQ_4() { VM vm = new VM(); - program = new Program(Hex.decode("622A3B4C14"), invoke); + program = new Program(compile("PUSH3 0x2A3B4C EQ"), invoke); try { vm.step(program); vm.step(program); @@ -783,7 +782,7 @@ public void testEQ_4() { public void testGT_1() { VM vm = new VM(); - program = new Program(Hex.decode("6001600211"), invoke); + program = new Program(compile("PUSH1 0x01 PUSH1 0x02 GT"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000000001"; vm.step(program); @@ -797,7 +796,7 @@ public void testGT_1() { public void testGT_2() { VM vm = new VM(); - program = new Program(Hex.decode("6001610F0011"), invoke); + program = new Program(compile("PUSH1 0x01 PUSH2 0x0F00 GT"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000000001"; vm.step(program); @@ -811,7 +810,7 @@ public void testGT_2() { public void testGT_3() { VM vm = new VM(); - program = new Program(Hex.decode("6301020304610F0011"), invoke); + program = new Program(compile("PUSH4 0x01020304 PUSH2 0x0F00 GT"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000000000"; vm.step(program); @@ -825,7 +824,7 @@ public void testGT_3() { public void testGT_4() { VM vm = new VM(); - program = new Program(Hex.decode("622A3B4C11"), invoke); + program = new Program(compile("PUSH3 0x2A3B4C GT"), invoke); try { vm.step(program); vm.step(program); @@ -839,7 +838,7 @@ public void testGT_4() { public void testSGT_1() { VM vm = new VM(); - program = new Program(Hex.decode("6001600213"), invoke); + program = new Program(compile("PUSH1 0x01 PUSH1 0x02 SGT"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000000001"; vm.step(program); @@ -853,9 +852,9 @@ public void testSGT_1() { public void testSGT_2() { VM vm = new VM(); - program = new Program(Hex.decode("7F000000000000000000000000000000000000000000000000000000000000001E" + // 30 - "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56" + // -170 - "13"), invoke); + program = new Program(compile("PUSH32 0x000000000000000000000000000000000000000000000000000000000000001E " + // 30 + "PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56 " + // -170 + "SGT"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000000000"; @@ -870,9 +869,9 @@ public void testSGT_2() { public void testSGT_3() { VM vm = new VM(); - program = new Program(Hex.decode("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56" + // -170 - "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF57" + // -169 - "13"), invoke); + program = new Program(compile("PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56 " + // -170 + "PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF57 " + // -169 + "SGT"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000000001"; @@ -887,8 +886,8 @@ public void testSGT_3() { public void testSGT_4() { VM vm = new VM(); - program = new Program(Hex.decode("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56" + // -170 - "13"), invoke); + program = new Program(compile("PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56 " + // -170 + "SGT"), invoke); try { vm.step(program); vm.step(program); @@ -902,7 +901,7 @@ public void testSGT_4() { public void testLT_1() { VM vm = new VM(); - program = new Program(Hex.decode("6001600210"), invoke); + program = new Program(compile("PUSH1 0x01 PUSH1 0x02 LT"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000000000"; vm.step(program); @@ -916,7 +915,7 @@ public void testLT_1() { public void testLT_2() { VM vm = new VM(); - program = new Program(Hex.decode("6001610F0010"), invoke); + program = new Program(compile("PUSH1 0x01 PUSH2 0x0F00 LT"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000000000"; vm.step(program); @@ -930,7 +929,7 @@ public void testLT_2() { public void testLT_3() { VM vm = new VM(); - program = new Program(Hex.decode("6301020304610F0010"), invoke); + program = new Program(compile("PUSH4 0x01020304 PUSH2 0x0F00 LT"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000000001"; vm.step(program); @@ -944,7 +943,7 @@ public void testLT_3() { public void testLT_4() { VM vm = new VM(); - program = new Program(Hex.decode("622A3B4C10"), invoke); + program = new Program(compile("PUSH3 0x2A3B4C LT"), invoke); try { vm.step(program); vm.step(program); @@ -958,7 +957,7 @@ public void testLT_4() { public void testSLT_1() { VM vm = new VM(); - program = new Program(Hex.decode("6001600212"), invoke); + program = new Program(compile("PUSH1 0x01 PUSH1 0x02 SLT"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000000000"; vm.step(program); @@ -972,9 +971,9 @@ public void testSLT_1() { public void testSLT_2() { VM vm = new VM(); - program = new Program(Hex.decode("7F000000000000000000000000000000000000000000000000000000000000001E" + // 30 - "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56" + // -170 - "12"), invoke); + program = new Program(compile("PUSH32 0x000000000000000000000000000000000000000000000000000000000000001E " + // 30 + "PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56 " + // -170 + "SLT"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000000001"; @@ -989,9 +988,9 @@ public void testSLT_2() { public void testSLT_3() { VM vm = new VM(); - program = new Program(Hex.decode("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56" + // -170 - "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF57" + // -169 - "12"), invoke); + program = new Program(compile("PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56 " + // -170 + "PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF57 " + // -169 + "SLT"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000000000"; @@ -1006,8 +1005,8 @@ public void testSLT_3() { public void testSLT_4() { VM vm = new VM(); - program = new Program(Hex.decode("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56" + // -170 - "12"), invoke); + program = new Program(compile("PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56 " + // -170 + "SLT"), invoke); try { vm.step(program); vm.step(program); @@ -1021,7 +1020,7 @@ public void testSLT_4() { public void testNOT_1() { VM vm = new VM(); - program = new Program(Hex.decode("600119"), invoke); + program = new Program(compile("PUSH1 0x01 NOT"), invoke); String expected = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"; vm.step(program); @@ -1034,7 +1033,7 @@ public void testNOT_1() { public void testNOT_2() { VM vm = new VM(); - program = new Program(Hex.decode("61A00319"), invoke); + program = new Program(compile("PUSH2 0xA003 NOT"), invoke); String expected = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5FFC"; vm.step(program); @@ -1048,7 +1047,7 @@ public void testNOT_2() { public void testBNOT_4() { VM vm = new VM(); - program = new Program(Hex.decode("1a"), invoke); + program = new Program(compile("NOT"), invoke); try { vm.step(program); vm.step(program); @@ -1061,7 +1060,7 @@ public void testBNOT_4() { public void testNOT_5() { VM vm = new VM(); - program = new Program(Hex.decode("600019"), invoke); + program = new Program(compile("PUSH1 0x00 NOT"), invoke); String expected = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; vm.step(program); @@ -1075,7 +1074,7 @@ public void testNOT_5() { public void testPOP_1() { VM vm = new VM(); - program = new Program(Hex.decode("61000060016200000250"), invoke); + program = new Program(compile("PUSH2 0x0000 PUSH1 0x01 PUSH3 0x000002 POP"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000000001"; vm.step(program); @@ -1090,7 +1089,7 @@ public void testPOP_1() { public void testPOP_2() { VM vm = new VM(); - program = new Program(Hex.decode("6100006001620000025050"), invoke); + program = new Program(compile("PUSH2 0x0000 PUSH1 0x01 PUSH3 0x000002 POP POP"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000000000"; vm.step(program); @@ -1106,7 +1105,7 @@ public void testPOP_2() { public void testPOP_3() { VM vm = new VM(); - program = new Program(Hex.decode("61000060016200000250505050"), invoke); + program = new Program(compile("PUSH2 0x0000 PUSH1 0x01 PUSH3 0x000002 POP POP POP POP"), invoke); try { vm.step(program); vm.step(program); @@ -1135,12 +1134,15 @@ public void testDUPS() { private void testDUPN_1(int n) { VM vm = new VM(); - byte operation = (byte) (OpCode.DUP1.val() + n - 1); String programCode = ""; + for (int i = 0; i < n; i++) { - programCode += "60" + (12 + i); + programCode += "PUSH1 0x" + (12 + i) + " "; } - program = new Program(ByteUtil.appendByte(Hex.decode(programCode.getBytes()), operation), invoke); + + programCode += "DUP" + n; + + program = new Program(compile(programCode), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000000012"; int expectedLen = n + 1; @@ -1160,7 +1162,7 @@ private void testDUPN_1(int n) { public void testDUPN_2() { VM vm = new VM(); - program = new Program(Hex.decode("80"), invoke); + program = new Program(compile("DUP1"), invoke); try { vm.step(program); } finally { @@ -1183,18 +1185,17 @@ public void testSWAPS() { private void testSWAPN_1(int n) { VM vm = new VM(); - byte operation = (byte) (OpCode.SWAP1.val() + n - 1); String programCode = ""; String top = new DataWord(0x10 + n).toString(); - for (int i = n; i > -1; --i) { - programCode += "60" + oneByteToHexString((byte) (0x10 + i)); + for (int i = n; i > -1; --i) { + programCode += "PUSH1 0x" + oneByteToHexString((byte) (0x10 + i)) + " "; } - programCode += Hex.toHexString(new byte[]{ (byte)(OpCode.SWAP1.val() + n - 1) }); + programCode += "SWAP" + n; - program = new Program(ByteUtil.appendByte(Hex.decode(programCode), operation), invoke); + program = new Program(compile(programCode), invoke); for (int i = 0; i < n + 2; ++i) { vm.step(program); @@ -1208,7 +1209,7 @@ private void testSWAPN_1(int n) { public void testSWAPN_2() { VM vm = new VM(); - program = new Program(Hex.decode("90"), invoke); + program = new Program(compile("SWAP1"), invoke); try { vm.step(program); @@ -1221,7 +1222,7 @@ public void testSWAPN_2() { public void testMSTORE_1() { VM vm = new VM(); - program = new Program(Hex.decode("611234600052"), invoke); + program = new Program(compile("PUSH2 0x1234 PUSH1 0x00 MSTORE"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000001234"; vm.step(program); @@ -1236,7 +1237,7 @@ public void testMSTORE_1() { public void tesLog0() { VM vm = new VM(); - program = new Program(Hex.decode("61123460005260206000A0"), invoke); + program = new Program(compile("PUSH2 0x1234 PUSH1 0x00 MSTORE PUSH1 0x20 PUSH1 0x00 LOG0"), invoke); vm.step(program); vm.step(program); @@ -1258,7 +1259,7 @@ public void tesLog0() { public void tesLog1() { VM vm = new VM(); - program = new Program(Hex.decode("61123460005261999960206000A1"), invoke); + program = new Program(compile("PUSH2 0x1234 PUSH1 0x00 MSTORE PUSH2 0x9999 PUSH1 0x20 PUSH1 0x00 LOG1"), invoke); vm.step(program); vm.step(program); @@ -1281,7 +1282,7 @@ public void tesLog1() { public void tesLog2() { VM vm = new VM(); - program = new Program(Hex.decode("61123460005261999961666660206000A2"), invoke); + program = new Program(compile("PUSH2 0x1234 PUSH1 0x00 MSTORE PUSH2 0x9999 PUSH2 0x6666 PUSH1 0x20 PUSH1 0x00 LOG2"), invoke); vm.step(program); vm.step(program); @@ -1305,7 +1306,7 @@ public void tesLog2() { public void tesLog3() { VM vm = new VM(); - program = new Program(Hex.decode("61123460005261999961666661333360206000A3"), invoke); + program = new Program(compile("PUSH2 0x1234 PUSH1 0x00 MSTORE PUSH2 0x9999 PUSH2 0x6666 PUSH2 0x3333 PUSH1 0x20 PUSH1 0x00 LOG3"), invoke); vm.step(program); vm.step(program); @@ -1331,7 +1332,7 @@ public void tesLog3() { public void tesLog4() { VM vm = new VM(); - program = new Program(Hex.decode("61123460005261999961666661333361555560206000A4"), invoke); + program = new Program(compile("PUSH2 0x1234 PUSH1 0x00 MSTORE PUSH2 0x9999 PUSH2 0x6666 PUSH2 0x3333 PUSH2 0x5555 PUSH1 0x20 PUSH1 0x00 LOG4"), invoke); vm.step(program); vm.step(program); @@ -1358,7 +1359,7 @@ public void tesLog4() { public void testMSTORE_2() { VM vm = new VM(); - program = new Program(Hex.decode("611234600052615566602052"), invoke); + program = new Program(compile("PUSH2 0x1234 PUSH1 0x00 MSTORE PUSH2 0x5566 PUSH1 0x20 MSTORE"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000001234" + "0000000000000000000000000000000000000000000000000000000000005566"; @@ -1376,7 +1377,7 @@ public void testMSTORE_2() { public void testMSTORE_3() { VM vm = new VM(); - program = new Program(Hex.decode("611234600052615566602052618888600052"), invoke); + program = new Program(compile("PUSH2 0x1234 PUSH1 0x00 MSTORE PUSH2 0x5566 PUSH1 0x20 MSTORE PUSH2 0x8888 PUSH1 0x00 MSTORE"), invoke); String expected = "0000000000000000000000000000000000000000000000000000000000008888" + "0000000000000000000000000000000000000000000000000000000000005566"; @@ -1397,7 +1398,7 @@ public void testMSTORE_3() { public void testMSTORE_4() { VM vm = new VM(); - program = new Program(Hex.decode("61123460A052"), invoke); + program = new Program(compile("PUSH2 0x1234 PUSH1 0xA0 MSTORE"), invoke); String expected = "" + "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000" + @@ -1417,7 +1418,7 @@ public void testMSTORE_4() { public void testMSTORE_5() { VM vm = new VM(); - program = new Program(Hex.decode("61123452"), invoke); + program = new Program(compile("PUSH2 0x1234 MSTORE"), invoke); try { vm.step(program); vm.step(program); @@ -1430,7 +1431,7 @@ public void testMSTORE_5() { public void testMLOAD_1() { VM vm = new VM(); - program = new Program(Hex.decode("600051"), invoke); + program = new Program(compile("PUSH1 0x00 MLOAD"), invoke); String m_expected = "0000000000000000000000000000000000000000000000000000000000000000"; String s_expected = "0000000000000000000000000000000000000000000000000000000000000000"; @@ -1445,7 +1446,7 @@ public void testMLOAD_1() { public void testMLOAD_2() { VM vm = new VM(); - program = new Program(Hex.decode("602251"), invoke); + program = new Program(compile("PUSH1 0x22 MLOAD"), invoke); String m_expected = "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000"; @@ -1463,7 +1464,7 @@ public void testMLOAD_2() { public void testMLOAD_3() { VM vm = new VM(); - program = new Program(Hex.decode("602051"), invoke); + program = new Program(compile("PUSH1 0x20 MLOAD"), invoke); String m_expected = "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000"; String s_expected = "0000000000000000000000000000000000000000000000000000000000000000"; @@ -1479,7 +1480,7 @@ public void testMLOAD_3() { public void testMLOAD_4() { VM vm = new VM(); - program = new Program(Hex.decode("611234602052602051"), invoke); + program = new Program(compile("PUSH2 0x1234 PUSH1 0x20 MSTORE PUSH1 0x20 MLOAD"), invoke); String m_expected = "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000001234"; String s_expected = "0000000000000000000000000000000000000000000000000000000000001234"; @@ -1498,7 +1499,7 @@ public void testMLOAD_4() { public void testMLOAD_5() { VM vm = new VM(); - program = new Program(Hex.decode("611234602052601F51"), invoke); + program = new Program(compile("PUSH2 0x1234 PUSH1 0x20 MSTORE PUSH1 0x1F MLOAD"), invoke); String m_expected = "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000001234"; String s_expected = "0000000000000000000000000000000000000000000000000000000000000012"; @@ -1517,7 +1518,7 @@ public void testMLOAD_5() { public void testMLOAD_6() { VM vm = new VM(); - program = new Program(Hex.decode("51"), invoke); + program = new Program(compile("MLOAD"), invoke); try { vm.step(program); } finally { @@ -1529,7 +1530,7 @@ public void testMLOAD_6() { public void testMSTORE8_1() { VM vm = new VM(); - program = new Program(Hex.decode("6011600053"), invoke); + program = new Program(compile("PUSH1 0x11 PUSH1 0x00 MSTORE8"), invoke); String m_expected = "1100000000000000000000000000000000000000000000000000000000000000"; vm.step(program); @@ -1544,7 +1545,7 @@ public void testMSTORE8_1() { public void testMSTORE8_2() { VM vm = new VM(); - program = new Program(Hex.decode("6022600153"), invoke); + program = new Program(compile("PUSH1 0x22 PUSH1 0x01 MSTORE8"), invoke); String m_expected = "0022000000000000000000000000000000000000000000000000000000000000"; vm.step(program); @@ -1558,7 +1559,7 @@ public void testMSTORE8_2() { public void testMSTORE8_3() { VM vm = new VM(); - program = new Program(Hex.decode("6022602153"), invoke); + program = new Program(compile("PUSH1 0x22 PUSH1 0x21 MSTORE8"), invoke); String m_expected = "0000000000000000000000000000000000000000000000000000000000000000" + "0022000000000000000000000000000000000000000000000000000000000000"; @@ -1573,7 +1574,7 @@ public void testMSTORE8_3() { public void testMSTORE8_4() { VM vm = new VM(); - program = new Program(Hex.decode("602253"), invoke); + program = new Program(compile("PUSH1 0x22 MSTORE8"), invoke); try { vm.step(program); vm.step(program); @@ -1587,7 +1588,7 @@ public void testSSTORE_1() { VM vm = new VM(); - program = new Program(Hex.decode("602260AA55"), invoke); + program = new Program(compile("PUSH1 0x22 PUSH1 0xAA SSTORE"), invoke); String s_expected_key = "00000000000000000000000000000000000000000000000000000000000000AA"; String s_expected_val = "0000000000000000000000000000000000000000000000000000000000000022"; @@ -1607,7 +1608,7 @@ public void testSSTORE_2() { VM vm = new VM(); - program = new Program(Hex.decode("602260AA55602260BB55"), invoke); + program = new Program(compile("PUSH1 0x22 PUSH1 0xAA SSTORE PUSH1 0x22 PUSH1 0xBB SSTORE"), invoke); String s_expected_key = "00000000000000000000000000000000000000000000000000000000000000BB"; String s_expected_val = "0000000000000000000000000000000000000000000000000000000000000022"; @@ -1629,7 +1630,7 @@ public void testSSTORE_2() { public void testSSTORE_3() { VM vm = new VM(); - program = new Program(Hex.decode("602255"), invoke); + program = new Program(compile("PUSH1 0x22 SSTORE"), invoke); try { vm.step(program); vm.step(program); @@ -1642,7 +1643,7 @@ public void testSSTORE_3() { public void testSLOAD_1() { VM vm = new VM(); - program = new Program(Hex.decode("60AA54"), invoke); + program = new Program(compile("PUSH1 0xAA SLOAD"), invoke); String s_expected = "0000000000000000000000000000000000000000000000000000000000000000"; vm.step(program); @@ -1655,7 +1656,7 @@ public void testSLOAD_1() { public void testSLOAD_2() { VM vm = new VM(); - program = new Program(Hex.decode("602260AA5560AA54"), invoke); + program = new Program(compile("PUSH1 0x22 PUSH1 0xAA SSTORE PUSH1 0xAA SLOAD"), invoke); String s_expected = "0000000000000000000000000000000000000000000000000000000000000022"; vm.step(program); @@ -1671,7 +1672,7 @@ public void testSLOAD_2() { public void testSLOAD_3() { VM vm = new VM(); - program = new Program(Hex.decode("602260AA55603360CC5560CC54"), invoke); + program = new Program(compile("PUSH1 0x22 PUSH1 0xAA SSTORE PUSH1 0x33 PUSH1 0xCC SSTORE PUSH1 0xCC SLOAD"), invoke); String s_expected = "0000000000000000000000000000000000000000000000000000000000000033"; vm.step(program); @@ -1690,7 +1691,7 @@ public void testSLOAD_3() { public void testSLOAD_4() { VM vm = new VM(); - program = new Program(Hex.decode("56"), invoke); + program = new Program(compile("SLOAD"), invoke); try { vm.step(program); } finally { @@ -1702,7 +1703,7 @@ public void testSLOAD_4() { public void testPC_1() { VM vm = new VM(); - program = new Program(Hex.decode("58"), invoke); + program = new Program(compile("PC"), invoke); String s_expected = "0000000000000000000000000000000000000000000000000000000000000000"; vm.step(program); @@ -1715,7 +1716,7 @@ public void testPC_1() { public void testPC_2() { VM vm = new VM(); - program = new Program(Hex.decode("602260AA5260AA5458"), invoke); + program = new Program(compile("PUSH1 0x22 PUSH1 0xAA MSTORE PUSH1 0xAA SLOAD PC"), invoke); String s_expected = "0000000000000000000000000000000000000000000000000000000000000008"; vm.step(program); @@ -1732,7 +1733,7 @@ public void testPC_2() { public void testJUMP_1() { VM vm = new VM(); - program = new Program(Hex.decode("60AA60BB600E5660CC60DD60EE5B60FF"), invoke); + program = new Program(compile("PUSH1 0xAA PUSH1 0xBB PUSH1 0x0E JUMP PUSH1 0xCC PUSH1 0xDD PUSH1 0xEE JUMPDEST PUSH1 0xFF"), invoke); String s_expected = "00000000000000000000000000000000000000000000000000000000000000FF"; vm.step(program); @@ -1748,7 +1749,7 @@ public void testJUMP_1() { public void testJUMP_2() { VM vm = new VM(); - program = new Program(Hex.decode("600C600C905660CC60DD60EE60FF"), invoke); + program = new Program(compile("PUSH1 0x0C PUSH1 0x0C SWAP1 JUMP PUSH1 0xCC PUSH1 0xDD PUSH1 0xEE PUSH1 0xFF"), invoke); try { vm.step(program); vm.step(program); @@ -1764,7 +1765,7 @@ public void testJUMP_2() { public void testJUMPI_1() { VM vm = new VM(); - program = new Program(Hex.decode("60016005575B60CC"), invoke); + program = new Program(compile("PUSH1 0x01 PUSH1 0x05 JUMPI JUMPDEST PUSH1 0xCC"), invoke); String s_expected = "00000000000000000000000000000000000000000000000000000000000000CC"; vm.step(program); @@ -1781,7 +1782,7 @@ public void testJUMPI_1() { public void testJUMPI_2() { VM vm = new VM(); - program = new Program(Hex.decode("630000000060445760CC60DD"), invoke); + program = new Program(compile("PUSH4 0x00000000 PUSH1 0x44 JUMPI PUSH1 0xCC PUSH1 0xDD"), invoke); String s_expected_1 = "00000000000000000000000000000000000000000000000000000000000000DD"; String s_expected_2 = "00000000000000000000000000000000000000000000000000000000000000CC"; @@ -1802,7 +1803,7 @@ public void testJUMPI_2() { public void testJUMPI_3() { VM vm = new VM(); - program = new Program(Hex.decode("600157"), invoke); + program = new Program(compile("PUSH1 0x01 JUMPI"), invoke); try { vm.step(program); vm.step(program); @@ -1815,7 +1816,7 @@ public void testJUMPI_3() { public void testJUMPI_4() { VM vm = new VM(); - program = new Program(Hex.decode("60016022909057"), invoke); + program = new Program(compile("PUSH1 0x01 PUSH1 0x22 SWAP1 SWAP1 JUMPI"), invoke); try { vm.step(program); vm.step(program); @@ -1831,7 +1832,7 @@ public void testJUMPI_4() { public void testJUMPDEST_1() { VM vm = new VM(); - program = new Program(Hex.decode("602360085660015b600255"), invoke); + program = new Program(compile("PUSH1 0x23 PUSH1 0x08 JUMP PUSH1 0x01 JUMPDEST PUSH1 0x02 SSTORE"), invoke); String s_expected_key = "0000000000000000000000000000000000000000000000000000000000000002"; String s_expected_val = "0000000000000000000000000000000000000000000000000000000000000023"; @@ -1854,7 +1855,7 @@ public void testJUMPDEST_1() { public void testJUMPDEST_2() { VM vm = new VM(); - program = new Program(Hex.decode("6023600160095760015b600255"), invoke); + program = new Program(compile("PUSH1 0x23 PUSH1 0x01 PUSH1 0x09 JUMPI PUSH1 0x01 JUMPDEST PUSH1 0x02 SSTORE"), invoke); String s_expected_key = "0000000000000000000000000000000000000000000000000000000000000002"; String s_expected_val = "0000000000000000000000000000000000000000000000000000000000000023"; @@ -1879,7 +1880,7 @@ public void testJUMPDEST_2() { public void testADD_1() { VM vm = new VM(); - program = new Program(Hex.decode("6002600201"), invoke); + program = new Program(compile("PUSH1 0x02 PUSH1 0x02 ADD"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000000004"; vm.step(program); @@ -1894,7 +1895,7 @@ public void testADD_1() { public void testADD_2() { VM vm = new VM(); - program = new Program(Hex.decode("611002600201"), invoke); + program = new Program(compile("PUSH2 0x1002 PUSH1 0x02 ADD"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000001004"; vm.step(program); @@ -1909,7 +1910,7 @@ public void testADD_2() { public void testADD_3() { VM vm = new VM(); - program = new Program(Hex.decode("6110026512345678900901"), invoke); + program = new Program(compile("PUSH2 0x1002 PUSH6 0x123456789009 ADD"), invoke); String s_expected_1 = "000000000000000000000000000000000000000000000000000012345678A00B"; vm.step(program); @@ -1924,7 +1925,7 @@ public void testADD_3() { public void testADD_4() { VM vm = new VM(); - program = new Program(Hex.decode("61123401"), invoke); + program = new Program(compile("PUSH2 0x1234 ADD"), invoke); try { vm.step(program); vm.step(program); @@ -1936,7 +1937,7 @@ public void testADD_4() { @Test // ADDMOD OP mal public void testADDMOD_1() { VM vm = new VM(); - program = new Program(Hex.decode("60026002600308"), invoke); + program = new Program(compile("PUSH1 0x02 PUSH1 0x02 PUSH1 0x03 ADDMOD"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000000001"; vm.step(program); @@ -1952,7 +1953,7 @@ public void testADDMOD_1() { @Test // ADDMOD OP public void testADDMOD_2() { VM vm = new VM(); - program = new Program(Hex.decode("6110006002611002086000"), invoke); + program = new Program(compile("PUSH2 0x1000 PUSH1 0x02 PUSH2 0x1002 ADDMOD PUSH1 0x00"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000000004"; vm.step(program); @@ -1968,7 +1969,7 @@ public void testADDMOD_2() { @Test // ADDMOD OP public void testADDMOD_3() { VM vm = new VM(); - program = new Program(Hex.decode("61100265123456789009600208"), invoke); + program = new Program(compile("PUSH2 0x1002 PUSH6 0x123456789009 PUSH1 0x02 ADDMOD"), invoke); String s_expected_1 = "000000000000000000000000000000000000000000000000000000000000093B"; vm.step(program); @@ -1984,7 +1985,7 @@ public void testADDMOD_3() { @Test(expected = StackTooSmallException.class) // ADDMOD OP mal public void testADDMOD_4() { VM vm = new VM(); - program = new Program(Hex.decode("61123408"), invoke); + program = new Program(compile("PUSH2 0x1234 ADDMOD"), invoke); try { vm.step(program); vm.step(program); @@ -1997,7 +1998,7 @@ public void testADDMOD_4() { public void testMUL_1() { VM vm = new VM(); - program = new Program(Hex.decode("6003600202"), invoke); + program = new Program(compile("PUSH1 0x03 PUSH1 0x02 MUL"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000000006"; vm.step(program); @@ -2012,7 +2013,7 @@ public void testMUL_1() { public void testMUL_2() { VM vm = new VM(); - program = new Program(Hex.decode("62222222600302"), invoke); + program = new Program(compile("PUSH3 0x222222 PUSH1 0x03 MUL"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000666666"; vm.step(program); @@ -2027,7 +2028,7 @@ public void testMUL_2() { public void testMUL_3() { VM vm = new VM(); - program = new Program(Hex.decode("622222226233333302"), invoke); + program = new Program(compile("PUSH3 0x222222 PUSH3 0x333333 MUL"), invoke); String s_expected_1 = "000000000000000000000000000000000000000000000000000006D3A05F92C6"; vm.step(program); @@ -2042,7 +2043,7 @@ public void testMUL_3() { public void testMUL_4() { VM vm = new VM(); - program = new Program(Hex.decode("600102"), invoke); + program = new Program(compile("PUSH1 0x01 MUL"), invoke); try { vm.step(program); vm.step(program); @@ -2054,7 +2055,7 @@ public void testMUL_4() { @Test // MULMOD OP public void testMULMOD_1() { VM vm = new VM(); - program = new Program(Hex.decode("60036002600409"), invoke); + program = new Program(compile("PUSH1 0x03 PUSH1 0x02 PUSH1 0x04 MULMOD"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000000002"; vm.step(program); @@ -2069,7 +2070,7 @@ public void testMULMOD_1() { @Test // MULMOD OP public void testMULMOD_2() { VM vm = new VM(); - program = new Program(Hex.decode("622222226003600409"), invoke); + program = new Program(compile("PUSH3 0x222222 PUSH1 0x03 PUSH1 0x04 MULMOD"), invoke); String s_expected_1 = "000000000000000000000000000000000000000000000000000000000000000C"; vm.step(program); @@ -2084,7 +2085,7 @@ public void testMULMOD_2() { @Test // MULMOD OP public void testMULMOD_3() { VM vm = new VM(); - program = new Program(Hex.decode("62222222623333336244444409"), invoke); + program = new Program(compile("PUSH3 0x222222 PUSH3 0x333333 PUSH3 0x444444 MULMOD"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000000000"; vm.step(program); @@ -2099,7 +2100,7 @@ public void testMULMOD_3() { @Test(expected = StackTooSmallException.class) // MULMOD OP mal public void testMULMOD_4() { VM vm = new VM(); - program = new Program(Hex.decode("600109"), invoke); + program = new Program(compile("PUSH1 0x01 MULMOD"), invoke); try { vm.step(program); vm.step(program); @@ -2112,7 +2113,7 @@ public void testMULMOD_4() { public void testDIV_1() { VM vm = new VM(); - program = new Program(Hex.decode("6002600404"), invoke); + program = new Program(compile("PUSH1 0x02 PUSH1 0x04 DIV"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000000002"; vm.step(program); @@ -2127,7 +2128,7 @@ public void testDIV_1() { public void testDIV_2() { VM vm = new VM(); - program = new Program(Hex.decode("6033609904"), invoke); + program = new Program(compile("PUSH1 0x33 PUSH1 0x99 DIV"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000000003"; vm.step(program); @@ -2143,7 +2144,7 @@ public void testDIV_2() { public void testDIV_3() { VM vm = new VM(); - program = new Program(Hex.decode("6022609904"), invoke); + program = new Program(compile("PUSH1 0x22 PUSH1 0x99 DIV"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000000004"; vm.step(program); @@ -2158,7 +2159,7 @@ public void testDIV_3() { public void testDIV_4() { VM vm = new VM(); - program = new Program(Hex.decode("6015609904"), invoke); + program = new Program(compile("PUSH1 0x15 PUSH1 0x99 DIV"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000000007"; vm.step(program); @@ -2174,7 +2175,7 @@ public void testDIV_4() { public void testDIV_5() { VM vm = new VM(); - program = new Program(Hex.decode("6004600704"), invoke); + program = new Program(compile("PUSH1 0x04 PUSH1 0x07 DIV"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000000001"; vm.step(program); @@ -2189,7 +2190,7 @@ public void testDIV_5() { public void testDIV_6() { VM vm = new VM(); - program = new Program(Hex.decode("600704"), invoke); + program = new Program(compile("PUSH1 0x07 DIV"), invoke); try { vm.step(program); vm.step(program); @@ -2202,8 +2203,7 @@ public void testDIV_6() { public void testSDIV_1() { VM vm = new VM(); - program = new Program(Hex.decode("6103E87FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC1805" + - ""), invoke); + program = new Program(compile("PUSH2 0x03E8 PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC18 SDIV"), invoke); String s_expected_1 = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; vm.step(program); @@ -2218,7 +2218,7 @@ public void testSDIV_1() { public void testSDIV_2() { VM vm = new VM(); - program = new Program(Hex.decode("60FF60FF05"), invoke); + program = new Program(compile("PUSH1 0xFF PUSH1 0xFF SDIV"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000000001"; vm.step(program); @@ -2233,7 +2233,7 @@ public void testSDIV_2() { public void testSDIV_3() { VM vm = new VM(); - program = new Program(Hex.decode("600060FF05"), invoke); + program = new Program(compile("PUSH1 0x00 PUSH1 0xFF SDIV"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000000000"; vm.step(program); @@ -2248,7 +2248,7 @@ public void testSDIV_3() { public void testSDIV_4() { VM vm = new VM(); - program = new Program(Hex.decode("60FF05"), invoke); + program = new Program(compile("PUSH1 0xFF SDIV"), invoke); try { vm.step(program); @@ -2262,7 +2262,7 @@ public void testSDIV_4() { public void testSUB_1() { VM vm = new VM(); - program = new Program(Hex.decode("6004600603"), invoke); + program = new Program(compile("PUSH1 0x04 PUSH1 0x06 SUB"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000000002"; vm.step(program); @@ -2277,7 +2277,7 @@ public void testSUB_1() { public void testSUB_2() { VM vm = new VM(); - program = new Program(Hex.decode("61444461666603"), invoke); + program = new Program(compile("PUSH2 0x4444 PUSH2 0x6666 SUB"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000002222"; vm.step(program); @@ -2292,7 +2292,7 @@ public void testSUB_2() { public void testSUB_3() { VM vm = new VM(); - program = new Program(Hex.decode("614444639999666603"), invoke); + program = new Program(compile("PUSH2 0x4444 PUSH4 0x99996666 SUB"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000099992222"; vm.step(program); @@ -2307,7 +2307,7 @@ public void testSUB_3() { public void testSUB_4() { VM vm = new VM(); - program = new Program(Hex.decode("639999666603"), invoke); + program = new Program(compile("PUSH4 0x99996666 SUB"), invoke); try { vm.step(program); vm.step(program); @@ -2320,7 +2320,7 @@ public void testSUB_4() { public void testMSIZE_1() { VM vm = new VM(); - program = new Program(Hex.decode("59"), invoke); + program = new Program(compile("MSIZE"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000000000"; vm.step(program); @@ -2333,7 +2333,7 @@ public void testMSIZE_1() { public void testMSIZE_2() { VM vm = new VM(); - program = new Program(Hex.decode("602060305259"), invoke); + program = new Program(compile("PUSH1 0x20 PUSH1 0x30 MSTORE MSIZE"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000000060"; vm.step(program); @@ -2350,7 +2350,7 @@ public void testMSIZE_2() { public void testSTOP_1() { VM vm = new VM(); - program = new Program(Hex.decode("60206030601060306011602300"), invoke); + program = new Program(compile("PUSH1 0x20 PUSH1 0x30 PUSH1 0x10 PUSH1 0x30 PUSH1 0x11 PUSH1 0x23 STOP"), invoke); int expectedSteps = 7; int i = 0; @@ -2367,7 +2367,7 @@ public void testSTOP_1() { public void testEXP_1() { VM vm = new VM(); - program = new Program(Hex.decode("600360020a"), invoke); + program = new Program(compile("PUSH1 0x03 PUSH1 0x02 EXP"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000000008"; vm.step(program); @@ -2386,7 +2386,7 @@ public void testEXP_1() { public void testEXP_2() { VM vm = new VM(); - program = new Program(Hex.decode("6000621234560a"), invoke); + program = new Program(compile("PUSH1 0x00 PUSH3 0x123456 EXP"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000000001"; vm.step(program); @@ -2405,7 +2405,7 @@ public void testEXP_2() { public void testEXP_3() { VM vm = new VM(); - program = new Program(Hex.decode("61112260010a"), invoke); + program = new Program(compile("PUSH2 0x1122 PUSH1 0x01 EXP"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000000001"; vm.step(program); @@ -2424,7 +2424,7 @@ public void testEXP_3() { public void testEXP_4() { VM vm = new VM(); - program = new Program(Hex.decode("621234560a"), invoke); + program = new Program(compile("PUSH3 0x123456 EXP"), invoke); try { vm.step(program); vm.step(program); @@ -2437,7 +2437,7 @@ public void testEXP_4() { public void testRETURN_1() { VM vm = new VM(); - program = new Program(Hex.decode("61123460005260206000F3"), invoke); + program = new Program(compile("PUSH2 0x1234 PUSH1 0x00 MSTORE PUSH1 0x20 PUSH1 0x00 RETURN"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000001234"; vm.step(program); @@ -2456,7 +2456,7 @@ public void testRETURN_1() { public void testRETURN_2() { VM vm = new VM(); - program = new Program(Hex.decode("6112346000526020601FF3"), invoke); + program = new Program(compile("PUSH2 0x1234 PUSH1 0x00 MSTORE PUSH1 0x20 PUSH1 0x1F RETURN"), invoke); String s_expected_1 = "3400000000000000000000000000000000000000000000000000000000000000"; vm.step(program); @@ -2475,8 +2475,8 @@ public void testRETURN_3() { VM vm = new VM(); program = - new Program(Hex.decode - ("7FA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4D4E4F4A1B160005260206000F3"), + new Program(compile + ("PUSH32 0xA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4D4E4F4A1B1 PUSH1 0x00 MSTORE PUSH1 0x20 PUSH1 0x00 RETURN"), invoke); String s_expected_1 = "A0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4D4E4F4A1B1"; @@ -2497,8 +2497,8 @@ public void testRETURN_4() { VM vm = new VM(); program = - new Program(Hex.decode - ("7FA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4D4E4F4A1B160005260206010F3"), + new Program(compile + ("PUSH32 0xA0B0C0D0E0F0A1B1C1D1E1F1A2B2C2D2E2F2A3B3C3D3E3F3A4B4C4D4E4F4A1B1 PUSH1 0x00 MSTORE PUSH1 0x20 PUSH1 0x10 RETURN"), invoke); String s_expected_1 = "E2F2A3B3C3D3E3F3A4B4C4D4E4F4A1B100000000000000000000000000000000"; @@ -2519,7 +2519,7 @@ public void testCODECOPY_1() { VM vm = new VM(); program = - new Program(Hex.decode("60036007600039123456"), invoke); + new Program(compile("PUSH1 0x03 PUSH1 0x07 PUSH1 0x00 CODECOPY SLT CALLVALUE JUMP"), invoke); String m_expected_1 = "1234560000000000000000000000000000000000000000000000000000000000"; vm.step(program); @@ -2538,8 +2538,8 @@ public void testCODECOPY_2() { VM vm = new VM(); program = - new Program(Hex.decode - ("605E60076000396000605f556014600054601e60205463abcddcba6040545b51602001600a5254516040016014525451606001601e5254516080016028525460a052546016604860003960166000f26000603f556103e75660005460005360200235602054"), + new Program(compile + ("PUSH1 0x5E PUSH1 0x07 PUSH1 0x00 CODECOPY PUSH1 0x00 PUSH1 0x5f SSTORE PUSH1 0x14 PUSH1 0x00 SLOAD PUSH1 0x1e PUSH1 0x20 SLOAD PUSH4 0xabcddcba PUSH1 0x40 SLOAD JUMPDEST MLOAD PUSH1 0x20 ADD PUSH1 0x0a MSTORE SLOAD MLOAD PUSH1 0x40 ADD PUSH1 0x14 MSTORE SLOAD MLOAD PUSH1 0x60 ADD PUSH1 0x1e MSTORE SLOAD MLOAD PUSH1 0x80 ADD PUSH1 0x28 MSTORE SLOAD PUSH1 0xa0 MSTORE SLOAD PUSH1 0x16 PUSH1 0x48 PUSH1 0x00 CODECOPY PUSH1 0x16 PUSH1 0x00 CALLCODE PUSH1 0x00 PUSH1 0x3f SSTORE PUSH2 0x03e7 JUMP PUSH1 0x00 SLOAD PUSH1 0x00 MSTORE8 PUSH1 0x20 MUL CALLDATALOAD PUSH1 0x20 SLOAD"), invoke); String m_expected_1 = "6000605F556014600054601E60205463ABCDDCBA6040545B51602001600A5254516040016014525451606001601E5254516080016028525460A052546016604860003960166000F26000603F556103E756600054600053602002356020540000"; @@ -2769,7 +2769,7 @@ public void testEXTCODESIZE_1() { @Test // MOD OP public void testMOD_1() { VM vm = new VM(); - program = new Program(Hex.decode("6003600406"), invoke); + program = new Program(compile("PUSH1 0x03 PUSH1 0x04 MOD"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000000001"; vm.step(program); @@ -2783,7 +2783,7 @@ public void testMOD_1() { @Test // MOD OP public void testMOD_2() { VM vm = new VM(); - program = new Program(Hex.decode("61012C6101F406"), invoke); + program = new Program(compile("PUSH2 0x012C PUSH2 0x01F4 MOD"), invoke); String s_expected_1 = "00000000000000000000000000000000000000000000000000000000000000C8"; vm.step(program); @@ -2797,7 +2797,7 @@ public void testMOD_2() { @Test // MOD OP public void testMOD_3() { VM vm = new VM(); - program = new Program(Hex.decode("6004600206"), invoke); + program = new Program(compile("PUSH1 0x04 PUSH1 0x02 MOD"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000000002"; vm.step(program); @@ -2812,7 +2812,7 @@ public void testMOD_3() { public void testMOD_4() { VM vm = new VM(); - program = new Program(Hex.decode("600406"), invoke); + program = new Program(compile("PUSH1 0x04 MOD"), invoke); try { vm.step(program); @@ -2826,7 +2826,7 @@ public void testMOD_4() { @Test // SMOD OP public void testSMOD_1() { VM vm = new VM(); - program = new Program(Hex.decode("6003600407"), invoke); + program = new Program(compile("PUSH1 0x03 PUSH1 0x04 SMOD"), invoke); String s_expected_1 = "0000000000000000000000000000000000000000000000000000000000000001"; vm.step(program); @@ -2840,9 +2840,9 @@ public void testSMOD_1() { @Test // SMOD OP public void testSMOD_2() { VM vm = new VM(); - program = new Program(Hex.decode("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE2" + // -30 - "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56" + // -170 - "07"), invoke); + program = new Program(compile("PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE2 " + // -30 + "PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56 " + // -170 + "SMOD"), invoke); String s_expected_1 = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC"; vm.step(program); @@ -2856,9 +2856,9 @@ public void testSMOD_2() { @Test // SMOD OP public void testSMOD_3() { VM vm = new VM(); - program = new Program(Hex.decode("7F000000000000000000000000000000000000000000000000000000000000001E" + // 30 - "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56" + // -170 - "07"), invoke); + program = new Program(compile("PUSH32 0x000000000000000000000000000000000000000000000000000000000000001E " + // 30 + "PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56 " + // -170 + "SMOD"), invoke); String s_expected_1 = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC"; vm.step(program); @@ -2872,8 +2872,8 @@ public void testSMOD_3() { @Test(expected = StackTooSmallException.class) // SMOD OP mal public void testSMOD_4() { VM vm = new VM(); - program = new Program(Hex.decode("7F000000000000000000000000000000000000000000000000000000000000001E" + // 30 - "07"), invoke); + program = new Program(compile("PUSH32 0x000000000000000000000000000000000000000000000000000000000000001E " + // 30 + "SMOD"), invoke); try { vm.step(program); vm.step(program); @@ -2896,6 +2896,10 @@ public void regression2Test() { String result = Program.stringifyMultiline(Hex.decode(code2)); assertTrue(result.contains("00000000000000000000000000000000")); // detecting bynary data in bytecode } + + private byte[] compile(String code) { + return new BytecodeCompiler().compile(code); + } } // TODO: add gas expeted and calculated to all test cases diff --git a/ethereumj-core/src/test/resources/config/genesis-sample.json b/ethereumj-core/src/test/resources/config/genesis-sample.json new file mode 100644 index 0000000000..6fa581b7e5 --- /dev/null +++ b/ethereumj-core/src/test/resources/config/genesis-sample.json @@ -0,0 +1,13 @@ +{ + "alloc": { + "0000000000000000000000000000000000000001": { "balance": "1" } + }, + "nonce": "0x0000000000000042", + "difficulty": "0x000002", + "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa", + "gasLimit": "0x4c4b40" +} \ No newline at end of file diff --git a/ethereumj-core/src/test/resources/logback-test.xml b/ethereumj-core/src/test/resources/logback-test.xml index d84288f647..b507161f6f 100644 --- a/ethereumj-core/src/test/resources/logback-test.xml +++ b/ethereumj-core/src/test/resources/logback-test.xml @@ -56,5 +56,6 @@ + diff --git a/ethereumj-core/src/test/resources/longrun/conf/live-fast-noprune.conf b/ethereumj-core/src/test/resources/longrun/conf/live-fast-noprune.conf new file mode 100644 index 0000000000..8201ce71ae --- /dev/null +++ b/ethereumj-core/src/test/resources/longrun/conf/live-fast-noprune.conf @@ -0,0 +1,42 @@ +peer.discovery = { + + # List of the peers to start + # the search of the online peers + # values: [ip:port, ip:port, ip:port ...] + ip.list = [ + "zero.parity.io:30303", + "54.94.239.50:30303", + "52.16.188.185:30303", + "frontier-2.ether.camp:30303", + "frontier-3.ether.camp:30303", + "frontier-4.ether.camp:30303" + ] +} + +# Network id +peer.networkId = 1 + +# Enable EIP-8 +peer.p2p.eip8 = true + +# the folder resources/genesis +# contains several versions of +# genesis configuration according +# to the network the peer will run on +genesis = frontier.json + +# Blockchain settings (constants and algorithms) which are +# not described in the genesis file (like MINIMUM_DIFFICULTY or Mining algorithm) +blockchain.config.name = "main" + +database { + # place to save physical storage files + dir = database-live +} + +peer.discovery.enabled = true +database.reset = false +sync.enabled = true +sync.fast.enabled = true +keyvalue.datasource = rocksdb +database.prune.enabled = false diff --git a/ethereumj-core/src/test/resources/longrun/conf/live-fast.conf b/ethereumj-core/src/test/resources/longrun/conf/live-fast.conf index aa548103fc..a074d21217 100644 --- a/ethereumj-core/src/test/resources/longrun/conf/live-fast.conf +++ b/ethereumj-core/src/test/resources/longrun/conf/live-fast.conf @@ -38,4 +38,4 @@ peer.discovery.enabled = true database.reset = false sync.enabled = true sync.fast.enabled = true -keyvalue.datasource = leveldb +keyvalue.datasource = rocksdb diff --git a/ethereumj-core/src/test/resources/longrun/conf/live-noprune.conf b/ethereumj-core/src/test/resources/longrun/conf/live-noprune.conf index 5a1ea0b6cc..62b577b1d1 100644 --- a/ethereumj-core/src/test/resources/longrun/conf/live-noprune.conf +++ b/ethereumj-core/src/test/resources/longrun/conf/live-noprune.conf @@ -38,5 +38,5 @@ peer.discovery.enabled = true database.reset = false sync.enabled = true sync.fast.enabled = false -keyvalue.datasource = leveldb +keyvalue.datasource = rocksdb database.prune.enabled = false diff --git a/ethereumj-core/src/test/resources/longrun/conf/live.conf b/ethereumj-core/src/test/resources/longrun/conf/live.conf index 3a3e0dc1d0..f9052524ae 100644 --- a/ethereumj-core/src/test/resources/longrun/conf/live.conf +++ b/ethereumj-core/src/test/resources/longrun/conf/live.conf @@ -31,11 +31,11 @@ blockchain.config.name = "main" database { # place to save physical storage files - dir = database-live + dir = database } peer.discovery.enabled = true database.reset = false sync.enabled = true sync.fast.enabled = false -keyvalue.datasource = leveldb +keyvalue.datasource = rocksdb diff --git a/ethereumj-core/src/test/resources/longrun/conf/ropsten-fast.conf b/ethereumj-core/src/test/resources/longrun/conf/ropsten-fast.conf index fe5a3d5af0..d1a330d7d7 100644 --- a/ethereumj-core/src/test/resources/longrun/conf/ropsten-fast.conf +++ b/ethereumj-core/src/test/resources/longrun/conf/ropsten-fast.conf @@ -34,4 +34,4 @@ peer.discovery.enabled = true database.reset = false sync.enabled = true sync.fast.enabled = true -keyvalue.datasource = leveldb +keyvalue.datasource = rocksdb diff --git a/ethereumj-core/src/test/resources/longrun/conf/ropsten-noprune.conf b/ethereumj-core/src/test/resources/longrun/conf/ropsten-noprune.conf index 55537dde39..8e477380ee 100644 --- a/ethereumj-core/src/test/resources/longrun/conf/ropsten-noprune.conf +++ b/ethereumj-core/src/test/resources/longrun/conf/ropsten-noprune.conf @@ -34,5 +34,5 @@ peer.discovery.enabled = true database.reset = false sync.enabled = true sync.fast.enabled = false -keyvalue.datasource = leveldb +keyvalue.datasource = rocksdb database.prune.enabled = false diff --git a/ethereumj-core/src/test/resources/longrun/conf/ropsten.conf b/ethereumj-core/src/test/resources/longrun/conf/ropsten.conf index 5a0759a579..e5acffd516 100644 --- a/ethereumj-core/src/test/resources/longrun/conf/ropsten.conf +++ b/ethereumj-core/src/test/resources/longrun/conf/ropsten.conf @@ -34,4 +34,4 @@ peer.discovery.enabled = true database.reset = false sync.enabled = true sync.fast.enabled = false -keyvalue.datasource = leveldb +keyvalue.datasource = rocksdb diff --git a/ethereumj-core/src/test/resources/longrun/logback.xml b/ethereumj-core/src/test/resources/longrun/logback.xml index fb1af6cb9a..87b8818d00 100644 --- a/ethereumj-core/src/test/resources/longrun/logback.xml +++ b/ethereumj-core/src/test/resources/longrun/logback.xml @@ -82,6 +82,7 @@ + diff --git a/ethereumj-core/src/test/resources/rlp/block.go.snappy b/ethereumj-core/src/test/resources/rlp/block.go.snappy new file mode 100644 index 0000000000..1e00aee72b --- /dev/null +++ b/ethereumj-core/src/test/resources/rlp/block.go.snappy @@ -0,0 +1 @@ +d6f85cf04cfa173c52f9025ca0e2a68f631cfce5e343360d0d25cbc8bffeee7562d429d59667dc8cae4289d995a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794000000420300f065a0f7a24aad39346de16e9f9c30808af19fb777f0fb968badc0fe2539864cd7c934a0fb446a7522356f8f025b9215242307e7f3dc8c68ef26ecfe85b6c81fc7902837a0e0cc0113ce83af4ec19f6a9ea0a2943406482bc37d3cd76288c86c3a3ab2cc8db90100427700fe0100fe0100fe0100ba0100b002830428ed835ebd1d835d884c84592cad6fb861d783010602846765746887676f312e372e33856c696e7578002d1cf0436f8da524f8eca93a7cf28af68dd4b11d09dae511ff4921a6c7659e73a5eb186249956dc070eac8e800833efaad28e4cbaa9b4de921a78121a8f34bf1876ecb2001a000000d4b5a010000881118c0fa1739eefa17397a56850430e23444835d3645940bda0334085413e606afb9dc807954795cd2ae8483100000ba17390f001139fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe01008e01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100820100f0b32ba06d37d3f3a1a86138990fc44c19a7e4eaeb14d71e0ee691a3f698449d66c011c5a055bfb3d854e70444ca6d7cf058ef75923ce3bab88b3da4b5d40666def6bcd1e2f86e82042e850430e23442825208943659387299a766930cbf9948899690cc5336202a8868155a43676e0000802ca0d370f301fe1402df69cb7a5e43fda113cec9c704229a21e45da21d3c90cb466da032b02fe9654dbf6b2b7726ba64106803ee8157f3d51e6edb9a01ceb84a2b1820c0 \ No newline at end of file diff --git a/ethereumj-core/src/test/resources/rlp/block.py.snappy b/ethereumj-core/src/test/resources/rlp/block.py.snappy new file mode 100644 index 0000000000..4216abf3f0 --- /dev/null +++ b/ethereumj-core/src/test/resources/rlp/block.py.snappy @@ -0,0 +1 @@ +d6f85cf04cfa173c52f9025ca0e2a68f631cfce5e343360d0d25cbc8bffeee7562d429d59667dc8cae4289d995a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794000000420200f066a0f7a24aad39346de16e9f9c30808af19fb777f0fb968badc0fe2539864cd7c934a0fb446a7522356f8f025b9215242307e7f3dc8c68ef26ecfe85b6c81fc7902837a0e0cc0113ce83af4ec19f6a9ea0a2943406482bc37d3cd76288c86c3a3ab2cc8db9010000427800fe0100fe0100fe0100b60100b002830428ed835ebd1d835d884c84592cad6fb861d783010602846765746887676f312e372e33856c696e7578002d1bf0426f8da524f8eca93a7cf28af68dd4b11d09dae511ff4921a6c7659e73a5eb186249956dc070eac8e800833efaad28e4cbaa9b4de921a78121a8f34bf1876ecb2001a0000d4a5e010000881119c0fa1739eefa17397a56850430e23444835d3645940bda0334085413e606afb9dc807954795cd2ae8483100000ba17390f001139fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe01008e01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fa01000000fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100fe0100820100f0b32ba06d37d3f3a1a86138990fc44c19a7e4eaeb14d71e0ee691a3f698449d66c011c5a055bfb3d854e70444ca6d7cf058ef75923ce3bab88b3da4b5d40666def6bcd1e2f86e82042e850430e23442825208943659387299a766930cbf9948899690cc5336202a8868155a43676e0000802ca0d370f301fe1402df69cb7a5e43fda113cec9c704229a21e45da21d3c90cb466da032b02fe9654dbf6b2b7726ba64106803ee8157f3d51e6edb9a01ceb84a2b1820c0 \ No newline at end of file diff --git a/ethereumj-core/src/test/resources/solidity/file2.sol b/ethereumj-core/src/test/resources/solidity/file2.sol index 8d038be125..30c992be3a 100644 --- a/ethereumj-core/src/test/resources/solidity/file2.sol +++ b/ethereumj-core/src/test/resources/solidity/file2.sol @@ -1,6 +1,6 @@ pragma solidity ^0.4.7; -import "file1.sol"; +import "./file1.sol"; -contract test2{ +contract test2 is test1 { } diff --git a/ethereumj-core/src/test/resources/solidity/foo/file3.sol b/ethereumj-core/src/test/resources/solidity/foo/file3.sol new file mode 100644 index 0000000000..ffde182d57 --- /dev/null +++ b/ethereumj-core/src/test/resources/solidity/foo/file3.sol @@ -0,0 +1,6 @@ +pragma solidity ^0.4.7; + +import "../file1.sol"; + +contract test3 is test1 { +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 05ef575b0c..94af96d16f 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 484f5f6f18..4cb2e008e1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Jun 07 16:00:51 OMST 2017 +#Wed Mar 21 12:14:18 OMST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip diff --git a/gradlew b/gradlew index 9d82f78915..4453ccea33 100755 --- a/gradlew +++ b/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -6,12 +6,30 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,26 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -85,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -150,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save ( ) { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index aec99730b4..e95643d6a2 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +46,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line