Browse Source

New OpCode.compile() to simplify compilation, with checking!

OpCode.compile(...) takes varargs as necessary, and returns byte[].
It will complain if the number of args is incorrect, or if any arg
cannot be coerced into the correct form (byte/int/short/long).
It will also complain if you try to use a branch offset that is too
big to fit into byte.

Two-pass compilation support added with OpCode.calcOffset(ByteBuffer, Integer)
where Integer is null during the first pass, but also set to ByteBuffer.position()
during first pass, so proper value can be calculated during second pass.

MachineState.disassemble() changed into a public static method,
requiring far less effort to call.

Checking of branch target bounds now done by each OpCode as
doing it in OpCodeParam wasn't reliable as PC wasn't available.

Yet more visibility tightening.

Improved comments.

Yet more tests.
master
catbref 5 years ago
parent
commit
a58b4d4bec
  1. 21
      Java/src/main/java/org/ciyam/at/CompilationException.java
  2. 7
      Java/src/main/java/org/ciyam/at/MachineState.java
  3. 57
      Java/src/main/java/org/ciyam/at/OpCode.java
  4. 79
      Java/src/main/java/org/ciyam/at/OpCodeParam.java
  5. 19
      Java/src/main/java/org/ciyam/at/Utils.java
  6. 287
      Java/src/test/java/org/ciyam/at/CompileTests.java
  7. 18
      Java/src/test/java/org/ciyam/at/DisassemblyTests.java
  8. 84
      Java/src/test/java/org/ciyam/at/SerializationTests.java
  9. 8
      Java/src/test/java/org/ciyam/at/test/TestUtils.java

21
Java/src/main/java/org/ciyam/at/CompilationException.java

@ -0,0 +1,21 @@
package org.ciyam.at;
@SuppressWarnings("serial")
public class CompilationException extends ExecutionException {
public CompilationException() {
}
public CompilationException(String message) {
super(message);
}
public CompilationException(Throwable cause) {
super(cause);
}
public CompilationException(String message, Throwable cause) {
super(message, cause);
}
}

7
Java/src/main/java/org/ciyam/at/MachineState.java

@ -762,10 +762,11 @@ public class MachineState {
}
/** Return disassembly of code bytes */
public String disassemble() throws ExecutionException {
public static String disassemble(byte[] codeBytes, int dataBufferLength) throws ExecutionException {
StringBuilder output = new StringBuilder();
codeByteBuffer.position(0);
ByteBuffer codeByteBuffer = ByteBuffer.wrap(codeBytes);
ByteBuffer dataByteBuffer = ByteBuffer.allocate(dataBufferLength);
while (codeByteBuffer.hasRemaining()) {
byte rawOpCode = codeByteBuffer.get();
@ -779,7 +780,7 @@ public class MachineState {
if (output.length() != 0)
output.append("\n");
output.append(String.format("[PC: %04x] %s", codeByteBuffer.position() - 1,nextOpCode.disassemble(codeByteBuffer, dataByteBuffer)));
output.append(String.format("[PC: %04x] %s", codeByteBuffer.position() - 1, nextOpCode.disassemble(codeByteBuffer, dataByteBuffer)));
}
return output.toString();

57
Java/src/main/java/org/ciyam/at/OpCode.java

@ -457,10 +457,7 @@ public enum OpCode {
int address = (int) args[0];
byte offset = (byte) args[1];
int branchTarget = state.getProgramCounter() + offset;
if (branchTarget < 0 || branchTarget >= state.codeByteBuffer.limit())
throw new InvalidAddressException("branch target out of bounds");
int branchTarget = calculateBranchTarget(state, offset);
long value = state.dataByteBuffer.getLong(address);
@ -480,10 +477,7 @@ public enum OpCode {
int address = (int) args[0];
byte offset = (byte) args[1];
int branchTarget = state.getProgramCounter() + offset;
if (branchTarget < 0 || branchTarget >= state.codeByteBuffer.limit())
throw new InvalidAddressException("branch target out of bounds");
int branchTarget = calculateBranchTarget(state, offset);
long value = state.dataByteBuffer.getLong(address);
@ -875,7 +869,7 @@ public enum OpCode {
*/
protected abstract void executeWithParams(MachineState state, Object... args) throws ExecutionException;
public void execute(MachineState state) throws ExecutionException {
/* package */ void execute(MachineState state) throws ExecutionException {
List<Object> args = new ArrayList<>();
for (OpCodeParam param : this.params)
@ -884,6 +878,36 @@ public enum OpCode {
this.executeWithParams(state, args.toArray());
}
public static int calcOffset(ByteBuffer byteBuffer, Integer branchTarget) {
if (branchTarget == null)
return 0;
return branchTarget - byteBuffer.position();
}
public byte[] compile(Object... args) throws CompilationException {
if (args.length != this.params.length)
throw new IllegalArgumentException(String.format("%s requires %d args, only %d passed", this.name(), this.params.length, args.length));
ByteBuffer byteBuffer = ByteBuffer.allocate(32); // 32 should easily be enough
byteBuffer.put(this.value);
for (int i = 0; i < this.params.length; ++i)
try {
byteBuffer.put(this.params[i].compile(args[i]));
} catch (ClassCastException e) {
throw new CompilationException(String.format("%s arg[%d] could not coerced to required type", this.name(), i));
}
byteBuffer.flip();
byte[] bytes = new byte[byteBuffer.limit()];
byteBuffer.get(bytes);
return bytes;
}
/**
* Returns string representing disassembled OpCode and parameters
*
@ -941,10 +965,7 @@ public enum OpCode {
int address2 = (int) args[1];
byte offset = (byte) args[2];
int branchTarget = state.getProgramCounter() + offset;
if (branchTarget < 0 || branchTarget >= state.codeByteBuffer.limit())
throw new InvalidAddressException("branch target out of bounds");
int branchTarget = calculateBranchTarget(state, offset);
long value1 = state.dataByteBuffer.getLong(address1);
long value2 = state.dataByteBuffer.getLong(address2);
@ -953,4 +974,14 @@ public enum OpCode {
state.codeByteBuffer.position(branchTarget);
}
protected int calculateBranchTarget(MachineState state, byte offset) throws ExecutionException {
final int branchTarget = state.getProgramCounter() + offset;
if (branchTarget < 0 || branchTarget >= state.codeByteBuffer.limit())
throw new InvalidAddressException(String.format("%s code target PC(%04x) + %02x = %04x out of bounds: 0x0000 to 0x%04x",
this.name(), state.getProgramCounter(), offset, branchTarget, state.codeByteBuffer.limit() - 1));
return branchTarget;
}
}

79
Java/src/main/java/org/ciyam/at/OpCodeParam.java

@ -1,10 +1,11 @@
package org.ciyam.at;
import java.nio.ByteBuffer;
import java.util.function.Function;
enum OpCodeParam {
VALUE {
VALUE(OpCodeParam::compileLong) {
@Override
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
return Long.valueOf(Utils.getCodeValue(codeByteBuffer));
@ -15,7 +16,7 @@ enum OpCodeParam {
return String.format("#%016x", (Long) value);
}
},
DEST_ADDR {
DEST_ADDR(OpCodeParam::compileInt) {
@Override
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
return Integer.valueOf(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
@ -26,7 +27,7 @@ enum OpCodeParam {
return String.format("@%08x", ((Integer) value) / MachineState.VALUE_SIZE);
}
},
INDIRECT_DEST_ADDR {
INDIRECT_DEST_ADDR(OpCodeParam::compileInt) {
@Override
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
return Integer.valueOf(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
@ -37,7 +38,7 @@ enum OpCodeParam {
return String.format("@($%08x)", ((Integer) value) / MachineState.VALUE_SIZE);
}
},
INDIRECT_DEST_ADDR_WITH_INDEX {
INDIRECT_DEST_ADDR_WITH_INDEX(OpCodeParam::compileInt) {
@Override
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
return Integer.valueOf(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
@ -48,7 +49,7 @@ enum OpCodeParam {
return String.format("@($%08x", ((Integer) value) / MachineState.VALUE_SIZE);
}
},
SRC_ADDR {
SRC_ADDR(OpCodeParam::compileInt) {
@Override
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
return Integer.valueOf(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
@ -59,7 +60,7 @@ enum OpCodeParam {
return String.format("$%08x", ((Integer) value) / MachineState.VALUE_SIZE);
}
},
INDIRECT_SRC_ADDR {
INDIRECT_SRC_ADDR(OpCodeParam::compileInt) {
@Override
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
return Integer.valueOf(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
@ -70,7 +71,7 @@ enum OpCodeParam {
return String.format("$($%08x)", ((Integer) value) / MachineState.VALUE_SIZE);
}
},
INDIRECT_SRC_ADDR_WITH_INDEX {
INDIRECT_SRC_ADDR_WITH_INDEX(OpCodeParam::compileInt) {
@Override
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
return Integer.valueOf(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
@ -81,7 +82,7 @@ enum OpCodeParam {
return String.format("$($%08x", ((Integer) value) / MachineState.VALUE_SIZE);
}
},
INDEX {
INDEX(OpCodeParam::compileInt) {
@Override
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
return Integer.valueOf(Utils.getDataAddress(codeByteBuffer, dataByteBuffer));
@ -92,7 +93,7 @@ enum OpCodeParam {
return String.format("+ $%08x)", ((Integer) value) / MachineState.VALUE_SIZE);
}
},
CODE_ADDR {
CODE_ADDR(OpCodeParam::compileInt) {
@Override
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
return Integer.valueOf(Utils.getCodeAddress(codeByteBuffer));
@ -103,7 +104,7 @@ enum OpCodeParam {
return String.format("[%04x]", (Integer) value);
}
},
OFFSET {
OFFSET(OpCodeParam::compileByte) {
@Override
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
return Byte.valueOf(Utils.getCodeOffset(codeByteBuffer));
@ -114,7 +115,7 @@ enum OpCodeParam {
return String.format("PC+%02x=[%04x]", (int) ((Byte) value), postOpcodeProgramCounter - 1 + (Byte) value);
}
},
FUNC {
FUNC(OpCodeParam::compileFunc) {
@Override
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
return Short.valueOf(codeByteBuffer.getShort());
@ -135,7 +136,7 @@ enum OpCodeParam {
return "\"" + functionCode.name() + "\"" + String.format("{%04x}", (Short) value);
}
},
BLOCK_HEIGHT {
BLOCK_HEIGHT(OpCodeParam::compileInt) {
@Override
public Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException {
return Integer.valueOf(codeByteBuffer.getInt());
@ -147,8 +148,62 @@ enum OpCodeParam {
}
};
private final Function<? super Object, byte[]> compiler;
private OpCodeParam(Function<? super Object, byte[]> compiler) {
this.compiler = compiler;
}
public abstract Object fetch(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws ExecutionException;
private static byte[] compileByte(Object o) {
// Highly likely to be an Integer, so try that first
try {
int intValue = (int) o;
if (intValue < Byte.MIN_VALUE || intValue > Byte.MAX_VALUE)
throw new ClassCastException("Value too large to compile to byte");
return new byte[] { (byte) intValue };
} catch (ClassCastException e) {
// Try again using Byte
return new byte[] { (byte) o };
}
}
private static byte[] compileShort(Object o) {
short s = (short) o;
return new byte[] { (byte) (s >>> 8), (byte) (s) };
}
private static byte[] compileInt(Object o) {
return MachineState.toByteArray((int) o);
}
private static byte[] compileLong(Object o) {
// Highly likely to be a Long, so try that first
try {
return MachineState.toByteArray((long) o);
} catch (ClassCastException e) {
// Try again using Integer
return MachineState.toByteArray((long)(int) o);
}
}
private static byte[] compileFunc(Object o) {
try {
FunctionCode func = (FunctionCode) o;
return compileShort(func.value);
} catch (ClassCastException e) {
// Couldn't cast to FunctionCode,
// but try Short in case caller is using API-PASSTHROUGH range
return compileShort(o);
}
}
protected byte[] compile(Object arg) {
return this.compiler.apply(arg);
}
public String disassemble(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer, int postOpcodeProgramCounter) throws ExecutionException {
Object value = fetch(codeByteBuffer, dataByteBuffer);

19
Java/src/main/java/org/ciyam/at/Utils.java

@ -17,9 +17,9 @@ interface Utils {
*/
static FunctionCode getFunctionCode(ByteBuffer codeByteBuffer) throws CodeSegmentException, IllegalFunctionCodeException {
try {
int rawFunctionCode = codeByteBuffer.getShort();
final int rawFunctionCode = codeByteBuffer.getShort();
FunctionCode functionCode = FunctionCode.valueOf(rawFunctionCode);
final FunctionCode functionCode = FunctionCode.valueOf(rawFunctionCode);
if (functionCode == null)
throw new IllegalFunctionCodeException("Unknown function code");
@ -44,7 +44,7 @@ interface Utils {
*/
static int getCodeAddress(ByteBuffer codeByteBuffer) throws CodeSegmentException, InvalidAddressException {
try {
int address = codeByteBuffer.getInt();
final int address = codeByteBuffer.getInt();
if (address < 0 || address > MachineState.MAX_CODE_ADDRESS || address >= codeByteBuffer.limit())
throw new InvalidAddressException("Code address out of bounds");
@ -69,7 +69,7 @@ interface Utils {
*/
static int getDataAddress(ByteBuffer codeByteBuffer, ByteBuffer dataByteBuffer) throws CodeSegmentException, InvalidAddressException {
try {
int address = codeByteBuffer.getInt() * MachineState.VALUE_SIZE;
final int address = codeByteBuffer.getInt() * MachineState.VALUE_SIZE;
if (address < 0 || address + MachineState.VALUE_SIZE >= dataByteBuffer.limit())
throw new InvalidAddressException("Data address out of bounds");
@ -90,18 +90,11 @@ interface Utils {
* @param codeByteBuffer
* @return byte offset
* @throws CodeSegmentException if we ran out of bytes trying to fetch offset
* @throws InvalidAddressException if position + offset is outside of code segment
*/
static byte getCodeOffset(ByteBuffer codeByteBuffer) throws CodeSegmentException, InvalidAddressException {
try {
final byte offset = codeByteBuffer.get();
final int target = codeByteBuffer.position() + offset;
if (target < 0 || target >= codeByteBuffer.limit())
throw new InvalidAddressException(String.format("Code target PC(%04x) + %02x = %04x out of bounds: 0x0000 to 0x%04x",
codeByteBuffer.position() - 1, offset, target, codeByteBuffer.limit() - 1));
return offset;
// We can't do bounds checking here as we don't have access to program counter.
return codeByteBuffer.get();
} catch (BufferUnderflowException e) {
throw new CodeSegmentException("No code bytes left to get code offset", e);
}

287
Java/src/test/java/org/ciyam/at/CompileTests.java

@ -0,0 +1,287 @@
package org.ciyam.at;
import static org.ciyam.at.OpCode.calcOffset;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.nio.ByteBuffer;
import java.util.Arrays;
import org.ciyam.at.test.TestUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class CompileTests {
private ByteBuffer codeByteBuffer;
@Before
public void before() {
this.codeByteBuffer = ByteBuffer.allocate(512);
}
@After
public void after() {
this.codeByteBuffer = null;
}
@Test
public void testSimpleCompile() throws CompilationException {
int address = 1234;
long value = System.currentTimeMillis();
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(address).putLong(value);
byte[] expectedBytes = TestUtils.getBytes(codeByteBuffer);
byte[] actualBytes = OpCode.SET_VAL.compile(address, value);
assertTrue(Arrays.equals(expectedBytes, actualBytes));
}
@Test
public void testWideningCompile() throws CompilationException {
int address = 1234;
int value = 9999;
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(address).putLong(value);
byte[] expectedBytes = TestUtils.getBytes(codeByteBuffer);
byte[] actualBytes = OpCode.SET_VAL.compile(address, value);
assertTrue(Arrays.equals(expectedBytes, actualBytes));
}
@Test
public void testBranchCompile() throws CompilationException {
int address = 1234;
byte offset = (byte) 16;
codeByteBuffer.put(OpCode.BZR_DAT.value).putInt(address).put(offset);
byte[] expectedBytes = TestUtils.getBytes(codeByteBuffer);
byte[] actualBytes = OpCode.BZR_DAT.compile(address, offset);
assertTrue(Arrays.equals(expectedBytes, actualBytes));
}
@Test
public void testBranchNarrowingCompile() throws CompilationException {
int address = 1234;
int offset = 16; // fits within byte
codeByteBuffer.put(OpCode.BZR_DAT.value).putInt(address).put((byte) offset);
byte[] expectedBytes = TestUtils.getBytes(codeByteBuffer);
byte[] actualBytes = OpCode.BZR_DAT.compile(address, offset);
assertTrue(Arrays.equals(expectedBytes, actualBytes));
}
@Test
public void testBranchCompileFailure() throws CompilationException {
int address = 1234;
int offset = 9999; // larger than a byte
try {
codeByteBuffer.put(OpCode.BZR_DAT.value).putInt(address).put((byte) offset);
} catch (Throwable t) {
fail("Narrowing to byte would silently fail with old code");
}
try {
byte[] expectedBytes = TestUtils.getBytes(codeByteBuffer);
byte[] actualBytes = OpCode.BZR_DAT.compile(address, offset);
assertTrue(Arrays.equals(expectedBytes, actualBytes));
} catch (CompilationException e) {
// this is to be expected as offset is too big to fit into byte
System.out.println("Expected error: " + e.getMessage());
return;
}
fail("Narrowing to byte should have caused exception");
}
@Test
public void testFunctionCompile() throws CompilationException {
int addrValue1 = 1234;
int addrValue2 = 5678;
int addrResult = 9999;
codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.value).putShort(FunctionCode.ADD_MINUTES_TO_TIMESTAMP.value).putInt(addrResult).putInt(addrValue1).putInt(addrValue2);
byte[] expectedBytes = TestUtils.getBytes(codeByteBuffer);
byte[] actualBytes = OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.ADD_MINUTES_TO_TIMESTAMP, addrResult, addrValue1, addrValue2);
assertTrue(Arrays.equals(expectedBytes, actualBytes));
}
@Test
public void testTwoPassCompile() throws CompilationException {
int addrData = 0;
Integer actualTarget = null;
int expectedTarget = 0x06;
// Old version
codeByteBuffer.put(OpCode.BZR_DAT.value).putInt(addrData).put((byte) expectedTarget);
// Two-pass version
ByteBuffer compileBuffer = ByteBuffer.allocate(512);
for (int pass = 0; pass < 2; ++pass) {
compileBuffer.clear();
compileBuffer.put(OpCode.BZR_DAT.compile(addrData, calcOffset(compileBuffer, actualTarget)));
actualTarget = compileBuffer.position();
}
assertEquals(expectedTarget, (int) actualTarget);
byte[] expectedBytes = TestUtils.getBytes(codeByteBuffer);
byte[] actualBytes = TestUtils.getBytes(compileBuffer);
assertTrue(Arrays.equals(expectedBytes, actualBytes));
}
@SuppressWarnings("unused")
@Test
public void testComplexCompile() throws CompilationException {
// Labels for data segment addresses
int addrCounter = 0;
// Constants (with corresponding dataByteBuffer.put*() calls below)
final int addrHashPart1 = addrCounter++;
final int addrHashPart2 = addrCounter++;
final int addrHashPart3 = addrCounter++;
final int addrHashPart4 = addrCounter++;
final int addrAddressPart1 = addrCounter++;
final int addrAddressPart2 = addrCounter++;
final int addrAddressPart3 = addrCounter++;
final int addrAddressPart4 = addrCounter++;
final int addrRefundMinutes = addrCounter++;
final int addrHashTempIndex = addrCounter++;
final int addrHashTempLength = addrCounter++;
final int addrInitialPayoutAmount = addrCounter++;
final int addrExpectedTxType = addrCounter++;
// Variables
final int addrRefundTimestamp = addrCounter++;
final int addrLastTimestamp = addrCounter++;
final int addrBlockTimestamp = addrCounter++;
final int addrTxType = addrCounter++;
final int addrComparator = addrCounter++;
final int addrAddressTemp1 = addrCounter++;
final int addrAddressTemp2 = addrCounter++;
final int addrAddressTemp3 = addrCounter++;
final int addrAddressTemp4 = addrCounter++;
final int addrHashTemp1 = addrCounter++;
final int addrHashTemp2 = addrCounter++;
final int addrHashTemp3 = addrCounter++;
final int addrHashTemp4 = addrCounter++;
Integer labelTxLoop = null;
Integer labelRefund = null;
Integer labelCheckTx = null;
// Two-pass version
for (int pass = 0; pass < 2; ++pass) {
codeByteBuffer.clear();
/* Initialization */
// Use AT creation 'timestamp' as starting point for finding transactions sent to AT
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_CREATION_TIMESTAMP, addrLastTimestamp));
// Calculate refund 'timestamp' by adding minutes to above 'timestamp', then save into addrRefundTimestamp
codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.ADD_MINUTES_TO_TIMESTAMP, addrRefundTimestamp, addrLastTimestamp, addrRefundMinutes));
// Load recipient's address into B register
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.SET_B_IND, addrAddressPart1));
// Send initial payment to recipient so they have enough funds to message AT if all goes well
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PAY_TO_ADDRESS_IN_B, addrInitialPayoutAmount));
// Set restart position to after this opcode
codeByteBuffer.put(OpCode.SET_PCS.compile());
/* Main loop */
// Fetch current block 'timestamp'
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_BLOCK_TIMESTAMP, addrBlockTimestamp));
// If we're not past refund 'timestamp' then look for next transaction
codeByteBuffer.put(OpCode.BLT_DAT.compile(addrBlockTimestamp, addrRefundTimestamp, calcOffset(codeByteBuffer, labelTxLoop)));
// We're past refund 'timestamp' so go refund everything back to AT creator
codeByteBuffer.put(OpCode.JMP_ADR.compile(labelRefund == null ? 0 : labelRefund));
/* Transaction processing loop */
labelTxLoop = codeByteBuffer.position();
// Find next transaction to this AT since the last one (if any)
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A, addrLastTimestamp));
// If no transaction found, A will be zero. If A is zero, set addrComparator to 1, otherwise 0.
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.CHECK_A_IS_ZERO, addrComparator));
// If addrComparator is zero (i.e. A is non-zero, transaction was found) then go check transaction
codeByteBuffer.put(OpCode.BZR_DAT.compile(addrComparator, calcOffset(codeByteBuffer, labelCheckTx)));
// Stop and wait for next block
codeByteBuffer.put(OpCode.STP_IMD.compile());
/* Check transaction */
labelCheckTx = codeByteBuffer.position();
// Update our 'last found transaction's timestamp' using 'timestamp' from transaction
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A, addrLastTimestamp));
// Extract transaction type (message/payment) from transaction and save type in addrTxType
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_TYPE_FROM_TX_IN_A, addrTxType));
// If transaction type is not MESSAGE type then go look for another transaction
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrTxType, addrExpectedTxType, calcOffset(codeByteBuffer, labelTxLoop)));
/* Check transaction's sender */
// Extract sender address from transaction into B register
codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_ADDRESS_FROM_TX_IN_A_INTO_B));
// Save B register into data segment starting at addrAddressTemp1
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_B_IND, addrAddressTemp1));
// Compare each part of transaction's sender's address with expected address. If they don't match, look for another transaction.
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrAddressTemp1, addrAddressPart1, calcOffset(codeByteBuffer, labelTxLoop)));
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrAddressTemp2, addrAddressPart2, calcOffset(codeByteBuffer, labelTxLoop)));
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrAddressTemp3, addrAddressPart3, calcOffset(codeByteBuffer, labelTxLoop)));
codeByteBuffer.put(OpCode.BNE_DAT.compile(addrAddressTemp4, addrAddressPart4, calcOffset(codeByteBuffer, labelTxLoop)));
/* Check 'secret' in transaction's message */
// Extract message from transaction into B register
codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B));
// Save B register into data segment starting at addrHashTemp1
codeByteBuffer.put(OpCode.EXT_FUN_RET.compile(FunctionCode.GET_B_IND, addrHashTemp1));
// Load B register with expected hash result
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.SET_B_IND, addrHashPart1));
// Perform HASH160 using source data at addrHashTemp1 through addrHashTemp4. (Location and length specified via addrHashTempIndex and addrHashTemplength).
// Save the equality result (1 if they match, 0 otherwise) into addrComparator.
codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.compile(FunctionCode.CHECK_HASH160_WITH_B, addrComparator, addrHashTempIndex, addrHashTempLength));
// If hashes don't match, addrComparator will be zero so go find another transaction
codeByteBuffer.put(OpCode.BZR_DAT.compile(addrComparator, calcOffset(codeByteBuffer, labelTxLoop)));
/* Success! Pay balance to intended recipient */
// Load B register with intended recipient address.
codeByteBuffer.put(OpCode.EXT_FUN_DAT.compile(FunctionCode.SET_B_IND, addrAddressPart1));
// Pay AT's balance to recipient
codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B));
// We're finished forever
codeByteBuffer.put(OpCode.FIN_IMD.compile());
/* Refund balance back to AT creator */
labelRefund = codeByteBuffer.position();
// Load B register with AT creator's address.
codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PUT_CREATOR_INTO_B));
// Pay AT's balance back to AT's creator.
codeByteBuffer.put(OpCode.EXT_FUN.compile(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B));
// We're finished forever
codeByteBuffer.put(OpCode.FIN_IMD.compile());
}
byte[] expectedBytes = TestUtils.getBytes(codeByteBuffer);
assertTrue(expectedBytes.length > 0);
}
}

18
Java/src/test/java/org/ciyam/at/DisassemblyTests.java

@ -49,13 +49,10 @@ public class DisassemblyTests extends ExecutableTest {
codeByteBuffer.put(OpCode.FIN_IMD.value);
byte[] headerBytes = TestUtils.HEADER_BYTES;
byte[] codeBytes = codeByteBuffer.array();
byte[] dataBytes = dataByteBuffer.array();
byte[] codeBytes = TestUtils.getBytes(codeByteBuffer);
int dataBufferLength = dataByteBuffer.position();
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
System.out.println(state.disassemble());
System.out.println(MachineState.disassemble(codeBytes, dataBufferLength));
}
@Test
@ -64,16 +61,11 @@ public class DisassemblyTests extends ExecutableTest {
byte[] randomCode = new byte[200];
random.nextBytes(randomCode);
codeByteBuffer.put(randomCode);
byte[] headerBytes = TestUtils.HEADER_BYTES;
byte[] codeBytes = codeByteBuffer.array();
byte[] dataBytes = dataByteBuffer.array();
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
int dataBufferLength = 1024;
try {
System.out.println(state.disassemble());
System.out.println(MachineState.disassemble(randomCode, dataBufferLength));
} catch (ExecutionException e) {
// we expect this to fail
return;

84
Java/src/test/java/org/ciyam/at/SerializationTests.java

@ -15,44 +15,20 @@ import org.junit.Test;
public class SerializationTests extends ExecutableTest {
private byte[] simulate() {
byte[] headerBytes = TestUtils.HEADER_BYTES;
byte[] codeBytes = codeByteBuffer.array();
byte[] dataBytes = new byte[0];
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
return executeAndCheck(state);
}
private byte[] continueSimulation(byte[] savedState) {
byte[] codeBytes = codeByteBuffer.array();
state = MachineState.fromBytes(api, logger, savedState, codeBytes);
// Pretend we're on next block
api.bumpCurrentBlockHeight();
@Test
public void testIntToByteArray() {
byte[] expectedBytes = hexToBytes("fedcba98");
byte[] actualBytes = MachineState.toByteArray(0xfedcba98);
return executeAndCheck(state);
assertTrue(Arrays.equals(expectedBytes, actualBytes));
}
private byte[] executeAndCheck(MachineState state) {
state.execute();
// Fetch current state, and code bytes
byte[] stateBytes = unwrapState(state);
byte[] codeBytes = state.getCodeBytes();
// Rebuild new MachineState using fetched state & bytes
MachineState restoredState = MachineState.fromBytes(api, logger, stateBytes, codeBytes);
// Extract rebuilt state and code bytes
byte[] restoredStateBytes = restoredState.toBytes();
byte[] restoredCodeBytes = state.getCodeBytes();
// Check that both states and bytes match
assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(stateBytes, restoredStateBytes));
assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(codeBytes, restoredCodeBytes));
@Test
public void testLongToByteArray() {
byte[] expectedBytes = hexToBytes("fedcba9876543210");
byte[] actualBytes = MachineState.toByteArray(0xfedcba9876543210L);
return stateBytes;
assertTrue(Arrays.equals(expectedBytes, actualBytes));
}
/** Test serialization of state with stop address. */
@ -137,4 +113,44 @@ public class SerializationTests extends ExecutableTest {
assertTrue(Arrays.equals(packedState, packedRestoredSate));
}
private byte[] simulate() {
byte[] headerBytes = TestUtils.HEADER_BYTES;
byte[] codeBytes = codeByteBuffer.array();
byte[] dataBytes = new byte[0];
state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes);
return executeAndCheck(state);
}
private byte[] continueSimulation(byte[] savedState) {
byte[] codeBytes = codeByteBuffer.array();
state = MachineState.fromBytes(api, logger, savedState, codeBytes);
// Pretend we're on next block
api.bumpCurrentBlockHeight();
return executeAndCheck(state);
}
private byte[] executeAndCheck(MachineState state) {
state.execute();
// Fetch current state, and code bytes
byte[] stateBytes = unwrapState(state);
byte[] codeBytes = state.getCodeBytes();
// Rebuild new MachineState using fetched state & bytes
MachineState restoredState = MachineState.fromBytes(api, logger, stateBytes, codeBytes);
// Extract rebuilt state and code bytes
byte[] restoredStateBytes = restoredState.toBytes();
byte[] restoredCodeBytes = state.getCodeBytes();
// Check that both states and bytes match
assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(stateBytes, restoredStateBytes));
assertTrue("Serialization->Deserialization->Reserialization error", Arrays.equals(codeBytes, restoredCodeBytes));
return stateBytes;
}
}

8
Java/src/test/java/org/ciyam/at/test/TestUtils.java

@ -56,4 +56,12 @@ public class TestUtils {
return byteBuffer.array();
}
public static byte[] getBytes(ByteBuffer byteBuffer) {
byteBuffer.flip();
byte[] bytes = new byte[byteBuffer.limit()];
byteBuffer.get(bytes);
byteBuffer.clear();
return bytes;
}
}

Loading…
Cancel
Save