Browse Source

Updated for Java 11 & other improvements

API:
Added sample AT-emitted transaction types (payment/message).
Maximum number of steps per execution round no longer hard-coded.
API.putTransactionAfterTimestampInA() sets A to zero if no more transactions.
API.putMessageFromTransactionInAIntoB sets B to zero if not a message transaction.
Added some convenience methods.

MachineState:
Added support for minimum activation amount.
Added static method for packing AT into "creation bytes".
No need to store unchanging code in per-height AT state data.

Added support for multiple blockchains to "Timestamp".

General improvements based on Sonarlint suggestions.
General improvements to comments.
Replaced deprecated Byte/Short/Integer/Long constructor call with corresponding .valueOf() call.
Replaced some string concatenations with StringBuilder.

Moved Java-related .gitignore from root to /Java/

Removed .classpath and .project, and added same to .gitignore

Added info on how to add CIYAM AT JAR to other projects.

Updated pom.xml:
Bumped version to 1.2
Bumped Java version from 1.8 to 11
Bumped BouncyCastle from 1.60 to 1.64

Added more tests.
master
catbref 5 years ago
parent
commit
00fd8b040d
  1. 3
      .gitignore
  2. 27
      Java/.classpath
  3. 4
      Java/.gitignore
  4. 23
      Java/.project
  5. 11
      Java/maven-import.txt
  6. 68
      Java/pom.xml
  7. 115
      Java/src/main/java/org/ciyam/at/API.java
  8. 0
      Java/src/main/java/org/ciyam/at/CodeSegmentException.java
  9. 0
      Java/src/main/java/org/ciyam/at/ExecutionException.java
  10. 15
      Java/src/main/java/org/ciyam/at/FunctionCode.java
  11. 0
      Java/src/main/java/org/ciyam/at/FunctionData.java
  12. 0
      Java/src/main/java/org/ciyam/at/IllegalFunctionCodeException.java
  13. 0
      Java/src/main/java/org/ciyam/at/IllegalOperationException.java
  14. 0
      Java/src/main/java/org/ciyam/at/InvalidAddressException.java
  15. 0
      Java/src/main/java/org/ciyam/at/LoggerInterface.java
  16. 153
      Java/src/main/java/org/ciyam/at/MachineState.java
  17. 18
      Java/src/main/java/org/ciyam/at/OpCode.java
  18. 24
      Java/src/main/java/org/ciyam/at/OpCodeParam.java
  19. 0
      Java/src/main/java/org/ciyam/at/StackBoundsException.java
  20. 109
      Java/src/main/java/org/ciyam/at/Timestamp.java
  21. 0
      Java/src/main/java/org/ciyam/at/TwoValueComparator.java
  22. 0
      Java/src/main/java/org/ciyam/at/TwoValueOperator.java
  23. 0
      Java/src/main/java/org/ciyam/at/Utils.java
  24. 66
      Java/src/org/ciyam/at/Timestamp.java
  25. 0
      Java/src/test/java/BranchingOpCodeTests.java
  26. 0
      Java/src/test/java/CallStackOpCodeTests.java
  27. 0
      Java/src/test/java/DataOpCodeTests.java
  28. 8
      Java/src/test/java/DisassemblyTests.java
  29. 0
      Java/src/test/java/FunctionCodeTests.java
  30. 68
      Java/src/test/java/MiscTests.java
  31. 0
      Java/src/test/java/OpCodeTests.java
  32. 14
      Java/src/test/java/SerializationTests.java
  33. 12
      Java/src/test/java/TestACCT.java
  34. 0
      Java/src/test/java/ToolchainTests.java
  35. 0
      Java/src/test/java/UserStackOpCodeTests.java
  36. 10
      Java/src/test/java/common/ACCTAPI.java
  37. 13
      Java/src/test/java/common/ExecutableTest.java
  38. 33
      Java/src/test/java/common/TestAPI.java
  39. 0
      Java/src/test/java/common/TestLogger.java
  40. 0
      Java/src/test/java/common/TestUtils.java
  41. 36
      Java/tests/MiscTests.java

3
.gitignore vendored

@ -1,3 +0,0 @@
/Java/bin/
/Java/target/
/Java/.settings/

27
Java/.classpath

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="tests">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

4
Java/.gitignore vendored

@ -0,0 +1,4 @@
target/
.settings*
.classpath
.project

23
Java/.project

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>CIYAM-AT-Java</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

11
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}

68
Java/pom.xml

@ -1,28 +1,42 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>CIYAM-AT-Java</groupId>
<artifactId>CIYAM-AT-Java</artifactId>
<version>1.0</version>
<build>
<sourceDirectory>src</sourceDirectory>
<testSourceDirectory>tests</testSourceDirectory>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.60</version>
<scope>test</scope>
</dependency>
</dependencies>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.ciyam</groupId>
<artifactId>AT</artifactId>
<version>1.2</version>
<packaging>jar</packaging>
<properties>
<skipTests>true</skipTests>
<bouncycastle.version>1.64</bouncycastle.version>
</properties>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<release>11</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M4</version>
<configuration>
<skipTests>${skipTests}</skipTests>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>${bouncycastle.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

115
Java/src/org/ciyam/at/API.java → 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.
* <p>
@ -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<Long, ATTransactionType> 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();
}
}

0
Java/src/org/ciyam/at/CodeSegmentException.java → Java/src/main/java/org/ciyam/at/CodeSegmentException.java

0
Java/src/org/ciyam/at/ExecutionException.java → Java/src/main/java/org/ciyam/at/ExecutionException.java

15
Java/src/org/ciyam/at/FunctionCode.java → 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);
}
};

0
Java/src/org/ciyam/at/FunctionData.java → Java/src/main/java/org/ciyam/at/FunctionData.java

0
Java/src/org/ciyam/at/IllegalFunctionCodeException.java → Java/src/main/java/org/ciyam/at/IllegalFunctionCodeException.java

0
Java/src/org/ciyam/at/IllegalOperationException.java → Java/src/main/java/org/ciyam/at/IllegalOperationException.java

0
Java/src/org/ciyam/at/InvalidAddressException.java → Java/src/main/java/org/ciyam/at/InvalidAddressException.java

0
Java/src/org/ciyam/at/LoggerInterface.java → Java/src/main/java/org/ciyam/at/LoggerInterface.java

153
Java/src/org/ciyam/at/MachineState.java → 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<Short, VersionedConstants> VERSIONED_CONSTANTS = new HashMap<Short, VersionedConstants>();
private static final Map<Short, VersionedConstants> 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.
* <p>
* 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();
}
}

18
Java/src/org/ciyam/at/OpCode.java → Java/src/main/java/org/ciyam/at/OpCode.java

@ -409,7 +409,7 @@ public enum OpCode {
* <tt>@addr1 <<= $addr2</tt>
*/
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<Byte, OpCode> map = Arrays.stream(OpCode.values()).collect(Collectors.toMap(opcode -> opcode.value, opcode -> opcode));
private static final Map<Byte, OpCode> 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<Object> args = new ArrayList<Object>();
List<Object> 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();
}
/**

24
Java/src/org/ciyam/at/OpCodeParam.java → 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

0
Java/src/org/ciyam/at/StackBoundsException.java → Java/src/main/java/org/ciyam/at/StackBoundsException.java

109
Java/src/main/java/org/ciyam/at/Timestamp.java

@ -0,0 +1,109 @@
package org.ciyam.at;
/**
* CIYAM-AT "Timestamp"
* <p>
* With CIYAM-ATs, "timestamp" does not mean a real timestamp but instead is an artificial timestamp that includes three parts:
* <p>
* <ul>
* <li>block height (32 bits)</li>
* <li>blockchain ID (8 bits)</li>
* <li>intra-block transaction sequence (24 bits)</li>
* </ul>
* This allows up to 256 different blockchains and up to ~16million transactions per block.
* <p>
* A blockchain ID of zero is assumed to be the 'native' blockchain.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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;
}
}

0
Java/src/org/ciyam/at/TwoValueComparator.java → Java/src/main/java/org/ciyam/at/TwoValueComparator.java

0
Java/src/org/ciyam/at/TwoValueOperator.java → Java/src/main/java/org/ciyam/at/TwoValueOperator.java

0
Java/src/org/ciyam/at/Utils.java → Java/src/main/java/org/ciyam/at/Utils.java

66
Java/src/org/ciyam/at/Timestamp.java

@ -1,66 +0,0 @@
package org.ciyam.at;
/**
* CIYAM-AT "Timestamp"
* <p>
* 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.
* <p>
*
* @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;
}
}

0
Java/tests/BranchingOpCodeTests.java → Java/src/test/java/BranchingOpCodeTests.java

0
Java/tests/CallStackOpCodeTests.java → Java/src/test/java/CallStackOpCodeTests.java

0
Java/tests/DataOpCodeTests.java → Java/src/test/java/DataOpCodeTests.java

8
Java/tests/DisassemblyTests.java → 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];

0
Java/tests/FunctionCodeTests.java → Java/src/test/java/FunctionCodeTests.java

68
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());
}
}

0
Java/tests/OpCodeTests.java → Java/src/test/java/OpCodeTests.java

14
Java/tests/SerializationTests.java → 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;
}

12
Java/tests/TestACCT.java → 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;
}

0
Java/tests/ToolchainTests.java → Java/src/test/java/ToolchainTests.java

0
Java/tests/UserStackOpCodeTests.java → Java/src/test/java/UserStackOpCodeTests.java

10
Java/tests/common/ACCTAPI.java → 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
}

13
Java/tests/common/ExecutableTest.java → 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);

33
Java/tests/common/TestAPI.java → 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));
}
}

0
Java/tests/common/TestLogger.java → Java/src/test/java/common/TestLogger.java

0
Java/tests/common/TestUtils.java → Java/src/test/java/common/TestUtils.java

36
Java/tests/MiscTests.java

@ -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());
}
}
Loading…
Cancel
Save