mirror of
https://github.com/Qortal/AT.git
synced 2025-01-30 19:02:14 +00:00
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.
This commit is contained in:
parent
297ccbdaf6
commit
00fd8b040d
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +0,0 @@
|
|||||||
/Java/bin/
|
|
||||||
/Java/target/
|
|
||||||
/Java/.settings/
|
|
@ -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
Normal file
4
Java/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
target/
|
||||||
|
.settings*
|
||||||
|
.classpath
|
||||||
|
.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
Normal file
11
Java/maven-import.txt
Normal file
@ -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
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">
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
<modelVersion>4.0.0</modelVersion>
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
<groupId>CIYAM-AT-Java</groupId>
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<artifactId>CIYAM-AT-Java</artifactId>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<version>1.0</version>
|
<groupId>org.ciyam</groupId>
|
||||||
<build>
|
<artifactId>AT</artifactId>
|
||||||
<sourceDirectory>src</sourceDirectory>
|
<version>1.2</version>
|
||||||
<testSourceDirectory>tests</testSourceDirectory>
|
<packaging>jar</packaging>
|
||||||
<plugins>
|
<properties>
|
||||||
<plugin>
|
<skipTests>true</skipTests>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<bouncycastle.version>1.64</bouncycastle.version>
|
||||||
<version>3.5.1</version>
|
</properties>
|
||||||
<configuration>
|
<build>
|
||||||
<source>1.8</source>
|
<sourceDirectory>src/main/java</sourceDirectory>
|
||||||
<target>1.8</target>
|
<testSourceDirectory>src/test/java</testSourceDirectory>
|
||||||
</configuration>
|
<plugins>
|
||||||
</plugin>
|
<plugin>
|
||||||
</plugins>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
</build>
|
<version>3.8.0</version>
|
||||||
<dependencies>
|
<configuration>
|
||||||
<dependency>
|
<release>11</release>
|
||||||
<groupId>org.bouncycastle</groupId>
|
</configuration>
|
||||||
<artifactId>bcprov-jdk15on</artifactId>
|
</plugin>
|
||||||
<version>1.60</version>
|
<plugin>
|
||||||
<scope>test</scope>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
</dependency>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
</dependencies>
|
<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>
|
</project>
|
@ -1,5 +1,12 @@
|
|||||||
package org.ciyam.at;
|
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.
|
* API for CIYAM AT "Function Codes" for blockchain-specific interactions.
|
||||||
* <p>
|
* <p>
|
||||||
@ -12,6 +19,27 @@ package org.ciyam.at;
|
|||||||
*/
|
*/
|
||||||
public abstract class API {
|
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" */
|
/** Returns fee for executing opcode in terms of execution "steps" */
|
||||||
public abstract int getOpCodeSteps(OpCode opcode);
|
public abstract int getOpCodeSteps(OpCode opcode);
|
||||||
|
|
||||||
@ -32,7 +60,7 @@ public abstract class API {
|
|||||||
/** Put previous block's signature hash in A */
|
/** Put previous block's signature hash in A */
|
||||||
public abstract void putPreviousBlockHashInA(MachineState state);
|
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);
|
public abstract void putTransactionAfterTimestampInA(Timestamp timestamp, MachineState state);
|
||||||
|
|
||||||
/** Return type from transaction in A, or 0xffffffffffffffff if A not valid transaction */
|
/** 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);
|
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);
|
public abstract void putMessageFromTransactionInAIntoB(MachineState state);
|
||||||
|
|
||||||
/** Put sender/creator address from transaction in A into B */
|
/** 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);
|
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);
|
public abstract void onFinished(long amount, MachineState state);
|
||||||
|
|
||||||
/** AT has encountered fatal error */
|
/** AT has encountered fatal error */
|
||||||
public abstract void onFatalError(MachineState state, ExecutionException e);
|
public abstract void onFatalError(MachineState state, ExecutionException e);
|
||||||
|
|
||||||
/** Pre-execute checking of param requirements for platform-specific functions */
|
/** 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;
|
throws IllegalFunctionCodeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -96,7 +130,7 @@ public abstract class API {
|
|||||||
*
|
*
|
||||||
* @throws ExecutionException
|
* @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 */
|
/** Convenience method to allow subclasses to access package-scoped MachineState.setIsSleeping */
|
||||||
protected void setIsSleeping(MachineState state, boolean isSleeping) {
|
protected void setIsSleeping(MachineState state, boolean isSleeping) {
|
||||||
@ -108,37 +142,90 @@ public abstract class API {
|
|||||||
return state.isFirstOpCodeAfterSleeping();
|
return state.isFirstOpCodeAfterSleeping();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Convenience methods to allow subclasses to access package-scoped a1-a4, b1-b4 variables */
|
/** Convenience method to allow subclasses to access MachineState.rewindCodePosition */
|
||||||
protected void setA1(MachineState state, long value) {
|
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;
|
state.a1 = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setA2(MachineState state, long value) {
|
public void setA2(MachineState state, long value) {
|
||||||
state.a2 = value;
|
state.a2 = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setA3(MachineState state, long value) {
|
public void setA3(MachineState state, long value) {
|
||||||
state.a3 = value;
|
state.a3 = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setA4(MachineState state, long value) {
|
public void setA4(MachineState state, long value) {
|
||||||
state.a4 = 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;
|
state.b1 = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setB2(MachineState state, long value) {
|
public void setB2(MachineState state, long value) {
|
||||||
state.b2 = value;
|
state.b2 = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setB3(MachineState state, long value) {
|
public void setB3(MachineState state, long value) {
|
||||||
state.b3 = value;
|
state.b3 = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setB4(MachineState state, long value) {
|
public void setB4(MachineState state, long value) {
|
||||||
state.b4 = 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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -474,8 +474,8 @@ public enum FunctionCode {
|
|||||||
|
|
||||||
state.b1 = digestByteBuffer.getLong();
|
state.b1 = digestByteBuffer.getLong();
|
||||||
state.b2 = digestByteBuffer.getLong();
|
state.b2 = digestByteBuffer.getLong();
|
||||||
state.b3 = 0L; // XXX Or do we leave B3 untouched?
|
state.b3 = 0L;
|
||||||
state.b4 = 0L; // XXX Or do we leave B4 untouched?
|
state.b4 = 0L;
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
throw new ExecutionException("No MD5 message digest service available", e);
|
throw new ExecutionException("No MD5 message digest service available", e);
|
||||||
}
|
}
|
||||||
@ -544,7 +544,7 @@ public enum FunctionCode {
|
|||||||
state.b1 = digestByteBuffer.getLong();
|
state.b1 = digestByteBuffer.getLong();
|
||||||
state.b2 = digestByteBuffer.getLong();
|
state.b2 = digestByteBuffer.getLong();
|
||||||
state.b3 = (long) digestByteBuffer.getInt() & 0xffffffffL;
|
state.b3 = (long) digestByteBuffer.getInt() & 0xffffffffL;
|
||||||
state.b4 = 0L; // XXX Or do we leave B4 untouched?
|
state.b4 = 0L;
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
throw new ExecutionException("No RIPEMD160 message digest service available", 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.b1);
|
||||||
digestByteBuffer.putLong(state.b2);
|
digestByteBuffer.putLong(state.b2);
|
||||||
digestByteBuffer.putInt((int) (state.b3 & 0xffffffffL));
|
digestByteBuffer.putInt((int) (state.b3 & 0xffffffffL));
|
||||||
// XXX: b4 ignored
|
// NOTE: b4 ignored
|
||||||
|
|
||||||
byte[] expectedDigest = digestByteBuffer.array();
|
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 API set isSleeping then rewind program counter (actually codeByteBuffer) ready for being awoken
|
||||||
if (state.getIsSleeping()) {
|
if (state.getIsSleeping()) {
|
||||||
// EXT_FUN_RET(1) + our function code(2) + address(4)
|
// 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.rewindCodePosition(MachineState.OPCODE_SIZE + MachineState.FUNCTIONCODE_SIZE + MachineState.ADDRESS_SIZE);
|
||||||
state.codeByteBuffer.position(newPosition);
|
|
||||||
|
|
||||||
// If specific sleep height not set, default to next block
|
// If specific sleep height not set, default to next block
|
||||||
if (state.getSleepUntilHeight() == null)
|
if (state.getSleepUntilHeight() == null)
|
||||||
@ -910,12 +909,12 @@ public enum FunctionCode {
|
|||||||
API_PASSTHROUGH(0x0500, 0, false) {
|
API_PASSTHROUGH(0x0500, 0, false) {
|
||||||
@Override
|
@Override
|
||||||
public void preExecuteCheck(int paramCount, boolean returnValueExpected, MachineState state, short rawFunctionCode) throws ExecutionException {
|
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
|
@Override
|
||||||
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
|
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
|
||||||
state.getAPI().platformSpecificPostCheckExecute(rawFunctionCode, functionData, state);
|
state.getAPI().platformSpecificPostCheckExecute(functionData, state, rawFunctionCode);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -11,7 +11,8 @@ import java.util.Map;
|
|||||||
public class MachineState {
|
public class MachineState {
|
||||||
|
|
||||||
/** Header bytes length */
|
/** 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) */
|
/** Size of one OpCode - typically 1 byte (byte) */
|
||||||
public static final int OPCODE_SIZE = 1;
|
public static final int OPCODE_SIZE = 1;
|
||||||
@ -28,9 +29,6 @@ public class MachineState {
|
|||||||
/** Maximum value for an address in the code segment */
|
/** Maximum value for an address in the code segment */
|
||||||
public static final int MAX_CODE_ADDRESS = 0x0000ffff;
|
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 {
|
private static class VersionedConstants {
|
||||||
/** Bytes per code page */
|
/** Bytes per code page */
|
||||||
public final int CODE_PAGE_SIZE;
|
public final int CODE_PAGE_SIZE;
|
||||||
@ -50,10 +48,10 @@ public class MachineState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Map of constants (e.g. CODE_PAGE_SIZE) by AT version */
|
/** 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 {
|
static {
|
||||||
VERSIONED_CONSTANTS.put((short) 1, new VersionedConstants(256, 256, 256, 256));
|
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
|
// Set during construction
|
||||||
@ -63,6 +61,7 @@ public class MachineState {
|
|||||||
public final short numDataPages;
|
public final short numDataPages;
|
||||||
public final short numCallStackPages;
|
public final short numCallStackPages;
|
||||||
public final short numUserStackPages;
|
public final short numUserStackPages;
|
||||||
|
public final long minActivationAmount;
|
||||||
|
|
||||||
private final byte[] headerBytes;
|
private final byte[] headerBytes;
|
||||||
|
|
||||||
@ -148,7 +147,7 @@ public class MachineState {
|
|||||||
|
|
||||||
this.version = byteBuffer.getShort();
|
this.version = byteBuffer.getShort();
|
||||||
if (this.version < 1)
|
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);
|
this.constants = VERSIONED_CONSTANTS.get(this.version);
|
||||||
if (this.constants == null)
|
if (this.constants == null)
|
||||||
@ -172,6 +171,8 @@ public class MachineState {
|
|||||||
if (this.numUserStackPages < 0)
|
if (this.numUserStackPages < 0)
|
||||||
throw new IllegalArgumentException("Number of user stack pages must be >= 0");
|
throw new IllegalArgumentException("Number of user stack pages must be >= 0");
|
||||||
|
|
||||||
|
this.minActivationAmount = byteBuffer.getLong();
|
||||||
|
|
||||||
// Header OK - set up code and data buffers
|
// Header OK - set up code and data buffers
|
||||||
this.codeByteBuffer = ByteBuffer.allocate(this.numCodePages * this.constants.CODE_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN);
|
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);
|
this.dataByteBuffer = ByteBuffer.allocate(this.numDataPages * this.constants.DATA_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
@ -207,7 +208,7 @@ public class MachineState {
|
|||||||
commonFinalConstruction();
|
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) {
|
public MachineState(API api, LoggerInterface logger, byte[] headerBytes, byte[] codeBytes, byte[] dataBytes) {
|
||||||
this(api, logger, headerBytes);
|
this(api, logger, headerBytes);
|
||||||
|
|
||||||
@ -236,6 +237,14 @@ public class MachineState {
|
|||||||
this.isFinished = false;
|
this.isFinished = false;
|
||||||
this.hadFatalError = false;
|
this.hadFatalError = false;
|
||||||
this.previousBalance = 0;
|
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
|
// Getters / setters
|
||||||
@ -275,8 +284,8 @@ public class MachineState {
|
|||||||
return this.sleepUntilHeight;
|
return this.sleepUntilHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ void setSleepUntilHeight(Integer address) {
|
/* package */ void setSleepUntilHeight(Integer height) {
|
||||||
this.sleepUntilHeight = address;
|
this.sleepUntilHeight = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getIsStopped() {
|
public boolean getIsStopped() {
|
||||||
@ -319,7 +328,6 @@ public class MachineState {
|
|||||||
this.hadFatalError = hadFatalError;
|
this.hadFatalError = hadFatalError;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No corresponding setters due to package-scope - see above
|
|
||||||
public long getA1() {
|
public long getA1() {
|
||||||
return this.a1;
|
return this.a1;
|
||||||
}
|
}
|
||||||
@ -336,6 +344,18 @@ public class MachineState {
|
|||||||
return this.a4;
|
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() {
|
public long getB1() {
|
||||||
return this.b1;
|
return this.b1;
|
||||||
}
|
}
|
||||||
@ -351,7 +371,18 @@ public class MachineState {
|
|||||||
public long getB4() {
|
public long getB4() {
|
||||||
return this.b4;
|
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() {
|
public int getCurrentBlockHeight() {
|
||||||
return this.currentBlockHeight;
|
return this.currentBlockHeight;
|
||||||
@ -389,8 +420,80 @@ public class MachineState {
|
|||||||
return this.isFirstOpCodeAfterSleeping;
|
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
|
// 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 */
|
/** For serializing a machine state */
|
||||||
public byte[] toBytes() {
|
public byte[] toBytes() {
|
||||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||||
@ -399,9 +502,6 @@ public class MachineState {
|
|||||||
// Header first
|
// Header first
|
||||||
bytes.write(this.headerBytes);
|
bytes.write(this.headerBytes);
|
||||||
|
|
||||||
// Code
|
|
||||||
bytes.write(this.codeByteBuffer.array());
|
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
bytes.write(this.dataByteBuffer.array());
|
bytes.write(this.dataByteBuffer.array());
|
||||||
|
|
||||||
@ -473,7 +573,7 @@ public class MachineState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** For restoring a previously serialized machine state */
|
/** 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);
|
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
byte[] headerBytes = new byte[HEADER_LENGTH];
|
byte[] headerBytes = new byte[HEADER_LENGTH];
|
||||||
@ -481,8 +581,9 @@ public class MachineState {
|
|||||||
|
|
||||||
MachineState state = new MachineState(api, logger, headerBytes);
|
MachineState state = new MachineState(api, logger, headerBytes);
|
||||||
|
|
||||||
byte[] codeBytes = new byte[state.codeByteBuffer.capacity()];
|
if (codeBytes.length != state.codeByteBuffer.capacity())
|
||||||
byteBuffer.get(codeBytes);
|
throw new IllegalStateException("Passed codeBytes does not match length in header");
|
||||||
|
|
||||||
System.arraycopy(codeBytes, 0, state.codeByteBuffer.array(), 0, codeBytes.length);
|
System.arraycopy(codeBytes, 0, state.codeByteBuffer.array(), 0, codeBytes.length);
|
||||||
|
|
||||||
byte[] dataBytes = new byte[state.dataByteBuffer.capacity()];
|
byte[] dataBytes = new byte[state.dataByteBuffer.capacity()];
|
||||||
@ -631,7 +732,9 @@ public class MachineState {
|
|||||||
this.isFrozen = false;
|
this.isFrozen = false;
|
||||||
this.frozenBalance = null;
|
this.frozenBalance = null;
|
||||||
|
|
||||||
|
// Cache useful info from API
|
||||||
long feePerStep = this.api.getFeePerStep();
|
long feePerStep = this.api.getFeePerStep();
|
||||||
|
int maxSteps = api.getMaxStepsPerRound();
|
||||||
|
|
||||||
// Set byte buffer position using program counter
|
// Set byte buffer position using program counter
|
||||||
codeByteBuffer.position(this.programCounter);
|
codeByteBuffer.position(this.programCounter);
|
||||||
@ -650,8 +753,8 @@ public class MachineState {
|
|||||||
int opcodeSteps = this.api.getOpCodeSteps(nextOpCode);
|
int opcodeSteps = this.api.getOpCodeSteps(nextOpCode);
|
||||||
long opcodeFee = opcodeSteps * feePerStep;
|
long opcodeFee = opcodeSteps * feePerStep;
|
||||||
|
|
||||||
if (this.steps + opcodeSteps > MAX_STEPS) {
|
if (this.steps + opcodeSteps > maxSteps) {
|
||||||
logger.debug("Enforced sleep due to exceeding maximum number of steps (" + MAX_STEPS + ") per execution round");
|
logger.debug("Enforced sleep due to exceeding maximum number of steps (" + maxSteps + ") per execution round");
|
||||||
this.isSleeping = true;
|
this.isSleeping = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -717,7 +820,7 @@ public class MachineState {
|
|||||||
|
|
||||||
/** Return disassembly of code bytes */
|
/** Return disassembly of code bytes */
|
||||||
public String disassemble() throws ExecutionException {
|
public String disassemble() throws ExecutionException {
|
||||||
String output = "";
|
StringBuilder output = new StringBuilder();
|
||||||
|
|
||||||
codeByteBuffer.position(0);
|
codeByteBuffer.position(0);
|
||||||
|
|
||||||
@ -730,13 +833,13 @@ public class MachineState {
|
|||||||
if (nextOpCode == null)
|
if (nextOpCode == null)
|
||||||
throw new IllegalOperationException("OpCode 0x" + String.format("%02x", rawOpCode) + " not recognised");
|
throw new IllegalOperationException("OpCode 0x" + String.format("%02x", rawOpCode) + " not recognised");
|
||||||
|
|
||||||
if (!output.isEmpty())
|
if (output.length() != 0)
|
||||||
output += "\n";
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -409,7 +409,7 @@ public enum OpCode {
|
|||||||
* <tt>@addr1 <<= $addr2</tt>
|
* <tt>@addr1 <<= $addr2</tt>
|
||||||
*/
|
*/
|
||||||
SHL_DAT(0x17, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
|
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
|
@Override
|
||||||
public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
|
public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
|
||||||
@ -424,7 +424,7 @@ public enum OpCode {
|
|||||||
* Note: new MSB bit will be zero
|
* Note: new MSB bit will be zero
|
||||||
*/
|
*/
|
||||||
SHR_DAT(0x18, OpCodeParam.DEST_ADDR, OpCodeParam.SRC_ADDR) {
|
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
|
@Override
|
||||||
public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
|
public void executeWithParams(MachineState state, Object... args) throws ExecutionException {
|
||||||
@ -845,7 +845,7 @@ public enum OpCode {
|
|||||||
public final OpCodeParam[] params;
|
public final OpCodeParam[] params;
|
||||||
|
|
||||||
// Create a map of opcode values to OpCode
|
// 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) {
|
private OpCode(int value, OpCodeParam... params) {
|
||||||
this.value = (byte) value;
|
this.value = (byte) value;
|
||||||
@ -876,7 +876,7 @@ public enum OpCode {
|
|||||||
public abstract void executeWithParams(MachineState state, Object... args) throws ExecutionException;
|
public abstract void executeWithParams(MachineState state, Object... args) throws ExecutionException;
|
||||||
|
|
||||||
public void execute(MachineState state) 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)
|
for (OpCodeParam param : this.params)
|
||||||
args.add(param.fetch(state.codeByteBuffer, state.dataByteBuffer));
|
args.add(param.fetch(state.codeByteBuffer, state.dataByteBuffer));
|
||||||
@ -893,14 +893,16 @@ public enum OpCode {
|
|||||||
* @throws ExecutionException
|
* @throws ExecutionException
|
||||||
*/
|
*/
|
||||||
public String disassemble(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) 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();
|
int postOpcodeProgramCounter = codeByteBuffer.position();
|
||||||
|
|
||||||
for (OpCodeParam param : this.params)
|
for (OpCodeParam param : this.params) {
|
||||||
output += " " + param.disassemble(codeByteBuffer, dataByteBuffer, postOpcodeProgramCounter);
|
output.append(" ");
|
||||||
|
output.append(param.disassemble(codeByteBuffer, dataByteBuffer, postOpcodeProgramCounter));
|
||||||
|
}
|
||||||
|
|
||||||
return output;
|
return output.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
@ -7,7 +7,7 @@ public enum OpCodeParam {
|
|||||||
VALUE {
|
VALUE {
|
||||||
@Override
|
@Override
|
||||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
||||||
return new Long(Utils.getCodeValue(codeByteBuffer));
|
return Long.valueOf(Utils.getCodeValue(codeByteBuffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -18,7 +18,7 @@ public enum OpCodeParam {
|
|||||||
DEST_ADDR {
|
DEST_ADDR {
|
||||||
@Override
|
@Override
|
||||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
||||||
return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
|
return Integer.valueOf(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -29,7 +29,7 @@ public enum OpCodeParam {
|
|||||||
INDIRECT_DEST_ADDR {
|
INDIRECT_DEST_ADDR {
|
||||||
@Override
|
@Override
|
||||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
||||||
return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
|
return Integer.valueOf(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -40,7 +40,7 @@ public enum OpCodeParam {
|
|||||||
INDIRECT_DEST_ADDR_WITH_INDEX {
|
INDIRECT_DEST_ADDR_WITH_INDEX {
|
||||||
@Override
|
@Override
|
||||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
||||||
return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
|
return Integer.valueOf(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -51,7 +51,7 @@ public enum OpCodeParam {
|
|||||||
SRC_ADDR {
|
SRC_ADDR {
|
||||||
@Override
|
@Override
|
||||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
||||||
return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
|
return Integer.valueOf(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -62,7 +62,7 @@ public enum OpCodeParam {
|
|||||||
INDIRECT_SRC_ADDR {
|
INDIRECT_SRC_ADDR {
|
||||||
@Override
|
@Override
|
||||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
||||||
return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
|
return Integer.valueOf(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -73,7 +73,7 @@ public enum OpCodeParam {
|
|||||||
INDIRECT_SRC_ADDR_WITH_INDEX {
|
INDIRECT_SRC_ADDR_WITH_INDEX {
|
||||||
@Override
|
@Override
|
||||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
||||||
return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
|
return Integer.valueOf(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -84,7 +84,7 @@ public enum OpCodeParam {
|
|||||||
INDEX {
|
INDEX {
|
||||||
@Override
|
@Override
|
||||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
||||||
return new Integer(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
|
return Integer.valueOf(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -95,7 +95,7 @@ public enum OpCodeParam {
|
|||||||
CODE_ADDR {
|
CODE_ADDR {
|
||||||
@Override
|
@Override
|
||||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
||||||
return new Integer(Utils.getCodeAddress(codeByteBuffer));
|
return Integer.valueOf(Utils.getCodeAddress(codeByteBuffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -106,7 +106,7 @@ public enum OpCodeParam {
|
|||||||
OFFSET {
|
OFFSET {
|
||||||
@Override
|
@Override
|
||||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
||||||
return new Byte(Utils.getCodeOffset(codeByteBuffer));
|
return Byte.valueOf(Utils.getCodeOffset(codeByteBuffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -117,7 +117,7 @@ public enum OpCodeParam {
|
|||||||
FUNC {
|
FUNC {
|
||||||
@Override
|
@Override
|
||||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
||||||
return new Short(codeByteBuffer.getShort());
|
return Short.valueOf(codeByteBuffer.getShort());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -138,7 +138,7 @@ public enum OpCodeParam {
|
|||||||
BLOCK_HEIGHT {
|
BLOCK_HEIGHT {
|
||||||
@Override
|
@Override
|
||||||
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
|
||||||
return new Integer(codeByteBuffer.getInt());
|
return Integer.valueOf(codeByteBuffer.getInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
109
Java/src/main/java/org/ciyam/at/Timestamp.java
Normal file
109
Java/src/main/java/org/ciyam/at/Timestamp.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -62,8 +62,8 @@ public class DisassemblyTests {
|
|||||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.ECHO.value).putInt(1);
|
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.ECHO.value).putInt(1);
|
||||||
codeByteBuffer.put(OpCode.FIN_IMD.value);
|
codeByteBuffer.put(OpCode.FIN_IMD.value);
|
||||||
|
|
||||||
// version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4
|
// version 0002, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4, minActivation = 0
|
||||||
byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000");
|
byte[] headerBytes = hexToBytes("0200" + "0000" + "0002" + "2000" + "1000" + "1000" + "0000000000000000");
|
||||||
byte[] codeBytes = codeByteBuffer.array();
|
byte[] codeBytes = codeByteBuffer.array();
|
||||||
byte[] dataBytes = new byte[0];
|
byte[] dataBytes = new byte[0];
|
||||||
|
|
||||||
@ -81,8 +81,8 @@ public class DisassemblyTests {
|
|||||||
codeByteBuffer.put(hexToBytes("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"));
|
codeByteBuffer.put(hexToBytes("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"));
|
||||||
codeByteBuffer.put(hexToBytes("000000000000"));
|
codeByteBuffer.put(hexToBytes("000000000000"));
|
||||||
|
|
||||||
// version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4
|
// version 0002, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4, minActivation = 0
|
||||||
byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000");
|
byte[] headerBytes = hexToBytes("0200" + "0000" + "0002" + "2000" + "1000" + "1000" + "0000000000000000");
|
||||||
byte[] codeBytes = codeByteBuffer.array();
|
byte[] codeBytes = codeByteBuffer.array();
|
||||||
byte[] dataBytes = new byte[0];
|
byte[] dataBytes = new byte[0];
|
||||||
|
|
68
Java/src/test/java/MiscTests.java
Normal file
68
Java/src/test/java/MiscTests.java
Normal file
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -27,7 +27,7 @@ public class SerializationTests {
|
|||||||
public void beforeTest() {
|
public void beforeTest() {
|
||||||
logger = new TestLogger();
|
logger = new TestLogger();
|
||||||
api = new TestAPI();
|
api = new TestAPI();
|
||||||
codeByteBuffer = ByteBuffer.allocate(256).order(ByteOrder.LITTLE_ENDIAN);
|
codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@ -38,8 +38,8 @@ public class SerializationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private byte[] simulate() {
|
private byte[] simulate() {
|
||||||
// version 0003, reserved 0000, code 0100 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4
|
// version 0002, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4, minActivation = 0
|
||||||
byte[] headerBytes = hexToBytes("0300" + "0000" + "0001" + "2000" + "1000" + "1000");
|
byte[] headerBytes = hexToBytes("0200" + "0000" + "0002" + "2000" + "1000" + "1000" + "0000000000000000");
|
||||||
byte[] codeBytes = codeByteBuffer.array();
|
byte[] codeBytes = codeByteBuffer.array();
|
||||||
byte[] dataBytes = new byte[0];
|
byte[] dataBytes = new byte[0];
|
||||||
|
|
||||||
@ -49,7 +49,8 @@ public class SerializationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private byte[] continueSimulation(byte[] savedState) {
|
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
|
// Pretend we're on next block
|
||||||
api.bumpCurrentBlockHeight();
|
api.bumpCurrentBlockHeight();
|
||||||
@ -61,10 +62,13 @@ public class SerializationTests {
|
|||||||
state.execute();
|
state.execute();
|
||||||
|
|
||||||
byte[] stateBytes = state.toBytes();
|
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[] restoredStateBytes = restoredState.toBytes();
|
||||||
|
byte[] restoredCodeBytes = state.getCodeBytes();
|
||||||
|
|
||||||
assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(stateBytes, restoredStateBytes));
|
assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(stateBytes, restoredStateBytes));
|
||||||
|
assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(codeBytes, restoredCodeBytes));
|
||||||
|
|
||||||
return stateBytes;
|
return stateBytes;
|
||||||
}
|
}
|
@ -52,8 +52,8 @@ public class TestACCT {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private byte[] simulate() {
|
private byte[] simulate() {
|
||||||
// version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4
|
// version 0002, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4, minActivation = 0
|
||||||
byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000");
|
byte[] headerBytes = hexToBytes("0200" + "0000" + "0002" + "2000" + "1000" + "1000" + "0000000000000000");
|
||||||
byte[] codeBytes = codeByteBuffer.array();
|
byte[] codeBytes = codeByteBuffer.array();
|
||||||
byte[] dataBytes = dataByteBuffer.array();
|
byte[] dataBytes = dataByteBuffer.array();
|
||||||
|
|
||||||
@ -63,7 +63,8 @@ public class TestACCT {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private byte[] continueSimulation(byte[] savedState) {
|
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);
|
return executeAndCheck(state);
|
||||||
}
|
}
|
||||||
@ -74,10 +75,13 @@ public class TestACCT {
|
|||||||
api.setCurrentBalance(state.getCurrentBalance());
|
api.setCurrentBalance(state.getCurrentBalance());
|
||||||
|
|
||||||
byte[] stateBytes = state.toBytes();
|
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[] restoredStateBytes = restoredState.toBytes();
|
||||||
|
byte[] restoredCodeBytes = state.getCodeBytes();
|
||||||
|
|
||||||
assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(stateBytes, restoredStateBytes));
|
assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(stateBytes, restoredStateBytes));
|
||||||
|
assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(codeBytes, restoredCodeBytes));
|
||||||
|
|
||||||
return stateBytes;
|
return stateBytes;
|
||||||
}
|
}
|
@ -150,6 +150,11 @@ public class ACCTAPI extends API {
|
|||||||
return accounts.get(accountIndex).address;
|
return accounts.get(accountIndex).address;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxStepsPerRound() {
|
||||||
|
return 500;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getOpCodeSteps(OpCode opcode) {
|
public int getOpCodeSteps(OpCode opcode) {
|
||||||
return 1;
|
return 1;
|
||||||
@ -331,12 +336,13 @@ public class ACCTAPI extends API {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
// NOT USED
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
// NOT USED
|
||||||
}
|
}
|
||||||
|
|
@ -14,8 +14,7 @@ import org.junit.BeforeClass;
|
|||||||
|
|
||||||
public abstract class ExecutableTest {
|
public abstract class ExecutableTest {
|
||||||
|
|
||||||
public static final int CODE_OFFSET = 6 * 2;
|
public static final int DATA_OFFSET = 6 * 2 + 8;
|
||||||
public static final int DATA_OFFSET = CODE_OFFSET + 0x0200;
|
|
||||||
public static final int CALL_STACK_OFFSET = DATA_OFFSET + 0x0020 * 8;
|
public static final int CALL_STACK_OFFSET = DATA_OFFSET + 0x0020 * 8;
|
||||||
|
|
||||||
public TestLogger logger;
|
public TestLogger logger;
|
||||||
@ -49,8 +48,8 @@ public abstract class ExecutableTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void execute(boolean onceOnly) {
|
protected void execute(boolean onceOnly) {
|
||||||
// version 0003, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 8
|
// version 0002, reserved 0000, code 0200 * 1, data 0020 * 8, call stack 0010 * 4, user stack 0010 * 4, minActivation = 0
|
||||||
byte[] headerBytes = hexToBytes("0300" + "0000" + "0002" + "2000" + "1000" + "1000");
|
byte[] headerBytes = hexToBytes("0200" + "0000" + "0002" + "2000" + "1000" + "1000" + "0000000000000000");
|
||||||
byte[] codeBytes = codeByteBuffer.array();
|
byte[] codeBytes = codeByteBuffer.array();
|
||||||
byte[] dataBytes = new byte[0];
|
byte[] dataBytes = new byte[0];
|
||||||
|
|
||||||
@ -82,6 +81,10 @@ public abstract class ExecutableTest {
|
|||||||
|
|
||||||
System.out.println("Frozen: " + state.getIsFrozen());
|
System.out.println("Frozen: " + state.getIsFrozen());
|
||||||
|
|
||||||
|
long newBalance = state.getCurrentBalance();
|
||||||
|
System.out.println("New balance: " + newBalance);
|
||||||
|
api.setCurrentBalance(newBalance);
|
||||||
|
|
||||||
// Bump block height
|
// Bump block height
|
||||||
api.bumpCurrentBlockHeight();
|
api.bumpCurrentBlockHeight();
|
||||||
} while (!onceOnly && !state.getIsFinished());
|
} while (!onceOnly && !state.getIsFinished());
|
||||||
@ -90,7 +93,7 @@ public abstract class ExecutableTest {
|
|||||||
byte[] stateBytes = state.toBytes();
|
byte[] stateBytes = state.toBytes();
|
||||||
|
|
||||||
// We know how the state will be serialized so we can extract values
|
// 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);
|
stateByteBuffer = ByteBuffer.wrap(stateBytes).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
callStackSize = stateByteBuffer.getInt(CALL_STACK_OFFSET);
|
callStackSize = stateByteBuffer.getInt(CALL_STACK_OFFSET);
|
@ -13,15 +13,22 @@ public class TestAPI extends API {
|
|||||||
private static final int BLOCK_PERIOD = 10 * 60; // average period between blocks in seconds
|
private static final int BLOCK_PERIOD = 10 * 60; // average period between blocks in seconds
|
||||||
|
|
||||||
private int currentBlockHeight;
|
private int currentBlockHeight;
|
||||||
|
private long currentBalance;
|
||||||
|
|
||||||
public TestAPI() {
|
public TestAPI() {
|
||||||
this.currentBlockHeight = 10;
|
this.currentBlockHeight = 10;
|
||||||
|
this.currentBalance = 1234L;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bumpCurrentBlockHeight() {
|
public void bumpCurrentBlockHeight() {
|
||||||
++this.currentBlockHeight;
|
++this.currentBlockHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxStepsPerRound() {
|
||||||
|
return 500;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getOpCodeSteps(OpCode opcode) {
|
public int getOpCodeSteps(OpCode opcode) {
|
||||||
if (opcode.value >= OpCode.EXT_FUN.value && opcode.value <= OpCode.EXT_FUN_RET_DAT_2.value)
|
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
|
@Override
|
||||||
public long generateRandomUsingTransactionInA(MachineState state) {
|
public long generateRandomUsingTransactionInA(MachineState state) {
|
||||||
if (isFirstOpCodeAfterSleeping(state)) {
|
if (!isFirstOpCodeAfterSleeping(state)) {
|
||||||
// First call
|
// First call
|
||||||
System.out.println("generateRandomUsingTransactionInA: first call - sleeping");
|
System.out.println("generateRandomUsingTransactionInA: first call - sleeping");
|
||||||
|
|
||||||
@ -125,7 +132,12 @@ public class TestAPI extends API {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getCurrentBalance(MachineState state) {
|
public long getCurrentBalance(MachineState state) {
|
||||||
return 12345L;
|
return this.currentBalance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugging only
|
||||||
|
public void setCurrentBalance(long currentBalance) {
|
||||||
|
this.currentBalance = currentBalance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -154,11 +166,12 @@ public class TestAPI extends API {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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;
|
Integer requiredParamCount;
|
||||||
Boolean returnsValue;
|
Boolean returnsValue;
|
||||||
|
|
||||||
switch (functionCodeValue) {
|
switch (rawFunctionCode) {
|
||||||
case 0x0501:
|
case 0x0501:
|
||||||
// take one arg, no return value
|
// take one arg, no return value
|
||||||
requiredParamCount = 1;
|
requiredParamCount = 1;
|
||||||
@ -173,7 +186,7 @@ public class TestAPI extends API {
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
// Unrecognised platform-specific function code
|
// 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)
|
if (requiredParamCount == null || returnsValue == null)
|
||||||
@ -181,16 +194,16 @@ public class TestAPI extends API {
|
|||||||
|
|
||||||
if (paramCount != requiredParamCount)
|
if (paramCount != requiredParamCount)
|
||||||
throw new IllegalFunctionCodeException("Passed paramCount (" + paramCount + ") does not match platform-specific function code 0x"
|
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)
|
if (returnValueExpected != returnsValue)
|
||||||
throw new IllegalFunctionCodeException("Passed returnValueExpected (" + returnValueExpected + ") does not match platform-specific function code 0x"
|
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
|
@Override
|
||||||
public void platformSpecificPostCheckExecute(short functionCodeValue, FunctionData functionData, MachineState state) throws ExecutionException {
|
public void platformSpecificPostCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
|
||||||
switch (functionCodeValue) {
|
switch (rawFunctionCode) {
|
||||||
case 0x0501:
|
case 0x0501:
|
||||||
System.out.println("Platform-specific function 0x0501 called with 0x" + String.format("%016x", functionData.value1));
|
System.out.println("Platform-specific function 0x0501 called with 0x" + String.format("%016x", functionData.value1));
|
||||||
break;
|
break;
|
||||||
@ -202,7 +215,7 @@ public class TestAPI extends API {
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
// Unrecognised platform-specific function code
|
// 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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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…
Reference in New Issue
Block a user