mirror of
https://github.com/Qortal/AT.git
synced 2025-01-30 02:42: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">
|
||||
<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>
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
@ -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
|
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.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];
|
||||
|
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() {
|
||||
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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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);
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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