├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── .travis.yml ├── .travis ├── deploy.sh └── settings.xml ├── LICENSE.md ├── README.md ├── mvnw ├── pom.xml └── src ├── main └── java │ └── io │ └── github │ └── ganchix │ └── ganache │ ├── Account.java │ ├── GanacheConstants.java │ ├── GanacheContainer.java │ └── LogGanacheExtractorConsumer.java └── test ├── java └── io │ └── github │ └── ganchix │ └── ganache │ └── GanacheContainerTest.java └── resources └── logback-test.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | ### STS ### 4 | .apt_generated 5 | .classpath 6 | .factorypath 7 | .project 8 | .settings 9 | .springBeans 10 | 11 | ### IntelliJ IDEA ### 12 | .idea 13 | *.iws 14 | *.iml 15 | *.ipr 16 | 17 | ### NetBeans ### 18 | nbproject/private/ 19 | build/ 20 | nbbuild/ 21 | dist/ 22 | nbdist/ 23 | .nb-gradle/ 24 | /versioning-spring-boot/target/ 25 | /versioning-spring-boot-starter/target/ -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ganchix/testcontainers-java-module-ganache/558225b5040664f79f2de95bd004de31ae74e94e/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | 5 | script: "mvn clean cobertura:cobertura" 6 | 7 | after_success: 8 | - bash <(curl -s https://codecov.io/bash) 9 | 10 | sudo: required 11 | services: 12 | - docker 13 | 14 | install: 15 | - mvn --settings .travis/settings.xml install -DskipTests=true -Dgpg.skip -Dmaven.javadoc.skip=true -B -V 16 | before_install: 17 | - if [ ! -z "$GPG_SECRET_KEYS" ]; then echo $GPG_SECRET_KEYS | base64 --decode | $GPG_EXECUTABLE --import --batch --yes; fi 18 | - if [ ! -z "$GPG_OWNERTRUST" ]; then echo $GPG_OWNERTRUST | base64 --decode | $GPG_EXECUTABLE --import-ownertrust --batch --yes; fi 19 | deploy: 20 | - 21 | provider: script 22 | script: .travis/deploy.sh 23 | skip_cleanup: true 24 | on: 25 | repo: ganchix/testcontainers-java-module-ganache 26 | branch: master 27 | jdk: oraclejdk8 28 | - 29 | provider: script 30 | script: .travis/deploy.sh 31 | skip_cleanup: true 32 | on: 33 | repo: ganchix/testcontainers-java-module-ganache 34 | tags: true 35 | jdk: oraclejdk8 36 | 37 | -------------------------------------------------------------------------------- /.travis/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd `dirname $0`/.. 3 | 4 | if [ -z "$SONATYPE_USERNAME" ] 5 | then 6 | echo "error: please set SONATYPE_USERNAME and SONATYPE_PASSWORD environment variable" 7 | exit 1 8 | fi 9 | 10 | if [ -z "$SONATYPE_PASSWORD" ] 11 | then 12 | echo "error: please set SONATYPE_PASSWORD environment variable" 13 | exit 1 14 | fi 15 | 16 | if [ ! -z "$TRAVIS_TAG" ] 17 | then 18 | echo "on a tag -> set pom.xml to $TRAVIS_TAG" 19 | mvn -B --settings .travis/settings.xml org.codehaus.mojo:versions-maven-plugin:2.1:set -DnewVersion=$TRAVIS_TAG 1>/dev/null 2>/dev/null 20 | else 21 | echo "not on a tag -> keep snapshot version in pom.xml" 22 | fi 23 | 24 | mvn clean deploy --settings .travis/settings.xml -DskipTests=true -B -U 25 | -------------------------------------------------------------------------------- /.travis/settings.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | ossrh 9 | ${env.SONATYPE_USERNAME} 10 | ${env.SONATYPE_PASSWORD} 11 | 12 | 13 | 14 | 15 | ossrh 16 | 17 | true 18 | 19 | 20 | ${env.GPG_EXECUTABLE} 21 | ${env.GPG_PASSPHRASE} 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Rafael Ríos Moya 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TestContainers Ganache testing module [![Build Status](https://travis-ci.org/ganchix/testcontainers-java-module-ganache.svg?branch=master)](https://travis-ci.org/ganchix/testcontainers-java-module-ganache) [![codecov](https://codecov.io/gh/ganchix/testcontainers-java-module-ganache/branch/master/graph/badge.svg)](https://codecov.io/gh/ganchix/testcontainers-java-module-ganache) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.ganchix/testcontainers-java-module-ganache/badge.svg?style=plastic)](https://maven-badges.herokuapp.com/maven-central/io.github.ganchix/testcontainers-java-module-ganache) [![GitHub stars](https://img.shields.io/github/stars/badges/shields.svg?style=social&label=Star)](https://github.com/ganchix/testcontainers-java-module-ganache) 2 | 3 | Testcontainers module for [Ganache](http://truffleframework.com/ganache/). 4 | 5 | # Table of Contents 6 | 7 | - [Overview](#overview) 8 | - [Getting started](#getting-started) 9 | - [Considerations](#considerations) 10 | - [License](#license) 11 | 12 | 13 | ### Overview 14 | 15 | A simple way to test [Ethereum](https://www.ethereum.org/) in your Java Code 16 | 17 | See [testcontainers.org](https://www.testcontainers.org) for more information about Testcontainers. 18 | 19 | ### Getting started 20 | 21 | #### Add dependency 22 | 23 | ##### Maven 24 | 25 | ``` 26 | 27 | io.github.ganchix 28 | testcontainers-java-module-ganache 29 | 0.0.4 30 | 31 | ``` 32 | 33 | ##### Gradle 34 | 35 | ``` 36 | compile group: 'io.github.ganchix', name: 'testcontainers-java-module-ganache', version: '0.0.4' 37 | ``` 38 | 39 | #### Code example 40 | 41 | Running Ganache during a test: 42 | 43 | ```java 44 | public class SomeTest { 45 | 46 | 47 | @Rule 48 | public GanacheContainer GanacheContainer = new GanacheContainer(); 49 | 50 | 51 | @Test 52 | public void simpleTestWithClientCreation() { 53 | Web3j web3j = ganacheContainer.getWeb3j(); 54 | assertEquals( web3j.ethBlockNumber().send().getBlockNumber(), BigInteger.ZERO); 55 | assertNotNull(ganacheContainer); 56 | } 57 | } 58 | ``` 59 | 60 | ### Considerations 61 | To obtain the data of addresses and privates keys of console log we implemented a custom log consumer [LogGanacheExtractorConsumer](src/main/java/io/github/ganchix/ganache/LogGanacheExtractorConsumer.java), 62 | if you overwrite it this information will not be extracted. 63 | 64 | 65 | ### License 66 | 67 | Testcontainers module for Ganache is licensed under the MIT License. See [LICENSE](LICENSE.md) for details. 68 | 69 | Copyright (c) 2018 Rafael Ríos Moya 70 | 71 | 72 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # 58 | # Look for the Apple JDKs first to preserve the existing behaviour, and then look 59 | # for the new JDKs provided by Oracle. 60 | # 61 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then 62 | # 63 | # Apple JDKs 64 | # 65 | export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home 66 | fi 67 | 68 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then 69 | # 70 | # Apple JDKs 71 | # 72 | export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 73 | fi 74 | 75 | if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then 76 | # 77 | # Oracle JDKs 78 | # 79 | export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 80 | fi 81 | 82 | if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then 83 | # 84 | # Apple JDKs 85 | # 86 | export JAVA_HOME=`/usr/libexec/java_home` 87 | fi 88 | ;; 89 | esac 90 | 91 | if [ -z "$JAVA_HOME" ] ; then 92 | if [ -r /etc/gentoo-release ] ; then 93 | JAVA_HOME=`java-config --jre-home` 94 | fi 95 | fi 96 | 97 | if [ -z "$M2_HOME" ] ; then 98 | ## resolve links - $0 may be a link to maven's home 99 | PRG="$0" 100 | 101 | # need this for relative symlinks 102 | while [ -h "$PRG" ] ; do 103 | ls=`ls -ld "$PRG"` 104 | link=`expr "$ls" : '.*-> \(.*\)$'` 105 | if expr "$link" : '/.*' > /dev/null; then 106 | PRG="$link" 107 | else 108 | PRG="`dirname "$PRG"`/$link" 109 | fi 110 | done 111 | 112 | saveddir=`pwd` 113 | 114 | M2_HOME=`dirname "$PRG"`/.. 115 | 116 | # make it fully qualified 117 | M2_HOME=`cd "$M2_HOME" && pwd` 118 | 119 | cd "$saveddir" 120 | # echo Using m2 at $M2_HOME 121 | fi 122 | 123 | # For Cygwin, ensure paths are in UNIX format before anything is touched 124 | if $cygwin ; then 125 | [ -n "$M2_HOME" ] && 126 | M2_HOME=`cygpath --unix "$M2_HOME"` 127 | [ -n "$JAVA_HOME" ] && 128 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 129 | [ -n "$CLASSPATH" ] && 130 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 131 | fi 132 | 133 | # For Migwn, ensure paths are in UNIX format before anything is touched 134 | if $mingw ; then 135 | [ -n "$M2_HOME" ] && 136 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 137 | [ -n "$JAVA_HOME" ] && 138 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 139 | # TODO classpath? 140 | fi 141 | 142 | if [ -z "$JAVA_HOME" ]; then 143 | javaExecutable="`which javac`" 144 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 145 | # readlink(1) is not available as standard on Solaris 10. 146 | readLink=`which readlink` 147 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 148 | if $darwin ; then 149 | javaHome="`dirname \"$javaExecutable\"`" 150 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 151 | else 152 | javaExecutable="`readlink -f \"$javaExecutable\"`" 153 | fi 154 | javaHome="`dirname \"$javaExecutable\"`" 155 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 156 | JAVA_HOME="$javaHome" 157 | export JAVA_HOME 158 | fi 159 | fi 160 | fi 161 | 162 | if [ -z "$JAVACMD" ] ; then 163 | if [ -n "$JAVA_HOME" ] ; then 164 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 165 | # IBM's JDK on AIX uses strange locations for the executables 166 | JAVACMD="$JAVA_HOME/jre/sh/java" 167 | else 168 | JAVACMD="$JAVA_HOME/bin/java" 169 | fi 170 | else 171 | JAVACMD="`which java`" 172 | fi 173 | fi 174 | 175 | if [ ! -x "$JAVACMD" ] ; then 176 | echo "Error: JAVA_HOME is not defined correctly." >&2 177 | echo " We cannot execute $JAVACMD" >&2 178 | exit 1 179 | fi 180 | 181 | if [ -z "$JAVA_HOME" ] ; then 182 | echo "Warning: JAVA_HOME environment variable is not set." 183 | fi 184 | 185 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 186 | 187 | # For Cygwin, switch paths to Windows format before running java 188 | if $cygwin; then 189 | [ -n "$M2_HOME" ] && 190 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 191 | [ -n "$JAVA_HOME" ] && 192 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 193 | [ -n "$CLASSPATH" ] && 194 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 195 | fi 196 | 197 | # traverses directory structure from process work directory to filesystem root 198 | # first directory with .mvn subdirectory is considered project base directory 199 | find_maven_basedir() { 200 | local basedir=$(pwd) 201 | local wdir=$(pwd) 202 | while [ "$wdir" != '/' ] ; do 203 | if [ -d "$wdir"/.mvn ] ; then 204 | basedir=$wdir 205 | break 206 | fi 207 | wdir=$(cd "$wdir/.."; pwd) 208 | done 209 | echo "${basedir}" 210 | } 211 | 212 | # concatenates all lines of a file 213 | concat_lines() { 214 | if [ -f "$1" ]; then 215 | echo "$(tr -s '\n' ' ' < "$1")" 216 | fi 217 | } 218 | 219 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} 220 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 221 | 222 | # Provide a "standardized" way to retrieve the CLI args that will 223 | # work with both Windows and non-Windows executions. 224 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 225 | export MAVEN_CMD_LINE_ARGS 226 | 227 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 228 | 229 | exec "$JAVACMD" \ 230 | $MAVEN_OPTS \ 231 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 232 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 233 | ${WRAPPER_LAUNCHER} "$@" 234 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | 9 | testcontainers-parent 10 | org.testcontainers 11 | 1.6.0 12 | 13 | 14 | io.github.ganchix 15 | testcontainers-java-module-ganache 16 | 0.0.5-SNAPSHOT 17 | 18 | TestContainers :: Ganache 19 | 20 | Ganache container for testing under Testcontainers. 21 | 22 | https://github.com/ganchix/testcontainers-java-module-ganache 23 | 24 | 25 | 26 | MIT License 27 | http://www.opensource.org/licenses/mit-license.php 28 | 29 | 30 | 31 | 32 | scm:git:https://github.com/ganchix/testcontainers-java-module-ganache.git 33 | scm:git:https://github.com/ganchix/testcontainers-java-module-ganache.git 34 | 35 | https://github.com/ganchix/testcontainers-java-module-ganache/tree/master 36 | HEAD 37 | 38 | 39 | 40 | 41 | Rafael Ríos Moya 42 | ganchix@gmail.com 43 | Ganchix 44 | 45 | 46 | 47 | 48 | 3.4.0 49 | 1.4 50 | 51 | 52 | 53 | 54 | org.testcontainers 55 | testcontainers 56 | ${project.parent.version} 57 | 58 | 59 | commons-cli 60 | commons-cli 61 | ${commons-cli.version} 62 | 63 | 64 | org.web3j 65 | core 66 | ${web3j.version} 67 | 68 | 69 | 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-compiler-plugin 75 | 3.6.1 76 | 77 | 1.8 78 | 1.8 79 | 80 | 81 | 82 | org.sonatype.plugins 83 | nexus-staging-maven-plugin 84 | 1.6.8 85 | true 86 | 87 | ossrh 88 | https://oss.sonatype.org/ 89 | true 90 | 91 | 92 | 93 | org.apache.maven.plugins 94 | maven-source-plugin 95 | 3.0.1 96 | 97 | 98 | attach-sources 99 | 100 | jar-no-fork 101 | 102 | 103 | 104 | 105 | 106 | org.apache.maven.plugins 107 | maven-javadoc-plugin 108 | 2.10.4 109 | 110 | 111 | attach-javadoc 112 | 113 | jar 114 | 115 | 116 | 117 | 118 | 119 | org.apache.maven.plugins 120 | maven-gpg-plugin 121 | 1.6 122 | 123 | 124 | sign-artifacts 125 | verify 126 | 127 | sign 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | org.codehaus.mojo 137 | cobertura-maven-plugin 138 | 2.7 139 | 140 | 141 | html 142 | xml 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | ossrh 154 | https://oss.sonatype.org/content/repositories/snapshots 155 | 156 | 157 | ossrh 158 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /src/main/java/io/github/ganchix/ganache/Account.java: -------------------------------------------------------------------------------- 1 | package io.github.ganchix.ganache; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.web3j.crypto.Credentials; 8 | 9 | import java.math.BigInteger; 10 | 11 | /** 12 | * Created by Rafael Ríos on 20/05/18. 13 | */ 14 | @Data 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | @Builder 18 | public class Account { 19 | 20 | String privateKey; 21 | BigInteger balance; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/io/github/ganchix/ganache/GanacheConstants.java: -------------------------------------------------------------------------------- 1 | package io.github.ganchix.ganache; 2 | 3 | public class GanacheConstants { 4 | 5 | public static final String IMAGE = "trufflesuite/ganache-cli"; 6 | public static final String LATEST_VERSION = "latest"; 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/io/github/ganchix/ganache/GanacheContainer.java: -------------------------------------------------------------------------------- 1 | package io.github.ganchix.ganache; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.testcontainers.containers.GenericContainer; 5 | import org.web3j.crypto.Credentials; 6 | import org.web3j.protocol.Web3j; 7 | import org.web3j.protocol.http.HttpService; 8 | 9 | import java.math.BigInteger; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.stream.Collectors; 13 | 14 | import static io.github.ganchix.ganache.GanacheConstants.IMAGE; 15 | import static io.github.ganchix.ganache.GanacheConstants.LATEST_VERSION; 16 | 17 | @Slf4j 18 | public class GanacheContainer> extends GenericContainer { 19 | 20 | private static final Object DRIVER_LOAD_MUTEX = new Object(); 21 | private List options = new ArrayList<>(); 22 | private Web3j web3j; 23 | private Integer port = 8545; 24 | private List credentials = new ArrayList<>(); 25 | 26 | public GanacheContainer() { 27 | this(LATEST_VERSION); 28 | } 29 | 30 | public GanacheContainer(String version) { 31 | super(IMAGE + ":" + version); 32 | } 33 | 34 | 35 | @Override 36 | protected void configure() { 37 | withExposedPorts(port); 38 | withLogConsumer(new LogGanacheExtractorConsumer(log, this)); 39 | if (options.size() > 0) { 40 | withCommand(String.join(" ", options)); 41 | } 42 | } 43 | 44 | void addAccountPrivateKey(Integer position, String privateKey) { 45 | credentials.add(position, Credentials.create(privateKey)); 46 | } 47 | 48 | public List getCredentials() { 49 | return this.credentials; 50 | } 51 | 52 | public SELF withNumberAccounts(Integer accounts) { 53 | String option = "--accounts ".concat(accounts.toString()); 54 | options.add(option); 55 | return self(); 56 | } 57 | 58 | public SELF withDefaultBalanceEther(BigInteger defaultBalanceEther) { 59 | String option = "--defaultBalanceEther ".concat(defaultBalanceEther.toString()); 60 | options.add(option); 61 | return self(); 62 | } 63 | 64 | public SELF withBlockTime(BigInteger blockTime) { 65 | String option = "--blockTime ".concat(blockTime.toString()); 66 | options.add(option); 67 | return self(); 68 | } 69 | 70 | public SELF withDeterministic() { 71 | options.add("--deterministic"); 72 | return self(); 73 | } 74 | 75 | 76 | public SELF withGasPrice(BigInteger gasPrice) { 77 | String option = "--gasPrice ".concat(gasPrice.toString()); 78 | options.add(option); 79 | return self(); 80 | } 81 | 82 | public SELF withGasLimit(BigInteger gasLimit) { 83 | String option = "--gasLimit ".concat(gasLimit.toString()); 84 | options.add(option); 85 | return self(); 86 | } 87 | 88 | public SELF withNetworkId(Long networkId) { 89 | String option = "--networkId ".concat(networkId.toString()); 90 | options.add(option); 91 | return self(); 92 | } 93 | 94 | public SELF withFork(String location) { 95 | if (!location.startsWith("http")) { 96 | throw new RuntimeException("Location must start with http"); 97 | } 98 | String option = "--fork ".concat(location); 99 | options.add(option); 100 | return self(); 101 | } 102 | 103 | public SELF withNoVMErrorsOnRPCResponse() { 104 | options.add("--noVMErrorsOnRPCResponse"); 105 | return self(); 106 | } 107 | 108 | public SELF withSecure() { 109 | options.add("--secure"); 110 | return self(); 111 | } 112 | 113 | public SELF withMnemonic(List words) { 114 | if (words == null || words.isEmpty()) { 115 | throw new RuntimeException("Mnemonic needs a list of words"); 116 | } 117 | String option = "--mnemonic ".concat(String.join(",", words)); 118 | options.add(option); 119 | return self(); 120 | } 121 | 122 | public SELF withSeed() { 123 | options.add("--seed"); 124 | return self(); 125 | } 126 | 127 | 128 | 129 | public SELF withAccounts(List accounts) { 130 | if (accounts == null || accounts.isEmpty()) { 131 | throw new RuntimeException("Accounts can't be empty"); 132 | } 133 | List listOfAccounts = accounts.stream() 134 | .map(account -> "--account=" + generatePrivateKey(account.getPrivateKey()) + "," + account.getBalance() ) 135 | .collect(Collectors.toList()); 136 | 137 | options.addAll(listOfAccounts); 138 | return self(); 139 | } 140 | 141 | public SELF withUnlockedAccountByAddress(List addresses) { 142 | if (addresses == null || addresses.isEmpty()) { 143 | throw new RuntimeException("Addresses can't be empty"); 144 | } 145 | List listOfAccounts = addresses.stream() 146 | .map(address -> "--unlock=" + address ) 147 | .collect(Collectors.toList()); 148 | 149 | options.addAll(listOfAccounts); 150 | return self(); 151 | } 152 | 153 | public SELF withUnlockedAccountByPosition(List positions) { 154 | if (positions == null || positions.isEmpty()) { 155 | throw new RuntimeException("Positions can't be empty"); 156 | } 157 | List listOfAccounts = positions.stream() 158 | .map(position -> "--unlock=" + position ) 159 | .collect(Collectors.toList()); 160 | 161 | options.addAll(listOfAccounts); 162 | return self(); 163 | } 164 | 165 | 166 | public SELF withPort(Integer port) { 167 | this.port = port; 168 | options.add("--port ".concat(port.toString())); 169 | return self(); 170 | } 171 | 172 | public SELF withDebug() { 173 | options.add("--debug"); 174 | return self(); 175 | } 176 | 177 | public SELF withMemoryUsage() { 178 | options.add("--mem"); 179 | return self(); 180 | } 181 | 182 | public Web3j getWeb3j() { 183 | synchronized (DRIVER_LOAD_MUTEX) { 184 | if (web3j == null) { 185 | try { 186 | web3j = Web3j.build(new HttpService("http://" + getContainerIpAddress() + ":" + getMappedPort(port) + "/")); 187 | log.info("Start Web3j with net version: {}", web3j.netVersion().send().getNetVersion()); 188 | } catch (Exception e) { 189 | throw new RuntimeException("Could not get Web3j", e); 190 | } 191 | } 192 | } 193 | 194 | return web3j; 195 | 196 | } 197 | 198 | private String generatePrivateKey(String privateKey) { 199 | return privateKey.startsWith("0x") ? privateKey : "0x".concat(privateKey); 200 | } 201 | 202 | } 203 | -------------------------------------------------------------------------------- /src/main/java/io/github/ganchix/ganache/LogGanacheExtractorConsumer.java: -------------------------------------------------------------------------------- 1 | package io.github.ganchix.ganache; 2 | 3 | import org.slf4j.Logger; 4 | import org.testcontainers.containers.output.OutputFrame; 5 | 6 | import java.util.function.Consumer; 7 | import java.util.regex.Pattern; 8 | 9 | /** 10 | * Created by Rafael Ríos on 20/05/18. 11 | */ 12 | public class LogGanacheExtractorConsumer implements Consumer { 13 | private static final Pattern ANSI_CODE_PATTERN = Pattern.compile("\\[\\d[ABCD]"); 14 | private static final Pattern IS_ADDRESS_OR_PRIVATE_KEY = Pattern.compile("^\\((\\d{1,})\\) 0x.*([^(.*)])"); 15 | private final Logger logger; 16 | private String prefix = ""; 17 | private GanacheContainer ganacheContainer; 18 | 19 | public LogGanacheExtractorConsumer(Logger logger, GanacheContainer ganacheContainer) { 20 | this.logger = logger; 21 | this.ganacheContainer = ganacheContainer; 22 | } 23 | 24 | public LogGanacheExtractorConsumer withPrefix(String prefix) { 25 | this.prefix = "[" + prefix + "] "; 26 | return this; 27 | } 28 | 29 | @Override 30 | public void accept(OutputFrame outputFrame) { 31 | String utf8String = outputFrame.getUtf8String(); 32 | 33 | if (utf8String != null) { 34 | OutputFrame.OutputType outputType = outputFrame.getType(); 35 | String message = utf8String.trim(); 36 | if (IS_ADDRESS_OR_PRIVATE_KEY.matcher(message).matches()) { 37 | 38 | String[] split = message.split(" "); 39 | String privateKey = split[1].trim(); 40 | String position = split[0].replaceFirst("\\(", "").replaceFirst("\\)", ""); 41 | if (privateKey.startsWith("0x")) { 42 | ganacheContainer.addAccountPrivateKey(Integer.parseInt(position), privateKey); 43 | } 44 | } 45 | 46 | if (ANSI_CODE_PATTERN.matcher(message).matches()) { 47 | return; 48 | } 49 | 50 | switch (outputType) { 51 | case END: 52 | break; 53 | case STDOUT: 54 | case STDERR: 55 | logger.info("{}{}: {}", prefix, outputType, message); 56 | break; 57 | default: 58 | throw new IllegalArgumentException("Unexpected outputType " + outputType); 59 | } 60 | } 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /src/test/java/io/github/ganchix/ganache/GanacheContainerTest.java: -------------------------------------------------------------------------------- 1 | package io.github.ganchix.ganache; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.junit.Rule; 5 | import org.junit.Test; 6 | import org.web3j.crypto.Credentials; 7 | import org.web3j.protocol.Web3j; 8 | import org.web3j.protocol.core.DefaultBlockParameter; 9 | 10 | import java.io.IOException; 11 | import java.math.BigInteger; 12 | import java.util.Arrays; 13 | 14 | import static org.junit.Assert.assertEquals; 15 | import static org.junit.Assert.assertNotNull; 16 | 17 | @Slf4j 18 | public class GanacheContainerTest { 19 | 20 | private String PRIVATE_KEY_0 = "ae020c8ddb6fbc24e167b011666639d2ce3d4aa0d9c13d02d726d6865618a781"; 21 | private String PRIVATE_KEY_1 = "81ad1ba5c4da47feb0f0163c0c61a66c4d0e6a66bd839827444b1e3362016140"; 22 | 23 | 24 | @Rule 25 | public GanacheContainer ganacheContainer = getDefaultGanacheContainer(); 26 | 27 | private GanacheContainer getDefaultGanacheContainer() { 28 | return new GanacheContainer() 29 | .withDebug() 30 | .withPort(1485) 31 | .withNumberAccounts(2) 32 | .withDefaultBalanceEther(new BigInteger(String.valueOf(1258))) 33 | .withNetworkId(10L) 34 | .withBlockTime(BigInteger.ONE) 35 | .withMemoryUsage() 36 | .withSecure() 37 | .withNoVMErrorsOnRPCResponse() 38 | .withUnlockedAccountByPosition(Arrays.asList(0, 1)); 39 | } 40 | 41 | 42 | @Test 43 | public void simpleTestWithClientCreation() throws IOException { 44 | Web3j web3j = ganacheContainer.getWeb3j(); 45 | assertNotNull(web3j); 46 | assertEquals(web3j.ethBlockNumber().send().getBlockNumber(), BigInteger.ZERO); 47 | assertNotNull(ganacheContainer.getCredentials()); 48 | assertEquals(ganacheContainer.getCredentials().size(), 2); 49 | } 50 | 51 | 52 | @Test 53 | public void testAccountCreation() throws IOException { 54 | ganacheContainer.stop(); 55 | ganacheContainer = new GanacheContainer() 56 | .withAccounts( 57 | Arrays.asList( 58 | Account.builder().privateKey(PRIVATE_KEY_0).balance(BigInteger.ONE).build(), 59 | Account.builder().privateKey(PRIVATE_KEY_1).balance(BigInteger.TEN).build() 60 | ) 61 | ); 62 | ganacheContainer.start(); 63 | Web3j web3j = ganacheContainer.getWeb3j(); 64 | assertEquals(web3j.ethBlockNumber().send().getBlockNumber(), BigInteger.ZERO); 65 | assertEquals(ganacheContainer.getCredentials().size(), 2); 66 | assertEquals(web3j.ethGetBalance(((Credentials) ganacheContainer.getCredentials().get(0)).getAddress(), DefaultBlockParameter.valueOf("latest")).send().getBalance(), 67 | BigInteger.ONE); 68 | 69 | assertEquals(web3j.ethGetBalance(((Credentials) ganacheContainer.getCredentials().get(1)).getAddress(), DefaultBlockParameter.valueOf("latest")).send().getBalance(), 70 | BigInteger.TEN); 71 | 72 | 73 | 74 | } 75 | 76 | @Test(expected = Exception.class) 77 | public void testGetClientFail(){ 78 | ganacheContainer.stop(); 79 | ganacheContainer.getWeb3j(); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} %-5level %logger - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | PROFILER 25 | DENY 26 | 27 | 28 | --------------------------------------------------------------------------------