mirror of
https://github.com/Qortal/AT.git
synced 2025-01-30 02:42:14 +00:00
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.
This commit is contained in:
parent
92281a1d04
commit
36c63b0be0
@ -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);
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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
Normal file
219
Java/src/test/java/BlockchainFunctionCodeTests.java
Normal file
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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_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 {
|
||||
// Note: non-fatal error because error handler IS set
|
||||
|
||||
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_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 {
|
||||
// Note: non-fatal error because error handler IS set
|
||||
|
||||
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);
|
||||
|
||||
|
@ -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 };
|
||||
private static final byte[] TEST_BYTES = "This string is exactly 32 bytes!".getBytes();
|
||||
|
||||
@Test
|
||||
public void testMD5() throws ExecutionException {
|
||||
testHash("MD5", FunctionCode.MD5_INTO_B, "1388a82384756096e627e3671e2624bf");
|
||||
}
|
||||
public void testABGetSet() throws ExecutionException {
|
||||
int sourceAddress = 2;
|
||||
int destAddress = sourceAddress + MachineState.AB_REGISTER_SIZE / MachineState.VALUE_SIZE;
|
||||
|
||||
@Test
|
||||
public void testCHECK_MD5() throws ExecutionException {
|
||||
checkHash("MD5", FunctionCode.CHECK_MD5_WITH_B, "1388a82384756096e627e3671e2624bf");
|
||||
}
|
||||
// Address of source bytes
|
||||
dataByteBuffer.putLong(sourceAddress);
|
||||
// Address where to save bytes
|
||||
dataByteBuffer.putLong(destAddress);
|
||||
|
||||
@Test
|
||||
public void testRMD160() throws ExecutionException {
|
||||
testHash("RIPE-MD160", FunctionCode.RMD160_INTO_B, "b5a4b1898af3745dbbb5becb83e72787df9952c9");
|
||||
}
|
||||
// Data to load into A (or B)
|
||||
assertEquals(sourceAddress * MachineState.VALUE_SIZE, dataByteBuffer.position());
|
||||
dataByteBuffer.put(TEST_BYTES);
|
||||
|
||||
@Test
|
||||
public void testCHECK_RMD160() throws ExecutionException {
|
||||
checkHash("RIPE-MD160", FunctionCode.CHECK_RMD160_WITH_B, "b5a4b1898af3745dbbb5becb83e72787df9952c9");
|
||||
}
|
||||
// Data saved from A (or B)
|
||||
assertEquals(destAddress * MachineState.VALUE_SIZE, dataByteBuffer.position());
|
||||
|
||||
@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");
|
||||
}
|
||||
|
||||
@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
Normal file
148
Java/src/test/java/HashingFunctionCodeTests.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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…
Reference in New Issue
Block a user