├── .gitignore ├── README.markdown ├── build.sh ├── pom.xml └── src ├── assemble └── single-jar.xml ├── main ├── java │ └── com │ │ └── thaler │ │ └── miner │ │ ├── CommandLine.java │ │ ├── Main.java │ │ ├── address │ │ └── ImmutableAddress.java │ │ ├── block │ │ ├── Block.java │ │ └── BlockUtil.java │ │ ├── ddb │ │ ├── Bits256.java │ │ ├── Bits256Timestamp.java │ │ ├── Bits256UnsignedFraction.java │ │ ├── Bits256UnsignedInteger.java │ │ ├── HashableByteArray.java │ │ ├── HashableString.java │ │ └── UnsignedBitsUtil.java │ │ ├── localdb │ │ └── LocalDatabase.java │ │ ├── merkle │ │ ├── Hashable.java │ │ ├── MerkleTreeNode.java │ │ ├── MerkleUtil.java │ │ └── Pair.java │ │ ├── transaction │ │ └── TransactionHash.java │ │ └── util │ │ └── CryptoUtil.java └── resources │ ├── buildInfo.properties │ └── log4j.properties └── test └── java └── com └── thaler └── miner ├── BitManipulationUnitTests.java ├── CryptoUtilTests.java ├── LocalDbTests.java ├── MainTest.java ├── MerkleTests.java └── MineBlockTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | /target/* 2 | **/target/* 3 | */target/* 4 | target/ 5 | **/bin/* 6 | generated/* 7 | **/*.ec 8 | **/*.ipr 9 | **/*.iml 10 | **/*.iws 11 | **/.classpath 12 | .classpath 13 | **/.project 14 | .project 15 | **/.settings 16 | .settings 17 | .settings/ 18 | **/*.class 19 | .DS_Store 20 | **/.DS_Store 21 | /generated 22 | /target-ide 23 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # What is this thing? 2 | 3 | This is an attempt at creating a blockchain in Java from scratch. 4 | 5 | ### Why? 6 | 7 | Just for the fun of it really. I'm fascinated with blockchain technology and 8 | what better way to learn about it than to create my own? 9 | 10 | ### Implemented (at least partially) 11 | 12 | * Merkle Tree creation/manipulation. 13 | * Fractions instead of decimals for the token value (cleaner divisibility, 14 | novel, less chance of floating-point rounding errors) 15 | * Block structure 16 | * Proof of Work (Scrypt) 17 | 18 | ### TODO 19 | * Wire protocol (Protobuf?, JSON?, ???) 20 | * Transaction language 21 | * User Interface for transferring tokens 22 | * Economic decisions (inflation rate, pre-allocation, mining rewards, etc...) 23 | 24 | ### License 25 | 26 | Released in the public domain. I would appreciate credit where possible. :) 27 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | mvn clean validate install assembly:single 3 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.thaler 7 | miner 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | A single-jar Application 12 | 13 | 14 | UTF-8 15 | ${project.groupId}.Main 16 | 1.6.4 17 | 1.2.6 18 | 3.8.1 19 | 20 | 21 | 22 | ${project.artifactId} 23 | 24 | http://www.matthewjosephtaylor.com 25 | 26 | scm:git:git@github.com:matthewjosephtaylor/${project.artifactId}.git 27 | scm:git:git@github.com:matthewjosephtaylor/${project.artifactId}.git 28 | scm:git:git@github.com:matthewjosephtaylor/${project.artifactId}.git 29 | 30 | 31 | 32 | 33 | 34 | 35 | junit 36 | junit 37 | ${junit.version} 38 | test 39 | 40 | 42 | 43 | log4j 44 | log4j 45 | ${log4j.version} 46 | 47 | 48 | org.slf4j 49 | slf4j-api 50 | ${slf4j.version} 51 | 52 | 53 | org.slf4j 54 | jul-to-slf4j 55 | ${slf4j.version} 56 | 57 | 58 | org.slf4j 59 | slf4j-log4j12 60 | ${slf4j.version} 61 | 62 | 63 | args4j 64 | args4j 65 | 2.0.16 66 | 67 | 68 | bouncycastle 69 | bcprov-jdk16 70 | 140 71 | 72 | 73 | com.google.guava 74 | guava 75 | 17.0 76 | 77 | 78 | commons-codec 79 | commons-codec 80 | 1.9 81 | 82 | 83 | org.jgrapht 84 | jgrapht-core 85 | 0.9.0 86 | 87 | 88 | com.h2database 89 | h2 90 | 1.4.178 91 | 92 | 93 | 94 | commons-dbcp 95 | commons-dbcp 96 | 1.4 97 | 98 | 99 | com.lambdaworks 100 | scrypt 101 | 1.4.0 102 | 103 | 104 | org.apache.commons 105 | commons-lang3 106 | 3.3.2 107 | 108 | 109 | org.threeten 110 | threetenbp 111 | 0.9 112 | 113 | 114 | org.threeten 115 | threeten-extra 116 | 0.8 117 | 118 | 119 | org.apache.commons 120 | commons-math3 121 | 3.3 122 | 123 | 124 | 125 | 126 | 127 | ${project.artifactId}-${project.version}-r${buildNumber} 128 | 129 | 130 | src/main/resources 131 | true 132 | 133 | 134 | 135 | 136 | maven-compiler-plugin 137 | 3.1 138 | 139 | 1.8 140 | 1.8 141 | 142 | 143 | 144 | org.codehaus.groovy.maven 145 | gmaven-plugin 146 | 1.0 147 | 148 | 149 | validate 150 | 151 | execute 152 | 153 | 154 | project.properties["hostname"] = 155 | InetAddress.getLocalHost().getHostName() 156 | 157 | 158 | 159 | 160 | 161 | org.codehaus.mojo 162 | buildnumber-maven-plugin 163 | 1.0 164 | 165 | 166 | validate 167 | 168 | create 169 | 170 | 171 | 172 | 173 | UNK 174 | false 175 | false 176 | {0,date,yyyyMMdd.HH.mm.ss}.${hostname} 177 | 178 | timestamp 179 | 180 | 181 | 182 | 183 | maven-assembly-plugin 184 | 185 | ${project.artifactId} 186 | 187 | src/assemble/single-jar.xml 188 | 189 | 190 | 191 | ${mainClass} 192 | true 193 | true 194 | 195 | 196 | ${buildNumber} 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 206 | 207 | org.eclipse.m2e 208 | lifecycle-mapping 209 | 1.0.0 210 | 211 | 212 | 213 | 214 | 215 | org.codehaus.groovy.maven 216 | gmaven-plugin 217 | [1.0,) 218 | 219 | execute 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | -------------------------------------------------------------------------------- /src/assemble/single-jar.xml: -------------------------------------------------------------------------------- 1 | 2 | single-jar 3 | 4 | jar 5 | 6 | false 7 | 8 | 9 | 10 | true 11 | true 12 | 13 | 14 | 15 | 16 | target/classes 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/main/java/com/thaler/miner/CommandLine.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner; 2 | 3 | import org.kohsuke.args4j.Option; 4 | 5 | public class CommandLine { 6 | 7 | @Option(name = "--help", usage = "Print Help", aliases = { "--help", "-h" }) 8 | private boolean help; 9 | 10 | @Option(name = "--noOperation", usage = "Do Nothing (Testing only)", aliases = { "-noop" }) 11 | private boolean doNothing = false; 12 | 13 | 14 | public boolean isDoNothing() { 15 | return doNothing; 16 | } 17 | 18 | public boolean isHelp() { 19 | return help; 20 | } 21 | 22 | 23 | public void setDoNothing(final boolean doNothing) { 24 | this.doNothing = doNothing; 25 | } 26 | 27 | public void setHelp(final boolean help) { 28 | this.help = help; 29 | } 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/thaler/miner/Main.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner; 2 | 3 | import java.util.ResourceBundle; 4 | 5 | import org.apache.log4j.Logger; 6 | import org.kohsuke.args4j.CmdLineException; 7 | import org.kohsuke.args4j.CmdLineParser; 8 | import org.slf4j.bridge.SLF4JBridgeHandler; 9 | 10 | /** 11 | * An awesome miner 12 | * 13 | */ 14 | public class Main 15 | { 16 | private static final Logger logger = Logger.getLogger(Main.class); 17 | private static final CommandLine commandLine = new CommandLine(); 18 | private static CmdLineParser parser; 19 | 20 | public static void main(final String[] args) throws Exception 21 | { 22 | SLF4JBridgeHandler.install(); 23 | logger.info("Application v" + Main.class.getPackage().getImplementationVersion()); 24 | logger.info("Build: " + ResourceBundle.getBundle("buildInfo").getString("buildNumber")); 25 | parseCommandLine(args); 26 | } 27 | 28 | private static void parseCommandLine(final String[] args) { 29 | parser = new CmdLineParser(commandLine); 30 | try { 31 | parser.parseArgument(args); 32 | if (commandLine.isHelp()) { 33 | printUsageAndExit(); 34 | } 35 | 36 | } catch (final CmdLineException e) { 37 | logger.error(e); 38 | printUsageAndExit(); 39 | } 40 | } 41 | 42 | private static void printUsageAndExit() { 43 | System.out.println("usage: java -jar [options...]"); 44 | parser.printUsage(System.out); 45 | System.exit(-1); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/thaler/miner/address/ImmutableAddress.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner.address; 2 | 3 | 4 | // 160 bit hash of public key 5 | public class ImmutableAddress { 6 | 7 | private byte[] hash; 8 | 9 | public ImmutableAddress(byte[] hash) { 10 | this.hash = hash; 11 | } 12 | 13 | public byte[] getBytes(){ 14 | return this.hash; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/thaler/miner/block/Block.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner.block; 2 | 3 | import java.math.BigInteger; 4 | import java.util.List; 5 | 6 | import org.apache.commons.lang3.builder.ToStringBuilder; 7 | import org.apache.log4j.Logger; 8 | 9 | import com.google.common.collect.FluentIterable; 10 | import com.google.common.collect.Lists; 11 | import com.thaler.miner.ddb.Bits256; 12 | import com.thaler.miner.ddb.Bits256Timestamp; 13 | import com.thaler.miner.ddb.Bits256UnsignedInteger; 14 | import com.thaler.miner.merkle.Hashable; 15 | import com.thaler.miner.merkle.MerkleTreeNode; 16 | import com.thaler.miner.merkle.MerkleUtil; 17 | import com.thaler.miner.util.CryptoUtil; 18 | 19 | /** 20 | * The block, the heart and soul of the system. 21 | * 22 | * Any data that can't fit inside of 256 bits is represented by its Merkle root. 23 | * 24 | * nonceMerkleRoot: nonce for proof of work is the coin base transaction 25 | * signature. 26 | * 27 | * maxIndex: the last index referenced in the spentIndexBitmap. 28 | * 29 | * targetDifficulty: recalculated periodically based on a set of moving averages 30 | * 31 | * spentIndexBitmap: 1=spent, 0=unspent. Use maxIndex to determine length of 32 | * bitmap 33 | * 34 | */ 35 | public class Block { 36 | 37 | private static final Logger logger = Logger.getLogger(Block.class); 38 | 39 | public enum Version { 40 | TEST(Bits256UnsignedInteger.create(0)), 41 | MAIN(Bits256UnsignedInteger.create(1)); 42 | private final Bits256UnsignedInteger versionNumber; 43 | 44 | Version(Bits256UnsignedInteger versionNumber) { 45 | this.versionNumber = versionNumber; 46 | } 47 | 48 | public Bits256UnsignedInteger value() { 49 | return this.versionNumber; 50 | } 51 | } 52 | 53 | public Bits256 merkleRoot; // also proof of work. 54 | public Bits256 previousBlockMerkleRoot; 55 | public Bits256UnsignedInteger version; 56 | public Bits256UnsignedInteger sequence; 57 | public Bits256Timestamp timeStamp; 58 | public Bits256UnsignedInteger maxIndex; 59 | public Bits256UnsignedInteger targetDifficulty; 60 | public Bits256 newIndexAddresseAmountTupleListMerkleRoot; // Index,Address,Amount,HashOfIAA 61 | public Bits256 nonceMerkleRoot; // nonce must be a valid merklePath to a given address/index 62 | public Bits256 newTransactionTuplesMerkleRoot; 63 | public Bits256 spentIndexBitmapMerkleRoot; // 1=spent, 0=unspent 64 | 65 | public static Block create(Bits256 previousBlockMerkleRoot, Version version, Bits256UnsignedInteger sequence, 66 | Bits256Timestamp timeStamp, Bits256UnsignedInteger maxIndex, Bits256UnsignedInteger targetDifficulty) { 67 | Block result = new Block(); 68 | result.previousBlockMerkleRoot = previousBlockMerkleRoot; 69 | result.version = version.value(); 70 | result.sequence = sequence; 71 | result.timeStamp = timeStamp; 72 | result.maxIndex = maxIndex; 73 | result.targetDifficulty = targetDifficulty; 74 | 75 | return result; 76 | } 77 | 78 | public FluentIterable getBaseDataBytes() { 79 | List baseData = Lists.newArrayList(); 80 | baseData.add(previousBlockMerkleRoot); 81 | baseData.add(version); 82 | baseData.add(sequence); 83 | baseData.add(timeStamp); 84 | baseData.add(maxIndex); 85 | baseData.add(newIndexAddresseAmountTupleListMerkleRoot); 86 | baseData.add(nonceMerkleRoot); 87 | baseData.add(newTransactionTuplesMerkleRoot); 88 | baseData.add(spentIndexBitmapMerkleRoot); 89 | return FluentIterable.from(baseData); 90 | } 91 | 92 | @Override 93 | public String toString() { 94 | return ToStringBuilder.reflectionToString(this); 95 | 96 | } 97 | 98 | public boolean isValidMerkleRoot() { 99 | if(merkleRoot == null){ 100 | return false; 101 | } 102 | MerkleTreeNode calculatedMerkleRoot = MerkleUtil.createMerkleTree(getBaseDataBytes(), null); 103 | logger.info("this mr: " + merkleRoot); 104 | logger.info("calculated mr: " + calculatedMerkleRoot.combinedHash); 105 | return merkleRoot.equals(calculatedMerkleRoot.combinedHash); 106 | } 107 | 108 | public boolean isValidProofOfWork() { 109 | //BigInteger targetHashAsInteger = Bits256UnsignedInteger.create(this.merkleRoot.getBytes()).toBigInteger(); 110 | Bits256 targetHash = CryptoUtil.scrypt(this.merkleRoot.getBytes()); 111 | BigInteger targetHashAsInteger = Bits256UnsignedInteger.create(targetHash.getBytes()).toBigInteger(); 112 | logger.info("t: " + targetDifficulty.toBinary()); 113 | logger.info("a: " + targetHash.toBinary()); 114 | 115 | return targetHashAsInteger.compareTo(targetDifficulty.toBigInteger()) <= 0; 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/thaler/miner/block/BlockUtil.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner.block; 2 | 3 | import com.thaler.miner.merkle.MerkleTreeNode; 4 | import com.thaler.miner.merkle.MerkleUtil; 5 | 6 | public class BlockUtil { 7 | 8 | public static void mine(Block block){ 9 | MerkleTreeNode merkleRoot = MerkleUtil.createMerkleTree(block.getBaseDataBytes(), null); 10 | block.merkleRoot = merkleRoot.combinedHash; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/thaler/miner/ddb/Bits256.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner.ddb; 2 | 3 | import java.util.Arrays; 4 | 5 | import org.apache.commons.codec.binary.Base64; 6 | import org.apache.commons.codec.binary.Hex; 7 | 8 | import com.google.common.base.Preconditions; 9 | import com.thaler.miner.merkle.Hashable; 10 | 11 | public class Bits256 implements Hashable { 12 | 13 | private byte[] _bytes; 14 | 15 | public static Bits256 create(byte[] bytes) { 16 | 17 | return new Bits256(bytes); 18 | 19 | } 20 | 21 | private Bits256(byte[] bytes) { 22 | Preconditions.checkArgument(bytes.length == 32); 23 | this._bytes = bytes; 24 | } 25 | 26 | public byte[] getBytes() { 27 | return this._bytes; 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return Base64.encodeBase64URLSafeString(this._bytes); 33 | } 34 | 35 | public String toHex() { 36 | return Hex.encodeHexString(this._bytes); 37 | } 38 | 39 | public String toBinary() { 40 | StringBuilder sb = new StringBuilder(); 41 | for (int i = 0; i < this._bytes.length; i++) { 42 | Byte b = this._bytes[i]; 43 | sb.append(String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0')); 44 | } 45 | return sb.toString(); 46 | } 47 | 48 | @Override 49 | public int hashCode() { 50 | final int prime = 31; 51 | int result = 1; 52 | result = prime * result + Arrays.hashCode(_bytes); 53 | return result; 54 | } 55 | 56 | @Override 57 | public boolean equals(Object obj) { 58 | if (this == obj) return true; 59 | if (obj == null) return false; 60 | if (getClass() != obj.getClass()) return false; 61 | Bits256 other = (Bits256) obj; 62 | if (!Arrays.equals(_bytes, other._bytes)) return false; 63 | return true; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/thaler/miner/ddb/Bits256Timestamp.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner.ddb; 2 | 3 | import java.math.BigInteger; 4 | import java.time.Instant; 5 | import java.util.Arrays; 6 | 7 | import org.apache.log4j.Logger; 8 | import org.threeten.extra.scale.TaiInstant; 9 | 10 | import com.google.common.base.Preconditions; 11 | import com.thaler.miner.merkle.Hashable; 12 | 13 | /** 14 | * Timescale is International Atomic Time (TAI) 15 | * 16 | * http://en.wikipedia.org/wiki/International_Atomic_Time 17 | * 18 | * Second resolution 19 | * 20 | * Stored in unsigned 256 bit integer 21 | * 22 | */ 23 | // 256-bit unsigned integer (big endian) 24 | public class Bits256Timestamp implements Hashable { 25 | 26 | private static final Logger logger = Logger.getLogger(Bits256Timestamp.class); 27 | 28 | private byte[] _bytes; 29 | 30 | public static Bits256Timestamp create(long val) { 31 | return create(BigInteger.valueOf(val)); 32 | } 33 | 34 | public static Bits256Timestamp now() { 35 | TaiInstant taiInstant = TaiInstant.of(Instant.now()); 36 | long taiSeconds = taiInstant.getTaiSeconds(); 37 | return create(taiSeconds); 38 | 39 | } 40 | 41 | public static Bits256Timestamp create(BigInteger bigInteger) { 42 | return new Bits256Timestamp(bigInteger); 43 | 44 | } 45 | 46 | private Bits256Timestamp(BigInteger bigInteger) { 47 | Bits256UnsignedInteger bits256UnsignedInteger = Bits256UnsignedInteger.create(bigInteger); 48 | this._bytes = bits256UnsignedInteger.getBytes(); 49 | } 50 | 51 | public byte[] getBytes() { 52 | return this._bytes; 53 | } 54 | 55 | public long toTaiSeconds() { 56 | return getBigInteger().longValue(); 57 | } 58 | 59 | public Instant toInstant() { 60 | long taiSeconds = toTaiSeconds(); 61 | TaiInstant taiInstant = TaiInstant.ofTaiSeconds(taiSeconds, 0); 62 | return taiInstant.toInstant(); 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | long taiSeconds = toTaiSeconds(); 68 | TaiInstant taiInstant = TaiInstant.ofTaiSeconds(taiSeconds, 0); 69 | return taiInstant.toString(); 70 | } 71 | 72 | public BigInteger getBigInteger() { 73 | return new BigInteger(this._bytes); 74 | } 75 | 76 | public static Bits256Timestamp create(byte[] bytes) { 77 | BigInteger bigInteger = Bits256UnsignedInteger.create(bytes).toBigInteger(); 78 | return create(bigInteger); 79 | } 80 | 81 | @Override 82 | public int hashCode() { 83 | final int prime = 31; 84 | int result = 1; 85 | result = prime * result + Arrays.hashCode(_bytes); 86 | return result; 87 | } 88 | 89 | @Override 90 | public boolean equals(Object obj) { 91 | if (this == obj) return true; 92 | if (obj == null) return false; 93 | if (getClass() != obj.getClass()) return false; 94 | Bits256Timestamp other = (Bits256Timestamp) obj; 95 | if (!Arrays.equals(_bytes, other._bytes)) return false; 96 | return true; 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/thaler/miner/ddb/Bits256UnsignedFraction.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner.ddb; 2 | 3 | import java.math.BigDecimal; 4 | import java.math.BigInteger; 5 | 6 | import org.apache.commons.codec.binary.BinaryCodec; 7 | import org.apache.commons.codec.binary.Hex; 8 | import org.apache.commons.math3.fraction.BigFraction; 9 | import org.apache.log4j.Logger; 10 | 11 | import com.google.common.base.Preconditions; 12 | import com.thaler.miner.merkle.Hashable; 13 | 14 | /** 15 | * Floating point arithmetic and money does not mix. 16 | * 17 | * Lots of financial operations involve division. 18 | * 19 | * Fractions don't lose precision over the course of calculation. 20 | * 21 | * 128 bit numerator 22 | * 128 bit denominator 23 | * 24 | * zero denominator is allowed as a valid fraction (no math checks) 25 | * 26 | */ 27 | public class Bits256UnsignedFraction implements Hashable { 28 | 29 | private static final Logger logger = Logger.getLogger(Bits256UnsignedFraction.class); 30 | 31 | private byte[] _numeratorBytes; //numerator/denominator (each 128 bits) 32 | private byte[] _denominatorBytes; //numerator/denominator (each 128 bits) 33 | 34 | public static Bits256UnsignedFraction create(final BigInteger numerator, final BigInteger denominator) { 35 | return new Bits256UnsignedFraction(numerator, denominator); 36 | 37 | } 38 | 39 | public static Bits256UnsignedFraction create(final byte[] bytes) { 40 | return new Bits256UnsignedFraction(bytes); 41 | } 42 | 43 | public static Bits256UnsignedFraction create(final long numerator, final long denominator) { 44 | return create(BigInteger.valueOf(numerator), BigInteger.valueOf(denominator)); 45 | } 46 | 47 | public Bits256UnsignedFraction(final byte[] bytes) { 48 | _numeratorBytes = new byte[16]; 49 | _denominatorBytes = new byte[16]; 50 | System.arraycopy(bytes, 0, _numeratorBytes, 0, _numeratorBytes.length); 51 | System.arraycopy(bytes, _numeratorBytes.length, _denominatorBytes, 0, _denominatorBytes.length); 52 | } 53 | 54 | private Bits256UnsignedFraction(final BigInteger numeratorBigInteger, final BigInteger denominatorBigInteger) { 55 | _numeratorBytes = UnsignedBitsUtil.bigIntegerToBits(numeratorBigInteger, 128); 56 | _denominatorBytes = UnsignedBitsUtil.bigIntegerToBits(denominatorBigInteger, 128); 57 | } 58 | 59 | @Override 60 | public byte[] getBytes() { 61 | byte[] result = new byte[_numeratorBytes.length + _denominatorBytes.length]; 62 | System.arraycopy(_numeratorBytes, 0, result, 0, _numeratorBytes.length); 63 | System.arraycopy(_denominatorBytes, 0, result, _numeratorBytes.length, _denominatorBytes.length); 64 | return result; 65 | } 66 | 67 | public BigFraction toBigFraction() { 68 | return new BigFraction(getNumerator(), getDenominator()); 69 | } 70 | 71 | public BigInteger getNumerator() { 72 | return UnsignedBitsUtil.bitsToBigInteger(_numeratorBytes); 73 | } 74 | 75 | public BigInteger getDenominator() { 76 | return UnsignedBitsUtil.bitsToBigInteger(_denominatorBytes); 77 | } 78 | 79 | public String toHex() { 80 | return Hex.encodeHexString(getBytes()); 81 | } 82 | 83 | public String toBinary() { 84 | StringBuilder sb = new StringBuilder(); 85 | byte[] bytes = getBytes(); 86 | for (int i = 0; i < bytes.length; i++) { 87 | Byte b = bytes[i]; 88 | sb.append(String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0')); 89 | } 90 | return sb.toString(); 91 | } 92 | 93 | @Override 94 | public String toString() { 95 | return getNumerator() + "/" + getDenominator(); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/thaler/miner/ddb/Bits256UnsignedInteger.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner.ddb; 2 | 3 | import java.math.BigInteger; 4 | 5 | import org.apache.commons.codec.binary.BinaryCodec; 6 | import org.apache.commons.codec.binary.Hex; 7 | import org.apache.log4j.Logger; 8 | 9 | import com.google.common.base.Preconditions; 10 | import com.thaler.miner.merkle.Hashable; 11 | 12 | // 256-bit unsigned integer (big endian) 13 | public class Bits256UnsignedInteger implements Hashable { 14 | 15 | private static final Logger logger = Logger.getLogger(Bits256UnsignedInteger.class); 16 | 17 | private static final byte[] maxIntegerBytes = new byte[32]; 18 | { 19 | for (int i = 0; i < maxIntegerBytes.length; i++) { 20 | maxIntegerBytes[i] = 0xFFFFFFFF; 21 | } 22 | } 23 | 24 | private static final BigInteger maxIntergerBigInteger = new BigInteger("115792089237316195423570985008687907853269984665640564039457584007913129639935"); 25 | 26 | public static final Bits256UnsignedInteger MAX_VALUE = Bits256UnsignedInteger.create(maxIntegerBytes); 27 | 28 | public static final Bits256UnsignedInteger MIN_VALUE = Bits256UnsignedInteger.create(0); 29 | 30 | private byte[] _bytes; 31 | 32 | public static Bits256UnsignedInteger create(final BigInteger bigInteger) { 33 | return new Bits256UnsignedInteger(bigInteger); 34 | 35 | } 36 | 37 | public static Bits256UnsignedInteger create(final byte[] bytes) { 38 | return new Bits256UnsignedInteger(bytes); 39 | } 40 | 41 | public static Bits256UnsignedInteger create(final long val) { 42 | return create(BigInteger.valueOf(val)); 43 | } 44 | 45 | public Bits256UnsignedInteger(final byte[] bytes) { 46 | _bytes = bytes; 47 | } 48 | 49 | private Bits256UnsignedInteger(final BigInteger bigInteger) { 50 | Preconditions.checkArgument(bigInteger.compareTo(BigInteger.ZERO) >= 0, "value must be greater than or equal to zero"); 51 | Preconditions.checkArgument(bigInteger.compareTo(maxIntergerBigInteger) <= 0, "value is too large"); 52 | 53 | _bytes = new byte[32]; 54 | final byte[] bigIntByteArray = bigInteger.toByteArray(); 55 | int startIndex; 56 | if (bigIntByteArray.length == 33) { 57 | startIndex = 1; 58 | } else { 59 | startIndex = 0; 60 | } 61 | final int offset = _bytes.length - bigIntByteArray.length; 62 | for (int i = startIndex; i < bigIntByteArray.length; i++) { 63 | _bytes[i + offset] = bigIntByteArray[i]; 64 | } 65 | 66 | //logger.info(this._bytes.length); 67 | //Preconditions.checkArgument(this._bytes.length == 32); 68 | 69 | } 70 | 71 | @Override 72 | public byte[] getBytes() { 73 | return _bytes; 74 | } 75 | 76 | public BigInteger toBigInteger() { 77 | return new BigInteger(1, _bytes); 78 | } 79 | 80 | public String toHex() { 81 | return Hex.encodeHexString(this._bytes); 82 | } 83 | 84 | public String toBinary() { 85 | StringBuilder sb = new StringBuilder(); 86 | for(int i=0; i< this._bytes.length; i++){ 87 | Byte b = this._bytes[i]; 88 | sb.append(String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0')); 89 | } 90 | return sb.toString(); 91 | } 92 | 93 | 94 | @Override 95 | public String toString() { 96 | return toBigInteger().toString(); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/thaler/miner/ddb/HashableByteArray.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner.ddb; 2 | 3 | import org.apache.commons.codec.binary.Base64; 4 | import org.apache.commons.codec.binary.Hex; 5 | 6 | import com.thaler.miner.merkle.Hashable; 7 | 8 | public class HashableByteArray implements Hashable{ 9 | 10 | 11 | private byte[] _bytes; 12 | 13 | public static HashableByteArray create(byte[] bytes){ 14 | return new HashableByteArray(bytes); 15 | } 16 | 17 | public HashableByteArray(byte[] bytes) { 18 | this._bytes = bytes; 19 | } 20 | 21 | @Override 22 | public String toString(){ 23 | return Base64.encodeBase64URLSafeString(this._bytes); 24 | } 25 | 26 | public String toHex(){ 27 | return Hex.encodeHexString(this._bytes); 28 | } 29 | 30 | 31 | @Override 32 | public byte[] getBytes() { 33 | return this._bytes; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/thaler/miner/ddb/HashableString.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner.ddb; 2 | 3 | import org.bouncycastle.util.Strings; 4 | 5 | import com.thaler.miner.merkle.Hashable; 6 | 7 | public class HashableString implements Hashable{ 8 | 9 | private String _string; 10 | 11 | public static HashableString create(String string){ 12 | return new HashableString(string); 13 | } 14 | 15 | public HashableString(String string) { 16 | this._string = string; 17 | } 18 | 19 | public String getString(){ 20 | return this._string; 21 | } 22 | 23 | @Override 24 | public byte[] getBytes() { 25 | return Strings.toUTF8ByteArray(this._string); 26 | } 27 | 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/thaler/miner/ddb/UnsignedBitsUtil.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner.ddb; 2 | 3 | import java.math.BigInteger; 4 | 5 | import com.google.common.base.Preconditions; 6 | 7 | public final class UnsignedBitsUtil { 8 | 9 | public static byte[] bigIntegerToBits(BigInteger bigInteger, int bits) { 10 | Preconditions.checkArgument(bits % 8 == 0); 11 | Preconditions.checkArgument(bits > 0); 12 | 13 | byte[] bytes = new byte[bits / 8]; 14 | final byte[] bigIntByteArray = bigInteger.toByteArray(); 15 | int startIndex; 16 | 17 | if (bigIntByteArray.length == bytes.length + 1) { 18 | startIndex = 1; 19 | } else if (bigIntByteArray.length <= bytes.length) { 20 | startIndex = 0; 21 | } else { 22 | throw new RuntimeException("Number to large to fit in byte array. bits: " + bits + " integer value: " + bigInteger + " integer bytes:" + bigIntByteArray.length); 23 | } 24 | 25 | final int offset = bytes.length - bigIntByteArray.length; 26 | for (int i = startIndex; i < bigIntByteArray.length; i++) { 27 | bytes[i + offset] = bigIntByteArray[i]; 28 | } 29 | 30 | return bytes; 31 | } 32 | 33 | public static BigInteger bitsToBigInteger(byte[] byteArray) { 34 | return new BigInteger(1, byteArray); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/thaler/miner/localdb/LocalDatabase.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner.localdb; 2 | 3 | import java.sql.CallableStatement; 4 | import java.sql.Connection; 5 | import java.sql.DriverManager; 6 | import java.sql.PreparedStatement; 7 | import java.sql.ResultSet; 8 | import java.sql.SQLException; 9 | 10 | import org.apache.commons.dbcp.ConnectionFactory; 11 | import org.apache.commons.dbcp.DriverManagerConnectionFactory; 12 | import org.apache.commons.dbcp.PoolableConnectionFactory; 13 | import org.apache.commons.dbcp.PoolingDriver; 14 | import org.apache.commons.pool.ObjectPool; 15 | import org.apache.commons.pool.impl.GenericObjectPool; 16 | import org.apache.log4j.Logger; 17 | import org.h2.Driver; 18 | 19 | import com.google.common.base.Optional; 20 | import com.google.common.base.Preconditions; 21 | import com.google.common.collect.ImmutableSet; 22 | import com.thaler.miner.ddb.Bits256; 23 | import com.thaler.miner.ddb.HashableByteArray; 24 | import com.thaler.miner.merkle.Hashable; 25 | 26 | public class LocalDatabase { 27 | 28 | private static final Logger logger = Logger.getLogger(LocalDatabase.class); 29 | 30 | private static final long H2_CACHE_SIZE_KB = 1024 * 10; 31 | 32 | private static final String H2_OPTIONS = ";CACHE_SIZE=" + H2_CACHE_SIZE_KB; 33 | 34 | public static final String LOCAL_DATABASE_FILEPATH = "durableMessageStore.filepath"; 35 | 36 | 37 | private static final String CONNECTION_POOL_JDBC_DRIVER_URI = "jdbc:apache:commons:dbcp:"; 38 | private static final String CONNECTION_POOL_NAME = "connection_pool"; 39 | 40 | private static final String CONNECTION_POOL_URI = "jdbc:apache:commons:dbcp:" + CONNECTION_POOL_NAME; 41 | 42 | private static final int LARGEST_VALUE_SIZE_BYTES = 4096; 43 | 44 | 45 | private static LocalDatabase instance; 46 | 47 | private LocalDatabase(){ 48 | setupDb(); 49 | } 50 | 51 | public static LocalDatabase getInstance(){ 52 | if(null == instance){ 53 | instance = new LocalDatabase(); 54 | } 55 | return instance; 56 | } 57 | 58 | public void put(Bits256 key, Hashable value){ 59 | Preconditions.checkNotNull(key); 60 | Preconditions.checkNotNull(value); 61 | 62 | if(exists(key)){ 63 | return; 64 | } 65 | 66 | Connection connection = getConnection(); 67 | try { 68 | final PreparedStatement statement = getPreparedStatement(connection, "INSERT INTO KEY_VALUE_PAIRS(KEY,VALUE) VALUES(?,?)"); 69 | statement.setBytes(1, key.getBytes()); 70 | statement.setBytes(2, value.getBytes()); 71 | final int result = statement.executeUpdate(); 72 | if (result != 1) { 73 | throw new RuntimeException("Error inserting into local database. key: " + key.toString()); 74 | } 75 | } catch (final SQLException e) { 76 | throw new RuntimeException(e); 77 | } finally { 78 | closeConneciton(connection); 79 | } 80 | } 81 | 82 | public boolean exists(Bits256 key){ 83 | return get(key).isPresent(); 84 | } 85 | 86 | public Optional get(Bits256 key){ 87 | Preconditions.checkNotNull(key); 88 | Connection connection = getConnection(); 89 | try { 90 | final PreparedStatement statement = getPreparedStatement(connection, "SELECT VALUE FROM KEY_VALUE_PAIRS"); 91 | final ResultSet resultSet = statement.executeQuery(); 92 | 93 | Hashable result; 94 | 95 | if(resultSet.first()){ 96 | result = HashableByteArray.create(resultSet.getBytes(1)); 97 | }else{ 98 | result = null; 99 | } 100 | return Optional.fromNullable(result); 101 | } catch (final SQLException e) { 102 | throw new RuntimeException(e); 103 | } finally { 104 | closeConneciton(connection); 105 | } 106 | } 107 | 108 | private PreparedStatement getPreparedStatement(Connection connection, final String statement) { 109 | PreparedStatement result; 110 | try { 111 | result = connection.prepareStatement(statement); 112 | } catch (final SQLException e) { 113 | throw new RuntimeException(e); 114 | } 115 | return result; 116 | } 117 | 118 | private void setupDb() { 119 | try { 120 | Class.forName(Driver.class.getName()); 121 | Class.forName(PoolingDriver.class.getName()); 122 | } catch (final ClassNotFoundException e) { 123 | throw new RuntimeException(e); 124 | } 125 | 126 | setupDbConnectionPool(); 127 | setupDbSchema(); 128 | } 129 | 130 | private void setupDbSchema() { 131 | executePreparedStatements( 132 | "CREATE CACHED TABLE IF NOT EXISTS KEY_VALUE_PAIRS(KV_ID IDENTITY, KEY BINARY(32), VALUE BINARY("+LARGEST_VALUE_SIZE_BYTES+"), CREATED LONGVARCHAR)", 133 | "CREATE UNIQUE INDEX IF NOT EXISTS KEY_INDEX ON KEY_VALUE_PAIRS(KEY)"); 134 | } 135 | 136 | private void executePreparedStatements(final String... statements) { 137 | Connection connection = getConnection(); 138 | try { 139 | 140 | for (String statement : statements) { 141 | { 142 | final CallableStatement callableStatement = connection.prepareCall(statement); 143 | callableStatement.execute(); 144 | } 145 | } 146 | } catch (final SQLException e) { 147 | throw new RuntimeException(e); 148 | } finally { 149 | closeConneciton(connection); 150 | } 151 | } 152 | 153 | private static void closeConneciton(Connection connection) { 154 | try { 155 | connection.close(); 156 | } catch (SQLException e) { 157 | throw new RuntimeException(e); 158 | } 159 | } 160 | 161 | 162 | public Connection getConnection() { 163 | Connection connection; 164 | try { 165 | connection = DriverManager.getConnection(CONNECTION_POOL_URI); 166 | } catch (SQLException e) { 167 | throw new RuntimeException(e); 168 | } 169 | return connection; 170 | } 171 | 172 | private void setupDbConnectionPool() { 173 | try { 174 | final String durableMessageStoreFile = System.getProperty(LOCAL_DATABASE_FILEPATH, "/tmp/miner"); 175 | logger.info("DurableMessageSender storage directory: " + durableMessageStoreFile); 176 | 177 | String jdbcUri = "jdbc:h2:" + durableMessageStoreFile + H2_OPTIONS; 178 | 179 | ObjectPool connectionPool = new GenericObjectPool(null); 180 | ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(jdbcUri, null); 181 | PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory(connectionFactory, connectionPool, null, null, false, true); 182 | 183 | Class.forName("org.apache.commons.dbcp.PoolingDriver"); 184 | PoolingDriver driver = (PoolingDriver) DriverManager.getDriver(CONNECTION_POOL_JDBC_DRIVER_URI); 185 | 186 | driver.registerPool(CONNECTION_POOL_NAME, connectionPool); 187 | } catch (final SQLException e) { 188 | throw new RuntimeException(e); 189 | } catch (ClassNotFoundException e) { 190 | throw new RuntimeException(e); 191 | } 192 | 193 | } 194 | 195 | public static void logConnectionPoolDriverStats() { 196 | PoolingDriver driver; 197 | try { 198 | driver = (PoolingDriver) DriverManager.getDriver(CONNECTION_POOL_JDBC_DRIVER_URI); 199 | ObjectPool connectionPool = driver.getConnectionPool(CONNECTION_POOL_NAME); 200 | 201 | logger.info("Connection Pool NumActive: " + connectionPool.getNumActive()); 202 | logger.info("Connection Pool NumIdle: " + connectionPool.getNumIdle()); 203 | } catch (SQLException e) { 204 | throw new RuntimeException(e); 205 | } 206 | } 207 | 208 | } 209 | -------------------------------------------------------------------------------- /src/main/java/com/thaler/miner/merkle/Hashable.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner.merkle; 2 | 3 | public interface Hashable { 4 | 5 | public byte[] getBytes(); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/thaler/miner/merkle/MerkleTreeNode.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner.merkle; 2 | 3 | import com.thaler.miner.ddb.Bits256; 4 | import com.thaler.miner.util.CryptoUtil; 5 | 6 | 7 | public class MerkleTreeNode { 8 | 9 | public static MerkleTreeNode create(Bits256 left, Bits256 right, String name) { 10 | return new MerkleTreeNode(left, right, name); 11 | } 12 | 13 | private MerkleTreeNode(Bits256 left, Bits256 right, String name) { 14 | this.left = left; 15 | this.right = right; 16 | this.name = name; 17 | this.combinedHash = CryptoUtil.hash(left,right); 18 | } 19 | public Bits256 combinedHash; 20 | public Bits256 left; 21 | public Bits256 right; 22 | public String name; 23 | 24 | public boolean isChild(MerkleTreeNode childNode){ 25 | return childNode.combinedHash.equals(left) || childNode.combinedHash.equals(right); 26 | } 27 | 28 | 29 | @Override 30 | public String toString() { 31 | return name + " (" + combinedHash + " | " + left + " , " + right + ")"; 32 | } 33 | 34 | @Override 35 | public int hashCode() { 36 | final int prime = 31; 37 | int result = 1; 38 | result = prime * result + ((combinedHash == null) ? 0 : combinedHash.hashCode()); 39 | result = prime * result + ((left == null) ? 0 : left.hashCode()); 40 | result = prime * result + ((name == null) ? 0 : name.hashCode()); 41 | result = prime * result + ((right == null) ? 0 : right.hashCode()); 42 | return result; 43 | } 44 | 45 | @Override 46 | public boolean equals(Object obj) { 47 | if (this == obj) return true; 48 | if (obj == null) return false; 49 | if (getClass() != obj.getClass()) return false; 50 | MerkleTreeNode other = (MerkleTreeNode) obj; 51 | if (combinedHash == null) { 52 | if (other.combinedHash != null) return false; 53 | } else if (!combinedHash.equals(other.combinedHash)) return false; 54 | if (left == null) { 55 | if (other.left != null) return false; 56 | } else if (!left.equals(other.left)) return false; 57 | if (name == null) { 58 | if (other.name != null) return false; 59 | } else if (!name.equals(other.name)) return false; 60 | if (right == null) { 61 | if (other.right != null) return false; 62 | } else if (!right.equals(other.right)) return false; 63 | return true; 64 | } 65 | 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/thaler/miner/merkle/MerkleUtil.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner.merkle; 2 | 3 | import java.math.BigInteger; 4 | import java.util.Iterator; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import org.apache.log4j.Logger; 9 | 10 | import com.google.common.base.Objects; 11 | import com.google.common.base.Preconditions; 12 | import com.google.common.collect.FluentIterable; 13 | import com.google.common.collect.Lists; 14 | import com.google.common.collect.Maps; 15 | import com.thaler.miner.ddb.Bits256; 16 | import com.thaler.miner.util.CryptoUtil; 17 | 18 | public class MerkleUtil { 19 | 20 | private static final Logger logger = Logger.getLogger(MerkleUtil.class); 21 | 22 | 23 | public static MerkleTreeNode createMerkleTree(FluentIterable baseData, Map merkleTreeMap) { 24 | Preconditions.checkArgument(baseData.size() >= 2); 25 | 26 | FluentIterable intermediateNodes = createInitialTreeNodes(baseData); 27 | addNodesToMap(intermediateNodes, merkleTreeMap); 28 | BigInteger column = BigInteger.ZERO; 29 | while (intermediateNodes.size() > 1) { // TODO 2 Billion node limit!!! 30 | //intermediateNodes = graphParents(intermediateNodes); 31 | intermediateNodes = createTreeNodes(intermediateNodes, column.toString()); 32 | addNodesToMap(intermediateNodes, merkleTreeMap); 33 | column = column.add(BigInteger.ONE); 34 | //logger.info("intermediateNodesSize: " + intermediateNodes.size()); 35 | } 36 | 37 | return intermediateNodes.first().get(); 38 | 39 | } 40 | 41 | private static void addNodesToMap(FluentIterable intermediateNodes, Map merkleTreeMap) { 42 | if(merkleTreeMap == null){ 43 | return; 44 | } 45 | for(MerkleTreeNode merkleTreeNode : intermediateNodes) { 46 | merkleTreeMap.put(merkleTreeNode.combinedHash, merkleTreeNode); 47 | } 48 | } 49 | 50 | private static FluentIterable createTreeNodes(FluentIterable singles, String columnName) { 51 | List result = Lists.newArrayList(); 52 | Iterator iterator = singles.iterator(); 53 | BigInteger row = BigInteger.ZERO; 54 | while (iterator.hasNext()) { 55 | Bits256 left = iterator.next().combinedHash; 56 | Bits256 right; 57 | if (iterator.hasNext()) { 58 | right = iterator.next().combinedHash; 59 | } else { 60 | right = left; 61 | } 62 | MerkleTreeNode pair = MerkleTreeNode.create(left, right, columnName + "," + row); 63 | result.add(pair); 64 | row = row.add(BigInteger.ONE); 65 | } 66 | return FluentIterable.from(result); 67 | } 68 | 69 | 70 | private static FluentIterable createInitialTreeNodes(FluentIterable nodeDataItems) { 71 | List nodes = Lists.newArrayList(); 72 | BigInteger row = BigInteger.ZERO; 73 | for (Hashable nodeDataItem : nodeDataItems) { 74 | //TODO MJT quadruple hash of base data uneeded 75 | MerkleTreeNode treeNode = MerkleTreeNode.create( CryptoUtil.hash(nodeDataItem), CryptoUtil.hash(nodeDataItem), "initial data row: " + row); 76 | row = row.add(BigInteger.ONE); 77 | nodes.add(treeNode); 78 | } 79 | return FluentIterable.from(nodes); 80 | } 81 | 82 | public static List createMerkePath(Map merkleMap, MerkleTreeNode merkleRoot, Bits256 targetHash) { 83 | List result = Lists.newArrayList(); 84 | 85 | Map childToParentMap = Maps.newHashMap(); 86 | for(MerkleTreeNode merkleTreeNode: merkleMap.values()){ 87 | childToParentMap.put(merkleTreeNode.left, merkleTreeNode); 88 | childToParentMap.put(merkleTreeNode.right, merkleTreeNode); 89 | } 90 | 91 | MerkleTreeNode node = null; 92 | while(node == null || !node.equals(merkleRoot)){ 93 | 94 | node = childToParentMap.get(targetHash); 95 | targetHash = node.combinedHash; 96 | //logger.info("stepping node: " + node); 97 | result.add(node); 98 | } 99 | 100 | return result; 101 | } 102 | 103 | public static boolean validateMerklePath(List merklePath, MerkleTreeNode merkleRoot){ 104 | List reverseMerklePath = Lists.reverse(merklePath); 105 | if(reverseMerklePath.get(0).combinedHash.equals(merkleRoot)){ 106 | logger.info("merkle root not found"); 107 | return false; 108 | } 109 | FluentIterable reverseMerklePathIterable = FluentIterable.from(reverseMerklePath); 110 | 111 | MerkleTreeNode previousNode = reverseMerklePathIterable.first().get(); 112 | logger.info("Merkle Root: " + previousNode); 113 | for(MerkleTreeNode merkleTreeNode : reverseMerklePathIterable.skip(1)){ 114 | logger.info(merkleTreeNode); 115 | if(!previousNode.isChild(merkleTreeNode)){ 116 | logger.warn("Invalid merkle path detected.\npreviousNode: " + previousNode + "\nmerkleTreeNode: " + merkleTreeNode); 117 | return false; 118 | } 119 | previousNode = merkleTreeNode; 120 | 121 | } 122 | return true; 123 | 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/com/thaler/miner/merkle/Pair.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner.merkle; 2 | 3 | public class Pair { 4 | 5 | private T left; 6 | private T right; 7 | 8 | public static Pair create(T left, T right) { 9 | 10 | return new Pair(left, right); 11 | } 12 | 13 | public Pair(T left, T right) { 14 | this.left = left; 15 | this.right = right; 16 | } 17 | 18 | public T getLeft() { 19 | return left; 20 | } 21 | 22 | public T getRight() { 23 | return right; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/thaler/miner/transaction/TransactionHash.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner.transaction; 2 | 3 | public class TransactionHash { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/thaler/miner/util/CryptoUtil.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner.util; 2 | 3 | import java.security.GeneralSecurityException; 4 | import java.security.MessageDigest; 5 | import java.security.NoSuchAlgorithmException; 6 | 7 | import com.lambdaworks.crypto.SCrypt; 8 | import com.thaler.miner.ddb.Bits256; 9 | import com.thaler.miner.merkle.Hashable; 10 | 11 | public class CryptoUtil { 12 | 13 | private static final String SHA_256 = "SHA-256"; 14 | 15 | public static Bits256 hash(byte[] bytes) { 16 | MessageDigest md; 17 | try { 18 | md = MessageDigest.getInstance(SHA_256); 19 | } catch (NoSuchAlgorithmException e) { 20 | throw new RuntimeException(e); 21 | } 22 | md.update(bytes); 23 | return Bits256.create(md.digest()); 24 | 25 | } 26 | 27 | public static Bits256 scrypt(byte[] P){ 28 | 29 | //byte[] P, S; 30 | int N, r, p, dkLen; 31 | String DK; 32 | 33 | 34 | 35 | //P = "password2".getBytes("UTF-8"); 36 | //S = "NaCl".getBytes("UTF-8"); 37 | byte[] S = P; 38 | N = (int)Math.pow(2, 15); // cpu 15 == 1s/h 39 | //r = 8; // memory 40 | r = 8; 41 | //p = 16; // parallel 42 | p = 1; // parallel 43 | dkLen = 32; 44 | 45 | byte[] result; 46 | try { 47 | result = SCrypt.scrypt(P, S, N, r, p, dkLen); 48 | } catch (GeneralSecurityException e) { 49 | throw new RuntimeException(e); 50 | } 51 | return Bits256.create(result); 52 | } 53 | 54 | public static Bits256 hash(Hashable... hashables) { 55 | MessageDigest md; 56 | try { 57 | md = MessageDigest.getInstance(SHA_256); 58 | } catch (NoSuchAlgorithmException e) { 59 | throw new RuntimeException(e); 60 | } 61 | 62 | for (Hashable hashable : hashables) { 63 | md.update(hashable.getBytes()); 64 | } 65 | 66 | return Bits256.create(md.digest()); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/resources/buildInfo.properties: -------------------------------------------------------------------------------- 1 | buildNumber=${buildNumber} -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # 3 | # The following properties set the logging levels and log appender. The 4 | # log4j.rootCategory variable defines the default log level and one or more 5 | # appenders. For the console, use 'S'. For the daily rolling file, use 'R'. 6 | # For an HTML formatted log, use 'H'. 7 | # 8 | # To override the default (rootCategory) log level, define a property of the 9 | # form (see below for available values): 10 | # 11 | # Possible Log Levels: 12 | # FATAL, ERROR, WARN, INFO, DEBUG, TRACE 13 | # 14 | #------------------------------------------------------------------------------ 15 | log4j.rootCategory=ALL, R, S 16 | 17 | log4j.logger.org.apache.activemq=ERROR 18 | log4j.logger.org.springframework=ERROR 19 | log4j.logger.org.w3c.tidy=FATAL 20 | 21 | #------------------------------------------------------------------------------ 22 | # 23 | # The following properties configure the console (stdout) appender. 24 | # See http://logging.apache.org/log4j/docs/api/index.html for details. 25 | # 26 | #------------------------------------------------------------------------------ 27 | log4j.appender.S = org.apache.log4j.ConsoleAppender 28 | log4j.appender.S.layout = org.apache.log4j.PatternLayout 29 | log4j.appender.S.layout.ConversionPattern = %d %t (%c{1}.java:%L) %p - %m%n 30 | 31 | #------------------------------------------------------------------------------ 32 | # 33 | # The following properties configure the Daily Rolling File appender. 34 | # See http://logging.apache.org/log4j/docs/api/index.html for details. 35 | # 36 | #------------------------------------------------------------------------------ 37 | log4j.appender.R = org.apache.log4j.DailyRollingFileAppender 38 | log4j.appender.R.File = /tmp/thaler-miner.log 39 | log4j.appender.R.Append = true 40 | log4j.appender.R.layout = org.apache.log4j.PatternLayout 41 | log4j.appender.R.layout.ConversionPattern = %d %t (%c{1}.java:%L) %p - %m%n 42 | 43 | #------------------------------------------------------------------------------ 44 | # 45 | # The following properties configure the Rolling File appender in HTML. 46 | # See http://logging.apache.org/log4j/docs/api/index.html for details. 47 | # 48 | #------------------------------------------------------------------------------ 49 | log4j.appender.H = org.apache.log4j.RollingFileAppender 50 | log4j.appender.H.File = logs/bensApps.html 51 | log4j.appender.H.MaxFileSize = 100KB 52 | log4j.appender.H.Append = false 53 | log4j.appender.H.layout = org.apache.log4j.HTMLLayout -------------------------------------------------------------------------------- /src/test/java/com/thaler/miner/BitManipulationUnitTests.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner; 2 | 3 | import java.math.BigInteger; 4 | import java.time.Instant; 5 | 6 | import org.apache.log4j.Logger; 7 | import org.junit.Assert; 8 | import org.junit.Rule; 9 | import org.junit.Test; 10 | import org.junit.rules.ExpectedException; 11 | 12 | import com.thaler.miner.ddb.Bits256Timestamp; 13 | import com.thaler.miner.ddb.Bits256UnsignedFraction; 14 | import com.thaler.miner.ddb.Bits256UnsignedInteger; 15 | 16 | public class BitManipulationUnitTests { 17 | 18 | private static final Logger logger = Logger.getLogger(BitManipulationUnitTests.class); 19 | 20 | private static final String MAX_VALUE_STRING = "115792089237316195423570985008687907853269984665640564039457584007913129639935"; 21 | 22 | @Rule 23 | public ExpectedException expectedException = ExpectedException.none(); 24 | 25 | @Test 26 | public void testBits256Integer() { 27 | 28 | // basic sanity check 29 | { 30 | final BigInteger bigInteger = BigInteger.valueOf(1098); 31 | final Bits256UnsignedInteger bits256UnsignedInteger = Bits256UnsignedInteger.create(bigInteger); 32 | Assert.assertTrue(bits256UnsignedInteger.toBigInteger().equals(bigInteger)); 33 | } 34 | 35 | // max value check 36 | { 37 | 38 | final BigInteger max256BitNumberFromString = new BigInteger(MAX_VALUE_STRING); 39 | Assert.assertTrue(max256BitNumberFromString.equals(Bits256UnsignedInteger.MAX_VALUE.toBigInteger())); 40 | } 41 | 42 | // min value check 43 | { 44 | Assert.assertTrue(Bits256UnsignedInteger.MIN_VALUE.toBigInteger().equals(BigInteger.valueOf(0))); 45 | } 46 | 47 | } 48 | 49 | @Test 50 | public void testBits256IntegerMaxBigInt(){ 51 | Bits256UnsignedInteger.create(Bits256UnsignedInteger.MAX_VALUE.toBigInteger()); 52 | } 53 | 54 | @Test 55 | public void testBits256IntegerMaxValueOverflow() { 56 | expectedException.expect(IllegalArgumentException.class); 57 | expectedException.expectMessage("value is too large"); 58 | final BigInteger max256BitNumberFromString = new BigInteger(MAX_VALUE_STRING); 59 | final BigInteger tooLarge = max256BitNumberFromString.add(BigInteger.ONE); 60 | final Bits256UnsignedInteger bits256UnsignedInteger = Bits256UnsignedInteger.create(tooLarge); 61 | logger.info(bits256UnsignedInteger); 62 | } 63 | 64 | @Test 65 | public void testBits256IntegerPositiveValuesOnly() { 66 | expectedException.expect(IllegalArgumentException.class); 67 | expectedException.expectMessage("value must be greater than or equal to zero"); 68 | Bits256UnsignedInteger.create(BigInteger.valueOf(-1)); 69 | } 70 | 71 | @Test 72 | public void testBits256TimeStamp() { 73 | final Bits256Timestamp bits256Timestamp = Bits256Timestamp.now(); 74 | logger.info(bits256Timestamp); 75 | logger.info(Instant.now().getEpochSecond()); 76 | logger.info(bits256Timestamp.toInstant().getEpochSecond()); 77 | Assert.assertEquals(Instant.now().getEpochSecond(), bits256Timestamp.toInstant().getEpochSecond()); 78 | final byte[] bytes = bits256Timestamp.getBytes(); 79 | final Bits256Timestamp fromRawBytesTimeStamp = Bits256Timestamp.create(bytes); 80 | logger.info(fromRawBytesTimeStamp); 81 | Assert.assertEquals(bits256Timestamp, fromRawBytesTimeStamp); 82 | 83 | } 84 | 85 | @Test 86 | public void testBits256Fraction() { 87 | Bits256UnsignedFraction bits256UnsignedFraction = Bits256UnsignedFraction.create(3, 5); 88 | logger.info(bits256UnsignedFraction); 89 | logger.info(bits256UnsignedFraction.toHex()); 90 | logger.info(bits256UnsignedFraction.toBinary()); 91 | Assert.assertEquals(BigInteger.valueOf(3), bits256UnsignedFraction.getNumerator()); 92 | Assert.assertEquals(BigInteger.valueOf(5), bits256UnsignedFraction.getDenominator()); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/test/java/com/thaler/miner/CryptoUtilTests.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner; 2 | 3 | 4 | import org.apache.log4j.Logger; 5 | import org.junit.Test; 6 | 7 | import com.lambdaworks.crypto.SCrypt; 8 | import com.thaler.miner.ddb.HashableByteArray; 9 | 10 | public class CryptoUtilTests { 11 | 12 | private static final Logger logger = Logger.getLogger(CryptoUtilTests.class); 13 | 14 | @Test 15 | public void test() throws Exception{ 16 | 17 | byte[] P, S; 18 | int N, r, p, dkLen; 19 | String DK; 20 | 21 | 22 | 23 | P = "password2".getBytes("UTF-8"); 24 | S = "NaCl".getBytes("UTF-8"); 25 | //N = (int)Math.pow(2, 15); // cpu 15 == 1s/h 26 | N = (int)Math.pow(2, 1); // cpu 15 == 1s/h 27 | logger.info("n:" + N); 28 | //r = 8; // memory 29 | r = (int)Math.pow(2, 20); 30 | logger.info("r:" + r); 31 | //p = 16; // parallel 32 | p = 1; // parallel 33 | dkLen = 32; 34 | DK = "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640"; 35 | 36 | logger.info("start!"); 37 | for(int i=0; i<100000000; i++){ 38 | P = ("password" + i).getBytes("UTF-8"); 39 | byte[] result = SCrypt.scrypt(P, S, N, r, p, dkLen); 40 | //logger.info("length: " + result.length); 41 | HashableByteArray hashableByteArray = HashableByteArray.create(result); 42 | logger.info(hashableByteArray.toHex()); 43 | } 44 | 45 | //Assert.assertArrayEquals(decode(DK), SCrypt.scrypt(P, S, N, r, p, dkLen)); 46 | 47 | } 48 | 49 | public static byte[] decode(String str) { 50 | byte[] bytes = new byte[str.length() / 2]; 51 | int index = 0; 52 | 53 | for (int i = 0; i < str.length(); i += 2) { 54 | int high = hexValue(str.charAt(i)); 55 | int low = hexValue(str.charAt(i + 1)); 56 | bytes[index++] = (byte) ((high << 4) + low); 57 | } 58 | 59 | return bytes; 60 | } 61 | public static int hexValue(char c) { 62 | return c >= 'a' ? c - 87 : c - 48; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/com/thaler/miner/LocalDbTests.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner; 2 | 3 | import org.apache.log4j.Logger; 4 | import org.junit.Assert; 5 | import org.junit.Test; 6 | 7 | import com.thaler.miner.ddb.Bits256; 8 | import com.thaler.miner.ddb.HashableByteArray; 9 | import com.thaler.miner.localdb.LocalDatabase; 10 | import com.thaler.miner.merkle.Hashable; 11 | import com.thaler.miner.util.CryptoUtil; 12 | 13 | public class LocalDbTests { 14 | private static final Logger logger = Logger.getLogger(LocalDbTests.class); 15 | 16 | @Test 17 | public void putGetTest() { 18 | LocalDatabase ldb = LocalDatabase.getInstance(); 19 | 20 | Hashable value = HashableByteArray.create("The value".getBytes()); 21 | logger.info("value: " + value); 22 | Bits256 key = CryptoUtil.hash(value); 23 | 24 | ldb.put(key, value); 25 | { 26 | Hashable returnedValue = ldb.get(key).get(); 27 | logger.info("returned value: " + returnedValue); 28 | Assert.assertArrayEquals(value.getBytes(), returnedValue.getBytes()); 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/com/thaler/miner/MainTest.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner; 2 | 3 | import junit.framework.Test; 4 | import junit.framework.TestCase; 5 | import junit.framework.TestSuite; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class MainTest 11 | extends TestCase 12 | { 13 | /** 14 | * Create the test case 15 | * 16 | * @param testName name of the test case 17 | */ 18 | public MainTest( String testName ) 19 | { 20 | super( testName ); 21 | } 22 | 23 | /** 24 | * @return the suite of tests being tested 25 | */ 26 | public static Test suite() 27 | { 28 | return new TestSuite( MainTest.class ); 29 | } 30 | 31 | /** 32 | * Rigourous Test :-) 33 | */ 34 | public void testApp() 35 | { 36 | assertTrue( true ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/com/thaler/miner/MerkleTests.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner; 2 | 3 | import static org.junit.Assert.fail; 4 | 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import org.apache.log4j.Logger; 9 | import org.junit.Assert; 10 | import org.junit.Test; 11 | 12 | import com.google.common.collect.FluentIterable; 13 | import com.google.common.collect.Lists; 14 | import com.google.common.collect.Maps; 15 | import com.thaler.miner.ddb.Bits256; 16 | import com.thaler.miner.ddb.HashableString; 17 | import com.thaler.miner.merkle.Hashable; 18 | import com.thaler.miner.merkle.MerkleUtil; 19 | import com.thaler.miner.merkle.MerkleTreeNode; 20 | import com.thaler.miner.util.CryptoUtil; 21 | 22 | public class MerkleTests { 23 | 24 | public String TSA = "Test String A"; 25 | public String TSB = "Test String B"; 26 | public String TSC = "Test String C"; 27 | public String TSD = "Test String D"; 28 | 29 | private FluentIterable createBaseData() { 30 | List baseData = Lists.newArrayList(); 31 | for(int i=0; i< 100160; i++){ 32 | baseData.add(HashableString.create("Test data: " + i)); 33 | } 34 | return FluentIterable.from(baseData); 35 | } 36 | 37 | private static final Logger logger = Logger.getLogger(MerkleTests.class); 38 | 39 | @Test 40 | public void merkleRootTest() { 41 | 42 | { 43 | Map merkleMap = Maps.newHashMap(); 44 | FluentIterable baseData = createBaseData(); 45 | MerkleTreeNode merkleRoot = MerkleUtil.createMerkleTree(baseData, merkleMap); 46 | logger.info("merkle tree Root: " + merkleRoot.combinedHash.toString()); 47 | logger.info("tree root: " + merkleMap.get(merkleRoot.combinedHash)); 48 | 49 | Hashable firstDataItem = baseData.first().get(); 50 | Bits256 doubleHashOfFirstDataItem = CryptoUtil.hash(CryptoUtil.hash(firstDataItem), CryptoUtil.hash(firstDataItem)); 51 | logger.info("first data item: " + merkleMap.get(doubleHashOfFirstDataItem)); 52 | } 53 | 54 | } 55 | 56 | @Test 57 | public void merklePathCreationTest() { 58 | FluentIterable baseData = createBaseData(); 59 | Hashable firstDataItem = baseData.last().get(); 60 | Bits256 doubleHashOfFirstDataItem = CryptoUtil.hash(CryptoUtil.hash(firstDataItem), CryptoUtil.hash(firstDataItem)); 61 | Map merkleMap = Maps.newHashMap(); 62 | MerkleTreeNode merkleRoot = MerkleUtil.createMerkleTree(baseData, merkleMap); 63 | List merklePath = MerkleUtil.createMerkePath(merkleMap, merkleRoot, doubleHashOfFirstDataItem); 64 | 65 | logger.info("merkle tree Root: " + merkleRoot.combinedHash.toString()); 66 | logger.info("first data item node: " + merkleMap.get(doubleHashOfFirstDataItem)); 67 | 68 | logger.info("----path----"); 69 | for(MerkleTreeNode merkleTreeNode : merklePath){ 70 | logger.info(merkleTreeNode); 71 | } 72 | logger.info("----path end----"); 73 | } 74 | 75 | @Test 76 | public void merklePathValidationTest() { 77 | FluentIterable baseData = createBaseData(); 78 | Hashable firstDataItem = baseData.last().get(); 79 | Bits256 doubleHashOfFirstDataItem = CryptoUtil.hash(CryptoUtil.hash(firstDataItem), CryptoUtil.hash(firstDataItem)); 80 | Map merkleMap = Maps.newHashMap(); 81 | MerkleTreeNode merkleRoot = MerkleUtil.createMerkleTree(baseData, merkleMap); 82 | List merklePath = MerkleUtil.createMerkePath(merkleMap, merkleRoot, doubleHashOfFirstDataItem); 83 | 84 | logger.info("merkle tree Root: " + merkleRoot.combinedHash.toString()); 85 | logger.info("first data item node: " + merkleMap.get(doubleHashOfFirstDataItem)); 86 | 87 | logger.info("----path----"); 88 | for(MerkleTreeNode merkleTreeNode : merklePath){ 89 | logger.info(merkleTreeNode); 90 | } 91 | logger.info("----path end----"); 92 | 93 | Assert.assertTrue(MerkleUtil.validateMerklePath(merklePath, merkleRoot)); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/test/java/com/thaler/miner/MineBlockTests.java: -------------------------------------------------------------------------------- 1 | package com.thaler.miner; 2 | 3 | import java.math.BigInteger; 4 | import java.time.Instant; 5 | 6 | import org.apache.log4j.Logger; 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | 10 | import com.thaler.miner.block.Block; 11 | import com.thaler.miner.block.Block.Version; 12 | import com.thaler.miner.block.BlockUtil; 13 | import com.thaler.miner.ddb.Bits256; 14 | import com.thaler.miner.ddb.Bits256Timestamp; 15 | import com.thaler.miner.ddb.Bits256UnsignedInteger; 16 | import com.thaler.miner.util.CryptoUtil; 17 | 18 | public class MineBlockTests { 19 | private static final Logger logger = Logger.getLogger(MineBlockTests.class); 20 | 21 | @Test 22 | public void calcProofOfWork() { 23 | 24 | Bits256 previousBlockMerkleRoot = CryptoUtil.hash("Test".getBytes()); 25 | 26 | Block.Version version = Version.TEST; 27 | Bits256UnsignedInteger sequence = Bits256UnsignedInteger.create(999); 28 | Bits256Timestamp timeStamp = Bits256Timestamp.now(); 29 | Bits256UnsignedInteger maxIndex = Bits256UnsignedInteger.create(1000); 30 | Bits256UnsignedInteger targetDifficutly = Bits256UnsignedInteger.create( 31 | Bits256UnsignedInteger.MAX_VALUE.toBigInteger() 32 | .clearBit(255) 33 | .clearBit(254) 34 | .clearBit(253) 35 | .clearBit(252) 36 | .clearBit(251) 37 | .clearBit(250) 38 | .clearBit(249) 39 | .clearBit(248) 40 | .clearBit(247) 41 | ); 42 | 43 | Block b = Block.create(previousBlockMerkleRoot, version, sequence, timeStamp, maxIndex, targetDifficutly); 44 | 45 | b.newIndexAddresseAmountTupleListMerkleRoot = previousBlockMerkleRoot; 46 | b.nonceMerkleRoot = previousBlockMerkleRoot; 47 | b.newTransactionTuplesMerkleRoot = previousBlockMerkleRoot; 48 | b.spentIndexBitmapMerkleRoot = previousBlockMerkleRoot; 49 | 50 | logger.info(b); 51 | Assert.assertNull(b.merkleRoot); 52 | BlockUtil.mine(b); 53 | Assert.assertNotNull(b.merkleRoot); 54 | logger.info(b); 55 | Assert.assertTrue(b.isValidMerkleRoot()); 56 | b.maxIndex = Bits256UnsignedInteger.create(maxIndex.toBigInteger().subtract(BigInteger.valueOf(1))); 57 | Assert.assertFalse(b.isValidMerkleRoot()); 58 | 59 | for (int i = 0; i < 1000; i++) { 60 | b.maxIndex = Bits256UnsignedInteger.create(i); 61 | BlockUtil.mine(b); 62 | //logger.info(b.targetDifficulty.toHex()); 63 | // logger.info(b.targetDifficulty.toBinary()); 64 | // logger.info(b.merkleRoot.toBinary()); 65 | 66 | boolean validPow = b.isValidProofOfWork(); 67 | // logger.info(validPow); 68 | 69 | if (validPow) { 70 | logger.info("i=" + i); 71 | break; 72 | } 73 | } 74 | 75 | //Assert.assertTrue(b.isValidProofOfWork()); 76 | } 77 | 78 | } 79 | --------------------------------------------------------------------------------