diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 2aaea06..0000000 --- a/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/Java/bin/ -/Java/target/ -/Java/.settings/ diff --git a/Java/.classpath b/Java/.classpath deleted file mode 100644 index f3d3455..0000000 --- a/Java/.classpath +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Java/.gitignore b/Java/.gitignore new file mode 100644 index 0000000..e941587 --- /dev/null +++ b/Java/.gitignore @@ -0,0 +1,4 @@ +target/ +.settings* +.classpath +.project diff --git a/Java/.project b/Java/.project deleted file mode 100644 index 70a0c8c..0000000 --- a/Java/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - CIYAM-AT-Java - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.m2e.core.maven2Nature - org.eclipse.jdt.core.javanature - - diff --git a/Java/maven-import.txt b/Java/maven-import.txt new file mode 100644 index 0000000..160a427 --- /dev/null +++ b/Java/maven-import.txt @@ -0,0 +1,11 @@ +# How to import CIYAM AT JAR into your project + +# Assumes: +# your project is called MY-PROJECT +# your project has local repository in MY-PROJECT/lib/ +# CIYAM AT JAR pathname is in ${CIYAM_AT_JAR} + +CIYAM_AT_VERSION=1.2 +CIYAM_AT_JAR=../CIYAM-AT/Java/target/AT-${CIYAM_AT_VERSION}.jar +cd MY-PROJECT +mvn install:install-file -DlocalRepositoryPath=lib/ -Dfile=${CIYAM_AT_JAR} -DgroupId=org.ciyam -DartifactId=at -Dpackaging=jar -Dversion=${CIYAM_AT_VERSION} diff --git a/Java/pom.xml b/Java/pom.xml index 96a3131..c207635 100644 --- a/Java/pom.xml +++ b/Java/pom.xml @@ -1,28 +1,42 @@ - - 4.0.0 - CIYAM-AT-Java - CIYAM-AT-Java - 1.0 - - src - tests - - - maven-compiler-plugin - 3.5.1 - - 1.8 - 1.8 - - - - - - - org.bouncycastle - bcprov-jdk15on - 1.60 - test - - + + 4.0.0 + org.ciyam + AT + 1.2 + jar + + true + 1.64 + + + src/main/java + src/test/java + + + maven-compiler-plugin + 3.8.0 + + 11 + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M4 + + ${skipTests} + + + + + + + org.bouncycastle + bcprov-jdk15on + ${bouncycastle.version} + test + + \ No newline at end of file diff --git a/Java/src/org/ciyam/at/API.java b/Java/src/main/java/org/ciyam/at/API.java similarity index 57% rename from Java/src/org/ciyam/at/API.java rename to Java/src/main/java/org/ciyam/at/API.java index 5c45d72..07bd3e4 100644 --- a/Java/src/org/ciyam/at/API.java +++ b/Java/src/main/java/org/ciyam/at/API.java @@ -1,5 +1,12 @@ package org.ciyam.at; +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.toMap; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Map; + /** * API for CIYAM AT "Function Codes" for blockchain-specific interactions. *

@@ -12,6 +19,27 @@ package org.ciyam.at; */ public abstract class API { + /** Suggested transaction types to be used by the AT sub-system */ + public enum ATTransactionType { + PAYMENT(0), + MESSAGE(1); + + public final long value; + + private static final Map map = stream(ATTransactionType.values()).collect(toMap(type -> type.value, type -> type)); + + ATTransactionType(long value) { + this.value = value; + } + + public static ATTransactionType valueOf(long value) { + return map.get(value); + } + } + + /** Returns maximum number of permitted steps per execution round */ + public abstract int getMaxStepsPerRound(); + /** Returns fee for executing opcode in terms of execution "steps" */ public abstract int getOpCodeSteps(OpCode opcode); @@ -32,7 +60,7 @@ public abstract class API { /** Put previous block's signature hash in A */ public abstract void putPreviousBlockHashInA(MachineState state); - /** Put next transaction to AT after timestamp in A */ + /** Put next transaction to AT after timestamp in A, or zero A if no more transactions */ public abstract void putTransactionAfterTimestampInA(Timestamp timestamp, MachineState state); /** Return type from transaction in A, or 0xffffffffffffffff if A not valid transaction */ @@ -56,7 +84,7 @@ public abstract class API { */ public abstract long generateRandomUsingTransactionInA(MachineState state); - /** Put 'message' from transaction in A into B */ + /** Put 'message' from transaction in A into B, or zero B if not a message transaction */ public abstract void putMessageFromTransactionInAIntoB(MachineState state); /** Put sender/creator address from transaction in A into B */ @@ -81,14 +109,20 @@ public abstract class API { */ public abstract long addMinutesToTimestamp(Timestamp timestamp, long minutes, MachineState state); - /** AT has finished. Return remaining funds to creator */ + /** + * AT has finished. Return remaining funds to creator. + * + * @param amount + * - final balance to be returned to creator + * @param state + */ public abstract void onFinished(long amount, MachineState state); /** AT has encountered fatal error */ public abstract void onFatalError(MachineState state, ExecutionException e); /** Pre-execute checking of param requirements for platform-specific functions */ - public abstract void platformSpecificPreExecuteCheck(short functionCodeValue, int paramCount, boolean returnValueExpected) + public abstract void platformSpecificPreExecuteCheck(int paramCount, boolean returnValueExpected, MachineState state, short rawFunctionCode) throws IllegalFunctionCodeException; /** @@ -96,7 +130,7 @@ public abstract class API { * * @throws ExecutionException */ - public abstract void platformSpecificPostCheckExecute(short functionCodeValue, FunctionData functionData, MachineState state) throws ExecutionException; + public abstract void platformSpecificPostCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException; /** Convenience method to allow subclasses to access package-scoped MachineState.setIsSleeping */ protected void setIsSleeping(MachineState state, boolean isSleeping) { @@ -108,37 +142,90 @@ public abstract class API { return state.isFirstOpCodeAfterSleeping(); } - /** Convenience methods to allow subclasses to access package-scoped a1-a4, b1-b4 variables */ - protected void setA1(MachineState state, long value) { + /** Convenience method to allow subclasses to access MachineState.rewindCodePosition */ + protected void rewindCodePosition(MachineState state, int offset) { + state.rewindCodePosition(offset); + } + + protected void setSleepUntilHeight(MachineState state, int height) { + state.setSleepUntilHeight(height); + } + + /* Convenience methods to allow subclasses to access package-scoped a1-a4, b1-b4 variables */ + + public void zeroA(MachineState state) { + state.a1 = 0L; + state.a2 = 0L; + state.a3 = 0L; + state.a4 = 0L; + } + + public void zeroB(MachineState state) { + state.b1 = 0L; + state.b2 = 0L; + state.b3 = 0L; + state.b4 = 0L; + } + + public void setAToMaxValue(MachineState state) { + state.a1 = 0xffffffffffffffffL; + state.a2 = 0xffffffffffffffffL; + state.a3 = 0xffffffffffffffffL; + state.a4 = 0xffffffffffffffffL; + } + + public void setA1(MachineState state, long value) { state.a1 = value; } - protected void setA2(MachineState state, long value) { + public void setA2(MachineState state, long value) { state.a2 = value; } - protected void setA3(MachineState state, long value) { + public void setA3(MachineState state, long value) { state.a3 = value; } - protected void setA4(MachineState state, long value) { + public void setA4(MachineState state, long value) { state.a4 = value; } - protected void setB1(MachineState state, long value) { + public void setA(MachineState state, byte[] bytes) { + // Enforce endian + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + + state.a1 = byteBuffer.getLong(); + state.a2 = byteBuffer.getLong(); + state.a3 = byteBuffer.getLong(); + state.a4 = byteBuffer.getLong(); + } + + public void setB1(MachineState state, long value) { state.b1 = value; } - protected void setB2(MachineState state, long value) { + public void setB2(MachineState state, long value) { state.b2 = value; } - protected void setB3(MachineState state, long value) { + public void setB3(MachineState state, long value) { state.b3 = value; } - protected void setB4(MachineState state, long value) { + public void setB4(MachineState state, long value) { state.b4 = value; } + public void setB(MachineState state, byte[] bytes) { + // Enforce endian + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + + state.b1 = byteBuffer.getLong(); + state.b2 = byteBuffer.getLong(); + state.b3 = byteBuffer.getLong(); + state.b4 = byteBuffer.getLong(); + } + } diff --git a/Java/src/org/ciyam/at/CodeSegmentException.java b/Java/src/main/java/org/ciyam/at/CodeSegmentException.java similarity index 100% rename from Java/src/org/ciyam/at/CodeSegmentException.java rename to Java/src/main/java/org/ciyam/at/CodeSegmentException.java diff --git a/Java/src/org/ciyam/at/ExecutionException.java b/Java/src/main/java/org/ciyam/at/ExecutionException.java similarity index 100% rename from Java/src/org/ciyam/at/ExecutionException.java rename to Java/src/main/java/org/ciyam/at/ExecutionException.java diff --git a/Java/src/org/ciyam/at/FunctionCode.java b/Java/src/main/java/org/ciyam/at/FunctionCode.java similarity index 98% rename from Java/src/org/ciyam/at/FunctionCode.java rename to Java/src/main/java/org/ciyam/at/FunctionCode.java index 0bf8263..1a5e1bf 100644 --- a/Java/src/org/ciyam/at/FunctionCode.java +++ b/Java/src/main/java/org/ciyam/at/FunctionCode.java @@ -474,8 +474,8 @@ public enum FunctionCode { state.b1 = digestByteBuffer.getLong(); state.b2 = digestByteBuffer.getLong(); - state.b3 = 0L; // XXX Or do we leave B3 untouched? - state.b4 = 0L; // XXX Or do we leave B4 untouched? + state.b3 = 0L; + state.b4 = 0L; } catch (NoSuchAlgorithmException e) { throw new ExecutionException("No MD5 message digest service available", e); } @@ -544,7 +544,7 @@ public enum FunctionCode { state.b1 = digestByteBuffer.getLong(); state.b2 = digestByteBuffer.getLong(); state.b3 = (long) digestByteBuffer.getInt() & 0xffffffffL; - state.b4 = 0L; // XXX Or do we leave B4 untouched? + state.b4 = 0L; } catch (NoSuchAlgorithmException e) { throw new ExecutionException("No RIPEMD160 message digest service available", e); } @@ -577,7 +577,7 @@ public enum FunctionCode { digestByteBuffer.putLong(state.b1); digestByteBuffer.putLong(state.b2); digestByteBuffer.putInt((int) (state.b3 & 0xffffffffL)); - // XXX: b4 ignored + // NOTE: b4 ignored byte[] expectedDigest = digestByteBuffer.array(); @@ -762,8 +762,7 @@ public enum FunctionCode { // If API set isSleeping then rewind program counter (actually codeByteBuffer) ready for being awoken if (state.getIsSleeping()) { // EXT_FUN_RET(1) + our function code(2) + address(4) - int newPosition = state.codeByteBuffer.position() - MachineState.OPCODE_SIZE - MachineState.FUNCTIONCODE_SIZE - MachineState.ADDRESS_SIZE; - state.codeByteBuffer.position(newPosition); + state.rewindCodePosition(MachineState.OPCODE_SIZE + MachineState.FUNCTIONCODE_SIZE + MachineState.ADDRESS_SIZE); // If specific sleep height not set, default to next block if (state.getSleepUntilHeight() == null) @@ -910,12 +909,12 @@ public enum FunctionCode { API_PASSTHROUGH(0x0500, 0, false) { @Override public void preExecuteCheck(int paramCount, boolean returnValueExpected, MachineState state, short rawFunctionCode) throws ExecutionException { - state.getAPI().platformSpecificPreExecuteCheck(rawFunctionCode, paramCount, returnValueExpected); + state.getAPI().platformSpecificPreExecuteCheck(paramCount, returnValueExpected, state, rawFunctionCode); } @Override protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { - state.getAPI().platformSpecificPostCheckExecute(rawFunctionCode, functionData, state); + state.getAPI().platformSpecificPostCheckExecute(functionData, state, rawFunctionCode); } }; diff --git a/Java/src/org/ciyam/at/FunctionData.java b/Java/src/main/java/org/ciyam/at/FunctionData.java similarity index 100% rename from Java/src/org/ciyam/at/FunctionData.java rename to Java/src/main/java/org/ciyam/at/FunctionData.java diff --git a/Java/src/org/ciyam/at/IllegalFunctionCodeException.java b/Java/src/main/java/org/ciyam/at/IllegalFunctionCodeException.java similarity index 100% rename from Java/src/org/ciyam/at/IllegalFunctionCodeException.java rename to Java/src/main/java/org/ciyam/at/IllegalFunctionCodeException.java diff --git a/Java/src/org/ciyam/at/IllegalOperationException.java b/Java/src/main/java/org/ciyam/at/IllegalOperationException.java similarity index 100% rename from Java/src/org/ciyam/at/IllegalOperationException.java rename to Java/src/main/java/org/ciyam/at/IllegalOperationException.java diff --git a/Java/src/org/ciyam/at/InvalidAddressException.java b/Java/src/main/java/org/ciyam/at/InvalidAddressException.java similarity index 100% rename from Java/src/org/ciyam/at/InvalidAddressException.java rename to Java/src/main/java/org/ciyam/at/InvalidAddressException.java diff --git a/Java/src/org/ciyam/at/LoggerInterface.java b/Java/src/main/java/org/ciyam/at/LoggerInterface.java similarity index 100% rename from Java/src/org/ciyam/at/LoggerInterface.java rename to Java/src/main/java/org/ciyam/at/LoggerInterface.java diff --git a/Java/src/org/ciyam/at/MachineState.java b/Java/src/main/java/org/ciyam/at/MachineState.java similarity index 83% rename from Java/src/org/ciyam/at/MachineState.java rename to Java/src/main/java/org/ciyam/at/MachineState.java index 0883ca0..4bf7866 100644 --- a/Java/src/org/ciyam/at/MachineState.java +++ b/Java/src/main/java/org/ciyam/at/MachineState.java @@ -11,7 +11,8 @@ import java.util.Map; public class MachineState { /** Header bytes length */ - public static final int HEADER_LENGTH = 2 + 2 + 2 + 2 + 2 + 2; // version reserved code data call-stack user-stack + // version + reserved + code + data + call-stack + user-stack + min-activation-amount + public static final int HEADER_LENGTH = 2 + 2 + 2 + 2 + 2 + 2 + 8; /** Size of one OpCode - typically 1 byte (byte) */ public static final int OPCODE_SIZE = 1; @@ -28,9 +29,6 @@ public class MachineState { /** Maximum value for an address in the code segment */ public static final int MAX_CODE_ADDRESS = 0x0000ffff; - /** Maximum number of steps per execution round */ - public static final int MAX_STEPS = 500; - private static class VersionedConstants { /** Bytes per code page */ public final int CODE_PAGE_SIZE; @@ -50,10 +48,10 @@ public class MachineState { } /** Map of constants (e.g. CODE_PAGE_SIZE) by AT version */ - private static final Map VERSIONED_CONSTANTS = new HashMap(); + private static final Map VERSIONED_CONSTANTS = new HashMap<>(); static { VERSIONED_CONSTANTS.put((short) 1, new VersionedConstants(256, 256, 256, 256)); - VERSIONED_CONSTANTS.put((short) 3, new VersionedConstants(OPCODE_SIZE, VALUE_SIZE, ADDRESS_SIZE, VALUE_SIZE)); + VERSIONED_CONSTANTS.put((short) 2, new VersionedConstants(OPCODE_SIZE, VALUE_SIZE, ADDRESS_SIZE, VALUE_SIZE)); } // Set during construction @@ -63,6 +61,7 @@ public class MachineState { public final short numDataPages; public final short numCallStackPages; public final short numUserStackPages; + public final long minActivationAmount; private final byte[] headerBytes; @@ -148,7 +147,7 @@ public class MachineState { this.version = byteBuffer.getShort(); if (this.version < 1) - throw new IllegalArgumentException("Version must be >= 0"); + throw new IllegalArgumentException("Version must be > 0"); this.constants = VERSIONED_CONSTANTS.get(this.version); if (this.constants == null) @@ -172,6 +171,8 @@ public class MachineState { if (this.numUserStackPages < 0) throw new IllegalArgumentException("Number of user stack pages must be >= 0"); + this.minActivationAmount = byteBuffer.getLong(); + // Header OK - set up code and data buffers this.codeByteBuffer = ByteBuffer.allocate(this.numCodePages * this.constants.CODE_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN); this.dataByteBuffer = ByteBuffer.allocate(this.numDataPages * this.constants.DATA_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN); @@ -207,7 +208,7 @@ public class MachineState { commonFinalConstruction(); } - /** For creating a new machine state */ + /** For creating a new machine state - used in tests */ public MachineState(API api, LoggerInterface logger, byte[] headerBytes, byte[] codeBytes, byte[] dataBytes) { this(api, logger, headerBytes); @@ -236,6 +237,14 @@ public class MachineState { this.isFinished = false; this.hadFatalError = false; this.previousBalance = 0; + + // If we have a minimum activation amount then create AT in frozen state, requiring that amount to unfreeze. + // If creator also sends funds with creation then AT will unfreeze on first call. + if (this.minActivationAmount > 0) { + this.isFrozen = true; + // -1 because current balance has to exceed frozenBalance to unfreeze AT + this.frozenBalance = this.minActivationAmount - 1; + } } // Getters / setters @@ -275,8 +284,8 @@ public class MachineState { return this.sleepUntilHeight; } - /* package */ void setSleepUntilHeight(Integer address) { - this.sleepUntilHeight = address; + /* package */ void setSleepUntilHeight(Integer height) { + this.sleepUntilHeight = height; } public boolean getIsStopped() { @@ -319,7 +328,6 @@ public class MachineState { this.hadFatalError = hadFatalError; } - // No corresponding setters due to package-scope - see above public long getA1() { return this.a1; } @@ -336,6 +344,18 @@ public class MachineState { return this.a4; } + public byte[] getA() { + ByteBuffer byteBuffer = ByteBuffer.allocate(4 * 8); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + + byteBuffer.putLong(this.a1); + byteBuffer.putLong(this.a2); + byteBuffer.putLong(this.a3); + byteBuffer.putLong(this.a4); + + return byteBuffer.array(); + } + public long getB1() { return this.b1; } @@ -351,7 +371,18 @@ public class MachineState { public long getB4() { return this.b4; } - // End of package-scope pseudo-registers + + public byte[] getB() { + ByteBuffer byteBuffer = ByteBuffer.allocate(4 * 8); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + + byteBuffer.putLong(this.b1); + byteBuffer.putLong(this.b2); + byteBuffer.putLong(this.b3); + byteBuffer.putLong(this.b4); + + return byteBuffer.array(); + } public int getCurrentBlockHeight() { return this.currentBlockHeight; @@ -389,8 +420,80 @@ public class MachineState { return this.isFirstOpCodeAfterSleeping; } + /** + * Rewinds program counter by amount. + *

+ * Actually rewinds codeByteBuffer's position, not PC, as the later is synchronized from the former after each OpCode is executed. + * + * @param offset + */ + /* package */ void rewindCodePosition(int offset) { + this.codeByteBuffer.position(this.codeByteBuffer.position() - offset); + } + // Serialization + public static byte[] toCreationBytes(short version, byte[] codeBytes, byte[] dataBytes, short numCallStackPages, short numUserStackPages, long minActivationAmount) { + if (version < 1) + throw new IllegalArgumentException("Version must be > 0"); + + VersionedConstants constants = VERSIONED_CONSTANTS.get(version); + if (constants == null) + throw new IllegalArgumentException("Version " + version + " unsupported"); + + // Calculate number of code pages + if (codeBytes.length == 0) + throw new IllegalArgumentException("Empty code bytes"); + short numCodePages = (short) (((codeBytes.length - 1) / constants.CODE_PAGE_SIZE) + 1); + + // Calculate number of data pages + if (dataBytes.length == 0) + throw new IllegalArgumentException("Empty data bytes"); + short numDataPages = (short) (((dataBytes.length - 1) / constants.DATA_PAGE_SIZE) + 1); + + int creationBytesLength = HEADER_LENGTH + numCodePages * constants.CODE_PAGE_SIZE + numDataPages + constants.DATA_PAGE_SIZE; + byte[] creationBytes = new byte[creationBytesLength]; + + ByteBuffer byteBuffer = ByteBuffer.wrap(creationBytes); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + + // Header bytes: + + // Version + byteBuffer.putShort(version); + + // Reserved + byteBuffer.putShort((short) 0); + + // Code length + byteBuffer.putShort(numCodePages); + + // Data length + byteBuffer.putShort(numDataPages); + + // Call stack length + byteBuffer.putShort(numCallStackPages); + + // User stack length + byteBuffer.putShort(numUserStackPages); + + // Minimum activation amount + byteBuffer.putLong(minActivationAmount); + + // Code bytes + System.arraycopy(codeBytes, 0, creationBytes, HEADER_LENGTH, codeBytes.length); + + // Data bytes + System.arraycopy(dataBytes, 0, creationBytes, HEADER_LENGTH + numCodePages * constants.CODE_PAGE_SIZE, dataBytes.length); + + return creationBytes; + } + + /** Returns code bytes only as these are read-only so no need to be duplicated in every serialized state */ + public byte[] getCodeBytes() { + return this.codeByteBuffer.array(); + } + /** For serializing a machine state */ public byte[] toBytes() { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); @@ -399,9 +502,6 @@ public class MachineState { // Header first bytes.write(this.headerBytes); - // Code - bytes.write(this.codeByteBuffer.array()); - // Data bytes.write(this.dataByteBuffer.array()); @@ -473,7 +573,7 @@ public class MachineState { } /** For restoring a previously serialized machine state */ - public static MachineState fromBytes(API api, LoggerInterface logger, byte[] bytes) { + public static MachineState fromBytes(API api, LoggerInterface logger, byte[] bytes, byte[] codeBytes) { ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); byte[] headerBytes = new byte[HEADER_LENGTH]; @@ -481,8 +581,9 @@ public class MachineState { MachineState state = new MachineState(api, logger, headerBytes); - byte[] codeBytes = new byte[state.codeByteBuffer.capacity()]; - byteBuffer.get(codeBytes); + if (codeBytes.length != state.codeByteBuffer.capacity()) + throw new IllegalStateException("Passed codeBytes does not match length in header"); + System.arraycopy(codeBytes, 0, state.codeByteBuffer.array(), 0, codeBytes.length); byte[] dataBytes = new byte[state.dataByteBuffer.capacity()]; @@ -631,7 +732,9 @@ public class MachineState { this.isFrozen = false; this.frozenBalance = null; + // Cache useful info from API long feePerStep = this.api.getFeePerStep(); + int maxSteps = api.getMaxStepsPerRound(); // Set byte buffer position using program counter codeByteBuffer.position(this.programCounter); @@ -650,8 +753,8 @@ public class MachineState { int opcodeSteps = this.api.getOpCodeSteps(nextOpCode); long opcodeFee = opcodeSteps * feePerStep; - if (this.steps + opcodeSteps > MAX_STEPS) { - logger.debug("Enforced sleep due to exceeding maximum number of steps (" + MAX_STEPS + ") per execution round"); + if (this.steps + opcodeSteps > maxSteps) { + logger.debug("Enforced sleep due to exceeding maximum number of steps (" + maxSteps + ") per execution round"); this.isSleeping = true; break; } @@ -717,7 +820,7 @@ public class MachineState { /** Return disassembly of code bytes */ public String disassemble() throws ExecutionException { - String output = ""; + StringBuilder output = new StringBuilder(); codeByteBuffer.position(0); @@ -730,13 +833,13 @@ public class MachineState { if (nextOpCode == null) throw new IllegalOperationException("OpCode 0x" + String.format("%02x", rawOpCode) + " not recognised"); - if (!output.isEmpty()) - output += "\n"; + if (output.length() != 0) + output.append("\n"); - output += "[PC: " + String.format("%04x", codeByteBuffer.position() - 1) + "] " + nextOpCode.disassemble(codeByteBuffer, dataByteBuffer); + output.append(String.format("[PC: %04x] %s", codeByteBuffer.position() - 1,nextOpCode.disassemble(codeByteBuffer, dataByteBuffer))); } - return output; + return output.toString(); } } diff --git a/Java/src/org/ciyam/at/OpCode.java b/Java/src/main/java/org/ciyam/at/OpCode.java similarity index 98% rename from Java/src/org/ciyam/at/OpCode.java rename to Java/src/main/java/org/ciyam/at/OpCode.java index 3fbfcbb..935201e 100644 --- a/Java/src/org/ciyam/at/OpCode.java +++ b/Java/src/main/java/org/ciyam/at/OpCode.java @@ -409,7 +409,7 @@ public enum OpCode { * @addr1 <<= $addr2 */ SHL_DAT(0x17, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) { - private static final long MAX_SHIFT = MachineState.VALUE_SIZE * 8; + private static final long MAX_SHIFT = MachineState.VALUE_SIZE * 8L; @Override public void executeWithParams(MachineState state, Object... args) throws ExecutionException { @@ -424,7 +424,7 @@ public enum OpCode { * Note: new MSB bit will be zero */ SHR_DAT(0x18, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) { - private static final long MAX_SHIFT = MachineState.VALUE_SIZE * 8; + private static final long MAX_SHIFT = MachineState.VALUE_SIZE * 8L; @Override public void executeWithParams(MachineState state, Object... args) throws ExecutionException { @@ -845,7 +845,7 @@ public enum OpCode { public final OpCodeParam[] params; // Create a map of opcode values to OpCode - private final static Map map = Arrays.stream(OpCode.values()).collect(Collectors.toMap(opcode -> opcode.value, opcode -> opcode)); + private static final Map map = Arrays.stream(OpCode.values()).collect(Collectors.toMap(opcode -> opcode.value, opcode -> opcode)); private OpCode(int value, OpCodeParam... params) { this.value = (byte) value; @@ -876,7 +876,7 @@ public enum OpCode { public abstract void executeWithParams(MachineState state, Object... args) throws ExecutionException; public void execute(MachineState state) throws ExecutionException { - List args = new ArrayList(); + List args = new ArrayList<>(); for (OpCodeParam param : this.params) args.add(param.fetch(state.codeByteBuffer, state.dataByteBuffer)); @@ -893,14 +893,16 @@ public enum OpCode { * @throws ExecutionException */ public String disassemble(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { - String output = this.name(); + StringBuilder output = new StringBuilder(this.name()); int postOpcodeProgramCounter = codeByteBuffer.position(); - for (OpCodeParam param : this.params) - output += " " + param.disassemble(codeByteBuffer, dataByteBuffer, postOpcodeProgramCounter); + for (OpCodeParam param : this.params) { + output.append(" "); + output.append(param.disassemble(codeByteBuffer, dataByteBuffer, postOpcodeProgramCounter)); + } - return output; + return output.toString(); } /** diff --git a/Java/src/org/ciyam/at/OpCodeParam.java b/Java/src/main/java/org/ciyam/at/OpCodeParam.java similarity index 84% rename from Java/src/org/ciyam/at/OpCodeParam.java rename to Java/src/main/java/org/ciyam/at/OpCodeParam.java index 00b0e75..88acc99 100644 --- a/Java/src/org/ciyam/at/OpCodeParam.java +++ b/Java/src/main/java/org/ciyam/at/OpCodeParam.java @@ -7,7 +7,7 @@ public enum OpCodeParam { VALUE { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { - return new Long(Utils.getCodeValue(codeByteBuffer)); + return Long.valueOf(Utils.getCodeValue(codeByteBuffer)); } @Override @@ -18,7 +18,7 @@ public enum OpCodeParam { DEST_ADDR { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { - return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer)); + return Integer.valueOf(Utils.getDataAddress(codeByteBuffer, dataByteBuffer)); } @Override @@ -29,7 +29,7 @@ public enum OpCodeParam { INDIRECT_DEST_ADDR { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { - return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer)); + return Integer.valueOf(Utils.getDataAddress(codeByteBuffer, dataByteBuffer)); } @Override @@ -40,7 +40,7 @@ public enum OpCodeParam { INDIRECT_DEST_ADDR_WITH_INDEX { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { - return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer)); + return Integer.valueOf(Utils.getDataAddress(codeByteBuffer, dataByteBuffer)); } @Override @@ -51,7 +51,7 @@ public enum OpCodeParam { SRC_ADDR { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { - return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer)); + return Integer.valueOf(Utils.getDataAddress(codeByteBuffer, dataByteBuffer)); } @Override @@ -62,7 +62,7 @@ public enum OpCodeParam { INDIRECT_SRC_ADDR { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { - return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer)); + return Integer.valueOf(Utils.getDataAddress(codeByteBuffer, dataByteBuffer)); } @Override @@ -73,7 +73,7 @@ public enum OpCodeParam { INDIRECT_SRC_ADDR_WITH_INDEX { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { - return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer)); + return Integer.valueOf(Utils.getDataAddress(codeByteBuffer, dataByteBuffer)); } @Override @@ -84,7 +84,7 @@ public enum OpCodeParam { INDEX { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { - return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer)); + return Integer.valueOf(Utils.getDataAddress(codeByteBuffer, dataByteBuffer)); } @Override @@ -95,7 +95,7 @@ public enum OpCodeParam { CODE_ADDR { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { - return new Integer(Utils.getCodeAddress(codeByteBuffer)); + return Integer.valueOf(Utils.getCodeAddress(codeByteBuffer)); } @Override @@ -106,7 +106,7 @@ public enum OpCodeParam { OFFSET { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { - return new Byte(Utils.getCodeOffset(codeByteBuffer)); + return Byte.valueOf(Utils.getCodeOffset(codeByteBuffer)); } @Override @@ -117,7 +117,7 @@ public enum OpCodeParam { FUNC { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { - return new Short(codeByteBuffer.getShort()); + return Short.valueOf(codeByteBuffer.getShort()); } @Override @@ -138,7 +138,7 @@ public enum OpCodeParam { BLOCK_HEIGHT { @Override public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException { - return new Integer(codeByteBuffer.getInt()); + return Integer.valueOf(codeByteBuffer.getInt()); } @Override diff --git a/Java/src/org/ciyam/at/StackBoundsException.java b/Java/src/main/java/org/ciyam/at/StackBoundsException.java similarity index 100% rename from Java/src/org/ciyam/at/StackBoundsException.java rename to Java/src/main/java/org/ciyam/at/StackBoundsException.java diff --git a/Java/src/main/java/org/ciyam/at/Timestamp.java b/Java/src/main/java/org/ciyam/at/Timestamp.java new file mode 100644 index 0000000..aaec030 --- /dev/null +++ b/Java/src/main/java/org/ciyam/at/Timestamp.java @@ -0,0 +1,109 @@ +package org.ciyam.at; + +/** + * CIYAM-AT "Timestamp" + *

+ * With CIYAM-ATs, "timestamp" does not mean a real timestamp but instead is an artificial timestamp that includes three parts: + *

+ *

    + *
  • block height (32 bits)
  • + *
  • blockchain ID (8 bits)
  • + *
  • intra-block transaction sequence (24 bits)
  • + *
+ * This allows up to 256 different blockchains and up to ~16million transactions per block. + *

+ * A blockchain ID of zero is assumed to be the 'native' blockchain. + *

+ * Timestamp values are not directly manipulated by AT OpCodes so endianness isn't important here. + * + * @see Timestamp#Timestamp(int, int, int) + * @see Timestamp#Timestamp(long) + * @see Timestamp#longValue() + * @see Timestamp#toLong(int, int, int) + * + */ +public class Timestamp { + + public static final int NATIVE_BLOCKCHAIN_ID = 0; + + public int blockHeight; + public int blockchainId; + public int transactionSequence; + + /** + * Constructs new CIYAM-AT "timestamp" using block height, blockchain ID and transaction sequence. + * + * @param blockHeight + * @param blockchainId + * @param transactionSequence + */ + public Timestamp(int blockHeight, int blockchainId, int transactionSequence) { + this.blockHeight = blockHeight; + this.blockchainId = blockchainId; + this.transactionSequence = transactionSequence; + } + + /** + * Constructs new CIYAM-AT "timestamp" using only block height and transaction sequence. + *

+ * Assumes native blockchain ID. + * + * @param blockHeight + * @param transactionSequence + */ + public Timestamp(int blockHeight, int transactionSequence) { + this(blockHeight, NATIVE_BLOCKCHAIN_ID, transactionSequence); + } + + /** + * Constructs new CIYAM-AT "timestamp" using long packed with block height, blockchain ID and transaction sequence. + * + * @param timestamp + */ + public Timestamp(long timestamp) { + this.blockHeight = (int) (timestamp >> 32); + this.blockchainId = (int) ((timestamp >> 24) & 0xffL); + this.transactionSequence = (int) (timestamp & 0x00ffffffL); + } + + /** + * Returns CIYAM-AT "timestamp" long representing block height, blockchain ID and transaction sequence. + * + * @return CIYAM-AT "timestamp" as long + */ + public long longValue() { + return Timestamp.toLong(this.blockHeight, this.blockchainId, this.transactionSequence); + } + + /** + * Returns CIYAM-AT "timestamp" long representing block height, blockchain ID and transaction sequence. + * + * @param blockHeight + * @param blockchainId + * @param transactionSequence + * @return CIYAM-AT "timestamp" as long + */ + public static long toLong(int blockHeight, int blockchainId, int transactionSequence) { + long longValue = ((long) blockHeight) << 32; + longValue |= ((long) blockchainId) << 24; + longValue |= transactionSequence; + return longValue; + } + + /** + * Returns CIYAM-AT "timestamp" long representing block height, blockchain ID and transaction sequence. + *

+ * Assumes native blockchain ID. + * + * @param blockHeight + * @param transactionSequence + * @return CIYAM-AT "timestamp" as long + */ + public static long toLong(int blockHeight, int transactionSequence) { + long longValue = ((long) blockHeight) << 32; + // NOP: longValue |= ((long) NATIVE_BLOCKCHAIN_ID) << 24; + longValue |= transactionSequence; + return longValue; + } + +} diff --git a/Java/src/org/ciyam/at/TwoValueComparator.java b/Java/src/main/java/org/ciyam/at/TwoValueComparator.java similarity index 100% rename from Java/src/org/ciyam/at/TwoValueComparator.java rename to Java/src/main/java/org/ciyam/at/TwoValueComparator.java diff --git a/Java/src/org/ciyam/at/TwoValueOperator.java b/Java/src/main/java/org/ciyam/at/TwoValueOperator.java similarity index 100% rename from Java/src/org/ciyam/at/TwoValueOperator.java rename to Java/src/main/java/org/ciyam/at/TwoValueOperator.java diff --git a/Java/src/org/ciyam/at/Utils.java b/Java/src/main/java/org/ciyam/at/Utils.java similarity index 100% rename from Java/src/org/ciyam/at/Utils.java rename to Java/src/main/java/org/ciyam/at/Utils.java diff --git a/Java/src/org/ciyam/at/Timestamp.java b/Java/src/org/ciyam/at/Timestamp.java deleted file mode 100644 index 55f6064..0000000 --- a/Java/src/org/ciyam/at/Timestamp.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.ciyam.at; - -/** - * CIYAM-AT "Timestamp" - *

- * With CIYAM-ATs, "timestamp" does not mean a real timestamp but instead is an artificial timestamp that includes two parts. The first part is a block height - * (32 bits) with the second part being the number of the transaction if applicable (also 32 bits and zero if not applicable). Timestamps can thus be - * represented as a 64 bit long. - *

- * - * @see Timestamp#Timestamp(int, int) - * @see Timestamp#Timestamp(long) - * @see Timestamp#longValue() - * @see Timestamp#toLong(int, int) - * - */ -public class Timestamp { - - public int blockHeight; - public int transactionSequence; - - /** - * Constructs new CIYAM-AT "timestamp" using block height and transaction sequence. - * - * @param blockHeight - * @param transactionSequence - */ - public Timestamp(int blockHeight, int transactionSequence) { - this.blockHeight = blockHeight; - this.transactionSequence = transactionSequence; - } - - /** - * Constructs new CIYAM-AT "timestamp" using long packed with block height and transaction sequence. - * - * @param timestamp - */ - public Timestamp(long timestamp) { - this.blockHeight = (int) (timestamp >> 32); - this.transactionSequence = (int) (timestamp & 0xffffff); - } - - /** - * Returns CIYAM-AT "timestamp" long representing block height and transaction sequence. - * - * @return CIYAM-AT "timestamp" as long - */ - public long longValue() { - return Timestamp.toLong(this.blockHeight, this.transactionSequence); - } - - /** - * Returns CIYAM-AT "timestamp" long representing block height and transaction sequence. - * - * @param blockHeight - * @param transactionSequence - * @return CIYAM-AT "timestamp" as long - */ - public static long toLong(int blockHeight, int transactionSequence) { - long longValue = blockHeight; - longValue <<= 32; - longValue |= transactionSequence; - return longValue; - } - -} diff --git a/Java/tests/BranchingOpCodeTests.java b/Java/src/test/java/BranchingOpCodeTests.java similarity index 100% rename from Java/tests/BranchingOpCodeTests.java rename to Java/src/test/java/BranchingOpCodeTests.java diff --git a/Java/tests/CallStackOpCodeTests.java b/Java/src/test/java/CallStackOpCodeTests.java similarity index 100% rename from Java/tests/CallStackOpCodeTests.java rename to Java/src/test/java/CallStackOpCodeTests.java diff --git a/Java/tests/DataOpCodeTests.java b/Java/src/test/java/DataOpCodeTests.java similarity index 100% rename from Java/tests/DataOpCodeTests.java rename to Java/src/test/java/DataOpCodeTests.java diff --git a/Java/tests/DisassemblyTests.java b/Java/src/test/java/DisassemblyTests.java similarity index 88% rename from Java/tests/DisassemblyTests.java rename to Java/src/test/java/DisassemblyTests.java index 01b0fc3..a7640a6 100644 --- a/Java/tests/DisassemblyTests.java +++ b/Java/src/test/java/DisassemblyTests.java @@ -62,8 +62,8 @@ public class DisassemblyTests { codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.ECHO.value).putInt(1); codeByteBuffer.put(OpCode.FIN_IMD.value); - // version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4 - byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000"); + // version 0002, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4, minActivation = 0 + byte[] headerBytes = hexToBytes("0200" + "0000" + "0002" + "2000" + "1000" + "1000" + "0000000000000000"); byte[] codeBytes = codeByteBuffer.array(); byte[] dataBytes = new byte[0]; @@ -81,8 +81,8 @@ public class DisassemblyTests { codeByteBuffer.put(hexToBytes("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")); codeByteBuffer.put(hexToBytes("000000000000")); - // version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4 - byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000"); + // version 0002, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4, minActivation = 0 + byte[] headerBytes = hexToBytes("0200" + "0000" + "0002" + "2000" + "1000" + "1000" + "0000000000000000"); byte[] codeBytes = codeByteBuffer.array(); byte[] dataBytes = new byte[0]; diff --git a/Java/tests/FunctionCodeTests.java b/Java/src/test/java/FunctionCodeTests.java similarity index 100% rename from Java/tests/FunctionCodeTests.java rename to Java/src/test/java/FunctionCodeTests.java diff --git a/Java/src/test/java/MiscTests.java b/Java/src/test/java/MiscTests.java new file mode 100644 index 0000000..69ed62d --- /dev/null +++ b/Java/src/test/java/MiscTests.java @@ -0,0 +1,68 @@ +import static common.TestUtils.hexToBytes; +import static org.junit.Assert.*; + +import org.ciyam.at.ExecutionException; +import org.ciyam.at.FunctionCode; +import org.ciyam.at.MachineState; +import org.ciyam.at.OpCode; +import org.junit.Test; + +import common.ExecutableTest; + +public class MiscTests extends ExecutableTest { + + @Test + public void testSimpleCode() throws ExecutionException { + long testValue = 8888L; + codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(testValue); + codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.ECHO.value).putInt(0); + codeByteBuffer.put(OpCode.FIN_IMD.value); + + execute(true); + + assertTrue(state.getIsFinished()); + assertFalse(state.getHadFatalError()); + assertEquals("Data does not match", testValue, getData(0)); + } + + @Test + public void testInvalidOpCode() throws ExecutionException { + codeByteBuffer.put((byte) 0xdd); + + execute(true); + + assertTrue(state.getIsFinished()); + assertTrue(state.getHadFatalError()); + } + + @Test + public void testFreeze() throws ExecutionException { + // Infinite loop + codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(0); + + // If starting balance is 1234 then should take about 3 rounds as 500 steps max each round. + for (int i = 0; i < 3; ++i) + execute(true); + + assertTrue(state.getIsFrozen()); + + Long frozenBalance = state.getFrozenBalance(); + assertNotNull(frozenBalance); + } + + @Test + public void testMinActivation() throws ExecutionException { + long minActivation = 12345L; // 0x0000000000003039 + + // version 0002, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4, minActivation = 12345L + byte[] headerBytes = hexToBytes("0200" + "0000" + "0002" + "2000" + "1000" + "1000" + "3930000000000000"); + byte[] codeBytes = codeByteBuffer.array(); + byte[] dataBytes = new byte[0]; + + state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes); + + assertTrue(state.getIsFrozen()); + assertEquals((Long) (minActivation - 1L), state.getFrozenBalance()); + } + +} diff --git a/Java/tests/OpCodeTests.java b/Java/src/test/java/OpCodeTests.java similarity index 100% rename from Java/tests/OpCodeTests.java rename to Java/src/test/java/OpCodeTests.java diff --git a/Java/tests/SerializationTests.java b/Java/src/test/java/SerializationTests.java similarity index 81% rename from Java/tests/SerializationTests.java rename to Java/src/test/java/SerializationTests.java index 7f08d6a..8b620b1 100644 --- a/Java/tests/SerializationTests.java +++ b/Java/src/test/java/SerializationTests.java @@ -27,7 +27,7 @@ public class SerializationTests { public void beforeTest() { logger = new TestLogger(); api = new TestAPI(); - codeByteBuffer = ByteBuffer.allocate(256).order(ByteOrder.LITTLE_ENDIAN); + codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN); } @After @@ -38,8 +38,8 @@ public class SerializationTests { } private byte[] simulate() { - // version 0003, reserved 0000, code 0100 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4 - byte[] headerBytes = hexToBytes("0300" + "0000" + "0001" + "2000" + "1000" + "1000"); + // version 0002, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4, minActivation = 0 + byte[] headerBytes = hexToBytes("0200" + "0000" + "0002" + "2000" + "1000" + "1000" + "0000000000000000"); byte[] codeBytes = codeByteBuffer.array(); byte[] dataBytes = new byte[0]; @@ -49,7 +49,8 @@ public class SerializationTests { } private byte[] continueSimulation(byte[] savedState) { - state = MachineState.fromBytes(api, logger, savedState); + byte[] codeBytes = codeByteBuffer.array(); + state = MachineState.fromBytes(api, logger, savedState, codeBytes); // Pretend we're on next block api.bumpCurrentBlockHeight(); @@ -61,10 +62,13 @@ public class SerializationTests { state.execute(); byte[] stateBytes = state.toBytes(); - MachineState restoredState = MachineState.fromBytes(api, logger, stateBytes); + byte[] codeBytes = state.getCodeBytes(); + MachineState restoredState = MachineState.fromBytes(api, logger, stateBytes, codeBytes); byte[] restoredStateBytes = restoredState.toBytes(); + byte[] restoredCodeBytes = state.getCodeBytes(); assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(stateBytes, restoredStateBytes)); + assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(codeBytes, restoredCodeBytes)); return stateBytes; } diff --git a/Java/tests/TestACCT.java b/Java/src/test/java/TestACCT.java similarity index 93% rename from Java/tests/TestACCT.java rename to Java/src/test/java/TestACCT.java index 12ca354..3bfe5bc 100644 --- a/Java/tests/TestACCT.java +++ b/Java/src/test/java/TestACCT.java @@ -52,8 +52,8 @@ public class TestACCT { } private byte[] simulate() { - // version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4 - byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000"); + // version 0002, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4, minActivation = 0 + byte[] headerBytes = hexToBytes("0200" + "0000" + "0002" + "2000" + "1000" + "1000" + "0000000000000000"); byte[] codeBytes = codeByteBuffer.array(); byte[] dataBytes = dataByteBuffer.array(); @@ -63,7 +63,8 @@ public class TestACCT { } private byte[] continueSimulation(byte[] savedState) { - state = MachineState.fromBytes(api, logger, savedState); + byte[] codeBytes = codeByteBuffer.array(); + state = MachineState.fromBytes(api, logger, savedState, codeBytes); return executeAndCheck(state); } @@ -74,10 +75,13 @@ public class TestACCT { api.setCurrentBalance(state.getCurrentBalance()); byte[] stateBytes = state.toBytes(); - MachineState restoredState = MachineState.fromBytes(api, logger, stateBytes); + byte[] codeBytes = state.getCodeBytes(); + MachineState restoredState = MachineState.fromBytes(api, logger, stateBytes, codeBytes); byte[] restoredStateBytes = restoredState.toBytes(); + byte[] restoredCodeBytes = state.getCodeBytes(); assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(stateBytes, restoredStateBytes)); + assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(codeBytes, restoredCodeBytes)); return stateBytes; } diff --git a/Java/tests/ToolchainTests.java b/Java/src/test/java/ToolchainTests.java similarity index 100% rename from Java/tests/ToolchainTests.java rename to Java/src/test/java/ToolchainTests.java diff --git a/Java/tests/UserStackOpCodeTests.java b/Java/src/test/java/UserStackOpCodeTests.java similarity index 100% rename from Java/tests/UserStackOpCodeTests.java rename to Java/src/test/java/UserStackOpCodeTests.java diff --git a/Java/tests/common/ACCTAPI.java b/Java/src/test/java/common/ACCTAPI.java similarity index 96% rename from Java/tests/common/ACCTAPI.java rename to Java/src/test/java/common/ACCTAPI.java index 7856b33..de03e2b 100644 --- a/Java/tests/common/ACCTAPI.java +++ b/Java/src/test/java/common/ACCTAPI.java @@ -150,6 +150,11 @@ public class ACCTAPI extends API { return accounts.get(accountIndex).address; } + @Override + public int getMaxStepsPerRound() { + return 500; + } + @Override public int getOpCodeSteps(OpCode opcode) { return 1; @@ -331,12 +336,13 @@ public class ACCTAPI extends API { } @Override - public void platformSpecificPreExecuteCheck(short functionCodeValue, int paramCount, boolean returnValueExpected) throws IllegalFunctionCodeException { + public void platformSpecificPreExecuteCheck(int paramCount, boolean returnValueExpected, MachineState state, short rawFunctionCode) + throws IllegalFunctionCodeException { // NOT USED } @Override - public void platformSpecificPostCheckExecute(short functionCodeValue, FunctionData functionData, MachineState state) throws ExecutionException { + public void platformSpecificPostCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { // NOT USED } diff --git a/Java/tests/common/ExecutableTest.java b/Java/src/test/java/common/ExecutableTest.java similarity index 87% rename from Java/tests/common/ExecutableTest.java rename to Java/src/test/java/common/ExecutableTest.java index 6a2144c..866b83f 100644 --- a/Java/tests/common/ExecutableTest.java +++ b/Java/src/test/java/common/ExecutableTest.java @@ -14,8 +14,7 @@ import org.junit.BeforeClass; public abstract class ExecutableTest { - public static final int CODE_OFFSET = 6 * 2; - public static final int DATA_OFFSET = CODE_OFFSET + 0x0200; + public static final int DATA_OFFSET = 6 * 2 + 8; public static final int CALL_STACK_OFFSET = DATA_OFFSET + 0x0020 * 8; public TestLogger logger; @@ -49,8 +48,8 @@ public abstract class ExecutableTest { } protected void execute(boolean onceOnly) { - // version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 8 - byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000"); + // version 0002, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4, minActivation = 0 + byte[] headerBytes = hexToBytes("0200" + "0000" + "0002" + "2000" + "1000" + "1000" + "0000000000000000"); byte[] codeBytes = codeByteBuffer.array(); byte[] dataBytes = new byte[0]; @@ -82,6 +81,10 @@ public abstract class ExecutableTest { System.out.println("Frozen: " + state.getIsFrozen()); + long newBalance = state.getCurrentBalance(); + System.out.println("New balance: " + newBalance); + api.setCurrentBalance(newBalance); + // Bump block height api.bumpCurrentBlockHeight(); } while (!onceOnly && !state.getIsFinished()); @@ -90,7 +93,7 @@ public abstract class ExecutableTest { byte[] stateBytes = state.toBytes(); // We know how the state will be serialized so we can extract values - // header(6) + code(0x0200) + data(0x0020 * 8) + callStack length(4) + callStack + userStack length(4) + userStack + // header(6) + data(0x0020 * 8) + callStack length(4) + callStack + userStack length(4) + userStack stateByteBuffer = ByteBuffer.wrap(stateBytes).order(ByteOrder.LITTLE_ENDIAN); callStackSize = stateByteBuffer.getInt(CALL_STACK_OFFSET); diff --git a/Java/tests/common/TestAPI.java b/Java/src/test/java/common/TestAPI.java similarity index 83% rename from Java/tests/common/TestAPI.java rename to Java/src/test/java/common/TestAPI.java index 89011df..155e49b 100644 --- a/Java/tests/common/TestAPI.java +++ b/Java/src/test/java/common/TestAPI.java @@ -13,15 +13,22 @@ public class TestAPI extends API { private static final int BLOCK_PERIOD = 10 * 60; // average period between blocks in seconds private int currentBlockHeight; + private long currentBalance; public TestAPI() { this.currentBlockHeight = 10; + this.currentBalance = 1234L; } public void bumpCurrentBlockHeight() { ++this.currentBlockHeight; } + @Override + public int getMaxStepsPerRound() { + return 500; + } + @Override public int getOpCodeSteps(OpCode opcode) { if (opcode.value >= OpCode.EXT_FUN.value && opcode.value <= OpCode.EXT_FUN_RET_DAT_2.value) @@ -79,7 +86,7 @@ public class TestAPI extends API { @Override public long generateRandomUsingTransactionInA(MachineState state) { - if (isFirstOpCodeAfterSleeping(state)) { + if (!isFirstOpCodeAfterSleeping(state)) { // First call System.out.println("generateRandomUsingTransactionInA: first call - sleeping"); @@ -125,7 +132,12 @@ public class TestAPI extends API { @Override public long getCurrentBalance(MachineState state) { - return 12345L; + return this.currentBalance; + } + + // Debugging only + public void setCurrentBalance(long currentBalance) { + this.currentBalance = currentBalance; } @Override @@ -154,11 +166,12 @@ public class TestAPI extends API { } @Override - public void platformSpecificPreExecuteCheck(short functionCodeValue, int paramCount, boolean returnValueExpected) throws IllegalFunctionCodeException { + public void platformSpecificPreExecuteCheck(int paramCount, boolean returnValueExpected, MachineState state, short rawFunctionCode) + throws IllegalFunctionCodeException { Integer requiredParamCount; Boolean returnsValue; - switch (functionCodeValue) { + switch (rawFunctionCode) { case 0x0501: // take one arg, no return value requiredParamCount = 1; @@ -173,7 +186,7 @@ public class TestAPI extends API { default: // Unrecognised platform-specific function code - throw new IllegalFunctionCodeException("Unrecognised platform-specific function code 0x" + String.format("%04x", functionCodeValue)); + throw new IllegalFunctionCodeException("Unrecognised platform-specific function code 0x" + String.format("%04x", rawFunctionCode)); } if (requiredParamCount == null || returnsValue == null) @@ -181,16 +194,16 @@ public class TestAPI extends API { if (paramCount != requiredParamCount) throw new IllegalFunctionCodeException("Passed paramCount (" + paramCount + ") does not match platform-specific function code 0x" - + String.format("%04x", functionCodeValue) + " required paramCount (" + requiredParamCount + ")"); + + String.format("%04x", rawFunctionCode) + " required paramCount (" + requiredParamCount + ")"); if (returnValueExpected != returnsValue) throw new IllegalFunctionCodeException("Passed returnValueExpected (" + returnValueExpected + ") does not match platform-specific function code 0x" - + String.format("%04x", functionCodeValue) + " return signature (" + returnsValue + ")"); + + String.format("%04x", rawFunctionCode) + " return signature (" + returnsValue + ")"); } @Override - public void platformSpecificPostCheckExecute(short functionCodeValue, FunctionData functionData, MachineState state) throws ExecutionException { - switch (functionCodeValue) { + public void platformSpecificPostCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException { + switch (rawFunctionCode) { case 0x0501: System.out.println("Platform-specific function 0x0501 called with 0x" + String.format("%016x", functionData.value1)); break; @@ -202,7 +215,7 @@ public class TestAPI extends API { default: // Unrecognised platform-specific function code - throw new IllegalFunctionCodeException("Unrecognised platform-specific function code 0x" + String.format("%04x", functionCodeValue)); + throw new IllegalFunctionCodeException("Unrecognised platform-specific function code 0x" + String.format("%04x", rawFunctionCode)); } } diff --git a/Java/tests/common/TestLogger.java b/Java/src/test/java/common/TestLogger.java similarity index 100% rename from Java/tests/common/TestLogger.java rename to Java/src/test/java/common/TestLogger.java diff --git a/Java/tests/common/TestUtils.java b/Java/src/test/java/common/TestUtils.java similarity index 100% rename from Java/tests/common/TestUtils.java rename to Java/src/test/java/common/TestUtils.java diff --git a/Java/tests/MiscTests.java b/Java/tests/MiscTests.java deleted file mode 100644 index 48241ae..0000000 --- a/Java/tests/MiscTests.java +++ /dev/null @@ -1,36 +0,0 @@ -import static org.junit.Assert.*; - -import org.ciyam.at.ExecutionException; -import org.ciyam.at.FunctionCode; -import org.ciyam.at.OpCode; -import org.junit.Test; - -import common.ExecutableTest; - -public class MiscTests extends ExecutableTest { - - @Test - public void testSimpleCode() throws ExecutionException { - long testValue = 8888L; - codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(testValue); - codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.ECHO.value).putInt(0); - codeByteBuffer.put(OpCode.FIN_IMD.value); - - execute(true); - - assertTrue(state.getIsFinished()); - assertFalse(state.getHadFatalError()); - assertEquals("Data does not match", testValue, getData(0)); - } - - @Test - public void testInvalidOpCode() throws ExecutionException { - codeByteBuffer.put((byte) 0xdd); - - execute(true); - - assertTrue(state.getIsFinished()); - assertTrue(state.getHadFatalError()); - } - -}