Merge pull request #38 from schierlm/local-pow

Add support for local PoW - thx to @schierlm
This commit is contained in:
adrianziser 2017-09-18 16:14:44 +02:00 committed by GitHub
commit a8ca265825
5 changed files with 278 additions and 0 deletions

View File

@ -0,0 +1,217 @@
package cfb.pearldiver;
/**
* (c) 2016 Come-from-Beyond.
*
* See <https://github.com/iotaledger/PearlDiver>.
*/
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;
}
}
}
}

View File

@ -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);
}
}

View File

@ -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<GetAttachToTangleResponse> 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;
}
}
}

View File

@ -0,0 +1,8 @@
package jota;
/**
* Interface for an implementation to perform local PoW.
*/
public interface IotaLocalPoW {
public String performPoW(String trytes, int minWeightMagnitude);
}

View File

@ -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.
*