diff --git a/Java/src/main/java/org/ciyam/at/CompilationException.java b/Java/src/main/java/org/ciyam/at/CompilationException.java new file mode 100644 index 0000000..5cfb34f --- /dev/null +++ b/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); + } + +} diff --git a/Java/src/main/java/org/ciyam/at/MachineState.java b/Java/src/main/java/org/ciyam/at/MachineState.java index cc68eb6..d2a04dc 100644 --- a/Java/src/main/java/org/ciyam/at/MachineState.java +++ b/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(); diff --git a/Java/src/main/java/org/ciyam/at/OpCode.java b/Java/src/main/java/org/ciyam/at/OpCode.java index 574728f..215a1af 100644 --- a/Java/src/main/java/org/ciyam/at/OpCode.java +++ b/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 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; + } + } diff --git a/Java/src/main/java/org/ciyam/at/OpCodeParam.java b/Java/src/main/java/org/ciyam/at/OpCodeParam.java index ea15bf4..b7deb99 100644 --- a/Java/src/main/java/org/ciyam/at/OpCodeParam.java +++ b/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 compiler; + + private OpCodeParam(Function 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); diff --git a/Java/src/main/java/org/ciyam/at/Utils.java b/Java/src/main/java/org/ciyam/at/Utils.java index 4c98281..d03dcb7 100644 --- a/Java/src/main/java/org/ciyam/at/Utils.java +++ b/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); } diff --git a/Java/src/test/java/org/ciyam/at/CompileTests.java b/Java/src/test/java/org/ciyam/at/CompileTests.java new file mode 100644 index 0000000..4b31f0e --- /dev/null +++ b/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); + } + +} diff --git a/Java/src/test/java/org/ciyam/at/DisassemblyTests.java b/Java/src/test/java/org/ciyam/at/DisassemblyTests.java index f1b6978..d74a9ca 100644 --- a/Java/src/test/java/org/ciyam/at/DisassemblyTests.java +++ b/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; diff --git a/Java/src/test/java/org/ciyam/at/SerializationTests.java b/Java/src/test/java/org/ciyam/at/SerializationTests.java index 81d5756..dbe3479 100644 --- a/Java/src/test/java/org/ciyam/at/SerializationTests.java +++ b/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]; + @Test + public void testIntToByteArray() { + byte[] expectedBytes = hexToBytes("fedcba98"); + byte[] actualBytes = MachineState.toByteArray(0xfedcba98); - state = new MachineState(api, logger, headerBytes, codeBytes, dataBytes); - - return executeAndCheck(state); + assertTrue(Arrays.equals(expectedBytes, actualBytes)); } - private byte[] continueSimulation(byte[] savedState) { - byte[] codeBytes = codeByteBuffer.array(); - state = MachineState.fromBytes(api, logger, savedState, codeBytes); + @Test + public void testLongToByteArray() { + byte[] expectedBytes = hexToBytes("fedcba9876543210"); + byte[] actualBytes = MachineState.toByteArray(0xfedcba9876543210L); - // 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; + 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; + } + } diff --git a/Java/src/test/java/org/ciyam/at/test/TestUtils.java b/Java/src/test/java/org/ciyam/at/test/TestUtils.java index fce6c76..cb093f8 100644 --- a/Java/src/test/java/org/ciyam/at/test/TestUtils.java +++ b/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; + } + }