mirror of
https://github.com/Qortal/AT.git
synced 2025-01-30 02:42:14 +00:00
Added more blockchain-related FunctionCode tests & improved TestAPI accordingly.
This commit is contained in:
parent
ca7ec689f7
commit
e0fe988b71
@ -1,15 +1,19 @@
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
import org.ciyam.at.API;
|
||||
import org.ciyam.at.ExecutionException;
|
||||
import org.ciyam.at.FunctionCode;
|
||||
import org.ciyam.at.MachineState;
|
||||
import org.ciyam.at.OpCode;
|
||||
import org.ciyam.at.Timestamp;
|
||||
import org.junit.Test;
|
||||
|
||||
import common.ExecutableTest;
|
||||
import common.TestAPI;
|
||||
import common.TestAPI.TestAccount;
|
||||
import common.TestAPI.TestBlock;
|
||||
import common.TestAPI.TestTransaction;
|
||||
|
||||
@ -131,13 +135,20 @@ public class BlockchainFunctionCodeTests extends ExecutableTest {
|
||||
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();
|
||||
TestBlock newBlock = api.generateBlockWithNonAtTransactions();
|
||||
api.addBlockToChain(newBlock);
|
||||
api.bumpCurrentBlockHeight();
|
||||
|
||||
int currentBlockHeight = api.blockchain.size();
|
||||
api.setCurrentBlockHeight(currentBlockHeight);
|
||||
newBlock = api.generateBlockWithNonAtTransactions();
|
||||
api.addBlockToChain(newBlock);
|
||||
api.bumpCurrentBlockHeight();
|
||||
|
||||
// Generate a block containing transaction to AT
|
||||
newBlock = api.generateBlockWithAtTransaction();
|
||||
api.addBlockToChain(newBlock);
|
||||
api.bumpCurrentBlockHeight();
|
||||
|
||||
int currentBlockHeight = api.getCurrentBlockHeight();
|
||||
|
||||
// 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);
|
||||
@ -162,9 +173,12 @@ public class BlockchainFunctionCodeTests extends ExecutableTest {
|
||||
dataByteBuffer.putLong(initialTimestamp);
|
||||
|
||||
// Generate a block containing transaction to AT
|
||||
api.generateBlockWithAtTransaction();
|
||||
TestBlock newBlock = api.generateBlockWithAtTransaction();
|
||||
api.addBlockToChain(newBlock);
|
||||
api.bumpCurrentBlockHeight();
|
||||
api.generateBlockWithAtTransaction();
|
||||
|
||||
newBlock = api.generateBlockWithAtTransaction();
|
||||
api.addBlockToChain(newBlock);
|
||||
api.bumpCurrentBlockHeight();
|
||||
|
||||
long expectedTransactionsCount = 0;
|
||||
@ -199,6 +213,100 @@ public class BlockchainFunctionCodeTests extends ExecutableTest {
|
||||
assertFalse(state.getHadFatalError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTypeFromTxInA() throws ExecutionException {
|
||||
int initialBlockHeight = TestAPI.DEFAULT_INITIAL_BLOCK_HEIGHT;
|
||||
long initialTimestamp = Timestamp.toLong(initialBlockHeight, 0);
|
||||
dataByteBuffer.putLong(initialTimestamp);
|
||||
|
||||
// Generate new block containing 2 transactions to AT, one PAYMENT, one MESSAGE
|
||||
TestBlock newBlock = api.generateEmptyBlock();
|
||||
|
||||
String sender = "Bystander";
|
||||
String recipient = TestAPI.AT_ADDRESS;
|
||||
|
||||
TestTransaction paymentTx = api.generateTransaction(sender, recipient, API.ATTransactionType.PAYMENT);
|
||||
newBlock.transactions.add(paymentTx);
|
||||
|
||||
TestTransaction messageTx = api.generateTransaction(sender, recipient, API.ATTransactionType.MESSAGE);
|
||||
newBlock.transactions.add(messageTx);
|
||||
|
||||
api.addBlockToChain(newBlock);
|
||||
api.bumpCurrentBlockHeight();
|
||||
|
||||
// Get transaction after timestamp held in address 0
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A.value).putInt(0);
|
||||
// Save transaction type into address 1
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TYPE_FROM_TX_IN_A.value).putInt(1);
|
||||
// Update latest timestamp in address 0
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A.value).putInt(0);
|
||||
|
||||
// Get transaction after timestamp held in address 0
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A.value).putInt(0);
|
||||
// Save transaction type into address 2
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TYPE_FROM_TX_IN_A.value).putInt(2);
|
||||
// Done
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.value);
|
||||
|
||||
execute(true);
|
||||
|
||||
long paymentTxType = getData(1);
|
||||
assertEquals("Payment tx type mismatch", paymentTx.txType.value, paymentTxType);
|
||||
|
||||
long messageTxType = getData(2);
|
||||
assertEquals("Message tx type mismatch", messageTx.txType.value, messageTxType);
|
||||
|
||||
assertTrue(state.getIsFinished());
|
||||
assertFalse(state.getHadFatalError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAmountFromTxInA() throws ExecutionException {
|
||||
int initialBlockHeight = TestAPI.DEFAULT_INITIAL_BLOCK_HEIGHT;
|
||||
long initialTimestamp = Timestamp.toLong(initialBlockHeight, 0);
|
||||
dataByteBuffer.putLong(initialTimestamp);
|
||||
|
||||
// Generate new block containing 2 transactions to AT, one PAYMENT, one MESSAGE
|
||||
TestBlock newBlock = api.generateEmptyBlock();
|
||||
|
||||
String sender = "Bystander";
|
||||
String recipient = TestAPI.AT_ADDRESS;
|
||||
|
||||
TestTransaction paymentTx = api.generateTransaction(sender, recipient, API.ATTransactionType.PAYMENT);
|
||||
newBlock.transactions.add(paymentTx);
|
||||
|
||||
TestTransaction messageTx = api.generateTransaction(sender, recipient, API.ATTransactionType.MESSAGE);
|
||||
newBlock.transactions.add(messageTx);
|
||||
|
||||
api.addBlockToChain(newBlock);
|
||||
api.bumpCurrentBlockHeight();
|
||||
|
||||
// Get transaction after timestamp held in address 0
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A.value).putInt(0);
|
||||
// Save transaction's amount into address 1
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_AMOUNT_FROM_TX_IN_A.value).putInt(1);
|
||||
// Update latest timestamp in address 0
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A.value).putInt(0);
|
||||
|
||||
// Get transaction after timestamp held in address 0
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A.value).putInt(0);
|
||||
// Save transaction's amount into address 2
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_AMOUNT_FROM_TX_IN_A.value).putInt(2);
|
||||
// Done
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.value);
|
||||
|
||||
execute(true);
|
||||
|
||||
long paymentTxAmount = getData(1);
|
||||
assertEquals("Payment tx amount mismatch", paymentTx.amount, paymentTxAmount);
|
||||
|
||||
long messageTxAmount = getData(2);
|
||||
assertEquals("Message tx amount mismatch", messageTx.amount, messageTxAmount);
|
||||
|
||||
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));
|
||||
@ -216,4 +324,474 @@ public class BlockchainFunctionCodeTests extends ExecutableTest {
|
||||
assertFalse(state.getHadFatalError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutMessageFromTxInAIntoB() throws ExecutionException {
|
||||
int initialBlockHeight = TestAPI.DEFAULT_INITIAL_BLOCK_HEIGHT;
|
||||
long initialTimestamp = Timestamp.toLong(initialBlockHeight, 0);
|
||||
dataByteBuffer.putLong(initialTimestamp);
|
||||
|
||||
// Where to save message (in B) from payment tx
|
||||
dataByteBuffer.putLong(4L);
|
||||
|
||||
// Where to save message (in B) from message tx
|
||||
dataByteBuffer.putLong(8L);
|
||||
|
||||
// Generate new block containing 2 transactions to AT, one PAYMENT, one MESSAGE
|
||||
TestBlock newBlock = api.generateEmptyBlock();
|
||||
|
||||
String sender = "Bystander";
|
||||
String recipient = TestAPI.AT_ADDRESS;
|
||||
|
||||
TestTransaction paymentTx = api.generateTransaction(sender, recipient, API.ATTransactionType.PAYMENT);
|
||||
newBlock.transactions.add(paymentTx);
|
||||
|
||||
TestTransaction messageTx = api.generateTransaction(sender, recipient, API.ATTransactionType.MESSAGE);
|
||||
newBlock.transactions.add(messageTx);
|
||||
|
||||
api.addBlockToChain(newBlock);
|
||||
api.bumpCurrentBlockHeight();
|
||||
|
||||
// Get transaction after timestamp held in address 0
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A.value).putInt(0);
|
||||
// Save transaction's message into addresses 4 to 7
|
||||
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B.value);
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.GET_B_IND.value).putInt(1);
|
||||
// Update latest timestamp in address 0
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_TIMESTAMP_FROM_TX_IN_A.value).putInt(0);
|
||||
|
||||
// Get transaction after timestamp held in address 0
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A.value).putInt(0);
|
||||
// Save transaction's message into addresses 4 to 7
|
||||
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_MESSAGE_FROM_TX_IN_A_INTO_B.value);
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.GET_B_IND.value).putInt(2);
|
||||
// Done
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.value);
|
||||
|
||||
execute(true);
|
||||
|
||||
byte[] actualMessage = new byte[32];
|
||||
|
||||
byte[] expectedMessage = new byte[32]; // Blank for non-message transactions
|
||||
getDataBytes(4, actualMessage);
|
||||
assertTrue("Payment tx message mismatch", Arrays.equals(expectedMessage, actualMessage));
|
||||
|
||||
expectedMessage = messageTx.message;
|
||||
getDataBytes(8, actualMessage);
|
||||
assertTrue("Message tx message mismatch", Arrays.equals(expectedMessage, actualMessage));
|
||||
|
||||
assertTrue(state.getIsFinished());
|
||||
assertFalse(state.getHadFatalError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutAddressFromTxInAIntoB() throws ExecutionException {
|
||||
int initialBlockHeight = TestAPI.DEFAULT_INITIAL_BLOCK_HEIGHT;
|
||||
long initialTimestamp = Timestamp.toLong(initialBlockHeight, 0);
|
||||
dataByteBuffer.putLong(initialTimestamp);
|
||||
|
||||
// Where to save address (in B) from tx
|
||||
dataByteBuffer.putLong(4L);
|
||||
|
||||
// Generate new block containing a transaction to AT
|
||||
TestBlock newBlock = api.generateEmptyBlock();
|
||||
|
||||
String sender = "Bystander";
|
||||
String recipient = TestAPI.AT_ADDRESS;
|
||||
|
||||
TestTransaction messageTx = api.generateTransaction(sender, recipient, API.ATTransactionType.MESSAGE);
|
||||
newBlock.transactions.add(messageTx);
|
||||
|
||||
api.addBlockToChain(newBlock);
|
||||
api.bumpCurrentBlockHeight();
|
||||
|
||||
// Get transaction after timestamp held in address 0
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PUT_TX_AFTER_TIMESTAMP_INTO_A.value).putInt(0);
|
||||
// Save transaction's sender into addresses 4 to 7
|
||||
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_ADDRESS_FROM_TX_IN_A_INTO_B.value);
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.GET_B_IND.value).putInt(1);
|
||||
// Done
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.value);
|
||||
|
||||
execute(true);
|
||||
|
||||
byte[] expectedSenderBytes = TestAPI.encodeAddress(sender);
|
||||
byte[] actualSenderBytes = new byte[32];
|
||||
getDataBytes(4, actualSenderBytes);
|
||||
assertTrue("Sender address bytes mismatch", Arrays.equals(expectedSenderBytes, actualSenderBytes));
|
||||
|
||||
String expectedSender = sender;
|
||||
String actualSender = TestAPI.decodeAddress(actualSenderBytes);
|
||||
assertEquals("Sender address string mismatch", expectedSender, actualSender);
|
||||
|
||||
assertTrue(state.getIsFinished());
|
||||
assertFalse(state.getHadFatalError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutCreatorIntoB() throws ExecutionException {
|
||||
// Where to save creator address (in B)
|
||||
dataByteBuffer.putLong(4L);
|
||||
|
||||
// Save creator's address into data addresses 4 to 7
|
||||
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_CREATOR_INTO_B.value);
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.GET_B_IND.value).putInt(0);
|
||||
// Done
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.value);
|
||||
|
||||
execute(true);
|
||||
|
||||
byte[] expectedAtCreatorBytes = TestAPI.encodeAddress(TestAPI.AT_CREATOR_ADDRESS);
|
||||
byte[] actualAtCreatorBytes = new byte[32];
|
||||
getDataBytes(4, actualAtCreatorBytes);
|
||||
assertTrue("AT creator address bytes mismatch", Arrays.equals(expectedAtCreatorBytes, actualAtCreatorBytes));
|
||||
|
||||
String expectedAtCreator = TestAPI.AT_CREATOR_ADDRESS;
|
||||
String actualAtCreator = TestAPI.decodeAddress(actualAtCreatorBytes);
|
||||
assertEquals("AT creator address string mismatch", expectedAtCreator, actualAtCreator);
|
||||
|
||||
assertTrue(state.getIsFinished());
|
||||
assertFalse(state.getHadFatalError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCurrentBalance() throws ExecutionException {
|
||||
// Number of bits to shift right
|
||||
dataByteBuffer.putLong(1L);
|
||||
|
||||
// Save current balance into address 1
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_CURRENT_BALANCE.value).putInt(1);
|
||||
// Copy balance from address 1 into address 2
|
||||
codeByteBuffer.put(OpCode.SET_DAT.value).putInt(2).putInt(1);
|
||||
// Halve balance in address 2
|
||||
codeByteBuffer.put(OpCode.SHR_DAT.value).putInt(2).putInt(0);
|
||||
// Pay amount in address 2 to creator (via B)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_CREATOR_INTO_B.value);
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PAY_TO_ADDRESS_IN_B.value).putInt(2);
|
||||
// Save new current balance into address 3
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_CURRENT_BALANCE.value).putInt(3);
|
||||
// Done
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.value);
|
||||
|
||||
final long initialBalance = api.accounts.get(TestAPI.AT_ADDRESS).balance;
|
||||
|
||||
execute(true);
|
||||
|
||||
long expectedBalance = initialBalance - TestAPI.STEPS_PER_FUNCTION_CALL /* GET_CURRENT_BALANCE */;
|
||||
assertEquals("Initial 'current balance' mismatch", expectedBalance, getData(1));
|
||||
|
||||
final long amount = expectedBalance >>> 1;
|
||||
expectedBalance -= 1 /* SET_DAT */
|
||||
+ 1 /* SHR_DAT */
|
||||
+ TestAPI.STEPS_PER_FUNCTION_CALL /* PUT_CREATOR_INTO_B */
|
||||
+ TestAPI.STEPS_PER_FUNCTION_CALL /* PAY_TO_ADDRESS_IN_B */
|
||||
+ TestAPI.STEPS_PER_FUNCTION_CALL /* GET_CURRENT_BALANCE */
|
||||
+ amount;
|
||||
|
||||
assertEquals("Final 'current balance' mismatch", expectedBalance, getData(3));
|
||||
|
||||
assertTrue(state.getIsFinished());
|
||||
assertFalse(state.getHadFatalError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPreviousBalance() throws ExecutionException {
|
||||
// Save previous balance into address 1
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET.value).putShort(FunctionCode.GET_PREVIOUS_BALANCE.value).putInt(1);
|
||||
// Done
|
||||
codeByteBuffer.put(OpCode.STP_IMD.value);
|
||||
|
||||
final long initialBalance = api.accounts.get(TestAPI.AT_ADDRESS).balance;
|
||||
|
||||
execute(true);
|
||||
|
||||
long expectedBalance = initialBalance;
|
||||
assertEquals("Initial 'previous balance' mismatch", expectedBalance, getData(1));
|
||||
|
||||
execute(true);
|
||||
|
||||
expectedBalance -= 1 /* STP_IMD */ + TestAPI.STEPS_PER_FUNCTION_CALL /* GET_CURRENT_BALANCE */;
|
||||
|
||||
assertEquals("Final 'previous balance' mismatch", expectedBalance, getData(1));
|
||||
|
||||
assertFalse(state.getHadFatalError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPayToAddressInB() throws ExecutionException {
|
||||
final long amount = 123L;
|
||||
TestAccount recipient = api.accounts.get("Bystander");
|
||||
|
||||
// Where recipient address is stored
|
||||
final long addressPosition = 2L;
|
||||
dataByteBuffer.putLong(addressPosition);
|
||||
|
||||
// Amount to send to recipient
|
||||
dataByteBuffer.putLong(amount);
|
||||
|
||||
// Recipient address
|
||||
assertEquals(addressPosition * MachineState.VALUE_SIZE, dataByteBuffer.position());
|
||||
dataByteBuffer.put(TestAPI.encodeAddress(recipient.address));
|
||||
|
||||
// Copy recipient address into B
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B_IND.value).putInt(0);
|
||||
// Pay amount in address 1 to recipient (via B)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PAY_TO_ADDRESS_IN_B.value).putInt(1);
|
||||
// STOP, not finish, so we retain balance instead of sending leftover to AT creator
|
||||
codeByteBuffer.put(OpCode.STP_IMD.value);
|
||||
|
||||
final long initialAtBalance = api.accounts.get(TestAPI.AT_ADDRESS).balance;
|
||||
final long initialRecipientBalance = recipient.balance;
|
||||
|
||||
execute(true);
|
||||
|
||||
long expectedBalance = initialAtBalance - amount;
|
||||
long actualBalance = api.accounts.get(TestAPI.AT_ADDRESS).balance;
|
||||
assertTrue("Final AT balance mismatch", actualBalance <= expectedBalance && actualBalance > 0);
|
||||
|
||||
expectedBalance = initialRecipientBalance + amount;
|
||||
actualBalance = recipient.balance;
|
||||
assertEquals("Final recipient balance mismatch", expectedBalance, actualBalance);
|
||||
|
||||
assertFalse(state.getHadFatalError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPayToAddressInBexcessive() throws ExecutionException {
|
||||
final long amount = 999999999L; // More than AT's balance
|
||||
TestAccount recipient = api.accounts.get("Bystander");
|
||||
|
||||
// Where recipient address is stored
|
||||
final long addressPosition = 2L;
|
||||
dataByteBuffer.putLong(addressPosition);
|
||||
|
||||
// Amount to send to recipient
|
||||
dataByteBuffer.putLong(amount);
|
||||
|
||||
// Recipient address
|
||||
assertEquals(addressPosition * MachineState.VALUE_SIZE, dataByteBuffer.position());
|
||||
dataByteBuffer.put(TestAPI.encodeAddress(recipient.address));
|
||||
|
||||
// Copy recipient address into B
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B_IND.value).putInt(0);
|
||||
// Pay amount in address 1 to recipient (via B)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.PAY_TO_ADDRESS_IN_B.value).putInt(1);
|
||||
// STOP, not finish, so we retain balance instead of sending leftover to AT creator
|
||||
codeByteBuffer.put(OpCode.STP_IMD.value);
|
||||
|
||||
final long initialAtBalance = api.accounts.get(TestAPI.AT_ADDRESS).balance;
|
||||
final long initialRecipientBalance = recipient.balance;
|
||||
|
||||
execute(true);
|
||||
|
||||
long expectedAmount = initialAtBalance
|
||||
- TestAPI.STEPS_PER_FUNCTION_CALL /* SET_B_IND */
|
||||
- TestAPI.STEPS_PER_FUNCTION_CALL /* PAY_TO_ADDRESS_IN_B */;
|
||||
|
||||
long expectedBalance = 0L;
|
||||
long actualBalance = api.accounts.get(TestAPI.AT_ADDRESS).balance;
|
||||
assertEquals("Final AT balance mismatch", expectedBalance, actualBalance);
|
||||
|
||||
expectedBalance = initialRecipientBalance + expectedAmount;
|
||||
actualBalance = recipient.balance;
|
||||
assertEquals("Final recipient balance mismatch", expectedBalance, actualBalance);
|
||||
|
||||
assertFalse(state.getHadFatalError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPayAllToAddressInB() throws ExecutionException {
|
||||
TestAccount recipient = api.accounts.get("Bystander");
|
||||
|
||||
// Where recipient address is stored
|
||||
final long addressPosition = 1L;
|
||||
dataByteBuffer.putLong(addressPosition);
|
||||
|
||||
// Recipient address
|
||||
assertEquals(addressPosition * MachineState.VALUE_SIZE, dataByteBuffer.position());
|
||||
dataByteBuffer.put(TestAPI.encodeAddress(recipient.address));
|
||||
|
||||
// Copy recipient address into B
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B_IND.value).putInt(0);
|
||||
// Pay all amount to recipient (via B)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PAY_ALL_TO_ADDRESS_IN_B.value);
|
||||
// STOP, not finish, so we retain balance instead of sending leftover to AT creator
|
||||
codeByteBuffer.put(OpCode.STP_IMD.value);
|
||||
|
||||
final long initialAtBalance = api.accounts.get(TestAPI.AT_ADDRESS).balance;
|
||||
final long initialRecipientBalance = recipient.balance;
|
||||
|
||||
execute(true);
|
||||
|
||||
long expectedAmount = initialAtBalance
|
||||
- TestAPI.STEPS_PER_FUNCTION_CALL /* SET_B_IND */
|
||||
- TestAPI.STEPS_PER_FUNCTION_CALL /* PAY_ALL_TO_ADDRESS_IN_B */;
|
||||
|
||||
long expectedBalance = 0L;
|
||||
long actualBalance = api.accounts.get(TestAPI.AT_ADDRESS).balance;
|
||||
assertEquals("Final AT balance mismatch", expectedBalance, actualBalance);
|
||||
|
||||
expectedBalance = initialRecipientBalance + expectedAmount;
|
||||
actualBalance = recipient.balance;
|
||||
assertEquals("Final recipient balance mismatch", expectedBalance, actualBalance);
|
||||
|
||||
assertFalse(state.getHadFatalError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPayPreviousToAddressInB() throws ExecutionException {
|
||||
TestAccount recipient = api.accounts.get("Bystander");
|
||||
|
||||
// Where recipient address is stored
|
||||
final long addressPosition = 1L;
|
||||
dataByteBuffer.putLong(addressPosition);
|
||||
|
||||
// Recipient address
|
||||
assertEquals(addressPosition * MachineState.VALUE_SIZE, dataByteBuffer.position());
|
||||
dataByteBuffer.put(TestAPI.encodeAddress(recipient.address));
|
||||
|
||||
// Copy recipient address into B
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B_IND.value).putInt(0);
|
||||
// Pay previous balance to recipient (via B)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PAY_PREVIOUS_TO_ADDRESS_IN_B.value);
|
||||
// STOP, not finish, so we retain balance instead of sending leftover to AT creator
|
||||
codeByteBuffer.put(OpCode.STP_IMD.value);
|
||||
|
||||
final long initialAtBalance = api.accounts.get(TestAPI.AT_ADDRESS).balance;
|
||||
final long initialRecipientBalance = recipient.balance;
|
||||
|
||||
execute(true);
|
||||
|
||||
long expectedAmount = initialAtBalance
|
||||
- TestAPI.STEPS_PER_FUNCTION_CALL /* SET_B_IND */
|
||||
- TestAPI.STEPS_PER_FUNCTION_CALL /* PAY_PREVIOUS_TO_ADDRESS_IN_B */;
|
||||
|
||||
long expectedBalance = 0L;
|
||||
long actualBalance = api.accounts.get(TestAPI.AT_ADDRESS).balance;
|
||||
assertEquals("Final AT balance mismatch", expectedBalance, actualBalance);
|
||||
|
||||
expectedBalance = initialRecipientBalance + expectedAmount;
|
||||
actualBalance = recipient.balance;
|
||||
assertEquals("Final recipient balance mismatch", expectedBalance, actualBalance);
|
||||
|
||||
assertFalse(state.getHadFatalError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPayPreviousToAddressInBextra() throws ExecutionException {
|
||||
TestAccount recipient = api.accounts.get("Bystander");
|
||||
|
||||
// Where recipient address is stored
|
||||
final long addressPosition = 1L;
|
||||
dataByteBuffer.putLong(addressPosition);
|
||||
|
||||
// Recipient address
|
||||
assertEquals(addressPosition * MachineState.VALUE_SIZE, dataByteBuffer.position());
|
||||
dataByteBuffer.put(TestAPI.encodeAddress(recipient.address));
|
||||
|
||||
// Sleep until next block
|
||||
codeByteBuffer.put(OpCode.SLP_IMD.value);
|
||||
// Copy recipient address into B
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B_IND.value).putInt(0);
|
||||
// Pay previous balance to recipient (via B)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PAY_PREVIOUS_TO_ADDRESS_IN_B.value);
|
||||
// STOP, not finish, so we retain balance instead of sending leftover to AT creator
|
||||
codeByteBuffer.put(OpCode.STP_IMD.value);
|
||||
|
||||
execute(true);
|
||||
|
||||
final TestAccount atAccount = api.accounts.get(TestAPI.AT_ADDRESS);
|
||||
|
||||
final long previousAtBalance = atAccount.balance;
|
||||
final long initialRecipientBalance = recipient.balance;
|
||||
|
||||
// Simulate AT receiving a payment (in excess of cost of running AT for one round)
|
||||
final long incomingAtPayment = 25000L;
|
||||
atAccount.balance += incomingAtPayment;
|
||||
|
||||
execute(true);
|
||||
|
||||
long expectedBalance = incomingAtPayment
|
||||
- TestAPI.STEPS_PER_FUNCTION_CALL /* SET_B_IND */
|
||||
- TestAPI.STEPS_PER_FUNCTION_CALL /* PAY_PREVIOUS_TO_ADDRESS_IN_B */
|
||||
- 1 /* STP_IMD */;
|
||||
long actualBalance = atAccount.balance;
|
||||
assertEquals("Final AT balance mismatch", expectedBalance, actualBalance);
|
||||
|
||||
expectedBalance = initialRecipientBalance + previousAtBalance;
|
||||
actualBalance = recipient.balance;
|
||||
assertEquals("Final recipient balance mismatch", expectedBalance, actualBalance);
|
||||
|
||||
assertFalse(state.getHadFatalError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessageAToAddressInB() throws ExecutionException {
|
||||
Random random = new Random();
|
||||
|
||||
byte[] message = new byte[32];
|
||||
random.nextBytes(message);
|
||||
|
||||
TestAccount recipient = api.accounts.get("Bystander");
|
||||
|
||||
// Where message is stored
|
||||
final long messagePosition = 2L;
|
||||
dataByteBuffer.putLong(messagePosition);
|
||||
|
||||
// Where recipient address is stored
|
||||
final long addressPosition = 6L;
|
||||
dataByteBuffer.putLong(addressPosition);
|
||||
|
||||
// Message
|
||||
assertEquals(messagePosition * MachineState.VALUE_SIZE, dataByteBuffer.position());
|
||||
dataByteBuffer.put(message);
|
||||
|
||||
// Recipient address
|
||||
assertEquals(addressPosition * MachineState.VALUE_SIZE, dataByteBuffer.position());
|
||||
dataByteBuffer.put(TestAPI.encodeAddress(recipient.address));
|
||||
|
||||
// Copy message into A
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_A_IND.value).putInt(0);
|
||||
// Copy recipient address into B
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_DAT.value).putShort(FunctionCode.SET_B_IND.value).putInt(1);
|
||||
// Send message (in A) to recipient (via B)
|
||||
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.MESSAGE_A_TO_ADDRESS_IN_B.value);
|
||||
// Done
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.value);
|
||||
|
||||
execute(true);
|
||||
|
||||
assertEquals("Recipient message count incorrect", 1, recipient.messages.size());
|
||||
|
||||
byte[] actualMessage = recipient.messages.get(0);
|
||||
assertTrue("Recipient message incorrect", Arrays.equals(message, actualMessage));
|
||||
|
||||
assertTrue(state.getIsFinished());
|
||||
assertFalse(state.getHadFatalError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddMinutesToTimestamp() throws ExecutionException {
|
||||
int initialBlockHeight = TestAPI.DEFAULT_INITIAL_BLOCK_HEIGHT;
|
||||
long initialTimestamp = Timestamp.toLong(initialBlockHeight, 0);
|
||||
dataByteBuffer.putLong(initialTimestamp);
|
||||
|
||||
long minutes = 34L;
|
||||
dataByteBuffer.putLong(minutes);
|
||||
|
||||
// Add minutes (from address 1) to timestamp (in address 0) and store the result in address 2
|
||||
codeByteBuffer.put(OpCode.EXT_FUN_RET_DAT_2.value).putShort(FunctionCode.ADD_MINUTES_TO_TIMESTAMP.value).putInt(2).putInt(0).putInt(1);
|
||||
// Done
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.value);
|
||||
|
||||
execute(true);
|
||||
|
||||
int expectedBlockHeight = initialBlockHeight + ((int) minutes * 60 / TestAPI.BLOCK_PERIOD);
|
||||
long expectedTimestamp = Timestamp.toLong(expectedBlockHeight, 0);
|
||||
long actualTimestamp = getData(2);
|
||||
assertEquals("Expected timestamp incorrect", expectedTimestamp, actualTimestamp);
|
||||
|
||||
assertTrue(state.getIsFinished());
|
||||
assertFalse(state.getHadFatalError());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -61,21 +61,21 @@ public class TestAPI extends API {
|
||||
public long amount;
|
||||
public byte[] message;
|
||||
|
||||
private TestTransaction(byte[] txHash, API.ATTransactionType txType, String creator, String recipient) {
|
||||
private TestTransaction(byte[] txHash, API.ATTransactionType txType, String sender, String recipient) {
|
||||
this.txHash = txHash;
|
||||
this.txType = txType;
|
||||
this.sender = creator;
|
||||
this.sender = sender;
|
||||
this.recipient = recipient;
|
||||
}
|
||||
|
||||
public TestTransaction(byte[] txHash, String creator, String recipient, long amount) {
|
||||
this(txHash, API.ATTransactionType.PAYMENT, creator, recipient);
|
||||
public TestTransaction(byte[] txHash, String sender, String recipient, long amount) {
|
||||
this(txHash, API.ATTransactionType.PAYMENT, sender, recipient);
|
||||
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public TestTransaction(byte[] txHash, String creator, String recipient, byte[] message) {
|
||||
this(txHash, API.ATTransactionType.MESSAGE, creator, recipient);
|
||||
public TestTransaction(byte[] txHash, String sender, String recipient, byte[] message) {
|
||||
this(txHash, API.ATTransactionType.MESSAGE, sender, recipient);
|
||||
|
||||
this.message = new byte[32];
|
||||
System.arraycopy(message, 0, this.message, 0, message.length);
|
||||
@ -127,6 +127,21 @@ public class TestAPI extends API {
|
||||
transactions = new HashMap<>();
|
||||
}
|
||||
|
||||
public static byte[] encodeAddress(String address) {
|
||||
byte[] encodedAddress = new byte[32];
|
||||
System.arraycopy(address.getBytes(), 0, encodedAddress, 0, address.length());
|
||||
return encodedAddress;
|
||||
}
|
||||
|
||||
public static String decodeAddress(byte[] encodedAddress) {
|
||||
String address = new String(encodedAddress, StandardCharsets.ISO_8859_1);
|
||||
return address.replace("\0", "");
|
||||
}
|
||||
|
||||
public static String stringifyHash(byte[] hash) {
|
||||
return new String(hash, StandardCharsets.ISO_8859_1);
|
||||
}
|
||||
|
||||
public void bumpCurrentBlockHeight() {
|
||||
++this.currentBlockHeight;
|
||||
}
|
||||
@ -138,12 +153,28 @@ public class TestAPI extends API {
|
||||
this.currentBlockHeight = blockHeight;
|
||||
}
|
||||
|
||||
private void generateBlock(boolean withTransactions, boolean includeTransactionToAt) {
|
||||
TestBlock newBlock = new TestBlock();
|
||||
public TestBlock addBlockToChain(TestBlock newBlock) {
|
||||
blockchain.add(newBlock);
|
||||
final int blockHeight = blockchain.size();
|
||||
|
||||
for (int seq = 0; seq < newBlock.transactions.size(); ++seq) {
|
||||
TestTransaction transaction = newBlock.transactions.get(seq);
|
||||
|
||||
// Set transaction timestamp
|
||||
transaction.timestamp = Timestamp.toLong(blockHeight, seq);
|
||||
|
||||
// Add to transactions map
|
||||
transactions.put(stringifyHash(transaction.txHash), transaction);
|
||||
}
|
||||
|
||||
return newBlock;
|
||||
}
|
||||
|
||||
private TestBlock generateBlock(boolean withTransactions, boolean includeTransactionToAt) {
|
||||
TestBlock newBlock = new TestBlock();
|
||||
|
||||
if (!withTransactions)
|
||||
return;
|
||||
return newBlock;
|
||||
|
||||
TestAccount atAccount = accounts.get(AT_ADDRESS);
|
||||
List<TestAccount> senderAccounts = new ArrayList<>(accounts.values());
|
||||
@ -160,39 +191,46 @@ public class TestAPI extends API {
|
||||
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);
|
||||
TestTransaction transaction = generateTransaction(sender.address, recipient.address, txType);
|
||||
newBlock.transactions.add(transaction);
|
||||
|
||||
if (recipient.address.equals(AT_ADDRESS))
|
||||
includesAtTransaction = true;
|
||||
}
|
||||
|
||||
return newBlock;
|
||||
}
|
||||
|
||||
public void generateEmptyBlock() {
|
||||
generateBlock(false, false);
|
||||
public TestBlock generateEmptyBlock() {
|
||||
return generateBlock(false, false);
|
||||
}
|
||||
|
||||
public void generateBlockWithNonAtTransactions() {
|
||||
generateBlock(true, false);
|
||||
public TestBlock generateBlockWithNonAtTransactions() {
|
||||
return generateBlock(true, false);
|
||||
}
|
||||
|
||||
public void generateBlockWithAtTransaction() {
|
||||
generateBlock(true, true);
|
||||
public TestBlock generateBlockWithAtTransaction() {
|
||||
return generateBlock(true, true);
|
||||
}
|
||||
|
||||
public TestTransaction generateTransaction(String sender, String recipient, API.ATTransactionType txType) {
|
||||
TestTransaction transaction;
|
||||
|
||||
// Generate tx hash
|
||||
byte[] txHash = new byte[32];
|
||||
RANDOM.nextBytes(txHash);
|
||||
|
||||
if (txType == API.ATTransactionType.PAYMENT) {
|
||||
long amount = RANDOM.nextInt(100); // small amounts
|
||||
transaction = new TestTransaction(txHash, sender, recipient, amount);
|
||||
} else {
|
||||
byte[] message = new byte[32];
|
||||
RANDOM.nextBytes(message);
|
||||
transaction = new TestTransaction(txHash, sender, recipient, message);
|
||||
}
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -239,7 +277,7 @@ public class TestAPI extends API {
|
||||
|
||||
List<TestTransaction> transactions = block.transactions;
|
||||
|
||||
if (transactionSequence > transactions.size() - 1) {
|
||||
if (transactionSequence >= transactions.size()) {
|
||||
// No more transactions at this height
|
||||
++blockHeight;
|
||||
transactionSequence = 0;
|
||||
@ -261,12 +299,13 @@ public class TestAPI extends API {
|
||||
}
|
||||
|
||||
// Nothing found
|
||||
System.out.println("No more transactions found at height " + this.currentBlockHeight);
|
||||
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
|
||||
String txHashString = stringifyHash(aBytes);
|
||||
return transactions.get(txHashString);
|
||||
}
|
||||
|
||||
@ -323,15 +362,13 @@ public class TestAPI extends API {
|
||||
@Override
|
||||
public void putAddressFromTransactionInAIntoB(MachineState state) {
|
||||
TestTransaction transaction = getTransactionFromA(state);
|
||||
byte[] bBytes = new byte[32];
|
||||
System.arraycopy(transaction.sender.getBytes(), 0, bBytes, 0, transaction.sender.length());
|
||||
byte[] bBytes = encodeAddress(transaction.sender);
|
||||
this.setB(state, bBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putCreatorAddressIntoB(MachineState state) {
|
||||
byte[] bBytes = new byte[32];
|
||||
System.arraycopy(AT_CREATOR_ADDRESS.getBytes(), 0, bBytes, 0, AT_CREATOR_ADDRESS.length());
|
||||
byte[] bBytes = encodeAddress(AT_CREATOR_ADDRESS);
|
||||
this.setB(state, bBytes);
|
||||
}
|
||||
|
||||
@ -348,30 +385,25 @@ public class TestAPI extends API {
|
||||
|
||||
@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", "");
|
||||
String address = decodeAddress(bBytes);
|
||||
|
||||
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;
|
||||
System.out.println(String.format("Paid %s to '%s', their balance now: %s", prettyAmount(amount), recipient.address, prettyAmount(recipient.balance)));
|
||||
|
||||
TestAccount atAccount = accounts.get(AT_ADDRESS);
|
||||
atAccount.balance -= amount;
|
||||
System.out.println(String.format("AT balance now: %s", atAccount.balance));
|
||||
final long previousBalance = state.getCurrentBalance();
|
||||
final long newBalance = previousBalance - amount;
|
||||
System.out.println(String.format("AT balance was %s, now: %s", prettyAmount(previousBalance), prettyAmount(newBalance)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageAToB(MachineState state) {
|
||||
byte[] bBytes = state.getB();
|
||||
String address = new String(bBytes, StandardCharsets.ISO_8859_1);
|
||||
address = address.replace("\0", "");
|
||||
String address = decodeAddress(bBytes);
|
||||
|
||||
TestAccount recipient = accounts.get(address);
|
||||
if (recipient == null)
|
||||
@ -382,7 +414,7 @@ public class TestAPI extends API {
|
||||
|
||||
@Override
|
||||
public long addMinutesToTimestamp(Timestamp timestamp, long minutes, MachineState state) {
|
||||
timestamp.blockHeight = ((int) minutes * 60) / BLOCK_PERIOD;
|
||||
timestamp.blockHeight += ((int) minutes * 60) / BLOCK_PERIOD;
|
||||
return timestamp.longValue();
|
||||
}
|
||||
|
||||
@ -392,7 +424,7 @@ public class TestAPI extends API {
|
||||
|
||||
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));
|
||||
System.out.println(String.format("Paid %s to AT creator '%s', their balance now: %s", prettyAmount(amount), atCreatorAccount.address, prettyAmount(atCreatorAccount.balance)));
|
||||
|
||||
accounts.get(AT_ADDRESS).balance -= amount;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user