3
0
mirror of https://github.com/Qortal/AT.git synced 2025-01-30 10:52:14 +00:00

OpCode refactoring, versioned constants, refactored tests

API is now an abstract class instead of an Interface. This is to
allow protected methods that give access to package-scoped methods
and variables inside MachineState.

MachineState now supports a different set of constants based on AT
version.

MachineState's methods and variables have had their scopes tightened
up and getters/setters added where appropriate.

MachineState.parseHeader() inlined into the constructor that calls it
so it can set public final variables.

Some reordering and additional comments in MachineState.

OpCode enum entries refactored so that:
a) variables provided via MachineState "state" are not explicitly
passed as well

b) args to each opcode are pre-fetched from the codeByteBuffer before
calling and only need to be cast before use. this comes almost
"for free" thanks to OpCodeParam.fetch

Calling functions from OpCode now cleaner as there's no write-access
to programCounter, only changing codeByteBuffer's position.

Also in OpCode, state.getProgramCounter() now provides a consistent
before-opcode position for branches, jumps, etc.

Lots of repeated code refactored out of unit tests into ExecutableTest
class. ExecutableTest also provides helper methods for examining
post-execution data values, stack positions and entries.
This commit is contained in:
catbref 2018-10-05 15:56:32 +01:00
parent f0e031599d
commit 454d4bed35
18 changed files with 1318 additions and 1550 deletions

View File

@ -10,33 +10,33 @@ package org.ciyam.at;
* bits) with the second part being the number of the transaction if applicable (also 32 bits and zero if not applicable).
*
*/
public interface API {
public abstract class API {
/** Returns current blockchain's height */
public int getCurrentBlockHeight();
public abstract int getCurrentBlockHeight();
/** Returns block height where AT was created */
public int getATCreationBlockHeight(MachineState state);
public abstract int getATCreationBlockHeight(MachineState state);
/** Returns previous block's height */
default public int getPreviousBlockHeight() {
public int getPreviousBlockHeight() {
return getCurrentBlockHeight() - 1;
}
/** Put previous block's signature hash in A */
public void putPreviousBlockHashInA(MachineState state);
public abstract void putPreviousBlockHashInA(MachineState state);
/** Put next transaction to AT after timestamp in A */
public 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 */
public long getTypeFromTransactionInA(MachineState state);
public abstract long getTypeFromTransactionInA(MachineState state);
/** Return amount from transaction in A, after transaction fees have been deducted, or 0xffffffffffffffff if A not valid transaction */
public long getAmountFromTransactionInA(MachineState state);
public abstract long getAmountFromTransactionInA(MachineState state);
/** Return timestamp from transaction in A, or 0xffffffffffffffff if A not valid transaction */
public long getTimestampFromTransactionInA(MachineState state);
public abstract long getTimestampFromTransactionInA(MachineState state);
/**
* Generate pseudo-random number using transaction in A.
@ -48,53 +48,92 @@ public interface API {
* <p>
* Returns 0xffffffffffffffff if A not valid transaction.
*/
public long generateRandomUsingTransactionInA(MachineState state);
public abstract long generateRandomUsingTransactionInA(MachineState state);
/** Put 'message' from transaction in A into B */
public void putMessageFromTransactionInAIntoB(MachineState state);
public abstract void putMessageFromTransactionInAIntoB(MachineState state);
/** Put sender/creator address from transaction in A into B */
public void putAddressFromTransactionInAIntoB(MachineState state);
public abstract void putAddressFromTransactionInAIntoB(MachineState state);
/** Put AT's creator's address into B */
public void putCreatorAddressIntoB(MachineState state);
public abstract void putCreatorAddressIntoB(MachineState state);
/** Return AT's current balance */
public long getCurrentBalance(MachineState state);
public abstract long getCurrentBalance(MachineState state);
/** Return AT's previous balance at end of last execution round. Does not include any amounts sent to AT since */
public long getPreviousBalance(MachineState state);
public abstract long getPreviousBalance(MachineState state);
/** Pay passed amount, or current balance if necessary, (fee inclusive) to address in B */
public void payAmountToB(long value1, MachineState state);
public abstract void payAmountToB(long value1, MachineState state);
/** Pay AT's current balance to address in B */
public void payCurrentBalanceToB(MachineState state);
public abstract void payCurrentBalanceToB(MachineState state);
/** Pay AT's previous balance to address in B */
public void payPreviousBalanceToB(MachineState state);
public abstract void payPreviousBalanceToB(MachineState state);
/** Send 'message' in A to address in B */
public void messageAToB(MachineState state);
public abstract void messageAToB(MachineState state);
/**
* Returns <tt>minutes</tt> of blocks added to 'timestamp'
* <p>
* <tt>minutes</tt> is converted to rough number of blocks and added to 'timestamp' to create return value.
*/
public long addMinutesToTimestamp(Timestamp timestamp, long minutes, MachineState state);
public abstract long addMinutesToTimestamp(Timestamp timestamp, long minutes, MachineState state);
/** AT has encountered fatal error. Return remaining funds to creator */
public void onFatalError(MachineState state, ExecutionException e);
public abstract void onFatalError(MachineState state, ExecutionException e);
/** Pre-execute checking of param requirements for platform-specific functions */
public void platformSpecificPreExecuteCheck(short functionCodeValue, int paramCount, boolean returnValueExpected) throws IllegalFunctionCodeException;
public abstract void platformSpecificPreExecuteCheck(short functionCodeValue, int paramCount, boolean returnValueExpected)
throws IllegalFunctionCodeException;
/**
* Platform-specific function execution
*
* @throws ExecutionException
*/
public void platformSpecificPostCheckExecute(short functionCodeValue, FunctionData functionData, MachineState state) throws ExecutionException;
public abstract void platformSpecificPostCheckExecute(short functionCodeValue, FunctionData functionData, MachineState state) throws ExecutionException;
/** Convenience method to allow subclasses to access package-scoped MachineState.setIsSleeping */
protected void setIsSleeping(MachineState state, boolean isSleeping) {
state.setIsSleeping(isSleeping);
}
/** Convenience methods to allow subclasses to access package-scoped a1-a4, b1-b4 variables */
protected void setA1(MachineState state, long value) {
state.a1 = value;
}
protected void setA2(MachineState state, long value) {
state.a2 = value;
}
protected void setA3(MachineState state, long value) {
state.a3 = value;
}
protected void setA4(MachineState state, long value) {
state.a4 = value;
}
protected void setB1(MachineState state, long value) {
state.b1 = value;
}
protected void setB2(MachineState state, long value) {
state.b2 = value;
}
protected void setB3(MachineState state, long value) {
state.b3 = value;
}
protected void setB4(MachineState state, long value) {
state.b4 = value;
}
}

View File

@ -31,7 +31,7 @@ public enum FunctionCode {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
String message = String.valueOf(functionData.value1);
state.logger.echo(message);
state.getLogger().echo(message);
}
},
/**
@ -671,7 +671,7 @@ public enum FunctionCode {
GET_BLOCK_TIMESTAMP(0x0300, 0, true) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
functionData.returnValue = Timestamp.toLong(state.api.getCurrentBlockHeight(), 0);
functionData.returnValue = Timestamp.toLong(state.getAPI().getCurrentBlockHeight(), 0);
}
},
/**
@ -681,7 +681,7 @@ public enum FunctionCode {
GET_CREATION_TIMESTAMP(0x0301, 0, true) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
functionData.returnValue = Timestamp.toLong(state.api.getATCreationBlockHeight(state), 0);
functionData.returnValue = Timestamp.toLong(state.getAPI().getATCreationBlockHeight(state), 0);
}
},
/**
@ -691,7 +691,7 @@ public enum FunctionCode {
GET_PREVIOUS_BLOCK_TIMESTAMP(0x0302, 0, true) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
functionData.returnValue = Timestamp.toLong(state.api.getPreviousBlockHeight(), 0);
functionData.returnValue = Timestamp.toLong(state.getAPI().getPreviousBlockHeight(), 0);
}
},
/**
@ -701,7 +701,7 @@ public enum FunctionCode {
PUT_PREVIOUS_BLOCK_HASH_IN_A(0x0303, 0, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
state.api.putPreviousBlockHashInA(state);
state.getAPI().putPreviousBlockHashInA(state);
}
},
/**
@ -712,7 +712,7 @@ public enum FunctionCode {
PUT_TX_AFTER_TIMESTAMP_IN_A(0x0304, 1, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
state.api.putTransactionAfterTimestampInA(new Timestamp(functionData.value1), state);
state.getAPI().putTransactionAfterTimestampInA(new Timestamp(functionData.value1), state);
}
},
/**
@ -723,7 +723,7 @@ public enum FunctionCode {
GET_TYPE_FROM_TX_IN_A(0x0305, 0, true) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
functionData.returnValue = state.api.getTypeFromTransactionInA(state);
functionData.returnValue = state.getAPI().getTypeFromTransactionInA(state);
}
},
/**
@ -734,7 +734,7 @@ public enum FunctionCode {
GET_AMOUNT_FROM_TX_IN_A(0x0306, 0, true) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
functionData.returnValue = state.api.getAmountFromTransactionInA(state);
functionData.returnValue = state.getAPI().getAmountFromTransactionInA(state);
}
},
/**
@ -745,7 +745,7 @@ public enum FunctionCode {
GET_TIMESTAMP_FROM_TX_IN_A(0x0307, 0, true) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
functionData.returnValue = state.api.getTimestampFromTransactionInA(state);
functionData.returnValue = state.getAPI().getTimestampFromTransactionInA(state);
}
},
/**
@ -757,15 +757,17 @@ public enum FunctionCode {
GENERATE_RANDOM_USING_TX_IN_A(0x0308, 0, true) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
functionData.returnValue = state.api.generateRandomUsingTransactionInA(state);
functionData.returnValue = state.getAPI().generateRandomUsingTransactionInA(state);
// If API set isSleeping then rewind program counter ready for being awoken
if (state.isSleeping) {
state.programCounter -= 1 + 2 + 4; // EXT_FUN_RET(1) + our function code(2) + address(4)
// 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);
// If specific sleep height not set, default to next block
if (state.sleepUntilHeight == null)
state.sleepUntilHeight = state.currentBlockHeight + 1;
if (state.getSleepUntilHeight() == null)
state.setSleepUntilHeight(state.getCurrentBlockHeight() + 1);
}
}
},
@ -778,7 +780,7 @@ public enum FunctionCode {
PUT_MESSAGE_FROM_TX_IN_A_INTO_B(0x0309, 0, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
state.api.putMessageFromTransactionInAIntoB(state);
state.getAPI().putMessageFromTransactionInAIntoB(state);
}
},
/**
@ -788,7 +790,7 @@ public enum FunctionCode {
PUT_ADDRESS_FROM_TX_IN_A_INTO_B(0x030a, 0, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
state.api.putAddressFromTransactionInAIntoB(state);
state.getAPI().putAddressFromTransactionInAIntoB(state);
}
},
/**
@ -798,7 +800,7 @@ public enum FunctionCode {
PUT_CREATOR_INTO_B(0x030b, 0, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
state.api.putCreatorAddressIntoB(state);
state.getAPI().putCreatorAddressIntoB(state);
}
},
/**
@ -808,7 +810,7 @@ public enum FunctionCode {
GET_CURRENT_BALANCE(0x0400, 0, true) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
functionData.returnValue = state.api.getCurrentBalance(state);
functionData.returnValue = state.getAPI().getCurrentBalance(state);
}
},
/**
@ -819,7 +821,7 @@ public enum FunctionCode {
GET_PREVIOUS_BALANCE(0x0401, 0, true) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
functionData.returnValue = state.api.getPreviousBalance(state);
functionData.returnValue = state.getAPI().getPreviousBalance(state);
}
},
/**
@ -830,7 +832,7 @@ public enum FunctionCode {
PAY_TO_ADDRESS_IN_B(0x0402, 1, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
state.api.payAmountToB(functionData.value1, state);
state.getAPI().payAmountToB(functionData.value1, state);
}
},
/**
@ -840,7 +842,7 @@ public enum FunctionCode {
PAY_ALL_TO_ADDRESS_IN_B(0x0403, 0, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
state.api.payCurrentBalanceToB(state);
state.getAPI().payCurrentBalanceToB(state);
}
},
/**
@ -851,7 +853,7 @@ public enum FunctionCode {
PAY_PREVIOUS_TO_ADDRESS_IN_B(0x0404, 0, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
state.api.payPreviousBalanceToB(state);
state.getAPI().payPreviousBalanceToB(state);
}
},
/**
@ -861,7 +863,7 @@ public enum FunctionCode {
MESSAGE_A_TO_ADDRESS_IN_B(0x0405, 0, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
state.api.messageAToB(state);
state.getAPI().messageAToB(state);
}
},
/**
@ -871,7 +873,7 @@ public enum FunctionCode {
ADD_MINUTES_TO_TIMESTAMP(0x0406, 2, true) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
functionData.returnValue = state.api.addMinutesToTimestamp(new Timestamp(functionData.value1), functionData.value2, state);
functionData.returnValue = state.getAPI().addMinutesToTimestamp(new Timestamp(functionData.value1), functionData.value2, state);
}
},
/**
@ -882,12 +884,12 @@ public enum FunctionCode {
API_PASSTHROUGH(0x0500, 0, false) {
@Override
public void preExecuteCheck(int paramCount, boolean returnValueExpected, MachineState state, short rawFunctionCode) throws ExecutionException {
state.api.platformSpecificPreExecuteCheck(rawFunctionCode, paramCount, returnValueExpected);
state.getAPI().platformSpecificPreExecuteCheck(rawFunctionCode, paramCount, returnValueExpected);
}
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
state.api.platformSpecificPostCheckExecute(rawFunctionCode, functionData, state);
state.getAPI().platformSpecificPostCheckExecute(rawFunctionCode, functionData, state);
}
};
@ -943,7 +945,7 @@ public enum FunctionCode {
if (functionData.paramCount == 2 && functionData.value2 == null)
throw new IllegalFunctionCodeException("Passed value2 is null but function has paramCount of (" + this.paramCount + ")");
state.logger.debug("Function \"" + this.name() + "\"");
state.getLogger().debug("Function \"" + this.name() + "\"");
postCheckExecute(functionData, state, rawFunctionCode);
}

View File

@ -4,12 +4,21 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.HashMap;
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
/** Size of one OpCode - typically 1 byte (byte) */
public static final int OPCODE_SIZE = 1;
/** Size of one FunctionCode - typically 2 bytes (short) */
public static final int FUNCTIONCODE_SIZE = 2;
/** Size of value stored in data segment - typically 8 bytes (long) */
public static final int VALUE_SIZE = 8;
@ -19,107 +28,102 @@ public class MachineState {
/** Maximum value for an address in the code segment */
public static final int MAX_CODE_ADDRESS = 0x1fffffff;
private static class VersionedConstants {
/** Bytes per code page */
public static final int CODE_PAGE_SIZE = 1;
public final int CODE_PAGE_SIZE;
/** Bytes per data page */
public static final int DATA_PAGE_SIZE = VALUE_SIZE;
public final int DATA_PAGE_SIZE;
/** Bytes per call stack page */
public static final int CALL_STACK_PAGE_SIZE = ADDRESS_SIZE;
public final int CALL_STACK_PAGE_SIZE;
/** Bytes per user stack page */
public static final int USER_STACK_PAGE_SIZE = VALUE_SIZE;
public final int USER_STACK_PAGE_SIZE;
public VersionedConstants(int codePageSize, int dataPageSize, int callStackPageSize, int userStackPageSize) {
CODE_PAGE_SIZE = codePageSize;
DATA_PAGE_SIZE = dataPageSize;
CALL_STACK_PAGE_SIZE = callStackPageSize;
USER_STACK_PAGE_SIZE = userStackPageSize;
}
}
/** Map of constants (e.g. CODE_PAGE_SIZE) by AT version */
private static final Map<Short, VersionedConstants> VERSIONED_CONSTANTS = new HashMap<Short, VersionedConstants>();
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));
}
// Set during construction
public final short version;
public final short reserved;
public final short numCodePages;
public final short numDataPages;
public final short numCallStackPages;
public final short numUserStackPages;
private final byte[] headerBytes;
/** Constants set in effect */
private final VersionedConstants constants;
/** Program Counter: offset into code to point of current execution */
public int programCounter;
private int programCounter;
/** Initial program counter value to use on next block after current block's execution has stopped. 0 by default */
public int onStopAddress;
private int onStopAddress;
/** Program counter value to use if an error occurs during execution. If null upon error, refund all funds to creator and finish */
public Integer onErrorAddress;
private Integer onErrorAddress;
/** Execution for current block has stopped. Continue at current program counter on next/specific block */
public boolean isSleeping;
private boolean isSleeping;
/** Block height required to wake from sleeping, or null if not in use */
public Integer sleepUntilHeight;
private Integer sleepUntilHeight;
/** Execution for current block has stopped. Restart at onStopAddress on next block */
public boolean isStopped;
private boolean isStopped;
/** Execution stopped due to lack of funds for processing. Restart at onStopAddress if frozenBalance increases */
public boolean isFrozen;
private boolean isFrozen;
/** Balance at which there were not enough funds, or null if not in use */
public Long frozenBalance;
private Long frozenBalance;
/** Execution permanently stopped */
public boolean isFinished;
private boolean isFinished;
/** Execution permanently stopped due to fatal error */
public boolean hadFatalError;
private boolean hadFatalError;
// 256-bit pseudo-registers
public long a1;
public long a2;
public long a3;
public long a4;
// NOTE: These are package-scope to allow easy access/operations in FunctionCodes.
// Outside classes (e.g. unit tests) can use getters
/* package */ long a1;
/* package */ long a2;
/* package */ long a3;
/* package */ long a4;
public long b1;
public long b2;
public long b3;
public long b4;
/* package */ long b1;
/* package */ long b2;
/* package */ long b3;
/* package */ long b4;
public int currentBlockHeight;
private int currentBlockHeight;
/** Number of opcodes processed this execution */
public int steps;
private int steps;
public API api;
LoggerInterface logger;
private API api;
private LoggerInterface logger;
public short version;
public short reserved;
public short numCodePages;
public short numDataPages;
public short numCallStackPages;
public short numUserStackPages;
// NOTE: These are package-scope to allow easy access/operations in Opcode/FunctionCode.
/* package */ ByteBuffer codeByteBuffer;
/* package */ ByteBuffer dataByteBuffer;
/* package */ ByteBuffer callStackByteBuffer;
/* package */ ByteBuffer userStackByteBuffer;
public byte[] headerBytes;
public ByteBuffer codeByteBuffer;
public ByteBuffer dataByteBuffer;
public ByteBuffer callStackByteBuffer;
public ByteBuffer userStackByteBuffer;
private class Flags {
private int flags;
public Flags() {
flags = 0;
}
public Flags(int value) {
this.flags = value;
}
public void push(boolean flag) {
flags <<= 1;
flags |= flag ? 1 : 0;
}
public boolean pop() {
boolean result = (flags & 1) != 0;
flags >>>= 1;
return result;
}
public int intValue() {
return flags;
}
}
// Constructors
/** For internal use when recreating a machine state */
private MachineState(API api, LoggerInterface logger, byte[] headerBytes) {
@ -127,54 +131,233 @@ public class MachineState {
throw new IllegalArgumentException("headerBytes length " + headerBytes.length + " incorrect, expected " + HEADER_LENGTH);
this.headerBytes = headerBytes;
parseHeader();
this.codeByteBuffer = ByteBuffer.allocate(this.numCodePages * CODE_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN);
// Parsing header bytes
ByteBuffer byteBuffer = ByteBuffer.wrap(this.headerBytes);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
this.dataByteBuffer = ByteBuffer.allocate(this.numDataPages * DATA_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN);
this.version = byteBuffer.getShort();
if (this.version < 1)
throw new IllegalArgumentException("Version must be >= 0");
this.callStackByteBuffer = ByteBuffer.allocate(this.numCallStackPages * CALL_STACK_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN);
this.constants = VERSIONED_CONSTANTS.get(this.version);
if (this.constants == null)
throw new IllegalArgumentException("Version " + this.version + " unsupported");
this.reserved = byteBuffer.getShort();
this.numCodePages = byteBuffer.getShort();
if (this.numCodePages < 1)
throw new IllegalArgumentException("Number of code pages must be > 0");
this.numDataPages = byteBuffer.getShort();
if (this.numDataPages < 1)
throw new IllegalArgumentException("Number of data pages must be > 0");
this.numCallStackPages = byteBuffer.getShort();
if (this.numCallStackPages < 0)
throw new IllegalArgumentException("Number of call stack pages must be >= 0");
this.numUserStackPages = byteBuffer.getShort();
if (this.numUserStackPages < 0)
throw new IllegalArgumentException("Number of user stack pages must be >= 0");
// 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);
// Set up stacks
this.callStackByteBuffer = ByteBuffer.allocate(this.numCallStackPages * this.constants.CALL_STACK_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN);
this.callStackByteBuffer.position(this.callStackByteBuffer.limit()); // Downward-growing stack, so start at the end
this.userStackByteBuffer = ByteBuffer.allocate(this.numUserStackPages * USER_STACK_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN);
this.userStackByteBuffer = ByteBuffer.allocate(this.numUserStackPages * this.constants.USER_STACK_PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN);
this.userStackByteBuffer.position(this.userStackByteBuffer.limit()); // Downward-growing stack, so start at the end
this.api = api;
this.currentBlockHeight = api.getCurrentBlockHeight();
this.currentBlockHeight = 0;
this.steps = 0;
this.logger = logger;
}
/** For creating a new machine state */
public MachineState(byte[] creationBytes) {
this(null, null, Arrays.copyOfRange(creationBytes, 0, HEADER_LENGTH));
int expectedLength = HEADER_LENGTH + this.numCodePages * this.constants.CODE_PAGE_SIZE + this.numDataPages + this.constants.DATA_PAGE_SIZE;
if (creationBytes.length != expectedLength)
throw new IllegalArgumentException("Creation bytes length does not match header values");
System.arraycopy(creationBytes, HEADER_LENGTH, this.codeByteBuffer.array(), 0, this.numCodePages * this.constants.CODE_PAGE_SIZE);
System.arraycopy(creationBytes, HEADER_LENGTH + this.numCodePages * this.constants.CODE_PAGE_SIZE, this.dataByteBuffer.array(), 0,
this.numDataPages + this.constants.DATA_PAGE_SIZE);
commonFinalConstruction();
}
/** For creating a new machine state */
public MachineState(API api, LoggerInterface logger, byte[] headerBytes, byte[] codeBytes, byte[] dataBytes) {
this(api, logger, headerBytes);
// XXX: Why don't we simply ByteBuffer.wrap(codeBytes) as they're read-only?
// This would do away with the need to specify numCodePages, save space and provide automatic end-of-code detection during execution thanks to
// ByteBuffer's BufferUnderflowException
if (codeBytes.length > this.numCodePages * CODE_PAGE_SIZE)
if (codeBytes.length > this.numCodePages * this.constants.CODE_PAGE_SIZE)
throw new IllegalArgumentException("Number of code pages too small to hold code bytes");
if (dataBytes.length > this.numDataPages * DATA_PAGE_SIZE)
if (dataBytes.length > this.numDataPages * this.constants.DATA_PAGE_SIZE)
throw new IllegalArgumentException("Number of data pages too small to hold data bytes");
System.arraycopy(codeBytes, 0, this.codeByteBuffer.array(), 0, codeBytes.length);
System.arraycopy(dataBytes, 0, this.dataByteBuffer.array(), 0, dataBytes.length);
commonFinalConstruction();
}
private void commonFinalConstruction() {
this.programCounter = 0;
this.onStopAddress = 0;
this.onErrorAddress = null;
this.isSleeping = false;
this.sleepUntilHeight = null;
this.isStopped = false;
this.isFinished = false;
this.hadFatalError = false;
this.isFrozen = false;
this.frozenBalance = null;
this.isFinished = false;
this.hadFatalError = false;
}
// Getters / setters
// NOTE: Many setters have package-scope (i.e. org.ciyam.at only) to allow changes
// during execution but not by outside classes.
public int getProgramCounter() {
return this.programCounter;
}
public int getOnStopAddress() {
return this.onStopAddress;
}
/* package */ void setOnStopAddress(int address) {
this.onStopAddress = address;
}
public Integer getOnErrorAddress() {
return this.onErrorAddress;
}
/* package */ void setOnErrorAddress(Integer address) {
this.onErrorAddress = address;
}
public boolean getIsSleeping() {
return this.isSleeping;
}
/* package */ void setIsSleeping(boolean isSleeping) {
this.isSleeping = isSleeping;
}
public Integer getSleepUntilHeight() {
return this.sleepUntilHeight;
}
/* package */ void setSleepUntilHeight(Integer address) {
this.sleepUntilHeight = address;
}
public boolean getIsStopped() {
return this.isStopped;
}
/* package */ void setIsStopped(boolean isStopped) {
this.isStopped = isStopped;
}
public boolean getIsFrozen() {
return this.isFrozen;
}
/* package */ void setIsFrozen(boolean isFrozen) {
this.isFrozen = isFrozen;
}
public Long getFrozenBalance() {
return this.frozenBalance;
}
/* package */ void setFrozenBalance(Long frozenBalance) {
this.frozenBalance = frozenBalance;
}
public boolean getIsFinished() {
return this.isFinished;
}
/* package */ void setIsFinished(boolean isFinished) {
this.isFinished = isFinished;
}
public boolean getHadFatalError() {
return this.hadFatalError;
}
/* package */ void setHadFatalError(boolean hadFatalError) {
this.hadFatalError = hadFatalError;
}
// No corresponding setters due to package-scope - see above
public long getA1() {
return this.a1;
}
public long getA2() {
return this.a2;
}
public long getA3() {
return this.a3;
}
public long getA4() {
return this.a4;
}
public long getB1() {
return this.b1;
}
public long getB2() {
return this.b2;
}
public long getB3() {
return this.b3;
}
public long getB4() {
return this.b4;
}
// End of package-scope pseudo-registers
public int getCurrentBlockHeight() {
return this.currentBlockHeight;
}
public int getSteps() {
return this.steps;
}
public API getAPI() {
return this.api;
}
public LoggerInterface getLogger() {
return this.logger;
}
// Serialization
/** For serializing a machine state */
public byte[] toBytes() {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
@ -192,13 +375,13 @@ public class MachineState {
// Call stack length (32bit unsigned int)
int callStackLength = this.callStackByteBuffer.limit() - this.callStackByteBuffer.position();
bytes.write(toByteArray(callStackLength));
// Call stack
// Call stack (only the bytes actually in use)
bytes.write(this.callStackByteBuffer.array(), this.callStackByteBuffer.position(), callStackLength);
// User stack length (32bit unsigned int)
int userStackLength = this.userStackByteBuffer.limit() - this.userStackByteBuffer.position();
bytes.write(toByteArray(userStackLength));
// User stack
// User stack (only the bytes actually in use)
bytes.write(this.userStackByteBuffer.array(), this.userStackByteBuffer.position(), userStackLength);
// Actual state
@ -333,6 +516,34 @@ public class MachineState {
return state;
}
/** Class for pushing/popping boolean flags onto/from an int */
private class Flags {
private int flags;
public Flags() {
flags = 0;
}
public Flags(int value) {
this.flags = value;
}
public void push(boolean flag) {
flags <<= 1;
flags |= flag ? 1 : 0;
}
public boolean pop() {
boolean result = (flags & 1) != 0;
flags >>>= 1;
return result;
}
public int intValue() {
return flags;
}
}
/** Convert int to little-endian byte array */
private byte[] toByteArray(int value) {
return new byte[] { (byte) (value), (byte) (value >> 8), (byte) (value >> 16), (byte) (value >> 24) };
@ -344,32 +555,7 @@ public class MachineState {
(byte) (value >> 48), (byte) (value >> 56) };
}
private void parseHeader() {
ByteBuffer byteBuffer = ByteBuffer.wrap(this.headerBytes);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
this.version = byteBuffer.getShort();
if (this.version < 1)
throw new IllegalArgumentException("Version must be >= 0");
this.reserved = byteBuffer.getShort();
this.numCodePages = byteBuffer.getShort();
if (this.numCodePages < 1)
throw new IllegalArgumentException("Number of code pages must be > 0");
this.numDataPages = byteBuffer.getShort();
if (this.numDataPages < 1)
throw new IllegalArgumentException("Number of data pages must be > 0");
this.numCallStackPages = byteBuffer.getShort();
if (this.numCallStackPages < 0)
throw new IllegalArgumentException("Number of call stack pages must be >= 0");
this.numUserStackPages = byteBuffer.getShort();
if (this.numUserStackPages < 0)
throw new IllegalArgumentException("Number of user stack pages must be >= 0");
}
// Actual execution
public void execute() {
// Set byte buffer position using program counter
@ -382,6 +568,7 @@ public class MachineState {
this.isFrozen = false;
this.frozenBalance = null;
this.steps = 0;
this.currentBlockHeight = api.getCurrentBlockHeight();
while (!this.isSleeping && !this.isStopped && !this.isFinished && !this.isFrozen) {
byte rawOpCode = codeByteBuffer.get();
@ -393,7 +580,12 @@ public class MachineState {
this.logger.debug("[PC: " + String.format("%04x", this.programCounter) + "] " + nextOpCode.name());
nextOpCode.execute(codeByteBuffer, dataByteBuffer, userStackByteBuffer, callStackByteBuffer, this);
// TODO: Request cost from API, apply cost to balance, etc.
// At this point, programCounter is BEFORE opcode (and args).
nextOpCode.execute(this);
// Synchronize programCounter with codeByteBuffer in case of JMPs, branches, etc.
this.programCounter = codeByteBuffer.position();
} catch (ExecutionException e) {
this.logger.debug("Error at PC " + String.format("%04x", this.programCounter) + ": " + e.getMessage());
@ -401,6 +593,8 @@ public class MachineState {
if (this.onErrorAddress == null) {
this.isFinished = true;
this.hadFatalError = true;
// Ask API to refund remaining funds back to AT's creator
this.api.onFatalError(this, e);
break;
}
@ -418,7 +612,7 @@ public class MachineState {
}
}
// public String disassemble(List<String> dataLabels, Map<Integer, String> codeLabels) {
/** Return disassembly of code bytes */
public String disassemble() throws ExecutionException {
String output = "";

File diff suppressed because it is too large Load Diff

View File

@ -1,87 +1,12 @@
import static common.TestUtils.hexToBytes;
import static org.junit.Assert.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.ciyam.at.API;
import org.ciyam.at.ExecutionException;
import org.ciyam.at.MachineState;
import org.ciyam.at.OpCode;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import common.TestAPI;
import common.TestLogger;
import common.ExecutableTest;
public class BranchingOpCodeTests {
public TestLogger logger;
public API api;
public MachineState state;
public ByteBuffer codeByteBuffer;
@BeforeClass
public static void beforeClass() {
Security.insertProviderAt(new BouncyCastleProvider(), 0);
}
@Before
public void beforeTest() {
logger = new TestLogger();
api = new TestAPI();
codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN);
}
@After
public void afterTest() {
codeByteBuffer = null;
api = null;
logger = null;
}
private void execute() {
System.out.println("Starting execution:");
System.out.println("Current block height: " + state.currentBlockHeight);
state.execute();
System.out.println("After execution:");
System.out.println("Steps: " + state.steps);
System.out.println("Program Counter: " + String.format("%04x", state.programCounter));
System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress));
System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress)));
if (state.isSleeping)
System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight);
else
System.out.println("Sleeping: " + state.isSleeping);
System.out.println("Stopped: " + state.isStopped);
System.out.println("Finished: " + state.isFinished);
if (state.hadFatalError)
System.out.println("Finished due to fatal error!");
System.out.println("Frozen: " + state.isFrozen);
}
private void simulate() {
// 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");
byte[] codeBytes = codeByteBuffer.array();
byte[] dataBytes = new byte[0];
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
do {
execute();
// Bump block height
state.currentBlockHeight++;
} while (!state.isFinished);
}
public class BranchingOpCodeTests extends ExecutableTest {
@Test
public void testBZR_DATtrue() throws ExecutionException {
@ -98,11 +23,11 @@ public class BranchingOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 2L, getData(1));
}
@Test
@ -120,11 +45,11 @@ public class BranchingOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 1L, getData(1));
}
@Test
@ -142,11 +67,11 @@ public class BranchingOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 2L, getData(1));
}
@Test
@ -164,11 +89,11 @@ public class BranchingOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(2L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 1L, getData(1));
}
@Test
@ -187,11 +112,11 @@ public class BranchingOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 2L, getData(2));
}
@Test
@ -210,11 +135,11 @@ public class BranchingOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 1L, getData(2));
}
@Test
@ -233,11 +158,11 @@ public class BranchingOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 2L, getData(2));
}
@Test
@ -256,11 +181,11 @@ public class BranchingOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 1L, getData(2));
}
@Test
@ -279,11 +204,11 @@ public class BranchingOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 2L, getData(2));
}
@Test
@ -302,11 +227,11 @@ public class BranchingOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 2L, getData(2));
}
@Test
@ -325,11 +250,11 @@ public class BranchingOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 1L, getData(2));
}
@Test
@ -348,11 +273,11 @@ public class BranchingOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 2L, getData(2));
}
@Test
@ -371,11 +296,11 @@ public class BranchingOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 2L, getData(2));
}
@Test
@ -394,11 +319,11 @@ public class BranchingOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 1L, getData(2));
}
@Test
@ -417,11 +342,11 @@ public class BranchingOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 2L, getData(2));
}
@Test
@ -440,11 +365,11 @@ public class BranchingOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 1L, getData(2));
}
@Test
@ -463,11 +388,11 @@ public class BranchingOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 2L, getData(2));
}
@Test
@ -486,11 +411,11 @@ public class BranchingOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 1L, getData(2));
}
}

View File

@ -1,87 +1,14 @@
import static common.TestUtils.hexToBytes;
import static common.TestUtils.*;
import static org.junit.Assert.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.ciyam.at.API;
import org.ciyam.at.ExecutionException;
import org.ciyam.at.MachineState;
import org.ciyam.at.OpCode;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import common.TestAPI;
import common.TestLogger;
import common.ExecutableTest;
public class CallStackOpCodeTests {
public TestLogger logger;
public API api;
public MachineState state;
public ByteBuffer codeByteBuffer;
@BeforeClass
public static void beforeClass() {
Security.insertProviderAt(new BouncyCastleProvider(), 0);
}
@Before
public void beforeTest() {
logger = new TestLogger();
api = new TestAPI();
codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN);
}
@After
public void afterTest() {
codeByteBuffer = null;
api = null;
logger = null;
}
private void execute() {
System.out.println("Starting execution:");
System.out.println("Current block height: " + state.currentBlockHeight);
state.execute();
System.out.println("After execution:");
System.out.println("Steps: " + state.steps);
System.out.println("Program Counter: " + String.format("%04x", state.programCounter));
System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress));
System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress)));
if (state.isSleeping)
System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight);
else
System.out.println("Sleeping: " + state.isSleeping);
System.out.println("Stopped: " + state.isStopped);
System.out.println("Finished: " + state.isFinished);
if (state.hadFatalError)
System.out.println("Finished due to fatal error!");
System.out.println("Frozen: " + state.isFrozen);
}
private void simulate() {
// 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");
byte[] codeBytes = codeByteBuffer.array();
byte[] dataBytes = new byte[0];
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
do {
execute();
// Bump block height
state.currentBlockHeight++;
} while (!state.isFinished);
}
public class CallStackOpCodeTests extends ExecutableTest {
@Test
public void testJMP_SUB() throws ExecutionException {
@ -96,17 +23,17 @@ public class CallStackOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(4444L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
int expectedCallStackPosition = (state.numCallStackPages - 1) * MachineState.CALL_STACK_PAGE_SIZE;
assertEquals("Call stack pointer incorrect", expectedCallStackPosition, state.callStackByteBuffer.position());
int expectedCallStackPosition = (state.numCallStackPages - 1) * CALL_STACK_PAGE_SIZE;
assertEquals("Call stack pointer incorrect", expectedCallStackPosition, getCallStackPosition());
assertEquals("Return address does not match", returnAddress, state.callStackByteBuffer.getInt(expectedCallStackPosition));
assertEquals("Return address does not match", returnAddress, getCallStackEntry(expectedCallStackPosition));
assertEquals("Data does not match", 4444L, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE));
assertEquals("Data does not match", 4444L, getData(0));
}
@Test
@ -130,19 +57,19 @@ public class CallStackOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(5555L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
int expectedCallStackPosition = (state.numCallStackPages - 1 - 1) * MachineState.CALL_STACK_PAGE_SIZE;
assertEquals("Call stack pointer incorrect", expectedCallStackPosition, state.callStackByteBuffer.position());
int expectedCallStackPosition = (state.numCallStackPages - 1 - 1) * CALL_STACK_PAGE_SIZE;
assertEquals("Call stack pointer incorrect", expectedCallStackPosition, getCallStackPosition());
assertEquals("Return address does not match", returnAddress2, state.callStackByteBuffer.getInt(expectedCallStackPosition));
assertEquals("Return address does not match", returnAddress1, state.callStackByteBuffer.getInt(expectedCallStackPosition + MachineState.ADDRESS_SIZE));
assertEquals("Return address does not match", returnAddress2, getCallStackEntry(expectedCallStackPosition));
assertEquals("Return address does not match", returnAddress1, getCallStackEntry(expectedCallStackPosition + MachineState.ADDRESS_SIZE));
assertEquals("Data does not match", 4444L, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE));
assertEquals("Data does not match", 5555L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
assertEquals("Data does not match", 4444L, getData(0));
assertEquals("Data does not match", 5555L, getData(1));
}
@Test
@ -154,10 +81,10 @@ public class CallStackOpCodeTests {
}
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -174,18 +101,18 @@ public class CallStackOpCodeTests {
codeByteBuffer.put(OpCode.RET_SUB.value);
codeByteBuffer.put(OpCode.FIN_IMD.value); // not reached!
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
int expectedCallStackPosition = (state.numCallStackPages - 1 + 1) * MachineState.CALL_STACK_PAGE_SIZE;
assertEquals("Call stack pointer incorrect", expectedCallStackPosition, state.callStackByteBuffer.position());
int expectedCallStackPosition = (state.numCallStackPages - 1 + 1) * CALL_STACK_PAGE_SIZE;
assertEquals("Call stack pointer incorrect", expectedCallStackPosition, getCallStackPosition());
assertEquals("Return address not cleared", 0L, state.callStackByteBuffer.getInt(expectedCallStackPosition - MachineState.ADDRESS_SIZE));
assertEquals("Return address not cleared", 0L, getCallStackEntry(expectedCallStackPosition - MachineState.ADDRESS_SIZE));
assertEquals("Data does not match", 4444L, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE));
assertEquals("Data does not match", 7777L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
assertEquals("Data does not match", 4444L, getData(0));
assertEquals("Data does not match", 7777L, getData(1));
}
@Test
@ -211,20 +138,20 @@ public class CallStackOpCodeTests {
codeByteBuffer.put(OpCode.RET_SUB.value);
codeByteBuffer.put(OpCode.FIN_IMD.value); // not reached!
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
int expectedCallStackPosition = (state.numCallStackPages - 1 - 1 + 1 + 1) * MachineState.CALL_STACK_PAGE_SIZE;
assertEquals("Call stack pointer incorrect", expectedCallStackPosition, state.callStackByteBuffer.position());
int expectedCallStackPosition = (state.numCallStackPages - 1 - 1 + 1 + 1) * CALL_STACK_PAGE_SIZE;
assertEquals("Call stack pointer incorrect", expectedCallStackPosition, getCallStackPosition());
assertEquals("Return address not cleared", 0L, state.callStackByteBuffer.getInt(expectedCallStackPosition - MachineState.ADDRESS_SIZE));
assertEquals("Return address not cleared", 0L, getCallStackEntry(expectedCallStackPosition - MachineState.ADDRESS_SIZE));
assertEquals("Data does not match", 4444L, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE));
assertEquals("Data does not match", 7777L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
assertEquals("Data does not match", 2222L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertEquals("Data does not match", 3333L, state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
assertEquals("Data does not match", 4444L, getData(0));
assertEquals("Data does not match", 7777L, getData(1));
assertEquals("Data does not match", 2222L, getData(2));
assertEquals("Data does not match", 3333L, getData(3));
}
@Test
@ -232,10 +159,10 @@ public class CallStackOpCodeTests {
codeByteBuffer.put(OpCode.RET_SUB.value);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -246,10 +173,10 @@ public class CallStackOpCodeTests {
codeByteBuffer.put(OpCode.RET_SUB.value);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
}

View File

@ -1,98 +1,23 @@
import static common.TestUtils.hexToBytes;
import static org.junit.Assert.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.ciyam.at.API;
import org.ciyam.at.ExecutionException;
import org.ciyam.at.MachineState;
import org.ciyam.at.OpCode;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import common.TestAPI;
import common.TestLogger;
import common.ExecutableTest;
public class DataOpCodeTests {
public TestLogger logger;
public API api;
public MachineState state;
public ByteBuffer codeByteBuffer;
@BeforeClass
public static void beforeClass() {
Security.insertProviderAt(new BouncyCastleProvider(), 0);
}
@Before
public void beforeTest() {
logger = new TestLogger();
api = new TestAPI();
codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN);
}
@After
public void afterTest() {
codeByteBuffer = null;
api = null;
logger = null;
}
private void execute() {
System.out.println("Starting execution:");
System.out.println("Current block height: " + state.currentBlockHeight);
state.execute();
System.out.println("After execution:");
System.out.println("Steps: " + state.steps);
System.out.println("Program Counter: " + String.format("%04x", state.programCounter));
System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress));
System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress)));
if (state.isSleeping)
System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight);
else
System.out.println("Sleeping: " + state.isSleeping);
System.out.println("Stopped: " + state.isStopped);
System.out.println("Finished: " + state.isFinished);
if (state.hadFatalError)
System.out.println("Finished due to fatal error!");
System.out.println("Frozen: " + state.isFrozen);
}
private void simulate() {
// 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");
byte[] codeBytes = codeByteBuffer.array();
byte[] dataBytes = new byte[0];
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
do {
execute();
// Bump block height
state.currentBlockHeight++;
} while (!state.isFinished);
}
public class DataOpCodeTests extends ExecutableTest {
@Test
public void testSET_VAL() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 2222L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 2222L, getData(2));
}
@Test
@ -100,10 +25,10 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(9999).putLong(2222L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -112,11 +37,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.SET_DAT.value).putInt(1).putInt(2);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 2222L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 2222L, getData(1));
}
@Test
@ -124,10 +49,10 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.SET_DAT.value).putInt(9999).putInt(2);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -135,10 +60,10 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.SET_DAT.value).putInt(1).putInt(9999);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -147,15 +72,14 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.CLR_DAT.value).putInt(2);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
// Check data all zero
state.dataByteBuffer.position(0);
while (state.dataByteBuffer.hasRemaining())
assertEquals((byte) 0, state.dataByteBuffer.get());
for (int i = 0; i < 0x0020; ++i)
assertEquals(0L, getData(i));
}
@Test
@ -163,10 +87,10 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.CLR_DAT.value).putInt(9999);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -175,11 +99,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.INC_DAT.value).putInt(2);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 2222L + 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 2222L + 1L, getData(2));
}
@Test
@ -187,10 +111,10 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.INC_DAT.value).putInt(9999);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -199,11 +123,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.INC_DAT.value).putInt(2);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 0L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 0L, getData(2));
}
@Test
@ -212,11 +136,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.DEC_DAT.value).putInt(2);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 2222L - 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 2222L - 1L, getData(2));
}
@Test
@ -224,9 +148,9 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.DEC_DAT.value).putInt(9999);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
execute(true);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -235,11 +159,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.DEC_DAT.value).putInt(2);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 0xffffffffffffffffL, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 0xffffffffffffffffL, getData(2));
}
@Test
@ -249,11 +173,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.ADD_DAT.value).putInt(2).putInt(3);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 2222L + 3333L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 2222L + 3333L, getData(2));
}
@Test
@ -261,10 +185,10 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.ADD_DAT.value).putInt(9999).putInt(3);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -272,10 +196,10 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.ADD_DAT.value).putInt(2).putInt(9999);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -285,11 +209,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.ADD_DAT.value).putInt(2).putInt(3);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 0x0000000000000098L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 0x0000000000000098L, getData(2));
}
@Test
@ -299,11 +223,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.SUB_DAT.value).putInt(3).putInt(2);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 3333L - 2222L, state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 3333L - 2222L, getData(3));
}
@Test
@ -313,11 +237,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.MUL_DAT.value).putInt(3).putInt(2);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", (3333L * 2222L), state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", (3333L * 2222L), getData(3));
}
@Test
@ -329,11 +253,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.DIV_DAT.value).putInt(3).putInt(2);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", (3333L / 2222L), state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", (3333L / 2222L), getData(3));
}
@Test
@ -354,11 +278,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Error flag not set", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Error flag not set", 1L, getData(1));
}
@Test
@ -368,11 +292,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.BOR_DAT.value).putInt(3).putInt(2);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", (3333L | 2222L), state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", (3333L | 2222L), getData(3));
}
@Test
@ -382,11 +306,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.AND_DAT.value).putInt(3).putInt(2);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", (3333L & 2222L), state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", (3333L & 2222L), getData(3));
}
@Test
@ -396,11 +320,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.XOR_DAT.value).putInt(3).putInt(2);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", (3333L ^ 2222L), state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", (3333L ^ 2222L), getData(3));
}
@Test
@ -409,11 +333,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.NOT_DAT.value).putInt(2);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", ~2222L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", ~2222L, getData(2));
}
@Test
@ -428,11 +352,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.SET_IND.value).putInt(6).putInt(0);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 3333L, state.dataByteBuffer.getLong(6 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 3333L, getData(6));
}
@Test
@ -447,10 +371,10 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.SET_IND.value).putInt(6).putInt(9999);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -465,10 +389,10 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.SET_IND.value).putInt(6).putInt(0);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -484,11 +408,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.SET_IDX.value).putInt(0).putInt(6).putInt(7);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 4444L, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 4444L, getData(0));
}
@Test
@ -504,10 +428,10 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.SET_IDX.value).putInt(0).putInt(9999).putInt(7);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -523,10 +447,10 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.SET_IDX.value).putInt(0).putInt(6).putInt(7);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -542,10 +466,10 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.SET_IDX.value).putInt(0).putInt(6).putInt(7);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -561,10 +485,10 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.SET_IDX.value).putInt(0).putInt(6).putInt(9999);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -579,11 +503,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.IND_DAT.value).putInt(0).putInt(5);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 5555L, state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 5555L, getData(3));
}
@Test
@ -598,10 +522,10 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.SET_IND.value).putInt(9999).putInt(5);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -616,10 +540,10 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.SET_IND.value).putInt(0).putInt(5);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -635,11 +559,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.IDX_DAT.value).putInt(6).putInt(7).putInt(5);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 5555L, state.dataByteBuffer.getLong(4 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 5555L, getData(4));
}
@Test
@ -655,10 +579,10 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.IDX_DAT.value).putInt(9999).putInt(7).putInt(5);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -674,10 +598,10 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.IDX_DAT.value).putInt(6).putInt(7).putInt(5);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -693,10 +617,10 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.IDX_DAT.value).putInt(6).putInt(7).putInt(5);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -712,10 +636,10 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.IDX_DAT.value).putInt(6).putInt(9999).putInt(5);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -725,11 +649,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.MOD_DAT.value).putInt(2).putInt(3);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 2222L % 3333L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 2222L % 3333L, getData(2));
}
@Test
@ -750,11 +674,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Error flag not set", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Error flag not set", 1L, getData(1));
}
@Test
@ -764,11 +688,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.SHL_DAT.value).putInt(2).putInt(3);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 2222L << 3, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 2222L << 3, getData(2));
}
@Test
@ -778,11 +702,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.SHL_DAT.value).putInt(2).putInt(3);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 0L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 0L, getData(2));
}
@Test
@ -792,11 +716,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.SHR_DAT.value).putInt(2).putInt(3);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 2222L >> 3, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 2222L >> 3, getData(2));
}
@Test
@ -806,11 +730,11 @@ public class DataOpCodeTests {
codeByteBuffer.put(OpCode.SHR_DAT.value).putInt(2).putInt(3);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 0L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 0L, getData(2));
}
}

View File

@ -44,28 +44,6 @@ public class DisassemblyTests {
logger = null;
}
private void execute() {
System.out.println("Starting execution:");
System.out.println("Current block height: " + state.currentBlockHeight);
state.execute();
System.out.println("After execution:");
System.out.println("Steps: " + state.steps);
System.out.println("Program Counter: " + String.format("%04x", state.programCounter));
System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress));
System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress)));
if (state.isSleeping)
System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight);
else
System.out.println("Sleeping: " + state.isSleeping);
System.out.println("Stopped: " + state.isStopped);
System.out.println("Finished: " + state.isFinished);
if (state.hadFatalError)
System.out.println("Finished due to fatal error!");
System.out.println("Frozen: " + state.isFrozen);
}
@Test
public void testMD160disassembly() throws ExecutionException {
// MD160 of ffffffffffffffffffffffffffffffffffffffffffffffff is 90e735014ea23aa89190121b229c06d58fc71e83

View File

@ -1,89 +1,15 @@
import static common.TestUtils.hexToBytes;
import static org.junit.Assert.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.ciyam.at.API;
import org.ciyam.at.ExecutionException;
import org.ciyam.at.FunctionCode;
import org.ciyam.at.MachineState;
import org.ciyam.at.OpCode;
import org.ciyam.at.Timestamp;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import common.TestAPI;
import common.TestLogger;
import common.ExecutableTest;
public class FunctionCodeTests {
public TestLogger logger;
public API api;
public MachineState state;
public ByteBuffer codeByteBuffer;
@BeforeClass
public static void beforeClass() {
Security.insertProviderAt(new BouncyCastleProvider(), 0);
}
@Before
public void beforeTest() {
logger = new TestLogger();
api = new TestAPI();
codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN);
}
@After
public void afterTest() {
codeByteBuffer = null;
api = null;
logger = null;
}
private void execute() {
System.out.println("Starting execution:");
System.out.println("Current block height: " + state.currentBlockHeight);
state.execute();
System.out.println("After execution:");
System.out.println("Steps: " + state.steps);
System.out.println("Program Counter: " + String.format("%04x", state.programCounter));
System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress));
System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress)));
if (state.isSleeping)
System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight);
else
System.out.println("Sleeping: " + state.isSleeping);
System.out.println("Stopped: " + state.isStopped);
System.out.println("Finished: " + state.isFinished);
if (state.hadFatalError)
System.out.println("Finished due to fatal error!");
System.out.println("Frozen: " + state.isFrozen);
}
private void simulate() {
// 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");
byte[] codeBytes = codeByteBuffer.array();
byte[] dataBytes = new byte[0];
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
do {
execute();
// Bump block height
state.currentBlockHeight++;
} while (!state.isFinished);
}
public class FunctionCodeTests extends ExecutableTest {
@Test
public void testMD5() throws ExecutionException {
@ -109,11 +35,11 @@ public class FunctionCodeTests {
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("MD5 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("MD5 hashes do not match", 1L, getData(1));
}
@Test
@ -134,11 +60,11 @@ public class FunctionCodeTests {
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("MD5 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("MD5 hashes do not match", 1L, getData(1));
}
@Test
@ -165,11 +91,11 @@ public class FunctionCodeTests {
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("RIPEMD160 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("RIPEMD160 hashes do not match", 1L, getData(1));
}
@Test
@ -192,11 +118,11 @@ public class FunctionCodeTests {
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertEquals("RIPEMD160 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("RIPEMD160 hashes do not match", 1L, getData(1));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
}
@Test
@ -223,11 +149,11 @@ public class FunctionCodeTests {
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("RIPEMD160 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("RIPEMD160 hashes do not match", 1L, getData(1));
}
@Test
@ -252,11 +178,11 @@ public class FunctionCodeTests {
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertEquals("RIPEMD160 hashes do not match", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("RIPEMD160 hashes do not match", 1L, getData(1));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
}
@Test
@ -266,11 +192,11 @@ public class FunctionCodeTests {
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GENERATE_RANDOM_USING_TX_IN_A.value).putInt(1);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(false);
assertNotEquals("Random wasn't generated", 0L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertNotEquals("Random wasn't generated", 0L, getData(1));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
}
@Test
@ -278,10 +204,10 @@ public class FunctionCodeTests {
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort((short) 0xaaaa);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -290,10 +216,10 @@ public class FunctionCodeTests {
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort((short) 0x0501).putInt(0);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
}
@Test
@ -302,10 +228,10 @@ public class FunctionCodeTests {
codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.value).putShort((short) 0x0501).putInt(0).putInt(0); // Wrong OPCODE for function
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
}

View File

@ -1,88 +1,13 @@
import static common.TestUtils.hexToBytes;
import static org.junit.Assert.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.ciyam.at.API;
import org.ciyam.at.ExecutionException;
import org.ciyam.at.FunctionCode;
import org.ciyam.at.MachineState;
import org.ciyam.at.OpCode;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import common.TestAPI;
import common.TestLogger;
import common.ExecutableTest;
public class MiscTests {
public TestLogger logger;
public API api;
public MachineState state;
public ByteBuffer codeByteBuffer;
@BeforeClass
public static void beforeClass() {
Security.insertProviderAt(new BouncyCastleProvider(), 0);
}
@Before
public void beforeTest() {
logger = new TestLogger();
api = new TestAPI();
codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN);
}
@After
public void afterTest() {
codeByteBuffer = null;
api = null;
logger = null;
}
private void execute() {
System.out.println("Starting execution:");
System.out.println("Current block height: " + state.currentBlockHeight);
state.execute();
System.out.println("After execution:");
System.out.println("Steps: " + state.steps);
System.out.println("Program Counter: " + String.format("%04x", state.programCounter));
System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress));
System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress)));
if (state.isSleeping)
System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight);
else
System.out.println("Sleeping: " + state.isSleeping);
System.out.println("Stopped: " + state.isStopped);
System.out.println("Finished: " + state.isFinished);
if (state.hadFatalError)
System.out.println("Finished due to fatal error!");
System.out.println("Frozen: " + state.isFrozen);
}
private void simulate() {
// 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");
byte[] codeBytes = codeByteBuffer.array();
byte[] dataBytes = new byte[0];
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
do {
execute();
// Bump block height
state.currentBlockHeight++;
} while (!state.isFinished);
}
public class MiscTests extends ExecutableTest {
@Test
public void testSimpleCode() throws ExecutionException {
@ -91,21 +16,21 @@ public class MiscTests {
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.ECHO.value).putInt(0);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", testValue, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", testValue, getData(0));
}
@Test
public void testInvalidOpCode() throws ExecutionException {
codeByteBuffer.put((byte) 0xdd);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
}

View File

@ -1,102 +1,27 @@
import static common.TestUtils.hexToBytes;
import static org.junit.Assert.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.ciyam.at.API;
import org.ciyam.at.ExecutionException;
import org.ciyam.at.MachineState;
import org.ciyam.at.OpCode;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import common.TestAPI;
import common.TestLogger;
import common.ExecutableTest;
public class OpCodeTests {
public TestLogger logger;
public API api;
public MachineState state;
public ByteBuffer codeByteBuffer;
@BeforeClass
public static void beforeClass() {
Security.insertProviderAt(new BouncyCastleProvider(), 0);
}
@Before
public void beforeTest() {
logger = new TestLogger();
api = new TestAPI();
codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN);
}
@After
public void afterTest() {
codeByteBuffer = null;
api = null;
logger = null;
}
private void execute() {
System.out.println("Starting execution:");
System.out.println("Current block height: " + state.currentBlockHeight);
state.execute();
System.out.println("After execution:");
System.out.println("Steps: " + state.steps);
System.out.println("Program Counter: " + String.format("%04x", state.programCounter));
System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress));
System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress)));
if (state.isSleeping)
System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight);
else
System.out.println("Sleeping: " + state.isSleeping);
System.out.println("Stopped: " + state.isStopped);
System.out.println("Finished: " + state.isFinished);
if (state.hadFatalError)
System.out.println("Finished due to fatal error!");
System.out.println("Frozen: " + state.isFrozen);
}
private void simulate() {
// 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");
byte[] codeBytes = codeByteBuffer.array();
byte[] dataBytes = new byte[0];
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
do {
execute();
// Bump block height
state.currentBlockHeight++;
} while (!state.isFinished && !state.isFrozen && !state.isSleeping && !state.isStopped);
}
public class OpCodeTests extends ExecutableTest {
@Test
public void testNOP() throws ExecutionException {
codeByteBuffer.put(OpCode.NOP.value);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
// Check data unchanged
state.dataByteBuffer.position(0);
while (state.dataByteBuffer.hasRemaining())
assertEquals((byte) 0, state.dataByteBuffer.get());
for (int i = 0; i < 0x0020; ++i)
assertEquals(0L, getData(i));
}
@Test
@ -111,11 +36,11 @@ public class OpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(2L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Data does not match", 2L, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Data does not match", 2L, getData(0));
}
@Test
@ -126,12 +51,12 @@ public class OpCodeTests {
codeByteBuffer.put(OpCode.SLP_DAT.value).putInt(0);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isSleeping);
assertFalse(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Sleep-until block height incorrect", blockHeight, state.dataByteBuffer.getLong(0 * MachineState.VALUE_SIZE));
assertTrue(state.getIsSleeping());
assertFalse(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Sleep-until block height incorrect", blockHeight, getData(0));
}
@Test
@ -140,11 +65,11 @@ public class OpCodeTests {
codeByteBuffer.put(OpCode.FIZ_DAT.value).putInt(0);
codeByteBuffer.put(OpCode.SLP_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.isSleeping);
assertFalse(state.hadFatalError);
assertTrue(state.getIsFinished());
assertFalse(state.getIsSleeping());
assertFalse(state.getHadFatalError());
}
@Test
@ -153,11 +78,11 @@ public class OpCodeTests {
codeByteBuffer.put(OpCode.FIZ_DAT.value).putInt(0);
codeByteBuffer.put(OpCode.SLP_IMD.value);
simulate();
execute(true);
assertFalse(state.isFinished);
assertTrue(state.isSleeping);
assertFalse(state.hadFatalError);
assertFalse(state.getIsFinished());
assertTrue(state.getIsSleeping());
assertFalse(state.getHadFatalError());
}
@Test
@ -168,12 +93,12 @@ public class OpCodeTests {
codeByteBuffer.put(OpCode.STZ_DAT.value).putInt(0);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isStopped);
assertFalse(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Program counter incorrect", stopAddress, state.programCounter);
assertTrue(state.getIsStopped());
assertFalse(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Program counter incorrect", stopAddress, state.getProgramCounter());
}
@Test
@ -183,11 +108,11 @@ public class OpCodeTests {
codeByteBuffer.put(OpCode.STZ_DAT.value).putInt(0);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertFalse(state.isStopped);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertFalse(state.getIsStopped());
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
}
@Test
@ -195,11 +120,11 @@ public class OpCodeTests {
codeByteBuffer.put(OpCode.FIN_IMD.value);
codeByteBuffer.put(OpCode.STP_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.isStopped);
assertFalse(state.hadFatalError);
assertTrue(state.getIsFinished());
assertFalse(state.getIsStopped());
assertFalse(state.getHadFatalError());
}
@Test
@ -210,12 +135,12 @@ public class OpCodeTests {
codeByteBuffer.put(OpCode.STP_IMD.value);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isStopped);
assertFalse(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Program counter incorrect", stopAddress, state.programCounter);
assertTrue(state.getIsStopped());
assertFalse(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Program counter incorrect", stopAddress, state.getProgramCounter());
}
@Test
@ -224,12 +149,12 @@ public class OpCodeTests {
int nextAddress = codeByteBuffer.position();
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isSleeping);
assertFalse(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Program counter incorrect", nextAddress, state.programCounter);
assertTrue(state.getIsSleeping());
assertFalse(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Program counter incorrect", nextAddress, state.getProgramCounter());
}
@Test
@ -250,11 +175,11 @@ public class OpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(1L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Error flag not set", 1L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Error flag not set", 1L, getData(2));
}
@Test
@ -267,11 +192,11 @@ public class OpCodeTests {
int expectedStopAddress = codeByteBuffer.position();
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertEquals(expectedStopAddress, state.onStopAddress);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals(expectedStopAddress, state.getOnStopAddress());
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
}
@Test
@ -282,11 +207,11 @@ public class OpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000022222222"));
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertEquals(expectedStopAddress, state.onStopAddress);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals(expectedStopAddress, state.getOnStopAddress());
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
}
}

View File

@ -5,7 +5,6 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import org.ciyam.at.API;
import org.ciyam.at.ExecutionException;
import org.ciyam.at.FunctionCode;
import org.ciyam.at.MachineState;
@ -20,7 +19,7 @@ import common.TestLogger;
public class SerializationTests {
public TestLogger logger;
public API api;
public TestAPI api;
public MachineState state;
public ByteBuffer codeByteBuffer;
@ -53,7 +52,7 @@ public class SerializationTests {
state = MachineState.fromBytes(api, logger, savedState);
// Pretend we're on next block
state.currentBlockHeight++;
api.bumpCurrentBlockHeight();
return executeAndCheck(state);
}
@ -79,9 +78,9 @@ public class SerializationTests {
simulate();
assertEquals(0x0e, (int) state.onStopAddress);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals(0x0e, (int) state.getOnStopAddress());
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
}
@Test
@ -98,9 +97,9 @@ public class SerializationTests {
byte[] savedState = simulate();
assertEquals(0x0e, (int) state.onStopAddress);
assertTrue(state.isStopped);
assertFalse(state.hadFatalError);
assertEquals(0x0e, (int) state.getOnStopAddress());
assertTrue(state.getIsStopped());
assertFalse(state.getHadFatalError());
savedState = continueSimulation(savedState);
savedState = continueSimulation(savedState);

View File

@ -65,9 +65,6 @@ public class TestACCT {
private byte[] continueSimulation(byte[] savedState) {
state = MachineState.fromBytes(api, logger, savedState);
// Pretend we're on next block
state.currentBlockHeight++;
return executeAndCheck(state);
}
@ -208,8 +205,8 @@ public class TestACCT {
byte[] savedState = simulate();
while (!state.isFinished) {
((ACCTAPI) state.api).generateNextBlock(secret);
while (!state.getIsFinished()) {
((ACCTAPI) state.getAPI()).generateNextBlock(secret);
savedState = continueSimulation(savedState);
}

View File

@ -1,87 +1,13 @@
import static common.TestUtils.hexToBytes;
import static common.TestUtils.*;
import static org.junit.Assert.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.ciyam.at.API;
import org.ciyam.at.ExecutionException;
import org.ciyam.at.MachineState;
import org.ciyam.at.OpCode;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import common.TestAPI;
import common.TestLogger;
import common.ExecutableTest;
public class UserStackOpCodeTests {
public TestLogger logger;
public API api;
public MachineState state;
public ByteBuffer codeByteBuffer;
@BeforeClass
public static void beforeClass() {
Security.insertProviderAt(new BouncyCastleProvider(), 0);
}
@Before
public void beforeTest() {
logger = new TestLogger();
api = new TestAPI();
codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN);
}
@After
public void afterTest() {
codeByteBuffer = null;
api = null;
logger = null;
}
private void execute() {
System.out.println("Starting execution:");
System.out.println("Current block height: " + state.currentBlockHeight);
state.execute();
System.out.println("After execution:");
System.out.println("Steps: " + state.steps);
System.out.println("Program Counter: " + String.format("%04x", state.programCounter));
System.out.println("Stop Address: " + String.format("%04x", state.onStopAddress));
System.out.println("Error Address: " + (state.onErrorAddress == null ? "not set" : String.format("%04x", state.onErrorAddress)));
if (state.isSleeping)
System.out.println("Sleeping until current block height (" + state.currentBlockHeight + ") reaches " + state.sleepUntilHeight);
else
System.out.println("Sleeping: " + state.isSleeping);
System.out.println("Stopped: " + state.isStopped);
System.out.println("Finished: " + state.isFinished);
if (state.hadFatalError)
System.out.println("Finished due to fatal error!");
System.out.println("Frozen: " + state.isFrozen);
}
private void simulate() {
// 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");
byte[] codeBytes = codeByteBuffer.array();
byte[] dataBytes = new byte[0];
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
do {
execute();
// Bump block height
state.currentBlockHeight++;
} while (!state.isFinished);
}
public class UserStackOpCodeTests extends ExecutableTest {
@Test
public void testPSH_DAT() throws ExecutionException {
@ -89,14 +15,14 @@ public class UserStackOpCodeTests {
codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(0);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
int expectedUserStackPosition = (state.numUserStackPages - 1) * MachineState.USER_STACK_PAGE_SIZE;
assertEquals("User stack pointer incorrect", expectedUserStackPosition, state.userStackByteBuffer.position());
assertEquals("Data does not match", 4444L, state.userStackByteBuffer.getLong(expectedUserStackPosition));
int expectedUserStackPosition = (state.numUserStackPages - 1) * USER_STACK_PAGE_SIZE;
assertEquals("User stack pointer incorrect", expectedUserStackPosition, getUserStackPosition());
assertEquals("Data does not match", 4444L, getUserStackEntry(expectedUserStackPosition));
}
@Test
@ -107,14 +33,14 @@ public class UserStackOpCodeTests {
codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(1);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
int expectedUserStackPosition = (state.numUserStackPages - 2) * MachineState.USER_STACK_PAGE_SIZE;
assertEquals("User stack pointer incorrect", expectedUserStackPosition, state.userStackByteBuffer.position());
assertEquals("Data does not match", 3333L, state.userStackByteBuffer.getLong(expectedUserStackPosition));
int expectedUserStackPosition = (state.numUserStackPages - 2) * USER_STACK_PAGE_SIZE;
assertEquals("User stack pointer incorrect", expectedUserStackPosition, getUserStackPosition());
assertEquals("Data does not match", 3333L, getUserStackEntry(expectedUserStackPosition));
}
@Test
@ -126,10 +52,10 @@ public class UserStackOpCodeTests {
}
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -150,11 +76,11 @@ public class UserStackOpCodeTests {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertEquals("Error flag not set", 1L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
assertEquals("Error flag not set", 1L, getData(1));
}
@Test
@ -164,15 +90,16 @@ public class UserStackOpCodeTests {
codeByteBuffer.put(OpCode.POP_DAT.value).putInt(1);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
int expectedUserStackPosition = (state.numUserStackPages - 1 + 1) * MachineState.USER_STACK_PAGE_SIZE;
assertEquals("User stack pointer incorrect", expectedUserStackPosition, state.userStackByteBuffer.position());
assertEquals("Data does not match", 4444L, state.dataByteBuffer.getLong(1 * MachineState.VALUE_SIZE));
assertEquals("Stack entry not cleared", 0L, state.userStackByteBuffer.getLong(expectedUserStackPosition - MachineState.VALUE_SIZE));
int expectedUserStackPosition = (state.numUserStackPages - 1 + 1) * USER_STACK_PAGE_SIZE;
assertEquals("User stack pointer incorrect", expectedUserStackPosition, getUserStackPosition());
assertEquals("Data does not match", 4444L, getData(1));
// Following test is not applicable when using serialized state:
// assertEquals("Stack entry not cleared", 0L, getUserStackEntry(expectedUserStackPosition - MachineState.VALUE_SIZE));
}
@Test
@ -185,15 +112,15 @@ public class UserStackOpCodeTests {
codeByteBuffer.put(OpCode.POP_DAT.value).putInt(3);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertFalse(state.hadFatalError);
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
int expectedUserStackPosition = (state.numUserStackPages - 1 - 1 + 1 + 1) * MachineState.USER_STACK_PAGE_SIZE;
assertEquals("User stack pointer incorrect", expectedUserStackPosition, state.userStackByteBuffer.position());
assertEquals("Data does not match", 3333L, state.dataByteBuffer.getLong(2 * MachineState.VALUE_SIZE));
assertEquals("Data does not match", 4444L, state.dataByteBuffer.getLong(3 * MachineState.VALUE_SIZE));
int expectedUserStackPosition = (state.numUserStackPages - 1 - 1 + 1 + 1) * USER_STACK_PAGE_SIZE;
assertEquals("User stack pointer incorrect", expectedUserStackPosition, getUserStackPosition());
assertEquals("Data does not match", 3333L, getData(2));
assertEquals("Data does not match", 4444L, getData(3));
}
@Test
@ -201,10 +128,10 @@ public class UserStackOpCodeTests {
codeByteBuffer.put(OpCode.POP_DAT.value).putInt(0);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
@Test
@ -215,10 +142,10 @@ public class UserStackOpCodeTests {
codeByteBuffer.put(OpCode.POP_DAT.value).putInt(2);
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
execute(true);
assertTrue(state.isFinished);
assertTrue(state.hadFatalError);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
}

View File

@ -14,7 +14,7 @@ import org.ciyam.at.IllegalFunctionCodeException;
import org.ciyam.at.MachineState;
import org.ciyam.at.Timestamp;
public class ACCTAPI implements API {
public class ACCTAPI extends API {
private class Account {
public String address;
@ -130,6 +130,7 @@ public class ACCTAPI implements API {
}
/** Convert long to little-endian byte array */
@SuppressWarnings("unused")
private byte[] toByteArray(long value) {
return new byte[] { (byte) (value), (byte) (value >> 8), (byte) (value >> 16), (byte) (value >> 24), (byte) (value >> 32), (byte) (value >> 40),
(byte) (value >> 48), (byte) (value >> 56) };
@ -161,10 +162,10 @@ public class ACCTAPI implements API {
@Override
public void putPreviousBlockHashInA(MachineState state) {
state.a1 = this.blockchain.size() - 1;
state.a2 = state.a1;
state.a3 = state.a1;
state.a4 = state.a1;
this.setA1(state, this.blockchain.size() - 1);
this.setA2(state, state.getA1());
this.setA3(state, state.getA1());
this.setA4(state, state.getA1());
}
@Override
@ -191,10 +192,10 @@ public class ACCTAPI implements API {
System.out.println("Found transaction at height " + blockHeight + " sequence " + transactionSequence);
// Generate pseudo-hash of transaction
state.a1 = new Timestamp(blockHeight, transactionSequence).longValue();
state.a2 = state.a1;
state.a3 = state.a1;
state.a4 = state.a1;
this.setA1(state, new Timestamp(blockHeight, transactionSequence).longValue());
this.setA2(state, state.getA1());
this.setA3(state, state.getA1());
this.setA4(state, state.getA1());
return;
}
@ -202,15 +203,15 @@ public class ACCTAPI implements API {
}
// Nothing found
state.a1 = 0L;
state.a2 = 0L;
state.a3 = 0L;
state.a4 = 0L;
this.setA1(state, 0L);
this.setA2(state, 0L);
this.setA3(state, 0L);
this.setA4(state, 0L);
}
@Override
public long getTypeFromTransactionInA(MachineState state) {
Timestamp timestamp = new Timestamp(state.a1);
Timestamp timestamp = new Timestamp(state.getA1());
Block block = this.blockchain.get(timestamp.blockHeight - 1);
Transaction transaction = block.transactions.get(timestamp.transactionSequence);
return transaction.txType;
@ -218,7 +219,7 @@ public class ACCTAPI implements API {
@Override
public long getAmountFromTransactionInA(MachineState state) {
Timestamp timestamp = new Timestamp(state.a1);
Timestamp timestamp = new Timestamp(state.getA1());
Block block = this.blockchain.get(timestamp.blockHeight - 1);
Transaction transaction = block.transactions.get(timestamp.transactionSequence);
return transaction.amount;
@ -227,7 +228,7 @@ public class ACCTAPI implements API {
@Override
public long getTimestampFromTransactionInA(MachineState state) {
// Transaction hash in A is actually just 4 copies of transaction's "timestamp"
Timestamp timestamp = new Timestamp(state.a1);
Timestamp timestamp = new Timestamp(state.getA1());
return timestamp.longValue();
}
@ -239,33 +240,33 @@ public class ACCTAPI implements API {
@Override
public void putMessageFromTransactionInAIntoB(MachineState state) {
Timestamp timestamp = new Timestamp(state.a1);
Timestamp timestamp = new Timestamp(state.getA1());
Block block = this.blockchain.get(timestamp.blockHeight - 1);
Transaction transaction = block.transactions.get(timestamp.transactionSequence);
state.b1 = transaction.message[0];
state.b2 = transaction.message[1];
state.b3 = transaction.message[2];
state.b4 = transaction.message[3];
this.setB1(state, transaction.message[0]);
this.setB2(state, transaction.message[1]);
this.setB3(state, transaction.message[2]);
this.setB4(state, transaction.message[3]);
}
@Override
public void putAddressFromTransactionInAIntoB(MachineState state) {
Timestamp timestamp = new Timestamp(state.a1);
Timestamp timestamp = new Timestamp(state.getA1());
Block block = this.blockchain.get(timestamp.blockHeight - 1);
Transaction transaction = block.transactions.get(timestamp.transactionSequence);
state.b1 = transaction.creator.charAt(0);
state.b2 = state.b1;
state.b3 = state.b1;
state.b4 = state.b1;
this.setB1(state, transaction.creator.charAt(0));
this.setB2(state, state.getB1());
this.setB3(state, state.getB1());
this.setB4(state, state.getB1());
}
@Override
public void putCreatorAddressIntoB(MachineState state) {
// Dummy creator
state.b1 = "C".charAt(0);
state.b2 = state.b1;
state.b3 = state.b1;
state.b4 = state.b1;
this.setB1(state, "C".charAt(0));
this.setB2(state, state.getB1());
this.setB3(state, state.getB1());
this.setB4(state, state.getB1());
}
@Override
@ -280,7 +281,7 @@ public class ACCTAPI implements API {
@Override
public void payAmountToB(long value1, MachineState state) {
char firstChar = String.format("%c", state.b1).charAt(0);
char firstChar = String.format("%c", state.getB1()).charAt(0);
Account recipient = this.accounts.values().stream().filter((account) -> account.address.charAt(0) == firstChar).findFirst().get();
recipient.balance += value1;
System.out.println("Paid " + value1 + " to " + recipient.address + ", their balance now: " + recipient.balance);

View File

@ -0,0 +1,124 @@
package common;
import static common.TestUtils.hexToBytes;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.ciyam.at.MachineState;
import org.junit.After;
import org.junit.Before;
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 CALL_STACK_OFFSET = DATA_OFFSET + 0x0020 * 8;
public TestLogger logger;
public TestAPI api;
public MachineState state;
public ByteBuffer codeByteBuffer;
public ByteBuffer stateByteBuffer;
public int callStackSize;
public int userStackOffset;
public int userStackSize;
@BeforeClass
public static void beforeClass() {
Security.insertProviderAt(new BouncyCastleProvider(), 0);
}
@Before
public void beforeTest() {
logger = new TestLogger();
api = new TestAPI();
codeByteBuffer = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN);
stateByteBuffer = null;
}
@After
public void afterTest() {
stateByteBuffer = null;
codeByteBuffer = null;
api = null;
logger = null;
}
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");
byte[] codeBytes = codeByteBuffer.array();
byte[] dataBytes = new byte[0];
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
do {
System.out.println("Starting execution:");
System.out.println("Current block height: " + api.getCurrentBlockHeight());
// Actual execution
state.execute();
System.out.println("After execution:");
System.out.println("Steps: " + state.getSteps());
System.out.println("Program Counter: " + String.format("%04x", state.getProgramCounter()));
System.out.println("Stop Address: " + String.format("%04x", state.getOnStopAddress()));
System.out.println("Error Address: " + (state.getOnErrorAddress() == null ? "not set" : String.format("%04x", state.getOnErrorAddress())));
if (state.getIsSleeping())
System.out.println("Sleeping until current block height (" + state.getCurrentBlockHeight() + ") reaches " + state.getSleepUntilHeight());
else
System.out.println("Sleeping: " + state.getIsSleeping());
System.out.println("Stopped: " + state.getIsStopped());
System.out.println("Finished: " + state.getIsFinished());
if (state.getHadFatalError())
System.out.println("Finished due to fatal error!");
System.out.println("Frozen: " + state.getIsFrozen());
// Bump block height
api.bumpCurrentBlockHeight();
} while (!onceOnly && !state.getIsFinished());
// Ready for diagnosis
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
stateByteBuffer = ByteBuffer.wrap(stateBytes).order(ByteOrder.LITTLE_ENDIAN);
callStackSize = stateByteBuffer.getInt(CALL_STACK_OFFSET);
userStackOffset = CALL_STACK_OFFSET + 4 + callStackSize;
userStackSize = stateByteBuffer.getInt(userStackOffset);
}
protected long getData(int address) {
int index = DATA_OFFSET + address * MachineState.VALUE_SIZE;
return stateByteBuffer.getLong(index);
}
protected int getCallStackPosition() {
return 0x0010 * MachineState.ADDRESS_SIZE - callStackSize;
}
protected int getCallStackEntry(int address) {
int index = CALL_STACK_OFFSET + 4 + address - 0x0010 * MachineState.ADDRESS_SIZE + callStackSize;
return stateByteBuffer.getInt(index);
}
protected int getUserStackPosition() {
return 0x0010 * MachineState.VALUE_SIZE - userStackSize;
}
protected long getUserStackEntry(int address) {
int index = userStackOffset + 4 + address - 0x0010 * MachineState.VALUE_SIZE + userStackSize;
return stateByteBuffer.getLong(index);
}
}

View File

@ -7,13 +7,23 @@ import org.ciyam.at.IllegalFunctionCodeException;
import org.ciyam.at.MachineState;
import org.ciyam.at.Timestamp;
public class TestAPI implements API {
public class TestAPI extends API {
private static final int BLOCK_PERIOD = 10 * 60; // average period between blocks in seconds
private int currentBlockHeight;
public TestAPI() {
this.currentBlockHeight = 10;
}
public void bumpCurrentBlockHeight() {
++this.currentBlockHeight;
}
@Override
public int getCurrentBlockHeight() {
return 10;
return this.currentBlockHeight;
}
@Override
@ -23,19 +33,19 @@ public class TestAPI implements API {
@Override
public void putPreviousBlockHashInA(MachineState state) {
state.a1 = 9L;
state.a2 = 9L;
state.a3 = 9L;
state.a4 = 9L;
this.setA1(state, 9L);
this.setA2(state, 9L);
this.setA3(state, 9L);
this.setA4(state, 9L);
}
@Override
public void putTransactionAfterTimestampInA(Timestamp timestamp, MachineState state) {
// Cycle through transactions: 1 -> 2 -> 3 -> 0 -> 1 ...
state.a1 = (timestamp.transactionSequence + 1) % 4;
state.a2 = state.a1;
state.a3 = state.a1;
state.a4 = state.a1;
this.setA1(state, (timestamp.transactionSequence + 1) % 4);
this.setA2(state, state.getA1());
this.setA3(state, state.getA1());
this.setA4(state, state.getA1());
}
@Override
@ -55,13 +65,13 @@ public class TestAPI implements API {
@Override
public long generateRandomUsingTransactionInA(MachineState state) {
if (state.steps != 0) {
if (state.getSteps() != 0) {
// First call
System.out.println("generateRandomUsingTransactionInA: first call - sleeping");
// Perform init?
// first-call initialization would go here
state.isSleeping = true;
this.setIsSleeping(state, true);
return 0L; // not used
} else {
@ -69,34 +79,34 @@ public class TestAPI implements API {
System.out.println("generateRandomUsingTransactionInA: second call - returning random");
// HASH(A and new block hash)
return (state.a1 ^ 9L) << 3 ^ (state.a2 ^ 9L) << 12 ^ (state.a3 ^ 9L) << 5 ^ (state.a4 ^ 9L);
return (state.getA1() ^ 9L) << 3 ^ (state.getA2() ^ 9L) << 12 ^ (state.getA3() ^ 9L) << 5 ^ (state.getA4() ^ 9L);
}
}
@Override
public void putMessageFromTransactionInAIntoB(MachineState state) {
state.b1 = state.a4;
state.b2 = state.a3;
state.b3 = state.a2;
state.b4 = state.a1;
this.setB1(state, state.getA4());
this.setB2(state, state.getA3());
this.setB3(state, state.getA2());
this.setB4(state, state.getA1());
}
@Override
public void putAddressFromTransactionInAIntoB(MachineState state) {
// Dummy address
state.b1 = 0xaaaaaaaaaaaaaaaaL;
state.b2 = 0xaaaaaaaaaaaaaaaaL;
state.b3 = 0xaaaaaaaaaaaaaaaaL;
state.b4 = 0xaaaaaaaaaaaaaaaaL;
this.setB1(state, 0xaaaaaaaaaaaaaaaaL);
this.setB2(state, 0xaaaaaaaaaaaaaaaaL);
this.setB3(state, 0xaaaaaaaaaaaaaaaaL);
this.setB4(state, 0xaaaaaaaaaaaaaaaaL);
}
@Override
public void putCreatorAddressIntoB(MachineState state) {
// Dummy creator
state.b1 = 0xccccccccccccccccL;
state.b2 = 0xccccccccccccccccL;
state.b3 = 0xccccccccccccccccL;
state.b4 = 0xccccccccccccccccL;
this.setB1(state, 0xccccccccccccccccL);
this.setB2(state, 0xccccccccccccccccL);
this.setB3(state, 0xccccccccccccccccL);
this.setB4(state, 0xccccccccccccccccL);
}
@Override

View File

@ -2,8 +2,16 @@ package common;
import java.math.BigInteger;
import org.ciyam.at.MachineState;
public class TestUtils {
// v3 constants replicated due to private cope in MachineState
public static final int CODE_PAGE_SIZE = 1;
public static final int DATA_PAGE_SIZE = MachineState.VALUE_SIZE;
public static final int CALL_STACK_PAGE_SIZE = MachineState.ADDRESS_SIZE;
public static final int USER_STACK_PAGE_SIZE = MachineState.VALUE_SIZE;
public static byte[] hexToBytes(String hex) {
byte[] output = new byte[hex.length() / 2];
byte[] converted = new BigInteger("00" + hex, 16).toByteArray();