Browse Source

Improved testing.

Rolled the superior blockchain simulating parts from ACCTAPI into
TestAPI.

Tried to replace literal test values with named constants from TestAPI
class, or derived values.

Added some more opcode tests to cover more cases.

Renamed some functions of the form "put something ... in A" to
"put something .. into A" to help distinguish them from
"get ... based on something in A".

Added {GET,SET}_[AB]_IND functions as an addition to the long-winded
GET_A1..A4, SET_B1..B4.

Added more function code tests and separated those tests out into 3
different test classes for manageability.

Possible logic error in PAY_TO_ADDRESS_IN_B and PAY_PREVIOUS_TO_ADDRESS_IN_B
but needs testing!

Improved comments.
master
catbref 5 years ago
parent
commit
36c63b0be0
  1. 8
      Java/src/main/java/org/ciyam/at/API.java
  2. 94
      Java/src/main/java/org/ciyam/at/FunctionCode.java
  3. 7
      Java/src/main/java/org/ciyam/at/MachineState.java
  4. 219
      Java/src/test/java/BlockchainFunctionCodeTests.java
  5. 65
      Java/src/test/java/DataOpCodeTests.java
  6. 161
      Java/src/test/java/FunctionCodeTests.java
  7. 148
      Java/src/test/java/HashingFunctionCodeTests.java
  8. 9
      Java/src/test/java/MiscTests.java
  9. 70
      Java/src/test/java/SerializationTests.java
  10. 349
      Java/src/test/java/common/ACCTAPI.java
  11. 11
      Java/src/test/java/common/ExecutableTest.java
  12. 316
      Java/src/test/java/common/TestAPI.java

8
Java/src/main/java/org/ciyam/at/API.java

@ -56,11 +56,11 @@ public abstract class API {
return getCurrentBlockHeight() - 1;
}
/** Put previous block's signature hash in A */
public abstract void putPreviousBlockHashInA(MachineState state);
/** Put previous block's signature/hash into A */
public abstract void putPreviousBlockHashIntoA(MachineState state);
/** Put next transaction to AT after timestamp in A, or zero A if no more transactions */
public abstract void putTransactionAfterTimestampInA(Timestamp timestamp, MachineState state);
/** Put signature/hash of next transaction sent to AT after timestamp in A, or zero A if no more transactions */
public abstract void putTransactionAfterTimestampIntoA(Timestamp timestamp, MachineState state);
/** Return type from transaction in A, or 0xffffffffffffffff if A not valid transaction */
public abstract long getTypeFromTransactionInA(MachineState state);

94
Java/src/main/java/org/ciyam/at/FunctionCode.java

@ -113,6 +113,44 @@ public enum FunctionCode {
functionData.returnValue = state.b4;
}
},
/**
* <tt>0x0108</tt><br>
* Copies A into addr to addr+3
*/
GET_A_IND(0x0108, 1, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
// Validate data offset in arg1
if (functionData.value1 < 0L || functionData.value1 > Integer.MAX_VALUE || functionData.value1 >= state.numDataPages - 3)
throw new ExecutionException(this.name() + " data start address out of bounds");
int dataIndex = (int) (functionData.value1 & 0x7fffffffL);
state.dataByteBuffer.putLong(dataIndex++ * MachineState.VALUE_SIZE, state.a1);
state.dataByteBuffer.putLong(dataIndex++ * MachineState.VALUE_SIZE, state.a2);
state.dataByteBuffer.putLong(dataIndex++ * MachineState.VALUE_SIZE, state.a3);
state.dataByteBuffer.putLong(dataIndex++ * MachineState.VALUE_SIZE, state.a4);
}
},
/**
* <tt>0x0108</tt><br>
* Copies B into addr to addr+3
*/
GET_B_IND(0x0109, 1, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
// Validate data offset in arg1
if (functionData.value1 < 0L || functionData.value1 > Integer.MAX_VALUE || functionData.value1 >= state.numDataPages - 3)
throw new ExecutionException(this.name() + " data start address out of bounds");
int dataIndex = (int) (functionData.value1 & 0x7fffffffL);
state.dataByteBuffer.putLong(dataIndex++ * MachineState.VALUE_SIZE, state.b1);
state.dataByteBuffer.putLong(dataIndex++ * MachineState.VALUE_SIZE, state.b2);
state.dataByteBuffer.putLong(dataIndex++ * MachineState.VALUE_SIZE, state.b3);
state.dataByteBuffer.putLong(dataIndex++ * MachineState.VALUE_SIZE, state.b4);
}
},
/**
* Set A1<br>
* <tt>0x0110 value</tt>
@ -237,6 +275,44 @@ public enum FunctionCode {
state.b4 = functionData.value2;
}
},
/**
* <tt>0x0108</tt><br>
* Copies addr to addr+3 into A
*/
SET_A_IND(0x011c, 1, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
// Validate data offset in arg1
if (functionData.value1 < 0L || functionData.value1 > Integer.MAX_VALUE || functionData.value1 >= state.numDataPages - 3)
throw new ExecutionException(this.name() + " data start address out of bounds");
int dataIndex = (int) (functionData.value1 & 0x7fffffffL);
state.a1 = state.dataByteBuffer.getLong(dataIndex++ * MachineState.VALUE_SIZE);
state.a2 = state.dataByteBuffer.getLong(dataIndex++ * MachineState.VALUE_SIZE);
state.a3 = state.dataByteBuffer.getLong(dataIndex++ * MachineState.VALUE_SIZE);
state.a4 = state.dataByteBuffer.getLong(dataIndex++ * MachineState.VALUE_SIZE);
}
},
/**
* <tt>0x0108</tt><br>
* Copies addr to addr+3 into B
*/
SET_B_IND(0x011d, 1, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
// Validate data offset in arg1
if (functionData.value1 < 0L || functionData.value1 > Integer.MAX_VALUE || functionData.value1 >= state.numDataPages - 3)
throw new ExecutionException(this.name() + " data start address out of bounds");
int dataIndex = (int) (functionData.value1 & 0x7fffffffL);
state.b1 = state.dataByteBuffer.getLong(dataIndex++ * MachineState.VALUE_SIZE);
state.b2 = state.dataByteBuffer.getLong(dataIndex++ * MachineState.VALUE_SIZE);
state.b3 = state.dataByteBuffer.getLong(dataIndex++ * MachineState.VALUE_SIZE);
state.b4 = state.dataByteBuffer.getLong(dataIndex++ * MachineState.VALUE_SIZE);
}
},
/**
* Clear A<br>
* <tt>0x0120</tt>
@ -720,21 +796,21 @@ public enum FunctionCode {
* <tt>0x0303</tt><br>
* Put previous block's hash in A
*/
PUT_PREVIOUS_BLOCK_HASH_IN_A(0x0303, 0, false) {
PUT_PREVIOUS_BLOCK_HASH_INTO_A(0x0303, 0, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
state.getAPI().putPreviousBlockHashInA(state);
state.getAPI().putPreviousBlockHashIntoA(state);
}
},
/**
* <tt>0x0304</tt><br>
* Put transaction after timestamp in A, or zero if none<br>
* Put transaction (to this AT) after timestamp in A, or zero if none<br>
* a-k-a "A_To_Tx_After_Timestamp"
*/
PUT_TX_AFTER_TIMESTAMP_IN_A(0x0304, 1, false) {
PUT_TX_AFTER_TIMESTAMP_INTO_A(0x0304, 1, false) {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
state.getAPI().putTransactionAfterTimestampInA(new Timestamp(functionData.value1), state);
state.getAPI().putTransactionAfterTimestampIntoA(new Timestamp(functionData.value1), state);
}
},
/**
@ -854,7 +930,7 @@ public enum FunctionCode {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
// Reduce amount to current balance if insufficient funds to pay full amount in value1
long amount = Math.max(state.getCurrentBalance(), functionData.value1);
long amount = Math.min(state.getCurrentBalance(), functionData.value1);
// Actually pay
state.getAPI().payAmountToB(amount, state);
@ -890,7 +966,7 @@ public enum FunctionCode {
@Override
protected void postCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
// Reduce amount to previous balance if insufficient funds to pay previous balance amount
long amount = Math.max(state.getCurrentBalance(), state.getPreviousBalance());
long amount = Math.min(state.getCurrentBalance(), state.getPreviousBalance());
// Actually pay
state.getAPI().payAmountToB(amount, state);
@ -1003,11 +1079,11 @@ public enum FunctionCode {
// TODO: public abstract String disassemble();
protected byte[] getHashData(FunctionData functionData, MachineState state) throws ExecutionException {
// Validate data offset in A1
// Validate data offset in arg1
if (functionData.value1 < 0L || functionData.value1 > Integer.MAX_VALUE || functionData.value1 >= state.numDataPages)
throw new ExecutionException(this.name() + " data start address out of bounds");
// Validate data length in A2
// Validate data length in arg2
if (functionData.value2 < 0L || functionData.value2 > Integer.MAX_VALUE || functionData.value1 + byteLengthToDataLength(functionData.value2) > state.numDataPages)
throw new ExecutionException(this.name() + " data length invalid");

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

@ -28,6 +28,9 @@ public class MachineState {
/** Maximum value for an address in the code segment */
public static final int MAX_CODE_ADDRESS = 0x0000ffff;
/** Size of A or B register. */
public static final int AB_REGISTER_SIZE = 32;
private static class VersionedConstants {
/** Bytes per code page */
public final int CODE_PAGE_SIZE;
@ -676,12 +679,12 @@ public class MachineState {
}
/** Convert int to big-endian byte array */
private byte[] toByteArray(int value) {
public static byte[] toByteArray(int value) {
return new byte[] { (byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) (value) };
}
/** Convert long to big-endian byte array */
private byte[] toByteArray(long value) {
public static byte[] toByteArray(long value) {
return new byte[] { (byte) (value >> 56), (byte) (value >> 48), (byte) (value >> 40), (byte) (value >> 32),
(byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) (value) };
}

219
Java/src/test/java/BlockchainFunctionCodeTests.java

@ -0,0 +1,219 @@
import static org.junit.Assert.*;
import java.util.Arrays;
import org.ciyam.at.ExecutionException;
import org.ciyam.at.FunctionCode;
import org.ciyam.at.OpCode;
import org.ciyam.at.Timestamp;
import org.junit.Test;
import common.ExecutableTest;
import common.TestAPI;
import common.TestAPI.TestBlock;
import common.TestAPI.TestTransaction;
public class BlockchainFunctionCodeTests extends ExecutableTest {
/**
* GET_BLOCK_TIMESTAMP
* GET_CREATION_TIMESTAMP
* GET_PREVIOUS_BLOCK_TIMESTAMP
* PUT_PREVIOUS_BLOCK_HASH_INTO_A
* PUT_TX_AFTER_TIMESTAMP_INTO_A
* GET_TYPE_FROM_TX_IN_A
* GET_AMOUNT_FROM_TX_IN_A
* GET_TIMESTAMP_FROM_TX_IN_A
* GENERATE_RANDOM_USING_TX_IN_A
* PUT_MESSAGE_FROM_TX_IN_A_INTO_B
* PUT_ADDRESS_FROM_TX_IN_A_INTO_B
* PUT_CREATOR_INTO_B
* GET_CURRENT_BALANCE
* GET_PREVIOUS_BALANCE
* PAY_TO_ADDRESS_IN_B
* PAY_ALL_TO_ADDRESS_IN_B
* PAY_PREVIOUS_TO_ADDRESS_IN_B
* MESSAGE_A_TO_ADDRESS_IN_B
* ADD_MINUTES_TO_TIMESTAMP
*/
@Test
public void testGetBlockTimestamp() throws ExecutionException {
// Grab block 'timestamp' and save into address 0
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_BLOCK_TIMESTAMP.value).putInt(0);
codeByteBuffer.put(OpCode.FIN_IMD.value);
execute(true);
Timestamp blockTimestamp = new Timestamp(getData(0));
assertEquals("Block timestamp incorrect", TestAPI.DEFAULT_INITIAL_BLOCK_HEIGHT, blockTimestamp.blockHeight);
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
}
@Test
public void testMultipleGetBlockTimestamp() throws ExecutionException {
int expectedBlockHeight = TestAPI.DEFAULT_INITIAL_BLOCK_HEIGHT;
// Grab block 'timestamp' and save into address 0
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_BLOCK_TIMESTAMP.value).putInt(0);
codeByteBuffer.put(OpCode.STP_IMD.value);
execute(true); // TestAPI's block height bumped prior to return
Timestamp blockTimestamp = new Timestamp(getData(0));
assertEquals("Block timestamp incorrect", expectedBlockHeight, blockTimestamp.blockHeight);
// Re-test
++expectedBlockHeight;
execute(true); // TestAPI's block height bumped prior to return
blockTimestamp = new Timestamp(getData(0));
assertEquals("Block timestamp incorrect", expectedBlockHeight, blockTimestamp.blockHeight);
assertFalse(state.getHadFatalError());
}
@Test
public void testGetCreationTimestamp() throws ExecutionException {
// Grab AT creation 'timestamp' and save into address 0
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_CREATION_TIMESTAMP.value).putInt(0);
codeByteBuffer.put(OpCode.FIN_IMD.value);
execute(true);
Timestamp blockTimestamp = new Timestamp(getData(0));
assertEquals("Block timestamp incorrect", TestAPI.DEFAULT_AT_CREATION_BLOCK_HEIGHT, blockTimestamp.blockHeight);
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
}
@Test
public void testGetPreviousBlockTimestamp() throws ExecutionException {
int expectedBlockHeight = TestAPI.DEFAULT_INITIAL_BLOCK_HEIGHT - 1;
// Grab previous block 'timestamp' and save into address 0
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_PREVIOUS_BLOCK_TIMESTAMP.value).putInt(0);
codeByteBuffer.put(OpCode.FIN_IMD.value);
execute(true);
Timestamp blockTimestamp = new Timestamp(getData(0));
assertEquals("Block timestamp incorrect", expectedBlockHeight, blockTimestamp.blockHeight);
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
}
@Test
public void testPutPreviousBlockHashIntoA() throws ExecutionException {
int previousBlockHeight = TestAPI.DEFAULT_INITIAL_BLOCK_HEIGHT - 1;
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_PREVIOUS_BLOCK_HASH_INTO_A.value);
codeByteBuffer.put(OpCode.FIN_IMD.value);
execute(true);
byte[] expectedBlockHash = api.blockchain.get(previousBlockHeight - 1).blockHash;
byte[] aBytes = state.getA();
assertTrue("Block hash mismatch", Arrays.equals(expectedBlockHash, aBytes));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
}
@Test
public void testPutTransactionAfterTimestampIntoA() throws ExecutionException {
long initialTimestamp = Timestamp.toLong(TestAPI.DEFAULT_INITIAL_BLOCK_HEIGHT, 0);
dataByteBuffer.putLong(initialTimestamp);
// Generate some blocks containing transactions (but none to AT)
api.generateBlockWithNonAtTransactions();
api.generateBlockWithNonAtTransactions();
// Generate a block containing transaction to AT
api.generateBlockWithAtTransaction();
int currentBlockHeight = api.blockchain.size();
api.setCurrentBlockHeight(currentBlockHeight);
// Fetch transaction signature/hash after timestamp stored in address 0
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A.value).putInt(0);
codeByteBuffer.put(OpCode.FIN_IMD.value);
execute(true);
TestTransaction transaction = api.getTransactionFromA(state);
assertNotNull(transaction);
Timestamp txTimestamp = new Timestamp(transaction.timestamp);
assertEquals("Transaction hash mismatch", currentBlockHeight, txTimestamp.blockHeight);
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
}
@Test
public void testPutNoTransactionAfterTimestampIntoA() throws ExecutionException {
int initialBlockHeight = TestAPI.DEFAULT_INITIAL_BLOCK_HEIGHT;
long initialTimestamp = Timestamp.toLong(initialBlockHeight, 0);
dataByteBuffer.putLong(initialTimestamp);
// Generate a block containing transaction to AT
api.generateBlockWithAtTransaction();
api.bumpCurrentBlockHeight();
api.generateBlockWithAtTransaction();
api.bumpCurrentBlockHeight();
long expectedTransactionsCount = 0;
for (int blockHeight = initialBlockHeight + 1; blockHeight <= api.blockchain.size(); ++blockHeight) {
TestBlock block = api.blockchain.get(blockHeight - 1);
expectedTransactionsCount += block.transactions.stream().filter(transaction -> transaction.recipient.equals(TestAPI.AT_ADDRESS)).count();
}
// Count how many transactions after timestamp
int targetPosition = 0x15;
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A.value).putInt(0);
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_A_IS_ZERO.value).putInt(1);
int bzrPosition = codeByteBuffer.position();
codeByteBuffer.put(OpCode.BZR_DAT.value).putInt(1).put((byte) (targetPosition - bzrPosition));
codeByteBuffer.put(OpCode.FIN_IMD.value);
assertEquals("targetPosition incorrect", targetPosition, codeByteBuffer.position());
// Update latest timestamp in address 0
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A.value).putInt(0);
// Increment transactions count
codeByteBuffer.put(OpCode.INC_DAT.value).putInt(2);
// Loop again
codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(0);
execute(true);
long transactionsCount = getData(2);
assertEquals("Transaction count incorrect", expectedTransactionsCount, transactionsCount);
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
}
@Test
public void testRandom() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(Timestamp.toLong(api.getCurrentBlockHeight(), 0));
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A.value).putInt(0);
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GENERATE_RANDOM_USING_TX_IN_A.value).putInt(1);
codeByteBuffer.put(OpCode.FIN_IMD.value);
// Generate a block containing transaction to AT
api.generateBlockWithAtTransaction();
execute(false);
assertNotEquals("Random wasn't generated", 0L, getData(1));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
}
}

65
Java/src/test/java/DataOpCodeTests.java

@ -20,6 +20,7 @@ public class DataOpCodeTests extends ExecutableTest {
assertEquals("Data does not match", 2222L, getData(2));
}
/** Check that trying to use an address outside data segment throws a fatal error. */
@Test
public void testSET_VALunbounded() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(9999).putLong(2222L);
@ -44,6 +45,7 @@ public class DataOpCodeTests extends ExecutableTest {
assertEquals("Data does not match", 2222L, getData(1));
}
/** Check that trying to use an address outside data segment throws a fatal error. */
@Test
public void testSET_DATunbounded() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_DAT.value).putInt(9999).putInt(2);
@ -55,6 +57,7 @@ public class DataOpCodeTests extends ExecutableTest {
assertTrue(state.getHadFatalError());
}
/** Check that trying to use an address outside data segment throws a fatal error. */
@Test
public void testSET_DATunbounded2() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_DAT.value).putInt(1).putInt(9999);
@ -82,6 +85,7 @@ public class DataOpCodeTests extends ExecutableTest {
assertEquals(0L, getData(i));
}
/** Check that trying to use an address outside data segment throws a fatal error. */
@Test
public void testCLR_DATunbounded() throws ExecutionException {
codeByteBuffer.put(OpCode.CLR_DAT.value).putInt(9999);
@ -106,6 +110,7 @@ public class DataOpCodeTests extends ExecutableTest {
assertEquals("Data does not match", 2222L + 1L, getData(2));
}
/** Check that trying to use an address outside data segment throws a fatal error. */
@Test
public void testINC_DATunbounded() throws ExecutionException {
codeByteBuffer.put(OpCode.INC_DAT.value).putInt(9999);
@ -117,6 +122,7 @@ public class DataOpCodeTests extends ExecutableTest {
assertTrue(state.getHadFatalError());
}
/** Check that incrementing maximum unsigned long value overflows back to zero correctly. */
@Test
public void testINC_DAToverflow() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(0xffffffffffffffffL);
@ -143,6 +149,7 @@ public class DataOpCodeTests extends ExecutableTest {
assertEquals("Data does not match", 2222L - 1L, getData(2));
}
/** Check that trying to use an address outside data segment throws a fatal error. */
@Test
public void testDEC_DATunbounded() throws ExecutionException {
codeByteBuffer.put(OpCode.DEC_DAT.value).putInt(9999);
@ -153,6 +160,7 @@ public class DataOpCodeTests extends ExecutableTest {
assertTrue(state.getHadFatalError());
}
/** Check that decrementing zero long value underflows back to maximum unsigned long correctly. */
@Test
public void testDEC_DATunderflow() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(0L);
@ -180,6 +188,7 @@ public class DataOpCodeTests extends ExecutableTest {
assertEquals("Data does not match", 2222L + 3333L, getData(2));
}
/** Check that trying to use an address outside data segment throws a fatal error. */
@Test
public void testADD_DATunbounded() throws ExecutionException {
codeByteBuffer.put(OpCode.ADD_DAT.value).putInt(9999).putInt(3);
@ -191,6 +200,7 @@ public class DataOpCodeTests extends ExecutableTest {
assertTrue(state.getHadFatalError());
}
/** Check that trying to use an address outside data segment throws a fatal error. */
@Test
public void testADD_DATunbounded2() throws ExecutionException {
codeByteBuffer.put(OpCode.ADD_DAT.value).putInt(2).putInt(9999);
@ -202,6 +212,7 @@ public class DataOpCodeTests extends ExecutableTest {
assertTrue(state.getHadFatalError());
}
/** Check that adding to an unsigned long value overflows correctly. */
@Test
public void testADD_DAToverflow() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(0x7fffffffffffffffL);
@ -246,8 +257,6 @@ public class DataOpCodeTests extends ExecutableTest {
@Test
public void testDIV_DAT() throws ExecutionException {
// Note: fatal error because error handler not set
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(2).putLong(2222L);
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
codeByteBuffer.put(OpCode.DIV_DAT.value).putInt(3).putInt(2);
@ -260,10 +269,23 @@ public class DataOpCodeTests extends ExecutableTest {
assertEquals("Data does not match", (3333L / 2222L), getData(3));
}
/** Check divide-by-zero throws fatal error because error handler not set. */
@Test
public void testDIV_DATzeroWithOnError() throws ExecutionException {
// Note: non-fatal error because error handler IS set
public void testDIV_DATzero() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(0L);
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
codeByteBuffer.put(OpCode.DIV_DAT.value).putInt(3).putInt(0);
codeByteBuffer.put(OpCode.FIN_IMD.value);
execute(true);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
/** Check divide-by-zero is non-fatal because error handler is set. */
@Test
public void testDIV_DATzeroWithOnError() throws ExecutionException {
int errorAddr = 0x29;
codeByteBuffer.put(OpCode.ERR_ADR.value).putInt(errorAddr);
@ -275,6 +297,7 @@ public class DataOpCodeTests extends ExecutableTest {
// errorAddr:
assertEquals(errorAddr, codeByteBuffer.position());
// Set 1 at address 1 to indicate we handled error OK
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L);
codeByteBuffer.put(OpCode.FIN_IMD.value);
@ -348,7 +371,9 @@ public class DataOpCodeTests extends ExecutableTest {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(4).putLong(4444L);
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(5).putLong(5555L);
// @(6) = $($0) aka $(3) aka 3333
// Set address 6 to the value stored in the address pointed to in address 0.
// So, address 0 contains '3', which means use the value stored in address '3',
// and address '3' contains 3333L so save this into address 6.
codeByteBuffer.put(OpCode.SET_IND.value).putInt(6).putInt(0);
codeByteBuffer.put(OpCode.FIN_IMD.value);
@ -359,6 +384,7 @@ public class DataOpCodeTests extends ExecutableTest {
assertEquals("Data does not match", 3333L, getData(6));
}
/** Check that trying to use an address outside data segment throws a fatal error. */
@Test
public void testSET_INDunbounded() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(3L);
@ -377,6 +403,7 @@ public class DataOpCodeTests extends ExecutableTest {
assertTrue(state.getHadFatalError());
}
/** Check that trying to use an address outside data segment throws a fatal error. */
@Test
public void testSET_INDunbounded2() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(9999L);
@ -415,6 +442,7 @@ public class DataOpCodeTests extends ExecutableTest {
assertEquals("Data does not match", 4444L, getData(0));
}
/** Check that trying to use an address outside data segment throws a fatal error. */
@Test
public void testSET_IDXunbounded() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
@ -434,6 +462,7 @@ public class DataOpCodeTests extends ExecutableTest {
assertTrue(state.getHadFatalError());
}
/** Check that trying to use an address outside data segment throws a fatal error. */
@Test
public void testSET_IDXunbounded2() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
@ -453,6 +482,7 @@ public class DataOpCodeTests extends ExecutableTest {
assertTrue(state.getHadFatalError());
}
/** Check that trying to use an address outside data segment throws a fatal error. */
@Test
public void testSET_IDXunbounded3() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
@ -472,6 +502,7 @@ public class DataOpCodeTests extends ExecutableTest {
assertTrue(state.getHadFatalError());
}
/** Check that trying to use an address outside data segment throws a fatal error. */
@Test
public void testSET_IDXunbounded4() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
@ -510,6 +541,7 @@ public class DataOpCodeTests extends ExecutableTest {
assertEquals("Data does not match", 5555L, getData(3));
}
/** Check that trying to use an address outside data segment throws a fatal error. */
@Test
public void testIND_DATDunbounded() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(3L);
@ -528,6 +560,7 @@ public class DataOpCodeTests extends ExecutableTest {
assertTrue(state.getHadFatalError());
}
/** Check that trying to use an address outside data segment throws a fatal error. */
@Test
public void testIND_DATDunbounded2() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(9999L);
@ -566,6 +599,7 @@ public class DataOpCodeTests extends ExecutableTest {
assertEquals("Data does not match", 5555L, getData(4));
}
/** Check that trying to use an address outside data segment throws a fatal error. */
@Test
public void testIDX_DATunbounded() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
@ -585,6 +619,7 @@ public class DataOpCodeTests extends ExecutableTest {
assertTrue(state.getHadFatalError());
}
/** Check that trying to use an address outside data segment throws a fatal error. */
@Test
public void testIDX_DATunbounded2() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
@ -604,6 +639,7 @@ public class DataOpCodeTests extends ExecutableTest {
assertTrue(state.getHadFatalError());
}
/** Check that trying to use an address outside data segment throws a fatal error. */
@Test
public void testIDX_DATunbounded3() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
@ -623,6 +659,7 @@ public class DataOpCodeTests extends ExecutableTest {
assertTrue(state.getHadFatalError());
}
/** Check that trying to use an address outside data segment throws a fatal error. */
@Test
public void testIDX_DATunbounded4() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1111L);
@ -656,10 +693,23 @@ public class DataOpCodeTests extends ExecutableTest {
assertEquals("Data does not match", 2222L % 3333L, getData(2));
}
/** Check divide-by-zero throws fatal error because error handler not set. */
@Test
public void testMOD_DATzeroWithOnError() throws ExecutionException {
// Note: non-fatal error because error handler IS set
public void testMOD_DATzero() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(0L);
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(3).putLong(3333L);
codeByteBuffer.put(OpCode.MOD_DAT.value).putInt(2).putInt(0);
codeByteBuffer.put(OpCode.FIN_IMD.value);
execute(true);
assertTrue(state.getIsFinished());
assertTrue(state.getHadFatalError());
}
/** Check divide-by-zero is non-fatal because error handler is set. */
@Test
public void testMOD_DATzeroWithOnError() throws ExecutionException {
int errorAddr = 0x29;
codeByteBuffer.put(OpCode.ERR_ADR.value).putInt(errorAddr);
@ -671,6 +721,7 @@ public class DataOpCodeTests extends ExecutableTest {
// errorAddr:
assertEquals(errorAddr, codeByteBuffer.position());
// Set 1 at address 1 to indicate we handled error OK
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(1L);
codeByteBuffer.put(OpCode.FIN_IMD.value);

161
Java/src/test/java/FunctionCodeTests.java

@ -1,10 +1,10 @@
import static common.TestUtils.hexToBytes;
import static org.junit.Assert.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
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.Test;
@ -13,61 +13,38 @@ import common.ExecutableTest;
public class FunctionCodeTests extends ExecutableTest {
private static final String message = "The quick, brown fox jumped over the lazy dog.";
private static final byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);
private static final FunctionCode[] bSettingFunctions = new FunctionCode[] { FunctionCode.SET_B1, FunctionCode.SET_B2, FunctionCode.SET_B3, FunctionCode.SET_B4 };
@Test
public void testMD5() throws ExecutionException {
testHash("MD5", FunctionCode.MD5_INTO_B, "1388a82384756096e627e3671e2624bf");
}
private static final byte[] TEST_BYTES = "This string is exactly 32 bytes!".getBytes();
@Test
public void testCHECK_MD5() throws ExecutionException {
checkHash("MD5", FunctionCode.CHECK_MD5_WITH_B, "1388a82384756096e627e3671e2624bf");
}
@Test
public void testRMD160() throws ExecutionException {
testHash("RIPE-MD160", FunctionCode.RMD160_INTO_B, "b5a4b1898af3745dbbb5becb83e72787df9952c9");
}
public void testABGetSet() throws ExecutionException {
int sourceAddress = 2;
int destAddress = sourceAddress + MachineState.AB_REGISTER_SIZE / MachineState.VALUE_SIZE;
@Test
public void testCHECK_RMD160() throws ExecutionException {
checkHash("RIPE-MD160", FunctionCode.CHECK_RMD160_WITH_B, "b5a4b1898af3745dbbb5becb83e72787df9952c9");
}
@Test
public void testSHA256() throws ExecutionException {
testHash("SHA256", FunctionCode.SHA256_INTO_B, "c01d63749ebe5d6b16f7247015cac2e49a5ac4fb6c7f24bed07b8aa904da97f3");
}
@Test
public void testCHECK_SHA256() throws ExecutionException {
checkHash("SHA256", FunctionCode.CHECK_SHA256_WITH_B, "c01d63749ebe5d6b16f7247015cac2e49a5ac4fb6c7f24bed07b8aa904da97f3");
}
// Address of source bytes
dataByteBuffer.putLong(sourceAddress);
// Address where to save bytes
dataByteBuffer.putLong(destAddress);
@Test
public void testHASH160() throws ExecutionException {
testHash("HASH160", FunctionCode.HASH160_INTO_B, "54d54a03fd447996ab004dee87fab80bf9477e23");
}
// Data to load into A (or B)
assertEquals(sourceAddress * MachineState.VALUE_SIZE, dataByteBuffer.position());
dataByteBuffer.put(TEST_BYTES);
@Test
public void testCHECK_HASH160() throws ExecutionException {
checkHash("HASH160", FunctionCode.CHECK_HASH160_WITH_B, "54d54a03fd447996ab004dee87fab80bf9477e23");
}
// Data saved from A (or B)
assertEquals(destAddress * MachineState.VALUE_SIZE, dataByteBuffer.position());
@Test
public void testRandom() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(Timestamp.toLong(api.getCurrentBlockHeight(), 0));
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PUT_TX_AFTER_TIMESTAMP_IN_A.value).putInt(0);
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GENERATE_RANDOM_USING_TX_IN_A.value).putInt(1);
// Set A register using data pointed to by value held in address 0
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A_IND.value).putInt(0);
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.SWAP_A_AND_B.value);
// Save B register to data segment starting at value held in address 1
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.GET_B_IND.value).putInt(1);
codeByteBuffer.put(OpCode.FIN_IMD.value);
execute(false);
execute(true);
byte[] dest = new byte[TEST_BYTES.length];
getDataBytes(destAddress, dest);
assertTrue("Data wasn't copied correctly", Arrays.equals(TEST_BYTES, dest));
assertNotEquals("Random wasn't generated", 0L, getData(1));
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
}
@ -107,92 +84,4 @@ public class FunctionCodeTests extends ExecutableTest {
assertTrue(state.getHadFatalError());
}
private void testHash(String hashName, FunctionCode hashFunction, String expected) throws ExecutionException {
// Data addr 0 for setting values
dataByteBuffer.putLong(0L);
// Data addr 1 for results
dataByteBuffer.putLong(0L);
// Data addr 2 has start of message bytes (address 4)
dataByteBuffer.putLong(4L);
// Data addr 3 has length of message bytes
dataByteBuffer.putLong(messageBytes.length);
// Data addr 4+ for message
dataByteBuffer.put(messageBytes);
// Actual hash function
codeByteBuffer.put(OpCode.EXT_FUN_DAT_2.value).putShort(hashFunction.value).putInt(2).putInt(3);
// Hash functions usually put result into B, but we need it in A
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.SWAP_A_AND_B.value);
// Expected result goes into B
loadHashIntoB(expected);
// Check actual hash output (in A) with expected result (in B) and save equality output into address 1
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_A_EQUALS_B.value).putInt(1);
codeByteBuffer.put(OpCode.FIN_IMD.value);
execute(true);
assertTrue("MachineState isn't in finished state", state.getIsFinished());
assertFalse("MachineState encountered fatal error", state.getHadFatalError());
assertEquals(hashName + " hashes do not match", 1L, getData(1));
}
private void checkHash(String hashName, FunctionCode checkFunction, String expected) throws ExecutionException {
// Data addr 0 for setting values
dataByteBuffer.putLong(0L);
// Data addr 1 for results
dataByteBuffer.putLong(0L);
// Data addr 2 has start of message bytes (address 4)
dataByteBuffer.putLong(4L);
// Data addr 3 has length of message bytes
dataByteBuffer.putLong(messageBytes.length);
// Data addr 4+ for message
dataByteBuffer.put(messageBytes);
// Expected result goes into B
loadHashIntoB(expected);
codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.value).putShort(checkFunction.value).putInt(1).putInt(2).putInt(3);
codeByteBuffer.put(OpCode.FIN_IMD.value);
execute(true);
assertTrue("MachineState isn't in finished state", state.getIsFinished());
assertFalse("MachineState encountered fatal error", state.getHadFatalError());
assertEquals(hashName + " hashes do not match", 1L, getData(1));
}
private void loadHashIntoB(String expected) {
// Expected result goes into B
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.CLEAR_B.value);
// Each 16 hex-chars (8 bytes) fits into each B word (B1, B2, B3 and B4)
int numLongs = (expected.length() + 15) / 16;
for (int longIndex = 0; longIndex < numLongs; ++longIndex) {
final int endIndex = expected.length() - (numLongs - longIndex - 1) * 16;
final int beginIndex = Math.max(0, endIndex - 16);
String hexChars = expected.substring(beginIndex, endIndex);
codeByteBuffer.put(OpCode.SET_VAL.value);
codeByteBuffer.putInt(0); // addr 0
codeByteBuffer.put(new byte[8 - hexChars.length() / 2]); // pad LSB with zeros
codeByteBuffer.put(hexToBytes(hexChars));
final FunctionCode bSettingFunction = bSettingFunctions[longIndex];
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(bSettingFunction.value).putInt(0);
}
}
}

148
Java/src/test/java/HashingFunctionCodeTests.java

@ -0,0 +1,148 @@
import static common.TestUtils.hexToBytes;
import static org.junit.Assert.*;
import java.nio.charset.StandardCharsets;
import org.ciyam.at.ExecutionException;
import org.ciyam.at.FunctionCode;
import org.ciyam.at.OpCode;
import org.junit.Test;
import common.ExecutableTest;
public class HashingFunctionCodeTests extends ExecutableTest {
private static final String message = "The quick, brown fox jumped over the lazy dog.";
private static final byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);
private static final FunctionCode[] bSettingFunctions = new FunctionCode[] { FunctionCode.SET_B1, FunctionCode.SET_B2, FunctionCode.SET_B3, FunctionCode.SET_B4 };
@Test
public void testMD5() throws ExecutionException {
testHash("MD5", FunctionCode.MD5_INTO_B, "1388a82384756096e627e3671e2624bf");
}
@Test
public void testCHECK_MD5() throws ExecutionException {
checkHash("MD5", FunctionCode.CHECK_MD5_WITH_B, "1388a82384756096e627e3671e2624bf");
}
@Test
public void testRMD160() throws ExecutionException {
testHash("RIPE-MD160", FunctionCode.RMD160_INTO_B, "b5a4b1898af3745dbbb5becb83e72787df9952c9");
}
@Test
public void testCHECK_RMD160() throws ExecutionException {
checkHash("RIPE-MD160", FunctionCode.CHECK_RMD160_WITH_B, "b5a4b1898af3745dbbb5becb83e72787df9952c9");
}
@Test
public void testSHA256() throws ExecutionException {
testHash("SHA256", FunctionCode.SHA256_INTO_B, "c01d63749ebe5d6b16f7247015cac2e49a5ac4fb6c7f24bed07b8aa904da97f3");
}
@Test
public void testCHECK_SHA256() throws ExecutionException {
checkHash("SHA256", FunctionCode.CHECK_SHA256_WITH_B, "c01d63749ebe5d6b16f7247015cac2e49a5ac4fb6c7f24bed07b8aa904da97f3");
}
@Test
public void testHASH160() throws ExecutionException {
testHash("HASH160", FunctionCode.HASH160_INTO_B, "54d54a03fd447996ab004dee87fab80bf9477e23");
}
@Test
public void testCHECK_HASH160() throws ExecutionException {
checkHash("HASH160", FunctionCode.CHECK_HASH160_WITH_B, "54d54a03fd447996ab004dee87fab80bf9477e23");
}
private void testHash(String hashName, FunctionCode hashFunction, String expected) throws ExecutionException {
// Data addr 0 for setting values
dataByteBuffer.putLong(0L);
// Data addr 1 for results
dataByteBuffer.putLong(0L);
// Data addr 2 has start of message bytes (address 4)
dataByteBuffer.putLong(4L);
// Data addr 3 has length of message bytes
dataByteBuffer.putLong(messageBytes.length);
// Data addr 4+ for message
dataByteBuffer.put(messageBytes);
// Actual hash function
codeByteBuffer.put(OpCode.EXT_FUN_DAT_2.value).putShort(hashFunction.value).putInt(2).putInt(3);
// Hash functions usually put result into B, but we need it in A
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.SWAP_A_AND_B.value);
// Expected result goes into B
loadHashIntoB(expected);
// Check actual hash output (in A) with expected result (in B) and save equality output into address 1
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.CHECK_A_EQUALS_B.value).putInt(1);
codeByteBuffer.put(OpCode.FIN_IMD.value);
execute(true);
assertTrue("MachineState isn't in finished state", state.getIsFinished());
assertFalse("MachineState encountered fatal error", state.getHadFatalError());
assertEquals(hashName + " hashes do not match", 1L, getData(1));
}
private void checkHash(String hashName, FunctionCode checkFunction, String expected) throws ExecutionException {
// Data addr 0 for setting values
dataByteBuffer.putLong(0L);
// Data addr 1 for results
dataByteBuffer.putLong(0L);
// Data addr 2 has start of message bytes (address 4)
dataByteBuffer.putLong(4L);
// Data addr 3 has length of message bytes
dataByteBuffer.putLong(messageBytes.length);
// Data addr 4+ for message
dataByteBuffer.put(messageBytes);
// Expected result goes into B
loadHashIntoB(expected);
codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.value).putShort(checkFunction.value).putInt(1).putInt(2).putInt(3);
codeByteBuffer.put(OpCode.FIN_IMD.value);
execute(true);
assertTrue("MachineState isn't in finished state", state.getIsFinished());
assertFalse("MachineState encountered fatal error", state.getHadFatalError());
assertEquals(hashName + " hashes do not match", 1L, getData(1));
}
private void loadHashIntoB(String expected) {
// Expected result goes into B
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.CLEAR_B.value);
// Each 16 hex-chars (8 bytes) fits into each B word (B1, B2, B3 and B4)
int numLongs = (expected.length() + 15) / 16;
for (int longIndex = 0; longIndex < numLongs; ++longIndex) {
final int endIndex = expected.length() - (numLongs - longIndex - 1) * 16;
final int beginIndex = Math.max(0, endIndex - 16);
String hexChars = expected.substring(beginIndex, endIndex);
codeByteBuffer.put(OpCode.SET_VAL.value);
codeByteBuffer.putInt(0); // addr 0
codeByteBuffer.put(new byte[8 - hexChars.length() / 2]); // pad LSB with zeros
codeByteBuffer.put(hexToBytes(hexChars));
final FunctionCode bSettingFunction = bSettingFunctions[longIndex];
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(bSettingFunction.value).putInt(0);
}
}
}

9
Java/src/test/java/MiscTests.java

@ -7,6 +7,7 @@ import org.ciyam.at.OpCode;
import org.junit.Test;
import common.ExecutableTest;
import common.TestAPI;
import common.TestUtils;
public class MiscTests extends ExecutableTest {
@ -40,8 +41,9 @@ public class MiscTests extends ExecutableTest {
// Infinite loop
codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(0);
// If starting balance is 1234 then should take about 3 rounds as 500 steps max each round.
for (int i = 0; i < 3; ++i)
// We need enough rounds to exhaust balance
long minRounds = TestAPI.DEFAULT_INITIAL_BALANCE / TestAPI.MAX_STEPS_PER_ROUND + 1;
for (long i = 0; i < minRounds; ++i)
execute(true);
assertTrue(state.getIsFrozen());
@ -52,7 +54,8 @@ public class MiscTests extends ExecutableTest {
@Test
public void testMinActivation() throws ExecutionException {
long minActivationAmount = 12345L; // 0x0000000000003039
// Make sure minimum activation amount is greater than initial balance
long minActivationAmount = TestAPI.DEFAULT_INITIAL_BALANCE * 2L;
byte[] headerBytes = TestUtils.toHeaderBytes(TestUtils.VERSION, TestUtils.NUM_CODE_PAGES, TestUtils.NUM_DATA_PAGES, TestUtils.NUM_CALL_STACK_PAGES, TestUtils.NUM_USER_STACK_PAGES, minActivationAmount);
byte[] codeBytes = codeByteBuffer.array();

70
Java/src/test/java/SerializationTests.java

@ -1,41 +1,18 @@
import static common.TestUtils.hexToBytes;
import static org.junit.Assert.*;
import java.nio.ByteBuffer;
import java.util.Arrays;
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.Test;
import common.TestAPI;
import common.TestLogger;
import common.ExecutableTest;
import common.TestUtils;
public class SerializationTests {
public TestLogger logger;
public TestAPI api;
public MachineState state;
public ByteBuffer codeByteBuffer;
@Before
public void beforeTest() {
logger = new TestLogger();
api = new TestAPI();
codeByteBuffer = ByteBuffer.allocate(512);
}
@After
public void afterTest() {
codeByteBuffer = null;
api = null;
logger = null;
}
public class SerializationTests extends ExecutableTest {
private byte[] simulate() {
byte[] headerBytes = TestUtils.HEADER_BYTES;
@ -60,52 +37,85 @@ public class SerializationTests {
private byte[] executeAndCheck(MachineState state) {
state.execute();
byte[] stateBytes = state.toBytes();
// 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;
}
/** Test serialization of state with stop address. */
@Test
public void testPCS2() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000011111111"));
codeByteBuffer.put(OpCode.SET_PCS.value);
int expectedStopAddress = codeByteBuffer.position();
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).put(hexToBytes("0000000022222222"));
codeByteBuffer.put(OpCode.FIN_IMD.value);
simulate();
assertEquals(0x0e, (int) state.getOnStopAddress());
assertEquals(expectedStopAddress, (int) state.getOnStopAddress());
assertTrue(state.getIsFinished());
assertFalse(state.getHadFatalError());
}
/** Test serialization of state with data pushed onto user stack. */
@Test
public void testStopWithStacks() throws ExecutionException {
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(100); // 0000
long initialValue = 100L;
long increment = 10L;
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(0).putLong(initialValue); // 0000
codeByteBuffer.put(OpCode.SET_PCS.value); // 000d
int expectedStopAddress = codeByteBuffer.position();
codeByteBuffer.put(OpCode.JMP_SUB.value).putInt(0x002a); // 000e
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(10); // 0013
codeByteBuffer.put(OpCode.SET_VAL.value).putInt(1).putLong(increment); // 0013
codeByteBuffer.put(OpCode.ADD_DAT.value).putInt(0).putInt(1); // 0020
codeByteBuffer.put(OpCode.STP_IMD.value); // 0029
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.ECHO.value).putInt(0); // 002a
codeByteBuffer.put(OpCode.PSH_DAT.value).putInt(0); // 0031
codeByteBuffer.put(OpCode.RET_SUB.value); // 0036
byte[] savedState = simulate();
assertEquals(0x0e, (int) state.getOnStopAddress());
assertEquals(expectedStopAddress, (int) state.getOnStopAddress());
assertTrue(state.getIsStopped());
assertFalse(state.getHadFatalError());
// Just after first STP_IMD we expect address 0 to be initialValue + increment
long expectedValue = initialValue + increment;
assertEquals(expectedValue, getData(0));
// Perform another execution round
savedState = continueSimulation(savedState);
expectedValue += increment;
assertEquals(expectedValue, getData(0));
savedState = continueSimulation(savedState);
expectedValue += increment;
assertEquals(expectedValue, getData(0));
}
}

349
Java/src/test/java/common/ACCTAPI.java

@ -1,349 +0,0 @@
package common;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
import org.ciyam.at.API;
import org.ciyam.at.ExecutionException;
import org.ciyam.at.FunctionData;
import org.ciyam.at.IllegalFunctionCodeException;
import org.ciyam.at.MachineState;
import org.ciyam.at.OpCode;
import org.ciyam.at.Timestamp;
public class ACCTAPI extends API {
private class Account {
public String address;
public long balance;
public Account(String address, long amount) {
this.address = address;
this.balance = amount;
}
}
private class Transaction {
public int txType;
public String creator;
public String recipient;
public long amount;
public long[] message;
}
private class Block {
public List<Transaction> transactions;
public Block() {
this.transactions = new ArrayList<Transaction>();
}
}
//
private List<Block> blockchain;
private Map<String, Account> accounts;
private long balanceAT;
//
public ACCTAPI() {
// build blockchain
this.blockchain = new ArrayList<Block>();
Block genesisBlock = new Block();
this.blockchain.add(genesisBlock);
// generate accounts
this.accounts = new HashMap<String, Account>();
Account initiator = new Account("Initiator", 0);
this.accounts.put(initiator.address, initiator);
Account responder = new Account("Responder", 10000);
this.accounts.put(responder.address, responder);
Account bystander = new Account("Bystander", 999);
this.accounts.put(bystander.address, bystander);
Account creator = new Account("Creator", 0);
this.accounts.put(creator.address, creator);
this.balanceAT = 50000;
}
public void generateNextBlock(byte[] secret) {
Random random = new Random();
Block block = new Block();
System.out.println("Block " + (this.blockchain.size() + 1));
int transactionCount = random.nextInt(5);
for (int i = 0; i < transactionCount; ++i) {
Transaction transaction = new Transaction();
transaction.txType = random.nextInt(2);
switch (transaction.txType) {
case 0: // payment
transaction.amount = random.nextInt(1000);
System.out.print("Payment Tx [" + transaction.amount + "]");
break;
case 1: // message
System.out.print("Message Tx [");
transaction.message = new long[4];
if (random.nextInt(3) == 0) {
// correct message
transaction.message[0] = fromBytes(secret, 0);
transaction.message[1] = fromBytes(secret, 8);
transaction.message[2] = fromBytes(secret, 16);
transaction.message[3] = fromBytes(secret, 24);
} else {
// incorrect message
transaction.message[0] = 0xdeadbeefdeadbeefL;
transaction.message[1] = 0xdeadbeefdeadbeefL;
transaction.message[2] = 0xdeadbeefdeadbeefL;
transaction.message[3] = 0xdeadbeefdeadbeefL;
}
System.out.print(String.format("%016x", transaction.message[0]));
System.out.print(String.format("%016x", transaction.message[1]));
System.out.print(String.format("%016x", transaction.message[2]));
System.out.print(String.format("%016x", transaction.message[3]));
System.out.print("]");
break;
}
transaction.creator = getRandomAccount();
transaction.recipient = getRandomAccount();
System.out.println(" from " + transaction.creator + " to " + transaction.recipient);
block.transactions.add(transaction);
}
this.blockchain.add(block);
}
/** Convert long to big-endian byte array */
@SuppressWarnings("unused")
private byte[] toByteArray(long value) {
return new byte[] { (byte) (value >> 56), (byte) (value >> 48), (byte) (value >> 40), (byte) (value >> 32),
(byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) (value) };
}
/** Convert part of big-endian byte[] to long */
private long fromBytes(byte[] bytes, int start) {
return (bytes[start] & 0xffL) << 56 | (bytes[start + 1] & 0xffL) << 48 | (bytes[start + 2] & 0xffL) << 40 | (bytes[start + 3] & 0xffL) << 32
| (bytes[start + 4] & 0xffL) << 24 | (bytes[start + 5] & 0xffL) << 16 | (bytes[start + 6] & 0xffL) << 8 | (bytes[start + 7] & 0xffL);
}
private String getRandomAccount() {
int numAccounts = this.accounts.size();
int accountIndex = new Random().nextInt(numAccounts);
List<Account> accounts = this.accounts.values().stream().collect(Collectors.toList());
return accounts.get(accountIndex).address;
}
@Override
public int getMaxStepsPerRound() {
return 500;
}
@Override
public int getOpCodeSteps(OpCode opcode) {
return 1;
}
@Override
public long getFeePerStep() {
return 1L;
}
@Override
public int getCurrentBlockHeight() {
return this.blockchain.size();
}
@Override
public int getATCreationBlockHeight(MachineState state) {
return 1;
}
@Override
public void putPreviousBlockHashInA(MachineState state) {
this.setA1(state, this.blockchain.size() - 1);
this.setA2(state, state.getA1());
this.setA3(state, state.getA1());
this.setA4(state, state.getA1());
}
@Override
public void putTransactionAfterTimestampInA(Timestamp timestamp, MachineState state) {
int blockHeight = timestamp.blockHeight;
int transactionSequence = timestamp.transactionSequence + 1;
while (blockHeight <= this.blockchain.size()) {
Block block = this.blockchain.get(blockHeight - 1);
List<Transaction> transactions = block.transactions;
if (transactionSequence > transactions.size() - 1) {
// No more transactions at this height
++blockHeight;
transactionSequence = 0;
continue;
}
Transaction transaction = transactions.get(transactionSequence);
if (transaction.recipient.equals("Initiator")) {
// Found a transaction
System.out.println("Found transaction at height " + blockHeight + " sequence " + transactionSequence);
// Generate pseudo-hash of transaction
this.setA1(state, new Timestamp(blockHeight, transactionSequence).longValue());
this.setA2(state, state.getA1());
this.setA3(state, state.getA1());
this.setA4(state, state.getA1());
return;
}
++transactionSequence;
}
// Nothing found
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.getA1());
Block block = this.blockchain.get(timestamp.blockHeight - 1);
Transaction transaction = block.transactions.get(timestamp.transactionSequence);
return transaction.txType;
}
@Override
public long getAmountFromTransactionInA(MachineState state) {
Timestamp timestamp = new Timestamp(state.getA1());
Block block = this.blockchain.get(timestamp.blockHeight - 1);
Transaction transaction = block.transactions.get(timestamp.transactionSequence);
return transaction.amount;
}
@Override
public long getTimestampFromTransactionInA(MachineState state) {
// Transaction hash in A is actually just 4 copies of transaction's "timestamp"
Timestamp timestamp = new Timestamp(state.getA1());
return timestamp.longValue();
}
@Override
public long generateRandomUsingTransactionInA(MachineState state) {
// NOT USED
return 0L;
}
@Override
public void putMessageFromTransactionInAIntoB(MachineState state) {
Timestamp timestamp = new Timestamp(state.getA1());
Block block = this.blockchain.get(timestamp.blockHeight - 1);
Transaction transaction = block.transactions.get(timestamp.transactionSequence);
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.getA1());
Block block = this.blockchain.get(timestamp.blockHeight - 1);
Transaction transaction = block.transactions.get(timestamp.transactionSequence);
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
this.setB1(state, "C".charAt(0));
this.setB2(state, state.getB1());
this.setB3(state, state.getB1());
this.setB4(state, state.getB1());
}
@Override
public long getCurrentBalance(MachineState state) {
return this.balanceAT;
}
public void setCurrentBalance(long balance) {
this.balanceAT = balance;
System.out.println("New AT balance: " + balance);
}
@Override
public void payAmountToB(long amount, MachineState state) {
// Determine recipient using first char in B1
char firstChar = String.format("%c", (byte) state.getB1()).charAt(0);
Account recipient = this.accounts.values().stream().filter((account) -> account.address.charAt(0) == firstChar).findFirst().get();
// Simulate payment
recipient.balance += amount;
System.out.println("Paid " + amount + " to " + recipient.address + ", their balance now: " + recipient.balance);
// For debugging, output our new balance
long balance = state.getCurrentBalance() - amount;
System.out.println("Our balance now: " + balance);
}
@Override
public void messageAToB(MachineState state) {
// NOT USED
}
@Override
public long addMinutesToTimestamp(Timestamp timestamp, long minutes, MachineState state) {
timestamp.blockHeight += (int) minutes;
return timestamp.longValue();
}
@Override
public void onFinished(long amount, MachineState state) {
System.out.println("Finished - refunding remaining to creator");
Account creator = this.accounts.get("Creator");
creator.balance += amount;
System.out.println("Paid " + amount + " to " + creator.address + ", their balance now: " + creator.balance);
}
@Override
public void onFatalError(MachineState state, ExecutionException e) {
System.out.println("Fatal error: " + e.getMessage());
System.out.println("No error address set - will refund to creator and finish");
}
@Override
public void platformSpecificPreExecuteCheck(int paramCount, boolean returnValueExpected, MachineState state, short rawFunctionCode)
throws IllegalFunctionCodeException {
// NOT USED
}
@Override
public void platformSpecificPostCheckExecute(FunctionData functionData, MachineState state, short rawFunctionCode) throws ExecutionException {
// NOT USED
}
}

11
Java/src/test/java/common/ExecutableTest.java

@ -88,6 +88,10 @@ public abstract class ExecutableTest {
api.bumpCurrentBlockHeight();
} while (!onceOnly && !state.getIsFinished());
unwrapState(state);
}
protected byte[] unwrapState(MachineState state) {
// Ready for diagnosis
byte[] stateBytes = state.toBytes();
@ -98,6 +102,8 @@ public abstract class ExecutableTest {
callStackSize = stateByteBuffer.getInt(CALL_STACK_OFFSET);
userStackOffset = CALL_STACK_OFFSET + 4 + callStackSize;
userStackSize = stateByteBuffer.getInt(userStackOffset);
return stateBytes;
}
protected long getData(int address) {
@ -105,6 +111,11 @@ public abstract class ExecutableTest {
return stateByteBuffer.getLong(index);
}
protected void getDataBytes(int address, byte[] dest) {
int index = DATA_OFFSET + address * MachineState.VALUE_SIZE;
stateByteBuffer.slice().position(index).get(dest);
}
protected int getCallStackPosition() {
return TestUtils.NUM_CALL_STACK_PAGES * MachineState.ADDRESS_SIZE - callStackSize;
}

316
Java/src/test/java/common/TestAPI.java

@ -1,5 +1,13 @@
package common;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.ciyam.at.API;
import org.ciyam.at.ExecutionException;
import org.ciyam.at.FunctionData;
@ -10,29 +18,192 @@ import org.ciyam.at.Timestamp;
public class TestAPI extends API {
private static final int BLOCK_PERIOD = 10 * 60; // average period between blocks in seconds
/** Average period between blocks, in seconds. */
public static final int BLOCK_PERIOD = 10 * 60;
/** Maximum number of steps before auto-sleep. */
public static final int MAX_STEPS_PER_ROUND = 500;
/** Op-code step multiplier for calling functions. */
public static final int STEPS_PER_FUNCTION_CALL = 10;
/** Initial balance for simple test scenarios. */
public static final long DEFAULT_INITIAL_BALANCE = 1234L;
/** Initial block height for simple test scenarios. */
public static final int DEFAULT_INITIAL_BLOCK_HEIGHT = 10;
/** AT creation block height for simple test scenarios. */
public static final int DEFAULT_AT_CREATION_BLOCK_HEIGHT = 8;
public static final String AT_CREATOR_ADDRESS = "AT Creator";
public static final String AT_ADDRESS = "AT";
private static final Random RANDOM = new Random();
public static class TestAccount {
public String address;
public long balance;
public List<byte[]> messages = new ArrayList<>();
public TestAccount(String address, long amount) {
this.address = address;
this.balance = amount;
}
public void addToMap(Map<String, TestAccount> map) {
map.put(this.address, this);
}
}
public static class TestTransaction {
public long timestamp; // block height & sequence
public byte[] txHash;
public API.ATTransactionType txType;
public String sender;
public String recipient;
public long amount;
public byte[] message;
private TestTransaction(byte[] txHash, API.ATTransactionType txType, String creator, String recipient) {
this.txHash = txHash;
this.txType = txType;
this.sender = creator;
this.recipient = recipient;
}
public TestTransaction(byte[] txHash, String creator, String recipient, long amount) {
this(txHash, API.ATTransactionType.PAYMENT, creator, recipient);
this.amount = amount;
}
public TestTransaction(byte[] txHash, String creator, String recipient, byte[] message) {
this(txHash, API.ATTransactionType.MESSAGE, creator, recipient);
this.message = new byte[32];
System.arraycopy(message, 0, this.message, 0, message.length);
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
}
public static class TestBlock {
public byte[] blockHash;
public List<TestTransaction> transactions = new ArrayList<TestTransaction>();
public TestBlock() {
this.blockHash = new byte[32];
RANDOM.nextBytes(this.blockHash);
}
public TestBlock(byte[] blockHash) {
this.blockHash = new byte[32];
System.arraycopy(this.blockHash, 0, blockHash, 0, blockHash.length);
}
}
public List<TestBlock> blockchain;
public Map<String, TestAccount> accounts;
public Map<String, TestTransaction> transactions;
private int currentBlockHeight;
private long currentBalance;
public TestAPI() {
this.currentBlockHeight = 10;
this.currentBalance = 1234L;
this.currentBlockHeight = DEFAULT_INITIAL_BLOCK_HEIGHT;
// Fill block chain from block 1 to initial height with empty blocks
blockchain = new ArrayList<>();
for (int h = 1; h <= this.currentBlockHeight; ++h)
blockchain.add(new TestBlock());
// Set up test accounts
accounts = new HashMap<>();
new TestAccount(AT_CREATOR_ADDRESS, 1000000L).addToMap(accounts);
new TestAccount(AT_ADDRESS, DEFAULT_INITIAL_BALANCE).addToMap(accounts);
new TestAccount("Initiator", 100000L).addToMap(accounts);
new TestAccount("Responder", 200000L).addToMap(accounts);
new TestAccount("Bystander", 300000L).addToMap(accounts);
transactions = new HashMap<>();
}
public void bumpCurrentBlockHeight() {
++this.currentBlockHeight;
}
public void setCurrentBlockHeight(int blockHeight) {
if (blockHeight > blockchain.size())
throw new IllegalStateException("Refusing to set current block height to beyond blockchain end");
this.currentBlockHeight = blockHeight;
}
private void generateBlock(boolean withTransactions, boolean includeTransactionToAt) {
TestBlock newBlock = new TestBlock();
blockchain.add(newBlock);
if (!withTransactions)
return;
TestAccount atAccount = accounts.get(AT_ADDRESS);
List<TestAccount> senderAccounts = new ArrayList<>(accounts.values());
List<TestAccount> recipientAccounts = new ArrayList<>(accounts.values());
if (!includeTransactionToAt)
recipientAccounts.remove(atAccount);
boolean includesAtTransaction = false;
int transactionCount = 8 + RANDOM.nextInt(8);
for (int i = 0; i < transactionCount || includeTransactionToAt && !includesAtTransaction; ++i) {
// Pick random sender
TestAccount sender = senderAccounts.get(RANDOM.nextInt(senderAccounts.size()));
// Pick random recipient
TestAccount recipient = recipientAccounts.get(RANDOM.nextInt(recipientAccounts.size()));
// Pick random transaction type
API.ATTransactionType txType = API.ATTransactionType.valueOf(RANDOM.nextInt(2));
// Generate tx hash
byte[] txHash = new byte[32];
RANDOM.nextBytes(txHash);
TestTransaction transaction;
if (txType == API.ATTransactionType.PAYMENT) {
long amount = RANDOM.nextInt(100); // small amounts
transaction = new TestTransaction(txHash, sender.address, recipient.address, amount);
} else {
byte[] message = new byte[32];
RANDOM.nextBytes(message);
transaction = new TestTransaction(txHash, sender.address, recipient.address, message);
}
transaction.timestamp = Timestamp.toLong(blockchain.size(), newBlock.transactions.size());
transactions.put(new String(txHash, StandardCharsets.ISO_8859_1), transaction);
newBlock.transactions.add(transaction);
if (recipient.address.equals(AT_ADDRESS))
includesAtTransaction = true;
}
}
public void generateEmptyBlock() {
generateBlock(false, false);
}
public void generateBlockWithNonAtTransactions() {
generateBlock(true, false);
}
public void generateBlockWithAtTransaction() {
generateBlock(true, true);
}
@Override
public int getMaxStepsPerRound() {
return 500;
return MAX_STEPS_PER_ROUND;
}
@Override
public int getOpCodeSteps(OpCode opcode) {
if (opcode.value >= OpCode.EXT_FUN.value && opcode.value <= OpCode.EXT_FUN_RET_DAT_2.value)
return 10;
return STEPS_PER_FUNCTION_CALL;
return 1;
}
@ -49,39 +220,75 @@ public class TestAPI extends API {
@Override
public int getATCreationBlockHeight(MachineState state) {
return 5;
return DEFAULT_AT_CREATION_BLOCK_HEIGHT;
}
@Override
public void putPreviousBlockHashInA(MachineState state) {
this.setA1(state, 9L);
this.setA2(state, 9L);
this.setA3(state, 9L);
this.setA4(state, 9L);
public void putPreviousBlockHashIntoA(MachineState state) {
int previousBlockHeight = this.currentBlockHeight - 1;
this.setA(state, blockchain.get(previousBlockHeight - 1).blockHash);
}
@Override
public void putTransactionAfterTimestampInA(Timestamp timestamp, MachineState state) {
// Cycle through transactions: 1 -> 2 -> 3 -> 0 -> 1 ...
this.setA1(state, (timestamp.transactionSequence + 1) % 4);
this.setA2(state, state.getA1());
this.setA3(state, state.getA1());
this.setA4(state, state.getA1());
public void putTransactionAfterTimestampIntoA(Timestamp timestamp, MachineState state) {
int blockHeight = timestamp.blockHeight;
int transactionSequence = timestamp.transactionSequence + 1;
while (blockHeight <= this.currentBlockHeight) {
TestBlock block = this.blockchain.get(blockHeight - 1);
List<TestTransaction> transactions = block.transactions;
if (transactionSequence > transactions.size() - 1) {
// No more transactions at this height
++blockHeight;
transactionSequence = 0;
continue;
}
TestTransaction transaction = transactions.get(transactionSequence);
if (transaction.recipient.equals("AT")) {
// Found a transaction
System.out.println("Found transaction at height " + blockHeight + " sequence " + transactionSequence);
// Generate pseudo-hash of transaction
this.setA(state, transaction.txHash);
return;
}
++transactionSequence;
}
// Nothing found
this.setA(state, new byte[32]);
}
public TestTransaction getTransactionFromA(MachineState state) {
byte[] aBytes = state.getA();
String txHashString = new String(aBytes, StandardCharsets.ISO_8859_1); // ISO_8859_1 for simplistic 8-bit encoding
return transactions.get(txHashString);
}
@Override
public long getTypeFromTransactionInA(MachineState state) {
return 0L;
TestTransaction transaction = getTransactionFromA(state);
return transaction.txType.value;
}
@Override
public long getAmountFromTransactionInA(MachineState state) {
return 123L;
TestTransaction transaction = getTransactionFromA(state);
if (transaction.txType != API.ATTransactionType.PAYMENT)
return 0L;
return transaction.amount;
}
@Override
public long getTimestampFromTransactionInA(MachineState state) {
return 1536227162000L;
TestTransaction transaction = getTransactionFromA(state);
return transaction.timestamp;
}
@Override
@ -106,46 +313,71 @@ public class TestAPI extends API {
@Override
public void putMessageFromTransactionInAIntoB(MachineState state) {
this.setB1(state, state.getA4());
this.setB2(state, state.getA3());
this.setB3(state, state.getA2());
this.setB4(state, state.getA1());
TestTransaction transaction = getTransactionFromA(state);
if (transaction.txType != API.ATTransactionType.MESSAGE)
return;
this.setB(state, transaction.message);
}
@Override
public void putAddressFromTransactionInAIntoB(MachineState state) {
// Dummy address
this.setB1(state, 0xaaaaaaaaaaaaaaaaL);
this.setB2(state, 0xaaaaaaaaaaaaaaaaL);
this.setB3(state, 0xaaaaaaaaaaaaaaaaL);
this.setB4(state, 0xaaaaaaaaaaaaaaaaL);
TestTransaction transaction = getTransactionFromA(state);
byte[] bBytes = new byte[32];
System.arraycopy(transaction.sender.getBytes(), 0, bBytes, 0, transaction.sender.length());
this.setB(state, bBytes);
}
@Override
public void putCreatorAddressIntoB(MachineState state) {
// Dummy creator
this.setB1(state, 0xccccccccccccccccL);
this.setB2(state, 0xccccccccccccccccL);
this.setB3(state, 0xccccccccccccccccL);
this.setB4(state, 0xccccccccccccccccL);
byte[] bBytes = new byte[32];
System.arraycopy(AT_CREATOR_ADDRESS.getBytes(), 0, bBytes, 0, AT_CREATOR_ADDRESS.length());
this.setB(state, bBytes);
}
@Override
public long getCurrentBalance(MachineState state) {
return this.currentBalance;
return this.accounts.get(AT_ADDRESS).balance;
}
// Debugging only
public void setCurrentBalance(long currentBalance) {
this.currentBalance = currentBalance;
this.accounts.get(AT_ADDRESS).balance = currentBalance;
System.out.println(String.format("New AT balance: %s", prettyAmount(currentBalance)));
}
@Override
public void payAmountToB(long amount, MachineState state) {
if (amount <= 0)
return;
byte[] bBytes = state.getB();
String address = new String(bBytes, StandardCharsets.ISO_8859_1);
address = address.replace("\0", "");
TestAccount recipient = accounts.get(address);
if (recipient == null)
throw new IllegalStateException("Refusing to pay to unknown account: " + address);
System.out.println(String.format("Paid %s to %s, their balance now: %s", prettyAmount(amount), recipient.address, recipient.balance));
recipient.balance += amount;
TestAccount atAccount = accounts.get(AT_ADDRESS);
atAccount.balance -= amount;
System.out.println(String.format("AT balance now: %s", atAccount.balance));
}
@Override
public void messageAToB(MachineState state) {
byte[] bBytes = state.getB();
String address = new String(bBytes, StandardCharsets.ISO_8859_1);
address = address.replace("\0", "");
TestAccount recipient = accounts.get(address);
if (recipient == null)
throw new IllegalStateException("Refusing to send message to unknown account: " + address);
recipient.messages.add(state.getA());
}
@Override
@ -157,6 +389,12 @@ public class TestAPI extends API {
@Override
public void onFinished(long amount, MachineState state) {
System.out.println("Finished - refunding remaining to creator");
TestAccount atCreatorAccount = accounts.get(AT_CREATOR_ADDRESS);
atCreatorAccount.balance += amount;
System.out.println(String.format("Paid %s to creator %s, their balance now: %s", prettyAmount(amount), atCreatorAccount.address, atCreatorAccount.balance));
accounts.get(AT_ADDRESS).balance -= amount;
}
@Override
@ -219,4 +457,8 @@ public class TestAPI extends API {
}
}
public static String prettyAmount(long amount) {
return BigDecimal.valueOf(amount, 8).toPlainString();
}
}

Loading…
Cancel
Save