diff --git a/bin/multinodes.sh b/bin/multinodes.sh new file mode 100755 index 00000000..c5b7219c --- /dev/null +++ b/bin/multinodes.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +CURRENT=`pwd` +GETH_NODE_PORT=30303 +GETH_HTTP_PORT=8102 +CONSTELLATION_PORT=9000 +CAKESHOP_PORT=8080 +NODES=("node1" "node2" "node3" "node4") + + +case "$1" in + start) + for i in "${NODES[@]}" + do : + mkdir -p "$CURRENT/$i" + cp -n cakeshop.war "$CURRENT/$i" + cd "$CURRENT/$i" + nohup java -Dserver.port=$CAKESHOP_PORT -Dgeth.url=http://localhost:$GETH_HTTP_PORT -Dgeth.node.port=$GETH_NODE_PORT -Dgeth.constellaiton.url=http://127.0.0.1:$CONSTELLATION_PORT -jar cakeshop.war &>/dev/null & + sleep 5 + GETH_HTTP_PORT=$(( GETH_HTTP_PORT+1 )) + GETH_NODE_PORT=$(( GETH_NODE_PORT+1 )) + CONSTELLATION_PORT=$(( CONSTELLATION_PORT+1 )) + CAKESHOP_PORT=$(( CAKESHOP_PORT+1 )) + cd ../ + done + ;; + + stop) + killall constellation-node + killall java + ;; + + esac diff --git a/cakeshop-abi/pom.xml b/cakeshop-abi/pom.xml index 4398f1e6..ae4db867 100644 --- a/cakeshop-abi/pom.xml +++ b/cakeshop-abi/pom.xml @@ -6,7 +6,7 @@ com.jpmorgan cakeshop-parent - 0.9.1 + 0.10.0 cakeshop-abi diff --git a/cakeshop-abi/src/main/java/com/jpmorgan/cakeshop/model/ContractABI.java b/cakeshop-abi/src/main/java/com/jpmorgan/cakeshop/model/ContractABI.java index e2c14900..200bd850 100644 --- a/cakeshop-abi/src/main/java/com/jpmorgan/cakeshop/model/ContractABI.java +++ b/cakeshop-abi/src/main/java/com/jpmorgan/cakeshop/model/ContractABI.java @@ -78,8 +78,11 @@ public Constructor getConstructor() { @Override public boolean evaluate(Constructor obj) { return true; - }; - }); + } + ; + } + + ); } public Function findFunction(Predicate searchPredicate) { @@ -104,7 +107,6 @@ public String toString() { return toJson(); } - @JsonInclude(Include.NON_NULL) public static abstract class Entry { @@ -116,6 +118,7 @@ public enum Type { @JsonInclude(Include.NON_NULL) public static class Param { + public Boolean indexed; public String name; public SolidityType type; @@ -203,18 +206,21 @@ public SolidityType getType() { public final List inputs; public final List outputs; public final Type type; + public final Boolean payable; - public Entry(Boolean anonymous, Boolean constant, String name, List inputs, List outputs, Type type) { + public Entry(Boolean anonymous, Boolean constant, String name, List inputs, List outputs, Type type, Boolean payable) { this.anonymous = anonymous; this.constant = constant; this.name = name; this.inputs = inputs; this.outputs = outputs; this.type = type; + this.payable = payable == null ? Boolean.FALSE : payable; } /** * Signature of this entry, before hashing, e.g., "myfunc(uint,bytes32)" + * * @return */ public String formatSignature() { @@ -246,11 +252,11 @@ public byte[] encodeSignature() { @JsonCreator public static Entry create(@JsonProperty("anonymous") boolean anonymous, - @JsonProperty("constant") boolean constant, - @JsonProperty("name") String name, - @JsonProperty("inputs") List inputs, - @JsonProperty("outputs") List outputs, - @JsonProperty("type") Type type) { + @JsonProperty("constant") boolean constant, + @JsonProperty("name") String name, + @JsonProperty("inputs") List inputs, + @JsonProperty("outputs") List outputs, + @JsonProperty("type") Type type) { Entry result = null; switch (type) { case constructor: @@ -295,7 +301,7 @@ public Type getType() { public static class Constructor extends Entry { public Constructor(List inputs, List outputs) { - super(null, null, "", inputs, outputs, Type.constructor); + super(null, null, "", inputs, outputs, Type.constructor, false); } public List decode(byte[] encoded) { @@ -316,7 +322,7 @@ public static class Function extends Entry { private static final int ENCODED_SIGN_LENGTH = 4; public Function(boolean constant, String name, List inputs, List outputs) { - super(null, constant, name, inputs, outputs, Type.function); + super(null, constant, name, inputs, outputs, Type.function, false); } public String encodeAsHex(Object... args) { @@ -373,7 +379,7 @@ public String toString() { public static class Event extends Entry { public Event(boolean anonymous, String name, List inputs, List outputs) { - super(anonymous, null, name, inputs, outputs, Type.event); + super(anonymous, null, name, inputs, outputs, Type.event, false); } public List decode(byte[] data, byte[][] topics) { diff --git a/cakeshop-api/pom.xml b/cakeshop-api/pom.xml index 4c35d6bd..69311b35 100644 --- a/cakeshop-api/pom.xml +++ b/cakeshop-api/pom.xml @@ -1,636 +1,663 @@ - - 4.0.0 - - - com.jpmorgan - cakeshop-parent - 0.9.1 - - - cakeshop - war - - Cakeshop API - - - https://s3.amazonaws.com/com.jpmchase.cakeshop/ - - - - - windows - - - windows - - - - geth-1.5.4-b70acf3c-windows.zip - 4d5bb0b451aa9b59babba6007d56aa1538f5c7fe141ca9071c8e2fcb37e11c136ba3b9d98a810d41a5d39c5e8ad1b0a7bfd1a2aa3fcd2553d6e84b0033c4b652 - win - - - - mac - - - mac - - - - geth-1.5.4-b70acf3c-mac.zip - 0b7ee396d03c557764ea777e55282049bd308809067eea0e2b5e9f35b557f696c1662f400934e72f5cb183d0321545fd28057f7c62df76c7ae19dd6e21007e9d - mac - - - - linux - - - linux - - - - geth-1.5.4-b70acf3c-linux.zip - 966bb9b476c13f557d33c474ca63ab46146775cc7ed6dfef7b3bae64281a39cccfd2f6647d30d1dbdde39f460771daf11a90d0c1f1cae7f8f5d8fac15232807a - linux - - - - - - - - com.jpmorgan - cakeshop-abi - 0.9.1 - - - - - org.springframework.boot - spring-boot-starter-log4j - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-logging - - - org.springframework.boot - spring-boot-starter-tomcat - - - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.hateoas - spring-hateoas - - - org.springframework.plugin - spring-plugin-core - - - org.springframework - spring-context-support - - - - org.springframework.boot - spring-boot-starter-jetty - provided - - - - - org.freemarker - freemarker - - - - - org.springframework - spring-messaging - - - org.springframework - spring-websocket - - - - - org.springframework - spring-tx - - - org.springframework - spring-orm - - - org.springframework - spring-jdbc - - - org.hibernate - hibernate-core - - - org.hsqldb - hsqldb - - - mysql - mysql-connector-java - - - org.postgresql - postgresql - ${postgres.version} - - - - - - - com.fasterxml.jackson.core - jackson-annotations - - - com.fasterxml.jackson.core - jackson-core - - - com.fasterxml.jackson.core - jackson-databind - - - com.google.guava - guava - ${guava.version} - - - - - org.slf4j - slf4j-api - jar - - - org.slf4j - slf4j-log4j12 - - - - io.dropwizard.metrics - metrics-core - - - - - commons-io - commons-io - 2.4 - jar - - - org.apache.commons - commons-collections4 - 4.1 - jar - - - org.apache.commons - commons-lang3 - 3.3.2 - jar - - - org.apache.commons - commons-math3 - 3.6 - - - com.squareup.okhttp3 - okhttp - 3.2.0 - - - com.jcabi - jcabi-manifests - 1.1 - - - com.jcabi - jcabi-log - - - - - - - org.bouncycastle - bcprov-jdk15on - 1.52 - - - - - - net.java.dev.jna - jna-platform - 4.1.0 - - - - org.aspectj - aspectjweaver - - - - - - - - - - - - org.springframework - spring-test - provided - jar - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.boot - spring-boot-starter-logging - - - - - org.testng - testng - 6.9.6 - test - - - - junit - junit - jar - test - - - com.google.code.gson - gson - test - - - - - org.apache.bval - org.apache.bval.bundle - 0.5 - test - - - org.apache.geronimo.specs - geronimo-validation_1.0_spec - 1.1 - test - - - - - org.springframework.data - spring-data-jpa - 1.9.4.RELEASE - test - jar - - - javax - javaee-api - 6.0 - test - - - - - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + 4.0.0 + + + com.jpmorgan + cakeshop-parent + 0.10.0 + + + cakeshop + war + + Cakeshop API + + + https://github.com/fixanoid/cakeshop/releases/download/0.10.0/ + + + + + windows + + + windows + + + + geth-1.5.4-b70acf3c-windows.zip + 4d5bb0b451aa9b59babba6007d56aa1538f5c7fe141ca9071c8e2fcb37e11c136ba3b9d98a810d41a5d39c5e8ad1b0a7bfd1a2aa3fcd2553d6e84b0033c4b652 + win + + + + mac + + + mac + + + + geth-1.5.4-b70acf3c-mac.zip + 0b7ee396d03c557764ea777e55282049bd308809067eea0e2b5e9f35b557f696c1662f400934e72f5cb183d0321545fd28057f7c62df76c7ae19dd6e21007e9d + mac + + + + linux + + + linux + + + + geth-1.5.4-b70acf3c-linux.zip + 966bb9b476c13f557d33c474ca63ab46146775cc7ed6dfef7b3bae64281a39cccfd2f6647d30d1dbdde39f460771daf11a90d0c1f1cae7f8f5d8fac15232807a + linux + + + + - - - org.springframework.boot - spring-boot-dependencies - 1.3.3.RELEASE - pom - import - + + + com.jpmorgan + cakeshop-abi + 0.10.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-security + + + ch.qos.logback + logback-classic + + + + + org.springframework.hateoas + spring-hateoas + + + org.springframework.plugin + spring-plugin-core + + + org.springframework + spring-context-support + + + + + org.springframework.boot + spring-boot-starter-jetty + provided + + + + + org.freemarker + freemarker + + + + + org.springframework + spring-messaging + + + org.springframework + spring-websocket + + + + + org.springframework + spring-tx + + + org.springframework + spring-orm + + + org.springframework + spring-jdbc + + + org.hibernate + hibernate-core + + + org.hsqldb + hsqldb + + + mysql + mysql-connector-java + + + org.postgresql + postgresql + ${postgres.version} + + + + + + + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-databind + + + com.google.guava + guava + ${guava.version} + + + io.dropwizard.metrics + metrics-core + + + + + commons-io + commons-io + 2.5 + jar + + + org.apache.commons + commons-collections4 + 4.1 + jar + + + org.apache.commons + commons-lang3 + 3.3.2 + jar + + + org.apache.commons + commons-math3 + 3.6 + + + com.squareup.okhttp3 + okhttp + 3.2.0 + + + com.jcabi + jcabi-manifests + 1.1 + + + com.jcabi + jcabi-log + + + + + + + org.bouncycastle + bcprov-jdk15on + 1.52 + + + + + + net.java.dev.jna + jna-platform + 4.1.0 + + + + org.aspectj + aspectjweaver + + + org.thymeleaf + thymeleaf-spring4 + 2.1.4.RELEASE + + + org.thymeleaf.extras + thymeleaf-extras-springsecurity4 + 2.1.3.RELEASE + + + nz.net.ultraq.thymeleaf + thymeleaf-layout-dialect + 2.2.0 + + + io.springfox + springfox-swagger2 + 2.6.1 + + + io.springfox + springfox-swagger-ui + 2.6.1 + + + + + + + + org.springframework + spring-test + provided + jar + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.testng + testng + 6.9.6 + test + + + + junit + junit + jar + test + + + com.google.code.gson + gson + test + + + + + org.apache.bval + org.apache.bval.bundle + 0.5 + test + + + org.apache.geronimo.specs + geronimo-validation_1.0_spec + 1.1 + test + + + + + org.springframework.data + spring-data-jpa + 1.9.4.RELEASE + test + jar + + + javax + javaee-api + 6.0 + test + + - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - ${java.version} - ${java.version} - - ${endorsed.dir} - - - - - - org.apache.maven.plugins - maven-war-plugin - - false - - - true - - - ${project.version} - ${buildNumber} - ${timestamp} - - - **/node_modules/ - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - - - - - org.codehaus.mojo - buildnumber-maven-plugin - 1.3 - - - validate - - create - create-timestamp - - - - - false - false - {0,date,yyyy-MM-dd'T'HH:mm:ss.SSSZ} - - - - - org.apache.maven.plugins - maven-surefire-plugin - - ${skipTests} - - - - - com.github.eirslett - frontend-maven-plugin - 1.2 - - - src/main/webapp - target - v6.9.1 - 3.10.9 - - - - - - install node and npm - - install-node-and-npm - - - - - npm install - - npm - - - - - webpack build - - webpack - - - -p - - - - - npm-install-solc - - npm - - - src/main/resources/geth/solc - - - - - - - - maven-clean-plugin - - - - src/main/webapp/node_modules - false - - - src/main/resources/geth/solc/node_modules - false - - - src/main/webapp/js - - index-gen.js* - *.hot-update.* - vendor/cakeshop.js - - false - - - - - - - com.googlecode.maven-download-plugin - download-maven-plugin - 1.3.0 - - - install-geth - generate-resources - - wget - - - ${geth.url.base}/${geth.file} - true - ${project.build.directory}/classes/geth/bin/${geth.dir}/ - ${geth.sha512} - - - - - - - maven-resources-plugin - 3.0.1 - - - copy-nodejs - generate-resources - - copy-resources - - - ${project.build.directory}/classes/geth/bin/${geth.dir}/ - - - ${project.build.directory}/node/ - - node - node.exe - - - - - - - copy-cakeshop-js - generate-sources - - copy-resources - - - src/main/webapp/js/vendor/ - - - ../cakeshop-client-js/target/dist/ - - cakeshop.js - - - - - - - - - - - - - - - - org.eclipse.m2e - lifecycle-mapping - 1.0.0 - - - - - - org.codehaus.mojo - buildnumber-maven-plugin - [1.3,) - - create-timestamp - - - - - - - - - com.googlecode.maven-download-plugin - download-maven-plugin - [1.3,) - - wget - - - - - false - - - - - - com.github.eirslett - frontend-maven-plugin - [1.2,) - - - install-node-and-npm - npm - webpack - - - - - false - - - - - - - - - - - + + + + + + org.springframework.boot + spring-boot-dependencies + 1.4.3.RELEASE + pom + import + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + ${endorsed.dir} + + + + + + org.apache.maven.plugins + maven-war-plugin + + false + + + true + + + ${project.version} + ${buildNumber} + ${timestamp} + + + **/node_modules/ + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + org.codehaus.mojo + buildnumber-maven-plugin + 1.3 + + + validate + + create + create-timestamp + + + + + false + false + {0,date,yyyy-MM-dd'T'HH:mm:ss.SSSZ} + + + + + org.apache.maven.plugins + maven-surefire-plugin + + ${skipTests} + + + + + com.github.eirslett + frontend-maven-plugin + 1.2 + + + src/main/webapp + target + v6.9.1 + 3.10.9 + + + + + + install node and npm + + install-node-and-npm + + + + + npm install + + npm + + + + + webpack build + + webpack + + + -p + + + + + npm-install-solc + + npm + + + src/main/resources/geth/solc + + + + + + + + maven-clean-plugin + + + + src/main/webapp/node_modules + false + + + src/main/resources/geth/solc/node_modules + false + + + src/main/webapp/js + + index-gen.js* + *.hot-update.* + vendor/cakeshop.js + + false + + + + + + + com.googlecode.maven-download-plugin + download-maven-plugin + 1.3.0 + + + install-geth + generate-resources + + wget + + + ${geth.url.base}/${geth.file} + true + ${project.build.directory}/classes/geth/bin/${geth.dir}/ + ${geth.sha512} + + + + + + + maven-resources-plugin + 3.0.1 + + + copy-nodejs + generate-resources + + copy-resources + + + ${project.build.directory}/classes/geth/bin/${geth.dir}/ + + + ${project.build.directory}/node/ + + node + node.exe + + + + + + + copy-cakeshop-js + generate-sources + + copy-resources + + + src/main/webapp/js/vendor/ + + + ../cakeshop-client-js/target/dist/ + + cakeshop.js + + + + + + + + + + + + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + org.codehaus.mojo + buildnumber-maven-plugin + [1.3,) + + create-timestamp + + + + + + + + + com.googlecode.maven-download-plugin + download-maven-plugin + [1.3,) + + wget + + + + + false + + + + + + com.github.eirslett + frontend-maven-plugin + [1.2,) + + + install-node-and-npm + npm + webpack + + + + + false + + + + + + + + + + + diff --git a/cakeshop-api/src/main/java/com/jcabi/log/Logger.java b/cakeshop-api/src/main/java/com/jcabi/log/Logger.java index f2239965..e6c6da33 100644 --- a/cakeshop-api/src/main/java/com/jcabi/log/Logger.java +++ b/cakeshop-api/src/main/java/com/jcabi/log/Logger.java @@ -1,8 +1,8 @@ package com.jcabi.log; /** - * com.jcabi.log.Logger started causing issues in Spring Boot so we simply - * stub it out for now. + * com.jcabi.log.Logger started causing issues in Spring Boot so we simply stub + * it out for now. * * @author Chetan Sarva */ @@ -17,6 +17,9 @@ public static void info(final Object source, final String msg, Object... args) { public static void warn(final Object source, final String msg, Object... args) { } + public static void warn(final Object source, final String msg) { + } + public static void error(final Object source, final String msg, Object... args) { } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/bean/GethConfigBean.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/bean/GethConfigBean.java index 7b3b4d13..6c86bfd6 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/bean/GethConfigBean.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/bean/GethConfigBean.java @@ -2,7 +2,6 @@ import static com.jpmorgan.cakeshop.util.FileUtils.*; import static com.jpmorgan.cakeshop.util.ProcessUtils.*; -import static org.apache.commons.io.FileUtils.*; import com.jpmorgan.cakeshop.util.FileUtils; import com.jpmorgan.cakeshop.util.SortedProperties; @@ -14,6 +13,7 @@ import java.io.IOException; import java.net.URI; import java.util.Properties; +import java.util.Scanner; import javax.annotation.PostConstruct; @@ -40,6 +40,9 @@ public class GethConfigBean { @Value("${config.path}") private String CONFIG_ROOT; + @Autowired + private QuorumConfigBean quorumConfig; + private String configFile; private String binPath; @@ -48,6 +51,8 @@ public class GethConfigBean { private String gethPidFilename; + private String constPidFileName; + private String gethPasswordFile; private String genesisBlockFilename; @@ -58,10 +63,13 @@ public class GethConfigBean { private String solcPath; + private String publicKey; + /** * Whether or not this is a quorum node */ private Boolean isQuorum; + private boolean isEmbeddedQuorum; private Properties props; @@ -76,6 +84,8 @@ public class GethConfigBean { private final String GETH_AUTO_STOP = "geth.auto.stop"; private final String GETH_START_TIMEOUT = "geth.start.timeout"; private final String GETH_UNLOCK_TIMEOUT = "geth.unlock.timeout"; + private final String EMBEDDED_NODE = null != System.getProperty("geth.node") ? System.getProperty("geth.node") : null; + public final Boolean IS_BOOT_NODE = null != System.getProperty("geth.boot.node"); //geth.db.enabled private final String GETH_DB_ENABLED = "cakeshop.database.vendor"; @@ -86,6 +96,16 @@ public class GethConfigBean { private final String GETH_MINING = "geth.mining"; private final String GETH_IDENTITY = "geth.identity"; private final String GETH_EXTRA_PARAMS = "geth.params.extra"; + // Quorum specific settings + private final String GETH_BLOCK_MAKER = "geth.block.maker"; + private final String GETH_VOTE_ACCOUNT = "geth.vote.account"; + private final String GETH_BLOCK_MAKER_PASS = "geth.block.maker.pass"; + private final String GETH_VOTE_ACCOUNT_PASS = "geth.vote.account.pass"; + private final String GETH_MIN_BLOCKTIME = "geth.min.blocktime"; + private final String GETH_MAX_BLOCKTIME = "geth.max.blocktime"; + private final String GETH_VOTE_CONTARCT_ADDRESS = "geth.vote.contract.addr"; + private final String GETH_CONSTELLATION_ENABLED = "geth.constellation.enabled"; + private final String GETH_PERMISSIONED = "geth.permissioned"; public GethConfigBean() { } @@ -104,17 +124,12 @@ public void initFromVendorConfig() throws IOException { private void initBean() { try { initGethBean(); - } catch (IOException ex) { + } catch (IOException | InterruptedException ex) { LOG.error(ex.getMessage()); } } - private void initGethBean() throws IOException { - - // load props - configFile = FileUtils.expandPath(CONFIG_ROOT, "application.properties"); - props = new Properties(); - props.load(new FileInputStream(configFile)); + private void initGethBean() throws IOException, InterruptedException { // setup needed paths String baseResourcePath = System.getProperty("eth.geth.dir"); @@ -122,6 +137,11 @@ private void initGethBean() throws IOException { baseResourcePath = FileUtils.getClasspathName("geth"); } + // load props + configFile = FileUtils.expandPath(CONFIG_ROOT, "application.properties"); + props = new Properties(); + props.load(new FileInputStream(configFile)); + // Choose correct geth binary if (SystemUtils.IS_OS_WINDOWS) { LOG.debug("Using geth for windows"); @@ -141,10 +161,8 @@ private void initGethBean() throws IOException { throw new IOException("Path does not exist or is not executable: " + gethPath); } binPath = new File(gethPath).getParent(); - gethPidFilename = expandPath(CONFIG_ROOT, "geth.pid"); - // init genesis block file (using vendor copy if necessary) String vendorGenesisDir = expandPath(baseResourcePath, "genesis"); @@ -174,8 +192,8 @@ private void initGethBean() throws IOException { if (SystemUtils.IS_OS_WINDOWS) { nodePath = nodePath + ".exe"; } - solcPath = expandPath(baseResourcePath, "solc", "node_modules", "solc-cli", "bin", "solc"); - + solcPath = expandPath(baseResourcePath, "solc", "node_modules", "solc-cakeshop-cli", "bin", "solc"); + ensureNodeBins(solcPath); // Clean up data dir path for default config (not an absolute path) if (getDataDirPath() != null) { if (getDataDirPath().startsWith("/.ethereum")) { @@ -208,10 +226,33 @@ private void initGethBean() throws IOException { if (LOG.isDebugEnabled()) { LOG.debug(StringUtils.toString(this)); } + + if (StringUtils.isBlank(EMBEDDED_NODE)) { + // default to quorum + setGethPath(quorumConfig.getQuorumPath()); + + String destination = StringUtils.isNotBlank(System.getProperty("spring.config.location")) + ? System.getProperty("spring.config.location").replaceAll("file:", "") + .replaceAll("application.properties", "").concat("constellation-node/") + : getDataDirPath().concat("/constellation/"); + + quorumConfig.createKeys("node", destination); + quorumConfig.createQuorumConfig("node", destination); + setConstPidFileName(expandPath(CONFIG_ROOT, "constellation.pid")); + setIsEmbeddedQuorum(true); + + File pubKey = new File(destination.concat("node.pub")); + try (Scanner scanner = new Scanner(pubKey)) { + while (scanner.hasNext()) { + setPublicKey(scanner.nextLine()); + } + } + } } /** * Make sure all node bins are executable, both for win & mac/linux + * * @param nodePath * @param solcPath */ @@ -236,6 +277,20 @@ public void setGethPidFilename(String gethPidFilename) { this.gethPidFilename = gethPidFilename; } + /** + * @return the constPidFileName + */ + public String getConstPidFileName() { + return constPidFileName; + } + + /** + * @param constPidFileName the constPidFileName to set + */ + public void setConstPidFileName(String constPidFileName) { + this.constPidFileName = constPidFileName; + } + public String getDataDirPath() { return props.getProperty(GETH_DATA_DIR); } @@ -269,20 +324,36 @@ public void setRpcUrl(String rpcUrl) { } public String getRpcPort() { - String url = getRpcUrl(); - if (StringUtils.isBlank(url)) { - return null; - } - URI uri = URI.create(url); - return Integer.toString(uri.getPort()); + String url = getRpcUrl(); + if (StringUtils.isBlank(url)) { + return null; + } + URI uri = URI.create(url); + return Integer.toString(uri.getPort()); } public String getRpcApiList() { - return props.getProperty(GETH_RPCAPI_LIST); + if (StringUtils.isBlank(EMBEDDED_NODE)) { + if (props.getProperty(GETH_RPCAPI_LIST).contains("quorum")) { + return props.getProperty(GETH_RPCAPI_LIST); + } else { + return props.getProperty(GETH_RPCAPI_LIST).concat(",").concat("quorum"); + } + } else { + return props.getProperty(GETH_RPCAPI_LIST); + } } public void setRpcApiList(String rpcApiList) { - props.setProperty(GETH_RPCAPI_LIST, rpcApiList); + if (StringUtils.isBlank(EMBEDDED_NODE)) { + if (rpcApiList.contains("quorum")) { + props.setProperty(GETH_RPCAPI_LIST, rpcApiList); + } else { + props.setProperty(GETH_RPCAPI_LIST, rpcApiList.concat(",").concat("quorum")); + } + } else { + props.setProperty(GETH_RPCAPI_LIST, rpcApiList); + } } public String getGethNodePort() { @@ -341,6 +412,82 @@ public void setIdentity(String identity) { props.setProperty(GETH_IDENTITY, identity); } + public String getBlockMaker() { + return props.getProperty(GETH_BLOCK_MAKER); + } + + public void setBlockMaker(String blockMaker) { + props.setProperty(GETH_BLOCK_MAKER, blockMaker); + } + + public String getBlockMakerPass() { + return props.getProperty(GETH_BLOCK_MAKER_PASS); + } + + public void setBlockMakerPass(String pass) { + props.setProperty(GETH_BLOCK_MAKER_PASS, pass); + } + + public String getVoteAccount() { + return props.getProperty(GETH_VOTE_ACCOUNT); + } + + public void setVoteAccount(String voteAccount) { + props.setProperty(GETH_VOTE_ACCOUNT, voteAccount); + } + + public String getVoteAccountPass() { + return props.getProperty(GETH_VOTE_ACCOUNT_PASS); + } + + public void setVoteAccountPass(String pass) { + props.setProperty(GETH_VOTE_ACCOUNT_PASS, pass); + } + + public Integer getMinBlockTime() { + return StringUtils.isNotBlank(props.getProperty(GETH_MIN_BLOCKTIME)) ? Integer.valueOf(props.getProperty(GETH_MIN_BLOCKTIME)) : 2; + } + + public Integer getMaxBlockTime() { + return StringUtils.isNotBlank(props.getProperty(GETH_MAX_BLOCKTIME)) ? Integer.valueOf(props.getProperty(GETH_MAX_BLOCKTIME)) : 5; + } + + public void setMinBlockTime(Integer minblockTime) { + props.setProperty(GETH_MIN_BLOCKTIME, String.valueOf(minblockTime)); + } + + public void setMaxBlockTime(Integer maxblockTime) { + props.setProperty(GETH_MAX_BLOCKTIME, String.valueOf(maxblockTime)); + } + + public String getVoteContractAddress() { + return props.getProperty(GETH_VOTE_CONTARCT_ADDRESS); + } + + public void setVoteContractAddress(String address) { + props.setProperty(GETH_VOTE_CONTARCT_ADDRESS, address); + } + + public Boolean isConstellationEnabled() { + return StringUtils.isNotBlank(props.getProperty(GETH_CONSTELLATION_ENABLED)) + ? Boolean.valueOf(props.getProperty(GETH_CONSTELLATION_ENABLED)) + : Boolean.TRUE; + } + + public void setConstellationEnabled(Boolean isEnabled) { + props.setProperty(GETH_CONSTELLATION_ENABLED, String.valueOf(isEnabled)); + } + + public Boolean isPermissionedNode() { + return StringUtils.isNotBlank(props.getProperty(GETH_PERMISSIONED)) + ? Boolean.valueOf(props.getProperty(GETH_PERMISSIONED)) + : Boolean.FALSE; + } + + public void setPermissionedNode(Boolean isPermissionedNode) { + props.setProperty(GETH_PERMISSIONED, String.valueOf(isPermissionedNode)); + } + public String getBinPath() { return binPath; } @@ -429,10 +576,9 @@ public void setQuorum(Boolean isQuorum) { this.isQuorum = isQuorum; } - - /** * Write the underlying config file to disk (persist all properties) + * * @throws IOException */ public void save() throws IOException { @@ -450,8 +596,8 @@ public void setProperty(String key, String val) { } /** - * Simple wrapper around {@link Properties#getProperty(String)} which handles empty strings - * and nulls properly + * Simple wrapper around {@link Properties#getProperty(String)} which + * handles empty strings and nulls properly * * @param key * @param defaultStr @@ -461,4 +607,32 @@ private String get(String key, String defaultStr) { return StringUtils.defaultIfBlank(props.getProperty(key), defaultStr); } + /** + * @return the isEmbeddedQuorum + */ + public boolean isEmbeddedQuorum() { + return isEmbeddedQuorum; + } + + /** + * @param isEmbeddedQuorum the isEmbeddedQuorum to set + */ + public void setIsEmbeddedQuorum(boolean isEmbeddedQuorum) { + this.isEmbeddedQuorum = isEmbeddedQuorum; + } + + /** + * @return the publicKey + */ + public String getPublicKey() { + return publicKey; + } + + /** + * @param publicKey the publicKey to set + */ + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/bean/QuorumConfigBean.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/bean/QuorumConfigBean.java new file mode 100644 index 00000000..ad46efe5 --- /dev/null +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/bean/QuorumConfigBean.java @@ -0,0 +1,294 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jpmorgan.cakeshop.bean; + +import com.jpmorgan.cakeshop.util.FileUtils; +import static com.jpmorgan.cakeshop.util.FileUtils.expandPath; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.Scanner; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.SystemUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class QuorumConfigBean implements InitializingBean { + + private static final Logger LOG = LoggerFactory.getLogger(QuorumConfigBean.class); + + private static final String QUORUM_LINUX_COMMAND = "quorum/linux/geth"; + private static final String QUORUM_MAC_COMMAND = "quorum/mac/geth"; + private static final String CONSTELLATION_LINUX_COMMAND = "quorum/constellation/linux/constellation-node"; + private static final String CONSTELLATION_MAC_COMMAND = "quorum/constellation/mac/constellation-node"; + private static final String CONSTELLATION_LINUX_KEYGEN = "quorum/constellation/linux/constellation-enclave-keygen"; + private static final String CONSTELLATION_MAC_KEYGEN = "quorum/constellation/mac/constellation-enclave-keygen"; + private final String CONSTELLATION_URL = StringUtils.isNotBlank(System.getProperty("geth.constellaiton.url")) + ? System.getProperty("geth.constellaiton.url") : "http://127.0.0.1:9000"; + + private final String EMBEDDED_NODE = null != System.getProperty("geth.node") ? System.getProperty("geth.node") : null; + + private String quorumPath; + private String constellationPath; + private String keyGen; + private String constellationConfig; + + @Value("${geth.bootnodes.list:\"\"}") + private String bootNodes; + @Value("${geth.bootnode.key:\"\"}") + private String bootNodeKey; + @Value("${geth.bootnode.address:\"\"}") + private String bootNodeAddress; + @Value("${geth.boot.node:false}") + private Boolean isBootNode; + + /** + * @return the quorumPath + */ + public String getQuorumPath() { + return quorumPath; + } + + /** + * @param quorumPath the quorumPath to set + */ + private void setQuorumPath(String quorumPath) { + this.quorumPath = quorumPath; + } + + /** + * @return the constallationPath + */ + public String getConstellationPath() { + return constellationPath; + } + + /** + * @param constellationPath the constallationPath to set + */ + private void setConstellationPath(String constallationPath) { + this.constellationPath = constallationPath; + } + + /** + * @return the keyGen + */ + public String getKeyGen() { + return keyGen; + } + + /** + * @param keyGen the keyGen to set + */ + private void setKeyGen(String keyGen) { + this.keyGen = keyGen; + } + + public String getConstellationConfigPath() { + return constellationConfig; + } + + /** + * @return the bootNode + */ + public String getBootNodes() { + return bootNodes; + } + + /** + * @return the bootNodeKey + */ + public String getBootNodeKey() { + return bootNodeKey; + } + + /** + * @return the bootNodeAddress + */ + public String getBootNodeAddress() { + return bootNodeAddress; + } + + /** + * @return the isBootNode + */ + public Boolean isBootNode() { + return isBootNode; + } + + public void createKeys(final String keyName, final String destination) throws IOException, InterruptedException { + constellationConfig = destination; + File dir = new File(destination); + Boolean createKeys = true; + + if (!dir.exists()) { + dir.mkdirs(); + } else { + String[] fileNames = dir.list(); + if (fileNames.length >= 4) { + for (String fileName : fileNames) { + if (fileName.endsWith(".key") || fileName.endsWith(".pub")) { + createKeys = false; + break; + } + } + } + } + + if (createKeys) { + //create keys + ProcessBuilder pb = new ProcessBuilder(getKeyGen(), destination.concat(keyName)); + Process process = pb.start(); + try (Scanner scanner = new Scanner(process.getInputStream())) { + boolean flag = scanner.hasNext(); + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()))) { + while (flag) { + String line = scanner.next(); + if (line.isEmpty()) { + continue; + } + if (line.contains("[none]:")) { + writer.newLine(); + writer.flush(); + writer.newLine(); + writer.flush(); + flag = false; + } + } + } + } + + int ret = process.waitFor(); + if (ret != 0) { + LOG.error("Failed to generate keys. Please make sure that berkeley db is installed properly. Version of berkeley db is 6.2.23"); + } else { + //create archive keys + pb = new ProcessBuilder(getKeyGen(), destination.concat(keyName.concat("a"))); + process = pb.start(); + try (Scanner scanner = new Scanner(process.getInputStream())) { + boolean flag = scanner.hasNext(); + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()))) { + while (flag) { + String line = scanner.next(); + if (line.isEmpty()) { + continue; + } + if (line.contains("[none]:")) { + writer.write(" "); + writer.flush(); + writer.newLine(); + writer.flush(); + flag = false; + } + } + } + } + + ret = process.waitFor(); + if (ret != 0) { + LOG.error("Failed to generate keys. Please make sure that berkeley db is installed properly. Version of berkeley db is 6.2.23"); + } + } + + if (process.isAlive()) { + process.destroy(); + } + } + } + + public void createQuorumConfig(String keyName, final String destination) throws IOException { + File confFile = new File(destination.concat(keyName.concat(".conf"))); + if (!confFile.exists()) { + keyName = destination.concat(keyName); + try (FileWriter writer = new FileWriter(confFile)) { + String url = CONSTELLATION_URL.endsWith("/") ? CONSTELLATION_URL.replaceFirst("(.*)" + "/" + "$", "$1" + "") : CONSTELLATION_URL; + String port = url.substring(url.lastIndexOf(":") + 1, url.length()); + writer.write("url = \"" + url + "/" + "\""); + writer.write("\n"); + writer.write("port = " + port); + writer.write("\n"); + writer.write("socketPath = \"" + keyName + ".ipc\""); + writer.write("\n"); + writer.write("otherNodeUrls = []"); + writer.write("\n"); + writer.write("publicKeyPath = \"" + keyName + ".pub\""); + writer.write("\n"); + writer.write("privateKeyPath = \"" + keyName + ".key\""); + writer.write("\n"); + writer.write("archivalPublicKeyPath = \"" + keyName + "a" + ".pub\""); + writer.write("\n"); + writer.write("archivalPrivateKeyPath = \"" + keyName + "a" + ".key\""); + writer.write("\n"); + writer.write("storagePath = \"" + destination + "constellation\""); + writer.flush(); + } + } + } + + @Override + public void afterPropertiesSet() throws Exception { + initQuorumBean(); + } + + private void initQuorumBean() { + + // setup needed paths + String baseResourcePath = System.getProperty("eth.geth.dir"); + if (StringUtils.isBlank(baseResourcePath)) { + baseResourcePath = FileUtils.getClasspathName("geth"); + } + + if (SystemUtils.IS_OS_LINUX) { + LOG.debug("Using quorum for linux"); + setQuorumPath(expandPath(baseResourcePath, QUORUM_LINUX_COMMAND)); + setConstellationPath(expandPath(baseResourcePath, CONSTELLATION_LINUX_COMMAND)); + setKeyGen(expandPath(baseResourcePath, CONSTELLATION_LINUX_KEYGEN)); + + } else if (SystemUtils.IS_OS_MAC_OSX) { + LOG.debug("Using quorum for mac"); + setQuorumPath(expandPath(baseResourcePath, QUORUM_MAC_COMMAND)); + setConstellationPath(expandPath(baseResourcePath, CONSTELLATION_MAC_COMMAND)); + setKeyGen(expandPath(baseResourcePath, CONSTELLATION_MAC_KEYGEN)); + + } else if ( (SystemUtils.IS_OS_WINDOWS) && (StringUtils.equalsIgnoreCase(EMBEDDED_NODE, "geth")) ) { + // run GETH + return; + + } else if (SystemUtils.IS_OS_WINDOWS) { + LOG.error("Running on unsupported OS! Only Linux and Mac OS X are currently supported for Quorum, on Windoze, please run with -Dgeth.node=geth"); + throw new IllegalArgumentException("Running on unsupported OS! Only Linux and Mac OS X are currently supported for Quorum, on Windoze, please run with -Dgeth.node=geth"); + + } else { + LOG.error("Running on unsupported OS! Only Linux and Mac OS X are currently supported"); + throw new IllegalArgumentException("Running on unsupported OS! Only Linux and Mac OS X are currently supported"); + } + + File quorumExec = new File(getQuorumPath()); + if (!quorumExec.canExecute()) { + quorumExec.setExecutable(true); + } + + File constExec = new File(getConstellationPath()); + if (!constExec.canExecute()) { + constExec.setExecutable(true); + } + + File keyGenExec = new File(getKeyGen()); + if (!keyGenExec.canExecute()) { + keyGenExec.setExecutable(true); + } + + } + +} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/AppConfig.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/AppConfig.java index ed533219..79c38272 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/AppConfig.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/AppConfig.java @@ -32,21 +32,21 @@ public class AppConfig implements AsyncConfigurer { public static final String CONFIG_FILE = "application.properties"; /** - * Return the configured environment name (via spring.profiles.active system prop) + * Return the configured environment name (via spring.profiles.active system + * prop) * * @return String Environment name */ public static String getEnv() { - return System.getProperty("spring.profiles.active"); + return System.getProperty("spring.profiles.active"); } /** * Return the configured config location * - * Search order: - * - ETH_CONFIG environment variable - * - eth.config.dir system property (-Deth.config.dir param) - * - Detect tomcat (container) root relative to classpath + * Search order: - ETH_CONFIG environment variable - eth.config.dir system + * property (-Deth.config.dir param) - Detect tomcat (container) root + * relative to classpath * * @return */ @@ -99,12 +99,12 @@ public static void initVendorConfig(File configFile) throws IOException { Properties mergedProps = new Properties(); mergedProps.load(FileUtils.getClasspathStream(getVendorConfigFile())); mergedProps.load(FileUtils.getClasspathStream(getVendorEnvConfigFile())); - + setSecurity(mergedProps); SortedProperties.store(mergedProps, new FileOutputStream(configFile)); } public static PropertySourcesPlaceholderConfigurer createPropConfigurer(String configDir) - throws IOException { + throws IOException { if (StringUtils.isBlank(getEnv())) { throw new IOException("System property 'spring.profiles.active' not set; unable to load config"); @@ -116,11 +116,12 @@ public static PropertySourcesPlaceholderConfigurer createPropConfigurer(String c File configFile = new File(configPath.getPath() + File.separator + CONFIG_FILE); if (!configPath.exists() || !configFile.exists()) { + LOG.debug("Config dir does not exist, will init"); configPath.mkdirs(); if (!configPath.exists()) { - throw new IOException("Unable to create config dir: " + configPath.getAbsolutePath()); + throw new IOException("Unable to create config dir: " + configPath.getAbsolutePath()); } initVendorConfig(configFile); @@ -129,6 +130,7 @@ public static PropertySourcesPlaceholderConfigurer createPropConfigurer(String c Properties mergedProps = new Properties(); mergedProps.load(FileUtils.getClasspathStream(getVendorEnvConfigFile())); mergedProps.load(new FileInputStream(configFile)); // overwrite vendor props with our configs + setSecurity(mergedProps); SortedProperties.store(mergedProps, new FileOutputStream(configFile)); } @@ -146,7 +148,7 @@ public static PropertySourcesPlaceholderConfigurer createPropConfigurer(String c return propConfig; } - @Bean(name="asyncExecutor") + @Bean(name = "asyncExecutor") @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor(); @@ -170,5 +172,38 @@ public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new SimpleAsyncUncaughtExceptionHandler(); } + private static void setSecurity(Properties properties) { + Boolean securityEnabled + = StringUtils.isNotBlank(System.getProperty("cakeshop.security.enabled")) + ? Boolean.valueOf(System.getProperty("cakeshop.security.enabled")) + : false; + if (securityEnabled) { + if (StringUtils.isNotBlank(properties.getProperty("management.security.enabled"))) { + properties.remove("management.security.enabled"); + System.setProperty("management.security.enabled", "true"); + } + if (StringUtils.isNotBlank(properties.getProperty("security.basic.enabled"))) { + properties.remove("security.basic.enabled"); + System.setProperty("security.basic.enabled", "true"); + } + if (StringUtils.isNotBlank(properties.getProperty("security.ignored"))) { + System.setProperty("security.ignored", ""); + properties.remove("security.ignored"); + } + + } else { + if (StringUtils.isBlank(properties.getProperty("management.security.enabled"))) { + properties.setProperty("management.security.enabled", "false"); + } + if (StringUtils.isBlank(properties.getProperty("security.basic.enabled"))) { + properties.setProperty("security.basic.enabled", "false"); + } + if (StringUtils.isBlank(properties.getProperty("security.ignored"))) { + properties.setProperty("security.ignored", "/**"); + } + LOG.warn("Authentication disabled."); + } + + } } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/AppStartup.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/AppStartup.java index 9907c145..16ec3133 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/AppStartup.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/AppStartup.java @@ -7,16 +7,16 @@ import com.jpmorgan.cakeshop.service.GethHttpService; import com.jpmorgan.cakeshop.util.EEUtils; import com.jpmorgan.cakeshop.util.FileUtils; +import com.jpmorgan.cakeshop.util.MemoryUtils; import com.jpmorgan.cakeshop.util.ProcessUtils; import com.jpmorgan.cakeshop.util.StreamGobbler; import com.jpmorgan.cakeshop.util.StringUtils; import java.io.File; -import java.io.FilenameFilter; import java.io.IOException; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -34,10 +34,11 @@ import org.springframework.stereotype.Service; @Order(999999) -@Service(value="appStartup") +@Service(value = "appStartup") public class AppStartup implements ApplicationListener { private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(AppStartup.class); + private final Long REQUIRED_MEMORY = 2000000L; @Value("${config.path}") private String CONFIG_ROOT; @@ -66,15 +67,15 @@ public AppStartup() { public void onApplicationEvent(ApplicationEvent event) { if (event instanceof EmbeddedServletContainerInitializedEvent) { - // this event fires after context refresh and after geth has started - int port = ((EmbeddedServletContainerInitializedEvent) event).getEmbeddedServletContainer().getPort(); - System.out.println(" url: " + getSpringUrl(port)); - System.out.println(); - return; + // this event fires after context refresh and after geth has started + int port = ((EmbeddedServletContainerInitializedEvent) event).getEmbeddedServletContainer().getPort(); + System.out.println(" url: " + getSpringUrl(port)); + System.out.println(); + return; } if (!(event instanceof ContextRefreshedEvent)) { - return; + return; } if (autoStartFired) { @@ -97,10 +98,10 @@ public void onApplicationEvent(ApplicationEvent event) { gethConfig.setAutoStop(false); gethConfig.setRpcUrl("http://localhost:22000"); try { - gethConfig.save(); + gethConfig.save(); } catch (IOException e) { - LOG.error("Error writing application.properties: " + e.getMessage()); - System.exit(1); + LOG.error("Error writing application.properties: " + e.getMessage()); + System.exit(1); } System.out.println("initialization complete. wrote quorum-example config. exiting."); System.exit(0); @@ -108,7 +109,7 @@ public void onApplicationEvent(ApplicationEvent event) { if (gethConfig.isAutoStart()) { LOG.info("Autostarting geth node"); - healthy = geth.start(); + healthy = geth.start(); if (!healthy) { addError("GETH FAILED TO START"); } @@ -116,7 +117,6 @@ public void onApplicationEvent(ApplicationEvent event) { } else { // run startup tasks only geth.runPostStartupTasks(); - } } @@ -139,7 +139,6 @@ private void printErrorReport() { System.out.println(); System.out.println(StringUtils.repeat("*", 80)); - System.out.println("PRINTING DEBUG INFO"); System.out.println("-------------------"); System.out.println(); @@ -152,7 +151,6 @@ private void printErrorReport() { System.out.println(); System.out.println(getErrorInfo()); - System.out.println(); System.out.println(StringUtils.repeat("*", 80)); System.out.println(); @@ -177,53 +175,53 @@ private void printWelcomeMessage() { // Try to determine listening URL private String getSpringUrl(int port) { - String uri = "http://"; - try { - uri = uri + EEUtils.getAllIPs().get(0).getAddr(); - } catch (APIException e) { - uri = uri + "localhost"; - } - return uri + ":" + Integer.toString(port) + "/cakeshop/"; + String uri = "http://"; + try { + uri = uri + EEUtils.getAllIPs().get(0).getAddr(); + } catch (APIException e) { + uri = uri + "localhost"; + } + return uri + ":" + Integer.toString(port) + "/cakeshop/"; } public String getDebugInfo(ServletContext servletContext) { StringBuilder out = new StringBuilder(); - out.append("java.vendor: " + SystemUtils.JAVA_VENDOR + "\n"); - out.append("java.version: " + System.getProperty("java.version") + "\n"); - out.append("java.home: " + SystemUtils.JAVA_HOME + "\n"); - out.append("java.io.tmpdir: " + SystemUtils.JAVA_IO_TMPDIR + "\n"); + out.append("java.vendor: ").append(SystemUtils.JAVA_VENDOR).append("\n"); + out.append("java.version: ").append(System.getProperty("java.version")).append("\n"); + out.append("java.home: ").append(SystemUtils.JAVA_HOME).append("\n"); + out.append("java.io.tmpdir: ").append(SystemUtils.JAVA_IO_TMPDIR).append("\n"); out.append("\n"); out.append("servlet.container: "); if (servletContext != null) { - out.append(servletContext.getServerInfo()); + out.append(servletContext.getServerInfo()); } out.append("\n\n"); - out.append("cakeshop.version: " + AppVersion.BUILD_VERSION + "\n"); - out.append("cakeshop.build.id: " + AppVersion.BUILD_ID + "\n"); - out.append("cakeshop.build.date: " + AppVersion.BUILD_DATE + "\n"); + out.append("cakeshop.version: ").append(AppVersion.BUILD_VERSION).append("\n"); + out.append("cakeshop.build.id: ").append(AppVersion.BUILD_ID).append("\n"); + out.append("cakeshop.build.date: ").append(AppVersion.BUILD_DATE).append("\n"); out.append("\n"); - out.append("os.name: " + SystemUtils.OS_NAME + "\n"); - out.append("os.version: " + SystemUtils.OS_VERSION + "\n"); - out.append("os.arch: " + SystemUtils.OS_ARCH + "\n"); + out.append("os.name: ").append(SystemUtils.OS_NAME).append("\n"); + out.append("os.version: ").append(SystemUtils.OS_VERSION).append("\n"); + out.append("os.arch: ").append(SystemUtils.OS_ARCH).append("\n"); out.append("\n"); out.append(getLinuxInfo()); - out.append("user.dir: " + SystemUtils.getUserDir() + "\n"); - out.append("user.home: " + SystemUtils.getUserHome() + "\n"); + out.append("user.dir: ").append(SystemUtils.getUserDir()).append("\n"); + out.append("user.home: ").append(SystemUtils.getUserHome()).append("\n"); out.append("\n"); - out.append("app.root: " + FileUtils.getClasspathPath("") + "\n"); - out.append("eth.env: " + System.getProperty("eth.environment") + "\n"); - out.append("eth.config.dir: " + CONFIG_ROOT + "\n"); + out.append("app.root: ").append(FileUtils.getClasspathPath("")).append("\n"); + out.append("eth.env: ").append(System.getProperty("eth.environment")).append("\n"); + out.append("eth.config.dir: ").append(CONFIG_ROOT).append("\n"); out.append("\n"); - out.append("geth.path: " + gethConfig.getGethPath() + "\n"); - out.append("geth.data.dir: " + gethConfig.getDataDirPath() + "\n"); + out.append("geth.path: ").append(gethConfig.getGethPath()).append("\n"); + out.append("geth.data.dir: ").append(gethConfig.getDataDirPath()).append("\n"); out.append("geth.version: "); if (StringUtils.isNotBlank(gethVer)) { out.append(gethVer); @@ -232,7 +230,7 @@ public String getDebugInfo(ServletContext servletContext) { } out.append("\n\n"); - out.append("solc.path: " + gethConfig.getSolcPath() + "\n"); + out.append("solc.path: ").append(gethConfig.getSolcPath()).append("\n"); out.append("solc.version: "); if (StringUtils.isNotBlank(solcVer)) { out.append(solcVer); @@ -254,12 +252,7 @@ private String getLinuxInfo() { List files = new ArrayList<>(); if (dir.exists()) { - files.addAll(Lists.newArrayList(dir.listFiles(new FilenameFilter() { - @Override - public boolean accept(File dir, String filename) { - return filename.endsWith("release"); - } - }))); + files.addAll(Lists.newArrayList(dir.listFiles((File dir1, String filename) -> filename.endsWith("release")))); } // looks for the version file (not all linux distros) @@ -278,15 +271,15 @@ public boolean accept(File dir, String filename) { str.append("Linux release info:"); // prints all the version-related files - for (File f : files) { + files.forEach((f) -> { try { - String ver = FileUtils.readFileToString(f); + String ver = FileUtils.readFileToString(f, Charset.defaultCharset()); if (!StringUtils.isBlank(ver)) { str.append(ver); } } catch (IOException e) { } - } + }); str.append("\n\n"); return str.toString(); } @@ -297,17 +290,14 @@ private List getAllErrors() { allErrors.addAll(errors); allErrors.addAll(geth.getStartupErrors()); - Collections.sort(allErrors, new Comparator() { - @Override - public int compare(ErrorLog o1, ErrorLog o2) { - long result = o1.nanos - o2.nanos; - if (result < 0) { - return -1; - } else if (result > 0) { - return 1; - } else { - return 0; - } + Collections.sort(allErrors, (ErrorLog o1, ErrorLog o2) -> { + long result = o1.nanos - o2.nanos; + if (result < 0) { + return -1; + } else if (result > 0) { + return 1; + } else { + return 0; } }); return allErrors; @@ -320,9 +310,9 @@ public String getErrorInfo() { } StringBuilder out = new StringBuilder(); - for (ErrorLog err : allErrors) { + allErrors.forEach((err) -> { out.append(err.toString()).append("\n\n"); - } + }); return out.toString(); } @@ -332,7 +322,7 @@ public String getErrorInfo() { * @return */ private boolean testSystemHealth() { - boolean healthy = true; + boolean isHealthy = true; System.out.println(); System.out.println(); @@ -348,7 +338,7 @@ private boolean testSystemHealth() { System.out.println("OK"); } else { System.out.println("FAILED"); - healthy = false; + isHealthy = false; } // test config & db data dir @@ -360,15 +350,15 @@ private boolean testSystemHealth() { System.out.println("OK"); } else { System.out.println("FAILED"); - healthy = false; + isHealthy = false; } // test geth binary System.out.println(); System.out.println("Testing geth server binary"); String gethOutput = testBinary(gethConfig.getGethPath(), "version"); - if (gethOutput == null || gethOutput.indexOf("Version:") < 0) { - healthy = false; + if (gethOutput == null || !gethOutput.contains("Version:")) { + isHealthy = false; System.out.println("FAILED"); } else { Matcher matcher = Pattern.compile("^Version: (.*)", Pattern.MULTILINE).matcher(gethOutput); @@ -382,8 +372,8 @@ private boolean testSystemHealth() { System.out.println(); System.out.println("Testing solc compiler binary"); String solcOutput = testBinary(gethConfig.getNodePath(), gethConfig.getSolcPath(), "--version"); - if (solcOutput == null || solcOutput.indexOf("Version:") < 0) { - healthy = false; + if (solcOutput == null || !solcOutput.contains("Version:")) { + isHealthy = false; System.out.println("FAILED"); } else { Matcher matcher = Pattern.compile("^Version: (.*)", Pattern.MULTILINE).matcher(solcOutput); @@ -394,7 +384,7 @@ private boolean testSystemHealth() { } System.out.println(); - if (healthy) { + if (isHealthy) { System.out.println("ALL TESTS PASSED!"); } else { System.out.println("!!! SYSTEM FAILED SELF-TEST !!!"); @@ -404,7 +394,13 @@ private boolean testSystemHealth() { System.out.println(StringUtils.repeat("*", 80)); System.out.println(); - return healthy; + //Check if total memory or free memory is Less than 2 GB + if (MemoryUtils.getMemoryData(false) < REQUIRED_MEMORY && MemoryUtils.getMemoryData(true) < REQUIRED_MEMORY) { + errors.add(new ErrorLog("System does not have enough total or free RAM to run cakeshop. Need at least 2 GB of free RAM")); + isHealthy = false; + } + + return isHealthy; } private String testBinary(String... args) { diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/JsonMethodArgumentResolver.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/JsonMethodArgumentResolver.java deleted file mode 100644 index b9ed240d..00000000 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/JsonMethodArgumentResolver.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.jpmorgan.cakeshop.config; - -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import java.io.BufferedReader; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.List; -import java.util.Map; - -import javax.servlet.http.HttpServletRequest; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.core.MethodParameter; -import org.springframework.web.bind.annotation.ValueConstants; -import org.springframework.web.bind.support.WebDataBinderFactory; -import org.springframework.web.context.request.NativeWebRequest; -import org.springframework.web.method.support.HandlerMethodArgumentResolver; -import org.springframework.web.method.support.ModelAndViewContainer; - -public class JsonMethodArgumentResolver implements HandlerMethodArgumentResolver { - - private static final Logger LOG = LoggerFactory.getLogger(JsonMethodArgumentResolver.class); - - private static final ObjectMapper objectMapper = new ObjectMapper(); - - @Target(ElementType.PARAMETER) - @Retention(RetentionPolicy.RUNTIME) - @Documented - public @interface JsonBodyParam { - - /** - * The name of the request parameter to bind to. - */ - String value() default ""; - - /** - * Whether the parameter is required. - *

Default is {@code true}, leading to an exception thrown in case - * of the parameter missing in the request. Switch this to {@code false} - * if you prefer a {@code null} in case of the parameter missing. - *

Alternatively, provide a {@link #defaultValue() defaultValue}, - * which implicitly sets this flag to {@code false}. - */ - boolean required() default true; - - /** - * The default value to use as a fallback when the request parameter value - * is not provided or empty. Supplying a default value implicitly sets - * {@link #required()} to false. - */ - String defaultValue() default ValueConstants.DEFAULT_NONE; - - } - - @Override - public boolean supportsParameter(MethodParameter parameter) { - return (parameter.getParameterAnnotation(JsonBodyParam.class) != null); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - @Override - public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, - NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { - - if (!mavContainer.getModel().containsAttribute("_json_data")) { - BufferedReader postReader = ((HttpServletRequest)webRequest.getNativeRequest()).getReader(); - Map data = null; - try { - data = objectMapper.readValue(postReader, Map.class); - } catch (JsonMappingException ex) { - } - mavContainer.addAttribute("_json_data", data); - } - - - Map data = (Map) mavContainer.getModel().get("_json_data"); - - JsonBodyParam jsonParam = parameter.getParameterAnnotation(JsonBodyParam.class); - String param = jsonParam.value(); - if (param == null || param.isEmpty() || param.equals(jsonParam.defaultValue())) { - param = parameter.getParameterName(); // fallback to name of param itself - } - - Class paramType = parameter.getParameterType(); - Object val = data == null ? null : data.get(param); - if (val == null) { - // handle null val - if (!jsonParam.defaultValue().contentEquals(ValueConstants.DEFAULT_NONE)) { - return jsonParam.defaultValue(); - } - return val; - } - if (paramType == val.getClass() || paramType == Object.class) { - return val; // val types match exactly or Object type was requested - } - - if ((paramType == Long.class || paramType == Integer.class) - && (val instanceof Long || val instanceof Integer)) { - - // flip type of val - if (paramType == Long.class) { - return new Integer((int) val).longValue(); - } else { - return new Long((long) val).intValue(); - } - } - - if (paramType.isArray()) { - if (val.getClass().isArray()) { - return val; - } - - if (val instanceof List) { - return ((List) val).toArray(); - } - } - - if (paramType == List.class && val instanceof List) { - return val; - } - - LOG.warn("Param type mismatch for '" + parameter.getParameterName() + "'; got " + val.getClass().toString()); - return null; - } - -} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SecurityConfig.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SecurityConfig.java new file mode 100644 index 00000000..1054165b --- /dev/null +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SecurityConfig.java @@ -0,0 +1,44 @@ +package com.jpmorgan.cakeshop.config; + +import com.jpmorgan.cakeshop.service.auth.impl.AuthenticationService; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(SecurityConfig.class); + + @Autowired + @Qualifier("authService") + private AuthenticationService authService; + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + auth.eraseCredentials(Boolean.FALSE); + auth.authenticationProvider(authService); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + + http + .authorizeRequests() + .antMatchers("/resources/**", "/user").permitAll() + .anyRequest().authenticated() + .and().formLogin().failureUrl("/login?error") + .loginPage("/login").permitAll() + .and().httpBasic() + .and().logout().logoutSuccessUrl("/login").permitAll(); + + http.csrf().disable(); + } + +} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SecurityWebAppInitializer.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SecurityWebAppInitializer.java new file mode 100644 index 00000000..133579f2 --- /dev/null +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SecurityWebAppInitializer.java @@ -0,0 +1,8 @@ +package com.jpmorgan.cakeshop.config; + +import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; + +public class SecurityWebAppInitializer { + //extends AbstractSecurityWebApplicationInitializer { + +} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SpringBootApplication.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SpringBootApplication.java index f78e5382..c08e9435 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SpringBootApplication.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SpringBootApplication.java @@ -6,6 +6,7 @@ import java.io.File; import java.io.IOException; +import java.net.URL; import java.util.Enumeration; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -40,7 +41,7 @@ public static void main(String[] args) { } if (StringUtils.isNotBlank(System.getProperty("spring.profiles.active"))) { - System.setProperty("spring.config.location", "file:" + FileUtils.expandPath(AppConfig.getConfigPath(), "application.properties")); + System.setProperty("spring.config.location", "file:" + FileUtils.expandPath(AppConfig.getConfigPath(), "application.properties")); } // extract geth from WAR (if necessary) @@ -64,27 +65,35 @@ public static void main(String[] args) { // boot app new SpringApplicationBuilder(SpringBootApplication.class) - .profiles("container", "spring-boot") - .run(args); + .profiles("container", "spring-boot") + .run(args); } private static void extractGeth(String configDir) throws IOException { - File war = FileUtils.toFile(GethConfigBean.class.getClassLoader().getResource("")); - if (!war.toString().endsWith(".war")) { - return; // no need to copy - } + URL url = GethConfigBean.class.getClassLoader().getResource(""); + String warUrl = null; - String gethDir = FileUtils.expandPath(configDir, "geth"); - System.out.println("Extracting geth to " + gethDir); + if (url.getProtocol().equals("jar")) { + warUrl = url.toString().replaceFirst("jar:", ""); + warUrl = warUrl.substring(0, warUrl.indexOf("!")); + } + + URL newUrl = StringUtils.isNotBlank(warUrl) ? new URL(warUrl) : url; + File war = FileUtils.toFile(newUrl); + + if (!war.toString().endsWith(".war")) { + return; // no need to copy + } + + String gethDir = FileUtils.expandPath(configDir, "geth"); + System.out.println("Extracting geth to " + gethDir); - ZipFile warZip = null; - try { - warZip = new ZipFile(war); + try (ZipFile warZip = new ZipFile(war)) { Enumeration entries = warZip.entries(); while (entries.hasMoreElements()) { ZipEntry zipEntry = entries.nextElement(); String file = zipEntry.getName(); - if (zipEntry.isDirectory() || !file.startsWith("WEB-INF/classes/geth")) { + if (zipEntry.isDirectory() || !file.startsWith("WEB-INF/classes/geth")) { continue; } @@ -95,13 +104,9 @@ private static void extractGeth(String configDir) throws IOException { } FileUtils.copyInputStreamToFile(warZip.getInputStream(zipEntry), target); } - } finally { - if (warZip != null) { - warZip.close(); - } - } + } - System.setProperty("eth.geth.dir", gethDir); + System.setProperty("eth.geth.dir", gethDir); } @Bean diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SwaggerConfig.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SwaggerConfig.java new file mode 100644 index 00000000..cc02f03d --- /dev/null +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/SwaggerConfig.java @@ -0,0 +1,41 @@ +package com.jpmorgan.cakeshop.config; + +import com.google.common.base.Predicates; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +@Configuration +@EnableSwagger2 +public class SwaggerConfig { + + @Bean + public Docket api() { + return new Docket(DocumentationType.SWAGGER_2) + .select() + .apis(RequestHandlerSelectors.basePackage("com.jpmorgan.cakeshop.controller")) + .paths(Predicates.not(PathSelectors.regex("/login"))) + .paths(Predicates.not(PathSelectors.regex("/logout"))) + .paths(Predicates.not(PathSelectors.regex("/user"))) + .build() + .apiInfo(apiInfo()); + } + + private ApiInfo apiInfo() { + ApiInfo apiInfo = new ApiInfo( + "Cakeshop REST API", + "Sets of APIs to manage ethereum.", + "API 1.0", + "Terms of service", + ApiInfo.DEFAULT_CONTACT, + "License of API", + "API license URL"); + return apiInfo; + } + +} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/WebAppInit.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/WebAppInit.java index 52798644..591ea435 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/WebAppInit.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/WebAppInit.java @@ -13,37 +13,42 @@ import org.springframework.boot.context.web.SpringBootServletInitializer; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; @Configuration @EnableAutoConfiguration +@EnableWebMvc @ComponentScan(basePackages = "com.jpmorgan.cakeshop") public class WebAppInit extends SpringBootServletInitializer { - - + public static void setLoggingPath(boolean isSpringBoot) { // setup logging path for spring-boot if (StringUtils.isNotBlank(System.getProperty("logging.path"))) { return; } if (isSpringBoot) { - System.setProperty("logging.path", "."); + System.setProperty("logging.path", "logs"); return; } // running in a container, find home path - if (StringUtils.isNotBlank(System.getProperty("catalina.base"))) { + if (StringUtils.isNotBlank(System.getProperty("catalina.base")) + && StringUtils.isBlank(System.getProperty("logging.path"))) { // tomcat System.setProperty("logging.path", System.getProperty("catalina.base") + "/logs"); - } else if (StringUtils.isNotBlank(System.getProperty("catalina.home"))) { + } else if (StringUtils.isNotBlank(System.getProperty("catalina.home")) + && StringUtils.isBlank(System.getProperty("logging.path"))) { // tomcat System.setProperty("logging.path", System.getProperty("catalina.home") + "/logs"); - } else if (StringUtils.isNotBlank(System.getProperty("jetty.logging.dir"))) { + } else if (StringUtils.isNotBlank(System.getProperty("jetty.logging.dir")) + && StringUtils.isBlank(System.getProperty("logging.path"))) { // jetty System.setProperty("logging.path", System.getProperty("jetty.logging.dir")); - } else if (StringUtils.isNotBlank(System.getProperty("jetty.home"))) { + } else if (StringUtils.isNotBlank(System.getProperty("jetty.home")) + && StringUtils.isBlank(System.getProperty("logging.path"))) { // jetty System.setProperty("logging.path", System.getProperty("jetty.home") + "/logs"); @@ -57,14 +62,12 @@ public static void setLoggingPath(boolean isSpringBoot) { System.setProperty("logging.path", "/tmp"); } - } // @Override // protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { // return builder.profiles(env.getActiveProfiles()); // } - @Override public void onStartup(ServletContext container) throws ServletException { setLoggingPath(false); @@ -75,7 +78,7 @@ public void onStartup(ServletContext container) throws ServletException { } container.addListener(new SessionListener()); super.onStartup(container); - + } } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/WebConfig.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/WebConfig.java index a90d458d..b1ed8750 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/WebConfig.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/config/WebConfig.java @@ -1,9 +1,8 @@ package com.jpmorgan.cakeshop.config; -import java.util.ArrayList; -import java.util.List; +import java.io.FileInputStream; +import java.util.Properties; -import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import org.springframework.beans.factory.annotation.Autowired; @@ -13,15 +12,20 @@ import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; -import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; -import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import okhttp3.OkHttpClient; +import org.springframework.web.servlet.ViewResolver; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.thymeleaf.spring4.SpringTemplateEngine; +import org.thymeleaf.spring4.view.ThymeleafViewResolver; +import org.thymeleaf.templateresolver.ServletContextTemplateResolver; +import org.springframework.beans.factory.annotation.Value; +import com.jpmorgan.cakeshop.util.FileUtils; /** * @@ -31,50 +35,36 @@ @EnableScheduling public class WebConfig extends WebMvcConfigurerAdapter { - @Autowired - private Environment env; + private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(WebConfig.class); + + @Value("${config.path}") + private String CONFIG_ROOT; @Autowired - private RequestMappingHandlerAdapter adapter; + private Environment env; @Autowired private OkHttpClient okHttpClient; - @PostConstruct - public void prioritizeCustomArgumentMethodHandlers() { - // existing resolvers - List argumentResolvers - = new ArrayList<>(adapter.getArgumentResolvers()); - - // add our resolvers at pos 0 - List customResolvers - = adapter.getCustomArgumentResolvers(); - - // empty and re-add our custom list - argumentResolvers.removeAll(customResolvers); - argumentResolvers.addAll(0, customResolvers); - - adapter.setArgumentResolvers(argumentResolvers); - } - @Override - public void addArgumentResolvers(List argumentResolvers) { - super.addArgumentResolvers(argumentResolvers); - argumentResolvers.add(new JsonMethodArgumentResolver()); - } - - @Override - public void configureAsyncSupport(AsyncSupportConfigurer configurer) { configurer.setTaskExecutor(createMvcAsyncExecutor()); } @Override public void addCorsMappings(CorsRegistry registry) { - if (Boolean.valueOf(env.getProperty("geth.cors.enabled:true"))) { + String configFile = FileUtils.expandPath(CONFIG_ROOT, "application.properties"); + Properties props = new Properties(); + try { + props.load(new FileInputStream(configFile)); + } catch (Exception e) { } + + if (Boolean.valueOf(env.getProperty("geth.cors.enabled"))) { + registry.addMapping("/**") + .allowedOrigins(env.getProperty("geth.cors.url")); + } else if (Boolean.valueOf(props.getProperty("geth.cors.enabled"))) { registry.addMapping("/**") - .allowedOrigins(env.getProperty("geth.cors.url")) - .allowedMethods("POST"); + .allowedOrigins(props.getProperty("geth.cors.url")); } } @@ -84,6 +74,43 @@ public void configureDefaultServletHandling(DefaultServletHandlerConfigurer conf configurer.enable(); } + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("swagger-ui.html") + .addResourceLocations("classpath:/META-INF/resources/"); + + registry.addResourceHandler("/webjars/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/"); + } + + @Bean + public ServletContextTemplateResolver templateResolver() { + ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(); + templateResolver.setCacheable(false); + templateResolver.setTemplateMode("HTML5"); + templateResolver.setCharacterEncoding("UTF-8"); + templateResolver.setPrefix("/resources/"); + templateResolver.setSuffix(".html"); + + return templateResolver; + } + + @Bean + public SpringTemplateEngine templateEngine() { + SpringTemplateEngine templateEngine = new SpringTemplateEngine(); + templateEngine.setTemplateResolver(templateResolver()); + return templateEngine; + } + + @Bean + public ViewResolver getViewResolver() { + ThymeleafViewResolver resolver = new ThymeleafViewResolver(); + resolver.setOrder(1); + resolver.setViewNames(new String[]{"*.html"}); + resolver.setTemplateEngine(templateEngine()); + return resolver; + } + @PreDestroy public void shutdown() { okHttpClient.connectionPool().evictAll(); @@ -95,7 +122,7 @@ public void shutdown() { * * @return */ - @Bean(name="asyncTaskExecutor") + @Bean(name = "asyncTaskExecutor") public AsyncTaskExecutor createMvcAsyncExecutor() { ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor(); exec.setBeanName("asyncTaskExecutor"); @@ -106,7 +133,4 @@ public AsyncTaskExecutor createMvcAsyncExecutor() { exec.afterPropertiesSet(); return exec; } - - - } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/BlockController.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/BlockController.java index 9f1fa9ec..319f2b20 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/BlockController.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/BlockController.java @@ -1,42 +1,51 @@ package com.jpmorgan.cakeshop.controller; -import com.jpmorgan.cakeshop.config.JsonMethodArgumentResolver.JsonBodyParam; import com.jpmorgan.cakeshop.error.APIException; import com.jpmorgan.cakeshop.model.APIError; import com.jpmorgan.cakeshop.model.APIResponse; import com.jpmorgan.cakeshop.model.Block; +import com.jpmorgan.cakeshop.model.json.BlockPostJsonRequest; import com.jpmorgan.cakeshop.service.BlockService; +import com.jpmorgan.cakeshop.util.StringUtils; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping(value = "/api/block", - method = RequestMethod.POST, - consumes = MediaType.APPLICATION_JSON_VALUE, - produces = MediaType.APPLICATION_JSON_VALUE) + method = RequestMethod.POST, + consumes = MediaType.APPLICATION_JSON_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) public class BlockController extends BaseController { @Autowired BlockService blockService; + @ApiImplicitParams({ + @ApiImplicitParam(name = "id", required = false, value = "get block by id", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "hash", required = false, value = "get block by hash if id is missing", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "number", required = false, value = "get block by number if id or hash is missing", dataType = "java.lang.Long", paramType = "body") + , + @ApiImplicitParam(name = "tag", required = false, value = "get block by tag if id, hash and number are missing", dataType = "java.lang.String", paramType = "body") + }) @RequestMapping("/get") - public ResponseEntity getBlock( - @JsonBodyParam(required=false) String id, - @JsonBodyParam(required=false) String hash, - @JsonBodyParam(required=false) Long number, - @JsonBodyParam(required=false) String tag) throws APIException { - - if (id == null && hash != null) { - id = hash; // backwards compat + public ResponseEntity getBlock(@RequestBody BlockPostJsonRequest jsonRequest) throws APIException { + + if (StringUtils.isBlank(jsonRequest.getId()) && StringUtils.isNotBlank(jsonRequest.getHash())) { + jsonRequest.setId(jsonRequest.getHash()); // backwards compat } - Block block = blockService.get(id, number, tag); + Block block = blockService.get(jsonRequest.getId(), jsonRequest.getNumber(), jsonRequest.getTag()); APIResponse res = new APIResponse(); diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/ContractController.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/ContractController.java index 5f274ccf..91a6a7f5 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/ContractController.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/ContractController.java @@ -1,6 +1,5 @@ package com.jpmorgan.cakeshop.controller; -import com.jpmorgan.cakeshop.config.JsonMethodArgumentResolver.JsonBodyParam; import com.jpmorgan.cakeshop.error.APIException; import com.jpmorgan.cakeshop.model.APIData; import com.jpmorgan.cakeshop.model.APIError; @@ -13,9 +12,11 @@ import com.jpmorgan.cakeshop.model.Transaction; import com.jpmorgan.cakeshop.model.TransactionRequest; import com.jpmorgan.cakeshop.model.TransactionResult; -import com.jpmorgan.cakeshop.service.ContractRegistryService; +import com.jpmorgan.cakeshop.model.json.ContractPostJsonRequest; import com.jpmorgan.cakeshop.service.ContractService; import com.jpmorgan.cakeshop.service.ContractService.CodeType; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; import java.util.ArrayList; import java.util.List; @@ -26,6 +27,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @@ -33,24 +35,21 @@ @RestController @RequestMapping(value = "/api/contract", - method = RequestMethod.POST, - consumes = MediaType.APPLICATION_JSON_VALUE, - produces = MediaType.APPLICATION_JSON_VALUE) + method = RequestMethod.POST, + consumes = MediaType.APPLICATION_JSON_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) public class ContractController extends BaseController { - private static final String DEFAULT_CODE_TYPE = "solidity"; - @Autowired private ContractService contractService; - @Autowired - private ContractRegistryService contractRegistry; - + @ApiImplicitParams({ + @ApiImplicitParam(name = "address", required = true, value = "Contract address", dataType = "java.lang.String", paramType = "body") + }) @RequestMapping("/get") - public ResponseEntity getContract( - @JsonBodyParam String address) throws APIException { + public ResponseEntity getContract(@RequestBody ContractPostJsonRequest jsonRequest) throws APIException { - Contract contract = contractService.get(address); + Contract contract = contractService.get(jsonRequest.getAddress()); APIResponse res = new APIResponse(); @@ -66,14 +65,18 @@ public ResponseEntity getContract( return new ResponseEntity<>(res, HttpStatus.NOT_FOUND); } + @ApiImplicitParams({ + @ApiImplicitParam(name = "code", required = false, value = "Required. Contract code", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "code_type", required = false, value = "Required. Only solidity is supported for now.", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "optimize", required = false, value = "Optimize code.", dataType = "java.lang.Boollean", paramType = "body") + }) @RequestMapping("/compile") - public ResponseEntity compile( - @JsonBodyParam String code, - @JsonBodyParam(defaultValue=DEFAULT_CODE_TYPE) String code_type, - @JsonBodyParam(required=false) Boolean optimize) throws APIException { - - List contracts = contractService.compile(code, CodeType.valueOf(code_type), optimize); + public ResponseEntity compile(@RequestBody ContractPostJsonRequest jsonRequest) throws APIException { + List contracts = contractService.compile(jsonRequest.getCode(), + CodeType.valueOf(jsonRequest.getCode_type()), jsonRequest.getOptimize()); APIResponse res = new APIResponse(); if (contracts != null) { @@ -88,19 +91,27 @@ public ResponseEntity compile( return new ResponseEntity<>(res, HttpStatus.BAD_REQUEST); } + @ApiImplicitParams({ + @ApiImplicitParam(name = "code", required = false, value = "Required. Contract code", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "code_type", required = false, value = "Required. Only solidity is supported for now.", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "from", required = false, value = "Required. Account from address", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "args", required = false, value = "Required. Function arguments to pass", dataType = "java.util.Arrays", paramType = "body") + , + @ApiImplicitParam(name = "binary", required = false, value = "Bynary contract code.", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "privateFrom", required = false, value = "Private from Account", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "privateFor", required = false, value = "Private for Account", dataType = "java.lang.String", paramType = "body") + }) @RequestMapping("/create") - public ResponseEntity create( - @JsonBodyParam String from, - @JsonBodyParam String code, - @JsonBodyParam(defaultValue=DEFAULT_CODE_TYPE) String code_type, - @JsonBodyParam(required=false) Object[] args, - @JsonBodyParam(required=false) String binary, - @JsonBodyParam(required=false) Boolean optimize, - @JsonBodyParam final String privateFrom, - @JsonBodyParam final List privateFor) throws APIException { - - TransactionResult tx = contractService.create(from, code, CodeType.valueOf(code_type), args, binary, - privateFrom, privateFor); + public ResponseEntity create(@RequestBody ContractPostJsonRequest jsonRequest) throws APIException { + + TransactionResult tx = contractService.create(jsonRequest.getFrom(), jsonRequest.getCode(), + CodeType.valueOf(jsonRequest.getCode_type()), jsonRequest.getArgs(), jsonRequest.getBinary(), + jsonRequest.getPrivateFrom(), jsonRequest.getPrivateFor()); APIResponse res = new APIResponse(); @@ -125,15 +136,20 @@ public ResponseEntity list() throws APIException { return new ResponseEntity<>(res, HttpStatus.OK); } + @ApiImplicitParams({ + @ApiImplicitParam(name = "from", required = false, value = "Required. Account from address", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "address", required = false, value = "Required. Account address", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "method", required = false, value = "Required. Contract method to execute.", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "args", required = false, value = "Required. Args for contratc method", dataType = "java.util.Arrays", paramType = "body") + }) @RequestMapping("/read") - public ResponseEntity read( - @JsonBodyParam String from, - @JsonBodyParam String address, - @JsonBodyParam String method, - @JsonBodyParam Object[] args, - @JsonBodyParam(required=false) Object blockNumber) throws APIException { - - Object result = contractService.read(createTransactionRequest(from, address, method, args, true, blockNumber)); + public ResponseEntity read(@RequestBody ContractPostJsonRequest jsonRequest) throws APIException { + + Object result = contractService.read(createTransactionRequest(jsonRequest.getFrom(), jsonRequest.getAddress(), + jsonRequest.getMethod(), jsonRequest.getArgs(), true, jsonRequest.getBlockNumber())); APIResponse res = new APIResponse(); res.setData(result); @@ -157,8 +173,8 @@ private TransactionRequest createTransactionRequest(String from, String address, } /** - * Handle Base64 encoded byte/string inputs (byte arrays must be base64 encoded to put them on - * the wire w/ JSON) + * Handle Base64 encoded byte/string inputs (byte arrays must be base64 + * encoded to put them on the wire w/ JSON) * * @param method * @param args @@ -182,43 +198,50 @@ private Object[] decodeArgs(Function method, Object[] args) throws APIException return args; } + @ApiImplicitParams({ + @ApiImplicitParam(name = "from", required = false, value = "Required. Account from address", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "address", required = false, value = "Required. Account address", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "method", required = false, value = "Required. Contract method to execute.", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "args", required = false, value = "Required. Args for contract method", dataType = "java.util.Arrays", paramType = "body") + , + @ApiImplicitParam(name = "privateFrom", required = false, value = "Account private from", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "privateFor", required = false, value = "Account private for", dataType = "java.lang.String", paramType = "body") + }) @RequestMapping("/transact") - public WebAsyncTask> transact( - @JsonBodyParam final String from, - @JsonBodyParam final String address, - @JsonBodyParam final String method, - @JsonBodyParam final Object[] args, - @JsonBodyParam final String privateFrom, - @JsonBodyParam final List privateFor) throws APIException { - - Callable> callable = new Callable>() { - @Override - public ResponseEntity call() throws Exception { - TransactionRequest req = createTransactionRequest(from, address, method, args, false, null); - req.setPrivateFrom(privateFrom); - req.setPrivateFor(privateFor); - - TransactionResult tr = contractService.transact(req); - APIResponse res = new APIResponse(); - res.setData(tr.toAPIData()); - ResponseEntity response = new ResponseEntity<>(res, HttpStatus.OK); - return response; - } + public WebAsyncTask> transact(@RequestBody ContractPostJsonRequest jsonRequest) throws APIException { + + Callable> callable = () -> { + TransactionRequest req = createTransactionRequest(jsonRequest.getFrom(), jsonRequest.getAddress(), + jsonRequest.getMethod(), jsonRequest.getArgs(), false, null); + req.setPrivateFrom(jsonRequest.getPrivateFrom()); + req.setPrivateFor(jsonRequest.getPrivateFor()); + + TransactionResult tr = contractService.transact(req); + APIResponse res = new APIResponse(); + res.setData(tr.toAPIData()); + ResponseEntity response = new ResponseEntity<>(res, HttpStatus.OK); + return response; }; WebAsyncTask asyncTask = new WebAsyncTask(callable); return asyncTask; } + @ApiImplicitParams({ + @ApiImplicitParam(name = "address", required = false, value = "Required. Contract Address", dataType = "java.lang.String", paramType = "body") + }) @RequestMapping("/transactions/list") - public ResponseEntity listTransactions( - @JsonBodyParam String address) throws APIException { + public ResponseEntity listTransactions(@RequestBody final ContractPostJsonRequest jsonRequest) throws APIException { - List txns = contractService.listTransactions(address); + List txns = contractService.listTransactions(jsonRequest.getAddress()); List data = new ArrayList<>(); - for (Transaction tx : txns) { + txns.forEach((tx) -> { data.add(tx.toAPIData()); - } + }); APIResponse res = new APIResponse(); res.setData(data); @@ -226,16 +249,15 @@ public ResponseEntity listTransactions( return new ResponseEntity<>(res, HttpStatus.OK); } - private APIData toAPIData(Contract c) { return new APIData(c.getAddress(), Contract.API_DATA_TYPE, c); } private List toAPIData(List contracts) { List data = new ArrayList<>(); - for (Contract c : contracts) { - data.add(toAPIData(c)); - } + contracts.forEach((c) -> { + data.add(toAPIData(c)); + }); return data; } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/LogViewController.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/LogViewController.java new file mode 100644 index 00000000..4bf3de73 --- /dev/null +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/LogViewController.java @@ -0,0 +1,84 @@ +package com.jpmorgan.cakeshop.controller; + +import com.jpmorgan.cakeshop.bean.GethConfigBean; +import com.jpmorgan.cakeshop.error.APIException; +import com.jpmorgan.cakeshop.model.json.LogViewJsonRequest; +import com.jpmorgan.cakeshop.service.LogViewService; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import java.util.Deque; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@Controller +@RequestMapping(value = "/api/log", method = RequestMethod.POST, consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE) +public class LogViewController { + + private final Integer DEFAULT_NUMBER_LINES = 500; + + private final String LOG_PREFIX = System.getProperty("logging.path"); + + private final String CONSTELLATION_PATH = com.jpmorgan.cakeshop.util.StringUtils.isNotBlank(System.getProperty("spring.config.location")) + ? System.getProperty("spring.config.location").replaceAll("file:", "") + .replaceAll("application.properties", "").concat("constellation-node/") + : null; + + @Autowired + private GethConfigBean gethConfig; + + @Autowired + private LogViewService service; + + @ApiImplicitParams({ + @ApiImplicitParam(name = "logFileName", required = false, value = "Required. Name of the log to view", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "logType", required = false, value = "Required. What kind og log", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "numberLines", required = false, value = "Required. Number of last lines to view", dataType = "java.lang.Integer", paramType = "body") + }) + @RequestMapping("/view") + public ResponseEntity getLog(@RequestBody LogViewJsonRequest jsonRequest) throws APIException { + + String logPath = getLogPath(jsonRequest.getLogType(), jsonRequest.getLogFileName()); + + if (StringUtils.isNotBlank(logPath)) { + Deque log = service.getLog(logPath, jsonRequest.getNumberLines() != null ? jsonRequest.getNumberLines() : DEFAULT_NUMBER_LINES); + return new ResponseEntity(log, OK); + } else { + return new ResponseEntity(jsonRequest.getLogType(), BAD_REQUEST); + } + } + + private String getLogPath(String logType, String logFileName) { + String logPath; + if (StringUtils.isNotBlank(logType)) { + switch (logType) { + case "constellation": + logPath = StringUtils.isNotBlank(CONSTELLATION_PATH) + ? CONSTELLATION_PATH.concat("logs/") + .concat(StringUtils.isNotBlank(logFileName) ? logFileName : "constellation.log") + : gethConfig.getDataDirPath().concat("/constellation/logs/") + .concat(StringUtils.isNotBlank(logFileName) ? logFileName : "constellation.log"); + break; + case "geth": + logPath = LOG_PREFIX.concat("/").concat(StringUtils.isNotBlank(logFileName) ? logFileName : "geth.log"); + break; + default: + logPath = null; + break; + } + + } else { + return null; + } + return logPath; + } +} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/LoginController.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/LoginController.java new file mode 100644 index 00000000..6a8f2f02 --- /dev/null +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/LoginController.java @@ -0,0 +1,66 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jpmorgan.cakeshop.controller; + +import java.util.HashMap; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import static org.springframework.web.bind.annotation.RequestMethod.GET; + +@Controller +public class LoginController { + + @RequestMapping(value = "/login", method = GET) + public String loginView() { + return "login"; + } + + @RequestMapping(value = "/logout", method = GET) + public String logout(HttpServletRequest request, HttpServletResponse response) { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + + if (auth != null) { + new SecurityContextLogoutHandler().logout(request, response, auth); + } + + return "login"; + } + + @RequestMapping(value = "/user", method = GET) + public ResponseEntity userInfo(HttpServletRequest request, HttpServletResponse response) { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + + Boolean securityEnabled + = StringUtils.isNotBlank(System.getProperty("cakeshop.security.enabled")) + ? Boolean.valueOf(System.getProperty("cakeshop.security.enabled")) + : false; + + String userName = null; + if (null != auth && auth.getCredentials() instanceof String) { + userName = auth.getCredentials().toString(); + } + + Map userInfo = new HashMap<>(); + + if (StringUtils.isNotBlank(userName)) { + userInfo.put("username", userName); + } else { + userInfo.put("loggedout", Boolean.TRUE); + } + userInfo.put("securityEnabled", securityEnabled); + return new ResponseEntity(userInfo, HttpStatus.OK); + } + +} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/NodeController.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/NodeController.java index 1666845c..71ff37f7 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/NodeController.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/NodeController.java @@ -1,23 +1,36 @@ package com.jpmorgan.cakeshop.controller; -import com.jpmorgan.cakeshop.config.JsonMethodArgumentResolver.JsonBodyParam; +import com.jpmorgan.cakeshop.bean.GethConfigBean; import com.jpmorgan.cakeshop.error.APIException; import com.jpmorgan.cakeshop.model.APIData; import com.jpmorgan.cakeshop.model.APIError; import com.jpmorgan.cakeshop.model.APIResponse; +import com.jpmorgan.cakeshop.model.ContractABI; import com.jpmorgan.cakeshop.model.Node; +import com.jpmorgan.cakeshop.model.NodeSettings; import com.jpmorgan.cakeshop.model.Peer; +import com.jpmorgan.cakeshop.model.TransactionRequest; +import com.jpmorgan.cakeshop.model.json.NodePostJsonRequest; +import com.jpmorgan.cakeshop.service.ContractService; import com.jpmorgan.cakeshop.service.GethHttpService; import com.jpmorgan.cakeshop.service.NodeService; +import com.jpmorgan.cakeshop.util.FileUtils; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; @@ -28,19 +41,29 @@ * @author Samer Falah */ @RestController -@RequestMapping(value = "/api/node", - method = RequestMethod.POST, - consumes = MediaType.APPLICATION_JSON_VALUE, - produces = MediaType.APPLICATION_JSON_VALUE) +@RequestMapping(value = "/api/node", method = RequestMethod.POST, consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE) public class NodeController extends BaseController { + private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(NodeController.class); + @Autowired private GethHttpService gethService; @Autowired private NodeService nodeService; - @RequestMapping({ "/get" }) + @Autowired + private ContractService contractService; + @Autowired + private GethConfigBean gethConfig; + + private ContractABI voterAbi; + + public NodeController() throws IOException { + voterAbi = ContractABI.fromJson(FileUtils.readClasspathFile("contracts/BlockVoting.sol.json")); + } + + @RequestMapping({"/get"}) protected ResponseEntity doGet() throws APIException { Node node = nodeService.get(); @@ -51,40 +74,72 @@ protected ResponseEntity doGet() throws APIException { return new ResponseEntity<>(apiResponse, HttpStatus.OK); } + @ApiImplicitParams({ + @ApiImplicitParam(name = "extraParams", required = false, value = "Extra params to start geth", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "genesisBlock", required = false, value = "Genesis block", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "blockMakesAccount", required = false, value = "Block maker account (Quorum only)", dataType = "java.lang.Integer", paramType = "body") + , + @ApiImplicitParam(name = "voterAccount", required = false, value = "Voter account (Quorum only)", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "minBlockTime", required = false, value = "Minimial time to generete new Block (Quorum only)", dataType = "java.lang.Integer", paramType = "body") + , + @ApiImplicitParam(name = "maxBlockTime", required = false, value = "Maximum time to generete new Block (Quorum only)", dataType = "java.lang.Integer", paramType = "body") + , + @ApiImplicitParam(name = "logLevel", required = false, value = "Log verbosity level", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "networkId", required = false, value = "Network Id", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "committingTransactions", required = false, value = "Commit transactions true/false", dataType = "java.lang.Object", paramType = "body") + }) @RequestMapping("/update") - public ResponseEntity update( - @JsonBodyParam(required = false) String logLevel, - @JsonBodyParam(required = false) String networkId, - @JsonBodyParam(required = false) String identity, - @JsonBodyParam(required = false) Object committingTransactions, - @JsonBodyParam(required = false) String extraParams, - @JsonBodyParam(required = false) String genesisBlock) throws APIException { + public ResponseEntity update(@RequestBody NodePostJsonRequest jsonRequest) throws APIException { - Boolean isMining = null; + Boolean isMining; + NodeSettings nodeSettings = new NodeSettings().extraParams(jsonRequest.getExtraParams()).genesisBlock(jsonRequest.getGenesisBlock()) + .blockMakerAccount(jsonRequest.getBlockMakerAccount()).voterAccount(jsonRequest.getVoterAccount()).minBlockTime(jsonRequest.getMinBlockTime()) + .maxBlockTime(jsonRequest.getMaxBlockTime()); try { - Integer logLevelInt = null, - networkIDInt = null; - - if (!StringUtils.isEmpty(logLevel)) { - logLevelInt = Integer.parseInt(logLevel); + if (!StringUtils.isEmpty(jsonRequest.getLogLevel())) { + nodeSettings.logLevel(Integer.parseInt(jsonRequest.getLogLevel())); } - if (!StringUtils.isEmpty(networkId)) { - networkIDInt = Integer.parseInt(networkId); + if (!StringUtils.isEmpty(jsonRequest.getNetworkId())) { + nodeSettings.networkId(Integer.parseInt(jsonRequest.getNetworkId())); } - if (committingTransactions != null) { - if (committingTransactions instanceof String && StringUtils.isNotBlank((String) committingTransactions)) { - isMining = Boolean.parseBoolean((String) committingTransactions); + if (jsonRequest.getCommittingTransactions() != null) { + + if (jsonRequest.getCommittingTransactions() instanceof String + && StringUtils.isNotBlank((String) jsonRequest.getCommittingTransactions())) { + isMining = Boolean.parseBoolean((String) jsonRequest.getCommittingTransactions()); } else { - isMining = (Boolean) committingTransactions; + isMining = (Boolean) jsonRequest.getCommittingTransactions(); } + nodeSettings.setIsMining(isMining); + } + + if (StringUtils.isNotBlank(nodeSettings.getBlockMakerAccount()) + && (StringUtils.isNotBlank(gethConfig.getBlockMaker()) + && !nodeSettings.getBlockMakerAccount().contentEquals(gethConfig.getBlockMaker()))) { + updateVoteContract(gethConfig.getBlockMaker(), "addBlockMaker", + new Object[]{nodeSettings.getBlockMakerAccount()}); + updateVoteContract(nodeSettings.getBlockMakerAccount(), "removeBlockMaker", + new Object[]{gethConfig.getBlockMaker()}); } - nodeService.update(logLevelInt, networkIDInt, identity, isMining, - extraParams, genesisBlock); + if (StringUtils.isNotBlank(nodeSettings.getVoterAccount()) + && (StringUtils.isNotBlank(gethConfig.getVoteAccount()) + && !nodeSettings.getVoterAccount().contentEquals(gethConfig.getVoteAccount()))) { + updateVoteContract(gethConfig.getVoteAccount(), "addVoter", + new Object[]{nodeSettings.getVoterAccount()}); + updateVoteContract(nodeSettings.getVoterAccount(), "removeVoter", + new Object[]{gethConfig.getVoteAccount()}); + } + nodeService.update(nodeSettings); return doGet(); @@ -100,14 +155,16 @@ public ResponseEntity update( } } + @ApiImplicitParams({ + @ApiImplicitParam(name = "address", required = false, value = "Required. External node address to add", dataType = "java.lang.String", paramType = "body") + }) @RequestMapping("/peers/add") - public ResponseEntity addPeer(@JsonBodyParam String address) throws APIException { - if (StringUtils.isBlank(address)) { - return new ResponseEntity<>( - new APIResponse().error(new APIError().title("Missing param 'address'")), + public ResponseEntity addPeer(@RequestBody NodePostJsonRequest jsonRequest) throws APIException { + if (StringUtils.isBlank(jsonRequest.getAddress())) { + return new ResponseEntity<>(new APIResponse().error(new APIError().title("Missing param 'address'")), HttpStatus.BAD_REQUEST); } - boolean added = nodeService.addPeer(address); + boolean added = nodeService.addPeer(jsonRequest.getAddress()); return new ResponseEntity<>(APIResponse.newSimpleResponse(added), HttpStatus.OK); } @@ -126,20 +183,23 @@ public ResponseEntity peers() throws APIException { } @RequestMapping("/start") - protected @ResponseBody ResponseEntity startGeth() { + protected @ResponseBody + ResponseEntity startGeth() { Boolean started = gethService.start(); return new ResponseEntity<>(APIResponse.newSimpleResponse(started), HttpStatus.OK); } @RequestMapping("/stop") - protected @ResponseBody ResponseEntity stopGeth() { + protected @ResponseBody + ResponseEntity stopGeth() { Boolean stopped = gethService.stop(); gethService.deletePid(); return new ResponseEntity<>(APIResponse.newSimpleResponse(stopped), HttpStatus.OK); } @RequestMapping("/restart") - protected @ResponseBody ResponseEntity restartGeth() { + protected @ResponseBody + ResponseEntity restartGeth() { Boolean stopped = gethService.stop(); Boolean deleted = gethService.deletePid(); Boolean restarted = false; @@ -150,15 +210,83 @@ public ResponseEntity peers() throws APIException { } @RequestMapping("/reset") - protected @ResponseBody ResponseEntity resetGeth() { + protected @ResponseBody + ResponseEntity resetGeth() { Boolean reset = gethService.reset(); return new ResponseEntity<>(APIResponse.newSimpleResponse(reset), HttpStatus.OK); } @RequestMapping("/settings/reset") - protected @ResponseBody ResponseEntity resetNodeInfo() { + protected @ResponseBody + ResponseEntity resetNodeInfo() { Boolean reset = nodeService.reset(); return new ResponseEntity<>(APIResponse.newSimpleResponse(reset), HttpStatus.OK); } + @RequestMapping("/constellation/list") + protected @ResponseBody + ResponseEntity getConstellationList() throws APIException { + Map constellations = nodeService.getConstellationNodes(); + return new ResponseEntity<>(APIResponse.newSimpleResponse(constellations), HttpStatus.OK); + } + + @ApiImplicitParams({ + @ApiImplicitParam(name = "constellationNode", required = false, value = "Required. External constellation address(Quorum only)", dataType = "java.lang.String", paramType = "body") + }) + @RequestMapping("/constellation/add") + protected @ResponseBody + ResponseEntity addConstellation(@RequestBody NodePostJsonRequest jsonRequest) + throws APIException { + nodeService.addConstellationNode(jsonRequest.getConstellationNode()); + return doGet(); + } + + @ApiImplicitParams({ + @ApiImplicitParam(name = "constellationNode", required = false, value = "Required. External constellation address(Quorum only)", dataType = "java.lang.String", paramType = "body") + }) + @RequestMapping("/constellation/remove") + protected @ResponseBody + ResponseEntity removeConstellation(@RequestBody NodePostJsonRequest jsonRequest) + throws APIException { + nodeService.removeConstellationNode(jsonRequest.getConstellationNode()); + return doGet(); + } + + @RequestMapping("/constellation/stop") + protected @ResponseBody + ResponseEntity stopConstellation() throws APIException { + Boolean stopped = gethService.stopConstellation(); + gethConfig.setConstellationEnabled(false); + try { + gethConfig.save(); + } catch (IOException ex) { + throw new APIException(ex); + } + return new ResponseEntity<>(APIResponse.newSimpleResponse(stopped), HttpStatus.OK); + } + + @RequestMapping("/constellation/start") + protected @ResponseBody + ResponseEntity startConstellation() throws APIException { + Boolean started = gethService.startConstellation(); + gethConfig.setConstellationEnabled(true); + try { + gethConfig.save(); + } catch (IOException ex) { + throw new APIException(ex); + } + return new ResponseEntity<>(APIResponse.newSimpleResponse(started), HttpStatus.OK); + } + + private void updateVoteContract(String from, String method, Object[] args) throws APIException { + String address = gethConfig.getVoteContractAddress(); + TransactionRequest request = new TransactionRequest(from, address, voterAbi, method, args, false); + contractService.transact(request); + try { + TimeUnit.SECONDS.sleep(gethConfig.getMaxBlockTime()); + } catch (InterruptedException ex) { + LOG.error(from, ex); + } + } + } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/TransactionController.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/TransactionController.java index ee96d241..9d1cd062 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/TransactionController.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/TransactionController.java @@ -1,6 +1,5 @@ package com.jpmorgan.cakeshop.controller; -import com.jpmorgan.cakeshop.config.JsonMethodArgumentResolver.JsonBodyParam; import com.jpmorgan.cakeshop.error.APIException; import com.jpmorgan.cakeshop.model.APIData; import com.jpmorgan.cakeshop.model.APIError; @@ -8,7 +7,10 @@ import com.jpmorgan.cakeshop.model.DirectTransactionRequest; import com.jpmorgan.cakeshop.model.Transaction; import com.jpmorgan.cakeshop.model.TransactionResult; +import com.jpmorgan.cakeshop.model.json.TransPostJsonResquest; import com.jpmorgan.cakeshop.service.TransactionService; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; import java.util.ArrayList; import java.util.List; @@ -18,6 +20,8 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @@ -25,20 +29,27 @@ @RestController @RequestMapping(value = "/api/transaction", - method = RequestMethod.POST, - consumes = MediaType.APPLICATION_JSON_VALUE, - produces = MediaType.APPLICATION_JSON_VALUE) + method = RequestMethod.POST, + consumes = MediaType.APPLICATION_JSON_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) public class TransactionController extends BaseController { @Autowired private TransactionService transactionService; - + @ApiImplicitParams({ + @ApiImplicitParam(name = "id", required = false, value = "Required.Transaction Id", dataType = "java.lang.String", paramType = "body") + }) @RequestMapping("/get") public ResponseEntity getTransaction( - @JsonBodyParam(required=true) String id) throws APIException { + @RequestBody TransPostJsonResquest jsonRequest) throws APIException { + + if (StringUtils.isEmpty(jsonRequest.getId())) { + return new ResponseEntity<>(new APIResponse().error(new APIError().title("Missing param 'id'")), + HttpStatus.BAD_REQUEST); + } - Transaction tx = transactionService.get(id); + Transaction tx = transactionService.get(jsonRequest.getId()); APIResponse res = new APIResponse(); @@ -55,18 +66,25 @@ public ResponseEntity getTransaction( return new ResponseEntity<>(res, HttpStatus.NOT_FOUND); } + @ApiImplicitParams({ + @ApiImplicitParam(name = "ids", required = false, value = "Required. Hash or Transaction receipt", dataType = "java.util.List", paramType = "body") + }) @RequestMapping("/list") public ResponseEntity getTransactionList( - @JsonBodyParam(required=true) List ids) throws APIException { + @RequestBody TransPostJsonResquest jsonRequest) throws APIException { - List txns = transactionService.get(ids); - List data = new ArrayList<>(); + if (null == jsonRequest.getIds() || jsonRequest.getIds().isEmpty()) { + return new ResponseEntity<>(new APIResponse().error(new APIError().title("Missing param 'id'")), + HttpStatus.BAD_REQUEST); + } + List txns = transactionService.get(jsonRequest.getIds()); + List data = new ArrayList<>(); APIResponse res = new APIResponse(); if (txns != null && !txns.isEmpty()) { - for (Transaction txn : txns) { + txns.forEach((txn) -> { data.add(txn.toAPIData()); - } + }); res.setData(data); return new ResponseEntity<>(res, HttpStatus.OK); } @@ -79,31 +97,35 @@ public ResponseEntity getTransactionList( return new ResponseEntity<>(res, HttpStatus.NOT_FOUND); } + @ApiImplicitParams({ + @ApiImplicitParam(name = "from", required = false, value = "Required. Account from which transaction initiated", dataType = "java.ulang.String", paramType = "body") + , + @ApiImplicitParam(name = "to", required = false, value = "Required. Account to which transaction is going to", dataType = "java.ulang.String", paramType = "body") + , + @ApiImplicitParam(name = "data", required = false, value = "Required. Transaction data", dataType = "java.ulang.String", paramType = "body") + , + @ApiImplicitParam(name = "privateFrom", required = false, value = "Account transaction private from", dataType = "java.ulang.String", paramType = "body") + , + @ApiImplicitParam(name = "privateFor", required = false, value = "Account transsaction private for", dataType = "java.ulang.String", paramType = "body") + }) @RequestMapping("/save") public WebAsyncTask> transact( - @JsonBodyParam final String from, - @JsonBodyParam final String to, - @JsonBodyParam final String data, - @JsonBodyParam final String privateFrom, - @JsonBodyParam final List privateFor) throws APIException { - - Callable> callable = new Callable>() { - @Override - public ResponseEntity call() throws Exception { - DirectTransactionRequest req = new DirectTransactionRequest(from, to, data, false); - req.setPrivateFrom(privateFrom); - req.setPrivateFor(privateFor); - - TransactionResult result = transactionService.directTransact(req); - APIResponse res = new APIResponse(); - res.setData(result.toAPIData()); - ResponseEntity response = new ResponseEntity<>(res, HttpStatus.OK); - return response; - } + @RequestBody final TransPostJsonResquest jsonRequest) throws APIException { + + Callable> callable = () -> { + DirectTransactionRequest req = new DirectTransactionRequest(jsonRequest.getFrom(), + jsonRequest.getTo(), jsonRequest.getData(), false); + req.setPrivateFrom(jsonRequest.getPrivateFrom()); + req.setPrivateFor(jsonRequest.getPrivateFor()); + + TransactionResult result = transactionService.directTransact(req); + APIResponse res = new APIResponse(); + res.setData(result.toAPIData()); + ResponseEntity response = new ResponseEntity<>(res, HttpStatus.OK); + return response; }; WebAsyncTask asyncTask = new WebAsyncTask(callable); return asyncTask; } - } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/WalletController.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/WalletController.java index bf385bd2..889db6fd 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/WalletController.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/controller/WalletController.java @@ -5,24 +5,29 @@ import com.jpmorgan.cakeshop.model.APIError; import com.jpmorgan.cakeshop.model.APIResponse; import com.jpmorgan.cakeshop.model.Account; +import com.jpmorgan.cakeshop.model.json.WalletPostJsonRequest; import com.jpmorgan.cakeshop.service.WalletService; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; import java.util.ArrayList; import java.util.List; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping(value = "/api/wallet", - method = RequestMethod.POST, - consumes = MediaType.APPLICATION_JSON_VALUE, - produces = MediaType.APPLICATION_JSON_VALUE) + method = RequestMethod.POST, + consumes = MediaType.APPLICATION_JSON_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) public class WalletController extends BaseController { @Autowired @@ -36,9 +41,9 @@ public ResponseEntity getAccounts() throws APIException { List accounts = walletService.list(); if (accounts != null) { List data = new ArrayList<>(); - for (Account a : accounts) { - data.add(new APIData(a.getAddress(), "wallet", a)); - } + accounts.forEach((account) -> { + data.add(new APIData(account.getAddress(), "wallet", account)); + }); res.setData(data); return new ResponseEntity<>(res, HttpStatus.OK); } @@ -59,4 +64,70 @@ public ResponseEntity create() throws APIException { return new ResponseEntity<>(res, HttpStatus.OK); } + @ApiImplicitParams({ + @ApiImplicitParam(name = "account", required = false, value = "Required. Account to unlock", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "accountPassword", required = false, value = "Password used to create account. Required only if account was created with one.", dataType = "java.lang.String", paramType = "body") + }) + @RequestMapping("/unlock") + public ResponseEntity unlock(@RequestBody WalletPostJsonRequest request) throws APIException { + APIResponse res; + if (StringUtils.isBlank(request.getAccount())) { + res = new APIResponse(); + APIError err = new APIError(); + err.setStatus("400"); + err.setTitle("Bad request"); + res.addError(err); + return new ResponseEntity<>(res, HttpStatus.BAD_REQUEST); + } else { + res = APIResponse.newSimpleResponse(walletService.unlockAccount(request)); + return new ResponseEntity<>(res, HttpStatus.OK); + } + } + + @ApiImplicitParams({ + @ApiImplicitParam(name = "account", required = false, value = "Required. Account to lock", dataType = "java.lang.String", paramType = "body") + }) + @RequestMapping("/lock") + public ResponseEntity lock(@RequestBody WalletPostJsonRequest request) throws APIException { + + APIResponse res; + if (StringUtils.isBlank(request.getAccount())) { + res = new APIResponse(); + APIError err = new APIError(); + err.setStatus("400"); + err.setTitle("Bad request"); + res.addError(err); + return new ResponseEntity<>(res, HttpStatus.BAD_REQUEST); + } else { + res = APIResponse.newSimpleResponse(walletService.lockAccount(request)); + return new ResponseEntity<>(res, HttpStatus.OK); + } + } + + @ApiImplicitParams({ + @ApiImplicitParam(name = "fromAccount", required = false, value = "Required. Account to fund from", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "account", required = false, value = "Required. Account to fund", dataType = "java.lang.String", paramType = "body") + , + @ApiImplicitParam(name = "newBalance", required = false, value = "Required. Fund amount", dataType = "java.lang.Long", paramType = "body") + }) + @RequestMapping("/fund") + public ResponseEntity fundAccount(@RequestBody WalletPostJsonRequest request) throws APIException { + + APIResponse res; + if (StringUtils.isBlank(request.getFromAccount()) || StringUtils.isBlank(request.getAccount()) + || null == request.getNewBalance()) { + res = new APIResponse(); + APIError err = new APIError(); + err.setStatus("400"); + err.setTitle("Bad request"); + res.addError(err); + return new ResponseEntity<>(res, HttpStatus.BAD_REQUEST); + } else { + res = APIResponse.newSimpleResponse(walletService.fundAccount(request)); + return new ResponseEntity<>(res, HttpStatus.OK); + } + } + } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/dao/UserDAO.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/dao/UserDAO.java new file mode 100644 index 00000000..c990341c --- /dev/null +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/dao/UserDAO.java @@ -0,0 +1,74 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jpmorgan.cakeshop.dao; + +import com.jpmorgan.cakeshop.model.User; +import org.hibernate.Session; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +@Repository +public class UserDAO extends BaseDAO { + + private final PasswordEncoder ENCODER = new BCryptPasswordEncoder(); + + @Transactional + public User getUser(String userName) { + if (null != getCurrentSession()) { + return getCurrentSession().get(User.class, userName); + } + return null; + } + + @Transactional + public Boolean save(User user) { + if (null != getCurrentSession()) { + if (null != getCurrentSession().get(User.class, user.getUserName())) { + return false; + } else { + user.setPassword(ENCODER.encode(user.getPassword())); + getCurrentSession().save(user); + return true; + } + } + return false; + } + + @Transactional + public Boolean authenticate(String userName, String password) { + if (null != getCurrentSession()) { + User user = getCurrentSession().get(User.class, userName); + if (ENCODER.matches(password, user.getPassword())) { + return true; + } + } + return false; + } + + @Transactional + public void resetPassword(String userName, String newPassword) { + if (null != getCurrentSession()) { + User user = getCurrentSession().get(User.class, userName); + if (null != user) { + user.setPassword(ENCODER.encode(newPassword)); + getCurrentSession().update(user); + } + } + } + + @Override + public void reset() { + if (null != getCurrentSession()) { + Session session = getCurrentSession(); + session.createSQLQuery("TRUNCATE TABLE USERS").executeUpdate(); + session.flush(); + session.clear(); + } + } + +} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/Account.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/Account.java index ef38fa46..f05ea13b 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/Account.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/Account.java @@ -5,12 +5,13 @@ import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; +import javax.persistence.Transient; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; @Entity -@Table(name="ACCOUNTS") +@Table(name = "ACCOUNTS") public class Account implements Serializable { /** @@ -22,9 +23,12 @@ public class Account implements Serializable { * Ethereum account address */ @Id - String address; + private String address; - String balance; + private String balance; + + @Transient + private Boolean unlocked; public String getAddress() { return address; @@ -46,4 +50,12 @@ public void setBalance(String balance) { public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); } + + public Boolean isUnlocked() { + return unlocked; + } + + public void setUnlocked(Boolean unlocked) { + this.unlocked = unlocked; + } } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/NodeSettings.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/NodeSettings.java new file mode 100644 index 00000000..feba7ab3 --- /dev/null +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/NodeSettings.java @@ -0,0 +1,208 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jpmorgan.cakeshop.model; + +public class NodeSettings { + + private String identity, extraParams, genesisBlock, blockMakerAccount, voterAccount; + private Integer minBlockTime, maxBlockTime, logLevel, networkId; + private Boolean isMining; + + public NodeSettings() { + + } + + /** + * @return the logLevel + */ + public Integer getLogLevel() { + return logLevel; + } + + /** + * @param logLevel the logLevel to set + */ + public void setLogLevel(Integer logLevel) { + this.logLevel = logLevel; + } + + public NodeSettings logLevel(Integer logLevel) { + this.logLevel = logLevel; + return this; + } + + /** + * @return the networkId + */ + public Integer getNetworkId() { + return networkId; + } + + /** + * @param networkId the networkId to set + */ + public void setNetworkId(Integer networkId) { + this.networkId = networkId; + } + + public NodeSettings networkId(Integer networkId) { + this.networkId = networkId; + return this; + } + + /** + * @return the identity + */ + public String getIdentity() { + return identity; + } + + /** + * @param identity the identity to set + */ + public void setIdentity(String identity) { + this.identity = identity; + } + + public NodeSettings identity(String identity) { + this.identity = identity; + return this; + } + + /** + * @return the extraParams + */ + public String getExtraParams() { + return extraParams; + } + + /** + * @param extraParams the extraParams to set + */ + public void setExtraParams(String extraParams) { + this.extraParams = extraParams; + } + + public NodeSettings extraParams(String extraParams) { + this.extraParams = extraParams; + return this; + } + + /** + * @return the genesisBlock + */ + public String getGenesisBlock() { + return genesisBlock; + } + + /** + * @param genesisBlock the genesisBlock to set + */ + public void setGenesisBlock(String genesisBlock) { + this.genesisBlock = genesisBlock; + } + + public NodeSettings genesisBlock(String genesisBlock) { + this.genesisBlock = genesisBlock; + return this; + } + + /** + * @return the blockMakerAccount + */ + public String getBlockMakerAccount() { + return blockMakerAccount; + } + + /** + * @param blockMakerAccount the blockMakerAccount to set + */ + public void setBlockMakerAccount(String blockMakerAccount) { + this.blockMakerAccount = blockMakerAccount; + } + + public NodeSettings blockMakerAccount(String blockMakerAccount) { + this.blockMakerAccount = blockMakerAccount; + return this; + } + + /** + * @return the voterAccount + */ + public String getVoterAccount() { + return voterAccount; + } + + /** + * @param voterAccount the voterAccount to set + */ + public void setVoterAccount(String voterAccount) { + this.voterAccount = voterAccount; + } + + public NodeSettings voterAccount(String voterAccount) { + this.voterAccount = voterAccount; + return this; + } + + /** + * @return the minBlockTime + */ + public Integer getMinBlockTime() { + return minBlockTime; + } + + /** + * @param minBlockTime the minBlockTime to set + */ + public void setMinBlockTime(Integer minBlockTime) { + this.minBlockTime = minBlockTime; + } + + public NodeSettings minBlockTime(Integer minBlockTime) { + this.minBlockTime = minBlockTime; + return this; + } + + /** + * @return the maxBlockTime + */ + public Integer getMaxBlockTime() { + return maxBlockTime; + } + + /** + * @param maxBlockTime the maxBlockTime to set + */ + public void setMaxBlockTime(Integer maxBlockTime) { + this.maxBlockTime = maxBlockTime; + } + + public NodeSettings maxBlockTime(Integer maxBlockTime) { + this.maxBlockTime = maxBlockTime; + return this; + } + + /** + * @return the isMining + */ + public Boolean isMining() { + return isMining; + } + + /** + * @param isMining the isMining to set + */ + public void setIsMining(Boolean isMining) { + this.isMining = isMining; + } + + public NodeSettings isMining(Boolean isMining) { + this.isMining = isMining; + return this; + } + +} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/QuorumInfo.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/QuorumInfo.java index 725a54bb..2e5990ae 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/QuorumInfo.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/QuorumInfo.java @@ -15,24 +15,31 @@ public BlockMakerStrategy() { public int getMinBlockTime() { return minBlockTime; } + public void setMinBlockTime(int minBlockTime) { this.minBlockTime = minBlockTime; } + public int getMaxBlockTime() { return maxBlockTime; } + public void setMaxBlockTime(int maxBlockTime) { this.maxBlockTime = maxBlockTime; } + public String getStatus() { return status; } + public void setStatus(String status) { this.status = status; } + public String getType() { return type; } + public void setType(String type) { this.type = type; } @@ -47,8 +54,12 @@ public void setType(String type) { private String voteAccount; + private String nodeKey; + private Boolean canVote; + private Boolean isConstellationEnabled; + private BlockMakerStrategy blockMakerStrategy; public QuorumInfo() { @@ -102,4 +113,20 @@ public void setQuorum(Boolean isQuorum) { this.isQuorum = isQuorum; } + public String getNodeKey() { + return nodeKey; + } + + public void setNodeKey(String nodeKey) { + this.nodeKey = nodeKey; + } + + public Boolean getIsConstellationEnabled() { + return isConstellationEnabled; + } + + public void setIsConstellationEnabled(Boolean isConstellationEnabled) { + this.isConstellationEnabled = isConstellationEnabled; + } + } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/TransactionRequest.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/TransactionRequest.java index 633c477b..b244c4fd 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/TransactionRequest.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/TransactionRequest.java @@ -50,36 +50,36 @@ public TransactionRequest(String fromAddress, String contractAddress, ContractAB this.privateFrom = null; this.privateFor = null; - this.function = abi.getFunction(method); - if (this.function == null) { - throw new APIException("Invalid method '" + method + "'"); - } + this.function = abi.getFunction(method); + if (this.function == null) { + throw new APIException("Invalid method '" + method + "'"); + } } public Object[] toGethArgs() { Map req = new HashMap<>(); - req.put("from", fromAddress); - req.put("to", contractAddress); - req.put("gas", DEFAULT_GAS); - req.put("data", function.encodeAsHex(args)); + req.put("from", fromAddress); + req.put("to", contractAddress); + req.put("gas", DEFAULT_GAS); + req.put("data", function.encodeAsHex(args)); - if (StringUtils.isNotBlank(privateFrom)) { - req.put("privateFrom", privateFrom); - } + if (StringUtils.isNotBlank(privateFrom)) { + req.put("privateFrom", privateFrom); + } - if (privateFor != null && !privateFor.isEmpty()) { + if (privateFor != null && !privateFor.isEmpty()) { req.put("privateFor", privateFor); - } + } if (isRead) { if (blockNumber == null) { - return new Object[] { req, BLOCK_LATEST }; + return new Object[]{req, BLOCK_LATEST}; } else { - return new Object[] { req, blockNumber }; + return new Object[]{req, blockNumber}; } } else { - return new Object[] { req }; + return new Object[]{req}; } } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/User.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/User.java new file mode 100644 index 00000000..9224ce4d --- /dev/null +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/User.java @@ -0,0 +1,81 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jpmorgan.cakeshop.model; + +import java.io.Serializable; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Index; +import javax.persistence.Table; + +@Entity +@Table(name = "USERS", indexes = { + @Index(name = "user_name_idx", columnList = "userName")}) +public class User implements Serializable { + + @Id + private String userName; + private String password; + private String firstName; + private String lastName; + + /** + * @return the userName + */ + public String getUserName() { + return userName; + } + + /** + * @param userName the userName to set + */ + public void setUserName(String userName) { + this.userName = userName; + } + + /** + * @return the password + */ + public String getPassword() { + return password; + } + + /** + * @param password the password to set + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * @return the firstName + */ + public String getFirstName() { + return firstName; + } + + /** + * @param firstName the firstName to set + */ + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + /** + * @return the lastName + */ + public String getLastName() { + return lastName; + } + + /** + * @param lastName the lastName to set + */ + public void setLastName(String lastName) { + this.lastName = lastName; + } + +} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/BlockPostJsonRequest.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/BlockPostJsonRequest.java new file mode 100644 index 00000000..6207f03e --- /dev/null +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/BlockPostJsonRequest.java @@ -0,0 +1,68 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jpmorgan.cakeshop.model.json; + +public class BlockPostJsonRequest { + + private String id, hash, tag; + private Long number; + + /** + * @return the id + */ + public String getId() { + return id; + } + + /** + * @param id the id to set + */ + public void setId(String id) { + this.id = id; + } + + /** + * @return the hash + */ + public String getHash() { + return hash; + } + + /** + * @param hash the hash to set + */ + public void setHash(String hash) { + this.hash = hash; + } + + /** + * @return the tag + */ + public String getTag() { + return tag; + } + + /** + * @param tag the tag to set + */ + public void setTag(String tag) { + this.tag = tag; + } + + /** + * @return the number + */ + public Long getNumber() { + return number; + } + + /** + * @param number the number to set + */ + public void setNumber(Long number) { + this.number = number; + } +} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/ContractDeserializer.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/ContractDeserializer.java new file mode 100644 index 00000000..70eb4d9a --- /dev/null +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/ContractDeserializer.java @@ -0,0 +1,115 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jpmorgan.cakeshop.model.json; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.Lists; +import com.jpmorgan.cakeshop.util.StringUtils; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class ContractDeserializer extends JsonDeserializer { + + @Override + public ContractPostJsonRequest deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException { + ContractPostJsonRequest request = new ContractPostJsonRequest(); + JsonNode node = jp.getCodec().readTree(jp); + + if (null != node.get("privateFor")) { + JsonNode privateForNode = node.get("privateFor"); + List privateFor = null; + + if (privateForNode.isArray()) { + privateFor = Lists.newArrayList(); + for (Iterator iter = privateForNode.elements(); iter.hasNext();) { + privateFor.add(iter.next().asText()); + } + } else { + if (StringUtils.isNotBlank(node.get("privateFor").textValue())) { + privateFor = Lists.newArrayList(node.get("privateFor").textValue()); + } + } + + request.setPrivateFor(privateFor); + } + + if (null != node.get("code")) { + request.setCode(node.get("code").textValue()); + } + + if (null != node.get("code_type")) { + request.setCode_type(node.get("code_type").textValue()); + } + + if (null != node.get("from")) { + request.setFrom(node.get("from").textValue()); + } + + if (null != node.get("binary")) { + request.setBinary(node.get("binary").textValue()); + } + + if (null != node.get("privateFrom")) { + request.setPrivateFrom(node.get("privateFrom").textValue()); + } + + if (null != node.get("address")) { + request.setAddress(node.get("address").textValue()); + } + + if (null != node.get("method")) { + request.setMethod(node.get("method").textValue()); + } + + if (null != node.get("blockNumber")) { + request.setBlockNumber(node.get("blockNumber").textValue()); + } + + if (null != node.get("optimize")) { + request.setOptimize(node.get("optimize").booleanValue()); + } + + if (null != node.get("args")) { + JsonNode argsNode = node.get("args"); + List args = null; + + if (argsNode.isArray()) { + args = Lists.newArrayList(); + + for (Iterator iter = argsNode.elements(); iter.hasNext();) { + JsonNode arrEntry = iter.next(); + + if (arrEntry.isArray()) { + List argArr = new ArrayList<>(); + + for (Iterator iter2 = arrEntry.elements(); iter2.hasNext();) { + JsonNode arrEntry2 = iter2.next(); + argArr.add(arrEntry2.asText()); + } + + args.add(argArr); + } else { + args.add(arrEntry.asText()); + } + } + } else { + if (StringUtils.isNotBlank(node.get("args").textValue())) { + args = Lists.newArrayList(node.get("args").textValue()); + } + } + + request.setArgs(args.toArray()); + } + + return request; + } +} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/ContractPostJsonRequest.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/ContractPostJsonRequest.java new file mode 100644 index 00000000..835d2bed --- /dev/null +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/ContractPostJsonRequest.java @@ -0,0 +1,176 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jpmorgan.cakeshop.model.json; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.List; + +@JsonDeserialize(using = ContractDeserializer.class) +public class ContractPostJsonRequest { + + public static final String DEFAULT_CODE_TYPE = "solidity"; + + private String from, code, code_type = DEFAULT_CODE_TYPE, binary, privateFrom, address, method; + private Object args[]; + private Object blockNumber; + private Boolean optimize; + private List privateFor; + + /** + * @return the from + */ + public String getFrom() { + return from; + } + + /** + * @param from the from to set + */ + public void setFrom(String from) { + this.from = from; + } + + /** + * @return the code + */ + public String getCode() { + return code; + } + + /** + * @param code the code to set + */ + public void setCode(String code) { + this.code = code; + } + + /** + * @return the code_type + */ + public String getCode_type() { + return code_type; + } + + /** + * @param code_type the code_type to set + */ + public void setCode_type(String code_type) { + this.code_type = code_type; + } + + /** + * @return the binary + */ + public String getBinary() { + return binary; + } + + /** + * @param binary the binary to set + */ + public void setBinary(String binary) { + this.binary = binary; + } + + /** + * @return the privateFrom + */ + public String getPrivateFrom() { + return privateFrom; + } + + /** + * @param privateFrom the privateFrom to set + */ + public void setPrivateFrom(String privateFrom) { + this.privateFrom = privateFrom; + } + + /** + * @return the address + */ + public String getAddress() { + return address; + } + + /** + * @param address the address to set + */ + public void setAddress(String address) { + this.address = address; + } + + /** + * @return the method + */ + public String getMethod() { + return method; + } + + /** + * @param method the method to set + */ + public void setMethod(String method) { + this.method = method; + } + + /** + * @return the args + */ + public Object[] getArgs() { + return args; + } + + /** + * @param args the args to set + */ + public void setArgs(Object[] args) { + this.args = args; + } + + /** + * @return the blockNumber + */ + public Object getBlockNumber() { + return blockNumber; + } + + /** + * @param blockNumber the blockNumber to set + */ + public void setBlockNumber(Object blockNumber) { + this.blockNumber = blockNumber; + } + + /** + * @return the optimize + */ + public Boolean getOptimize() { + return optimize; + } + + /** + * @param optimize the optimize to set + */ + public void setOptimize(Boolean optimize) { + this.optimize = optimize; + } + + /** + * @return the privateFor + */ + public List getPrivateFor() { + return privateFor; + } + + /** + * @param privateFor the privateFor to set + */ + public void setPrivateFor(List privateFor) { + this.privateFor = privateFor; + } + +} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/LogViewJsonRequest.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/LogViewJsonRequest.java new file mode 100644 index 00000000..20478731 --- /dev/null +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/LogViewJsonRequest.java @@ -0,0 +1,55 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jpmorgan.cakeshop.model.json; + +public class LogViewJsonRequest { + + private String logType, logFileName; + private Integer numberLines; + + /** + * @return the logType + */ + public String getLogType() { + return logType; + } + + /** + * @param logType the logType to set + */ + public void setLogType(String logType) { + this.logType = logType; + } + + /** + * @return the logFileName + */ + public String getLogFileName() { + return logFileName; + } + + /** + * @param logFileName the logFileName to set + */ + public void setLogFileName(String logFileName) { + this.logFileName = logFileName; + } + + /** + * @return the numberLines + */ + public Integer getNumberLines() { + return numberLines; + } + + /** + * @param numberLines the numberLines to set + */ + public void setNumberLines(Integer numberLines) { + this.numberLines = numberLines; + } + +} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/NodePostJsonRequest.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/NodePostJsonRequest.java new file mode 100644 index 00000000..7ef849d0 --- /dev/null +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/NodePostJsonRequest.java @@ -0,0 +1,184 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jpmorgan.cakeshop.model.json; + +public class NodePostJsonRequest { + + private String address; + private String logLevel, networkId, identity, genesisBlock, blockMakerAccount, voterAccount, extraParams, + constellationNode; + private Object committingTransactions; + private Integer minBlockTime, maxBlockTime; + + /** + * @return the address + */ + public String getAddress() { + return address; + } + + /** + * @param address the address to set + */ + public void setAddress(String address) { + this.address = address; + } + + /** + * @return the logLevel + */ + public String getLogLevel() { + return logLevel; + } + + /** + * @param logLevel the logLevel to set + */ + public void setLogLevel(String logLevel) { + this.logLevel = logLevel; + } + + /** + * @return the networkId + */ + public String getNetworkId() { + return networkId; + } + + /** + * @param networkId the networkId to set + */ + public void setNetworkId(String networkId) { + this.networkId = networkId; + } + + /** + * @return the identity + */ + public String getIdentity() { + return identity; + } + + /** + * @param identity the identity to set + */ + public void setIdentity(String identity) { + this.identity = identity; + } + + /** + * @return the genesisBlock + */ + public String getGenesisBlock() { + return genesisBlock; + } + + /** + * @param genesisBlock the genesisBlock to set + */ + public void setGenesisBlock(String genesisBlock) { + this.genesisBlock = genesisBlock; + } + + /** + * @return the blockMakerAccount + */ + public String getBlockMakerAccount() { + return blockMakerAccount; + } + + /** + * @param blockMakerAccount the blockMakerAccount to set + */ + public void setBlockMakerAccount(String blockMakerAccount) { + this.blockMakerAccount = blockMakerAccount; + } + + /** + * @return the voterAccount + */ + public String getVoterAccount() { + return voterAccount; + } + + /** + * @param voterAccount the voterAccount to set + */ + public void setVoterAccount(String voterAccount) { + this.voterAccount = voterAccount; + } + + /** + * @return the committingTransactions + */ + public Object getCommittingTransactions() { + return committingTransactions; + } + + /** + * @param committingTransactions the committingTransactions to set + */ + public void setCommittingTransactions(Object committingTransactions) { + this.committingTransactions = committingTransactions; + } + + /** + * @return the minBlockTime + */ + public Integer getMinBlockTime() { + return minBlockTime; + } + + /** + * @param minBlockTime the minBlockTime to set + */ + public void setMinBlockTime(Integer minBlockTime) { + this.minBlockTime = minBlockTime; + } + + /** + * @return the maxBlockTime + */ + public Integer getMaxBlockTime() { + return maxBlockTime; + } + + /** + * @param maxBlockTime the maxBlockTime to set + */ + public void setMaxBlockTime(Integer maxBlockTime) { + this.maxBlockTime = maxBlockTime; + } + + /** + * @return the extraParams + */ + public String getExtraParams() { + return extraParams; + } + + /** + * @param extraParams the extraParams to set + */ + public void setExtraParams(String extraParams) { + this.extraParams = extraParams; + } + + /** + * @return the constellationNode + */ + public String getConstellationNode() { + return constellationNode; + } + + /** + * @param constellationNode the constellationNode to set + */ + public void setConstellationNode(String constellationNode) { + this.constellationNode = constellationNode; + } + +} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/TransPostJsonResquest.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/TransPostJsonResquest.java new file mode 100644 index 00000000..1f98b005 --- /dev/null +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/TransPostJsonResquest.java @@ -0,0 +1,114 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jpmorgan.cakeshop.model.json; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.List; + +@JsonDeserialize(using = TransactionDeserializer.class) +public class TransPostJsonResquest { + + private String id; + private String from; + private String to; + private String data; + private String privateFrom; + private List privateFor; + private List ids; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + /** + * @return the from + */ + public String getFrom() { + return from; + } + + /** + * @param from the from to set + */ + public void setFrom(String from) { + this.from = from; + } + + /** + * @return the to + */ + public String getTo() { + return to; + } + + /** + * @param to the to to set + */ + public void setTo(String to) { + this.to = to; + } + + /** + * @return the data + */ + public String getData() { + return data; + } + + /** + * @param data the data to set + */ + public void setData(String data) { + this.data = data; + } + + /** + * @return the privateFrom + */ + public String getPrivateFrom() { + return privateFrom; + } + + /** + * @param privateFrom the privateFrom to set + */ + public void setPrivateFrom(String privateFrom) { + this.privateFrom = privateFrom; + } + + /** + * @return the privateFor + */ + public List getPrivateFor() { + return privateFor; + } + + /** + * @param privateFor the privateFor to set + */ + public void setPrivateFor(List privateFor) { + this.privateFor = privateFor; + } + + /** + * @return the ids + */ + public List getIds() { + return ids; + } + + /** + * @param ids the ids to set + */ + public void setIds(List ids) { + this.ids = ids; + } + +} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/TransactionDeserializer.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/TransactionDeserializer.java new file mode 100644 index 00000000..3dd1a4dd --- /dev/null +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/TransactionDeserializer.java @@ -0,0 +1,82 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jpmorgan.cakeshop.model.json; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.Lists; +import java.io.IOException; +import java.util.Iterator; +import java.util.List; +import org.apache.commons.lang3.StringUtils; + +public class TransactionDeserializer extends JsonDeserializer { + + @Override + public TransPostJsonResquest deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException { + + TransPostJsonResquest request = new TransPostJsonResquest(); + JsonNode node = jp.getCodec().readTree(jp); + + if (null != node.get("privateFor")) { + JsonNode privateForNode = node.get("privateFor"); + List privateFor = null; + if (privateForNode.isArray()) { + privateFor = Lists.newArrayList(); + for (Iterator iter = privateForNode.elements(); iter.hasNext();) { + privateFor.add(iter.next().asText()); + } + } else { + if (StringUtils.isNotBlank(node.get("privateFor").textValue())) { + privateFor = Lists.newArrayList(node.get("privateFor").textValue()); + } + } + request.setPrivateFor(privateFor); + } + + if (null != node.get("ids")) { + JsonNode idsNode = node.get("ids"); + List ids = null; + if (idsNode.isArray()) { + ids = Lists.newArrayList(); + for (Iterator iter = idsNode.elements(); iter.hasNext();) { + ids.add(iter.next().asText()); + } + } else { + if (StringUtils.isNotBlank(node.get("ids").textValue())) { + ids = Lists.newArrayList(node.get("ids").textValue()); + } + } + request.setIds(ids); + } + + if (null != node.get("id")) { + request.setId(node.get("id").textValue()); + } + + if (null != node.get("from")) { + request.setFrom(node.get("from").textValue()); + } + + if (null != node.get("to")) { + request.setTo(node.get("to").textValue()); + } + + if (null != node.get("data")) { + request.setData(node.get("data").textValue()); + } + + if (null != node.get("privateFrom")) { + request.setPrivateFrom(node.get("privateFrom").textValue()); + } + + return request; + } + +} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/WalletPostJsonRequest.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/WalletPostJsonRequest.java new file mode 100644 index 00000000..a71dd5a5 --- /dev/null +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/model/json/WalletPostJsonRequest.java @@ -0,0 +1,64 @@ +package com.jpmorgan.cakeshop.model.json; + +public class WalletPostJsonRequest { + + private String fromAccount, account, accountPassword; + private Long newBalance; + + /** + * @return the formAccount + */ + public String getFromAccount() { + return fromAccount; + } + + /** + * @param formAccount the formAccount to set + */ + public void setFromAccount(String fromAccount) { + this.fromAccount = fromAccount; + } + + /** + * @return the account + */ + public String getAccount() { + return account; + } + + /** + * @param account the account to set + */ + public void setAccount(String account) { + this.account = account; + } + + /** + * @return the accountPassword + */ + public String getAccountPassword() { + return accountPassword; + } + + /** + * @param accountPassword the accountPassword to set + */ + public void setAccountPassword(String accountPassword) { + this.accountPassword = accountPassword; + } + + /** + * @return the newBalance + */ + public Long getNewBalance() { + return newBalance; + } + + /** + * @param newBalance the newBalance to set + */ + public void setNewBalance(Long newBalance) { + this.newBalance = newBalance; + } + +} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/GethHttpService.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/GethHttpService.java index cf988966..626021b9 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/GethHttpService.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/GethHttpService.java @@ -14,15 +14,13 @@ public interface GethHttpService { public static final String GETH_API_VERSION = "2.0"; - public static final Long GETH_REQUEST_ID = 42L; // We don't actually use this, so just use a constant - - + public static final Long GETH_REQUEST_ID = 42L; // We don't actually use this, so just use a constant /** * Call the given Geth RPC method * - * @param funcName RPC function name - * @param args Optional args + * @param funcName RPC function name + * @param args Optional args * * @return * @throws APIException @@ -48,6 +46,18 @@ public interface GethHttpService { */ public Boolean start(String... additionalParams); + /* + * Start constellation node + * @return + */ + public Boolean startConstellation(); + + /* + * Stop constellation node + * @return + */ + public Boolean stopConstellation(); + public void runPostStartupTasks(); /** @@ -69,7 +79,7 @@ public interface GethHttpService { * * @return */ - public Boolean reset(); + public Boolean reset(String... additionalParams); /** * Delete the PID file @@ -85,4 +95,6 @@ public interface GethHttpService { */ List getStartupErrors(); + public List setAdditionalParams(String[] additionalParamsArray); + } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/LogViewService.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/LogViewService.java new file mode 100644 index 00000000..b8f0a89d --- /dev/null +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/LogViewService.java @@ -0,0 +1,19 @@ +package com.jpmorgan.cakeshop.service; + +import com.jpmorgan.cakeshop.error.APIException; +import java.util.Deque; + +public interface LogViewService { + + /** + * + * @param logPath + * @param numberOfLines + * @return + * @throws com.jpmorgan.cakeshop.error.APIException + */ + public Deque getLog(String logPath, Integer numberOfLines) throws APIException; + + public String getLog(String logPath) throws APIException; + +} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/NodeService.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/NodeService.java index cc2b00b9..e16eec41 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/NodeService.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/NodeService.java @@ -3,9 +3,11 @@ import com.jpmorgan.cakeshop.error.APIException; import com.jpmorgan.cakeshop.model.Node; import com.jpmorgan.cakeshop.model.NodeConfig; +import com.jpmorgan.cakeshop.model.NodeSettings; import com.jpmorgan.cakeshop.model.Peer; import java.util.List; +import java.util.Map; public interface NodeService { @@ -23,20 +25,12 @@ public interface NodeService { /** * Update node configuration (may trigger restart) * - * @param logLevel Log level (0 = least verbose, 6 = most verbose) - * @param networkID - * @param identity - * @param mining + * @param settings Log level (0 = least verbose, 6 = most verbose) * @return * @throws APIException */ public NodeConfig update( - Integer logLevel, - Integer networkID, - String identity, - Boolean mining, - String extraParams, - String genesisBlock) throws APIException; + NodeSettings settings) throws APIException; /** * Reset node back to default configuration (will restart) @@ -62,4 +56,30 @@ public NodeConfig update( */ public boolean addPeer(String address) throws APIException; + /** + * Get list of constellation nodes + * + * @return + * @throws APIException + */ + public Map getConstellationNodes() throws APIException; + + /** + * Add new constellation node + * + * @param constellationNode + * @return + * @throws APIException + */ + public NodeConfig addConstellationNode(String constellationNode) throws APIException; + + /** + * Remove constellation node from the list + * + * @param constellationNode + * @return + * @throws APIException + */ + public NodeConfig removeConstellationNode(String constellationNode) throws APIException; + } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/WalletService.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/WalletService.java index 8ce8b34a..67182bf9 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/WalletService.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/WalletService.java @@ -2,10 +2,10 @@ import com.jpmorgan.cakeshop.error.APIException; import com.jpmorgan.cakeshop.model.Account; +import com.jpmorgan.cakeshop.model.json.WalletPostJsonRequest; import java.util.List; - public interface WalletService { /** @@ -24,6 +24,35 @@ public interface WalletService { */ public Account create() throws APIException; + /** + * Unlock account (passphrase should be part of request if account was + * created with one) + * + * @param request + * @return + * @throws APIException + */ + public Boolean unlockAccount(WalletPostJsonRequest request) throws APIException; + + /** + * Lock account (passphrase should be part of request if account was created + * with one) + * + * @param request + * @return + * @throws APIException + */ + public Boolean lockAccount(WalletPostJsonRequest request) throws APIException; + + /** + * Fund account + * + * @param request + * @return + * @throws APIException + */ + public Boolean fundAccount(WalletPostJsonRequest request) throws APIException; + /** * Test whether or not the given account is unlocked in the local wallet * diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/WebSocketPushService.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/WebSocketPushService.java index 181d38e1..5b8f906c 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/WebSocketPushService.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/WebSocketPushService.java @@ -13,10 +13,20 @@ public interface WebSocketPushService { public final String BLOCK_TOPIC = "/topic/block"; public final String PENDING_TRANSACTIONS_TOPIC = "/topic/pending/transactions"; public final String TRANSACTION_TOPIC = "/topic/transaction/"; + public final String GETH_LOG_TOPIC = "/topic/log/geth"; + public final String CONSTELLATION_LOG_TOPIC = "/topic/log/constellation"; + public final String GETH_LOG_PATH = "../logs/geth.log"; + public final String CONSTELLATION_PATH = com.jpmorgan.cakeshop.util.StringUtils.isNotBlank(System.getProperty("spring.config.location")) + ? System.getProperty("spring.config.location").replaceAll("file:", "") + .replaceAll("application.properties", "").concat("constellation-node/") + : null; public final Integer MAX_ASYNC_POOL = 100; public void pushNodeStatus() throws APIException; + public void pushLatestBlocks() throws APIException; + public void pushTransactions() throws APIException; +// public void pushGethLogs(String line) throws APIException; } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/auth/impl/AuthenticationService.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/auth/impl/AuthenticationService.java new file mode 100644 index 00000000..859c389a --- /dev/null +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/auth/impl/AuthenticationService.java @@ -0,0 +1,61 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jpmorgan.cakeshop.service.auth.impl; + +import com.jpmorgan.cakeshop.dao.UserDAO; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +@Service("authService") +public class AuthenticationService implements AuthenticationProvider { + + private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(AuthenticationService.class); + + private final PasswordEncoder ENCODER = new BCryptPasswordEncoder(); + + @Autowired + private UserDAO userDao; + + @Value("${geth.cred1:\"\"}") + private String cred1; + + @Value("${geth.cred2:\"\"}") + private String cred2; + + @Override + public Authentication authenticate(final Authentication authentication) throws AuthenticationException { + final String userName = authentication.getName(); + final String password = authentication.getCredentials().toString(); + if (userName.equals(cred1) && ENCODER.matches(password, cred2)) { + return new UsernamePasswordAuthenticationToken(userName, password); + } else { + throw new AuthenticationException("Unable to authenticate user") { + }; + } + //Use database for auth +// if (userDao.authenticate(userName, password)) { +// return new UsernamePasswordAuthenticationToken(userName, password); +// } else { +// throw new AuthenticationException("Unable to authenticate user") { +// }; +// } + + } + + @Override + public boolean supports(Class authentication) { + return authentication.equals(UsernamePasswordAuthenticationToken.class); + } + +} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/BlockServiceImpl.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/BlockServiceImpl.java index 5a14847f..0b41703d 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/BlockServiceImpl.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/BlockServiceImpl.java @@ -8,8 +8,6 @@ import com.jpmorgan.cakeshop.service.BlockService; import com.jpmorgan.cakeshop.service.GethHttpService; -import java.math.BigInteger; - import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -44,8 +42,8 @@ public Block get(String id, Long number, String tag) throws APIException { throw new APIException("Bad request"); } - Map blockData = - gethService.executeGethCall(method, new Object[]{ input, false }); + Map blockData + = gethService.executeGethCall(method, new Object[]{input, false}); return processBlockData(blockData); } @@ -60,15 +58,15 @@ private Block processBlockData(Map blockData) { Block block = new Block(); // add addresses directly - block.setId((String)blockData.get("hash")); - block.setParentId((String)blockData.get("parentHash")); - block.setNonce((String)blockData.get("nonce")); - block.setSha3Uncles((String)blockData.get("sha3Uncles")); - block.setLogsBloom((String)blockData.get("logsBloom")); - block.setTransactionsRoot((String)blockData.get("transactionsRoot")); - block.setStateRoot((String)blockData.get("stateRoot")); - block.setMiner((String)blockData.get("miner")); - block.setExtraData((String)blockData.get("extraData")); + block.setId((String) blockData.get("hash")); + block.setParentId((String) blockData.get("parentHash")); + block.setNonce((String) blockData.get("nonce")); + block.setSha3Uncles((String) blockData.get("sha3Uncles")); + block.setLogsBloom((String) blockData.get("logsBloom")); + block.setTransactionsRoot((String) blockData.get("transactionsRoot")); + block.setStateRoot((String) blockData.get("stateRoot")); + block.setMiner((String) blockData.get("miner")); + block.setExtraData((String) blockData.get("extraData")); block.setTransactions((List) blockData.get("transactions")); block.setUncles((List) blockData.get("uncles")); @@ -87,7 +85,7 @@ private Block processBlockData(Map blockData) { public List get(long start, long end) throws APIException { List reqs = new ArrayList<>(); for (long i = start; i <= end; i++) { - reqs.add(new RequestModel("eth_getBlockByNumber", new Object[]{ i, false }, 42L)); + reqs.add(new RequestModel("eth_getBlockByNumber", new Object[]{i, false}, 42L)); } return batchGet(reqs); } @@ -96,20 +94,20 @@ public List get(long start, long end) throws APIException { public List get(List numbers) throws APIException { List reqs = new ArrayList<>(); for (Long num : numbers) { - reqs.add(new RequestModel("eth_getBlockByNumber", new Object[]{ num, false }, 42L)); + reqs.add(new RequestModel("eth_getBlockByNumber", new Object[]{num, false}, 42L)); } - return batchGet(reqs); + return batchGet(reqs); } private List batchGet(List reqs) throws APIException { List> batchRes = gethService.batchExecuteGethCall(reqs); - // TODO ignore return order for now - List blocks = new ArrayList<>(); - for (Map blockData : batchRes) { - blocks.add(processBlockData(blockData)); - } - return blocks; + // TODO ignore return order for now + List blocks = new ArrayList<>(); + for (Map blockData : batchRes) { + blocks.add(processBlockData(blockData)); + } + return blocks; } } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/ContractServiceImpl.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/ContractServiceImpl.java index 80d82ee4..394b13c0 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/ContractServiceImpl.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/ContractServiceImpl.java @@ -22,6 +22,8 @@ import com.jpmorgan.cakeshop.util.StreamGobbler; import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -105,19 +107,42 @@ public List compile(String code, CodeType codeType, Boolean optimize) StreamGobbler stdout = StreamGobbler.create(proc.getInputStream()); StreamGobbler stderr = StreamGobbler.create(proc.getErrorStream()); - proc.getOutputStream().write(code.getBytes());; + proc.getOutputStream().write(code.getBytes()); proc.getOutputStream().close(); proc.waitFor(); if (proc.exitValue() != 0) { - throw new APIException("Failed to compile contract (solc exited with code " + proc.exitValue() + ")\n" + stderr.getString()); + //try with different iso encodings + List isoEncodings = Lists.newArrayList("ISO8859_1", "ISO8859_2", "ISO8859_4", "ISO8859_5", "ISO8859_7", + "ISO8859_9", "ISO8859_13", "ISO8859_15"); + for (String encoding : isoEncodings) { + proc = builder.start(); + + stdout = StreamGobbler.create(proc.getInputStream()); + stderr = StreamGobbler.create(proc.getErrorStream()); + + proc.getOutputStream().write(convertCodeToBytes(code, Charset.forName(encoding))); + proc.getOutputStream().close(); + + proc.waitFor(); + if (proc.exitValue() == 0) { + break; + } + } + if (proc.exitValue() != 0) { + LOG.error("Failed Contract code " + code); + throw new APIException("Failed to compile contract (solc exited with code " + proc.exitValue() + ")\n" + stderr.getString()); + } } res = objectMapper.readValue(stdout.getString(), Map.class); + if (proc.isAlive()) { + proc.destroy(); + } } catch (IOException | InterruptedException e) { - LOG.error("REASON FOR CONTRATC FAILURE " + e.getMessage()); + LOG.error("REASON FOR CONTRACT FAILURE " + e.getMessage()); throw new APIException("Failed to compile contract", e); } @@ -195,7 +220,7 @@ public TransactionResult create(String from, String code, CodeType codeType, Obj contractArgs.put("privateFrom", privateFrom); } if (privateFor != null && privateFor.size() > 0) { - contractArgs.put("privateFor", privateFor); + contractArgs.put("privateFor", privateFor); } Map contractRes = geth.executeGethCall("eth_sendTransaction", new Object[]{contractArgs}); @@ -336,4 +361,10 @@ private ContractABI lookupABI(String id) throws APIException { return null; } + private byte[] convertCodeToBytes(String input, Charset charset) { + ByteBuffer outputBuffer = charset.encode(input); + byte[] output = outputBuffer.array(); + return output; + } + } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/GethHttpServiceImpl.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/GethHttpServiceImpl.java index 3dfbb404..d4952e96 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/GethHttpServiceImpl.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/GethHttpServiceImpl.java @@ -9,6 +9,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.jpmorgan.cakeshop.bean.GethConfigBean; +import com.jpmorgan.cakeshop.bean.QuorumConfigBean; import com.jpmorgan.cakeshop.dao.BlockDAO; import com.jpmorgan.cakeshop.dao.TransactionDAO; import com.jpmorgan.cakeshop.dao.WalletDAO; @@ -32,7 +33,6 @@ import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; - import javax.annotation.PreDestroy; import org.apache.commons.lang3.StringUtils; @@ -60,43 +60,45 @@ public class GethHttpServiceImpl implements GethHttpService { public static final String SIMPLE_RESULT = "_result"; public static final Integer DEFAULT_NETWORK_ID = 1006; + public static final Integer DEFAULT_NUMBER_ACCOUNTS = 8; private static final Logger LOG = LoggerFactory.getLogger(GethHttpServiceImpl.class); private static final Logger GETH_LOG = LoggerFactory.getLogger("geth"); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @Autowired private GethConfigBean gethConfig; - @Autowired(required=false) + @Autowired + private QuorumConfigBean quorumConfig; + + @Autowired(required = false) private BlockDAO blockDAO; - @Autowired(required=false) + @Autowired(required = false) private TransactionDAO txDAO; - @Autowired(required=false) + @Autowired(required = false) private WalletDAO walletDAO; @Autowired private ApplicationContext applicationContext; + @Autowired + @Qualifier("asyncExecutor") + private TaskExecutor executor; + + @Autowired + private RestTemplate restTemplate; + private BlockScanner blockScanner; private Boolean running; - private static final ObjectMapper objectMapper = new ObjectMapper(); - private StreamLogAdapter stdoutLogger; private StreamLogAdapter stderrLogger; private final List startupErrors; - - @Autowired - @Qualifier("asyncExecutor") - private TaskExecutor executor; - - @Autowired - private RestTemplate restTemplate; - private final HttpHeaders jsonContentHeaders; public GethHttpServiceImpl() { @@ -133,7 +135,7 @@ private String executeGethCallInternal(String json) throws APIException { private String requestToJson(Object request) throws APIException { try { - return objectMapper.writeValueAsString(request); + return OBJECT_MAPPER.writeValueAsString(request); } catch (JsonProcessingException e) { throw new APIException("Failed to serialize request(s)", e); } @@ -155,7 +157,7 @@ public Map executeGethCall(RequestModel request) throws APIExcep Map data; try { - data = objectMapper.readValue(response, Map.class); + data = OBJECT_MAPPER.readValue(response, Map.class); } catch (IOException e) { throw new APIException("RPC call failed", e); } @@ -170,7 +172,7 @@ public List> batchExecuteGethCall(List request List> responses; try { - responses = objectMapper.readValue(response, List.class); + responses = OBJECT_MAPPER.readValue(response, List.class); List> results = new ArrayList<>(responses.size()); for (Map data : responses) { @@ -216,6 +218,12 @@ private Map processResponse(Map data) throws API public Boolean stop() { LOG.info("Stopping geth"); + if (gethConfig.isEmbeddedQuorum()) { + if (!stopConstellation()) { + LOG.error("Could not stop constellation"); + } + } + try { if (blockScanner != null) { blockScanner.shutdown(); @@ -239,7 +247,7 @@ public Boolean stop() { @CacheEvict(value = "contracts", allEntries = true) @Override - public Boolean reset() { + public Boolean reset(String... additionalParams) { boolean stopped = this.stop(); if (!stopped) { @@ -266,7 +274,7 @@ public Boolean reset() { walletDAO.reset(); } - return this.start(); + return this.start(additionalParams); } @Override @@ -308,6 +316,60 @@ public Boolean isRunning() { return true; } + @Override + public Boolean startConstellation() { + Boolean success = false; + File constellationLogDir = new File(quorumConfig.getConstellationConfigPath().concat("logs")); + File constellationLog = new File(quorumConfig.getConstellationConfigPath().concat("logs/").concat("constellation.log")); + if (!constellationLogDir.exists()) { + constellationLogDir.mkdirs(); + if (!constellationLog.exists()) { + try { + constellationLog.createNewFile(); + } catch (IOException ex) { + LOG.error("Could not create log for constellation", ex.getMessage()); + return false; + } + } + } + //TODO: When Windows verision for constellation is available - add the functionality to start it under Windows. + String[] command = new String[]{"/bin/sh", "-c", + quorumConfig.getConstellationPath().concat(" ") + .concat(quorumConfig.getConstellationConfigPath()).concat("node.conf") + .concat(" 2>> ").concat(constellationLog.getAbsolutePath()) + .concat(" &")}; + ProcessBuilder builder = new ProcessBuilder(command); + try { + Process process = builder.start(); + Integer constProcessId = getUnixPID(process); + writePidToFile(constProcessId, gethConfig.getConstPidFileName()); + success = true; + LOG.info("CONSTELLATION STARTED"); + TimeUnit.SECONDS.sleep(5); + } catch (IOException | InterruptedException ex) { + LOG.error(ex.getMessage()); + } + return success; + } + + @Override + public Boolean stopConstellation() { + Boolean success = false; + try { + String pid = ProcessUtils.getUnixPidByName("constellation"); + if (StringUtils.isNotBlank(pid)) { + success = killProcess(pid, null); + LOG.info("Stopping Constellation with pid " + pid); + new File(gethConfig.getConstPidFileName()).delete(); + } else { + LOG.warn("Could not get PID to stop Constellation"); + } + } catch (InterruptedException | IOException ex) { + LOG.error(ex.getMessage()); + } + return success; + } + @Override public Boolean start(String... additionalParams) { @@ -340,9 +402,21 @@ public Boolean start(String... additionalParams) { } } + if (gethConfig.isEmbeddedQuorum()) { + additionalParams = setAdditionalParams(additionalParams).toArray(new String[setAdditionalParams(additionalParams).size()]); + if (gethConfig.isConstellationEnabled() && !isProcessRunning(readPidFromFile(gethConfig.getConstPidFileName())) && !gethConfig.IS_BOOT_NODE) { + startConstellation(); + } + } + ProcessBuilder builder = createProcessBuilder(gethConfig, createGethCommand(additionalParams)); - Process process = builder.start(); + final Map env = builder.environment(); + if (gethConfig.isEmbeddedQuorum() && !gethConfig.IS_BOOT_NODE) { + env.put("PRIVATE_CONFIG", quorumConfig.getConstellationConfigPath().concat("node.conf")); + } + + Process process = builder.start(); this.stdoutLogger = (StreamLogAdapter) new StreamLogAdapter(GETH_LOG, process.getInputStream()).startAsync(); this.stderrLogger = (StreamLogAdapter) new StreamLogAdapter(GETH_LOG, process.getErrorStream()).startAsync(); @@ -357,7 +431,6 @@ public Boolean start(String... additionalParams) { } // TODO add a watcher thread to make sure it doesn't die.. - } catch (IOException ex) { logError("Cannot start process: " + ex.getMessage()); return this.running = false; @@ -383,6 +456,136 @@ public void runPostStartupTasks() { blockScanner.start(); } + @Override + public List setAdditionalParams(String[] additionalParamsArray) { + List additionalParams; + if (null != additionalParamsArray && additionalParamsArray.length > 0) { + additionalParams = Lists.newArrayList(additionalParamsArray); + } else { + additionalParams = new ArrayList<>(); + } + Boolean isBootNode = false; + Boolean saveProps = false; + //figure out if node is boot node + if (quorumConfig.isBootNode()) { + if (StringUtils.isNotBlank(quorumConfig.getBootNodeAddress())) { + additionalParams.add("bootnode"); + additionalParams.add("--nodekeyhex"); + additionalParams.add(quorumConfig.getBootNodeKey()); + additionalParams.add(" --addr"); + additionalParams.add(quorumConfig.getBootNodeAddress()); + isBootNode = true; + } else if (StringUtils.isNotBlank(System.getProperty("geth.bootnode.address")) + && StringUtils.isNotBlank(System.getProperty("geth.bootnode.key"))) { + String nodeport = System.getProperty("geth.bootnode.address", "127.0.0.1:33445").split(":")[1]; + gethConfig.setProperty("geth.boot.node", "true"); + gethConfig.setProperty("geth.bootnode.address", System.getProperty("geth.bootnode.address", "127.0.0.1:33445")); + gethConfig.setProperty("geth.bootnode.key", System.getProperty("geth.bootnode.key")); + gethConfig.setProperty("geth.node.port", nodeport); + additionalParams.add("bootnode"); + additionalParams.add("--nodekeyhex"); + additionalParams.add(System.getProperty("geth.bootnode.key")); + additionalParams.add(" --addr"); + additionalParams.add(System.getProperty("geth.bootnode.address", "127.0.0.1:33445")); + saveProps = true; + isBootNode = true; + } + } + + if (!isBootNode) { + if (StringUtils.isNotBlank(quorumConfig.getBootNodes())) { + additionalParams.add("--bootnodes"); + additionalParams.add(quorumConfig.getBootNodes()); + + } else if (StringUtils.isNotBlank(System.getProperty("geth.bootnodes.list"))) { + additionalParams.add("--bootnodes"); + additionalParams.add(System.getProperty("geth.bootnodes.list")); + gethConfig.setProperty("geth.bootnodes.list", System.getProperty("geth.bootnodes.list")); + saveProps = true; + } + + //Set Block Maker account + if (StringUtils.isNotBlank(gethConfig.getBlockMaker())) { + additionalParams.add("--blockmakeraccount"); + additionalParams.add(gethConfig.getBlockMaker()); + additionalParams.add("--blockmakerpassword"); + additionalParams.add(null != gethConfig.getBlockMakerPass() ? gethConfig.getBlockMakerPass() : ""); + } else if (StringUtils.isNotBlank(System.getProperty("geth.block.maker"))) { + additionalParams.add("--blockmakeraccount"); + additionalParams.add(System.getProperty("geth.block.maker")); + additionalParams.add("--blockmakerpassword"); + String pass = StringUtils.isNotBlank(System.getProperty("geth.block.maker.pass")) ? System.getProperty("geth.block.maker.pass") : ""; + additionalParams.add(pass); + gethConfig.setBlockMaker(System.getProperty("geth.block.maker")); + gethConfig.setBlockMakerPass(pass); + saveProps = true; + } + //Set min and max block time + if (null != gethConfig.getMinBlockTime()) { + additionalParams.add("--minblocktime"); + additionalParams.add(String.valueOf(gethConfig.getMinBlockTime())); + } else if (StringUtils.isNotBlank(System.getProperty("geth.min.blocktime"))) { + additionalParams.add("--minblocktime"); + additionalParams.add(System.getProperty("geth.min.blocktime")); + gethConfig.setProperty("geth.min.blocktime", System.getProperty("geth.min.blocktime")); + saveProps = true; + } else { + additionalParams.add("--minblocktime"); + additionalParams.add("2"); + } + + if (null != gethConfig.getMaxBlockTime()) { + additionalParams.add("--maxblocktime"); + additionalParams.add(String.valueOf(gethConfig.getMaxBlockTime())); + } else if (StringUtils.isNotBlank(System.getProperty("geth.max.blocktime"))) { + additionalParams.add("--maxblocktime"); + additionalParams.add(System.getProperty("geth.max.blocktime")); + gethConfig.setProperty("geth.max.blocktime", System.getProperty("geth.max.blocktime")); + saveProps = true; + } else { + additionalParams.add("--maxblocktime"); + additionalParams.add("5"); + } + + //Set Vote Account + if (StringUtils.isNotBlank(gethConfig.getVoteAccount())) { + additionalParams.add("--voteaccount"); + additionalParams.add(gethConfig.getVoteAccount()); + additionalParams.add("--votepassword"); + additionalParams.add(null != gethConfig.getVoteAccountPass() ? gethConfig.getVoteAccountPass() : ""); + } else if (StringUtils.isNotBlank(System.getProperty("geth.vote.account"))) { + additionalParams.add("--voteaccount"); + additionalParams.add(System.getProperty("geth.vote.account")); + additionalParams.add("--votepassword"); + String pass = StringUtils.isNotBlank(System.getProperty("geth.vote.account.pass")) ? System.getProperty("geth.vote.account.pass") : ""; + additionalParams.add(pass); + gethConfig.setVoteAccount(System.getProperty("geth.vote.account")); + gethConfig.setVoteAccountPass(pass); + saveProps = true; + } + //Set permissioned + if (gethConfig.isPermissionedNode()) { + additionalParams.add("--permissioned"); + } else if (StringUtils.isNotBlank(System.getProperty("geth.permissioned")) + && Boolean.valueOf(System.getProperty("geth.permissioned"))) { + additionalParams.add("--permissioned"); + gethConfig.setPermissionedNode(Boolean.valueOf(System.getProperty("geth.permissioned"))); + saveProps = true; + } + + } + if (saveProps) { + try { + gethConfig.save(); + } catch (IOException e) { + LOG.error("Error writing application.properties: " + e.getMessage()); + System.exit(1); + } + } + + return additionalParams; + } + /** * Initialize geth datadir via "geth init" command, using the configured * genesis block @@ -417,45 +620,55 @@ private List createGethInitCommand() { ); } - private List createGethCommand(String... additionalParams) { + private List createGethCommand(String... additionalParams) throws IOException { - // Figure out how many accounts need unlocking + // Only unlock accounts from genesis file String accountsToUnlock = ""; - int numAccounts = walletDAO.list().size(); - if (numAccounts == 0) { - accountsToUnlock = "0,1,2"; // default to accounts we ship - - } else { - for (int i = 0; i < numAccounts; i++) { - if (accountsToUnlock.length() > 0) { - accountsToUnlock += ","; - } - accountsToUnlock += i; + for (int i = 0; i < DEFAULT_NUMBER_ACCOUNTS; i++) { + if (accountsToUnlock.length() > 0) { + accountsToUnlock += ","; } + accountsToUnlock += i; + } + + //Option to overwrite default port nide post and geth http usr through command line + Boolean saveGethConfig = false; + if (StringUtils.isNotBlank(System.getProperty("geth.url"))) { + gethConfig.setRpcUrl(System.getProperty("geth.url")); + saveGethConfig = true; + } + + if (StringUtils.isNotBlank(System.getProperty("geth.node.port"))) { + gethConfig.setGethNodePort(System.getProperty("geth.node.port")); + saveGethConfig = true; } List commands = Lists.newArrayList(gethConfig.getGethPath(), "--port", gethConfig.getGethNodePort(), "--datadir", gethConfig.getDataDirPath(), "--solc", gethConfig.getSolcPath(), - "--nat", "none", "--nodiscover", + "--nat", "none", + "--nodiscover", "--unlock", accountsToUnlock, "--password", gethConfig.getGethPasswordFile(), "--rpc", "--rpcaddr", "127.0.0.1", "--rpcport", gethConfig.getRpcPort(), - "--rpcapi", gethConfig.getRpcApiList(), - "--ipcdisable" + "--rpcapi", gethConfig.getRpcApiList() ); if (null != additionalParams && additionalParams.length > 0) { commands.addAll(Lists.newArrayList(additionalParams)); } + if (!gethConfig.isEmbeddedQuorum()) { + commands.add("--ipcdisable"); + } + commands.add("--networkid"); commands.add(String.valueOf(gethConfig.getNetworkId() == null ? DEFAULT_NETWORK_ID : gethConfig.getNetworkId())); commands.add("--verbosity"); commands.add(String.valueOf(gethConfig.getVerbosity() == null ? "3" : gethConfig.getVerbosity())); - if (null != gethConfig.isMining() && gethConfig.isMining() == true) { + if (null != gethConfig.isMining() && gethConfig.isMining() == true && !gethConfig.isEmbeddedQuorum()) { commands.add("--mine"); commands.add("--minerthreads"); commands.add("1"); @@ -474,13 +687,16 @@ private List createGethCommand(String... additionalParams) { } } } + if (saveGethConfig) { + gethConfig.save(); + } return commands; } private boolean checkWalletUnlocked() { WalletService wallet = applicationContext.getBean(WalletService.class); - List accounts = null; + List accounts; try { accounts = wallet.list(); } catch (APIException e) { @@ -491,21 +707,34 @@ private boolean checkWalletUnlocked() { } long timeStart = System.currentTimeMillis(); - long timeout = gethConfig.getGethUnlockTimeout() * accounts.size(); // default 2 sec per account + long timeout = gethConfig.getGethUnlockTimeout() * DEFAULT_NUMBER_ACCOUNTS; // default 2 sec per account - LOG.info("Waiting up to " + timeout + "ms for " + accounts.size() + " accounts to unlock"); + LOG.info("Waiting up to " + timeout + "ms for " + DEFAULT_NUMBER_ACCOUNTS + " accounts to unlock"); int unlocked = 0; + + try { + TimeUnit.MILLISECONDS.sleep(timeout); + } catch (InterruptedException e) { + logError("Interrupted while waiting for wallet to unlock"); + return false; + } + + //check if default accounts are unlocked for (Account account : accounts) { - while (true) { + + while (true && unlocked < DEFAULT_NUMBER_ACCOUNTS) { + try { if (wallet.isUnlocked(account.getAddress())) { LOG.debug("Account " + account.getAddress() + " unlocked"); unlocked++; break; + } else { + LOG.debug("Account " + account.getAddress() + " is NOT unlocked"); } } catch (APIException e) { - LOG.debug("Address " + account.getAddress() + " is not unlocked", e); + LOG.warn("Could not unlock address " + account.getAddress(), e); } if (System.currentTimeMillis() - timeStart >= timeout) { @@ -514,12 +743,6 @@ private boolean checkWalletUnlocked() { return false; } - try { - TimeUnit.MILLISECONDS.sleep(50); - } catch (InterruptedException e) { - logError("Interrupted while waiting for wallet to unlock"); - return false; - } } } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/LogTailerListener.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/LogTailerListener.java new file mode 100644 index 00000000..f81dff59 --- /dev/null +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/LogTailerListener.java @@ -0,0 +1,27 @@ +package com.jpmorgan.cakeshop.service.impl; + +import com.jpmorgan.cakeshop.model.APIResponse; +import static com.jpmorgan.cakeshop.service.WebSocketPushService.GETH_LOG_TOPIC; +import org.apache.commons.io.input.TailerListenerAdapter; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.stereotype.Component; + +@Component +@Scope("prototype") +public class LogTailerListener extends TailerListenerAdapter { + + @Autowired(required = false) + private SimpMessagingTemplate template; + + @Override + public void handle(String line) { + if (StringUtils.isNotBlank(line)) { + template.convertAndSend(GETH_LOG_TOPIC, + APIResponse.newSimpleResponse(line)); + } + } + +} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/LogViewServiceImpl.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/LogViewServiceImpl.java new file mode 100644 index 00000000..848aa9c7 --- /dev/null +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/LogViewServiceImpl.java @@ -0,0 +1,63 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jpmorgan.cakeshop.service.impl; + +import com.jpmorgan.cakeshop.error.APIException; +import com.jpmorgan.cakeshop.service.LogViewService; +import com.jpmorgan.cakeshop.util.StringUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayDeque; +import java.util.Deque; + +import org.apache.commons.io.input.ReversedLinesFileReader; +import org.springframework.stereotype.Component; + +@Component +public class LogViewServiceImpl implements LogViewService { + + private String previousLine = ""; + + @Override + public Deque getLog(String logPath, Integer numberOfLines) throws APIException { + return getLines(logPath, numberOfLines); + } + + @Override + public String getLog(String logPath) throws APIException { + + Deque lines = getLines(logPath, 1); + + if (!lines.isEmpty() && !lines.getFirst().equals(previousLine)) { + previousLine = lines.getFirst(); + return previousLine; + } else { + return StringUtils.EMPTY; + } + } + + private Deque getLines(String logPath, Integer numberOfLines) throws APIException { + try { + File file = new File(logPath); + int counter = 0; + Deque lines = new ArrayDeque<>(); + try (ReversedLinesFileReader fileReader = new ReversedLinesFileReader(file, 4000, Charset.forName("UTF-8"))) { + String line; + + while ((line = fileReader.readLine()) != null && counter++ < numberOfLines) { + lines.addFirst(line); + } + } + + return lines; + } catch (IOException ex) { + throw new APIException(ex.getCause()); + } + } + +} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/NodeServiceImpl.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/NodeServiceImpl.java index 472897cd..d96cd21b 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/NodeServiceImpl.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/NodeServiceImpl.java @@ -1,13 +1,17 @@ package com.jpmorgan.cakeshop.service.impl; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import static com.jpmorgan.cakeshop.service.impl.GethHttpServiceImpl.*; import com.google.common.base.Joiner; +import com.google.common.collect.Lists; import com.jpmorgan.cakeshop.bean.GethConfigBean; import com.jpmorgan.cakeshop.dao.PeerDAO; import com.jpmorgan.cakeshop.error.APIException; import com.jpmorgan.cakeshop.model.Node; import com.jpmorgan.cakeshop.model.NodeConfig; +import com.jpmorgan.cakeshop.model.NodeSettings; import com.jpmorgan.cakeshop.model.Peer; import com.jpmorgan.cakeshop.service.GethHttpService; import com.jpmorgan.cakeshop.service.GethRpcConstants; @@ -16,13 +20,20 @@ import com.jpmorgan.cakeshop.util.AbiUtils; import com.jpmorgan.cakeshop.util.EEUtils; import com.jpmorgan.cakeshop.util.EEUtils.IP; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.Enumeration; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Properties; import org.apache.commons.lang3.StringUtils; import org.slf4j.LoggerFactory; @@ -76,7 +87,7 @@ public Node get() throws APIException { URI uri = new URI(nodeURI); String host = uri.getHost(); // if host or IP aren't set, then populate with correct IP - if(StringUtils.isEmpty(host) || "[::]".equals(host) || "0.0.0.0".equalsIgnoreCase(host)){ + if (StringUtils.isEmpty(host) || "[::]".equals(host) || "0.0.0.0".equalsIgnoreCase(host)) { try { List ips = EEUtils.getAllIPs(); @@ -147,7 +158,7 @@ public Node get() throws APIException { throw ex; - } catch (NumberFormatException ex){ + } catch (NumberFormatException ex) { LOG.error(ex.getMessage()); throw new APIException(ex.getMessage()); @@ -162,55 +173,96 @@ private NodeConfig createNodeConfig() throws IOException { } @Override - public NodeConfig update( - Integer logLevel, Integer networkID, String identity, Boolean mining, - String extraParams, String genesisBlock) throws APIException { + public NodeConfig update(NodeSettings settings) throws APIException { boolean restart = false; boolean reset = false; - if (networkID != null && networkID != gethConfig.getNetworkId()) { - gethConfig.setNetworkId(networkID); - restart = true; - } + if (null != settings) { + if (settings.getNetworkId() != null && !settings.getNetworkId().equals(gethConfig.getNetworkId())) { + gethConfig.setNetworkId(settings.getNetworkId()); + restart = true; + } - if (StringUtils.isNotEmpty(identity) && !identity.contentEquals(gethConfig.getIdentity())) { - gethConfig.setIdentity(identity); - restart = true; - } + if (StringUtils.isNotEmpty(settings.getIdentity()) && !settings.getIdentity().contentEquals(gethConfig.getIdentity())) { + gethConfig.setIdentity(settings.getIdentity()); + restart = true; + } - if (logLevel != null && logLevel != gethConfig.getVerbosity()) { - gethConfig.setVerbosity(logLevel); - if (!restart) { - // make it live immediately - gethService.executeGethCall(ADMIN_VERBOSITY, new Object[]{ logLevel }); + if (settings.getLogLevel() != null && !settings.getLogLevel().equals(gethConfig.getVerbosity())) { + gethConfig.setVerbosity(settings.getLogLevel()); + if (!restart) { + // make it live immediately + gethService.executeGethCall(ADMIN_VERBOSITY, new Object[]{settings.getLogLevel()}); + } } - } - String currExtraParams = gethConfig.getExtraParams(); - if (extraParams != null && (currExtraParams == null || !extraParams.contentEquals(currExtraParams))) { - gethConfig.setExtraParams(extraParams); - restart = true; - } + String currExtraParams = gethConfig.getExtraParams(); + if (StringUtils.isNotBlank(settings.getExtraParams()) && (currExtraParams == null || !settings.getExtraParams().contentEquals(currExtraParams))) { + gethConfig.setExtraParams(settings.getExtraParams()); + restart = true; + } - try { - if (StringUtils.isNotBlank(genesisBlock) && !genesisBlock.contentEquals(gethConfig.getGenesisBlock())) { - gethConfig.setGenesisBlock(genesisBlock); - reset = true; + try { + if (StringUtils.isNotBlank(settings.getGenesisBlock()) && !settings.getGenesisBlock().contentEquals(gethConfig.getGenesisBlock())) { + gethConfig.setGenesisBlock(settings.getGenesisBlock()); + reset = true; + } + } catch (IOException e) { + throw new APIException("Failed to update genesis block", e); } - } catch (IOException e) { - throw new APIException("Failed to update genesis block", e); - } - if (!quorumService.isQuorum() && mining != null && mining != gethConfig.isMining()) { - gethConfig.setMining(mining); + if (!quorumService.isQuorum() && settings.isMining() != null && !settings.isMining().equals(gethConfig.isMining())) { + gethConfig.setMining(settings.isMining()); - if (!restart) { - // make it live immediately - if (mining == true) { - gethService.executeGethCall(ADMIN_MINER_START, "1"); - } else { - gethService.executeGethCall(ADMIN_MINER_STOP); + if (!restart) { + // make it live immediately + if (settings.isMining()) { + gethService.executeGethCall(ADMIN_MINER_START, "1"); + } else { + gethService.executeGethCall(ADMIN_MINER_STOP); + } + } + } + + //Quorum specific settings + if (quorumService.isQuorum()) { + if (StringUtils.isNotBlank(settings.getBlockMakerAccount()) + && ((StringUtils.isNotBlank(gethConfig.getBlockMaker()) && !settings.getBlockMakerAccount().contentEquals(gethConfig.getBlockMaker())) + || StringUtils.isBlank(gethConfig.getVoteAccount()))) { + gethConfig.setBlockMaker(settings.getBlockMakerAccount()); + restart = true; + } + + if (StringUtils.isNotBlank(settings.getVoterAccount()) && ((StringUtils.isNotBlank(gethConfig.getVoteAccount()) + && !settings.getVoterAccount().contentEquals(gethConfig.getVoteAccount())) + || StringUtils.isBlank(gethConfig.getVoteAccount()))) { + gethConfig.setVoteAccount(settings.getVoterAccount()); + restart = true; + } + + if (null != settings.getMinBlockTime() + && (null != gethConfig.getMinBlockTime() && !settings.getMinBlockTime().equals(gethConfig.getMinBlockTime()))) { + gethConfig.setMinBlockTime(settings.getMinBlockTime()); + restart = true; + } else if (null != settings.getMinBlockTime() && null == gethConfig.getMinBlockTime()) { + gethConfig.setMinBlockTime(settings.getMinBlockTime()); + restart = true; + } + + if (null != settings.getMaxBlockTime() + && (null != gethConfig.getMaxBlockTime() && !settings.getMaxBlockTime().equals(gethConfig.getMaxBlockTime()))) { + gethConfig.setMinBlockTime(settings.getMaxBlockTime()); + restart = true; + } else if (null != settings.getMaxBlockTime() && null == gethConfig.getMaxBlockTime()) { + gethConfig.setMinBlockTime(settings.getMaxBlockTime()); + restart = true; + } + + if (settings.isMining() != null && !settings.isMining() && StringUtils.isNotBlank(gethConfig.getBlockMaker())) { + gethService.executeGethCall("quorum.pauseBlockMaker"); + } else if (settings.isMining() != null && settings.isMining() && StringUtils.isNotBlank(gethConfig.getBlockMaker())) { + gethService.executeGethCall("quorum.resumeBlockMaker"); } } } @@ -284,6 +336,29 @@ public boolean addPeer(String address) throws APIException { throw new APIException("Bad peer address URI: " + address, e); } + if (gethConfig.isPermissionedNode()) { + File permissionJson = new File(gethConfig.getDataDirPath().concat("/").concat("permissioned-nodes.json")); + List permissionNodes; + ObjectMapper mapper = new ObjectMapper(); + try { + if (!permissionJson.exists()) { + permissionJson.createNewFile(); + permissionNodes = Lists.newArrayList(address); + mapper.writeValue(permissionJson, permissionNodes); + } else { + permissionNodes = mapper.readValue(permissionJson, new TypeReference>() { + }); + if (!permissionNodes.contains(address)) { + permissionNodes.add(address); + mapper.writeValue(permissionJson, permissionNodes); + } + } + } catch (IOException ex) { + LOG.error("Could not operate with permission file", ex); + throw new APIException("Could not operate with permission file"); + } + } + Map res = gethService.executeGethCall(ADMIN_PEERS_ADD, address); if (res == null) { return false; @@ -298,13 +373,67 @@ public boolean addPeer(String address) throws APIException { peer.setNodeUrl(address); peerDAO.save(peer); // TODO if db is not enabled, save peers somewhere else? props file? + } return added; } + @Override + public Map getConstellationNodes() throws APIException { + try { + Properties constellationConfig = getConstellationConfig(); + Map constellationNodes = getConstellationNodesMap(constellationConfig); + return constellationNodes; + } catch (IOException ex) { + LOG.error("Error saving constellation config", ex); + throw new APIException("Error saving constellation config", ex); + } + } + + @Override + public NodeConfig addConstellationNode(String constellationNode) throws APIException { + NodeConfig nodeInfo; + try { + Properties constellationConfig = getConstellationConfig(); + List constellationNodes = (List) getConstellationNodesMap(constellationConfig).get("remote"); + if (null == constellationNodes) { + constellationNodes = new ArrayList<>(); + } + constellationNodes.add(constellationNode); + updateConstellationConfig(constellationConfig, constellationNodes); + nodeInfo = createNodeConfig(); + restart(); + return nodeInfo; + } catch (IOException e) { + LOG.error("Error saving constellation config", e); + throw new APIException("Error saving constellation config", e); + } + } + + @Override + public NodeConfig removeConstellationNode(String constellationNode) throws APIException { + NodeConfig nodeInfo; + try { + Properties constellationConfig = getConstellationConfig(); + List constellationNodes = (List) getConstellationNodesMap(constellationConfig).get("remote"); + if (null != constellationNodes) { + constellationNodes = new ArrayList<>(); + constellationNodes.remove(constellationNode); + updateConstellationConfig(constellationConfig, constellationNodes); + restart(); + } + nodeInfo = createNodeConfig(); + return nodeInfo; + } catch (IOException e) { + LOG.error("Error saving constellation config", e); + throw new APIException("Error saving constellation config", e); + } + } + @SuppressWarnings("unchecked") - private Peer createPeer(Map data) { + private Peer createPeer(Map data + ) { if (data == null || data.isEmpty()) { return null; } @@ -328,4 +457,64 @@ private Peer createPeer(Map data) { return peer; } + private Map getConstellationNodesMap(Properties props) throws IOException { + String constellations = props.getProperty("otherNodeUrls").replaceAll("\\[", "").replaceAll("\\]", "").replaceAll("\"", ""); + String localConstellation = props.getProperty("url").replaceAll("\"", ""); + Map constellationMap = new LinkedHashMap<>(); + constellationMap.put("local", localConstellation); + List constellaltionNodes; + if (StringUtils.isNotBlank(constellations)) { + constellaltionNodes = Lists.newArrayList(constellations.split(",")); + constellationMap.put("remote", constellaltionNodes); + return constellationMap; + } + return constellationMap; + } + + private Properties getConstellationConfig() throws IOException { + String destination = com.jpmorgan.cakeshop.util.StringUtils.isNotBlank(System.getProperty("spring.config.location")) + ? System.getProperty("spring.config.location").replaceAll("file:", "") + .replaceAll("application.properties", "/").concat("constellation-node/") + : gethConfig.getDataDirPath().concat("/constellation/"); + destination = destination.concat("node.conf"); + Properties props = new Properties(); + props.load(new FileReader(new File(destination))); + return props; + } + + private void updateConstellationConfig(Properties props, List constellaltionNodes) throws IOException { + String destination = com.jpmorgan.cakeshop.util.StringUtils.isNotBlank(System.getProperty("spring.config.location")) + ? System.getProperty("spring.config.location").replaceAll("file:", "") + .replaceAll("application.properties", "/").concat("constellation-node/") + : gethConfig.getDataDirPath().concat("/constellation/"); + destination = destination.concat("node.conf"); + + String updatedConstellations = "["; + Integer index = 0; + + for (String node : constellaltionNodes) { + updatedConstellations = updatedConstellations.concat("\"").concat(node).concat("\"").replaceAll(":", "\\:"); + index++; + if (index < constellaltionNodes.size()) { + updatedConstellations = updatedConstellations.concat(","); + } + } + + updatedConstellations = updatedConstellations.concat("]"); + props.setProperty("otherNodeUrls", updatedConstellations); + + Enumeration keys = props.keys(); + try (BufferedWriter out = new BufferedWriter(new FileWriter(destination))) { + while (keys.hasMoreElements()) { + String key = keys.nextElement().toString(); + String value = props.getProperty(key).replaceAll("\\\\", ""); + out.write(key); + out.write(" = "); + out.write(value); + out.newLine(); + } + out.flush(); + } + } + } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/QuorumServiceImpl.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/QuorumServiceImpl.java index e0b4d754..5aa9358f 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/QuorumServiceImpl.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/QuorumServiceImpl.java @@ -14,52 +14,54 @@ @Service public class QuorumServiceImpl implements QuorumService { - + @Autowired private GethConfigBean gethConfig; - + @Autowired private GethHttpService geth; - + @Override public boolean isQuorum() { if (gethConfig.isQuorum() == null) { try { getQuorumInfo(); gethConfig.setQuorum(true); - + } catch (APIException e) { if (e.getMessage().contains("method quorum_nodeInfo does not exist")) { gethConfig.setQuorum(false); } } } - + if (gethConfig.isQuorum() != null) { return gethConfig.isQuorum(); } - + return false; } - + @SuppressWarnings("unchecked") @Override public QuorumInfo getQuorumInfo() throws APIException { - + Map data = geth.executeGethCall("quorum_nodeInfo"); - + QuorumInfo info = new QuorumInfo(); info.setQuorum(true); - + if (data == null || data.isEmpty()) { return info; } - + info.setBlockMakerAccount((String) data.get("blockMakerAccount")); info.setVoteAccount((String) data.get("voteAccount")); info.setCanCreateBlocks((Boolean) data.get("canCreateBlocks")); info.setCanVote((Boolean) data.get("canVote")); - + info.setNodeKey(gethConfig.getPublicKey()); + info.setIsConstellationEnabled(gethConfig.isConstellationEnabled()); + Map strat = (Map) data.get("blockmakestrategy"); if (strat != null && !strat.isEmpty()) { BlockMakerStrategy bstrat = new BlockMakerStrategy(); @@ -69,8 +71,8 @@ public QuorumInfo getQuorumInfo() throws APIException { bstrat.setType((String) strat.get("type")); info.setBlockMakerStrategy(bstrat); } - + return info; } - + } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/WalletServiceImpl.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/WalletServiceImpl.java index 47ec21d6..db2e8fc7 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/WalletServiceImpl.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/WalletServiceImpl.java @@ -3,6 +3,7 @@ import com.jpmorgan.cakeshop.dao.WalletDAO; import com.jpmorgan.cakeshop.error.APIException; import com.jpmorgan.cakeshop.model.Account; +import com.jpmorgan.cakeshop.model.json.WalletPostJsonRequest; import com.jpmorgan.cakeshop.service.GethHttpService; import com.jpmorgan.cakeshop.service.GethRpcConstants; import com.jpmorgan.cakeshop.service.WalletService; @@ -10,6 +11,7 @@ import java.math.BigInteger; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -51,13 +53,15 @@ public List list() throws APIException { accounts = new ArrayList<>(); for (String address : accountList) { Map accountData = gethService.executeGethCall( - PERSONAL_GET_ACCOUNT_BALANCE, new Object[] { address, "latest" }); - String strBal = (String)accountData.get("_result"); + PERSONAL_GET_ACCOUNT_BALANCE, new Object[]{address, "latest"}); + String strBal = (String) accountData.get("_result"); BigInteger bal = AbiUtils.hexToBigInteger(strBal); account = new Account(); account.setAddress(address); account.setBalance(bal.toString()); + account.setUnlocked(isUnlocked(address)); accounts.add(account); + } } } @@ -77,10 +81,62 @@ public Account create() throws APIException { return account; } + @Override + public Boolean unlockAccount(WalletPostJsonRequest request) throws APIException { + try { + Map result = gethService.executeGethCall("personal_unlockAccount", new Object[]{request.getAccount(), request.getAccountPassword()}); + String response = result.get("_result").toString(); + if (StringUtils.isNotBlank(response) && Boolean.valueOf(response)) { + return true; + } + } catch (APIException ex) { + throw ex; + } + return Boolean.FALSE; + } + + @Override + public Boolean lockAccount(WalletPostJsonRequest request) throws APIException { + try { + Map result = gethService.executeGethCall("personal_lockAccount", new Object[]{request.getAccount()}); + String response = result.get("_result").toString(); + if (StringUtils.isNotBlank(response) && Boolean.valueOf(response)) { + return Boolean.TRUE; + } + } catch (APIException ex) { + throw ex; + } + return Boolean.FALSE; + } + + @Override + public Boolean fundAccount(WalletPostJsonRequest request) throws APIException { + try { + String accountFrom = StringUtils.isNotBlank(request.getFromAccount()) ? request.getFromAccount() + : list().get(0).getAddress(); + if (accountFrom.equals(request.getAccount())) { + accountFrom = list().get(1).getAddress(); + } + Map fundArgs = new HashMap<>(); + fundArgs.put("from", accountFrom); + fundArgs.put("to", request.getAccount()); + fundArgs.put("value", request.getNewBalance()); + Map result = gethService.executeGethCall("eth_sendTransaction", new Object[]{fundArgs}); + String response = result.get("_result").toString(); + if (StringUtils.isNotBlank(response)) { + return Boolean.TRUE; + } + } catch (APIException ex) { + throw ex; + } + return Boolean.FALSE; + + } + @Override public boolean isUnlocked(String address) throws APIException { try { - Map result = gethService.executeGethCall("eth_sign", new Object[] { address, DUMMY_PAYLOAD_HASH }); + Map result = gethService.executeGethCall("eth_sign", new Object[]{address, DUMMY_PAYLOAD_HASH}); if (StringUtils.isNotBlank((String) result.get("_result"))) { return true; } @@ -91,5 +147,4 @@ public boolean isUnlocked(String address) throws APIException { } return false; } - } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/WebSocketPushServiceImpl.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/WebSocketPushServiceImpl.java index 113c14e9..96970744 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/WebSocketPushServiceImpl.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/impl/WebSocketPushServiceImpl.java @@ -7,17 +7,18 @@ import com.jpmorgan.cakeshop.model.Block; import com.jpmorgan.cakeshop.model.Node; import com.jpmorgan.cakeshop.service.BlockService; -import com.jpmorgan.cakeshop.service.ContractService; import com.jpmorgan.cakeshop.service.GethHttpService; import com.jpmorgan.cakeshop.service.NodeService; -import com.jpmorgan.cakeshop.service.TransactionService; import com.jpmorgan.cakeshop.service.WebSocketAsyncPushService; import com.jpmorgan.cakeshop.service.WebSocketPushService; +import java.io.File; import java.util.List; import java.util.Map; +import javax.annotation.PreDestroy; import org.apache.commons.collections4.map.LRUMap; +import org.apache.commons.io.input.Tailer; import org.apache.commons.lang3.StringUtils; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -38,84 +39,85 @@ @Component public class WebSocketPushServiceImpl implements WebSocketPushService { - private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(WebSocketPushServiceImpl.class); + private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(WebSocketPushServiceImpl.class); + private final String GETH_LOG_PATH = StringUtils.isNotBlank(System.getProperty("logging.path")) ? System.getProperty("logging.path").concat("/").concat("geth.log") + : "/geth.log"; - private Integer openedSessions = 0; + private Integer openedSessions = 0, gethLogSessions = 0; - /** - * Transaction ID -> # of subscribers - */ - private final Map transactionsMap = new LRUMap(500); + /** + * Transaction ID -> # of subscribers + */ + private final Map transactionsMap = new LRUMap(500); - @Autowired(required = false) - private SimpMessagingTemplate template; + @Autowired(required = false) + private SimpMessagingTemplate template; - @Autowired - private GethHttpService geth; + @Autowired + private GethHttpService geth; - @Autowired - private ContractService contractService; + @Autowired + private NodeService nodeService; - @Autowired - private NodeService nodeService; + @Autowired + private BlockService blockService; - @Autowired - private BlockService blockService; + @Autowired + private WebSocketAsyncPushService asyncPushService; - @Autowired - private TransactionService transactionService; + @Autowired + private MetricsBlockListener metricsBlockListener; - @Autowired - private WebSocketAsyncPushService asyncPushService; + @Autowired + private LogTailerListener logListener; - // For tracking status changes - private Node previousNodeStatus; + private Tailer tailer; - // For tracking block changes - private Block previousBlock; + // For tracking status changes + private Node previousNodeStatus; - @Autowired - private MetricsBlockListener metricsBlockListener; + // For tracking block changes + private Block previousBlock; + + @Scheduled(fixedDelay = 1000) + public void pushTxnPerMin() { - @Scheduled(fixedDelay = 1000) - public void pushTxnPerMin() { - if (openedSessions <= 0) { - return; - } - - template.convertAndSend( - "/topic/metrics/txnPerMin", - APIResponse.newSimpleResponse(metricsBlockListener.getTxnPerMin())); - } - - @Scheduled(fixedDelay = 1000) - public void pushBlockPerMin() { - + return; + } + + template.convertAndSend( + "/topic/metrics/txnPerMin", + APIResponse.newSimpleResponse(metricsBlockListener.getTxnPerMin())); + } + + @Scheduled(fixedDelay = 1000) + public void pushBlockPerMin() { + if (openedSessions <= 0) { - return; - } - - template.convertAndSend( - "/topic/metrics/blocksPerMin", - APIResponse.newSimpleResponse(metricsBlockListener.getBlockPerMin())); - } - - @Override - @Scheduled(fixedDelay = 5000) - public void pushNodeStatus() throws APIException { - if (openedSessions <= 0) { - return; - } - - if (!geth.isRunning()) { - // send back a node-down response - Node node = new Node(); - node.setStatus(NodeService.NODE_NOT_RUNNING_STATUS); - template.convertAndSend(NODE_TOPIC, - new APIResponse().data(new APIData(node.getId(), "node", node))); - return; - } + return; + } + + template.convertAndSend( + "/topic/metrics/blocksPerMin", + APIResponse.newSimpleResponse(metricsBlockListener.getBlockPerMin())); + } + + @Override + @Scheduled(fixedDelay = 5000) + public void pushNodeStatus() throws APIException { + if (openedSessions <= 0) { + return; + } + + if (!geth.isRunning()) { + // send back a node-down response + Node node = new Node(); + node.setStatus(NodeService.NODE_NOT_RUNNING_STATUS); + template.convertAndSend(NODE_TOPIC, + new APIResponse().data(new APIData(node.getId(), "node", node))); + return; + } Node node = nodeService.get(); @@ -127,14 +129,14 @@ public void pushNodeStatus() throws APIException { APIResponse apiResponse = new APIResponse(); apiResponse.setData(new APIData(node.getId(), "node", node)); template.convertAndSend(NODE_TOPIC, apiResponse); - } + } - @Override - @Scheduled(fixedDelay = 5000) - public void pushLatestBlocks() throws APIException { - if (openedSessions <= 0 || !geth.isRunning()) { - return; - } + @Override + @Scheduled(fixedDelay = 5000) + public void pushLatestBlocks() throws APIException { + if (openedSessions <= 0 || !geth.isRunning()) { + return; + } Block block = blockService.get(null, null, "latest"); @@ -154,38 +156,46 @@ public void pushLatestBlocks() throws APIException { asyncPushService.pushTransactionAsync(transaction, template, null); } } - } + } - @Override - @Scheduled(fixedDelay = 200) - public void pushTransactions() throws APIException { - if (openedSessions <= 0 || transactionsMap.isEmpty() || !geth.isRunning()) { - return; - } + @Override + @Scheduled(fixedDelay = 200) + public void pushTransactions() throws APIException { + if (openedSessions <= 0 || transactionsMap.isEmpty() || !geth.isRunning()) { + return; + } for (String transactionAddress : transactionsMap.keySet()) { asyncPushService.pushTransactionAsync(transactionAddress, template, transactionsMap); } - } + } - @EventListener - public void onSessionConnect(SessionConnectEvent event) { - openedSessions++; - } + @EventListener + public void onSessionConnect(SessionConnectEvent event) { + openedSessions++; + } - @EventListener - public void onSessionDisconnect(SessionDisconnectEvent event) { - if (openedSessions > 0) { + @EventListener + public void onSessionDisconnect(SessionDisconnectEvent event) { + if (openedSessions > 0) { openedSessions--; if (openedSessions <= 0 && !transactionsMap.isEmpty()) { transactionsMap.clear(); } - } - } + } + if (gethLogSessions > 0) { + gethLogSessions--; + if (gethLogSessions <= 0) { + LOG.info("Stopping Tailer"); + tailer.stop(); + } + } + } - @EventListener + @EventListener public void onSessionUnsubscribe(SessionUnsubscribeEvent event) { String dest = StompHeaderAccessor.wrap(event.getMessage()).getSubscriptionId(); + if (StringUtils.isBlank(dest)) { return; } @@ -193,18 +203,18 @@ public void onSessionUnsubscribe(SessionUnsubscribeEvent event) { LOG.debug("Unsubscribed: " + dest); if (dest.startsWith(TRANSACTION_TOPIC)) { - String transactionKey = dest.substring(dest.lastIndexOf("/") + 1); - Integer subscribers; - - if (transactionsMap.containsKey(transactionKey)) { - subscribers = transactionsMap.get(transactionKey); - subscribers--; - if (subscribers <= 0) { - transactionsMap.remove(transactionKey); - } else { - transactionsMap.put(transactionKey, subscribers); - } - } + String transactionKey = dest.substring(dest.lastIndexOf("/") + 1); + Integer subscribers; + + if (transactionsMap.containsKey(transactionKey)) { + subscribers = transactionsMap.get(transactionKey); + subscribers--; + if (subscribers <= 0) { + transactionsMap.remove(transactionKey); + } else { + transactionsMap.put(transactionKey, subscribers); + } + } } } @@ -217,30 +227,47 @@ public void onSessionSubscribe(SessionSubscribeEvent event) { LOG.debug("Subscribed: " + dest); - if (dest.startsWith(TRANSACTION_TOPIC)) { - String transactionKey = dest.substring(dest.lastIndexOf("/") + 1); - if (transactionKey.contentEquals("all")) { - return; // special topic - } - - if (transactionsMap.containsKey(transactionKey)) { - Integer subscribers = transactionsMap.get(transactionKey); - transactionsMap.put(transactionKey, subscribers++); - } else { - transactionsMap.put(transactionKey, 1); - } - } - - // send status as soon as there is a (new) subscription - // this can cause the message to be sent multiple times if there are - // multiple subscribers on this topic (once per subscriber) - if (dest.startsWith(NODE_TOPIC)) { - try { - previousNodeStatus = null; // force push - pushNodeStatus(); - } catch (APIException e) { + if (dest.startsWith(GETH_LOG_TOPIC)) { + gethLogSessions++; + if (gethLogSessions == 1) { + LOG.info("Starting Tailier"); + tailer = Tailer.create(new File(GETH_LOG_PATH), logListener, 1); + tailer.run(); + } + if (openedSessions > 1) { + openedSessions--; + } + } else if (dest.startsWith(TRANSACTION_TOPIC)) { + String transactionKey = dest.substring(dest.lastIndexOf("/") + 1); + if (transactionKey.contentEquals("all")) { + return; // special topic + } + + if (transactionsMap.containsKey(transactionKey)) { + Integer subscribers = transactionsMap.get(transactionKey); + transactionsMap.put(transactionKey, subscribers++); + } else { + transactionsMap.put(transactionKey, 1); + } + + // send status as soon as there is a (new) subscription + // this can cause the message to be sent multiple times if there are + // multiple subscribers on this topic (once per subscriber) + if (dest.startsWith(NODE_TOPIC)) { + try { + previousNodeStatus = null; // force push + pushNodeStatus(); + } catch (APIException e) { + } } } + } + @PreDestroy + protected void destroyTailer() { + if (null != tailer) { + tailer.stop(); + } + } } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/task/BlockchainInitializerTask.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/task/BlockchainInitializerTask.java index 2e06db27..7437b529 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/task/BlockchainInitializerTask.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/service/task/BlockchainInitializerTask.java @@ -98,7 +98,8 @@ private void syncWalletDb() { } /** - * Deploy ContractRegistry (and SimpleStorage) if they don't already exist on the chain + * Deploy ContractRegistry (and SimpleStorage) if they don't already exist + * on the chain */ private void deployContractRegistry() { @@ -132,7 +133,7 @@ private void deployContractRegistry() { String binaryCode = (String) contractRes.get("_result"); if (binaryCode.contentEquals("0x")) { - LOG.warn("eth_getCode for " + contractRegistryAddress + " returned 0x"); + LOG.warn("eth_getCode for " + contractRegistryAddress + " returned 0x"); } else { // got code, contract exists @@ -149,7 +150,7 @@ private void deployContractRegistry() { LOG.info("ContractRegistry not found on chain"); try { - nodeService.update(null, null, null, true, null, null); // make sure mining is enabled + nodeService.update(null); // make sure mining is enabled LOG.info("Deploying ContractRegistry to chain"); contractRegistry.deploy(); @@ -158,7 +159,6 @@ private void deployContractRegistry() { LOG.error("Error deploying ContractRegistry to chain: " + e.getMessage(), e); } - LOG.info("Deploying sample contract (SimpleStorage) to chain"); try { String code = FileUtils.readClasspathFile("contracts/SimpleStorage.sol"); @@ -174,13 +174,12 @@ private void deployContractRegistry() { /** * Get the shared contract registry address, if configured * - * @return String shared registry address + * @return String shared registry address */ private String getSharedNetworkConfig() { // TODO this is a temp solution to the problem of sharing the ContractRegistry // address among multiple Cakeshop nodes running on the same machine. - File fSharedConfig = CakeshopUtils.getSharedNetworkConfigFile(); if (fSharedConfig == null) { return null; @@ -207,5 +206,4 @@ private String getSharedNetworkConfig() { return null; } - } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/util/MemoryUtils.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/util/MemoryUtils.java new file mode 100644 index 00000000..dea0d0c3 --- /dev/null +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/util/MemoryUtils.java @@ -0,0 +1,20 @@ +package com.jpmorgan.cakeshop.util; + +import com.sun.management.OperatingSystemMXBean; +import java.lang.management.ManagementFactory; + +public class MemoryUtils { + + public static Long getMemoryData(Boolean isFreeMemory) { + + OperatingSystemMXBean osMBean + = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); + if (isFreeMemory) { + return osMBean.getFreePhysicalMemorySize() / 1024; + } else { + return osMBean.getTotalPhysicalMemorySize() / 1024; + } + + } + +} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/util/ProcessUtils.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/util/ProcessUtils.java index deee83cd..fc6bfb32 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/util/ProcessUtils.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/util/ProcessUtils.java @@ -11,6 +11,7 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Field; import java.util.List; @@ -39,7 +40,6 @@ public static ProcessBuilder createProcessBuilder(GethConfigBean gethConfig, Lis env.put("PATH", prefixPathStr(gethConfig.getBinPath() + File.pathSeparator + solcDir, env.get("PATH"))); if (LOG.isDebugEnabled()) { - LOG.debug("PATH=" + env.get("PATH")); LOG.debug(Joiner.on(" ").join(builder.command())); } @@ -63,7 +63,8 @@ private static String prefixPathStr(String newPath, String currPath) { } /** - * Check if the given PID is running (supports both Unix and Windows systems) + * Check if the given PID is running (supports both Unix and Windows + * systems) * * @param pid * @return @@ -160,6 +161,45 @@ public static Integer getUnixPID(Process process) { return null; } + public static String getUnixPidByName(String processName) { + String[] command = new String[]{"/bin/sh", "-c", + " ps -ef | grep ".concat(processName)}; + ProcessBuilder builder = new ProcessBuilder(command); + try { + Process process = builder.start(); + try (InputStream input = process.getInputStream()) { + byte[] b = new byte[16]; + input.read(b, 0, b.length); + String[] commandLineresult = new String(b).split("\\s+"); + if (SystemUtils.IS_OS_MAC_OSX || SystemUtils.IS_OS_MAC) { + return commandLineresult[2]; + } else if (SystemUtils.IS_OS_LINUX || SystemUtils.IS_OS_UNIX) { + return commandLineresult[1]; + } + } + } catch (IOException ex) { + LOG.error(ex.getMessage()); + } + return null; + } + + //TODO: Test on Windows + public static String getWinPidByName(String processName) { + String[] command = new String[]{"TASKLIST /FI \"USERNAME ne NT AUTHORITY\\SYSTEM\" | findstr ".concat(processName)}; + ProcessBuilder builder = new ProcessBuilder(command); + try { + Process process = builder.start(); + try (InputStream input = process.getInputStream()) { + byte[] b = new byte[16]; + input.read(b, 0, b.length); + return new String(b).split("\\s+")[1]; + } + } catch (IOException ex) { + LOG.error(ex.getMessage()); + } + return null; + } + public static Integer getWinPID(Process proc) { if (proc.getClass().getName().equals("java.lang.Win32Process") || proc.getClass().getName().equals("java.lang.ProcessImpl")) { @@ -223,5 +263,4 @@ public static boolean ensureFileIsExecutable(String filename) { return false; } - } diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/util/StreamGobbler.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/util/StreamGobbler.java index ec1c970c..34408c4a 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/util/StreamGobbler.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/util/StreamGobbler.java @@ -13,9 +13,9 @@ public class StreamGobbler extends Thread { private static final Logger LOG = LoggerFactory.getLogger(StreamGobbler.class); - private InputStream stream; - private StringBuilderWriter sw; - private Charset encoding; + private final InputStream stream; + private final StringBuilderWriter sw; + private final Charset encoding; public static StreamGobbler create(InputStream stream) { StreamGobbler sg = new StreamGobbler(stream); @@ -47,4 +47,4 @@ public void run() { public String getString() { return sw.toString(); } -} \ No newline at end of file +} diff --git a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/util/StreamLogAdapter.java b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/util/StreamLogAdapter.java index 80277f28..d37aaacf 100644 --- a/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/util/StreamLogAdapter.java +++ b/cakeshop-api/src/main/java/com/jpmorgan/cakeshop/util/StreamLogAdapter.java @@ -10,7 +10,7 @@ public class StreamLogAdapter extends InterruptibleExecutionThreadService { private final Logger logger; - private BufferedReader reader; + private final BufferedReader reader; public StreamLogAdapter(Logger logger, InputStream stream) { super(); diff --git a/cakeshop-api/src/main/resources/config/application-local.properties b/cakeshop-api/src/main/resources/config/application-local.properties index c10d6bd4..f7ec420a 100644 --- a/cakeshop-api/src/main/resources/config/application-local.properties +++ b/cakeshop-api/src/main/resources/config/application-local.properties @@ -18,7 +18,25 @@ geth.cors.enabled=false contract.poll.delay.millis=5000 geth.db.enabled=true geth.params.extra= +geth.unlock.timeout=1000 contract.registry.addr= +geth.cred1=admin +geth.cred2=$2a$10$dbGiTnfK/w8MhcpIj3XgROYXRsFMlEYJRWoUYArkr8aSPypUFV25G +#quorum specific properties start +#bootnode hex +geth.bootnode.key= +#bootnode address +geth.bootnode.address= +#bootnodes list +geth.bootnodes.list= +#blockmaker account id +geth.block.maker=0xca843569e3427144cead5e4d5999a3d0ccf92b8e +#voter account list +geth.vote.account=0x0fbdc686b912d7722dc86510934589e0aaf3b55a + +geth.vote.contract.addr=0x0000000000000000000000000000000000000020 + +#quorum specific properties end # threadpools and queues cakeshop.mvc.async.pool.threads.core=250 @@ -50,3 +68,5 @@ server.compression.mime-types=application/json,application/xml,text/html,text/xm # spring boot actuator management.context-path=/manage endpoints.actuator.enabled=true + + diff --git a/cakeshop-api/src/main/resources/config/application.properties b/cakeshop-api/src/main/resources/config/application.properties index 2aca5790..a0b3fada 100644 --- a/cakeshop-api/src/main/resources/config/application.properties +++ b/cakeshop-api/src/main/resources/config/application.properties @@ -46,3 +46,7 @@ server.compression.mime-types=application/json,application/xml,text/html,text/xm # spring boot actuator management.context-path=/manage endpoints.actuator.enabled=true + + + + diff --git a/cakeshop-api/src/main/resources/contracts/BlockVoting.sol.json b/cakeshop-api/src/main/resources/contracts/BlockVoting.sol.json new file mode 100644 index 00000000..dced21ce --- /dev/null +++ b/cakeshop-api/src/main/resources/contracts/BlockVoting.sol.json @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"threshold","type":"uint256"}],"name":"setVoteThreshold","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"removeBlockMaker","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"voterCount","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"canCreateBlocks","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"voteThreshold","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"height","type":"uint256"}],"name":"getCanonHash","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"height","type":"uint256"},{"name":"hash","type":"bytes32"}],"name":"vote","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"addBlockMaker","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"removeVoter","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"height","type":"uint256"},{"name":"n","type":"uint256"}],"name":"getEntry","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[{"name":"addr","type":"address"}],"name":"isVoter","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"canVote","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"blockMakerCount","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"getSize","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"addr","type":"address"}],"name":"isBlockMaker","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"addVoter","outputs":[],"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"blockNumber","type":"uint256"},{"indexed":false,"name":"blockHash","type":"bytes32"}],"name":"Vote","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"","type":"address"}],"name":"AddVoter","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"","type":"address"}],"name":"RemovedVoter","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"","type":"address"}],"name":"AddBlockMaker","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"","type":"address"}],"name":"RemovedBlockMaker","type":"event"}] \ No newline at end of file diff --git a/cakeshop-api/src/main/resources/contracts/ContractRegistry.sol b/cakeshop-api/src/main/resources/contracts/ContractRegistry.sol index dc0279e5..8bb2250d 100644 --- a/cakeshop-api/src/main/resources/contracts/ContractRegistry.sol +++ b/cakeshop-api/src/main/resources/contracts/ContractRegistry.sol @@ -1,3 +1,4 @@ +pragma solidity ^0.4.9; contract ContractRegistry { address public owner; @@ -39,16 +40,19 @@ contract ContractRegistry _created_date = c.created_date; } + /* function getByName(string name) returns (address _id, string _name, string _abi, string _code, string _code_type) { // TODO } + */ function listAddrs() returns (address[] _addresses) { return addrs; } - +/* function listByOwner() { // TODO } +*/ } diff --git a/cakeshop-api/src/main/resources/contracts/SimpleStorage.sol b/cakeshop-api/src/main/resources/contracts/SimpleStorage.sol index e92c0dd6..bb6aee6f 100644 --- a/cakeshop-api/src/main/resources/contracts/SimpleStorage.sol +++ b/cakeshop-api/src/main/resources/contracts/SimpleStorage.sol @@ -1,4 +1,4 @@ - +pragma solidity ^0.4.9; contract SimpleStorage { uint public storedData; diff --git a/cakeshop-api/src/main/resources/geth/genesis/genesis_block.json b/cakeshop-api/src/main/resources/geth/genesis/genesis_block.json index 50241b88..1ce2f071 100644 --- a/cakeshop-api/src/main/resources/geth/genesis/genesis_block.json +++ b/cakeshop-api/src/main/resources/geth/genesis/genesis_block.json @@ -1,21 +1,59 @@ { - "nonce": "0xdeadbeefdeadbeef", - "timestamp": "0x00", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "extraData": "0x686f727365", - "gasLimit": "0x08000000", - "difficulty": "0x0400", - "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase": "0x3333333333333333333333333333333333333333", - "alloc": { - "0x2e219248f44546d966808cdd20cb6c36df6efa82": { - "Balance": "1606938044258990275541962092341162602522202993782792835301376" - }, - "0xcd5b17da5ad176905c12fc85ce43ec287ab55363": { - "Balance": "1606938044258990275541962092341162602522202993782792835301376" - }, - "0x50bb02281de5f00cc1f1dd5a6692da3fa9b2d912": { - "Balance": "1606938044258990275541962092341162602522202993782792835301376" + "alloc": { + "0x0000000000000000000000000000000000000020": { + "code": "606060405236156100c45760e060020a60003504631290948581146100c9578063284d163c146100f957806342169e4814610130578063488099a6146101395780634fe437d514610154578063559c390c1461015d57806368bb8bb61461025d57806372a571fc146102c857806386c1ff681461036957806398ba676d146103a0578063a7771ee31461040b578063adfaa72e14610433578063cf5289851461044e578063de8fa43114610457578063e814d1c71461046d578063f4ab9adf14610494575b610002565b610548600435600160a060020a03331660009081526003602052604090205460ff16156100c45760018190555b50565b610548600435600160a060020a03331660009081526005602052604090205460ff16156100c4576004546001141561055e57610002565b61045b60025481565b61054a60043560056020526000908152604090205460ff1681565b61045b60015481565b61045b60043560006000600060006000600050600186038154811015610002579080526002027f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5630192505b60018301548110156105d75760018301805484916000918490811015610002576000918252602080832090910154835282810193909352604091820181205485825292869052205410801561023257506001805490840180548591600091859081101561000257906000526020600020900160005054815260208101919091526040016000205410155b156102555760018301805482908110156100025760009182526020909120015491505b6001016101a8565b610548600435602435600160a060020a03331660009081526003602052604081205460ff16156100c4578054839010156105e45780548084038101808355908290829080158290116105df576002028160020283600052602060002091820191016105df919061066b565b610548600435600160a060020a03331660009081526005602052604090205460ff16156100c457600160a060020a0381166000908152604090205460ff1615156100f65760406000819020805460ff191660019081179091556004805490910190558051600160a060020a038316815290517f1a4ce6942f7aa91856332e618fc90159f13a340611a308f5d7327ba0707e56859181900360200190a16100f6565b610548600435600160a060020a03331660009081526003602052604090205460ff16156100c4576002546001141561071457610002565b61045b600435602435600060006000600050600185038154811015610002579080526002027f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5630181509050806001016000508381548110156100025750825250602090200154919050565b61054a600435600160a060020a03811660009081526003602052604090205460ff165b919050565b61054a60043560036020526000908152604090205460ff1681565b61045b60045481565b6000545b60408051918252519081900360200190f35b61054a600435600160a060020a03811660009081526005602052604090205460ff1661042e565b610548600435600160a060020a03331660009081526003602052604090205460ff16156100c457600160a060020a03811660009081526003602052604090205460ff1615156100f65760406000818120600160a060020a0384169182905260036020908152815460ff1916600190811790925560028054909201909155825191825291517f0ad2eca75347acd5160276fe4b5dad46987e4ff4af9e574195e3e9bc15d7e0ff929181900390910190a16100f6565b005b604080519115158252519081900360200190f35b600160a060020a03811660009081526005602052604090205460ff16156100f65760406000819020805460ff19169055600480546000190190558051600160a060020a038316815290517f8cee3054364d6799f1c8962580ad61273d9d38ca1ff26516bd1ad23c099a60229181900360200190a16100f6565b509392505050565b505050505b60008054600019850190811015610002578382526002027f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563016020819052604082205490925014156106b8578060010160005080548060010182818154818355818115116106a5578183600052602060002091820191016106a5919061068d565b50506002015b808211156106a157600181018054600080835591825260208220610665918101905b808211156106a1576000815560010161068d565b5090565b5050506000928352506020909120018290555b600082815260208281526040918290208054600101905581514381529081018490528151600160a060020a033316927f3d03ba7f4b5227cdb385f2610906e5bcee147171603ec40005b30915ad20e258928290030190a2505050565b600160a060020a03811660009081526003602052604090205460ff16156100f65760406000819020805460ff19169055600280546000190190558051600160a060020a038316815290517f183393fc5cffbfc7d03d623966b85f76b9430f42d3aada2ac3f3deabc78899e89181900360200190a16100f656", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x02", + + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x04", + "0x29ecdbdf95c7f6ceec92d6150c697aa14abeb0f8595dd58d808842ea237d8494": "0x01", + "0x6aa118c6537572d8b515a9f9154be55a3377a8de7991cd23bf6e5ceb368688e3": "0x01", + "0x50793743212c6f01d326957d7069005b912f8215f10c7536be6b10782c6c44cd": "0x01", + "0x38f6c908c5cc7ca668cec2f476abe61b4dbb1df20f0ad8e07ef5dbf6a2f1ffd4": "0x01", + + "0x0000000000000000000000000000000000000000000000000000000000000004": "0x02", + "0xaca3b76ed4968740c3180dd7fa37f4aa229a2c758a848f53920e9ccb4c4bb74e": "0x01", + "0xd188ba2dc293670542c1befaf7678b0859e5354a0727d1188b2afb6f47fe24d1": "0x01" } - } + + }, + "0xed9d02e382b34818e88b88a309c7fe71e65f419d": { + "balance": "1000000000000000000000000000" + }, + "0xca843569e3427144cead5e4d5999a3d0ccf92b8e": { + "balance": "1000000000000000000000000000" + }, + "0x0fbdc686b912d7722dc86510934589e0aaf3b55a": { + "balance": "1000000000000000000000000000" + }, + "0x9186eb3d20cbd1f5f992a950d808c4495153abd5": { + "balance": "1000000000000000000000000000" + }, + "0x0638e1574728b6d862dd5d3a3e0942c3be47d996": { + "balance": "1000000000000000000000000000" + }, + "0x0000000000000000000000000000000000000050": { + "code": "606060405234610000575b33600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060006001819055505b5b6114ba806100646000396000f30060606040523615610097576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063014bb7fe1461009c57806303d1e5dd1461010e578063105b197714610370578063371eb669146103935780635c82edba146103f057806369dc9ff3146105385780638da5cb5b14610809578063b336ad8314610858578063d9c55a1f14610ad7575b610000565b34610000576100a9610ae6565b60405180806020018281038252838181518152602001915080519060200190602002808383600083146100fb575b8051825260208311156100fb576020820191506020810190506020830392506100d7565b5050509050019250505060405180910390f35b346100005761013f600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610b84565b604051808773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018060200180602001806020018060200186815260200185810385528a8181518152602001915080519060200190808383600083146101d2575b8051825260208311156101d2576020820191506020810190506020830392506101ae565b505050905090810190601f1680156101fe5780820380516001836020036101000a031916815260200191505b50858103845289818151815260200191508051906020019080838360008314610246575b80518252602083111561024657602082019150602081019050602083039250610222565b505050905090810190601f1680156102725780820380516001836020036101000a031916815260200191505b508581038352888181518152602001915080519060200190808383600083146102ba575b8051825260208311156102ba57602082019150602081019050602083039250610296565b505050905090810190601f1680156102e65780820380516001836020036101000a031916815260200191505b5085810382528781815181526020019150805190602001908083836000831461032e575b80518252602083111561032e5760208201915060208101905060208303925061030a565b505050905090810190601f16801561035a5780820380516001836020036101000a031916815260200191505b509a505050505050505050505060405180910390f35b346100005761037d610ec4565b6040518082815260200191505060405180910390f35b34610000576103ae6004808035906020019091905050610eca565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3461000057610536600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091908035906020019091905050610f07565b005b3461000057610569600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050611397565b604051808873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018060200180602001806020018060200186815260200185810385528a8181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156106685780601f1061063d57610100808354040283529160200191610668565b820191906000526020600020905b81548152906001019060200180831161064b57829003601f168201915b50508581038452898181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156106eb5780601f106106c0576101008083540402835291602001916106eb565b820191906000526020600020905b8154815290600101906020018083116106ce57829003601f168201915b505085810383528881815460018160011615610100020316600290048152602001915080546001816001161561010002031660029004801561076e5780601f106107435761010080835404028352916020019161076e565b820191906000526020600020905b81548152906001019060200180831161075157829003601f168201915b50508581038252878181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156107f15780601f106107c6576101008083540402835291602001916107f1565b820191906000526020600020905b8154815290600101906020018083116107d457829003601f168201915b50509b50505050505050505050505060405180910390f35b3461000057610816611415565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34610000576108ad600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190505061143b565b604051808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018060200180602001806020018060200185810385528981815181526020019150805190602001908083836000831461093a575b80518252602083111561093a57602082019150602081019050602083039250610916565b505050905090810190601f1680156109665780820380516001836020036101000a031916815260200191505b508581038452888181518152602001915080519060200190808383600083146109ae575b8051825260208311156109ae5760208201915060208101905060208303925061098a565b505050905090810190601f1680156109da5780820380516001836020036101000a031916815260200191505b50858103835287818151815260200191508051906020019080838360008314610a22575b805182526020831115610a22576020820191506020810190506020830392506109fe565b505050905090810190601f168015610a4e5780820380516001836020036101000a031916815260200191505b50858103825286818151815260200191508051906020019080838360008314610a96575b805182526020831115610a9657602082019150602081019050602083039250610a72565b505050905090810190601f168015610ac25780820380516001836020036101000a031916815260200191505b50995050505050505050505060405180910390f35b3461000057610ae461148b565b005b60206040519081016040528060008152506002805480602002602001604051908101604052809291908181526020018280548015610b7957602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019060010190808311610b2f575b505050505090505b90565b6000602060405190810160405280600081525060206040519081016040528060008152506020604051908101604052806000815250602060405190810160405280600081525060006000600360008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002090508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169650806002018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610cce5780601f10610ca357610100808354040283529160200191610cce565b820191906000526020600020905b815481529060010190602001808311610cb157829003601f168201915b50505050509550806003018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610d6d5780601f10610d4257610100808354040283529160200191610d6d565b820191906000526020600020905b815481529060010190602001808311610d5057829003601f168201915b50505050509450806004018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610e0c5780601f10610de157610100808354040283529160200191610e0c565b820191906000526020600020905b815481529060010190602001808311610def57829003601f168201915b50505050509350806005018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610eab5780601f10610e8057610100808354040283529160200191610eab565b820191906000526020600020905b815481529060010190602001808311610e8e57829003601f168201915b50505050509250806006015491505b5091939550919395565b60015481565b600281815481101561000057906000526020600020900160005b915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600160008154600101919050819055600281815481835581811511610f5857818360005260206000209182019101610f5791905b80821115610f53576000816000905550600101610f3b565b5090565b5b50505050856002600160015403815481101561000057906000526020600020900160005b6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060e0604051908101604052808773ffffffffffffffffffffffffffffffffffffffff1681526020013373ffffffffffffffffffffffffffffffffffffffff16815260200186815260200185815260200184815260200183815260200182815250600360008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060208201518160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506040820151816002019080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061113757805160ff1916838001178555611165565b82800160010185558215611165579182015b82811115611164578251825591602001919060010190611149565b5b50905061118a91905b8082111561118657600081600090555060010161116e565b5090565b50506060820151816003019080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106111de57805160ff191683800117855561120c565b8280016001018555821561120c579182015b8281111561120b5782518255916020019190600101906111f0565b5b50905061123191905b8082111561122d576000816000905550600101611215565b5090565b50506080820151816004019080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061128557805160ff19168380011785556112b3565b828001600101855582156112b3579182015b828111156112b2578251825591602001919060010190611297565b5b5090506112d891905b808211156112d45760008160009055506001016112bc565b5090565b505060a0820151816005019080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061132c57805160ff191683800117855561135a565b8280016001018555821561135a579182015b8281111561135957825182559160200191906001019061133e565b5b50905061137f91905b8082111561137b576000816000905550600101611363565b5090565b505060c082015181600601559050505b505050505050565b60036020528060005260406000206000915090508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169080600201908060030190806004019080600501908060060154905087565b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600060206040519081016040528060008152506020604051908101604052806000815250602060405190810160405280600081525060206040519081016040528060008152505b91939590929450565b5b5600a165627a7a72305820e6c2f60a367ff9ef3746b39b6772828b1d3a4cb68ca71e82efb0e46538ec74150029" + }, + "0x2e219248f44546d966808cdd20cb6c36df6efa82": { + "balance": "1606938044258990275541962092341162602522202993782792835301376" + }, + "0xcd5b17da5ad176905c12fc85ce43ec287ab55363": { + "balance": "1606938044258990275541962092341162602522202993782792835301376" + }, + "0x50bb02281de5f00cc1f1dd5a6692da3fa9b2d912": { + "balance": "1606938044258990275541962092341162602522202993782792835301376" + } + }, + "coinbase": "0x0000000000000000000000000000000000000000", + "config": { + "homesteadBlock": 0 + }, + "difficulty": "0x0", + "extraData": "0x", + "gasLimit": "0x2FEFD800", + "mixhash": "0x00000000000000000000000000000000000000647572616c65787365646c6578", + "nonce": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "0x00" } diff --git a/cakeshop-api/src/main/resources/geth/genesis/keystore/UTC--2017-02-02T15-56-30.114820686Z--0638e1574728b6d862dd5d3a3e0942c3be47d996 b/cakeshop-api/src/main/resources/geth/genesis/keystore/UTC--2017-02-02T15-56-30.114820686Z--0638e1574728b6d862dd5d3a3e0942c3be47d996 new file mode 100644 index 00000000..f0ac8ffb --- /dev/null +++ b/cakeshop-api/src/main/resources/geth/genesis/keystore/UTC--2017-02-02T15-56-30.114820686Z--0638e1574728b6d862dd5d3a3e0942c3be47d996 @@ -0,0 +1 @@ +{"address":"0638e1574728b6d862dd5d3a3e0942c3be47d996","crypto":{"cipher":"aes-128-ctr","ciphertext":"d8119d67cb134bc65c53506577cfd633bbbf5acca976cea12dd507de3eb7fd6f","cipherparams":{"iv":"76e88f3f246d4bf9544448d1a27b06f4"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"6d05ade3ee96191ed73ea019f30c02cceb6fc0502c99f706b7b627158bfc2b0a"},"mac":"b39c2c56b35958c712225970b49238fb230d7981ef47d7c33c730c363b658d06"},"id":"00307b43-53a3-4e03-9d0c-4fcbb3da29df","version":3} diff --git a/cakeshop-api/src/main/resources/geth/genesis/keystore/UTC--2017-02-02T15-56-30.114820686Z--0fbdc686b912d7722dc86510934589e0aaf3b55a b/cakeshop-api/src/main/resources/geth/genesis/keystore/UTC--2017-02-02T15-56-30.114820686Z--0fbdc686b912d7722dc86510934589e0aaf3b55a new file mode 100644 index 00000000..6e460436 --- /dev/null +++ b/cakeshop-api/src/main/resources/geth/genesis/keystore/UTC--2017-02-02T15-56-30.114820686Z--0fbdc686b912d7722dc86510934589e0aaf3b55a @@ -0,0 +1 @@ +{"address":"0fbdc686b912d7722dc86510934589e0aaf3b55a","crypto":{"cipher":"aes-128-ctr","ciphertext":"6b2c72c6793f3da8185e36536e02f574805e41c18f551f24b58346ef4ecf3640","cipherparams":{"iv":"582f27a739f39580410faa108d5cc59f"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"1a79b0db3f8cb5c2ae4fa6ccb2b5917ce446bd5e42c8d61faeee512b97b4ad4a"},"mac":"cecb44d2797d6946805d5d744ff803805477195fab1d2209eddc3d1158f2e403"},"id":"f7292e90-af71-49af-a5b3-40e8493f4681","version":3} diff --git a/cakeshop-api/src/main/resources/geth/genesis/keystore/UTC--2017-02-02T15-56-30.114820686Z--9186eb3d20cbd1f5f992a950d808c4495153abd5 b/cakeshop-api/src/main/resources/geth/genesis/keystore/UTC--2017-02-02T15-56-30.114820686Z--9186eb3d20cbd1f5f992a950d808c4495153abd5 new file mode 100644 index 00000000..e2600a75 --- /dev/null +++ b/cakeshop-api/src/main/resources/geth/genesis/keystore/UTC--2017-02-02T15-56-30.114820686Z--9186eb3d20cbd1f5f992a950d808c4495153abd5 @@ -0,0 +1 @@ +{"address":"9186eb3d20cbd1f5f992a950d808c4495153abd5","crypto":{"cipher":"aes-128-ctr","ciphertext":"d160a630a39be3ff35556055406d8ff2a635f0535fe298d62ccc812d8f7b3bd5","cipherparams":{"iv":"82fce06bc6e1658a5e81ccef3b753329"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"8d0c486db4c942721f4f5e96d48e9344805d101dad8159962b8a2008ac718548"},"mac":"4a92bda949068968d470320260ae1a825aa22f6a40fb8567c9f91d700c3f7e91"},"id":"bdb3b4f6-d8d0-4b00-8473-e223ef371b5c","version":3} diff --git a/cakeshop-api/src/main/resources/geth/genesis/keystore/UTC--2017-02-02T15-56-30.114820686Z--ca843569e3427144cead5e4d5999a3d0ccf92b8e b/cakeshop-api/src/main/resources/geth/genesis/keystore/UTC--2017-02-02T15-56-30.114820686Z--ca843569e3427144cead5e4d5999a3d0ccf92b8e new file mode 100644 index 00000000..cceb36bf --- /dev/null +++ b/cakeshop-api/src/main/resources/geth/genesis/keystore/UTC--2017-02-02T15-56-30.114820686Z--ca843569e3427144cead5e4d5999a3d0ccf92b8e @@ -0,0 +1 @@ +{"address":"ca843569e3427144cead5e4d5999a3d0ccf92b8e","crypto":{"cipher":"aes-128-ctr","ciphertext":"01d409941ce57b83a18597058033657182ffb10ae15d7d0906b8a8c04c8d1e3a","cipherparams":{"iv":"0bfb6eadbe0ab7ffaac7e1be285fb4e5"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"7b90f455a95942c7c682e0ef080afc2b494ef71e749ba5b384700ecbe6f4a1bf"},"mac":"4cc851f9349972f851d03d75a96383a37557f7c0055763c673e922de55e9e307"},"id":"354e3b35-1fed-407d-a358-889a29111211","version":3} diff --git a/cakeshop-api/src/main/resources/geth/genesis/keystore/UTC--2017-02-02T15-56-30.114820686Z--ed9d02e382b34818e88b88a309c7fe71e65f419d b/cakeshop-api/src/main/resources/geth/genesis/keystore/UTC--2017-02-02T15-56-30.114820686Z--ed9d02e382b34818e88b88a309c7fe71e65f419d new file mode 100644 index 00000000..505ab00a --- /dev/null +++ b/cakeshop-api/src/main/resources/geth/genesis/keystore/UTC--2017-02-02T15-56-30.114820686Z--ed9d02e382b34818e88b88a309c7fe71e65f419d @@ -0,0 +1 @@ +{"address":"ed9d02e382b34818e88b88a309c7fe71e65f419d","crypto":{"cipher":"aes-128-ctr","ciphertext":"4e77046ba3f699e744acb4a89c36a3ea1158a1bd90a076d36675f4c883864377","cipherparams":{"iv":"a8932af2a3c0225ee8e872bc0e462c11"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"8ca49552b3e92f79c51f2cd3d38dfc723412c212e702bd337a3724e8937aff0f"},"mac":"6d1354fef5aa0418389b1a5d1f5ee0050d7273292a1171c51fd02f9ecff55264"},"id":"a65d1ac3-db7e-445d-a1cc-b6c5eeaa05e0","version":3} diff --git a/cakeshop-api/src/main/resources/geth/quorum/constellation/linux/constellation-enclave-keygen b/cakeshop-api/src/main/resources/geth/quorum/constellation/linux/constellation-enclave-keygen new file mode 100755 index 00000000..5f640df4 Binary files /dev/null and b/cakeshop-api/src/main/resources/geth/quorum/constellation/linux/constellation-enclave-keygen differ diff --git a/cakeshop-api/src/main/resources/geth/quorum/constellation/linux/constellation-node b/cakeshop-api/src/main/resources/geth/quorum/constellation/linux/constellation-node new file mode 100755 index 00000000..d97392bd Binary files /dev/null and b/cakeshop-api/src/main/resources/geth/quorum/constellation/linux/constellation-node differ diff --git a/cakeshop-api/src/main/resources/geth/quorum/constellation/mac/constellation-enclave-keygen b/cakeshop-api/src/main/resources/geth/quorum/constellation/mac/constellation-enclave-keygen new file mode 100755 index 00000000..2391acce Binary files /dev/null and b/cakeshop-api/src/main/resources/geth/quorum/constellation/mac/constellation-enclave-keygen differ diff --git a/cakeshop-api/src/main/resources/geth/quorum/constellation/mac/constellation-node b/cakeshop-api/src/main/resources/geth/quorum/constellation/mac/constellation-node new file mode 100755 index 00000000..a0a6f5b2 Binary files /dev/null and b/cakeshop-api/src/main/resources/geth/quorum/constellation/mac/constellation-node differ diff --git a/cakeshop-api/src/main/resources/geth/quorum/linux/geth b/cakeshop-api/src/main/resources/geth/quorum/linux/geth new file mode 100755 index 00000000..5f9732a2 Binary files /dev/null and b/cakeshop-api/src/main/resources/geth/quorum/linux/geth differ diff --git a/cakeshop-api/src/main/resources/geth/quorum/mac/geth b/cakeshop-api/src/main/resources/geth/quorum/mac/geth new file mode 100755 index 00000000..67e0833a Binary files /dev/null and b/cakeshop-api/src/main/resources/geth/quorum/mac/geth differ diff --git a/cakeshop-api/src/main/resources/geth/solc/package.json b/cakeshop-api/src/main/resources/geth/solc/package.json index 841041fe..102f691b 100644 --- a/cakeshop-api/src/main/resources/geth/solc/package.json +++ b/cakeshop-api/src/main/resources/geth/solc/package.json @@ -5,6 +5,6 @@ "author": "", "license": "Apache2", "dependencies": { - "solc-cli": "github:jpmorganchase/solc-cli" + "solc-cakeshop-cli": "0.3.5" } } diff --git a/cakeshop-api/src/main/resources/log4j2.xml b/cakeshop-api/src/main/resources/log4j2.xml new file mode 100644 index 00000000..8c5e6fa2 --- /dev/null +++ b/cakeshop-api/src/main/resources/log4j2.xml @@ -0,0 +1,56 @@ + + + + + + + [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n + > + + + + + + [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n + + + + + + + [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cakeshop-api/src/main/webapp/api.html b/cakeshop-api/src/main/webapp/api.html index a7ea0c52..31f7914f 100644 --- a/cakeshop-api/src/main/webapp/api.html +++ b/cakeshop-api/src/main/webapp/api.html @@ -1,110 +1,83 @@ - - Swagger UI + + Swagger UI - - - - - + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - + - - - - + + function log() { + if ('console' in window) { + console.log.apply(console, arguments); + } + } + }); + -
 
-
-
-
+
 
+
+
diff --git a/cakeshop-api/src/main/webapp/api/swagger.json b/cakeshop-api/src/main/webapp/api/swagger.json index f3a2c5a9..93a83047 100644 --- a/cakeshop-api/src/main/webapp/api/swagger.json +++ b/cakeshop-api/src/main/webapp/api/swagger.json @@ -13,14 +13,11 @@ "tags": [ { "name": "node" - }, - { + }, { "name": "block" - }, - { + }, { "name": "transaction" - }, - { + }, { "name": "contract" } ], @@ -45,15 +42,13 @@ "type": "string", "required": false, "in": "body" - }, - { + }, { "name": "number", "description": "Block number to retrieve", "type": "integer", "required": false, "in": "body" - }, - { + }, { "name": "tag", "description": "One of \"earliest\", \"latest\" or \"pending\"", "type": "string", @@ -67,14 +62,12 @@ "parameters": { "body": "{\n \"number\": 0\n}\n" } - }, - { + }, { "name": "Get block by ID", "parameters": { "body": "{\n \"id \"0x8564f939768d96f6fc0ba1334ed083ab6538da76f17b6d264082cb69aadc7b4c\"\n}\n" } - }, - { + }, { "name": "Get latest block", "parameters": { "body": "{\n \"tag\": \"latest\"\n}\n" @@ -169,8 +162,7 @@ "description": "Contract source code", "required": true, "in": "body" - }, - { + }, { "name": "code_type", "type": "string", "in": "body", @@ -180,8 +172,7 @@ "enum": [ "solidity" ] - }, - { + }, { "name": "optimize", "type": "boolean", "required": false, @@ -286,8 +277,7 @@ "type": "string", "description": "Contract source code", "in": "body" - }, - { + }, { "name": "code_type", "type": "string", "description": "Type of code being submitted", @@ -297,8 +287,7 @@ "enum": [ "solidity" ] - }, - { + }, { "name": "args", "type": "array", "required": false, @@ -307,15 +296,13 @@ "items": { "type": "object" } - }, - { + }, { "name": "binary", "type": "string", "required": false, "in": "body", "description": "If multiple contracts are specified in the source code, a specific binary to deploy can be passed here" - }, - { + }, { "name": "optimize", "type": "boolean", "required": false, @@ -416,9 +403,9 @@ ], "parameters": [ { - "name": "id", + "name": "address", "type": "string", - "description": "ID (address) of Contract to retrieve", + "description": "Address (id) of Contract to retrieve", "example": "0x01f999deef030a0f3c55cec46e6693c06c6b01f8", "in": "body" } @@ -600,21 +587,19 @@ ], "parameters": [ { - "name": "id", + "name": "address", "type": "string", "required": true, - "description": "Contract ID (address hash) to read from", + "description": "Contract address (id hash) to read from", "example": "", "in": "body" - }, - { + }, { "name": "method", "type": "string", "required": true, "description": "Method name to call", "in": "body" - }, - { + }, { "name": "args", "type": "array", "required": false, @@ -623,8 +608,7 @@ "items": { "type": "object" } - }, - { + }, { "name": "blockNumber", "type": "integer", "required": false, @@ -713,21 +697,19 @@ ], "parameters": [ { - "name": "id", + "name": "address", "type": "string", "required": true, - "description": "Contract ID (address hash) to transact with", + "description": "Contract address (id hash) to transact with", "example": "", "in": "body" - }, - { + }, { "name": "method", "type": "string", "required": true, "in": "body", "description": "Method name to call" - }, - { + }, { "name": "args", "type": "array", "required": false, @@ -1294,41 +1276,60 @@ "required": false, "description": "Set log level (0-6, 6 is most verbose)", "in": "body" - }, - { + }, { "name": "networkId", "type": "integer", "required": false, "description": "Network ID (any integer value; node will restart)", "in": "body" - }, - { + }, { "name": "identity", "type": "string", "required": false, "description": "Change node identity (node will restart)", "in": "body" - }, - { + }, { "name": "committingTransactions", "type": "boolean", "required": false, "description": "Whether or not the node should commit incoming transactions (i.e., toggle mining)", "in": "body" - }, - { + }, { "name": "extraParams", "type": "string", "required": false, "description": "Extra parameters to pass to the node (startup flags; node will restart)", "in": "body" - }, - { + }, { "name": "genesisBlock", "type": "string", "required": false, "description": "Genesis block to use for the chain (modifying this will reset the chain)", "in": "body" + }, { + "name": "voterAccount", + "type": "string", + "required": false, + "description": "[Quorum only] Node account to set as a voter", + "in": "body" + }, { + "name": "blockMakerAccount", + "type": "string", + "required": false, + "description": "[Quorum only] Node account to set as a block maker", + "in": "body" + }, { + "name": "minBlockTime", + "type": "integer", + "required": false, + "description": "[Quorum only] Block creation strategy, minimum time until block generation (seconds) ", + "in": "body" + }, { + "name": "maxBlockTime", + "type": "integer", + "required": false, + "description": "[Quorum only] Block creation strategy, maximum time until block generation (seconds)", + "in": "body" } ], "responses": { @@ -1392,6 +1393,243 @@ } } }, + + + "/node/constellation/stop": { + "post": { + "tags": [ + "node" + ], + "description": "[Quorum only] Stops constellation node from running. May cause node to restart.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "Success" + }, + "4xx": { + "description": "Error with the request", + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/definitions/APIError" + } + }, + "meta": { + "type": "object", + "description": "Extra metadata, such as API version" + } + } + } + }, + "5xx": { + "description": "Error processing the request", + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/definitions/APIError" + } + }, + "meta": { + "type": "object", + "description": "Extra metadata, such as API version" + } + } + } + } + } + } + }, + + "/node/constellation/start": { + "post": { + "tags": [ + "node" + ], + "description": "[Quorum only] Starts constellation node if its currently not running. May cause node to restart.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "Success" + }, + "4xx": { + "description": "Error with the request", + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/definitions/APIError" + } + }, + "meta": { + "type": "object", + "description": "Extra metadata, such as API version" + } + } + } + }, + "5xx": { + "description": "Error processing the request", + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/definitions/APIError" + } + }, + "meta": { + "type": "object", + "description": "Extra metadata, such as API version" + } + } + } + } + } + } + }, + + + "/node/constellation/list": { + "post": { + "tags": [ + "node" + ], + "description": "[Quorum only] Returns Constellation configuration", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "Success" + }, + "4xx": { + "description": "Error with the request", + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/definitions/APIError" + } + }, + "meta": { + "type": "object", + "description": "Extra metadata, such as API version" + } + } + } + }, + "5xx": { + "description": "Error processing the request", + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/definitions/APIError" + } + }, + "meta": { + "type": "object", + "description": "Extra metadata, such as API version" + } + } + } + } + } + } + }, + + "/node/constellation/add": { + "post": { + "tags": [ + "node" + ], + "description": "[Quorum only] Adds a constellation node's url for local node to mesh with.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "constellationNode", + "description": "FQDN URL of the destination node", + "type": "string", + "required": true, + "in": "body" + } + ], + "responses": { + "200": { + "description": "Success" + }, + "4xx": { + "description": "Error with the request", + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/definitions/APIError" + } + }, + "meta": { + "type": "object", + "description": "Extra metadata, such as API version" + } + } + } + }, + "5xx": { + "description": "Error processing the request", + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/definitions/APIError" + } + }, + "meta": { + "type": "object", + "description": "Extra metadata, such as API version" + } + } + } + } + } + } + }, + + "/wallet/create": { "post": { "tags": [ diff --git a/cakeshop-api/src/main/webapp/css/codemirror.css b/cakeshop-api/src/main/webapp/css/codemirror.css new file mode 100644 index 00000000..3543523e --- /dev/null +++ b/cakeshop-api/src/main/webapp/css/codemirror.css @@ -0,0 +1,334 @@ +/* BASICS */ + +.CodeMirror { + /* Set height, width, borders, and global font properties here */ + font-family: monospace; + height: 300px; + color: black; +} + +/* PADDING */ + +.CodeMirror-lines { + padding: 4px 0; /* Vertical padding around content */ +} +.CodeMirror pre { + padding: 0 4px; /* Horizontal padding of content */ +} + +.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + background-color: white; /* The little square between H and V scrollbars */ +} + +/* GUTTER */ + +.CodeMirror-gutters { + border-right: 1px solid #ddd; + background-color: #f7f7f7; + white-space: nowrap; +} +.CodeMirror-linenumbers {} +.CodeMirror-linenumber { + padding: 0 3px 0 5px; + min-width: 20px; + text-align: right; + color: #999; + white-space: nowrap; +} + +.CodeMirror-guttermarker { color: black; } +.CodeMirror-guttermarker-subtle { color: #999; } + +/* CURSOR */ + +.CodeMirror-cursor { + border-left: 1px solid black; + border-right: none; + width: 0; +} +/* Shown when moving in bi-directional text */ +.CodeMirror div.CodeMirror-secondarycursor { + border-left: 1px solid silver; +} +.cm-fat-cursor .CodeMirror-cursor { + width: auto; + border: 0; + background: #7e7; +} +.cm-fat-cursor div.CodeMirror-cursors { + z-index: 1; +} + +.cm-animate-fat-cursor { + width: auto; + border: 0; + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; + background-color: #7e7; +} +@-moz-keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} +@-webkit-keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} +@keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} + +/* Can style cursor different in overwrite (non-insert) mode */ +.CodeMirror-overwrite .CodeMirror-cursor {} + +.cm-tab { display: inline-block; text-decoration: inherit; } + +.CodeMirror-ruler { + border-left: 1px solid #ccc; + position: absolute; +} + +/* DEFAULT THEME */ + +.cm-s-default .cm-header {color: blue;} +.cm-s-default .cm-quote {color: #090;} +.cm-negative {color: #d44;} +.cm-positive {color: #292;} +.cm-header, .cm-strong {font-weight: bold;} +.cm-em {font-style: italic;} +.cm-link {text-decoration: underline;} +.cm-strikethrough {text-decoration: line-through;} + +.cm-s-default .cm-keyword {color: #708;} +.cm-s-default .cm-atom {color: #219;} +.cm-s-default .cm-number {color: #164;} +.cm-s-default .cm-def {color: #00f;} +.cm-s-default .cm-variable, +.cm-s-default .cm-punctuation, +.cm-s-default .cm-property, +.cm-s-default .cm-operator {} +.cm-s-default .cm-variable-2 {color: #05a;} +.cm-s-default .cm-variable-3 {color: #085;} +.cm-s-default .cm-comment {color: #a50;} +.cm-s-default .cm-string {color: #a11;} +.cm-s-default .cm-string-2 {color: #f50;} +.cm-s-default .cm-meta {color: #555;} +.cm-s-default .cm-qualifier {color: #555;} +.cm-s-default .cm-builtin {color: #30a;} +.cm-s-default .cm-bracket {color: #997;} +.cm-s-default .cm-tag {color: #170;} +.cm-s-default .cm-attribute {color: #00c;} +.cm-s-default .cm-hr {color: #999;} +.cm-s-default .cm-link {color: #00c;} + +.cm-s-default .cm-error {color: #f00;} +.cm-invalidchar {color: #f00;} + +.CodeMirror-composing { border-bottom: 2px solid; } + +/* Default styles for common addons */ + +div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} +.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } +.CodeMirror-activeline-background {background: #e8f2ff;} + +/* STOP */ + +/* The rest of this file contains styles related to the mechanics of + the editor. You probably shouldn't touch them. */ + +.CodeMirror { + position: relative; + overflow: hidden; + background: white; +} + +.CodeMirror-scroll { + overflow: scroll !important; /* Things will break if this is overridden */ + /* 30px is the magic margin used to hide the element's real scrollbars */ + /* See overflow: hidden in .CodeMirror */ + margin-bottom: -30px; margin-right: -30px; + padding-bottom: 30px; + height: 100%; + outline: none; /* Prevent dragging from highlighting the element */ + position: relative; +} +.CodeMirror-sizer { + position: relative; + border-right: 30px solid transparent; +} + +/* The fake, visible scrollbars. Used to force redraw during scrolling + before actuall scrolling happens, thus preventing shaking and + flickering artifacts. */ +.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + position: absolute; + z-index: 6; + display: none; +} +.CodeMirror-vscrollbar { + right: 0; top: 0; + overflow-x: hidden; + overflow-y: scroll; +} +.CodeMirror-hscrollbar { + bottom: 0; left: 0; + overflow-y: hidden; + overflow-x: scroll; +} +.CodeMirror-scrollbar-filler { + right: 0; bottom: 0; +} +.CodeMirror-gutter-filler { + left: 0; bottom: 0; +} + +.CodeMirror-gutters { + position: absolute; left: 0; top: 0; + z-index: 3; +} +.CodeMirror-gutter { + white-space: normal; + height: 100%; + display: inline-block; + margin-bottom: -30px; + /* Hack to make IE7 behave */ + *zoom:1; + *display:inline; +} +.CodeMirror-gutter-wrapper { + position: absolute; + z-index: 4; + background: none !important; + border: none !important; +} +.CodeMirror-gutter-background { + position: absolute; + top: 0; bottom: 0; + z-index: 4; +} +.CodeMirror-gutter-elt { + position: absolute; + cursor: default; + z-index: 4; +} +.CodeMirror-gutter-wrapper { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.CodeMirror-lines { + cursor: text; + min-height: 1px; /* prevents collapsing before first draw */ +} +.CodeMirror pre { + /* Reset some styles that the rest of the page might have set */ + -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; + border-width: 0; + background: transparent; + font-family: inherit; + font-size: inherit; + margin: 0; + white-space: pre; + word-wrap: normal; + line-height: inherit; + color: inherit; + z-index: 2; + position: relative; + overflow: visible; + -webkit-tap-highlight-color: transparent; +} +.CodeMirror-wrap pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: normal; +} + +.CodeMirror-linebackground { + position: absolute; + left: 0; right: 0; top: 0; bottom: 0; + z-index: 0; +} + +.CodeMirror-linewidget { + position: relative; + z-index: 2; + overflow: auto; +} + +.CodeMirror-widget {} + +.CodeMirror-code { + outline: none; +} + +/* Force content-box sizing for the elements where we expect it */ +.CodeMirror-scroll, +.CodeMirror-sizer, +.CodeMirror-gutter, +.CodeMirror-gutters, +.CodeMirror-linenumber { + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.CodeMirror-measure { + position: absolute; + width: 100%; + height: 0; + overflow: hidden; + visibility: hidden; +} + +.CodeMirror-cursor { position: absolute; } +.CodeMirror-measure pre { position: static; } + +div.CodeMirror-cursors { + visibility: hidden; + position: relative; + z-index: 3; +} +div.CodeMirror-dragcursors { + visibility: visible; +} + +.CodeMirror-focused div.CodeMirror-cursors { + visibility: visible; +} + +.CodeMirror-selected { background: #d9d9d9; } +.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } +.CodeMirror-crosshair { cursor: crosshair; } +.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } +.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } + +.cm-searching { + background: #ffa; + background: rgba(255, 255, 0, .4); +} + +/* IE7 hack to prevent it from returning funny offsetTops on the spans */ +.CodeMirror span { *vertical-align: text-bottom; } + +/* Used to force a border model for a node */ +.cm-force-border { padding-right: .1px; } + +@media print { + /* Hide the cursor when printing */ + .CodeMirror div.CodeMirror-cursors { + visibility: hidden; + } +} + +/* See issue #2901 */ +.cm-tab-wrap-hack:after { content: ''; } + +/* Help users use markselection to safely style text background */ +span.CodeMirror-selectedtext { background: none; } diff --git a/cakeshop-api/src/main/webapp/css/style.css b/cakeshop-api/src/main/webapp/css/style.css index 8d5f96cc..df6c000a 100644 --- a/cakeshop-api/src/main/webapp/css/style.css +++ b/cakeshop-api/src/main/webapp/css/style.css @@ -696,10 +696,56 @@ pre { } .version-info { - text-align: center; - font-size: smaller; + text-align: center; + font-size: smaller; } #newchainconfirm .modal-dialog { width: 30%; } + +.quorum-control { + display: none; +} + +.CodeMirror { + height: 100%; + font-size: 11px; +} + +td.locking-col, + th.locking-col { + float: right; + width: 100%; +} + +.locking-btn { + float: right; +} + +.unlocked-col { + width: 30px; + font-size: 17px; +} + +td.locked i.fa-lock, + td.locked i.fa-lock { + opacity: 1; +} + +.fund-accounts-form input { + margin-bottom: 8px; +} + +.error-msg { + color: #c51616; + padding-right: 5px; +} + +.form-group.fund-accounts-form label { + padding-top: 8px; +} + +.account-balance span { + float: right; +} diff --git a/cakeshop-api/src/main/webapp/index.html b/cakeshop-api/src/main/webapp/index.html index 2b0db765..2f2dda03 100644 --- a/cakeshop-api/src/main/webapp/index.html +++ b/cakeshop-api/src/main/webapp/index.html @@ -11,6 +11,7 @@ + @@ -159,6 +160,19 @@ + + + + +

diff --git a/cakeshop-api/src/main/webapp/js/index.js b/cakeshop-api/src/main/webapp/js/index.js index 0ac9dd34..8870e965 100644 --- a/cakeshop-api/src/main/webapp/js/index.js +++ b/cakeshop-api/src/main/webapp/js/index.js @@ -5,7 +5,7 @@ import 'd3'; import 'bootstrap-tour'; // importing here to be used by metrix widgets -import 'epoch-charting'; +import 'epoch-charting-ie-patched'; import moment from 'moment'; @@ -26,9 +26,77 @@ window.utils = utils; window.moment = moment; window.Tower = { + client: null, ready: false, current: null, - status: {}, + status: { + status: 'init' + }, + heartbeat: function(response) { + if (!Tower._set_ver && response.meta && response.meta['cakeshop-version']) { + Tower._set_ver = true; + + $('aside nav').append('

Cakeshop ' + response.meta["cakeshop-version"] + '
'); + + var build = response.meta['cakeshop-build-id']; + if (build && build.length > 0) { + $('aside nav').append('
Build ' + build.substring(0, 8) + '
'); + } + } + + var status = response.data.attributes; + + // Set the client once status is retrieved + if (Tower.client === null) { + if (status.quorumInfo === null) { + delete status.quorumInfo; + + Tower.client = 'geth'; + } else { + Tower.client = 'quorum'; + + // Show all quorum controls + $('.quorum-control').show(); + } + + // Redraw the current section + $('#' + Dashboard.section).click(); + } + + if (status.status === 'running') { + $('#default-node-status').html( $('', { html: 'Running' }) ); + + $('#default-node-status').parent().find('.fa') + .removeClass('fa-pause tower-txt-danger') + .addClass('fa-play tower-txt-success'); + } else { + $('#default-node-status').html( $('', { html: utils.capitalize(status.status) }) ); + + $('#default-node-status').parent().find('.fa') + .removeClass('fa-play tower-txt-success') + .addClass('fa-pause tower-txt-danger'); + } + + utils.prettyUpdate(Tower.status.peerCount, status.peerCount, $('#default-peers')); + utils.prettyUpdate(Tower.status.latestBlock, status.latestBlock, $('#default-blocks')); + utils.prettyUpdate(Tower.status.pendingTxn, status.pendingTxn, $('#default-txn')); + + if (status.status !== Tower.status.status) { + $(document).trigger('CakeshopEvent', ['node|status-flip|' + JSON.stringify({ + from: Tower.status.status, + to: status.status + }) ]); + } + + Tower.status = status; + + // Tower Control becomes ready only after the first status is received from the server + if (!Tower.ready) { + Tower.isReady(); + } + + Dashboard.Utils.emit('node-status|announce'); + }, // Tower Control becomes ready only after the first status is received from the server isReady: function() { @@ -53,6 +121,7 @@ window.Tower = { 'minHeight': 250 }); + // Shared widgets Dashboard.preregisterWidgets({ 'accounts' : require('./widgets/accounts'), 'block-detail' : require('./widgets/block-detail'), @@ -63,6 +132,7 @@ window.Tower = { 'contract-list' : require('./widgets/contract-list'), 'contract-paper-tape' : require('./widgets/contract-paper-tape'), 'doc-frame' : require('./widgets/doc-frame'), + 'fund-accounts' : require('./widgets/fund-accounts'), 'metrix-blocks-min' : require('./widgets/metrix-blocks-min'), 'metrix-txn-min' : require('./widgets/metrix-txn-min'), 'metrix-txn-sec' : require('./widgets/metrix-txn-sec'), @@ -72,7 +142,14 @@ window.Tower = { 'peers-add' : require('./widgets/peers-add'), 'peers-list' : require('./widgets/peers-list'), 'peers-neighborhood' : require('./widgets/peers-neighborhood'), - 'txn-detail' : require('./widgets/txn-detail'), + 'txn-detail' : require('./widgets/txn-detail') + }); + + + // Quorum widgets + Dashboard.preregisterWidgets({ + 'quorum-settings': require('./widgets/quorum-settings'), + 'constellation': require('./widgets/constellation') }); Dashboard.init(); @@ -90,9 +167,92 @@ window.Tower = { Tower.stomp = Client.stomp; Tower.stomp_subscriptions = Client._stomp_subscriptions; - Tower.section['default'](); + + // Manual status retrieve during init + $.when( + utils.load({ url: 'api/node/get' }) + ).done(function(response) { + Tower.heartbeat(response); + }).fail(function() { + // Handler for when its dead + Tower.heartbeat({ + data: { + attributes: { + status: 'DOWN', + peerCount: 'n/a', + latestBlock: 'n/a', + pendingTxn: 'n/a' + } + } + }); + }); + + // Socket subscription for status updates + Client.on('stomp:connect', function() { + Tower.subscription = utils.subscribe('/topic/node/status', Tower.heartbeat); + }); + + // Show disconnect notice when stomp looses connection + Client.on('stomp:disconnect', function() { + Tower.heartbeat({ + data: { + attributes: { + status: 'DOWN', + peerCount: 'n/a', + latestBlock: 'n/a', + pendingTxn: 'n/a' + } + } + }); + + Tower.subscription.unsubscribe(); + }); + + + // Handle session info + Tower.session(); + + // Retry session when status changes + $(document).on('CakeshopEvent', function(ev, action) { + if (action.indexOf('node|status-flip') >= 0) { + Tower.session(); + } + }); }, + session: function() { + if (Tower.hasOwnProperty('securityEnabled') && Tower.securityEnabled === false) { + return; + } + + // Fetch session info + $.ajax({ + type: 'GET', + url: 'user', + contentType: 'application/json', + cache: false, + async: true + }).then(function(res) { + if (Tower.hasOwnProperty('session_interval')) { + window.clearInterval(Tower.session_interval); + } + + // security is enabled + if (res.securityEnabled === true) { + Tower.securityEnabled = true; + + // security is on and session expired, reload + if (res.hasOwnProperty('loggedout')) { + window.location.reload(true); + } + + // defaulting to java's default session expiration + Tower.session_interval = window.setInterval(Tower.session, 1000 * 60 * 30); + } else { + Tower.securityEnabled = false; + } + }); + }, processHash: function() { // http://localhost:8080/cakeshop/index.html#section=explorer&widgetId=txn-detail&data=0xd6398cb5cb5bac9d191de62665c1e7e4ef8cd9fe1e9ff94eec181a7b4046345c @@ -140,66 +300,6 @@ window.Tower = { }, section: { - 'default': function() { - var statusUpdate = function(response) { - - if (!Tower._set_ver && response.meta && response.meta['cakeshop-version']) { - Tower._set_ver = true; - - $('aside nav').append('
Cakeshop ' + response.meta["cakeshop-version"] + '
'); - - var build = response.meta['cakeshop-build-id']; - if (build && build.length > 0) { - $('aside nav').append('
Build ' + build.substring(0, 8) + '
'); - } - } - - var status = response.data.attributes; - - if (status.status === 'running') { - $('#default-node-status').html( $('', { html: 'Running' }) ); - - $('#default-node-status').parent().find('.fa') - .removeClass('fa-pause tower-txt-danger') - .addClass('fa-play tower-txt-success'); - } else { - $('#default-node-status').html( $('', { html: utils.capitalize(status.status) }) ); - - $('#default-node-status').parent().find('.fa') - .removeClass('fa-play tower-txt-success') - .addClass('fa-pause tower-txt-danger'); - } - - utils.prettyUpdate(Tower.status.peerCount, status.peerCount, $('#default-peers')); - utils.prettyUpdate(Tower.status.latestBlock, status.latestBlock, $('#default-blocks')); - utils.prettyUpdate(Tower.status.pendingTxn, status.pendingTxn, $('#default-txn')); - - Tower.status = status; - - // Tower Control becomes ready only after the first status is received from the server - if (!Tower.ready) { - Tower.isReady(); - } - - Dashboard.Utils.emit('node-status|announce'); - }; - - $.when( - utils.load({ url: 'api/node/get' }) - ).done(function(response) { - statusUpdate(response); - }).fail(function() { - statusUpdate({ - status: 'DOWN', - peerCount: 'n/a', - latestBlock: 'n/a', - pendingTxn: 'n/a' - }); - }); - - utils.subscribe('/topic/node/status', statusUpdate); - }, - 'console': function() { var widgets = [ { widgetId: 'node-info' }, @@ -207,9 +307,15 @@ window.Tower = { { widgetId: 'node-settings' }, { widgetId: 'metrix-txn-sec' }, { widgetId: 'metrix-txn-min' }, - { widgetId: 'metrix-blocks-min' } + { widgetId: 'metrix-blocks-min' }, + { widgetId: 'node-log' } ]; + if (Tower.client === 'quorum') { + widgets.push({ widgetId: 'quorum-settings' }); + widgets.push({ widgetId: 'constellation' }); + } + Dashboard.showSection('console', widgets); }, @@ -220,6 +326,10 @@ window.Tower = { { widgetId: 'peers-neighborhood', data: Tower.status.nodeIP } ]; + if (Tower.client === 'quorum') { + widgets.push({ widgetId: 'constellation' }); + } + Dashboard.showSection('peers', widgets); }, @@ -251,7 +361,8 @@ window.Tower = { 'wallet': function() { var widgets = [ - { widgetId: 'accounts' } + { widgetId: 'accounts' }, + { widgetId: 'fund-accounts'} ]; Dashboard.showSection('wallet', widgets); @@ -303,9 +414,9 @@ $(function() { if (id === 'sandbox') { return; } else if (id === 'help') { + $(document).trigger('StartTour'); + Tower.tour.start(true); - $(document).trigger('StartTour'); - Tower.tour.start(true); return; } @@ -322,8 +433,6 @@ $(function() { }); - - // ---------- INIT ----------- Tower.init(); diff --git a/cakeshop-api/src/main/webapp/js/sandbox/contracts/Ballot.txt b/cakeshop-api/src/main/webapp/js/sandbox/contracts/Ballot.txt index 076abc37..6fd499a1 100644 --- a/cakeshop-api/src/main/webapp/js/sandbox/contracts/Ballot.txt +++ b/cakeshop-api/src/main/webapp/js/sandbox/contracts/Ballot.txt @@ -1,4 +1,4 @@ - +pragma solidity ^0.4.9; contract Ballot { struct Voter { diff --git a/cakeshop-api/src/main/webapp/js/sandbox/contracts/Bank.txt b/cakeshop-api/src/main/webapp/js/sandbox/contracts/Bank.txt index c4a7ab43..e6e20790 100644 --- a/cakeshop-api/src/main/webapp/js/sandbox/contracts/Bank.txt +++ b/cakeshop-api/src/main/webapp/js/sandbox/contracts/Bank.txt @@ -1,3 +1,4 @@ +pragma solidity ^0.4.9; contract Owned { address owner; @@ -7,7 +8,7 @@ contract Owned { // be used in derived contracts. // The function body is inserted where the special symbol "_" in the // definition of a modifier appears. - modifier only_contract_owner { if (msg.sender == owner) _ } + modifier only_contract_owner { if (msg.sender == owner) _; } } contract Bank is Owned { @@ -25,7 +26,7 @@ contract Bank is Owned { mapping (bytes32 => Record) vault; // only account owner or Bank owner is allowed - modifier only_account_owner(bytes32 _id) { if (vault[_id].owner != 0 && (vault[_id].owner == msg.sender || msg.sender == owner)) _ } + modifier only_account_owner(bytes32 _id) { if (vault[_id].owner != 0 && (vault[_id].owner == msg.sender || msg.sender == owner)) _; } // Log Events event Deposit(address indexed _from, bytes32 indexed _id, uint _value, string status); diff --git a/cakeshop-api/src/main/webapp/js/sandbox/contracts/Greeter.txt b/cakeshop-api/src/main/webapp/js/sandbox/contracts/Greeter.txt index ad7e0dc5..5f425c2f 100644 --- a/cakeshop-api/src/main/webapp/js/sandbox/contracts/Greeter.txt +++ b/cakeshop-api/src/main/webapp/js/sandbox/contracts/Greeter.txt @@ -1,3 +1,4 @@ +pragma solidity ^0.4.9; contract mortal { /* Define variable owner of the type address*/ address owner; diff --git a/cakeshop-api/src/main/webapp/js/sandbox/contracts/SimpleStorage.txt b/cakeshop-api/src/main/webapp/js/sandbox/contracts/SimpleStorage.txt index e92c0dd6..bb6aee6f 100644 --- a/cakeshop-api/src/main/webapp/js/sandbox/contracts/SimpleStorage.txt +++ b/cakeshop-api/src/main/webapp/js/sandbox/contracts/SimpleStorage.txt @@ -1,4 +1,4 @@ - +pragma solidity ^0.4.9; contract SimpleStorage { uint public storedData; diff --git a/cakeshop-api/src/main/webapp/js/sandbox/contracts/oracle-example.txt b/cakeshop-api/src/main/webapp/js/sandbox/contracts/oracle-example.txt index 78f97cb8..386d0778 100644 --- a/cakeshop-api/src/main/webapp/js/sandbox/contracts/oracle-example.txt +++ b/cakeshop-api/src/main/webapp/js/sandbox/contracts/oracle-example.txt @@ -25,6 +25,7 @@ r.replace_oracle('EQUITY_JPM', 0x6565e77ecbf9c49d591f4c5682c2b9a4c12fd69d, "[ABI }) */ +pragma solidity ^0.4.9; // ############## REGISTRY START ############## contract Registrat0r { // This is first SDK account diff --git a/cakeshop-api/src/main/webapp/js/sandbox/tx.js b/cakeshop-api/src/main/webapp/js/sandbox/tx.js index 4f30e4b9..d9892614 100644 --- a/cakeshop-api/src/main/webapp/js/sandbox/tx.js +++ b/cakeshop-api/src/main/webapp/js/sandbox/tx.js @@ -266,21 +266,23 @@ * @param [Object] method ABI object * @param [Element] container Container element which wraps all inputs */ - function collectInputVals(method, container) { - var params = {}; - method.inputs.forEach(function(input) { - var val; - if (input.type.match(/\[(\d+)?\]/)) { - val = container.find(".method-inputs[data-param=" + input.name + "] input").map(function(i, el) { - return $(el).val(); - }).toArray(); - } else { - val = container.find(".method-inputs[data-param=" + input.name + "] input").val(); - } - params[input.name] = val; - }); - return params; - } + function collectInputVals(method, container) { + var params = {}; + if (method !== null && method !== undefined) { + method.inputs.forEach(function (input) { + var val; + if (input.type.match(/\[(\d+)?\]/)) { + val = container.find(".method-inputs[data-param=" + input.name + "] input").map(function (i, el) { + return $(el).val(); + }).toArray(); + } else { + val = container.find(".method-inputs[data-param=" + input.name + "] input").val(); + } + params[input.name] = val; + }); + } + return params; + } /** * Highlight the given method in the source code editor diff --git a/cakeshop-api/src/main/webapp/js/widgets/accounts.js b/cakeshop-api/src/main/webapp/js/widgets/accounts.js index 70f28c4b..46982ad9 100644 --- a/cakeshop-api/src/main/webapp/js/widgets/accounts.js +++ b/cakeshop-api/src/main/webapp/js/widgets/accounts.js @@ -6,24 +6,68 @@ module.exports = function() { title: 'Accounts', size: 'medium', - url: 'api/wallet/list', + url_list: 'api/wallet/list', url_create: 'api/wallet/create', + url_lock: 'api/wallet/lock', + url_unlock: 'api/wallet/unlock', hideLink: true, customButtons: '
  • ', template: _.template('' + - '' + - '<%= rows %>
    AccountBalance
    '), + ' ' + + ' ' + + ' ' + + ' Account' + + ' Balance' + + ' '+ //for buttons + ' ' + + ' ' + + ' <%= rows %> ' + + '' + + '
    ' + + ' ' + + '
    '), - templateRow: _.template('<%= o.get("address") %><%= o.balance %>'), + templateRow: _.template('' + + '' + + '<% if( !o.unlocked ){ %>' + + '' + + '<%= o.address %>' + + '<%= o.balance %>' + + '' + + '' + + '' + + ''), + + modalTemplate: _.template( '' + + '' + + ''), + + modalConfirmation: _.template(''), fetch: function() { var _this = this; - Account.list().then(function(accounts) { + $.when( + utils.load({ url: _this.url_list }) + ).done(function(accounts) { var rows = []; - accounts.forEach(function(acct) { - var b = parseInt(acct.get('balance'), 10) / 1000000000000000000; + _.each(accounts.data, function(acct) { + acct = acct.attributes; + var b = parseInt(acct.balance, 10); if (b > 1000000000) { b = 'Unlimited'; @@ -32,22 +76,82 @@ module.exports = function() { } acct.balance = b + ' ETH'; - rows.push( _this.templateRow({ o: acct }) ); }); - $('#widget-' + _this.shell.id).html( _this.template({ rows: rows.join('') }) ); utils.makeAreaEditable('#widget-' + _this.shell.id + ' .value'); }); }, + subscribe: function() { + //fetch when account funds are transferred + Dashboard.Utils.on(function(e, action) { + if (action[0] == 'fundTransfer') { + this.fetch(); + } + }.bind(this)); + }, + postRender: function() { var _this = this; - $('#widget-shell-' + _this.shell.id + ' i.add-account').click(function(e) { + $('#widget-shell-' + _this.shell.id + ' .add-account').click(function(e) { $.when( utils.load({ url: _this.url_create }) ).done(function() { - $(e.target).parent().parent().find('.fa-rotate-right').click(); + Dashboard.Utils.emit(['accountUpdate'], true); + _this.fetch(); + }); + + }); + + $('#widget-' + _this.shell.id).on('click', '.locking-btn', function(e) { + var account = $(e.target.parentElement).data("account"), + url = _this.url_lock, + lock = 'lock'; + + if ($(e.target).hasClass('locked')) { + url = _this.url_unlock; + lock = 'unlock'; + } + + // set the modal text + $('#myModal .modal-content').html(_this.modalTemplate({ + account: account, + lock: lock + }) ); + + //open modal + $('#myModal').modal('show'); + + $('#locking-btn-final').click( function() { + var pwd = $('#account-pwd').val(); + + $.when( + utils.load({ + url: url, + data: { + "account": account, + "accountPassword": pwd, + "fromAccount": "", + "newBalance": "" + } + }) + ).done(function () { + if($(e.target).hasClass('locked') ) { + $(e.target).removeClass('locked'); + } else { + $(e.target).addClass('locked'); + } + $('#myModal').modal('hide'); + + Dashboard.Utils.emit(['accountUpdate'], true) + _this.fetch(); + + }).fail(function() { + $('#myModal .modal-content').html(_this.modalConfirmation({ + message: 'Sorry, could not ' + lock + ' account. Please try again.' + }) ); + }); }); }); diff --git a/cakeshop-api/src/main/webapp/js/widgets/constellation.js b/cakeshop-api/src/main/webapp/js/widgets/constellation.js new file mode 100644 index 00000000..146ff302 --- /dev/null +++ b/cakeshop-api/src/main/webapp/js/widgets/constellation.js @@ -0,0 +1,103 @@ +import utils from '../utils'; + +module.exports = function() { + var extended = { + name: 'constellation', + title: 'Constellation Settings', + size: 'small', + + hideLink: true, + + url: { + list: 'api/node/constellation/list', + add: 'api/node/constellation/add', + remove: 'api/node/constellation/remove' + }, + + template: _.template( + '' + + ' ' + + ' ' + + '
    Own node
    Other nodes
    ' + + '
    ' + + ' ' + + ' ' + + '
    '+ + '
    ' + + ' ' + + '
    '+ + '
    ' + + '
    '), + + rendered: false, + + fetch: function() { + var _this = this; + + if (!_this.rendered) { + _this._$().html( _this.template({}) ); + } + + _this.rendered = true; + + $.when( + utils.load({ url: this.url.list }) + ).done(function(info) { + info = info.data.attributes.result; + + _this._$('#own-node').html(info.local); + _this._$('#other-node').html( info.remote ? info.remote.join(', ') : '' ); + + _this.postFetch(); + }.bind(this)); + }, + + postRender: function() { + utils.makeAreaEditable('#widget-' + this.shell.id + ' .value'); + + this._$('button').click(this._handler); + }, + + _handler: function(ev) { + var _this = widget, + input = _this._$('#addy'), + notif = _this._$('#notification'); + + if (!input.val()) { + return; + } + + $.when( + utils.load({ url: _this.url.add, data: { 'constellationNode': input.val() } }) + ).done(function(r) { + notif.show(); + + if ( (r) && (r.error) ) { + notif + .addClass('text-danger') + .removeClass('text-success') + .html(r.error.message); + + } else { + input.val(''); + + notif + .removeClass('text-danger') + .addClass('text-success') + .html('Request to add constelltion node is sent'); + + setTimeout(function() { + notif.fadeOut(); + _this.fetch(); + }, 2000); + } + }); + } + }; + + + var widget = _.extend({}, widgetRoot, extended); + + // register presence with screen manager + Dashboard.addWidget(widget); +}; diff --git a/cakeshop-api/src/main/webapp/js/widgets/doc-frame.js b/cakeshop-api/src/main/webapp/js/widgets/doc-frame.js index 36400d9a..d15466dd 100644 --- a/cakeshop-api/src/main/webapp/js/widgets/doc-frame.js +++ b/cakeshop-api/src/main/webapp/js/widgets/doc-frame.js @@ -9,7 +9,8 @@ module.exports = function() { template: _.template( '
    ' + - ' ' + + ' ' + + ' ' + '
    '), getVisible: function() { diff --git a/cakeshop-api/src/main/webapp/js/widgets/fund-accounts.js b/cakeshop-api/src/main/webapp/js/widgets/fund-accounts.js new file mode 100644 index 00000000..78e5851d --- /dev/null +++ b/cakeshop-api/src/main/webapp/js/widgets/fund-accounts.js @@ -0,0 +1,134 @@ +import utils from '../utils'; + +module.exports = function() { + var extended = { + name: 'fund-accounts', + title: 'Fund Accounts', + size: 'small', + + url: 'api/wallet/fund', + accountList: [], + + hideLink: true, + + template: _.template( '
    ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + '
    '+ + '
    ' + + ' ' + + ' ' + + '
    '+ + '
    ' + + '
    '), + + modalTemplate: _.template( '' + + ' '), + + modalConfirmation: _.template( ''), + + subscribe: function() { + //repopulate account list when new account is added + // or, if accounts are locked /unlocked + Dashboard.Utils.on(function(e, action) { + if (action[0] == 'accountUpdate') { + this.fetch(); + } + }.bind(this)); + }, + + fetch: function() { + Account.list().then(function(accounts) { + var rows = ['']; + + accounts.forEach(function(acct) { + if (acct.get('unlocked')) { + //only add unlocked accounts + rows.push( '' ); + } + }); + + this._$('#transfer-from') + .html( rows.join('') ); + + this._$('#transfer-to') + .html( rows.join('') ); + + }.bind(this)); + }, + + postRender: function() { + var _this = this; + $('#widget-' + this.shell.id).html( this.template({}) ); + + //populate account dropdowns + this.fetch(); + + $('#widget-' + this.shell.id + ' #transfer-btn').click( function() { + var from = $('#widget-' + _this.shell.id + ' #transfer-from').val(), + to = $('#widget-' + _this.shell.id + ' #transfer-to').val(), + amount = $('#widget-' + _this.shell.id + ' #amount').val(); + + //verify that everything is filled out + if (from == '' || to == '' || amount == '') { + //error + $('#widget-' + _this.shell.id + ' .error-msg').html('All fields required.'); + } else { + //empty error fields just in case + $('#widget-' + _this.shell.id + ' .error-msg').html(''); + + // set the modal text + $('#myModal .modal-content').html(_this.modalTemplate({ + amount: amount, + from: from, + to: to + }) ); + + //open modal + $('#myModal').modal('show'); + } + + // send transfer request to backend + $('#transfer-btn-final').click(function() { + $.when( + utils.load({ + url: _this.url, + data: { + "account": to, + "accountPassword": '', + "fromAccount": from, + "newBalance": amount + } + }) + ).done(function(m) { + $('#myModal .modal-content').html(_this.modalConfirmation({ + message: 'Successfully transferred funds! You may have to wait a moment for the changes to reflect on the balance.' + }) ); + setTimeout( function(){ + Dashboard.Utils.emit(['fundTransfer'], true) + }, 6000); + }).fail(function(err) { + $('#myModal .modal-content').html(_this.modalConfirmation({ + message: 'Sorry, transaction did not complete. Please try again.' + }) ); + }); + }); + + }); + } + }; + + var widget = _.extend({}, widgetRoot, extended); + + // register presence with screen manager + Dashboard.addWidget(widget); +}; diff --git a/cakeshop-api/src/main/webapp/js/widgets/node-control.js b/cakeshop-api/src/main/webapp/js/widgets/node-control.js index dab4ef35..e0426f5a 100644 --- a/cakeshop-api/src/main/webapp/js/widgets/node-control.js +++ b/cakeshop-api/src/main/webapp/js/widgets/node-control.js @@ -18,30 +18,32 @@ module.exports = function() { '
  • '+ '
  • '+ '
  • '+ + '
  • '+ + '
  • '+ '' + ''), postRender: function() { - $('#widget-' + this.shell.id).html(widget.template({})); - $('#widget-' + this.shell.id + ' .ctrls').click(widget._handler); + this._$().html(widget.template({})); + this._$('.ctrls').click(widget._handler); }, _handler: function(ev) { diff --git a/cakeshop-api/src/main/webapp/js/widgets/node-info.js b/cakeshop-api/src/main/webapp/js/widgets/node-info.js index 8a44e13b..7905a594 100644 --- a/cakeshop-api/src/main/webapp/js/widgets/node-info.js +++ b/cakeshop-api/src/main/webapp/js/widgets/node-info.js @@ -11,7 +11,7 @@ module.exports = function() { // url: 'api/node/get', template: _.template('<%= rows %>
    '), - templateRow: _.template('<%= key %><%= value %>'), + templateRow: _.template('<%= key %><%= value %>'), subscribe: function() { @@ -27,7 +27,6 @@ module.exports = function() { // TODO: renders after every fetch. May need to re-render only when needed onData: function(status) { - var customOrder = _.reduce([ 'nodeUrl', 'rpcUrl', @@ -43,15 +42,11 @@ module.exports = function() { 'quorum', 'quorumInfo' ], function(memo, v, i) { - memo[v] = i; return memo; + memo[v] = i; + + return memo; }, {}); - if (status.quorumInfo === null) { - delete status.quorumInfo; - status.quorum = false; - } else { - status.quorum = true; - } var rows = [], keys = _.sortBy(_.keys(status), function(key) { @@ -59,24 +54,55 @@ module.exports = function() { if (key in customOrder) { return customOrder[key]; } + return (99999); }); keys = utils.idAlwaysFirst(keys); // objects not shown in this widget - keys = _.without(keys, 'config', 'peers'); + keys = _.without(keys, 'config', 'peers', 'quorumInfo'); _.each(keys, function(val, key) { - if (val === 'quorumInfo') { - // filter out nulls for the case when we have a quorum node but didn't get all status - var qinfo = _.reduce(status[val], function(memo, v, k) { if (v !== null) { memo[k] = v; } return memo; }, {}); - rows.push( this.templateRow({ key: utils.camelToRegularForm(val), value: JSON.stringify(qinfo) }) ); - } else { - rows.push( this.templateRow({ key: utils.camelToRegularForm(val), value: status[val] }) ); + rows.push( this.templateRow({ key: utils.camelToRegularForm(val), value: status[val] }) ); + }.bind(this)); + + + // Appending quorum info if present + if (status.hasOwnProperty('quorumInfo') && !_.isEmpty(status.quorumInfo)) { + rows.push( 'Quorum Info' ); + + keys =_.keys(status.quorumInfo).sort(function (a, b) { + if (a < b) return -1; + if (b < a) return 1; + + return 0; + }); + keys = _.without(keys, 'blockMakerStrategy'); + + _.each(keys, function(val, key) { + rows.push( this.templateRow({ key: utils.camelToRegularForm(val), value: status.quorumInfo[val] }) ); + }.bind(this)); + + // Strategy + if ( status.quorumInfo.hasOwnProperty('blockMakerStrategy') + && !_.isEmpty(status.quorumInfo.blockMakerStrategy) ) { + + rows.push( 'Block Maker Strategy' ); + + keys =_.keys(status.quorumInfo.blockMakerStrategy).sort(function (a, b) { + if (a < b) return -1; + if (b < a) return 1; + + return 0; + }); + + _.each(keys, function(val, key) { + rows.push( this.templateRow({ key: utils.camelToRegularForm(val), value: status.quorumInfo.blockMakerStrategy[val] }) ); + }.bind(this)); } - }.bind(this)); + } $('#widget-' + this.shell.id).html( this.template({ rows: rows.join('') }) ); diff --git a/cakeshop-api/src/main/webapp/js/widgets/node-log.js b/cakeshop-api/src/main/webapp/js/widgets/node-log.js new file mode 100644 index 00000000..20608ff6 --- /dev/null +++ b/cakeshop-api/src/main/webapp/js/widgets/node-log.js @@ -0,0 +1,74 @@ +import utils from '../utils'; +import CodeMirror from 'codemirror/lib/codemirror'; + +module.exports = function() { + var extended = { + name: 'node-log', + title: 'Node Log', + size: 'medium', + stream: false, + + hideLink: true, + + template: _.template('
    '), + + subscribe: function() { + if (this.stream) { + this.logSub = utils.subscribe('/topic/log/geth', this.onData); + + $(window).on('beforeunload', function() { + this.logSub.unsubscribe(); + }.bind(this)); + } + }, + + onData: function(data) { + widget.$log.replaceRange(data.data.attributes.result + '\n', { + line: 0, + ch: 0, + sticky: null + }); + }, + + renderCodeArea: function() { + this.stream = true; + + this._$().html(''); + + this.$log = CodeMirror.fromTextArea(this._$('#log-editor')[0], { + lineWrapping: true, + lineNumbers: true, + readOnly: 'nocursor' + }); + + this.subscribe(); + // this._$('.CodeMirror').height(200); + }, + + render: function() { + Dashboard.render.widget(this.name, this.shell.tpl); + + this.fetch(); + + this._$().css({ + 'height': '240px', + 'margin-bottom': '10px', + 'overflow-x': 'hidden', + 'width': '100%' + }).html(this.template()); + + this._$('button').on('click', function() { + this.renderCodeArea(); + }.bind(this)); + + this.postRender(); + $(document).trigger('WidgetInternalEvent', ['widget|rendered|' + this.name]); + } + }; + + + var widget = _.extend({}, widgetRoot, extended); + + // register presence with screen manager + Dashboard.addWidget(widget); +}; diff --git a/cakeshop-api/src/main/webapp/js/widgets/node-settings.js b/cakeshop-api/src/main/webapp/js/widgets/node-settings.js index 4907151b..65a19260 100644 --- a/cakeshop-api/src/main/webapp/js/widgets/node-settings.js +++ b/cakeshop-api/src/main/webapp/js/widgets/node-settings.js @@ -8,7 +8,7 @@ module.exports = function() { hideLink: true, - url: 'api/node/get', + url: 'api/node/get', update_url: 'api/node/update', template: _.template( @@ -21,7 +21,7 @@ module.exports = function() { '' + '
    ' + ' ' + - ' ' + + ' ' + '
    ' + '
    ' + ' ' + @@ -48,7 +48,6 @@ module.exports = function() { '
    '), - subscribe: function() { // adding listener to reload the widget if identity is updated Dashboard.Utils.on(function(ev, action) { @@ -60,17 +59,17 @@ module.exports = function() { rendered: false, onData:function(status) { - if (this.rendered) { + if ( (this.rendered) || _.isEmpty(status.config) ) { return; } this.rendered = true; - $('#widget-' + this.shell.id + ' #networkId').val( status.config.networkId ? status.config.networkId : '' ); - $('#widget-' + this.shell.id + ' #identity').val( status.config.identity ? status.config.identity : '' ); - $('#widget-' + this.shell.id + ' #logLevel').val( status.config.logLevel ? status.config.logLevel : '4' ); - $('#widget-' + this.shell.id + ' #committingTransactions').val( status.config.committingTransactions ? 'true' : 'false' ); - $('#widget-' + this.shell.id + ' #extraParams').val( status.config.extraParams ? status.config.extraParams : '' ); - $('#widget-' + this.shell.id + ' #genesisBlock').val( status.config.genesisBlock ? status.config.genesisBlock : '' ); + this._$('#networkId').val( status.config.networkId ? status.config.networkId : '' ); + this._$('#identity').val( status.config.identity ? status.config.identity : '' ); + this._$('#logLevel').val( status.config.logLevel ? status.config.logLevel : '4' ); + this._$('#committingTransactions').val( status.config.committingTransactions ? 'true' : 'false' ); + this._$('#extraParams').val( status.config.extraParams ? status.config.extraParams : '' ); + this._$('#genesisBlock').val( status.config.genesisBlock ? status.config.genesisBlock : '' ); }, @@ -83,12 +82,12 @@ module.exports = function() { render: function() { Dashboard.render.widget(this.name, this.shell.tpl); - $('#widget-' + this.shell.id) + this._$() .css({ 'height': '240px', 'margin-bottom': '10px', 'overflow': 'auto' }) .html( this.template({}) ); - $('#widget-' + this.shell.id + ' .form-control').change(this._handler); - $(document).trigger("WidgetInternalEvent", ["widget|rendered|" + this.name]); + this._$('.form-control').change(this._handler); + $(document).trigger('WidgetInternalEvent', ['widget|rendered|' + this.name]); }, diff --git a/cakeshop-api/src/main/webapp/js/widgets/quorum-settings.js b/cakeshop-api/src/main/webapp/js/widgets/quorum-settings.js new file mode 100644 index 00000000..b33da30a --- /dev/null +++ b/cakeshop-api/src/main/webapp/js/widgets/quorum-settings.js @@ -0,0 +1,115 @@ +import utils from '../utils'; + +module.exports = function() { + var extended = { + name: 'quorum-settings', + title: 'Quorum Settings', + size: 'small', + + hideLink: true, + + url: 'api/node/get', + update_url: 'api/node/update', + + template: _.template( + '
    ' + + ' ' + + ' ' + + '
    ' + + '
    ' + + ' ' + + ' ' + + '
    ' + + '
    ' + + ' ' + + ' ' + + '
    ' + + '
    ' + + ' ' + + ' ' + + '
    '), + + + subscribe: function() { + // adding listener to reload the widget if identity is updated + Dashboard.Utils.on(function(ev, action) { + if (action === 'node-status|announce') { + widget.onData(Tower.status); + } + }); + }, + + rendered: false, + onData:function(status) { + if (this.rendered) { + return; + } + + // Show & populate quorum settings if needed + if (status.hasOwnProperty('quorumInfo')) { + Account.list().then(function(accounts) { + var rows = ['']; + + accounts.forEach(function(acct) { + rows.push( '' ); + }); + + this._$('#blockMakerAccount') + .html( rows.join('') ) + .val( status.quorumInfo.blockMakerAccount ); + + this._$('#voterAccount') + .html( rows.join('') ) + .val( status.quorumInfo.voteAccount ); + + }.bind(this)); + + this._$('#minBlockTime').val( status.quorumInfo.blockMakerStrategy.minBlockTime ); + this._$('#maxBlockTime').val( status.quorumInfo.blockMakerStrategy.maxBlockTime ); + } + }, + + + fetch: function() { + this.rendered = false; + widget.onData(Tower.status); + }, + + + render: function() { + Dashboard.render.widget(this.name, this.shell.tpl); + + this._$() + .css({ 'height': '240px', 'margin-bottom': '10px', 'overflow': 'auto' }) + .html( this.template({}) ); + + this._$('.form-control').change(this._handler); + $(document).trigger('WidgetInternalEvent', ['widget|rendered|' + this.name]); + }, + + + _handler: function(ev) { + var _this = $(this), + action = _this.attr('id'), + val = _this.val(), + data = {}; + + data[action] = val; + + $.when( + utils.load({ url: widget.update_url, data: data }) + ).done(function(info) { + // trigger event update + Dashboard.Utils.emit(widget.name + '|updated|' + action); + }); + } + }; + + + var widget = _.extend({}, widgetRoot, extended); + + // register presence with screen manager + Dashboard.addWidget(widget); +}; diff --git a/cakeshop-api/src/main/webapp/js/widgets/widget-root.js b/cakeshop-api/src/main/webapp/js/widgets/widget-root.js index dfb145db..6b422545 100644 --- a/cakeshop-api/src/main/webapp/js/widgets/widget-root.js +++ b/cakeshop-api/src/main/webapp/js/widgets/widget-root.js @@ -77,7 +77,23 @@ window.widgetRoot = { }); this.postRender(); - $(document).trigger("WidgetInternalEvent", ["widget|rendered|" + this.name]); + $(document).trigger('WidgetInternalEvent', ['widget|rendered|' + this.name]); + }, + + _$: function(id) { + if (id !== undefined) { + return $('#widget-' + this.shell.id).find(id); + } else { + return $('#widget-' + this.shell.id); + } + }, + + _$shell: function(id) { + if (id !== undefined) { + return $('#widget-shell-' + this.shell.id).find(id); + } else { + return $('#widget-shell-' + this.shell.id); + } }, postRender: function() { }, diff --git a/cakeshop-api/src/main/webapp/package.json b/cakeshop-api/src/main/webapp/package.json index 4cff6af7..8e4aba44 100644 --- a/cakeshop-api/src/main/webapp/package.json +++ b/cakeshop-api/src/main/webapp/package.json @@ -26,10 +26,11 @@ "bluebird": "^3.4.1", "bootstrap": "^3.3.7", "bootstrap-tour": "^0.11.0", + "codemirror": "^5.23.0", "d3": "^4.2.1", - "jif-dashboard": "github:jpmorganchase/jif-dashboard", "draggabilly": "^2.1.1", - "epoch-charting": "github:fixanoid/epoch", + "epoch-charting-ie-patched": "0.8.4-patched", + "jif-dashboard": "^1.0.0", "jquery": "2.2.4", "jquery-slimscroll": "^1.3.8", "jquery-ui-dist": "^1.12.1", diff --git a/cakeshop-api/src/main/webapp/resources/login.html b/cakeshop-api/src/main/webapp/resources/login.html new file mode 100644 index 00000000..2b8488d3 --- /dev/null +++ b/cakeshop-api/src/main/webapp/resources/login.html @@ -0,0 +1,20 @@ + + + + Cakeshop Login + + +
    + Invalid username and password. +
    +
    + You have been logged out. +
    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/BaseGethRpcTest.java b/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/BaseGethRpcTest.java index 941d884e..0e060271 100644 --- a/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/BaseGethRpcTest.java +++ b/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/BaseGethRpcTest.java @@ -18,6 +18,7 @@ import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -42,7 +43,7 @@ @ActiveProfiles("test") @ContextConfiguration(classes = {TestAppConfig.class}) //@Listeners(CleanConsoleListener.class) // uncomment for extra debug help -@DirtiesContext(classMode=ClassMode.AFTER_CLASS) +@DirtiesContext(classMode = ClassMode.AFTER_CLASS) public abstract class BaseGethRpcTest extends AbstractTestNGSpringContextTests { private static final Logger LOG = LoggerFactory.getLogger(BaseGethRpcTest.class); @@ -52,11 +53,11 @@ public abstract class BaseGethRpcTest extends AbstractTestNGSpringContextTests { System.setProperty("cakeshop.database.vendor", "hsqldb"); } - @Autowired - private ContractService contractService; + @Autowired + private ContractService contractService; - @Autowired - private TransactionService transactionService; + @Autowired + private TransactionService transactionService; @Autowired private AppStartup appStartup; @@ -73,7 +74,6 @@ public abstract class BaseGethRpcTest extends AbstractTestNGSpringContextTests { @Autowired private GethConfigBean gethConfig; - @Autowired @Qualifier("hsql") private DataSource embeddedDb; @@ -86,7 +86,7 @@ public boolean runGeth() { return true; } - @AfterSuite(alwaysRun=true) + @AfterSuite(alwaysRun = true) public void stopSolc() throws IOException { List args = Lists.newArrayList( gethConfig.getNodePath(), @@ -97,7 +97,7 @@ public void stopSolc() throws IOException { builder.start(); } - @AfterSuite(alwaysRun=true) + @AfterSuite(alwaysRun = true) public void cleanupTempPaths() { try { if (CONFIG_ROOT != null) { @@ -115,7 +115,6 @@ public void startGeth() throws IOException { } assertTrue(appStartup.isHealthy(), "Healthcheck should pass"); - LOG.info("Starting Ethereum at test startup"); assertTrue(_startGeth()); } @@ -123,14 +122,27 @@ public void startGeth() throws IOException { private boolean _startGeth() throws IOException { gethConfig.setGenesisBlockFilename(FileUtils.getClasspathPath("genesis_block.json").toAbsolutePath().toString()); gethConfig.setKeystorePath(FileUtils.getClasspathPath("keystore").toAbsolutePath().toString()); - return geth.start(); + gethConfig.setIsEmbeddedQuorum(false); + List additionalParams = new ArrayList<>(); + additionalParams.add("--blockmakeraccount"); + additionalParams.add("0xca843569e3427144cead5e4d5999a3d0ccf92b8e"); + additionalParams.add("--blockmakerpassword"); + additionalParams.add(""); + additionalParams.add("--minblocktime"); + additionalParams.add("2"); + additionalParams.add("--maxblocktime"); + additionalParams.add("5"); + additionalParams.add("--voteaccount"); + additionalParams.add("0x0fbdc686b912d7722dc86510934589e0aaf3b55a"); + additionalParams.add("--votepassword"); + additionalParams.add(""); + return geth.start(additionalParams.toArray(additionalParams.toArray(new String[additionalParams.size()]))); } - /** * Stop geth & delete data dir */ - @AfterClass(alwaysRun=true) + @AfterClass(alwaysRun = true) public void stopGeth() { if (!runGeth()) { return; @@ -140,6 +152,7 @@ public void stopGeth() { } private void _stopGeth() { + gethConfig.setIsEmbeddedQuorum(false); geth.stop(); try { FileUtils.deleteDirectory(new File(ethDataDir)); @@ -160,7 +173,7 @@ private void _stopGeth() { * @throws IOException */ protected String readTestFile(String path) throws IOException { - return FileUtils.readClasspathFile(path); + return FileUtils.readClasspathFile(path); } /** @@ -171,8 +184,8 @@ protected String readTestFile(String path) throws IOException { * @throws InterruptedException */ protected String createContract() throws IOException, InterruptedException { - String code = readTestFile("contracts/simplestorage.sol"); - return createContract(code, null); + String code = readTestFile("contracts/simplestorage.sol"); + return createContract(code, null); } /** @@ -185,15 +198,17 @@ protected String createContract() throws IOException, InterruptedException { */ protected String createContract(String code, Object[] args) throws APIException, InterruptedException { TransactionResult result = contractService.create(null, code, ContractService.CodeType.solidity, args, null, null, null); - assertNotNull(result); - assertNotNull(result.getId()); - assertTrue(!result.getId().isEmpty()); + assertNotNull(result); + assertNotNull(result.getId()); + assertTrue(!result.getId().isEmpty()); - // make sure mining is enabled - Map res = geth.executeGethCall("miner_start", new Object[]{ }); + // make sure mining is enabled + if (!gethConfig.isQuorum()) { + Map res = geth.executeGethCall("miner_start", new Object[]{}); + } - Transaction tx = transactionService.waitForTx(result, 50, TimeUnit.MILLISECONDS); - return tx.getContractAddress(); + Transaction tx = transactionService.waitForTx(result, 50, TimeUnit.MILLISECONDS); + return tx.getContractAddress(); } } diff --git a/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/ContractServiceTest.java b/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/ContractServiceTest.java index 5af72b68..cb265dd4 100644 --- a/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/ContractServiceTest.java +++ b/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/ContractServiceTest.java @@ -27,21 +27,21 @@ public class ContractServiceTest extends BaseGethRpcTest { - @Autowired - private ContractService contractService; + @Autowired + private ContractService contractService; - @Autowired - private TransactionService transactionService; + @Autowired + private TransactionService transactionService; - @Autowired - private GethHttpService geth; + @Autowired + private GethHttpService geth; - @Autowired - private BlockScanner blockScanner; + @Autowired + private BlockScanner blockScanner; - @Test - public void testCompile() throws IOException { - long time = System.currentTimeMillis() / 1000; + @Test + public void testCompile() throws IOException { + long time = System.currentTimeMillis() / 1000; String code = readTestFile("contracts/simplestorage.sol"); List contracts = contractService.compile(code, CodeType.solidity, true); @@ -68,11 +68,9 @@ public void testCompile() throws IOException { List creation = (List) gasEstimates.get("creation"); assertNotNull(creation); assertEquals(creation.size(), 2); + } - assertNotEmptyString(c.getSolidityInterface()); - } - - @Test + @Test public void testCreate() throws IOException { String code = readTestFile("contracts/simplestorage.sol"); @@ -83,8 +81,8 @@ public void testCreate() throws IOException { } - @Test - public void testCreateWithBinary() throws IOException { + @Test + public void testCreateWithBinary() throws IOException { String code = readTestFile("contracts/simplestorage.sol"); List contracts = contractService.compile(code, CodeType.solidity, true); Contract c = contracts.get(0); @@ -94,165 +92,158 @@ public void testCreateWithBinary() throws IOException { assertNotNull(result); assertNotNull(result.getId()); assertTrue(!result.getId().isEmpty()); - } - - @Test - public void testGet() throws IOException, InterruptedException { - String contractAddress = createContract(); - - Contract contract = contractService.get(contractAddress); - assertNotNull(contract); - assertNotNull(contract.getBinary(), "Binary code should be present"); - assertNotEquals(contract.getBinary(), "0x", "binary should not be '0x'"); - assertTrue(contract.getBinary().length() > 2); - } - - @Test - public void testGetInvalidId() throws APIException { - assertThrows(APIException.class, new ThrowingRunnable() { + } + + @Test + public void testGet() throws IOException, InterruptedException { + String contractAddress = createContract(); + + Contract contract = contractService.get(contractAddress); + assertNotNull(contract); + assertNotNull(contract.getBinary(), "Binary code should be present"); + assertNotEquals(contract.getBinary(), "0x", "binary should not be '0x'"); + assertTrue(contract.getBinary().length() > 2); + } + + @Test + public void testGetInvalidId() throws APIException { + assertThrows(APIException.class, new ThrowingRunnable() { @Override public void run() throws Throwable { contractService.get("0xdeadbeef"); } }); - assertThrows(APIException.class, new ThrowingRunnable() { + assertThrows(APIException.class, new ThrowingRunnable() { @Override public void run() throws Throwable { contractService.get("0x81635fe3d9cecbcf44aa58e967af1ab7ceefb816"); } }); - } - - @Test - public void testReadByABI() throws InterruptedException, IOException { - String contractAddress = createContract(); - ContractABI abi = ContractABI.fromJson(readTestFile("contracts/simplestorage.abi.txt")); - - BigInteger val = (BigInteger) contractService.read(contractAddress, abi, null, "get", null, null)[0]; - assertEquals(val.intValue(), 100); - } - - @Test - public void testReadBytesArr() throws InterruptedException, IOException { - String addr = createContract(readTestFile("contracts/testbytesarr.sol"), null); - ContractABI abi = ContractABI.fromJson(readTestFile("contracts/testbytesarr.abi.txt")); - Object[] res = (Object[]) contractService.read(addr, abi, null, "foo", null, null)[0]; - assertNotNull(res); - assertEquals(res.length, 1); - assertEquals(new String((byte[]) res[0]).trim(), "foobar"); - } - - @Test - public void testConstructorArg() throws InterruptedException, IOException { - String code = readTestFile("contracts/simplestorage2.sol"); - - // create with constructor val 500 - String contractAddress = createContract(code, new Object[] { 500 }); - - ContractABI abi = ContractABI.fromJson(readTestFile("contracts/simplestorage2.abi.txt")); - - BigInteger val = (BigInteger) contractService.read(contractAddress, abi, null, "get", null, null)[0]; - assertEquals(val.intValue(), 500); - - - String owner = (String) contractService.read(contractAddress, abi, null, "owner", null, null)[0]; - assertEquals(owner, "0x2e219248f44546d966808cdd20cb6c36df6efa82"); - } - - @Test - public void testRead2ByABI() throws InterruptedException, IOException { - String contractAddress = createContract(); - - String code = readTestFile("contracts/simplestorage.sol"); - String json = readTestFile("contracts/simplestorage.abi.txt"); - ContractABI abi = ContractABI.fromJson(json); - - String addr = "0x81635fe3d9cecbcf44aa58e967af1ab7ceefb817"; - String str = "foobar47"; + } + @Test + public void testReadByABI() throws InterruptedException, IOException { + String contractAddress = createContract(); + ContractABI abi = ContractABI.fromJson(readTestFile("contracts/simplestorage.abi.txt")); + BigInteger val = (BigInteger) contractService.read(contractAddress, abi, null, "get", null, null)[0]; + assertEquals(val.intValue(), 100); + } + @Test + public void testReadBytesArr() throws InterruptedException, IOException { + String addr = createContract(readTestFile("contracts/testbytesarr.sol"), null); + ContractABI abi = ContractABI.fromJson(readTestFile("contracts/testbytesarr.abi.txt")); + Object[] res = (Object[]) contractService.read(addr, abi, null, "foo", null, null)[0]; + assertNotNull(res); + assertEquals(res.length, 1); + assertEquals(new String((byte[]) res[0]).trim(), "foobar"); + } - Object[] res = contractService.read( - contractAddress, abi, null, - "echo_2", - new Object[] { addr, str }, - null); + @Test + public void testConstructorArg() throws InterruptedException, IOException { + String code = readTestFile("contracts/simplestorage2.sol"); - String hexAddr = (String) res[0]; - assertEquals(hexAddr, addr); - assertEquals(res[1], str); + // create with constructor val 500 + String contractAddress = createContract(code, new Object[]{500}); + ContractABI abi = ContractABI.fromJson(readTestFile("contracts/simplestorage2.abi.txt")); + BigInteger val = (BigInteger) contractService.read(contractAddress, abi, null, "get", null, null)[0]; + assertEquals(val.intValue(), 500); + String owner = (String) contractService.read(contractAddress, abi, null, "owner", null, null)[0]; + assertEquals(owner, "0x2e219248f44546d966808cdd20cb6c36df6efa82"); + } - Object[] res2 = contractService.read( - contractAddress, abi, null, - "echo_contract", - new Object[] { contractAddress, "SimpleStorage", json, code, "solidity" }, - null); + @Test + public void testRead2ByABI() throws InterruptedException, IOException { + String contractAddress = createContract(); - assertEquals(res2[0], contractAddress); - assertEquals(res2[1], "SimpleStorage"); - assertEquals(res2[2], json); - } + String code = readTestFile("contracts/simplestorage.sol"); + String json = readTestFile("contracts/simplestorage.abi.txt"); + ContractABI abi = ContractABI.fromJson(json); + + String addr = "0x81635fe3d9cecbcf44aa58e967af1ab7ceefb817"; + String str = "foobar47"; + + Object[] res = contractService.read( + contractAddress, abi, null, + "echo_2", + new Object[]{addr, str}, + null); + + String hexAddr = (String) res[0]; + assertEquals(hexAddr, addr); + assertEquals(res[1], str); + + Object[] res2 = contractService.read( + contractAddress, abi, null, + "echo_contract", + new Object[]{contractAddress, "SimpleStorage", json, code, "solidity"}, + null); + + assertEquals(res2[0], contractAddress); + assertEquals(res2[1], "SimpleStorage"); + assertEquals(res2[2], json); + } - @Test - public void testTransactByABI() throws InterruptedException, IOException { + @Test + public void testTransactByABI() throws InterruptedException, IOException { - String contractAddress = createContract(); + String contractAddress = createContract(); - ContractABI abi = ContractABI.fromJson(readTestFile("contracts/simplestorage.abi.txt")); + ContractABI abi = ContractABI.fromJson(readTestFile("contracts/simplestorage.abi.txt")); - // 100 to start - BigInteger val = (BigInteger) contractService.read(contractAddress, abi, null, "get", null, null)[0]; - assertEquals(val.intValue(), 100); + // 100 to start + BigInteger val = (BigInteger) contractService.read(contractAddress, abi, null, "get", null, null)[0]; + assertEquals(val.intValue(), 100); - // modify value - TransactionResult tr = contractService.transact(contractAddress, abi, null, "set", new Object[]{ 200 }); - Transaction tx = transactionService.waitForTx(tr, 50, TimeUnit.MILLISECONDS); + // modify value + TransactionResult tr = contractService.transact(contractAddress, abi, null, "set", new Object[]{200}); + Transaction tx = transactionService.waitForTx(tr, 50, TimeUnit.MILLISECONDS); - // should now be 200 - BigInteger val2 = (BigInteger) contractService.read(contractAddress, abi, null, "get", null, null)[0]; - assertEquals(val2.intValue(), 200); + // should now be 200 + BigInteger val2 = (BigInteger) contractService.read(contractAddress, abi, null, "get", null, null)[0]; + assertEquals(val2.intValue(), 200); - // read the previous value - BigInteger valPrev = (BigInteger) contractService.read(contractAddress, abi, null, "get", null, tx.getBlockNumber().longValue() -1 )[0]; - assertEquals(valPrev.intValue(), 100); - } + // read the previous value + BigInteger valPrev = (BigInteger) contractService.read(contractAddress, abi, null, "get", null, tx.getBlockNumber().longValue() - 1)[0]; + assertEquals(valPrev.intValue(), 100); + } - @Test - public void testListTransactions() throws InterruptedException, IOException { + @Test + public void testListTransactions() throws InterruptedException, IOException { - String contractAddress = createContract(); + String contractAddress = createContract(); - ContractABI abi = ContractABI.fromJson(readTestFile("contracts/simplestorage.abi.txt")); + ContractABI abi = ContractABI.fromJson(readTestFile("contracts/simplestorage.abi.txt")); - // 100 to start - BigInteger val = (BigInteger) contractService.read(contractAddress, abi, null, "get", null, null)[0]; - assertEquals(val.intValue(), 100); + // 100 to start + BigInteger val = (BigInteger) contractService.read(contractAddress, abi, null, "get", null, null)[0]; + assertEquals(val.intValue(), 100); - // modify value - TransactionResult tr = contractService.transact(contractAddress, abi, null, "set", new Object[]{ 200 }); - Transaction tx = transactionService.waitForTx(tr, 50, TimeUnit.MILLISECONDS); + // modify value + TransactionResult tr = contractService.transact(contractAddress, abi, null, "set", new Object[]{200}); + Transaction tx = transactionService.waitForTx(tr, 50, TimeUnit.MILLISECONDS); - ((TestBlockScanner) blockScanner).manualRun(); + ((TestBlockScanner) blockScanner).manualRun(); - List txns = contractService.listTransactions(contractAddress); + List txns = contractService.listTransactions(contractAddress); - assertNotNull(txns); - assertTrue(!txns.isEmpty()); - assertEquals(txns.size(), 2); + assertNotNull(txns); + assertTrue(!txns.isEmpty()); + assertEquals(txns.size(), 2); - Transaction txSet = txns.get(1); - Input decodedInput = txSet.getDecodedInput(); - assertNotNull(decodedInput); - assertEquals(decodedInput.getMethod(), "set"); + Transaction txSet = txns.get(1); + Input decodedInput = txSet.getDecodedInput(); + assertNotNull(decodedInput); + assertEquals(decodedInput.getMethod(), "set"); - val = (BigInteger) decodedInput.getArgs()[0]; - assertEquals(val.intValue(), 200); + val = (BigInteger) decodedInput.getArgs()[0]; + assertEquals(val.intValue(), 200); - } + } } diff --git a/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/GethHttpServiceTest.java b/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/GethHttpServiceTest.java index cea7bd45..bb76e12e 100644 --- a/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/GethHttpServiceTest.java +++ b/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/GethHttpServiceTest.java @@ -5,6 +5,8 @@ import com.jpmorgan.cakeshop.error.APIException; import com.jpmorgan.cakeshop.service.BlockService; import com.jpmorgan.cakeshop.service.GethHttpService; +import java.util.ArrayList; +import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.testng.annotations.Test; @@ -19,11 +21,25 @@ public class GethHttpServiceTest extends BaseGethRpcTest { @Test public void testReset() throws APIException { + System.out.println("Running GethHttpServiceTest.testReset-------------------------------------"); assertTrue(geth.isRunning()); String blockId = blockService.get(null, 1L, null).getId(); - - assertTrue(geth.reset()); + List additionalParams = new ArrayList<>(); + additionalParams.add("--blockmakeraccount"); + additionalParams.add("0xca843569e3427144cead5e4d5999a3d0ccf92b8e"); + additionalParams.add("--blockmakerpassword"); + additionalParams.add(""); + additionalParams.add("--minblocktime"); + additionalParams.add("2"); + additionalParams.add("--maxblocktime"); + additionalParams.add("5"); + additionalParams.add("--voteaccount"); + additionalParams.add("0x0fbdc686b912d7722dc86510934589e0aaf3b55a"); + additionalParams.add("--votepassword"); + additionalParams.add(""); + + assertTrue(geth.reset(additionalParams.toArray(additionalParams.toArray(new String[additionalParams.size()])))); assertTrue(geth.isRunning()); assertNotEquals(blockService.get(null, 1L, null).getId(), blockId); diff --git a/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/GethRpcTest.java b/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/GethRpcTest.java index aed46aea..fd2f2f9b 100644 --- a/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/GethRpcTest.java +++ b/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/GethRpcTest.java @@ -13,19 +13,19 @@ public class GethRpcTest extends BaseGethRpcTest { - private final String expectedHash = "0xd93b8da4c2f48c98e2cb76bef403ec22cada28331946218487b0fd1335e52bdd"; + private final String expectedHash = "0x05d9ccffee8da905df840cb5e7508a0305270eadf028c63b74687aa77f6c8c6e"; @Test public void testExecWithParams() throws APIException { String method = "eth_getBlockByNumber"; - Map data = geth.executeGethCall(method, new Object[]{ "latest", false }); + Map data = geth.executeGethCall(method, new Object[]{"latest", false}); assertNotNull(data.get("hash")); - data = geth.executeGethCall(method, new Object[]{ 0, false }); + data = geth.executeGethCall(method, new Object[]{0, false}); assertEquals(data.get("hash"), expectedHash); - data = geth.executeGethCall("eth_getBlockByHash", new Object[]{ expectedHash, false }); + data = geth.executeGethCall("eth_getBlockByHash", new Object[]{expectedHash, false}); assertEquals(data.get("hash"), expectedHash); } @@ -33,8 +33,8 @@ public void testExecWithParams() throws APIException { public void testBatchExec() throws APIException { List reqs = new ArrayList<>(); - reqs.add(new RequestModel("eth_getBlockByNumber", new Object[]{ 0, false }, 1L)); - reqs.add(new RequestModel("eth_getBlockByNumber", new Object[]{ 0, false }, 1L)); + reqs.add(new RequestModel("eth_getBlockByNumber", new Object[]{0, false}, 1L)); + reqs.add(new RequestModel("eth_getBlockByNumber", new Object[]{0, false}, 1L)); List> batchRes = geth.batchExecuteGethCall(reqs); diff --git a/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/TransactionServiceTest.java b/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/TransactionServiceTest.java index 511e619b..7491e97f 100644 --- a/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/TransactionServiceTest.java +++ b/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/TransactionServiceTest.java @@ -1,5 +1,6 @@ package com.jpmorgan.cakeshop.test; +import com.jpmorgan.cakeshop.bean.GethConfigBean; import static org.testng.Assert.*; import com.jpmorgan.cakeshop.error.APIException; @@ -26,78 +27,83 @@ public class TransactionServiceTest extends BaseGethRpcTest { private static final Logger LOG = LoggerFactory.getLogger(TransactionServiceTest.class); - @Autowired - private ContractService contractService; + @Autowired + private ContractService contractService; - @Autowired - private TransactionService transactionService; + @Autowired + private TransactionService transactionService; - @Test - public void testGet() throws IOException { - String code = readTestFile("contracts/simplestorage.sol"); + @Autowired + private GethConfigBean gethConfig; - TransactionResult result = contractService.create(null, code, ContractService.CodeType.solidity, null, null, null, null); + @Test + public void testGet() throws IOException { + String code = readTestFile("contracts/simplestorage.sol"); + + TransactionResult result = contractService.create(null, code, ContractService.CodeType.solidity, null, null, null, null); LOG.info("EXECUTING testGet "); - assertNotNull(result); - assertNotNull(result.getId()); - assertTrue(!result.getId().isEmpty()); + assertNotNull(result); + assertNotNull(result.getId()); + assertTrue(!result.getId().isEmpty()); - Transaction tx = transactionService.get(result.getId()); - assertNotNull(tx); - assertNotNull(tx.getId()); - assertEquals(tx.getId(), result.getId()); - assertEquals(tx.getStatus(), Status.committed); - } + Transaction tx = transactionService.get(result.getId()); + assertNotNull(tx); + assertNotNull(tx.getId()); + assertEquals(tx.getId(), result.getId()); + assertEquals(tx.getStatus(), Status.committed); + } - @Test - public void testGetBatch() throws IOException, InterruptedException { + @Test + public void testGetBatch() throws IOException, InterruptedException { - String code = readTestFile("contracts/simplestorage.sol"); + String code = readTestFile("contracts/simplestorage.sol"); LOG.info("EXECUTING testGetBatch 1 "); - TransactionResult result = contractService.create(null, code, ContractService.CodeType.solidity, null, null, null, null); + TransactionResult result = contractService.create(null, code, ContractService.CodeType.solidity, null, null, null, null); LOG.info("EXECUTING testGetBatch 2 "); - TransactionResult result2 = contractService.create(null, code, ContractService.CodeType.solidity, null, null, null, null); - - List txns = transactionService.get(Lists.newArrayList(result.getId(), result2.getId())); - assertNotNull(txns); - assertEquals(txns.size(), 2); - assertEquals(txns.get(0).getId(), result.getId()); - assertEquals(txns.get(1).getId(), result2.getId()); - } - - @Test - public void testGetInvalidHash() throws IOException { - assertThrows(APIException.class, new ThrowingRunnable() { + TransactionResult result2 = contractService.create(null, code, ContractService.CodeType.solidity, null, null, null, null); + + List txns = transactionService.get(Lists.newArrayList(result.getId(), result2.getId())); + assertNotNull(txns); + assertEquals(txns.size(), 2); + assertEquals(txns.get(0).getId(), result.getId()); + assertEquals(txns.get(1).getId(), result2.getId()); + } + + @Test + public void testGetInvalidHash() throws IOException { + assertThrows(APIException.class, new ThrowingRunnable() { @Override public void run() throws Throwable { transactionService.get("0xasdf"); } }); - assertNull(transactionService.get("0x19ceb68f8df155fb05029ef89dc7e74d0f7bd7c97f9837cc659381ecb9e8eb49")); - } + assertNull(transactionService.get("0x19ceb68f8df155fb05029ef89dc7e74d0f7bd7c97f9837cc659381ecb9e8eb49")); + } - @Test - public void testGetPendingTx() throws IOException, InterruptedException { - String code = readTestFile("contracts/simplestorage.sol"); - ContractABI abi = ContractABI.fromJson(readTestFile("contracts/simplestorage.abi.txt")); + @Test + public void testGetPendingTx() throws IOException, InterruptedException { + String code = readTestFile("contracts/simplestorage.sol"); + ContractABI abi = ContractABI.fromJson(readTestFile("contracts/simplestorage.abi.txt")); LOG.info("EXECUTING testGetPendingTx "); - TransactionResult result = contractService.create(null, code, ContractService.CodeType.solidity, null, null, null, null); - assertNotNull(result); - assertNotNull(result.getId()); - - LOG.info("EXECUTING testGetPendingTx 2"); - Transaction createTx = transactionService.waitForTx(result, 20, TimeUnit.MILLISECONDS); - - // stop mining and submit tx - Map res = geth.executeGethCall("miner_stop", new Object[]{ }); - TransactionResult tr = contractService.transact(createTx.getContractAddress(), abi, null, "set", new Object[]{ 200 }); - - Transaction tx = transactionService.get(tr.getId()); - assertNotNull(tx); - assertEquals(tx.getId(), tr.getId()); - assertEquals(tx.getStatus(), Status.pending); - } + TransactionResult result = contractService.create(null, code, ContractService.CodeType.solidity, null, null, null, null); + assertNotNull(result); + assertNotNull(result.getId()); + + LOG.info("EXECUTING testGetPendingTx 2"); + Transaction createTx = transactionService.waitForTx(result, 20, TimeUnit.MILLISECONDS); + + // stop mining and submit tx + if (!gethConfig.isQuorum()) { + Map res = geth.executeGethCall("miner_stop", new Object[]{}); + } + TransactionResult tr = contractService.transact(createTx.getContractAddress(), abi, null, "set", new Object[]{200}); + + Transaction tx = transactionService.get(tr.getId()); + assertNotNull(tx); + assertEquals(tx.getId(), tr.getId()); + assertEquals(tx.getStatus(), Status.pending); + } } diff --git a/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/WalletServiceTest.java b/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/WalletServiceTest.java index 832d115d..c54eaec2 100644 --- a/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/WalletServiceTest.java +++ b/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/WalletServiceTest.java @@ -13,7 +13,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.testng.annotations.Test; -@Test(singleThreaded=true) +@Test(singleThreaded = true) public class WalletServiceTest extends BaseGethRpcTest { @Autowired @@ -22,20 +22,22 @@ public class WalletServiceTest extends BaseGethRpcTest { @Autowired private WalletDAO walletDAO; - @Test(priority=1) + @Test(priority = 1) public void testList() throws APIException { + System.out.println("Running WalletServiceTest.testList-------------------------------------"); List accounts = wallet.list(); assertNotNull(accounts); - assertEquals(accounts.size(), 3); + assertEquals(accounts.size(), 8); assertTrue(StringUtils.isNotBlank(accounts.get(0).getAddress())); - assertEquals(walletDAO.list().size(), 3); + assertEquals(walletDAO.list().size(), 8); } - @Test(priority=3) + @Test(priority = 3) public void testCreate() throws APIException { + List accounts = wallet.list(); assertNotNull(accounts); - assertEquals(accounts.size(), 3); + assertEquals(accounts.size(), 8); // create Account acc = wallet.create(); @@ -44,9 +46,9 @@ public void testCreate() throws APIException { accounts = wallet.list(); assertNotNull(accounts); - assertEquals(accounts.size(), 4); + assertEquals(accounts.size(), 9); - assertEquals(walletDAO.list().size(), 4); + assertEquals(walletDAO.list().size(), 9); } } diff --git a/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/config/TestAppConfig.java b/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/config/TestAppConfig.java index 3b09195f..79c7687f 100644 --- a/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/config/TestAppConfig.java +++ b/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/config/TestAppConfig.java @@ -2,6 +2,7 @@ import com.jpmorgan.cakeshop.config.AppConfig; import com.jpmorgan.cakeshop.config.SpringBootApplication; +import com.jpmorgan.cakeshop.config.SwaggerConfig; import com.jpmorgan.cakeshop.config.WebAppInit; import com.jpmorgan.cakeshop.config.WebConfig; import com.jpmorgan.cakeshop.db.BlockScanner; @@ -29,13 +30,13 @@ import org.testng.annotations.BeforeClass; @Configuration -@ComponentScan(basePackages="com.jpmorgan.cakeshop", - excludeFilters = { - @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, - value = { SpringBootApplication.class, WebConfig.class, WebAppInit.class, - BlockScanner.class } - ) - } +@ComponentScan(basePackages = "com.jpmorgan.cakeshop", + excludeFilters = { + @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, + value = {SpringBootApplication.class, WebConfig.class, WebAppInit.class, + BlockScanner.class, SwaggerConfig.class} + ) + } ) @ActiveProfiles("test") @Order(1) @@ -45,7 +46,6 @@ public class TestAppConfig implements EnvironmentAware { private static final Logger LOG = LoggerFactory.getLogger(TestAppConfig.class); private Environment env; - @BeforeClass public static void setUp() { System.setProperty("spring.profiles.active", "test"); @@ -54,12 +54,12 @@ public static void setUp() { @Bean @Profile("test") - public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() throws IOException { + public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() throws IOException { AppConfig appConfig = new AppConfig(); return appConfig.createPropConfigurer(TempFileManager.getTempPath()); } - @Bean(name="asyncExecutor") + @Bean(name = "asyncExecutor") public Executor getAsyncExecutor() { return new SyncTaskExecutor(); } diff --git a/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/controller/BaseControllerTest.java b/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/controller/BaseControllerTest.java index 021bee42..94e43284 100644 --- a/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/controller/BaseControllerTest.java +++ b/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/controller/BaseControllerTest.java @@ -8,11 +8,12 @@ import org.springframework.test.web.servlet.MockMvc; import org.testng.annotations.BeforeMethod; -import com.jpmorgan.cakeshop.config.JsonMethodArgumentResolver; +//import com.jpmorgan.cakeshop.config.JsonMethodArgumentResolver; import com.jpmorgan.cakeshop.test.BaseGethRpcTest; /** - * Base class for Controller testing. Simply subclass and implement getController() method + * Base class for Controller testing. Simply subclass and implement + * getController() method * * @author Chetan Sarva * @@ -26,13 +27,13 @@ public abstract class BaseControllerTest extends BaseGethRpcTest { /** * Return the @Controller instance under test. Used in setUp() + * * @return */ public abstract Object getController(); @BeforeMethod public void setupMockMvc() throws Exception { - mockMvc = standaloneSetup(getController()).setCustomArgumentResolvers(new JsonMethodArgumentResolver()).build(); + mockMvc = standaloneSetup(getController()).build(); } - } diff --git a/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/controller/BlockControllerTest.java b/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/controller/BlockControllerTest.java index c099ca66..d62aaf1b 100644 --- a/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/controller/BlockControllerTest.java +++ b/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/controller/BlockControllerTest.java @@ -13,7 +13,6 @@ import org.springframework.http.MediaType; import org.testng.annotations.Test; - public class BlockControllerTest extends BaseControllerTest { @Autowired @@ -28,7 +27,7 @@ public BlockControllerTest() { @Override public Object getController() { - return blockController; + return blockController; } @Test @@ -39,7 +38,8 @@ public void testGetBlockByNumber() throws Exception { @Test public void testGetBlockByHash() throws Exception { - commonTest("{\"hash\":\"0xd93b8da4c2f48c98e2cb76bef403ec22cada28331946218487b0fd1335e52bdd\"}", 0L); + Block block = blockService.get(null, 1L, null); + commonTest("{\"hash\":\"" + block.getId() + "\"}", 1L); } @Test @@ -47,8 +47,8 @@ public void testGetBlockByInvalidHash() throws Exception { String body = "{\"hash\":\"0xb067233bfb768b2d5b7c190b13601f5eb8628e8daf02bb21dd091369c330c25a\"}"; mockMvc.perform(post("/api/block/get") .contentType(MediaType.APPLICATION_JSON_VALUE).content(body)) - .andExpect(status().isNotFound()) - .andExpect(content().string(containsString("\"errors\":"))); + .andExpect(status().isNotFound()) + .andExpect(content().string(containsString("\"errors\":"))); } @Test @@ -60,9 +60,9 @@ public void testGetBlockByTag() throws Exception { private void commonTest(String postBody, long blockNum) throws Exception { mockMvc.perform(post("/api/block/get") .contentType(MediaType.APPLICATION_JSON_VALUE).content(postBody)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(content().string(containsString("\"number\":" + blockNum))); + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(content().string(containsString("\"number\":" + blockNum))); } } diff --git a/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/controller/NodeControllerTest.java b/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/controller/NodeControllerTest.java index 6d073895..f38e60c4 100644 --- a/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/controller/NodeControllerTest.java +++ b/cakeshop-api/src/test/java/com/jpmorgan/cakeshop/test/controller/NodeControllerTest.java @@ -10,7 +10,6 @@ import org.springframework.http.MediaType; import org.testng.annotations.Test; - public class NodeControllerTest extends BaseControllerTest { @Autowired @@ -25,22 +24,22 @@ public NodeControllerTest() { @Override public Object getController() { - return nodeController; + return nodeController; } @Test public void testInvalidEndPoint() throws Exception { mockMvc.perform(post("/api/node/testendpoint") .contentType(MediaType.APPLICATION_JSON_VALUE).content("")) - .andExpect(status().isNotFound()); + .andExpect(status().isNotFound()); } @Test public void testNodeGet() throws Exception { mockMvc.perform(post("/api/node/get") .contentType(MediaType.APPLICATION_JSON_VALUE).content("")) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)); + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)); } } diff --git a/cakeshop-api/src/test/resources/contracts/simplestorage.sol b/cakeshop-api/src/test/resources/contracts/simplestorage.sol index cab82c57..fa94ec26 100644 --- a/cakeshop-api/src/test/resources/contracts/simplestorage.sol +++ b/cakeshop-api/src/test/resources/contracts/simplestorage.sol @@ -1,3 +1,4 @@ +pragma solidity ^0.4.9; contract SimpleStorage { event Debug(string msg, uint val); uint storedData; diff --git a/cakeshop-api/src/test/resources/contracts/simplestorage2.sol b/cakeshop-api/src/test/resources/contracts/simplestorage2.sol index 9c69c893..125a2d26 100644 --- a/cakeshop-api/src/test/resources/contracts/simplestorage2.sol +++ b/cakeshop-api/src/test/resources/contracts/simplestorage2.sol @@ -1,4 +1,4 @@ - +pragma solidity ^0.4.9; contract SimpleStorage { uint public storedData; diff --git a/cakeshop-api/src/test/resources/contracts/testbytesarr.sol b/cakeshop-api/src/test/resources/contracts/testbytesarr.sol index 7a9d0381..657ae7de 100644 --- a/cakeshop-api/src/test/resources/contracts/testbytesarr.sol +++ b/cakeshop-api/src/test/resources/contracts/testbytesarr.sol @@ -1,3 +1,4 @@ +pragma solidity ^0.4.9; contract TestBytesArr { bytes32[] data; diff --git a/cakeshop-api/src/test/resources/genesis_block.json b/cakeshop-api/src/test/resources/genesis_block.json index 50241b88..1ce2f071 100644 --- a/cakeshop-api/src/test/resources/genesis_block.json +++ b/cakeshop-api/src/test/resources/genesis_block.json @@ -1,21 +1,59 @@ { - "nonce": "0xdeadbeefdeadbeef", - "timestamp": "0x00", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "extraData": "0x686f727365", - "gasLimit": "0x08000000", - "difficulty": "0x0400", - "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase": "0x3333333333333333333333333333333333333333", - "alloc": { - "0x2e219248f44546d966808cdd20cb6c36df6efa82": { - "Balance": "1606938044258990275541962092341162602522202993782792835301376" - }, - "0xcd5b17da5ad176905c12fc85ce43ec287ab55363": { - "Balance": "1606938044258990275541962092341162602522202993782792835301376" - }, - "0x50bb02281de5f00cc1f1dd5a6692da3fa9b2d912": { - "Balance": "1606938044258990275541962092341162602522202993782792835301376" + "alloc": { + "0x0000000000000000000000000000000000000020": { + "code": "606060405236156100c45760e060020a60003504631290948581146100c9578063284d163c146100f957806342169e4814610130578063488099a6146101395780634fe437d514610154578063559c390c1461015d57806368bb8bb61461025d57806372a571fc146102c857806386c1ff681461036957806398ba676d146103a0578063a7771ee31461040b578063adfaa72e14610433578063cf5289851461044e578063de8fa43114610457578063e814d1c71461046d578063f4ab9adf14610494575b610002565b610548600435600160a060020a03331660009081526003602052604090205460ff16156100c45760018190555b50565b610548600435600160a060020a03331660009081526005602052604090205460ff16156100c4576004546001141561055e57610002565b61045b60025481565b61054a60043560056020526000908152604090205460ff1681565b61045b60015481565b61045b60043560006000600060006000600050600186038154811015610002579080526002027f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5630192505b60018301548110156105d75760018301805484916000918490811015610002576000918252602080832090910154835282810193909352604091820181205485825292869052205410801561023257506001805490840180548591600091859081101561000257906000526020600020900160005054815260208101919091526040016000205410155b156102555760018301805482908110156100025760009182526020909120015491505b6001016101a8565b610548600435602435600160a060020a03331660009081526003602052604081205460ff16156100c4578054839010156105e45780548084038101808355908290829080158290116105df576002028160020283600052602060002091820191016105df919061066b565b610548600435600160a060020a03331660009081526005602052604090205460ff16156100c457600160a060020a0381166000908152604090205460ff1615156100f65760406000819020805460ff191660019081179091556004805490910190558051600160a060020a038316815290517f1a4ce6942f7aa91856332e618fc90159f13a340611a308f5d7327ba0707e56859181900360200190a16100f6565b610548600435600160a060020a03331660009081526003602052604090205460ff16156100c4576002546001141561071457610002565b61045b600435602435600060006000600050600185038154811015610002579080526002027f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5630181509050806001016000508381548110156100025750825250602090200154919050565b61054a600435600160a060020a03811660009081526003602052604090205460ff165b919050565b61054a60043560036020526000908152604090205460ff1681565b61045b60045481565b6000545b60408051918252519081900360200190f35b61054a600435600160a060020a03811660009081526005602052604090205460ff1661042e565b610548600435600160a060020a03331660009081526003602052604090205460ff16156100c457600160a060020a03811660009081526003602052604090205460ff1615156100f65760406000818120600160a060020a0384169182905260036020908152815460ff1916600190811790925560028054909201909155825191825291517f0ad2eca75347acd5160276fe4b5dad46987e4ff4af9e574195e3e9bc15d7e0ff929181900390910190a16100f6565b005b604080519115158252519081900360200190f35b600160a060020a03811660009081526005602052604090205460ff16156100f65760406000819020805460ff19169055600480546000190190558051600160a060020a038316815290517f8cee3054364d6799f1c8962580ad61273d9d38ca1ff26516bd1ad23c099a60229181900360200190a16100f6565b509392505050565b505050505b60008054600019850190811015610002578382526002027f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563016020819052604082205490925014156106b8578060010160005080548060010182818154818355818115116106a5578183600052602060002091820191016106a5919061068d565b50506002015b808211156106a157600181018054600080835591825260208220610665918101905b808211156106a1576000815560010161068d565b5090565b5050506000928352506020909120018290555b600082815260208281526040918290208054600101905581514381529081018490528151600160a060020a033316927f3d03ba7f4b5227cdb385f2610906e5bcee147171603ec40005b30915ad20e258928290030190a2505050565b600160a060020a03811660009081526003602052604090205460ff16156100f65760406000819020805460ff19169055600280546000190190558051600160a060020a038316815290517f183393fc5cffbfc7d03d623966b85f76b9430f42d3aada2ac3f3deabc78899e89181900360200190a16100f656", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x02", + + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x04", + "0x29ecdbdf95c7f6ceec92d6150c697aa14abeb0f8595dd58d808842ea237d8494": "0x01", + "0x6aa118c6537572d8b515a9f9154be55a3377a8de7991cd23bf6e5ceb368688e3": "0x01", + "0x50793743212c6f01d326957d7069005b912f8215f10c7536be6b10782c6c44cd": "0x01", + "0x38f6c908c5cc7ca668cec2f476abe61b4dbb1df20f0ad8e07ef5dbf6a2f1ffd4": "0x01", + + "0x0000000000000000000000000000000000000000000000000000000000000004": "0x02", + "0xaca3b76ed4968740c3180dd7fa37f4aa229a2c758a848f53920e9ccb4c4bb74e": "0x01", + "0xd188ba2dc293670542c1befaf7678b0859e5354a0727d1188b2afb6f47fe24d1": "0x01" } - } + + }, + "0xed9d02e382b34818e88b88a309c7fe71e65f419d": { + "balance": "1000000000000000000000000000" + }, + "0xca843569e3427144cead5e4d5999a3d0ccf92b8e": { + "balance": "1000000000000000000000000000" + }, + "0x0fbdc686b912d7722dc86510934589e0aaf3b55a": { + "balance": "1000000000000000000000000000" + }, + "0x9186eb3d20cbd1f5f992a950d808c4495153abd5": { + "balance": "1000000000000000000000000000" + }, + "0x0638e1574728b6d862dd5d3a3e0942c3be47d996": { + "balance": "1000000000000000000000000000" + }, + "0x0000000000000000000000000000000000000050": { + "code": "606060405234610000575b33600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060006001819055505b5b6114ba806100646000396000f30060606040523615610097576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063014bb7fe1461009c57806303d1e5dd1461010e578063105b197714610370578063371eb669146103935780635c82edba146103f057806369dc9ff3146105385780638da5cb5b14610809578063b336ad8314610858578063d9c55a1f14610ad7575b610000565b34610000576100a9610ae6565b60405180806020018281038252838181518152602001915080519060200190602002808383600083146100fb575b8051825260208311156100fb576020820191506020810190506020830392506100d7565b5050509050019250505060405180910390f35b346100005761013f600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610b84565b604051808773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018060200180602001806020018060200186815260200185810385528a8181518152602001915080519060200190808383600083146101d2575b8051825260208311156101d2576020820191506020810190506020830392506101ae565b505050905090810190601f1680156101fe5780820380516001836020036101000a031916815260200191505b50858103845289818151815260200191508051906020019080838360008314610246575b80518252602083111561024657602082019150602081019050602083039250610222565b505050905090810190601f1680156102725780820380516001836020036101000a031916815260200191505b508581038352888181518152602001915080519060200190808383600083146102ba575b8051825260208311156102ba57602082019150602081019050602083039250610296565b505050905090810190601f1680156102e65780820380516001836020036101000a031916815260200191505b5085810382528781815181526020019150805190602001908083836000831461032e575b80518252602083111561032e5760208201915060208101905060208303925061030a565b505050905090810190601f16801561035a5780820380516001836020036101000a031916815260200191505b509a505050505050505050505060405180910390f35b346100005761037d610ec4565b6040518082815260200191505060405180910390f35b34610000576103ae6004808035906020019091905050610eca565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3461000057610536600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091908035906020019091905050610f07565b005b3461000057610569600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050611397565b604051808873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018060200180602001806020018060200186815260200185810385528a8181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156106685780601f1061063d57610100808354040283529160200191610668565b820191906000526020600020905b81548152906001019060200180831161064b57829003601f168201915b50508581038452898181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156106eb5780601f106106c0576101008083540402835291602001916106eb565b820191906000526020600020905b8154815290600101906020018083116106ce57829003601f168201915b505085810383528881815460018160011615610100020316600290048152602001915080546001816001161561010002031660029004801561076e5780601f106107435761010080835404028352916020019161076e565b820191906000526020600020905b81548152906001019060200180831161075157829003601f168201915b50508581038252878181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156107f15780601f106107c6576101008083540402835291602001916107f1565b820191906000526020600020905b8154815290600101906020018083116107d457829003601f168201915b50509b50505050505050505050505060405180910390f35b3461000057610816611415565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34610000576108ad600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190505061143b565b604051808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018060200180602001806020018060200185810385528981815181526020019150805190602001908083836000831461093a575b80518252602083111561093a57602082019150602081019050602083039250610916565b505050905090810190601f1680156109665780820380516001836020036101000a031916815260200191505b508581038452888181518152602001915080519060200190808383600083146109ae575b8051825260208311156109ae5760208201915060208101905060208303925061098a565b505050905090810190601f1680156109da5780820380516001836020036101000a031916815260200191505b50858103835287818151815260200191508051906020019080838360008314610a22575b805182526020831115610a22576020820191506020810190506020830392506109fe565b505050905090810190601f168015610a4e5780820380516001836020036101000a031916815260200191505b50858103825286818151815260200191508051906020019080838360008314610a96575b805182526020831115610a9657602082019150602081019050602083039250610a72565b505050905090810190601f168015610ac25780820380516001836020036101000a031916815260200191505b50995050505050505050505060405180910390f35b3461000057610ae461148b565b005b60206040519081016040528060008152506002805480602002602001604051908101604052809291908181526020018280548015610b7957602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019060010190808311610b2f575b505050505090505b90565b6000602060405190810160405280600081525060206040519081016040528060008152506020604051908101604052806000815250602060405190810160405280600081525060006000600360008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002090508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169650806002018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610cce5780601f10610ca357610100808354040283529160200191610cce565b820191906000526020600020905b815481529060010190602001808311610cb157829003601f168201915b50505050509550806003018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610d6d5780601f10610d4257610100808354040283529160200191610d6d565b820191906000526020600020905b815481529060010190602001808311610d5057829003601f168201915b50505050509450806004018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610e0c5780601f10610de157610100808354040283529160200191610e0c565b820191906000526020600020905b815481529060010190602001808311610def57829003601f168201915b50505050509350806005018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610eab5780601f10610e8057610100808354040283529160200191610eab565b820191906000526020600020905b815481529060010190602001808311610e8e57829003601f168201915b50505050509250806006015491505b5091939550919395565b60015481565b600281815481101561000057906000526020600020900160005b915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600160008154600101919050819055600281815481835581811511610f5857818360005260206000209182019101610f5791905b80821115610f53576000816000905550600101610f3b565b5090565b5b50505050856002600160015403815481101561000057906000526020600020900160005b6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060e0604051908101604052808773ffffffffffffffffffffffffffffffffffffffff1681526020013373ffffffffffffffffffffffffffffffffffffffff16815260200186815260200185815260200184815260200183815260200182815250600360008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060208201518160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506040820151816002019080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061113757805160ff1916838001178555611165565b82800160010185558215611165579182015b82811115611164578251825591602001919060010190611149565b5b50905061118a91905b8082111561118657600081600090555060010161116e565b5090565b50506060820151816003019080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106111de57805160ff191683800117855561120c565b8280016001018555821561120c579182015b8281111561120b5782518255916020019190600101906111f0565b5b50905061123191905b8082111561122d576000816000905550600101611215565b5090565b50506080820151816004019080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061128557805160ff19168380011785556112b3565b828001600101855582156112b3579182015b828111156112b2578251825591602001919060010190611297565b5b5090506112d891905b808211156112d45760008160009055506001016112bc565b5090565b505060a0820151816005019080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061132c57805160ff191683800117855561135a565b8280016001018555821561135a579182015b8281111561135957825182559160200191906001019061133e565b5b50905061137f91905b8082111561137b576000816000905550600101611363565b5090565b505060c082015181600601559050505b505050505050565b60036020528060005260406000206000915090508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169080600201908060030190806004019080600501908060060154905087565b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600060206040519081016040528060008152506020604051908101604052806000815250602060405190810160405280600081525060206040519081016040528060008152505b91939590929450565b5b5600a165627a7a72305820e6c2f60a367ff9ef3746b39b6772828b1d3a4cb68ca71e82efb0e46538ec74150029" + }, + "0x2e219248f44546d966808cdd20cb6c36df6efa82": { + "balance": "1606938044258990275541962092341162602522202993782792835301376" + }, + "0xcd5b17da5ad176905c12fc85ce43ec287ab55363": { + "balance": "1606938044258990275541962092341162602522202993782792835301376" + }, + "0x50bb02281de5f00cc1f1dd5a6692da3fa9b2d912": { + "balance": "1606938044258990275541962092341162602522202993782792835301376" + } + }, + "coinbase": "0x0000000000000000000000000000000000000000", + "config": { + "homesteadBlock": 0 + }, + "difficulty": "0x0", + "extraData": "0x", + "gasLimit": "0x2FEFD800", + "mixhash": "0x00000000000000000000000000000000000000647572616c65787365646c6578", + "nonce": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "0x00" } diff --git a/cakeshop-api/src/test/resources/keystore/UTC--2017-02-02T15-56-30.114820686Z--0638e1574728b6d862dd5d3a3e0942c3be47d996 b/cakeshop-api/src/test/resources/keystore/UTC--2017-02-02T15-56-30.114820686Z--0638e1574728b6d862dd5d3a3e0942c3be47d996 new file mode 100644 index 00000000..f0ac8ffb --- /dev/null +++ b/cakeshop-api/src/test/resources/keystore/UTC--2017-02-02T15-56-30.114820686Z--0638e1574728b6d862dd5d3a3e0942c3be47d996 @@ -0,0 +1 @@ +{"address":"0638e1574728b6d862dd5d3a3e0942c3be47d996","crypto":{"cipher":"aes-128-ctr","ciphertext":"d8119d67cb134bc65c53506577cfd633bbbf5acca976cea12dd507de3eb7fd6f","cipherparams":{"iv":"76e88f3f246d4bf9544448d1a27b06f4"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"6d05ade3ee96191ed73ea019f30c02cceb6fc0502c99f706b7b627158bfc2b0a"},"mac":"b39c2c56b35958c712225970b49238fb230d7981ef47d7c33c730c363b658d06"},"id":"00307b43-53a3-4e03-9d0c-4fcbb3da29df","version":3} diff --git a/cakeshop-api/src/test/resources/keystore/UTC--2017-02-02T15-56-30.114820686Z--0fbdc686b912d7722dc86510934589e0aaf3b55a b/cakeshop-api/src/test/resources/keystore/UTC--2017-02-02T15-56-30.114820686Z--0fbdc686b912d7722dc86510934589e0aaf3b55a new file mode 100644 index 00000000..6e460436 --- /dev/null +++ b/cakeshop-api/src/test/resources/keystore/UTC--2017-02-02T15-56-30.114820686Z--0fbdc686b912d7722dc86510934589e0aaf3b55a @@ -0,0 +1 @@ +{"address":"0fbdc686b912d7722dc86510934589e0aaf3b55a","crypto":{"cipher":"aes-128-ctr","ciphertext":"6b2c72c6793f3da8185e36536e02f574805e41c18f551f24b58346ef4ecf3640","cipherparams":{"iv":"582f27a739f39580410faa108d5cc59f"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"1a79b0db3f8cb5c2ae4fa6ccb2b5917ce446bd5e42c8d61faeee512b97b4ad4a"},"mac":"cecb44d2797d6946805d5d744ff803805477195fab1d2209eddc3d1158f2e403"},"id":"f7292e90-af71-49af-a5b3-40e8493f4681","version":3} diff --git a/cakeshop-api/src/test/resources/keystore/UTC--2017-02-02T15-56-30.114820686Z--9186eb3d20cbd1f5f992a950d808c4495153abd5 b/cakeshop-api/src/test/resources/keystore/UTC--2017-02-02T15-56-30.114820686Z--9186eb3d20cbd1f5f992a950d808c4495153abd5 new file mode 100644 index 00000000..e2600a75 --- /dev/null +++ b/cakeshop-api/src/test/resources/keystore/UTC--2017-02-02T15-56-30.114820686Z--9186eb3d20cbd1f5f992a950d808c4495153abd5 @@ -0,0 +1 @@ +{"address":"9186eb3d20cbd1f5f992a950d808c4495153abd5","crypto":{"cipher":"aes-128-ctr","ciphertext":"d160a630a39be3ff35556055406d8ff2a635f0535fe298d62ccc812d8f7b3bd5","cipherparams":{"iv":"82fce06bc6e1658a5e81ccef3b753329"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"8d0c486db4c942721f4f5e96d48e9344805d101dad8159962b8a2008ac718548"},"mac":"4a92bda949068968d470320260ae1a825aa22f6a40fb8567c9f91d700c3f7e91"},"id":"bdb3b4f6-d8d0-4b00-8473-e223ef371b5c","version":3} diff --git a/cakeshop-api/src/test/resources/keystore/UTC--2017-02-02T15-56-30.114820686Z--ca843569e3427144cead5e4d5999a3d0ccf92b8e b/cakeshop-api/src/test/resources/keystore/UTC--2017-02-02T15-56-30.114820686Z--ca843569e3427144cead5e4d5999a3d0ccf92b8e new file mode 100644 index 00000000..cceb36bf --- /dev/null +++ b/cakeshop-api/src/test/resources/keystore/UTC--2017-02-02T15-56-30.114820686Z--ca843569e3427144cead5e4d5999a3d0ccf92b8e @@ -0,0 +1 @@ +{"address":"ca843569e3427144cead5e4d5999a3d0ccf92b8e","crypto":{"cipher":"aes-128-ctr","ciphertext":"01d409941ce57b83a18597058033657182ffb10ae15d7d0906b8a8c04c8d1e3a","cipherparams":{"iv":"0bfb6eadbe0ab7ffaac7e1be285fb4e5"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"7b90f455a95942c7c682e0ef080afc2b494ef71e749ba5b384700ecbe6f4a1bf"},"mac":"4cc851f9349972f851d03d75a96383a37557f7c0055763c673e922de55e9e307"},"id":"354e3b35-1fed-407d-a358-889a29111211","version":3} diff --git a/cakeshop-api/src/test/resources/keystore/UTC--2017-02-02T15-56-30.114820686Z--ed9d02e382b34818e88b88a309c7fe71e65f419d b/cakeshop-api/src/test/resources/keystore/UTC--2017-02-02T15-56-30.114820686Z--ed9d02e382b34818e88b88a309c7fe71e65f419d new file mode 100644 index 00000000..505ab00a --- /dev/null +++ b/cakeshop-api/src/test/resources/keystore/UTC--2017-02-02T15-56-30.114820686Z--ed9d02e382b34818e88b88a309c7fe71e65f419d @@ -0,0 +1 @@ +{"address":"ed9d02e382b34818e88b88a309c7fe71e65f419d","crypto":{"cipher":"aes-128-ctr","ciphertext":"4e77046ba3f699e744acb4a89c36a3ea1158a1bd90a076d36675f4c883864377","cipherparams":{"iv":"a8932af2a3c0225ee8e872bc0e462c11"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"8ca49552b3e92f79c51f2cd3d38dfc723412c212e702bd337a3724e8937aff0f"},"mac":"6d1354fef5aa0418389b1a5d1f5ee0050d7273292a1171c51fd02f9ecff55264"},"id":"a65d1ac3-db7e-445d-a1cc-b6c5eeaa05e0","version":3} diff --git a/cakeshop-client-java-codegen/pom.xml b/cakeshop-client-java-codegen/pom.xml index 867f8b89..0f2910ef 100644 --- a/cakeshop-client-java-codegen/pom.xml +++ b/cakeshop-client-java-codegen/pom.xml @@ -6,7 +6,7 @@ com.jpmorgan cakeshop-parent - 0.9.1 + 0.10.0 cakeshop-client-codegen @@ -22,7 +22,7 @@ com.jpmorgan cakeshop-abi - 0.9.1 + 0.10.0 diff --git a/cakeshop-client-java-sample/pom.xml b/cakeshop-client-java-sample/pom.xml index 60266a98..5be6c426 100644 --- a/cakeshop-client-java-sample/pom.xml +++ b/cakeshop-client-java-sample/pom.xml @@ -6,7 +6,7 @@ com.jpmorgan cakeshop-parent - 0.9.1 + 0.10.0 cakeshop-client-sample @@ -23,7 +23,7 @@ com.jpmorgan cakeshop-client - 0.9.1 + 0.10.0 diff --git a/cakeshop-client-java/pom.xml b/cakeshop-client-java/pom.xml index 5777d624..24c55795 100644 --- a/cakeshop-client-java/pom.xml +++ b/cakeshop-client-java/pom.xml @@ -6,7 +6,7 @@ com.jpmorgan cakeshop-parent - 0.9.1 + 0.10.0 cakeshop-client @@ -25,7 +25,7 @@ com.jpmorgan cakeshop-abi - 0.9.1 + 0.10.0 diff --git a/cakeshop-client-java/src/main/java/com/jpmorgan/cakeshop/client/api/NodeApi.java b/cakeshop-client-java/src/main/java/com/jpmorgan/cakeshop/client/api/NodeApi.java index 19d9a381..6053a732 100644 --- a/cakeshop-client-java/src/main/java/com/jpmorgan/cakeshop/client/api/NodeApi.java +++ b/cakeshop-client-java/src/main/java/com/jpmorgan/cakeshop/client/api/NodeApi.java @@ -9,6 +9,7 @@ import feign.Headers; import feign.RequestLine; +import java.util.List; public interface NodeApi extends ApiClient.Api { @@ -18,27 +19,59 @@ public interface NodeApi extends ApiClient.Api { * @return APIResponse> */ @RequestLine("POST /node/get") - @Headers({ "Content-type: application/json", "Accepts: application/json", }) + @Headers({"Content-type: application/json", "Accepts: application/json",}) APIResponse, Node> get(); @RequestLine("POST /node/update") - @Headers({ "Content-type: application/json", "Accepts: application/json", }) + @Headers({"Content-type: application/json", "Accepts: application/json",}) APIResponse, Node> update(NodeUpdateCommand command); @RequestLine("POST /node/start") - @Headers({ "Content-type: application/json", "Accepts: application/json", }) + @Headers({"Content-type: application/json", "Accepts: application/json",}) APIResponse, Boolean> start(); @RequestLine("POST /node/stop") - @Headers({ "Content-type: application/json", "Accepts: application/json", }) + @Headers({"Content-type: application/json", "Accepts: application/json",}) APIResponse, Boolean> stop(); @RequestLine("POST /node/restart") - @Headers({ "Content-type: application/json", "Accepts: application/json", }) + @Headers({"Content-type: application/json", "Accepts: application/json",}) APIResponse, Boolean> restart(); @RequestLine("POST /node/reset") - @Headers({ "Content-type: application/json", "Accepts: application/json", }) + @Headers({"Content-type: application/json", "Accepts: application/json",}) APIResponse, Boolean> reset(); + @RequestLine("POST /node/constellation/list") + @Headers({"Content-type: application/json", "Accepts: application/json",}) + APIResponse, Boolean> constellationList(); + + @RequestLine("POST /node/constellation/add") + @Headers({"Content-type: application/json", "Accepts: application/json",}) + APIResponse, Node> addConstellation(NodeUpdateCommand command); + + @RequestLine("POST /node/constellation/remove") + @Headers({"Content-type: application/json", "Accepts: application/json",}) + APIResponse, Node> removeConstellationNode(NodeUpdateCommand command); + + @RequestLine("POST /node/constellation/stop") + @Headers({"Content-type: application/json", "Accepts: application/json",}) + APIResponse, Boolean> stopConstellation(); + + @RequestLine("POST /node/constellation/start") + @Headers({"Content-type: application/json", "Accepts: application/json",}) + APIResponse, Boolean> startConstellation(); + + @RequestLine("POST /node/peers/add") + @Headers({"Content-type: application/json", "Accepts: application/json",}) + APIResponse, Boolean> addPeer(NodeUpdateCommand command); + + @RequestLine("POST /node/peers") + @Headers({"Content-type: application/json", "Accepts: application/json",}) + APIResponse peers(); + + @RequestLine("POST /node/settings/reset") + @Headers({"Content-type: application/json", "Accepts: application/json",}) + APIResponse, Boolean> resetNode(); + } diff --git a/cakeshop-client-java/src/main/java/com/jpmorgan/cakeshop/client/model/Node.java b/cakeshop-client-java/src/main/java/com/jpmorgan/cakeshop/client/model/Node.java index 931205b1..07b05c26 100644 --- a/cakeshop-client-java/src/main/java/com/jpmorgan/cakeshop/client/model/Node.java +++ b/cakeshop-client-java/src/main/java/com/jpmorgan/cakeshop/client/model/Node.java @@ -10,12 +10,8 @@ import io.swagger.annotations.ApiModelProperty; - - - - @javax.annotation.Generated(value = "class io.swagger.codegen.languages.JavaClientCodegen", date = "2016-05-26T13:00:18.987-04:00") -public class Node { +public class Node { private String status = null; private String id = null; @@ -24,19 +20,19 @@ public class Node { private String nodeIP = null; private String rpcUrl = null; private String dataDirectory = null; + private QuorumInfo quorumInfo; private Integer peerCount = null; private Integer pendingTxn = null; private Boolean mining = null; private Integer latestBlock = null; - private NodeConfig config = null; private List peers = null; - /** * True if node is running + * * @return */ public boolean isRunning() { @@ -45,7 +41,8 @@ public boolean isRunning() { /** * Status of the node, it has two values \"running\" or \"stopped\" - **/ + * + */ public Node status(String status) { this.status = status; return this; @@ -56,14 +53,15 @@ public Node status(String status) { public String getStatus() { return status; } + public void setStatus(String status) { this.status = status; } - /** * Unique Node ID - **/ + * + */ public Node id(String id) { this.id = id; return this; @@ -74,14 +72,15 @@ public Node id(String id) { public String getId() { return id; } + public void setId(String id) { this.id = id; } - /** * Friendly node name (includes client and version info) - **/ + * + */ public Node nodeName(String nodeName) { this.nodeName = nodeName; return this; @@ -92,14 +91,15 @@ public Node nodeName(String nodeName) { public String getNodeName() { return nodeName; } + public void setNodeName(String nodeName) { this.nodeName = nodeName; } - /** * enode URI (Includes host IP and port number) - **/ + * + */ public Node nodeUrl(String nodeUrl) { this.nodeUrl = nodeUrl; return this; @@ -110,14 +110,15 @@ public Node nodeUrl(String nodeUrl) { public String getNodeUrl() { return nodeUrl; } + public void setNodeUrl(String nodeUrl) { this.nodeUrl = nodeUrl; } - /** * IP address of the node - **/ + * + */ public Node nodeIP(String nodeIP) { this.nodeIP = nodeIP; return this; @@ -128,6 +129,7 @@ public Node nodeIP(String nodeIP) { public String getNodeIP() { return nodeIP; } + public void setNodeIP(String nodeIP) { this.nodeIP = nodeIP; } @@ -136,10 +138,12 @@ public Node rpcUrl(String rpcUrl) { this.rpcUrl = rpcUrl; return this; } + @JsonProperty("rpcUrl") public String getRpcUrl() { return rpcUrl; } + public void setRpcUrl(String rpcUrl) { this.rpcUrl = rpcUrl; } @@ -148,17 +152,34 @@ public Node dataDirectory(String dataDirectory) { this.dataDirectory = dataDirectory; return this; } + @JsonProperty("dataDirectory") public String getDataDirectory() { return dataDirectory; } + public void setDataDirectory(String dataDirectory) { this.dataDirectory = dataDirectory; } + /** + * @return the quorumInfo + */ + public QuorumInfo getQuorumInfo() { + return quorumInfo; + } + + /** + * @param quorumInfo the quorumInfo to set + */ + public void setQuorumInfo(QuorumInfo quorumInfo) { + this.quorumInfo = quorumInfo; + } + /** * Number of peers connected to the node - **/ + * + */ public Node peerCount(Integer peerCount) { this.peerCount = peerCount; return this; @@ -169,14 +190,15 @@ public Node peerCount(Integer peerCount) { public Integer getPeerCount() { return peerCount; } + public void setPeerCount(Integer peerCount) { this.peerCount = peerCount; } - /** * Number of transaction in the queue and haven't been mined - **/ + * + */ public Node pendingTxn(Integer pendingTxn) { this.pendingTxn = pendingTxn; return this; @@ -187,14 +209,16 @@ public Node pendingTxn(Integer pendingTxn) { public Integer getPendingTxn() { return pendingTxn; } + public void setPendingTxn(Integer pendingTxn) { this.pendingTxn = pendingTxn; } - /** - * Indicates weather the miner is running or not. It has a true or false values. - **/ + * Indicates weather the miner is running or not. It has a true or false + * values. + * + */ public Node mining(Boolean mining) { this.mining = mining; return this; @@ -205,14 +229,15 @@ public Node mining(Boolean mining) { public Boolean getMining() { return mining; } + public void setMining(Boolean mining) { this.mining = mining; } - /** * A number indicating the most recent block that has been mined. - **/ + * + */ public Node latestBlock(Integer latestBlock) { this.latestBlock = latestBlock; return this; @@ -223,19 +248,21 @@ public Node latestBlock(Integer latestBlock) { public Integer getLatestBlock() { return latestBlock; } + public void setLatestBlock(Integer latestBlock) { this.latestBlock = latestBlock; } - public Node config(NodeConfig config) { this.config = config; return this; } + @JsonProperty("config") public NodeConfig getConfig() { return config; } + public void setConfig(NodeConfig config) { this.config = config; } @@ -244,16 +271,16 @@ public Node peers(List peers) { this.peers = peers; return this; } + @JsonProperty("peers") public List getPeers() { return peers; } + public void setPeers(List peers) { this.peers = peers; } - - @Override public boolean equals(java.lang.Object o) { if (this == o) { @@ -263,15 +290,15 @@ public boolean equals(java.lang.Object o) { return false; } Node node = (Node) o; - return Objects.equals(this.status, node.status) && - Objects.equals(this.id, node.id) && - Objects.equals(this.nodeName, node.nodeName) && - Objects.equals(this.nodeUrl, node.nodeUrl) && - Objects.equals(this.nodeIP, node.nodeIP) && - Objects.equals(this.peerCount, node.peerCount) && - Objects.equals(this.pendingTxn, node.pendingTxn) && - Objects.equals(this.mining, node.mining) && - Objects.equals(this.latestBlock, node.latestBlock); + return Objects.equals(this.status, node.status) + && Objects.equals(this.id, node.id) + && Objects.equals(this.nodeName, node.nodeName) + && Objects.equals(this.nodeUrl, node.nodeUrl) + && Objects.equals(this.nodeIP, node.nodeIP) + && Objects.equals(this.peerCount, node.peerCount) + && Objects.equals(this.pendingTxn, node.pendingTxn) + && Objects.equals(this.mining, node.mining) + && Objects.equals(this.latestBlock, node.latestBlock); } @Override @@ -285,4 +312,3 @@ public String toString() { } } - diff --git a/cakeshop-client-java/src/main/java/com/jpmorgan/cakeshop/client/model/QuorumInfo.java b/cakeshop-client-java/src/main/java/com/jpmorgan/cakeshop/client/model/QuorumInfo.java new file mode 100644 index 00000000..8972653e --- /dev/null +++ b/cakeshop-client-java/src/main/java/com/jpmorgan/cakeshop/client/model/QuorumInfo.java @@ -0,0 +1,132 @@ +package com.jpmorgan.cakeshop.client.model; + +public class QuorumInfo { + + public static class BlockMakerStrategy { + + private int minBlockTime; + private int maxBlockTime; + private String status; + private String type; + + public BlockMakerStrategy() { + } + + public int getMinBlockTime() { + return minBlockTime; + } + + public void setMinBlockTime(int minBlockTime) { + this.minBlockTime = minBlockTime; + } + + public int getMaxBlockTime() { + return maxBlockTime; + } + + public void setMaxBlockTime(int maxBlockTime) { + this.maxBlockTime = maxBlockTime; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + } + + private Boolean isQuorum; + + private String blockMakerAccount; + + private Boolean canCreateBlocks; + + private String voteAccount; + + private String nodeKey; + + private Boolean canVote; + + private Boolean isConstellationEnabled; + + private BlockMakerStrategy blockMakerStrategy; + + public QuorumInfo() { + } + + public String getBlockMakerAccount() { + return blockMakerAccount; + } + + public void setBlockMakerAccount(String blockMakerAccount) { + this.blockMakerAccount = blockMakerAccount; + } + + public Boolean isCanCreateBlocks() { + return canCreateBlocks; + } + + public void setCanCreateBlocks(Boolean canCreateBlocks) { + this.canCreateBlocks = canCreateBlocks; + } + + public String getVoteAccount() { + return voteAccount; + } + + public void setVoteAccount(String voteAccount) { + this.voteAccount = voteAccount; + } + + public Boolean isCanVote() { + return canVote; + } + + public void setCanVote(Boolean canVote) { + this.canVote = canVote; + } + + public BlockMakerStrategy getBlockMakerStrategy() { + return blockMakerStrategy; + } + + public void setBlockMakerStrategy(BlockMakerStrategy blockMakerStrategy) { + this.blockMakerStrategy = blockMakerStrategy; + } + + public Boolean isQuorum() { + return isQuorum; + } + + public void setQuorum(Boolean isQuorum) { + this.isQuorum = isQuorum; + } + + public String getNodeKey() { + return nodeKey; + } + + public void setNodeKey(String nodeKey) { + this.nodeKey = nodeKey; + } + + public Boolean getIsConstellationEnabled() { + return isConstellationEnabled; + } + + public void setIsConstellationEnabled(Boolean isConstellationEnabled) { + this.isConstellationEnabled = isConstellationEnabled; + } + +} diff --git a/cakeshop-client-java/src/main/java/com/jpmorgan/cakeshop/client/model/req/NodeUpdateCommand.java b/cakeshop-client-java/src/main/java/com/jpmorgan/cakeshop/client/model/req/NodeUpdateCommand.java index 529226cf..d44acc79 100644 --- a/cakeshop-client-java/src/main/java/com/jpmorgan/cakeshop/client/model/req/NodeUpdateCommand.java +++ b/cakeshop-client-java/src/main/java/com/jpmorgan/cakeshop/client/model/req/NodeUpdateCommand.java @@ -2,11 +2,9 @@ public class NodeUpdateCommand { - private String logLevel; - private String networkId; + private String logLevel, genesisBlock, extraParams, networkId, blockMakerAccount, voterAccount, constellationNode, address; private Boolean committingTransactions; - private String extraParams; - private String genesisBlock; + private Integer minBlockTime, maxBlockTime; public NodeUpdateCommand() { } @@ -36,35 +34,158 @@ public NodeUpdateCommand genesisBlock(String genesisBlock) { return this; } + public NodeUpdateCommand blockMakerAccount(String blockMakerAccount) { + this.blockMakerAccount = blockMakerAccount; + return this; + } + + public NodeUpdateCommand voterAccount(String voterAccount) { + this.voterAccount = voterAccount; + return this; + } + + public NodeUpdateCommand constellationNode(String constellationNode) { + this.constellationNode = constellationNode; + return this; + } + + public NodeUpdateCommand address(String address) { + this.address = address; + return this; + } + + /** + * @return the address + */ + public String getAddress() { + return address; + } + + /** + * @param address the address to set + */ + public void setAddress(String address) { + this.address = address; + } + + public NodeUpdateCommand minBlockTime(Integer minBlockTime) { + this.minBlockTime = minBlockTime; + return this; + } + + public NodeUpdateCommand maxBlockTime(Integer maxBlockTime) { + this.maxBlockTime = maxBlockTime; + return this; + } + public String getLogLevel() { return logLevel; } + public void setLogLevel(String logLevel) { this.logLevel = logLevel; } + public String getNetworkId() { return networkId; } + public void setNetworkId(String networkId) { this.networkId = networkId; } + public Boolean getCommittingTransactions() { return committingTransactions; } + public void setCommittingTransactions(Boolean committingTransactions) { this.committingTransactions = committingTransactions; } + public String getExtraParams() { return extraParams; } + public void setExtraParams(String extraParams) { this.extraParams = extraParams; } + public String getGenesisBlock() { return genesisBlock; } + public void setGenesisBlock(String genesisBlock) { this.genesisBlock = genesisBlock; } + /** + * @return the blockMakerAccount + */ + public String getBlockMakerAccount() { + return blockMakerAccount; + } + + /** + * @param blockMakerAccount the blockMakerAccount to set + */ + public void setBlockMakerAccount(String blockMakerAccount) { + this.blockMakerAccount = blockMakerAccount; + } + + /** + * @return the voterAccount + */ + public String getVoterAccount() { + return voterAccount; + } + + /** + * @param voterAccount the voterAccount to set + */ + public void setVoterAccount(String voterAccount) { + this.voterAccount = voterAccount; + } + + /** + * @return the constellationNode + */ + public String getConstellationNode() { + return constellationNode; + } + + /** + * @param constellationNode the constellationNode to set + */ + public void setConstellationNode(String constellationNode) { + this.constellationNode = constellationNode; + } + + /** + * @return the minBlockTime + */ + public Integer getMinBlockTime() { + return minBlockTime; + } + + /** + * @param minBlockTime the minBlockTime to set + */ + public void setMinBlockTime(Integer minBlockTime) { + this.minBlockTime = minBlockTime; + } + + /** + * @return the maxBlockTime + */ + public Integer getMaxBlockTime() { + return maxBlockTime; + } + + /** + * @param maxBlockTime the maxBlockTime to set + */ + public void setMaxBlockTime(Integer maxBlockTime) { + this.maxBlockTime = maxBlockTime; + } + } diff --git a/cakeshop-client-js/pom.xml b/cakeshop-client-js/pom.xml index 328c66b2..72357ecb 100644 --- a/cakeshop-client-js/pom.xml +++ b/cakeshop-client-js/pom.xml @@ -6,7 +6,7 @@ com.jpmorgan cakeshop-parent - 0.9.1 + 0.10.0 cakeshop-client-js diff --git a/cakeshop-node-manager/README.md b/cakeshop-node-manager/README.md new file mode 100644 index 00000000..610b0313 --- /dev/null +++ b/cakeshop-node-manager/README.md @@ -0,0 +1,6 @@ +- To start node-manager in Spring Boot mode just execute this command java -jar cakeshop-node-manager.war +- To start it inside tomcat create setenv.sh(bat) file in tomcat/bin add following line to it -Dspring.profiles.active=container +- By default node-manager runs on embedded database (easy for development). It can also run on external database and we recommend to run it on external database when in production or staging mode. + The example how to configure external database for spring-boot is in application.properties + To run within tomcat using data source have this property added into properties file nodemanager.jndi.name= and nodemanager.database.vendor=oracle|postgres|mysql + or you can add -Dnodemanager.jndi.name= and -Dnodemanager.database.vendor=oracle|postgres|mysql into setenv.sh(bat) file diff --git a/cakeshop-node-manager/pom.xml b/cakeshop-node-manager/pom.xml new file mode 100644 index 00000000..ca45207c --- /dev/null +++ b/cakeshop-node-manager/pom.xml @@ -0,0 +1,129 @@ + + + 4.0.0 + + cakeshop-parent + com.jpmorgan + 0.10.0 + + + com.jpmorgan + cakeshop-node-manager + 0.10.0 + war + + + UTF-8 + 42.0.0.jre7 + + Cakeshop Node Manager + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + com.jpmorgan + cakeshop-client + 0.10.0 + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + + + org.hibernate + hibernate-core + + + mysql + mysql-connector-java + + + org.postgresql + postgresql + ${postgres.version} + + + org.hsqldb + hsqldb + + + + + + src/main/resources + true + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.1 + + ${java.version} + ${java.version} + + + + + org.apache.maven.plugins + maven-war-plugin + 3.0.0 + + false + + + true + + + **/node_modules/ + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + repackage + + + + + + + + diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/CakeshopNodeManager.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/CakeshopNodeManager.java new file mode 100644 index 00000000..bcad3fe0 --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/CakeshopNodeManager.java @@ -0,0 +1,39 @@ +package com.jpmorgan.cakeshop.manager; + +import java.util.concurrent.TimeUnit; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; +import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; +import org.springframework.boot.web.support.SpringBootServletInitializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +@Configuration +@ComponentScan +@EnableAutoConfiguration +public class CakeshopNodeManager extends SpringBootServletInitializer { + + private static final Class APPLICATION_CLASS = CakeshopNodeManager.class; + + public static void main(String[] args) { + SpringApplication.run(APPLICATION_CLASS, args); + } + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(APPLICATION_CLASS); + } + + @Bean + @Profile("spring-boot") + public EmbeddedServletContainerFactory servletContainer() { + TomcatEmbeddedServletContainerFactory factory + = new TomcatEmbeddedServletContainerFactory(); + factory.setSessionTimeout(15, TimeUnit.MINUTES); + return factory; + } +} diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/AppConfig.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/AppConfig.java new file mode 100644 index 00000000..39ecb2e2 --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/AppConfig.java @@ -0,0 +1,47 @@ +package com.jpmorgan.cakeshop.manager.config; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.jpmorgan.cakeshop.client.model.res.APIResponse; + +import java.util.List; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +@Configuration +@EnableWebMvc +public class AppConfig extends WebMvcConfigurerAdapter { + + @Bean + public static PropertySourcesPlaceholderConfigurer + propertySourcesPlaceholderConfigurer() { + return new PropertySourcesPlaceholderConfigurer(); + } + + @Override + public void configureMessageConverters(List> converters) { + MappingJackson2HttpMessageConverter jacksonMessageConverter = new MappingJackson2HttpMessageConverter(); + ObjectMapper objectMapper = jacksonMessageConverter.getObjectMapper(); + objectMapper.addMixIn(APIResponse.class, IgnoreAPIresponse.class); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + converters.add(jacksonMessageConverter); + } + + @Override + public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { + configurer.enable(); + } + + abstract class IgnoreAPIresponse { + + @JsonIgnore + public abstract Boolean getData(); + } +} diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/conditions/AbsentDataBaseConditon.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/conditions/AbsentDataBaseConditon.java new file mode 100644 index 00000000..bc682605 --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/conditions/AbsentDataBaseConditon.java @@ -0,0 +1,22 @@ +package com.jpmorgan.cakeshop.manager.config.conditions; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public class AbsentDataBaseConditon extends BaseCondition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + if (StringUtils.isBlank(databaseName)) { + databaseName = context.getEnvironment().getProperty("nodemanager.database.vendor"); + } + return StringUtils.isBlank(databaseName); + } + + @Override + public ConfigurationPhase getConfigurationPhase() { + return null; + } + +} diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/conditions/BaseCondition.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/conditions/BaseCondition.java new file mode 100644 index 00000000..d28ed78f --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/conditions/BaseCondition.java @@ -0,0 +1,14 @@ +package com.jpmorgan.cakeshop.manager.config.conditions; + +import org.springframework.context.annotation.ConfigurationCondition; + +public abstract class BaseCondition implements ConfigurationCondition { + + public final String ORACLE = "oracle"; + public final String MYSQL = "mysql"; + public final String POSTGRES = "postgres"; + public final String HSQL = "hsqldb"; + + public String databaseName = System.getProperty("nodemanager.database.vendor"); + +} diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/conditions/HsqlDataSourceConditon.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/conditions/HsqlDataSourceConditon.java new file mode 100644 index 00000000..d96731e9 --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/conditions/HsqlDataSourceConditon.java @@ -0,0 +1,22 @@ +package com.jpmorgan.cakeshop.manager.config.conditions; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public class HsqlDataSourceConditon extends BaseCondition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + if (StringUtils.isBlank(databaseName)) { + databaseName = context.getEnvironment().getProperty("nodemanager.database.vendor"); + } + return StringUtils.isNotBlank(databaseName) && databaseName.equalsIgnoreCase(HSQL); + } + + @Override + public ConfigurationPhase getConfigurationPhase() { + return ConfigurationPhase.PARSE_CONFIGURATION; + } + +} diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/conditions/MysqlDataSourceConditon.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/conditions/MysqlDataSourceConditon.java new file mode 100644 index 00000000..7efa34e5 --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/conditions/MysqlDataSourceConditon.java @@ -0,0 +1,21 @@ +package com.jpmorgan.cakeshop.manager.config.conditions; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public class MysqlDataSourceConditon extends BaseCondition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + if (StringUtils.isBlank(databaseName)) { + databaseName = context.getEnvironment().getProperty("nodemanager.database.vendor"); + } + return StringUtils.isNotBlank(databaseName) && databaseName.equalsIgnoreCase(MYSQL); + } + + @Override + public ConfigurationPhase getConfigurationPhase() { + return ConfigurationPhase.PARSE_CONFIGURATION; + } +} diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/conditions/OracleDataSourceConditon.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/conditions/OracleDataSourceConditon.java new file mode 100644 index 00000000..e969d205 --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/conditions/OracleDataSourceConditon.java @@ -0,0 +1,22 @@ +package com.jpmorgan.cakeshop.manager.config.conditions; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public class OracleDataSourceConditon extends BaseCondition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + if (StringUtils.isBlank(databaseName)) { + databaseName = context.getEnvironment().getProperty("nodemanager.database.vendor"); + } + return StringUtils.isNotBlank(databaseName) && databaseName.equalsIgnoreCase(ORACLE); + } + + @Override + public ConfigurationPhase getConfigurationPhase() { + return ConfigurationPhase.PARSE_CONFIGURATION; + } + +} diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/conditions/PostgresDataSourceConditon.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/conditions/PostgresDataSourceConditon.java new file mode 100644 index 00000000..0d86dc6f --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/conditions/PostgresDataSourceConditon.java @@ -0,0 +1,21 @@ +package com.jpmorgan.cakeshop.manager.config.conditions; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public class PostgresDataSourceConditon extends BaseCondition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + if (StringUtils.isBlank(databaseName)) { + databaseName = context.getEnvironment().getProperty("nodemanager.database.vendor"); + } + return StringUtils.isNotBlank(databaseName) && databaseName.equalsIgnoreCase(POSTGRES); + } + + @Override + public ConfigurationPhase getConfigurationPhase() { + return ConfigurationPhase.PARSE_CONFIGURATION; + } +} diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/db/AbstractDataSourceConfig.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/db/AbstractDataSourceConfig.java new file mode 100644 index 00000000..f195c2ef --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/db/AbstractDataSourceConfig.java @@ -0,0 +1,81 @@ +package com.jpmorgan.cakeshop.manager.config.db; + +import java.util.Properties; + +import javax.naming.NamingException; +import javax.sql.DataSource; +import org.apache.commons.lang3.StringUtils; + +import org.hibernate.SessionFactory; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup; +import org.springframework.orm.hibernate5.HibernateTemplate; +import org.springframework.orm.hibernate5.HibernateTransactionManager; +import org.springframework.orm.hibernate5.LocalSessionFactoryBean; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@Configuration +@EnableTransactionManagement +public abstract class AbstractDataSourceConfig implements ApplicationContextAware { + + protected static final org.slf4j.Logger LOG = LoggerFactory.getLogger(AbstractDataSourceConfig.class); + private final String JNDI_NAME_PROP = "nodemanager.jndi.name"; + private final String JNDI_NAME = System.getProperty(JNDI_NAME_PROP); + + protected final String JDBC_URL = "nodemanager.jdbc.url"; + protected final String JDBC_USER = "nodemanager.jdbc.user"; + protected final String JDBC_PASS = "nodemanager.jdbc.pass"; + protected final String HBM_2DDL_AUTO = "nodemanager.hibernate.hbm2ddl.auto"; + protected final String HIBERNATE_DIALECT = "nodemanager.hibernate.dialect"; + public static final String JDBC_BATCH_SIZE = "nodemanager.hibernate.jdbc.batch_size"; + + @Autowired + protected Environment env; + + protected ApplicationContext applicationContext; + + @Bean + public HibernateTemplate hibernateTemplate(SessionFactory sessionFactory) { + return new HibernateTemplate(sessionFactory); + } + + @Bean + public LocalSessionFactoryBean sessionFactory() throws ClassNotFoundException, NamingException { + LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean(); + sessionFactory.setDataSource(dataSource()); + sessionFactory.setPackagesToScan(new String[]{"com.jpmorgan.cakeshop.manager.db.entity"}); + sessionFactory.setHibernateProperties(hibernateProperties()); + + return sessionFactory; + } + + @Bean + public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) throws ClassNotFoundException, NamingException { + HibernateTransactionManager txManager = new HibernateTransactionManager(); + txManager.setSessionFactory(sessionFactory); + return txManager; + } + + protected abstract Properties hibernateProperties(); + + protected abstract DataSource getSimpleDataSource() throws ClassNotFoundException; + + @Bean + public DataSource dataSource() throws ClassNotFoundException, NamingException { + String jndiName = StringUtils.isNotBlank(JNDI_NAME) ? JNDI_NAME : env.getProperty(JNDI_NAME_PROP); + if (StringUtils.isNotBlank(jndiName)) { + final JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup(); + dataSourceLookup.setResourceRef(true); + DataSource dataSource = dataSourceLookup.getDataSource(jndiName); + return dataSource; + } else { + return getSimpleDataSource(); + } + } +} diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/db/EmbeddedDbDataSourceConfig.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/db/EmbeddedDbDataSourceConfig.java new file mode 100644 index 00000000..bfb2d1a0 --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/db/EmbeddedDbDataSourceConfig.java @@ -0,0 +1,158 @@ +package com.jpmorgan.cakeshop.manager.config.db; + +import com.jpmorgan.cakeshop.manager.config.conditions.HsqlDataSourceConditon; + +import java.sql.Driver; +import java.util.Properties; + +import javax.annotation.PreDestroy; +import javax.sql.DataSource; + +import org.hibernate.SessionFactory; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; +import org.springframework.jdbc.datasource.embedded.ConnectionProperties; +import org.springframework.jdbc.datasource.embedded.DataSourceFactory; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.orm.hibernate5.HibernateTemplate; +import org.springframework.orm.hibernate5.HibernateTransactionManager; +import org.springframework.orm.hibernate5.LocalSessionFactoryBean; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@Conditional(HsqlDataSourceConditon.class) +@Configuration +@EnableTransactionManagement +public class EmbeddedDbDataSourceConfig implements ApplicationContextAware { + + protected static final org.slf4j.Logger LOG = LoggerFactory.getLogger(EmbeddedDbDataSourceConfig.class); + + @Value("${hibernate.hbm2ddl.auto:validate}") + private String hibernateAuto; + + @Value("${hibernate.dialect:org.hibernate.dialect.HSQLDialect}") + private String hibernateDialect; + + @Value("${hibernate.jdbc.batch_size:20}") + private String hibernateBatchSize; + + private ApplicationContext applicationContext; + + private EmbeddedDatabase embeddedDb; + + @Bean + @Autowired + public HibernateTemplate hibernateTemplate(SessionFactory sessionFactory) { + return new HibernateTemplate(sessionFactory); + } + + @Bean + public LocalSessionFactoryBean sessionFactory() { + LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean(); + sessionFactory.setDataSource(applicationContext.getBean(DataSource.class)); + sessionFactory.setPackagesToScan(new String[]{"com.jpmorgan.cakeshop.node.manager.db.entity"}); + sessionFactory.setHibernateProperties(hibernateProperties()); + + return sessionFactory; + } + + @Bean + @Autowired + public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) { + HibernateTransactionManager txManager = new HibernateTransactionManager(); + txManager.setSessionFactory(sessionFactory); + return txManager; + } + + @SuppressWarnings("serial") + Properties hibernateProperties() { + return new Properties() { + { + setProperty("hibernate.jdbc.batch_size", hibernateBatchSize); + setProperty("hibernate.hbm2ddl.auto", hibernateAuto); + setProperty("hibernate.dialect", hibernateDialect); + setProperty("hibernate.default_schema", "PUBLIC"); + } + }; + } + + public DataSource createDataSource() { + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(); + dataSource.setDriverClass(org.hsqldb.jdbcDriver.class); + dataSource.setUrl("jdbc:hsqldb:file:" + getDbStoragePath() + ";hsqldb.default_table_type=cached"); + dataSource.setUsername("nmngr"); + dataSource.setPassword("nmngr"); + return dataSource; + } + + public String getDbStoragePath() { + return System.getProperty("user.dir").concat("/embedded/nmg"); + } + + @Bean(name = "hsql") + @Order(0) + public DataSource startDb() { + LOG.debug("USING Embedded HSQL DB"); + DataSourceFactory dataSourceFactory = new DataSourceFactory() { + @Override + public DataSource getDataSource() { + return createDataSource(); + } + + @Override + public ConnectionProperties getConnectionProperties() { + return new ConnectionProperties() { + @Override + public void setUsername(String username) { + } + + @Override + public void setUrl(String url) { + } + + @Override + public void setPassword(String password) { + } + + @Override + public void setDriverClass(Class driverClass) { + } + }; + } + }; + + this.embeddedDb = new EmbeddedDatabaseBuilder() + .generateUniqueName(true) + .setType(EmbeddedDatabaseType.HSQL) + .setScriptEncoding("UTF-8") + .setDataSourceFactory(dataSourceFactory) + .ignoreFailedDrops(true) + .build(); + + return this.embeddedDb; + } + + @PreDestroy + public void shutdownDb() { + if (this.embeddedDb != null) { + this.embeddedDb.shutdown(); + } + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + + } + +} diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/db/MysqlDataSourceConfig.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/db/MysqlDataSourceConfig.java new file mode 100644 index 00000000..dc312d35 --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/db/MysqlDataSourceConfig.java @@ -0,0 +1,60 @@ +package com.jpmorgan.cakeshop.manager.config.db; + +import com.jpmorgan.cakeshop.manager.config.conditions.MysqlDataSourceConditon; + +import java.util.Properties; +import javax.sql.DataSource; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; + +@Configuration +@Conditional(MysqlDataSourceConditon.class) +public class MysqlDataSourceConfig extends AbstractDataSourceConfig { + + @Bean + public PersistenceExceptionTranslationPostProcessor exceptionTranslation() { + return new PersistenceExceptionTranslationPostProcessor(); + } + + @Override + protected Properties hibernateProperties() { + LOG.debug("USING MYSQL HIBERNATE DIALECT"); + return new Properties() { + { + setProperty("hibernate.jdbc.batch_size", StringUtils.isNotBlank(System.getProperty(JDBC_BATCH_SIZE)) + ? System.getProperty(JDBC_BATCH_SIZE) : env.getProperty(JDBC_BATCH_SIZE, "20")); + setProperty("hibernate.hbm2ddl.auto", StringUtils.isNotBlank(System.getProperty(HBM_2DDL_AUTO)) + ? System.getProperty(HBM_2DDL_AUTO) : env.getProperty(HBM_2DDL_AUTO, "update")); + setProperty("hibernate.dialect", StringUtils.isNotBlank(System.getProperty(HIBERNATE_DIALECT)) + ? System.getProperty(HIBERNATE_DIALECT) : env.getProperty(HIBERNATE_DIALECT, "org.hibernate.dialect.MySQLDialect")); + setProperty("hibernate.id.new_generator_mappings", "true"); + } + }; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + @Override + protected DataSource getSimpleDataSource() throws ClassNotFoundException { + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(); + dataSource.setDriverClass(com.mysql.jdbc.Driver.class); + dataSource.setUrl(StringUtils.isNotBlank(System.getProperty(JDBC_URL)) + ? System.getProperty(JDBC_URL) : env.getProperty(JDBC_URL)); + dataSource.setUsername(StringUtils.isNotBlank(System.getProperty(JDBC_USER)) + ? System.getProperty(JDBC_USER) : env.getProperty(JDBC_USER)); + dataSource.setPassword(StringUtils.isNotBlank(System.getProperty(JDBC_PASS)) + ? System.getProperty(JDBC_PASS) : env.getProperty(JDBC_PASS)); + return dataSource; + } + +} diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/db/OracleDataSourceConfig.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/db/OracleDataSourceConfig.java new file mode 100644 index 00000000..2502b41d --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/db/OracleDataSourceConfig.java @@ -0,0 +1,62 @@ +package com.jpmorgan.cakeshop.manager.config.db; + +import com.jpmorgan.cakeshop.manager.config.conditions.OracleDataSourceConditon; +import java.util.Properties; + +import javax.sql.DataSource; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; + +@Configuration +@Conditional(OracleDataSourceConditon.class) +public class OracleDataSourceConfig extends AbstractDataSourceConfig { + + private static final String ORACLE_DRIVER_CLASS = "oracle.jdbc.OracleDriver"; + + @Bean + public PersistenceExceptionTranslationPostProcessor exceptionTranslation() { + return new PersistenceExceptionTranslationPostProcessor(); + } + + @Override + protected Properties hibernateProperties() { + LOG.debug("USING ORACLE HIBERNATE DIALECT"); + return new Properties() { + { + setProperty("hibernate.jdbc.batch_size", StringUtils.isNotBlank(System.getProperty(JDBC_BATCH_SIZE)) + ? System.getProperty(JDBC_BATCH_SIZE) : env.getProperty(JDBC_BATCH_SIZE, "20")); + setProperty("hibernate.hbm2ddl.auto", StringUtils.isNotBlank(System.getProperty(HBM_2DDL_AUTO)) + ? System.getProperty(HBM_2DDL_AUTO) : env.getProperty(HBM_2DDL_AUTO, "update")); + setProperty("hibernate.dialect", StringUtils.isNotBlank(System.getProperty(HIBERNATE_DIALECT)) + ? System.getProperty(HIBERNATE_DIALECT) : env.getProperty(HIBERNATE_DIALECT, "org.hibernate.dialect.Oracle10gDialect")); + } + }; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + @Override + protected DataSource getSimpleDataSource() throws ClassNotFoundException { + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(); + Class clazz = Class.forName(ORACLE_DRIVER_CLASS); + dataSource.setDriverClass(clazz); + dataSource.setUrl(StringUtils.isNotBlank(System.getProperty(JDBC_URL)) + ? System.getProperty(JDBC_URL) : env.getProperty(JDBC_URL)); + dataSource.setUsername(StringUtils.isNotBlank(System.getProperty(JDBC_USER)) + ? System.getProperty(JDBC_USER) : env.getProperty(JDBC_USER)); + dataSource.setPassword(StringUtils.isNotBlank(System.getProperty(JDBC_PASS)) + ? System.getProperty(JDBC_PASS) : env.getProperty(JDBC_PASS)); + return dataSource; + } + +} diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/db/PostgresDataSourceConfig.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/db/PostgresDataSourceConfig.java new file mode 100644 index 00000000..2bc11ba3 --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/config/db/PostgresDataSourceConfig.java @@ -0,0 +1,58 @@ +package com.jpmorgan.cakeshop.manager.config.db; + +import com.jpmorgan.cakeshop.manager.config.conditions.PostgresDataSourceConditon; +import java.util.Properties; +import javax.sql.DataSource; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; + +@Configuration +@Conditional(PostgresDataSourceConditon.class) +public class PostgresDataSourceConfig extends AbstractDataSourceConfig { + + @Bean + public PersistenceExceptionTranslationPostProcessor exceptionTranslation() { + return new PersistenceExceptionTranslationPostProcessor(); + } + + @Override + protected Properties hibernateProperties() { + LOG.info("USING POSTGRES HIBERNATE DIALECT"); + return new Properties() { + { + setProperty("hibernate.jdbc.batch_size", + StringUtils.isNotBlank(System.getProperty(JDBC_BATCH_SIZE)) + ? System.getProperty(JDBC_BATCH_SIZE) : env.getProperty(JDBC_BATCH_SIZE, "20")); + setProperty("hibernate.hbm2ddl.auto", + StringUtils.isNotBlank(System.getProperty(HBM_2DDL_AUTO)) + ? System.getProperty(HBM_2DDL_AUTO) : env.getProperty(HBM_2DDL_AUTO, "update")); + setProperty("hibernate.dialect", + StringUtils.isNotBlank(System.getProperty(HIBERNATE_DIALECT)) + ? System.getProperty(HIBERNATE_DIALECT) : env.getProperty(HIBERNATE_DIALECT, "org.hibernate.dialect.PostgreSQLDialect")); + } + }; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + @Override + protected DataSource getSimpleDataSource() throws ClassNotFoundException { + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(); + dataSource.setDriverClass(org.postgresql.Driver.class); + dataSource.setUrl(StringUtils.isNotBlank(System.getProperty(JDBC_URL)) ? System.getProperty(JDBC_URL) : env.getProperty(JDBC_URL)); + dataSource.setUsername(StringUtils.isNotBlank(System.getProperty(JDBC_USER)) ? System.getProperty(JDBC_USER) : env.getProperty(JDBC_USER)); + dataSource.setPassword(StringUtils.isNotBlank(System.getProperty(JDBC_PASS)) ? System.getProperty(JDBC_PASS) : env.getProperty(JDBC_PASS)); + return dataSource; + } + +} diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/controller/NodeManagerController.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/controller/NodeManagerController.java new file mode 100644 index 00000000..c2e32a60 --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/controller/NodeManagerController.java @@ -0,0 +1,135 @@ +package com.jpmorgan.cakeshop.manager.controller; + +import com.jpmorgan.cakeshop.client.model.Node; +import com.jpmorgan.cakeshop.client.model.req.NodeUpdateCommand; +import com.jpmorgan.cakeshop.client.model.res.APIData; +import com.jpmorgan.cakeshop.client.model.res.APIResponse; +import com.jpmorgan.cakeshop.manager.model.json.NodeJsonRequest; +import com.jpmorgan.cakeshop.manager.service.NodeManagerService; + +import org.springframework.beans.factory.annotation.Autowired; +import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import static org.springframework.web.bind.annotation.RequestMethod.POST; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(value = "/api/node", method = POST, consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE) +public class NodeManagerController { + + @Autowired + private NodeManagerService service; + + @RequestMapping({"/get"}) + protected ResponseEntity, Node>> get(@RequestBody NodeJsonRequest jsonRequest) { + APIResponse, Node> resposnse = service.get(jsonRequest.getCakeshopUrl(), jsonRequest.getCred1(), jsonRequest.getCred2()); + return new ResponseEntity<>(resposnse, OK); + } + + @RequestMapping("/update") + public ResponseEntity update(@RequestBody NodeJsonRequest jsonRequest) { + + NodeUpdateCommand command = new NodeUpdateCommand() + .extraParams(jsonRequest.getExtraParams()) + .blockMakerAccount(jsonRequest.getBlockMakerAccount()) + .logLevel(jsonRequest.getLogLevel()) + .networkId(jsonRequest.getNetworkId()) + .commitingTransactions(jsonRequest.getCommittingTransactions()) + .genesisBlock(jsonRequest.getGenesisBlock()) + .voterAccount(jsonRequest.getVoterAccount()) + .minBlockTime(jsonRequest.getMinBlockTime()) + .maxBlockTime(jsonRequest.getMaxBlockTime()); + APIResponse resposnse = service.update(jsonRequest.getCakeshopUrl(), jsonRequest.getCred1(), jsonRequest.getCred2(), command); + return new ResponseEntity<>(resposnse, OK); + } + + @RequestMapping("/start") + protected @ResponseBody + ResponseEntity start(@RequestBody NodeJsonRequest jsonRequest) { + APIResponse resposnse = service.start(jsonRequest.getCakeshopUrl(), jsonRequest.getCred1(), jsonRequest.getCred2()); + return new ResponseEntity<>(resposnse, OK); + } + + @RequestMapping("/stop") + protected @ResponseBody + ResponseEntity stop(@RequestBody NodeJsonRequest jsonRequest) { + APIResponse resposnse = service.stop(jsonRequest.getCakeshopUrl(), jsonRequest.getCred1(), jsonRequest.getCred2()); + return new ResponseEntity<>(resposnse, OK); + } + + @RequestMapping("/restart") + protected @ResponseBody + ResponseEntity restart(@RequestBody NodeJsonRequest jsonRequest) { + APIResponse resposnse = service.restart(jsonRequest.getCakeshopUrl(), jsonRequest.getCred1(), jsonRequest.getCred2()); + return new ResponseEntity<>(resposnse, OK); + } + + @RequestMapping("/reset") + protected @ResponseBody + ResponseEntity reset(@RequestBody NodeJsonRequest jsonRequest) { + APIResponse resposnse = service.reset(jsonRequest.getCakeshopUrl(), jsonRequest.getCred1(), jsonRequest.getCred2()); + return new ResponseEntity<>(resposnse, OK); + } + + @RequestMapping("/settings/reset") + protected @ResponseBody + ResponseEntity resetNode(@RequestBody NodeJsonRequest jsonRequest) { + APIResponse resposnse = service.resetNode(jsonRequest.getCakeshopUrl(), jsonRequest.getCred1(), jsonRequest.getCred2()); + return new ResponseEntity<>(resposnse, OK); + } + + @RequestMapping("/peers/add") + public ResponseEntity addPeer(@RequestBody NodeJsonRequest jsonRequest) { + NodeUpdateCommand command = new NodeUpdateCommand().address(jsonRequest.getAddress()); + APIResponse resposnse = service.addPeer(jsonRequest.getCakeshopUrl(), jsonRequest.getCred1(), jsonRequest.getCred2(), command); + return new ResponseEntity<>(resposnse, OK); + } + + @RequestMapping("/peers") + public ResponseEntity peers(@RequestBody NodeJsonRequest jsonRequest) { + APIResponse resposnse = service.peers(jsonRequest.getCakeshopUrl(), jsonRequest.getCred1(), jsonRequest.getCred2()); + return new ResponseEntity<>(resposnse.toString(), OK); + } + + @RequestMapping("/constellation/list") + protected @ResponseBody + ResponseEntity getConstellationList(@RequestBody NodeJsonRequest jsonRequest) { + APIResponse resposnse = service.constellationList(jsonRequest.getCakeshopUrl(), jsonRequest.getCred1(), jsonRequest.getCred2()); + return new ResponseEntity<>(resposnse, OK); + } + + @RequestMapping("/constellation/add") + protected @ResponseBody + ResponseEntity addConstellation(@RequestBody NodeJsonRequest jsonRequest) { + NodeUpdateCommand command = new NodeUpdateCommand().constellationNode(jsonRequest.getConstellationNode()); + APIResponse resposnse = service.addConstellation(jsonRequest.getCakeshopUrl(), jsonRequest.getCred1(), jsonRequest.getCred2(), command); + return new ResponseEntity<>(resposnse, OK); + } + + @RequestMapping("/constellation/remove") + protected @ResponseBody + ResponseEntity removeConstellation(@RequestBody NodeJsonRequest jsonRequest) { + NodeUpdateCommand command = new NodeUpdateCommand().constellationNode(jsonRequest.getConstellationNode()); + APIResponse resposnse = service.addConstellation(jsonRequest.getCakeshopUrl(), jsonRequest.getCred1(), jsonRequest.getCred2(), command); + return new ResponseEntity<>(resposnse, OK); + } + + @RequestMapping("/constellation/stop") + protected @ResponseBody + ResponseEntity stopConstellation(@RequestBody NodeJsonRequest jsonRequest) { + APIResponse resposnse = service.stopConstellation(jsonRequest.getCakeshopUrl(), jsonRequest.getCred1(), jsonRequest.getCred2()); + return new ResponseEntity<>(resposnse, OK); + } + + @RequestMapping("/constellation/start") + protected @ResponseBody + ResponseEntity startConstellation(@RequestBody NodeJsonRequest jsonRequest) { + APIResponse resposnse = service.startConstellation(jsonRequest.getCakeshopUrl(), jsonRequest.getCred1(), jsonRequest.getCred2()); + return new ResponseEntity<>(resposnse, OK); + } + +} diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/controller/SaveNodeController.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/controller/SaveNodeController.java new file mode 100644 index 00000000..f715daad --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/controller/SaveNodeController.java @@ -0,0 +1,118 @@ +package com.jpmorgan.cakeshop.manager.controller; + +import com.jpmorgan.cakeshop.client.model.Node; +import com.jpmorgan.cakeshop.client.model.req.NodeUpdateCommand; +import com.jpmorgan.cakeshop.client.model.res.APIData; +import com.jpmorgan.cakeshop.client.model.res.APIResponse; +import com.jpmorgan.cakeshop.client.model.res.SimpleResult; +import com.jpmorgan.cakeshop.manager.db.entity.RemoteNode; +import com.jpmorgan.cakeshop.manager.service.NodeManagerService; +import com.jpmorgan.cakeshop.manager.service.SaveNodeService; +import com.jpmorgan.cakeshop.manager.utils.Utils; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.function.Function; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import static org.springframework.web.bind.annotation.RequestMethod.POST; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(value = "/api/node", method = POST, consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE) +public class SaveNodeController { + + @Autowired + private SaveNodeService service; + + @Autowired + private NodeManagerService nodeService; + + @RequestMapping({"/db/save"}) + protected Boolean saveRemoteNode(@RequestBody RemoteNode node) throws URISyntaxException { + URI uri = new URI(node.getNodeAddress()); + node.setId(uri.getUserInfo()); + if (StringUtils.isNotBlank(node.getCred2())) { + node.setCred2(Base64.encodeBase64String(node.getCred2().getBytes())); + } + service.insert(node); + return true; + } + + @RequestMapping({"/db/update"}) + protected Boolean updateRemoteNode(@RequestBody RemoteNode node) { + if (StringUtils.isNotBlank(node.getCred2())) { + node.setCred2(Base64.encodeBase64String(node.getCred2().getBytes())); + } + service.update(node); + return true; + } + + @RequestMapping({"/db/list"}) + protected List listRemoteNodes() { + return service.getRemoteNodesList(); + } + + @RequestMapping({"/db/node"}) + protected RemoteNode getRemoteNode(@RequestBody RemoteNode node) { + return service.getNode(node.getId()); + } + + @RequestMapping({"/cluster/setup"}) + protected Boolean setupCluster() { + + Boolean success = false; + final List nodes = service.getRemoteNodesList(); + + for (RemoteNode node : nodes) { + for (RemoteNode otherNode : getOtherNodes(nodes, node.getUrl())) { + String cred2 = null; + if (StringUtils.isNotBlank(otherNode.getCred2())) { + cred2 = new String(Base64.decodeBase64(otherNode.getCred2())); + } + if (!node.isClustered()) { + success = setupCluster(node.getUrl(), otherNode.getNodeAddress(), otherNode.getConstellationUrl(), otherNode.getCred1(), cred2); + //make node clustered + node.setIsClustered(Boolean.TRUE); + service.update(node); + } else if (node.isClustered() && !otherNode.isClustered()) { + success = setupCluster(node.getUrl(), otherNode.getNodeAddress(), otherNode.getConstellationUrl(), otherNode.getCred1(), cred2); + } + } + } + return success; + } + + private List getOtherNodes(List nodes, String currentNodeUrl) { + Function, List>> otherNodesFunction = Utils::otherNodes; + return otherNodesFunction.apply(currentNodeUrl).apply(nodes); + } + + private Boolean setupCluster(String currentNodeUrl, String otherNodeAddress, String constellationUrl, String cred1, String cred2) { + + //add remote node + Boolean success = false; + NodeUpdateCommand command = new NodeUpdateCommand(); + + if (StringUtils.isNotBlank(otherNodeAddress)) { + command.address(otherNodeAddress); + APIResponse, Boolean> result = nodeService.addPeer(currentNodeUrl, cred1, cred2, command); + success = result.getErrors() == null || result.getErrors().isEmpty(); + } + + //add remote node constellation + if (StringUtils.isNotBlank(constellationUrl)) { + command = new NodeUpdateCommand(); + command.constellationNode(constellationUrl); + APIResponse, Node> result = nodeService.addConstellation(currentNodeUrl, cred1, cred2, command); + success = result.getErrors() == null || result.getErrors().isEmpty(); + } + + return success; + } + +} diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/dao/BaseDAO.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/dao/BaseDAO.java new file mode 100644 index 00000000..6076fa74 --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/dao/BaseDAO.java @@ -0,0 +1,30 @@ +package com.jpmorgan.cakeshop.manager.dao; + +import static com.jpmorgan.cakeshop.manager.config.db.AbstractDataSourceConfig.JDBC_BATCH_SIZE; +import org.apache.commons.lang3.StringUtils; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; + +public abstract class BaseDAO { + + @Value(value = "${" + JDBC_BATCH_SIZE + "}:20") + private String batchSize; + + @Autowired(required = false) + private SessionFactory sessionFactory; + + protected final Integer BATCH_SIZE = StringUtils.isNotBlank(System.getProperty(JDBC_BATCH_SIZE)) + ? Integer.valueOf(System.getProperty(JDBC_BATCH_SIZE)) + : StringUtils.isNotBlank(batchSize) ? Integer.valueOf(batchSize) + : 20; + + protected Session getCurrentSession() { + Session session = null != sessionFactory ? sessionFactory.getCurrentSession() : null; + return session; + } + + public abstract void reset(); + +} diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/dao/RemoteNodeDAO.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/dao/RemoteNodeDAO.java new file mode 100644 index 00000000..420809fd --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/dao/RemoteNodeDAO.java @@ -0,0 +1,35 @@ +package com.jpmorgan.cakeshop.manager.dao; + +import com.jpmorgan.cakeshop.manager.db.entity.RemoteNode; +import java.util.List; +import org.hibernate.Criteria; +import org.hibernate.criterion.Restrictions; +import org.springframework.stereotype.Repository; + +@Repository +public class RemoteNodeDAO extends BaseDAO { + + public void insert(RemoteNode node) { + getCurrentSession().save(node); + } + + public void update(RemoteNode node) { + getCurrentSession().update(node); + } + + public RemoteNode getRemoteNode(String id) { + Criteria criteria = getCurrentSession().createCriteria(RemoteNode.class); + criteria.add(Restrictions.eq("id", id)); + return (RemoteNode) criteria.uniqueResult(); + } + + public List getRemoteNodesList() { + return getCurrentSession().createCriteria(RemoteNode.class).list(); + } + + @Override + public void reset() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + +} diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/db/entity/RemoteNode.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/db/entity/RemoteNode.java new file mode 100644 index 00000000..b3506508 --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/db/entity/RemoteNode.java @@ -0,0 +1,104 @@ +package com.jpmorgan.cakeshop.manager.db.entity; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Index; +import javax.persistence.Table; + +@Entity +@Table(name = "REMOTE_NODES", indexes = { + @Index(name = "remote_node_indx", columnList = "id")}) +public class RemoteNode { + + @Id + private String id; + private String url; + private String constellationUrl; + private String nodeAddress; + private String cred1, cred2; + private Boolean clustered = false; + + /** + * @return the id + */ + public String getId() { + return id; + } + + /** + * @param id the id to set + */ + public void setId(String id) { + this.id = id; + } + + /** + * @return the url + */ + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getConstellationUrl() { + return constellationUrl; + } + + public void setConstellationUrl(String constellationUrl) { + this.constellationUrl = constellationUrl; + } + + /** + * @return the nodeAddress + */ + public String getNodeAddress() { + return nodeAddress; + } + + /** + * @param nodeAddress the nodeAddress to set + */ + public void setNodeAddress(String nodeAddress) { + this.nodeAddress = nodeAddress; + } + + /** + * @return the cred1 + */ + public String getCred1() { + return cred1; + } + + /** + * @param cred1 the cred1 to set + */ + public void setCred1(String cred1) { + this.cred1 = cred1; + } + + /** + * @return the cred2 + */ + public String getCred2() { + return cred2; + } + + /** + * @param cred2 the cred2 to set + */ + public void setCred2(String cred2) { + this.cred2 = cred2; + } + + public Boolean isClustered() { + return clustered; + } + + public void setIsClustered(Boolean isClustered) { + this.clustered = isClustered; + } + +} diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/model/json/NodeJsonRequest.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/model/json/NodeJsonRequest.java new file mode 100644 index 00000000..c10596ad --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/model/json/NodeJsonRequest.java @@ -0,0 +1,206 @@ +package com.jpmorgan.cakeshop.manager.model.json; + +public class NodeJsonRequest { + + private String cakeshopUrl, cred1, cred2, logLevel, genesisBlock, extraParams, networkId, + blockMakerAccount, voterAccount, constellationNode, address; + private Boolean committingTransactions; + private Integer minBlockTime, maxBlockTime; + + /** + * @return the cakeshopUrl + */ + public String getCakeshopUrl() { + return cakeshopUrl; + } + + /** + * @param cakeshopUrl the cakeshopUrl to set + */ + public void setCakeshopUrl(String cakeshopUrl) { + this.cakeshopUrl = cakeshopUrl; + } + + /** + * @return the cred1 + */ + public String getCred1() { + return cred1; + } + + /** + * @param cred1 the cred1 to set + */ + public void setCred1(String cred1) { + this.cred1 = cred1; + } + + /** + * @return the cred2 + */ + public String getCred2() { + return cred2; + } + + /** + * @param cred2 the cred2 to set + */ + public void setCred2(String cred2) { + this.cred2 = cred2; + } + + /** + * @return the logLevel + */ + public String getLogLevel() { + return logLevel; + } + + /** + * @param logLevel the logLevel to set + */ + public void setLogLevel(String logLevel) { + this.logLevel = logLevel; + } + + /** + * @return the genesisBlock + */ + public String getGenesisBlock() { + return genesisBlock; + } + + /** + * @param genesisBlock the genesisBlock to set + */ + public void setGenesisBlock(String genesisBlock) { + this.genesisBlock = genesisBlock; + } + + /** + * @return the extraParams + */ + public String getExtraParams() { + return extraParams; + } + + /** + * @param extraParams the extraParams to set + */ + public void setExtraParams(String extraParams) { + this.extraParams = extraParams; + } + + /** + * @return the networkId + */ + public String getNetworkId() { + return networkId; + } + + /** + * @param networkId the networkId to set + */ + public void setNetworkId(String networkId) { + this.networkId = networkId; + } + + /** + * @return the blockMakerAccount + */ + public String getBlockMakerAccount() { + return blockMakerAccount; + } + + /** + * @param blockMakerAccount the blockMakerAccount to set + */ + public void setBlockMakerAccount(String blockMakerAccount) { + this.blockMakerAccount = blockMakerAccount; + } + + /** + * @return the voterAccount + */ + public String getVoterAccount() { + return voterAccount; + } + + /** + * @param voterAccount the voterAccount to set + */ + public void setVoterAccount(String voterAccount) { + this.voterAccount = voterAccount; + } + + /** + * @return the constellationNode + */ + public String getConstellationNode() { + return constellationNode; + } + + /** + * @param constellationNode the constellationNode to set + */ + public void setConstellationNode(String constellationNode) { + this.constellationNode = constellationNode; + } + + /** + * @return the address + */ + public String getAddress() { + return address; + } + + /** + * @param address the address to set + */ + public void setAddress(String address) { + this.address = address; + } + + /** + * @return the committingTransactions + */ + public Boolean getCommittingTransactions() { + return committingTransactions; + } + + /** + * @param committingTransactions the committingTransactions to set + */ + public void setCommittingTransactions(Boolean committingTransactions) { + this.committingTransactions = committingTransactions; + } + + /** + * @return the minBlockTime + */ + public Integer getMinBlockTime() { + return minBlockTime; + } + + /** + * @param minBlockTime the minBlockTime to set + */ + public void setMinBlockTime(Integer minBlockTime) { + this.minBlockTime = minBlockTime; + } + + /** + * @return the maxBlockTime + */ + public Integer getMaxBlockTime() { + return maxBlockTime; + } + + /** + * @param maxBlockTime the maxBlockTime to set + */ + public void setMaxBlockTime(Integer maxBlockTime) { + this.maxBlockTime = maxBlockTime; + } + +} diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/service/NodeManagerService.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/service/NodeManagerService.java new file mode 100644 index 00000000..1e709ea2 --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/service/NodeManagerService.java @@ -0,0 +1,43 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jpmorgan.cakeshop.manager.service; + +import com.jpmorgan.cakeshop.client.model.Node; +import com.jpmorgan.cakeshop.client.model.req.NodeUpdateCommand; +import com.jpmorgan.cakeshop.client.model.res.APIData; +import com.jpmorgan.cakeshop.client.model.res.APIResponse; +import com.jpmorgan.cakeshop.client.model.res.SimpleResult; + +public interface NodeManagerService { + + public APIResponse, Node> get(final String cakeshopUrl, final String cred1, final String cred2); + + public APIResponse, Node> update(final String cakeshopUrl, final String cred1, final String cred2, final NodeUpdateCommand command); + + public APIResponse, Boolean> start(final String cakeshopUrl, final String cred1, final String cred2); + + public APIResponse, Boolean> stop(final String cakeshopUrl, final String cred1, final String cred2); + + public APIResponse, Boolean> restart(final String cakeshopUrl, final String cred1, final String cred2); + + public APIResponse, Boolean> reset(final String cakeshopUrl, final String cred1, final String cred2); + + public APIResponse, Boolean> constellationList(final String cakeshopUrl, final String cred1, final String cred2); + + public APIResponse, Node> addConstellation(final String cakeshopUrl, final String cred1, final String cred2, final NodeUpdateCommand command); + + public APIResponse, Node> removeConstellationNode(final String cakeshopUrl, final String cred1, final String cred2, final NodeUpdateCommand command); + + public APIResponse, Boolean> stopConstellation(final String cakeshopUrl, final String cred1, final String cred2); + + public APIResponse, Boolean> startConstellation(final String cakeshopUrl, final String cred1, final String cred2); + + public APIResponse, Boolean> addPeer(final String cakeshopUrl, final String cred1, final String cred2, final NodeUpdateCommand command); + + public APIResponse peers(final String cakeshopUrl, final String cred1, final String cred2); + + public APIResponse, Boolean> resetNode(final String cakeshopUrl, final String cred1, final String cred2); +} diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/service/SaveNodeService.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/service/SaveNodeService.java new file mode 100644 index 00000000..dd41bbfd --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/service/SaveNodeService.java @@ -0,0 +1,15 @@ +package com.jpmorgan.cakeshop.manager.service; + +import com.jpmorgan.cakeshop.manager.db.entity.RemoteNode; +import java.util.List; + +public interface SaveNodeService { + + public void insert(RemoteNode node); + + public void update(RemoteNode node); + + public RemoteNode getNode(String id); + + public List getRemoteNodesList(); +} diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/service/impl/NodeManagerServiceImpl.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/service/impl/NodeManagerServiceImpl.java new file mode 100644 index 00000000..f27eae40 --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/service/impl/NodeManagerServiceImpl.java @@ -0,0 +1,98 @@ +package com.jpmorgan.cakeshop.manager.service.impl; + +import com.jpmorgan.cakeshop.client.ApiClient; +import com.jpmorgan.cakeshop.client.api.NodeApi; +import com.jpmorgan.cakeshop.client.model.Node; +import com.jpmorgan.cakeshop.client.model.req.NodeUpdateCommand; +import com.jpmorgan.cakeshop.client.model.res.APIData; +import com.jpmorgan.cakeshop.client.model.res.APIResponse; +import com.jpmorgan.cakeshop.client.model.res.SimpleResult; +import com.jpmorgan.cakeshop.manager.service.NodeManagerService; +import feign.auth.BasicAuthRequestInterceptor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +@Service +public class NodeManagerServiceImpl implements NodeManagerService { + + @Override + public APIResponse, Node> get(final String cakeshopUrl, final String cred1, final String cred2) { + return getNodeApi(cakeshopUrl, cred1, cred2).get(); + } + + @Override + public APIResponse, Node> update(final String cakeshopUrl, final String cred1, final String cred2, final NodeUpdateCommand command) { + return getNodeApi(cakeshopUrl, cred1, cred2).update(command); + } + + @Override + public APIResponse, Boolean> start(final String cakeshopUrl, final String cred1, final String cred2) { + return getNodeApi(cakeshopUrl, cred1, cred2).start(); + } + + @Override + public APIResponse, Boolean> stop(final String cakeshopUrl, final String cred1, final String cred2) { + return getNodeApi(cakeshopUrl, cred1, cred2).stop(); + } + + @Override + public APIResponse, Boolean> restart(final String cakeshopUrl, final String cred1, final String cred2) { + return getNodeApi(cakeshopUrl, cred1, cred2).restart(); + } + + @Override + public APIResponse, Boolean> reset(final String cakeshopUrl, final String cred1, final String cred2) { + return getNodeApi(cakeshopUrl, cred1, cred2).reset(); + } + + @Override + public APIResponse, Boolean> constellationList(final String cakeshopUrl, final String cred1, final String cred2) { + return getNodeApi(cakeshopUrl, cred1, cred2).constellationList(); + } + + @Override + public APIResponse, Node> addConstellation(final String cakeshopUrl, final String cred1, final String cred2, final NodeUpdateCommand command) { + return getNodeApi(cakeshopUrl, cred1, cred2).addConstellation(command); + } + + @Override + public APIResponse, Node> removeConstellationNode(final String cakeshopUrl, final String cred1, final String cred2, final NodeUpdateCommand command) { + return getNodeApi(cakeshopUrl, cred1, cred2).removeConstellationNode(command); + } + + @Override + public APIResponse, Boolean> stopConstellation(final String cakeshopUrl, final String cred1, final String cred2) { + return getNodeApi(cakeshopUrl, cred1, cred2).stopConstellation(); + } + + @Override + public APIResponse, Boolean> startConstellation(final String cakeshopUrl, final String cred1, final String cred2) { + return getNodeApi(cakeshopUrl, cred1, cred2).startConstellation(); + } + + @Override + public APIResponse, Boolean> addPeer(final String cakeshopUrl, final String cred1, final String cred2, final NodeUpdateCommand command) { + return getNodeApi(cakeshopUrl, cred1, cred2).addPeer(command); + } + + @Override + public APIResponse peers(final String cakeshopUrl, final String cred1, final String cred2) { + return getNodeApi(cakeshopUrl, cred1, cred2).peers(); + } + + @Override + public APIResponse, Boolean> resetNode(final String cakeshopUrl, final String cred1, final String cred2) { + return getNodeApi(cakeshopUrl, cred1, cred2).resetNode(); + } + + private NodeApi getNodeApi(String cakeshopUrl, String cred1, String cred2) { + ApiClient apiClient = new ApiClient(); + apiClient.setBasePath(cakeshopUrl.concat("/api")); + if (StringUtils.isNotBlank(cred1) && StringUtils.isNotBlank(cred2)) { + BasicAuthRequestInterceptor interceptor = new BasicAuthRequestInterceptor(cred1, cred2); + apiClient.addAuthorization("basic", interceptor); + } + return apiClient.buildClient(NodeApi.class); + } + +} diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/service/impl/SaveNodeServiceImpl.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/service/impl/SaveNodeServiceImpl.java new file mode 100644 index 00000000..fb6a4863 --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/service/impl/SaveNodeServiceImpl.java @@ -0,0 +1,41 @@ +package com.jpmorgan.cakeshop.manager.service.impl; + +import com.jpmorgan.cakeshop.manager.dao.RemoteNodeDAO; +import com.jpmorgan.cakeshop.manager.db.entity.RemoteNode; +import com.jpmorgan.cakeshop.manager.service.SaveNodeService; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class SaveNodeServiceImpl implements SaveNodeService { + + @Autowired + private RemoteNodeDAO dao; + + @Override + @Transactional + public void insert(RemoteNode node) { + dao.insert(node); + } + + @Override + @Transactional + public void update(RemoteNode node) { + dao.update(node); + } + + @Override + @Transactional + public RemoteNode getNode(String id) { + return dao.getRemoteNode(id); + } + + @Override + @Transactional + public List getRemoteNodesList() { + return dao.getRemoteNodesList(); + } + +} diff --git a/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/utils/Utils.java b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/utils/Utils.java new file mode 100644 index 00000000..91d9258a --- /dev/null +++ b/cakeshop-node-manager/src/main/java/com/jpmorgan/cakeshop/manager/utils/Utils.java @@ -0,0 +1,20 @@ +package com.jpmorgan.cakeshop.manager.utils; + +import com.jpmorgan.cakeshop.manager.db.entity.RemoteNode; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +public class Utils { + + public static Function, List> otherNodes(String currentNodeUrl) { + Function, List> otherNodes = (List nodes) -> { + List nodesReturn = new ArrayList<>(); + nodes.stream().filter((node) -> (!node.getUrl().equals(currentNodeUrl))).forEachOrdered((node) -> { + nodesReturn.add(node); + }); + return nodesReturn; + }; + return otherNodes; + } +} diff --git a/cakeshop-node-manager/src/main/resources/config/application.properties b/cakeshop-node-manager/src/main/resources/config/application.properties new file mode 100644 index 00000000..07e34cd1 --- /dev/null +++ b/cakeshop-node-manager/src/main/resources/config/application.properties @@ -0,0 +1,13 @@ +# db config +nodemanager.database.vendor=hsqldb +#cakeshop.jdbc.url= +#cakeshop.jndi.name=jdbc/oracle +nodemanager.jdbc.user=nmngr +nodemanager.jdbc.pass=nmngr +nodemanager.hibernate.jdbc.batch_size=100 + +#postgres sample +#nodemanager.database.vendor=postgres +#nodemanager.jdbc.user=postgres +#nodemanager.jdbc.pass= +#nodemanager.jdbc.url=jdbc:postgresql://localhost:5432/postgres diff --git a/cakeshop-node-manager/src/main/webapp/META-INF/context.xml b/cakeshop-node-manager/src/main/webapp/META-INF/context.xml new file mode 100644 index 00000000..fd2fac1f --- /dev/null +++ b/cakeshop-node-manager/src/main/webapp/META-INF/context.xml @@ -0,0 +1,2 @@ + + diff --git a/cakeshop-node-manager/src/main/webapp/index.html b/cakeshop-node-manager/src/main/webapp/index.html new file mode 100644 index 00000000..3368e9c3 --- /dev/null +++ b/cakeshop-node-manager/src/main/webapp/index.html @@ -0,0 +1,10 @@ + + + + Start Page + + + +

    Hello World!

    + + diff --git a/pom.xml b/pom.xml index 0ee07056..3023877e 100644 --- a/pom.xml +++ b/pom.xml @@ -1,18 +1,17 @@ - - + + 4.0.0 org.springframework.boot spring-boot-starter-parent - 1.3.3.RELEASE + 1.4.3.RELEASE com.jpmorgan cakeshop-parent - 0.9.1 + 0.10.0 pom cakeshop @@ -25,10 +24,10 @@ ${project.build.directory}/endorsed UTF-8 - 1.7 + 1.8 - 4.2.8.RELEASE - 1.4.2.RELEASE + 4.3.5.RELEASE + 1.4.3.RELEASE 9.2.19.v20160908 @@ -54,6 +53,7 @@ cakeshop-client-java cakeshop-client-java-codegen cakeshop-client-java-sample + cakeshop-node-manager @@ -86,4 +86,4 @@ - + \ No newline at end of file