mirror of https://github.com/Qortal/AT
Browse Source
Rolled the superior blockchain simulating parts from ACCTAPI into TestAPI. Tried to replace literal test values with named constants from TestAPI class, or derived values. Added some more opcode tests to cover more cases. Renamed some functions of the form "put something ... in A" to "put something .. into A" to help distinguish them from "get ... based on something in A". Added {GET,SET}_[AB]_IND functions as an addition to the long-winded GET_A1..A4, SET_B1..B4. Added more function code tests and separated those tests out into 3 different test classes for manageability. Possible logic error in PAY_TO_ADDRESS_IN_B and PAY_PREVIOUS_TO_ADDRESS_IN_B but needs testing! Improved comments.master
catbref
5 years ago
12 changed files with 880 additions and 577 deletions
@ -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()); |
||||
} |
||||
|
||||
} |
@ -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); |
||||
} |
||||
} |
||||
|
||||
} |
@ -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
|
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue