diff --git a/ethereumj-core/src/main/java/org/ethereum/config/GenerateNodeIdRandomly.java b/ethereumj-core/src/main/java/org/ethereum/config/GenerateNodeIdRandomly.java new file mode 100644 index 0000000000..7ba746f7ab --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/config/GenerateNodeIdRandomly.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) [2017] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.config; + +import org.ethereum.crypto.ECKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.util.Properties; + +/** + * Strategy to randomly generate the nodeId and the nodePrivateKey. + * + * @author Lucas Saldanha + * @since 14.12.2017 + */ +public class GenerateNodeIdRandomly implements GenerateNodeIdStrategy { + + private static Logger logger = LoggerFactory.getLogger("general"); + + private String databaseDir; + + GenerateNodeIdRandomly(String databaseDir) { + this.databaseDir = databaseDir; + } + + @Override + public String getNodePrivateKey() { + ECKey key = new ECKey(); + Properties props = new Properties(); + props.setProperty("nodeIdPrivateKey", Hex.toHexString(key.getPrivKeyBytes())); + props.setProperty("nodeId", Hex.toHexString(key.getNodeId())); + + File file = new File(databaseDir, "nodeId.properties"); + file.getParentFile().mkdirs(); + try (Writer writer = new FileWriter(file)) { + props.store(writer, "Generated NodeID. To use your own nodeId please refer to 'peer.privateKey' config option."); + } catch (IOException e) { + throw new RuntimeException(e); + } + + logger.info("New nodeID generated: " + props.getProperty("nodeId")); + logger.info("Generated nodeID and its private key stored in " + file); + + return props.getProperty("nodeIdPrivateKey"); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/config/GenerateNodeIdStrategy.java b/ethereumj-core/src/main/java/org/ethereum/config/GenerateNodeIdStrategy.java new file mode 100644 index 0000000000..8625b822b2 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/config/GenerateNodeIdStrategy.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) [2017] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.config; + +/** + * Strategy interface to generate the nodeId and the nodePrivateKey. + *

+ * Two strategies are available: + *

    + *
  • {@link GetNodeIdFromPropsFile}: searches for a nodeId.properties + * and uses the values in the file to set the nodeId and the nodePrivateKey.
  • + *
  • {@link GenerateNodeIdRandomly}: generates a nodeId.properties file + * with a generated nodeId and nodePrivateKey.
  • + *
+ * + * @author Lucas Saldanha + * @see SystemProperties#getGeneratedNodePrivateKey() + * @since 14.12.2017 + */ +public interface GenerateNodeIdStrategy { + + String getNodePrivateKey(); + +} diff --git a/ethereumj-core/src/main/java/org/ethereum/config/GetNodeIdFromPropsFile.java b/ethereumj-core/src/main/java/org/ethereum/config/GetNodeIdFromPropsFile.java new file mode 100644 index 0000000000..3a4f2f3675 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/config/GetNodeIdFromPropsFile.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) [2017] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.config; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.util.Properties; + +/** + * Strategy to generate the nodeId and the nodePrivateKey from a nodeId.properties file. + *

+ * If the nodeId.properties file doesn't exist, it uses the + * {@link GetNodeIdFromPropsFile#fallbackGenerateNodeIdStrategy} as a fallback strategy + * to generate the nodeId and nodePrivateKey. + * + * @author Lucas Saldanha + * @since 14.12.2017 + */ +public class GetNodeIdFromPropsFile implements GenerateNodeIdStrategy { + + private String databaseDir; + private GenerateNodeIdStrategy fallbackGenerateNodeIdStrategy; + + GetNodeIdFromPropsFile(String databaseDir) { + this.databaseDir = databaseDir; + } + + @Override + public String getNodePrivateKey() { + Properties props = new Properties(); + File file = new File(databaseDir, "nodeId.properties"); + if (file.canRead()) { + try (Reader r = new FileReader(file)) { + props.load(r); + return props.getProperty("nodeIdPrivateKey"); + } catch (IOException e) { + throw new RuntimeException("Error reading 'nodeId.properties' file", e); + } + } else { + if (fallbackGenerateNodeIdStrategy != null) { + return fallbackGenerateNodeIdStrategy.getNodePrivateKey(); + } else { + throw new RuntimeException("Can't read 'nodeId.properties' and no fallback method has been set"); + } + } + } + + public GenerateNodeIdStrategy withFallback(GenerateNodeIdStrategy generateNodeIdStrategy) { + this.fallbackGenerateNodeIdStrategy = generateNodeIdStrategy; + return this; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/config/SystemProperties.java b/ethereumj-core/src/main/java/org/ethereum/config/SystemProperties.java index 96e52472e5..e69230cc2c 100644 --- a/ethereumj-core/src/main/java/org/ethereum/config/SystemProperties.java +++ b/ethereumj-core/src/main/java/org/ethereum/config/SystemProperties.java @@ -159,6 +159,8 @@ static boolean isUseOnlySpringConfig() { private final ClassLoader classLoader; + private GenerateNodeIdStrategy generateNodeIdStrategy = null; + public SystemProperties() { this(ConfigFactory.empty()); } @@ -217,21 +219,24 @@ public SystemProperties(Config apiConfig, ClassLoader classLoader) { // There could be several files with the same name from other packages, // "version.properties" is a very common name List iStreams = loadResources("version.properties", this.getClass().getClassLoader()); - for (InputStream is : iStreams) { - Properties props = new Properties(); - props.load(is); - if (props.getProperty("versionNumber") == null || props.getProperty("databaseVersion") == null) { - continue; - } - this.projectVersion = props.getProperty("versionNumber"); - this.projectVersion = this.projectVersion.replaceAll("'", ""); + for (InputStream is : iStreams) { + Properties props = new Properties(); + props.load(is); + if (props.getProperty("versionNumber") == null || props.getProperty("databaseVersion") == null) { + continue; + } + this.projectVersion = props.getProperty("versionNumber"); + this.projectVersion = this.projectVersion.replaceAll("'", ""); - if (this.projectVersion == null) this.projectVersion = "-.-.-"; + if (this.projectVersion == null) this.projectVersion = "-.-.-"; - this.projectVersionModifier = "master".equals(BuildInfo.buildBranch) ? "RELEASE" : "SNAPSHOT"; + this.projectVersionModifier = "master".equals(BuildInfo.buildBranch) ? "RELEASE" : "SNAPSHOT"; - this.databaseVersion = Integer.valueOf(props.getProperty("databaseVersion")); - break; + this.databaseVersion = Integer.valueOf(props.getProperty("databaseVersion")); + + this.generateNodeIdStrategy = new GetNodeIdFromPropsFile(databaseDir()) + .withFallback(new GenerateNodeIdRandomly(databaseDir())); + break; } } catch (Exception e) { logger.error("Can't read config.", e); @@ -677,28 +682,7 @@ public String privateKey() { private String getGeneratedNodePrivateKey() { if (generatedNodePrivateKey == null) { - try { - File file = new File(databaseDir(), "nodeId.properties"); - Properties props = new Properties(); - if (file.canRead()) { - try (Reader r = new FileReader(file)) { - props.load(r); - } - } else { - ECKey key = new ECKey(); - props.setProperty("nodeIdPrivateKey", Hex.toHexString(key.getPrivKeyBytes())); - props.setProperty("nodeId", Hex.toHexString(key.getNodeId())); - file.getParentFile().mkdirs(); - try (Writer w = new FileWriter(file)) { - props.store(w, "Generated NodeID. To use your own nodeId please refer to 'peer.privateKey' config option."); - } - logger.info("New nodeID generated: " + props.getProperty("nodeId")); - logger.info("Generated nodeID and its private key stored in " + file); - } - generatedNodePrivateKey = props.getProperty("nodeIdPrivateKey"); - } catch (IOException e) { - throw new RuntimeException(e); - } + generatedNodePrivateKey = generateNodeIdStrategy.getNodePrivateKey(); } return generatedNodePrivateKey; } @@ -972,4 +956,8 @@ public boolean githubTestsLoadLocal() { return config.hasPath("GitHubTests.testPath") && !config.getString("GitHubTests.testPath").isEmpty(); } + + void setGenerateNodeIdStrategy(GenerateNodeIdStrategy generateNodeIdStrategy) { + this.generateNodeIdStrategy = generateNodeIdStrategy; + } } diff --git a/ethereumj-core/src/test/java/org/ethereum/config/GenerateNodeIdRandomlyTest.java b/ethereumj-core/src/test/java/org/ethereum/config/GenerateNodeIdRandomlyTest.java new file mode 100644 index 0000000000..da1132211d --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/config/GenerateNodeIdRandomlyTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) [2017] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.config; + +import org.junit.Test; +import org.springframework.util.FileCopyUtils; +import org.springframework.util.StringUtils; + +import javax.annotation.concurrent.NotThreadSafe; +import java.io.File; +import java.io.FileReader; + +import static org.junit.Assert.*; + +/** + * Not thread safe - testGeneratedNodePrivateKey temporarily removes the nodeId.properties + * file which may influence other tests. + */ +@SuppressWarnings("ConstantConditions") +@NotThreadSafe +public class GenerateNodeIdRandomlyTest { + + @Test + public void testGenerateNodeIdRandomlyCreatesFileWithNodeIdAndPrivateKey() throws Exception { + File nodeIdPropertiesFile = new File("database-test/nodeId.properties"); + //Cleanup previous nodeId.properties file (if exists) + //noinspection ResultOfMethodCallIgnored + nodeIdPropertiesFile.delete(); + + new GenerateNodeIdRandomly("database-test").getNodePrivateKey(); + + assertTrue(nodeIdPropertiesFile.exists()); + String contents = FileCopyUtils.copyToString(new FileReader(nodeIdPropertiesFile)); + String[] lines = StringUtils.tokenizeToStringArray(contents, "\n"); + assertEquals(4, lines.length); + assertTrue(lines[0].startsWith("#Generated NodeID.")); + assertTrue(lines[1].startsWith("#")); + assertTrue(lines[2].startsWith("nodeIdPrivateKey=")); + assertEquals("nodeIdPrivateKey=".length() + 64, lines[2].length()); + assertTrue(lines[3].startsWith("nodeId=")); + assertEquals("nodeId=".length() + 128, lines[3].length()); + + //noinspection ResultOfMethodCallIgnored + nodeIdPropertiesFile.delete(); + } + +} diff --git a/ethereumj-core/src/test/java/org/ethereum/config/GetNodeIdFromPropsFileTest.java b/ethereumj-core/src/test/java/org/ethereum/config/GetNodeIdFromPropsFileTest.java new file mode 100644 index 0000000000..cd62159876 --- /dev/null +++ b/ethereumj-core/src/test/java/org/ethereum/config/GetNodeIdFromPropsFileTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) [2017] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ +package org.ethereum.config; + +import org.ethereum.crypto.ECKey; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.spongycastle.util.encoders.Hex; +import org.springframework.util.FileCopyUtils; +import org.springframework.util.StringUtils; + +import javax.annotation.concurrent.NotThreadSafe; +import java.io.*; +import java.util.Properties; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Not thread safe - testGeneratedNodePrivateKey temporarily removes the nodeId.properties + * file which may influence other tests. + */ +@SuppressWarnings("ConstantConditions") +@NotThreadSafe +public class GetNodeIdFromPropsFileTest { + + @Rule + public ExpectedException exception = ExpectedException.none(); + + private File nodeIdPropertiesFile; + + @Before + public void init() { + //Cleanup previous nodeId.properties file (if exists) + nodeIdPropertiesFile = new File("database-test/nodeId.properties"); + //noinspection ResultOfMethodCallIgnored + nodeIdPropertiesFile.delete(); + } + + @After + public void teardown() { + //Cleanup created nodeId.properties file + //noinspection ResultOfMethodCallIgnored + nodeIdPropertiesFile.delete(); + } + + @Test + public void testGenerateNodeIdFromPropsFileReadsExistingFile() throws Exception { + // Create temporary nodeId.properties file + ECKey key = new ECKey(); + String expectedNodePrivateKey = Hex.toHexString(key.getPrivKeyBytes()); + String expectedNodeId = Hex.toHexString(key.getNodeId()); + createNodeIdPropertiesFile("database-test", key); + + new GetNodeIdFromPropsFile("database-test").getNodePrivateKey(); + + assertTrue(nodeIdPropertiesFile.exists()); + String contents = FileCopyUtils.copyToString(new FileReader(nodeIdPropertiesFile)); + String[] lines = StringUtils.tokenizeToStringArray(contents, "\n"); + assertEquals(4, lines.length); + assertTrue(lines[0].startsWith("#Generated NodeID.")); + assertTrue(lines[1].startsWith("#")); + assertTrue(lines[2].startsWith("nodeIdPrivateKey=" + expectedNodePrivateKey)); + assertTrue(lines[3].startsWith("nodeId=" + expectedNodeId)); + } + + @Test + public void testGenerateNodeIdFromPropsDoesntGenerateRandomWhenFileIsPresent() throws Exception { + // Create temporary nodeId.properties file + createNodeIdPropertiesFile("database-test", new ECKey()); + + GenerateNodeIdRandomly randomNodeIdGeneratorStrategySpy = spy(new GenerateNodeIdRandomly("database-test")); + GenerateNodeIdStrategy fileNodeIdGeneratorStrategy = new GetNodeIdFromPropsFile("database-test") + .withFallback(randomNodeIdGeneratorStrategySpy); + + fileNodeIdGeneratorStrategy.getNodePrivateKey(); + + verifyZeroInteractions(randomNodeIdGeneratorStrategySpy); + } + + @Test + public void testGenerateNodeIdFromPropsGeneratesRandomWhenFileIsNotPresent() { + GenerateNodeIdRandomly randomNodeIdGeneratorStrategySpy = spy(new GenerateNodeIdRandomly("database-test")); + GenerateNodeIdStrategy fileNodeIdGeneratorStrategy = new GetNodeIdFromPropsFile("database-test") + .withFallback(randomNodeIdGeneratorStrategySpy); + + fileNodeIdGeneratorStrategy.getNodePrivateKey(); + + verify(randomNodeIdGeneratorStrategySpy).getNodePrivateKey(); + } + + @Test + public void testGenerateNodeIdFromPropsGeneratesRandomWhenFileIsNotPresentAndNoFallback() { + exception.expect(RuntimeException.class); + exception.expectMessage("Can't read 'nodeId.properties' and no fallback method has been set"); + + GenerateNodeIdStrategy fileNodeIdGeneratorStrategy = new GetNodeIdFromPropsFile("database-test"); + + fileNodeIdGeneratorStrategy.getNodePrivateKey(); + } + + private void createNodeIdPropertiesFile(String dir, ECKey key) throws IOException { + Properties props = new Properties(); + props.setProperty("nodeIdPrivateKey", Hex.toHexString(key.getPrivKeyBytes())); + props.setProperty("nodeId", Hex.toHexString(key.getNodeId())); + + File file = new File(dir, "nodeId.properties"); + file.getParentFile().mkdirs(); + try (Writer writer = new FileWriter(file)) { + props.store(writer, "Generated NodeID. To use your own nodeId please refer to 'peer.privateKey' config option."); + } + } + +} diff --git a/ethereumj-core/src/test/java/org/ethereum/config/SystemPropertiesTest.java b/ethereumj-core/src/test/java/org/ethereum/config/SystemPropertiesTest.java index c190c1df1f..9621d0c883 100644 --- a/ethereumj-core/src/test/java/org/ethereum/config/SystemPropertiesTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/config/SystemPropertiesTest.java @@ -51,6 +51,8 @@ import static com.google.common.collect.Lists.newArrayList; import static junit.framework.TestCase.assertNotNull; import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; /** * Not thread safe - testUseOnlySprintConfig temporarily sets a static flag that may influence other tests. @@ -431,6 +433,21 @@ public void testGeneratedNodePrivateKeyThroughECKey() throws Exception { assertEquals("nodeId=".length() + 128, lines[3].length()); } + @Test + public void testGeneratedNodePrivateKeyDelegatesToStrategy() { + File outputFile = new File("database-test/nodeId.properties"); + //noinspection ResultOfMethodCallIgnored + outputFile.delete(); + + GenerateNodeIdStrategy generateNodeIdStrategyMock = mock(GenerateNodeIdStrategy.class); + + SystemProperties props = new SystemProperties(); + props.setGenerateNodeIdStrategy(generateNodeIdStrategyMock); + props.privateKey(); + + verify(generateNodeIdStrategyMock).getNodePrivateKey(); + } + @Test public void testFastSyncPivotBlockHash() { SystemProperties props = new SystemProperties();