From 2a39e03f36340cff630437e76fbf675ebfb3525b Mon Sep 17 00:00:00 2001 From: Michael Schierl Date: Fri, 15 Sep 2017 20:36:04 +0200 Subject: [PATCH] Add support for local PoW To use it, call the `localPoW` method of the IotaAPI.Builder and pass your desired PoW implementation. The included implementation uses @Come-from-Beyond's PearlDiver (which according to a statement of him on Slack may be used "for whatever you wish" despite missing a license in the repo) to performs PoW by CPU. Other implementations are possible (even outside this lib) by implementing the IotaLocalPoW interface. The PearlDiver class has been taken from iotaledger/PearlDiver@212c332d6002095e9dc8f74789deb3747b8b4870, the only modification was to make it Java 7 compatible by replacing the lambda with a Runnable anonymous class and fixing the nested `this` references to point to the outer class. --- src/main/java/cfb/pearldiver/PearlDiver.java | 217 ++++++++++++++++++ .../cfb/pearldiver/PearlDiverLocalPoW.java | 20 ++ src/main/java/jota/IotaAPICore.java | 25 ++ src/main/java/jota/IotaLocalPoW.java | 8 + .../response/GetAttachToTangleResponse.java | 8 + 5 files changed, 278 insertions(+) create mode 100644 src/main/java/cfb/pearldiver/PearlDiver.java create mode 100644 src/main/java/cfb/pearldiver/PearlDiverLocalPoW.java create mode 100644 src/main/java/jota/IotaLocalPoW.java diff --git a/src/main/java/cfb/pearldiver/PearlDiver.java b/src/main/java/cfb/pearldiver/PearlDiver.java new file mode 100644 index 0000000..f2ed86d --- /dev/null +++ b/src/main/java/cfb/pearldiver/PearlDiver.java @@ -0,0 +1,217 @@ +package cfb.pearldiver; + +/** + * (c) 2016 Come-from-Beyond. + * + * See . + */ +public class PearlDiver { + + public static final int TRANSACTION_LENGTH = 8019; + + private static final int CURL_HASH_LENGTH = 243; + private static final int CURL_STATE_LENGTH = CURL_HASH_LENGTH * 3; + + private static final int RUNNING = 0; + private static final int CANCELLED = 1; + private static final int COMPLETED = 2; + + private volatile int state; + + public synchronized void cancel() { + + state = CANCELLED; + + notifyAll(); + } + + public synchronized boolean search(final int[] transactionTrits, final int minWeightMagnitude, int numberOfThreads) { + + if (transactionTrits.length != TRANSACTION_LENGTH) { + + throw new RuntimeException("Invalid transaction trits length: " + transactionTrits.length); + } + if (minWeightMagnitude < 0 || minWeightMagnitude > CURL_HASH_LENGTH) { + + throw new RuntimeException("Invalid min weight magnitude: " + minWeightMagnitude); + } + + state = RUNNING; + + final long[] midCurlStateLow = new long[CURL_STATE_LENGTH], midCurlStateHigh = new long[CURL_STATE_LENGTH]; + + { + for (int i = CURL_HASH_LENGTH; i < CURL_STATE_LENGTH; i++) { + + midCurlStateLow[i] = 0b1111111111111111111111111111111111111111111111111111111111111111L; + midCurlStateHigh[i] = 0b1111111111111111111111111111111111111111111111111111111111111111L; + } + + int offset = 0; + final long[] curlScratchpadLow = new long[CURL_STATE_LENGTH], curlScratchpadHigh = new long[CURL_STATE_LENGTH]; + for (int i = (TRANSACTION_LENGTH - CURL_HASH_LENGTH) / CURL_HASH_LENGTH; i-- > 0; ) { + + for (int j = 0; j < CURL_HASH_LENGTH; j++) { + + switch (transactionTrits[offset++]) { + + case 0: { + + midCurlStateLow[j] = 0b1111111111111111111111111111111111111111111111111111111111111111L; + midCurlStateHigh[j] = 0b1111111111111111111111111111111111111111111111111111111111111111L; + + } break; + + case 1: { + + midCurlStateLow[j] = 0b0000000000000000000000000000000000000000000000000000000000000000L; + midCurlStateHigh[j] = 0b1111111111111111111111111111111111111111111111111111111111111111L; + + } break; + + default: { + + midCurlStateLow[j] = 0b1111111111111111111111111111111111111111111111111111111111111111L; + midCurlStateHigh[j] = 0b0000000000000000000000000000000000000000000000000000000000000000L; + } + } + } + + transform(midCurlStateLow, midCurlStateHigh, curlScratchpadLow, curlScratchpadHigh); + } + + midCurlStateLow[0] = 0b1101101101101101101101101101101101101101101101101101101101101101L; + midCurlStateHigh[0] = 0b1011011011011011011011011011011011011011011011011011011011011011L; + midCurlStateLow[1] = 0b1111000111111000111111000111111000111111000111111000111111000111L; + midCurlStateHigh[1] = 0b1000111111000111111000111111000111111000111111000111111000111111L; + midCurlStateLow[2] = 0b0111111111111111111000000000111111111111111111000000000111111111L; + midCurlStateHigh[2] = 0b1111111111000000000111111111111111111000000000111111111111111111L; + midCurlStateLow[3] = 0b1111111111000000000000000000000000000111111111111111111111111111L; + midCurlStateHigh[3] = 0b0000000000111111111111111111111111111111111111111111111111111111L; + } + + if (numberOfThreads <= 0) { + + numberOfThreads = Runtime.getRuntime().availableProcessors() - 1; + if (numberOfThreads < 1) { + + numberOfThreads = 1; + } + } + + while (numberOfThreads-- > 0) { + + final int threadIndex = numberOfThreads; + (new Thread(new Runnable() { public void run() { + + final long[] midCurlStateCopyLow = new long[CURL_STATE_LENGTH], midCurlStateCopyHigh = new long[CURL_STATE_LENGTH]; + System.arraycopy(midCurlStateLow, 0, midCurlStateCopyLow, 0, CURL_STATE_LENGTH); + System.arraycopy(midCurlStateHigh, 0, midCurlStateCopyHigh, 0, CURL_STATE_LENGTH); + for (int i = threadIndex; i-- > 0; ) { + + increment(midCurlStateCopyLow, midCurlStateCopyHigh, CURL_HASH_LENGTH / 3, (CURL_HASH_LENGTH / 3) * 2); + } + + final long[] curlStateLow = new long[CURL_STATE_LENGTH], curlStateHigh = new long[CURL_STATE_LENGTH]; + final long[] curlScratchpadLow = new long[CURL_STATE_LENGTH], curlScratchpadHigh = new long[CURL_STATE_LENGTH]; + while (state == RUNNING) { + + increment(midCurlStateCopyLow, midCurlStateCopyHigh, (CURL_HASH_LENGTH / 3) * 2, CURL_HASH_LENGTH); + System.arraycopy(midCurlStateCopyLow, 0, curlStateLow, 0, CURL_STATE_LENGTH); + System.arraycopy(midCurlStateCopyHigh, 0, curlStateHigh, 0, CURL_STATE_LENGTH); + transform(curlStateLow, curlStateHigh, curlScratchpadLow, curlScratchpadHigh); + + NEXT_BIT_INDEX: + for (int bitIndex = 64; bitIndex-- > 0; ) { + + for (int i = minWeightMagnitude; i-- > 0; ) { + + if ((((int)(curlStateLow[CURL_HASH_LENGTH - 1 - i] >> bitIndex)) & 1) != (((int)(curlStateHigh[CURL_HASH_LENGTH - 1 - i] >> bitIndex)) & 1)) { + + continue NEXT_BIT_INDEX; + } + } + + synchronized (PearlDiver.this) { + + if (state == RUNNING) { + + state = COMPLETED; + + for (int i = 0; i < CURL_HASH_LENGTH; i++) { + + transactionTrits[TRANSACTION_LENGTH - CURL_HASH_LENGTH + i] = ((((int) (midCurlStateCopyLow[i] >> bitIndex)) & 1) == 0) ? 1 : (((((int) (midCurlStateCopyHigh[i] >> bitIndex)) & 1) == 0) ? -1 : 0); + } + + PearlDiver.this.notifyAll(); + } + } + + break; + } + } + + }})).start(); + } + + try { + + while (state == RUNNING) { + + wait(); + } + + } catch (final InterruptedException e) { + + state = CANCELLED; + } + + return state == COMPLETED; + } + + private static void transform(final long[] curlStateLow, final long[] curlStateHigh, final long[] curlScratchpadLow, final long[] curlScratchpadHigh) { + + int curlScratchpadIndex = 0; + for (int round = 27; round-- > 0; ) { + + System.arraycopy(curlStateLow, 0, curlScratchpadLow, 0, CURL_STATE_LENGTH); + System.arraycopy(curlStateHigh, 0, curlScratchpadHigh, 0, CURL_STATE_LENGTH); + + for (int curlStateIndex = 0; curlStateIndex < CURL_STATE_LENGTH; curlStateIndex++) { + + final long alpha = curlScratchpadLow[curlScratchpadIndex]; + final long beta = curlScratchpadHigh[curlScratchpadIndex]; + final long gamma = curlScratchpadHigh[curlScratchpadIndex += (curlScratchpadIndex < 365 ? 364 : -365)]; + final long delta = (alpha | (~gamma)) & (curlScratchpadLow[curlScratchpadIndex] ^ beta); + + curlStateLow[curlStateIndex] = ~delta; + curlStateHigh[curlStateIndex] = (alpha ^ gamma) | delta; + } + } + } + + private static void increment(final long[] midCurlStateCopyLow, final long[] midCurlStateCopyHigh, final int fromIndex, final int toIndex) { + + for (int i = fromIndex; i < toIndex; i++) { + + if (midCurlStateCopyLow[i] == 0b0000000000000000000000000000000000000000000000000000000000000000L) { + + midCurlStateCopyLow[i] = 0b1111111111111111111111111111111111111111111111111111111111111111L; + midCurlStateCopyHigh[i] = 0b0000000000000000000000000000000000000000000000000000000000000000L; + + } else { + + if (midCurlStateCopyHigh[i] == 0b0000000000000000000000000000000000000000000000000000000000000000L) { + + midCurlStateCopyHigh[i] = 0b1111111111111111111111111111111111111111111111111111111111111111L; + + } else { + + midCurlStateCopyLow[i] = 0b0000000000000000000000000000000000000000000000000000000000000000L; + } + + break; + } + } + } +} \ No newline at end of file diff --git a/src/main/java/cfb/pearldiver/PearlDiverLocalPoW.java b/src/main/java/cfb/pearldiver/PearlDiverLocalPoW.java new file mode 100644 index 0000000..a470be6 --- /dev/null +++ b/src/main/java/cfb/pearldiver/PearlDiverLocalPoW.java @@ -0,0 +1,20 @@ +package cfb.pearldiver; + +import jota.IotaLocalPoW; +import jota.utils.Converter; + +/** + * Perform local PoW using Come-from-Beyond's PearlDiver implementation. + */ +public class PearlDiverLocalPoW implements IotaLocalPoW { + + PearlDiver pearlDiver = new PearlDiver(); + + @Override + public String performPoW(String trytes, int minWeightMagnitude) { + int[] trits = Converter.trits(trytes); + if (!pearlDiver.search(trits, minWeightMagnitude, 0)) + throw new IllegalStateException("PearlDiver search failed"); + return Converter.trytes(trits); + } +} diff --git a/src/main/java/jota/IotaAPICore.java b/src/main/java/jota/IotaAPICore.java index e018a28..e1305ab 100644 --- a/src/main/java/jota/IotaAPICore.java +++ b/src/main/java/jota/IotaAPICore.java @@ -3,6 +3,7 @@ package jota; import jota.dto.request.*; import jota.dto.response.*; import jota.error.InvalidTrytesException; +import jota.model.Transaction; import jota.utils.InputValidator; import okhttp3.OkHttpClient; import org.slf4j.Logger; @@ -31,6 +32,7 @@ public class IotaAPICore { private IotaAPIService service; private String protocol, host, port; + private IotaLocalPoW localPoW; /** * Build the API core. @@ -41,6 +43,7 @@ public class IotaAPICore { protocol = builder.protocol; host = builder.host; port = builder.port; + localPoW = builder.localPoW; postConstruct(); } @@ -193,6 +196,19 @@ public class IotaAPICore { throw new InvalidTrytesException(); } + if (localPoW != null) { + final String[] resultTrytes = new String[trytes.length]; + String previousTransaction = null; + for (int i = 0; i < trytes.length; i++) { + Transaction txn = new Transaction(trytes[i]); + txn.setTrunkTransaction(previousTransaction == null ? trunkTransaction : previousTransaction); + txn.setBranchTransaction(previousTransaction == null ? branchTransaction : trunkTransaction); + resultTrytes[i] = localPoW.performPoW(txn.toTrytes(), minWeightMagnitude); + previousTransaction = new Transaction(resultTrytes[i]).getHash(); + } + return new GetAttachToTangleResponse(resultTrytes); + } + final Call res = service.attachToTangle(IotaAttachToTangleRequest.createAttachToTangleRequest(trunkTransaction, branchTransaction, minWeightMagnitude, trytes)); return wrapCheckedException(res).body(); } @@ -226,6 +242,7 @@ public class IotaAPICore { private Properties nodeConfig = null; String protocol, host, port; + IotaLocalPoW localPoW; public IotaAPICore build() { // resolution order: builder value, configuration file, default value @@ -307,5 +324,13 @@ public class IotaAPICore { return (T) this; } + /** + * @param protocol + * @return + */ + public T localPoW(IotaLocalPoW localPoW) { + this.localPoW = localPoW; + return (T) this; + } } } diff --git a/src/main/java/jota/IotaLocalPoW.java b/src/main/java/jota/IotaLocalPoW.java new file mode 100644 index 0000000..7a7ae7d --- /dev/null +++ b/src/main/java/jota/IotaLocalPoW.java @@ -0,0 +1,8 @@ +package jota; + +/** + * Interface for an implementation to perform local PoW. + */ +public interface IotaLocalPoW { + public String performPoW(String trytes, int minWeightMagnitude); +} diff --git a/src/main/java/jota/dto/response/GetAttachToTangleResponse.java b/src/main/java/jota/dto/response/GetAttachToTangleResponse.java index 1038087..ef76e84 100644 --- a/src/main/java/jota/dto/response/GetAttachToTangleResponse.java +++ b/src/main/java/jota/dto/response/GetAttachToTangleResponse.java @@ -14,6 +14,14 @@ public class GetAttachToTangleResponse extends AbstractResponse { setDuration(duration); } + /** + * Initializes a new instance of the GetAttachToTangleResponse class with the given trytes. + */ + public GetAttachToTangleResponse(String[] trytes) { + setDuration(0L); + this.trytes = trytes; + } + /** * Gets the rytes. *