├── .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 |
--------------------------------------------------------------------------------