mirror of
https://github.com/Qortal/AT.git
synced 2025-01-29 02:22:13 +00:00
Merge pull request #1 from catbref/master
Improve documentation and test support
This commit is contained in:
commit
9de6eccc0e
55
Java/pom.xml
55
Java/pom.xml
@ -6,16 +6,20 @@
|
||||
<artifactId>AT</artifactId>
|
||||
<version>1.4.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<skipTests>false</skipTests>
|
||||
|
||||
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
|
||||
<maven-source-plugin.version>3.2.0</maven-source-plugin.version>
|
||||
<maven-javadoc-plugin.version>3.3.1</maven-javadoc-plugin.version>
|
||||
<maven-surefire-plugin.version>3.0.0-M4</maven-surefire-plugin.version>
|
||||
<maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
|
||||
|
||||
<bouncycastle.version>1.64</bouncycastle.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<sourceDirectory>src/main/java</sourceDirectory>
|
||||
<testSourceDirectory>src/test/java</testSourceDirectory>
|
||||
@ -37,6 +41,19 @@
|
||||
<skipTests>${skipTests}</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>${maven-source-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
@ -50,9 +67,47 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>${maven-jar-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>test-jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>${maven-compiler-plugin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>${maven-surefire-plugin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>${maven-source-plugin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>${maven-javadoc-plugin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>${maven-jar-plugin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.ciyam.at;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface AtLoggerFactory {
|
||||
|
||||
AtLogger create(final Class<?> loggerName);
|
||||
|
@ -561,7 +561,7 @@ public enum OpCode {
|
||||
/**
|
||||
* <b>SL</b>ee<b>P</b> until <b>DAT</b>a<br>
|
||||
* <code>0x25 addr</code><br>
|
||||
* <code>sleep until $addr, then carry on from current PC</code><br>
|
||||
* Sleep until <code>$addr</code>, then carry on from current <code>PC</code><br>
|
||||
* Note: The value from <code>$addr</code> is considered to be a block height.
|
||||
*/
|
||||
SLP_DAT(0x25, OpCodeParam.BLOCK_HEIGHT) {
|
||||
@ -634,7 +634,7 @@ public enum OpCode {
|
||||
/**
|
||||
* <b>SL</b>ee<b>P</b> <b>IM</b>me<b>D</b>iately<br>
|
||||
* <code>0x2a</code><br>
|
||||
* <code>sleep until next block, then carry on from current PC</code>
|
||||
* Sleep until next block, then carry on from current <code>PC</code>
|
||||
*/
|
||||
SLP_IMD(0x2a) {
|
||||
@Override
|
||||
@ -659,8 +659,7 @@ public enum OpCode {
|
||||
/**
|
||||
* <b>SL</b>ee<b>P</b> for <b>VAL</b>ue blocks<br>
|
||||
* <code>0x2c value</code><br>
|
||||
* <code>sleep until $addr, then carry on from current PC</code><br>
|
||||
* Note: The value from <code>$addr</code> is considered to be a block height.
|
||||
* Sleep for <code>value</code> blocks, then carry on from current <code>PC</code>
|
||||
*/
|
||||
SLP_VAL(0x2c, OpCodeParam.VALUE) {
|
||||
@Override
|
||||
@ -1070,7 +1069,11 @@ public enum OpCode {
|
||||
|
||||
public byte[] compile(Object... args) throws CompilationException {
|
||||
if (args.length != this.params.length)
|
||||
throw new IllegalArgumentException(String.format("%s requires %d args, only %d passed", this.name(), this.params.length, args.length));
|
||||
throw new IllegalArgumentException(String.format("%s requires %d arg%s, but %d passed",
|
||||
this.name(),
|
||||
this.params.length,
|
||||
this.params.length != 1 ? "s" : "",
|
||||
args.length));
|
||||
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocate(32); // 32 should easily be enough
|
||||
|
||||
|
@ -86,13 +86,27 @@ public class BlockchainFunctionCodeTests extends ExecutableTest {
|
||||
|
||||
@Test
|
||||
public void testPutPreviousBlockHashIntoA() throws ExecutionException {
|
||||
int previousBlockHeight = TestAPI.DEFAULT_INITIAL_BLOCK_HEIGHT - 1;
|
||||
// Generate some blocks containing transactions (but none to AT)
|
||||
TestBlock newBlock = api.generateBlockWithNonAtTransactions();
|
||||
api.addBlockToChain(newBlock);
|
||||
api.bumpCurrentBlockHeight();
|
||||
|
||||
newBlock = api.generateBlockWithNonAtTransactions();
|
||||
api.addBlockToChain(newBlock);
|
||||
api.bumpCurrentBlockHeight();
|
||||
|
||||
// Generate a block containing transaction to AT
|
||||
newBlock = api.generateBlockWithAtTransaction();
|
||||
api.addBlockToChain(newBlock);
|
||||
int previousBlockHeight = api.getCurrentBlockHeight();
|
||||
api.bumpCurrentBlockHeight();
|
||||
|
||||
codeByteBuffer.put(OpCode.EXT_FUN.value).putShort(FunctionCode.PUT_PREVIOUS_BLOCK_HASH_INTO_A.value);
|
||||
codeByteBuffer.put(OpCode.FIN_IMD.value);
|
||||
|
||||
execute(true);
|
||||
|
||||
// previousBlockHeight - 1 because index into blockchain starts at 0, whereas block heights start at 1
|
||||
byte[] expectedBlockHash = api.blockchain.get(previousBlockHeight - 1).blockHash;
|
||||
|
||||
byte[] aBytes = api.getA(state);
|
||||
|
@ -35,18 +35,55 @@ public class MiscTests extends ExecutableTest {
|
||||
|
||||
@Test
|
||||
public void testFreeze() throws ExecutionException {
|
||||
// Choose initial balance so it used up before max-steps-per-round triggers
|
||||
long initialBalance = 5L;
|
||||
api.accounts.get(TestAPI.AT_ADDRESS).balance = initialBalance;
|
||||
|
||||
// Infinite loop
|
||||
codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(0);
|
||||
|
||||
// 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)
|
||||
// Test a few rounds to make sure AT is frozen and stays frozen
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
execute(true);
|
||||
|
||||
assertTrue(state.isFrozen());
|
||||
|
||||
Long frozenBalance = state.getFrozenBalance();
|
||||
assertNotNull(frozenBalance);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnfreeze() throws ExecutionException {
|
||||
// Choose initial balance so it used up before max-steps-per-round triggers
|
||||
long initialBalance = 5L;
|
||||
api.setCurrentBalance(initialBalance);
|
||||
|
||||
// Infinite loop
|
||||
codeByteBuffer.put(OpCode.JMP_ADR.value).putInt(0);
|
||||
|
||||
// Execute to make sure AT is frozen and stays frozen
|
||||
execute(true);
|
||||
|
||||
assertTrue(state.isFrozen());
|
||||
|
||||
Long frozenBalance = state.getFrozenBalance();
|
||||
assertNotNull(frozenBalance);
|
||||
|
||||
// Send payment to AT to allow unfreezing
|
||||
// Payment needs to be enough to trigger max-steps-per-round so we can detect unfreezing
|
||||
api.setCurrentBalance(TestAPI.MAX_STEPS_PER_ROUND * api.getFeePerStep() * 2);
|
||||
|
||||
// Execute AT
|
||||
execute(true);
|
||||
|
||||
// We expect AT to be sleeping, not frozen
|
||||
assertFalse(state.isFrozen());
|
||||
|
||||
frozenBalance = state.getFrozenBalance();
|
||||
assertNull(frozenBalance);
|
||||
|
||||
assertTrue(state.isSleeping());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -4,17 +4,18 @@ import java.nio.ByteBuffer;
|
||||
import java.security.Security;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.ciyam.at.AtLoggerFactory;
|
||||
import org.ciyam.at.MachineState;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
public abstract class ExecutableTest {
|
||||
public class ExecutableTest {
|
||||
|
||||
private static final int DATA_OFFSET = MachineState.HEADER_LENGTH; // code bytes are not present
|
||||
private static final int CALL_STACK_OFFSET = DATA_OFFSET + TestUtils.NUM_DATA_PAGES * MachineState.VALUE_SIZE;
|
||||
public static final int DATA_OFFSET = MachineState.HEADER_LENGTH; // code bytes are not present
|
||||
public static final int CALL_STACK_OFFSET = DATA_OFFSET + TestUtils.NUM_DATA_PAGES * MachineState.VALUE_SIZE;
|
||||
|
||||
public TestLoggerFactory loggerFactory;
|
||||
public AtLoggerFactory loggerFactory;
|
||||
public TestAPI api;
|
||||
public MachineState state;
|
||||
public ByteBuffer codeByteBuffer;
|
||||
@ -24,6 +25,7 @@ public abstract class ExecutableTest {
|
||||
public int userStackOffset;
|
||||
public int userStackSize;
|
||||
public byte[] packedState;
|
||||
public byte[] codeBytes;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
@ -50,28 +52,39 @@ public abstract class ExecutableTest {
|
||||
loggerFactory = null;
|
||||
}
|
||||
|
||||
protected void execute(boolean onceOnly) {
|
||||
byte[] headerBytes = TestUtils.HEADER_BYTES;
|
||||
byte[] codeBytes = codeByteBuffer.array();
|
||||
byte[] dataBytes = dataByteBuffer.array();
|
||||
|
||||
public void execute(boolean onceOnly) {
|
||||
if (packedState == null) {
|
||||
// First time
|
||||
System.out.println("First execution - deploying...");
|
||||
byte[] headerBytes = TestUtils.HEADER_BYTES;
|
||||
codeBytes = codeByteBuffer.array();
|
||||
byte[] dataBytes = dataByteBuffer.array();
|
||||
|
||||
state = new MachineState(api, loggerFactory, headerBytes, codeBytes, dataBytes);
|
||||
packedState = state.toBytes();
|
||||
}
|
||||
|
||||
do {
|
||||
state = MachineState.fromBytes(api, loggerFactory, packedState, codeBytes);
|
||||
execute_once();
|
||||
} while (!onceOnly && !state.isFinished());
|
||||
|
||||
System.out.println("Starting execution round!");
|
||||
System.out.println("Current block height: " + api.getCurrentBlockHeight());
|
||||
System.out.println("Previous balance: " + TestAPI.prettyAmount(state.getPreviousBalance()));
|
||||
System.out.println("Current balance: " + TestAPI.prettyAmount(state.getCurrentBalance()));
|
||||
unwrapState(state);
|
||||
}
|
||||
|
||||
public void execute_once() {
|
||||
state = MachineState.fromBytes(api, loggerFactory, packedState, codeBytes);
|
||||
|
||||
System.out.println("Starting execution round!");
|
||||
System.out.println("Current block height: " + api.getCurrentBlockHeight());
|
||||
System.out.println("Previous balance: " + TestAPI.prettyAmount(state.getPreviousBalance()));
|
||||
System.out.println("Current balance: " + TestAPI.prettyAmount(api.getCurrentBalance(state)));
|
||||
|
||||
// Actual execution
|
||||
if (api.willExecute(state, api.getCurrentBlockHeight())) {
|
||||
// Actual execution
|
||||
api.preExecute(state);
|
||||
state.execute();
|
||||
packedState = state.toBytes();
|
||||
|
||||
System.out.println("After execution round:");
|
||||
System.out.println("Steps: " + state.getSteps());
|
||||
@ -94,19 +107,23 @@ public abstract class ExecutableTest {
|
||||
|
||||
long newBalance = state.getCurrentBalance();
|
||||
System.out.println("New balance: " + TestAPI.prettyAmount(newBalance));
|
||||
|
||||
// Update AT balance due to execution costs, etc.
|
||||
api.setCurrentBalance(newBalance);
|
||||
} else {
|
||||
System.out.println("Skipped execution round");
|
||||
}
|
||||
|
||||
// Bump block height
|
||||
api.bumpCurrentBlockHeight();
|
||||
// Add block, possibly containing AT-created transactions, to chain to at least provide block hashes
|
||||
api.addCurrentBlockToChain();
|
||||
|
||||
packedState = state.toBytes();
|
||||
System.out.println("Execution round finished\n");
|
||||
} while (!onceOnly && !state.isFinished());
|
||||
// Bump block height
|
||||
api.bumpCurrentBlockHeight();
|
||||
|
||||
unwrapState(state);
|
||||
System.out.println("Execution round finished\n");
|
||||
}
|
||||
|
||||
protected byte[] unwrapState(MachineState state) {
|
||||
public byte[] unwrapState(MachineState state) {
|
||||
// Ready for diagnosis
|
||||
byte[] stateBytes = state.toBytes();
|
||||
|
||||
@ -121,30 +138,30 @@ public abstract class ExecutableTest {
|
||||
return stateBytes;
|
||||
}
|
||||
|
||||
protected long getData(int address) {
|
||||
public long getData(int address) {
|
||||
int index = DATA_OFFSET + address * MachineState.VALUE_SIZE;
|
||||
return stateByteBuffer.getLong(index);
|
||||
}
|
||||
|
||||
protected void getDataBytes(int address, byte[] dest) {
|
||||
public void getDataBytes(int address, byte[] dest) {
|
||||
int index = DATA_OFFSET + address * MachineState.VALUE_SIZE;
|
||||
stateByteBuffer.slice().position(index).get(dest);
|
||||
}
|
||||
|
||||
protected int getCallStackPosition() {
|
||||
public int getCallStackPosition() {
|
||||
return TestUtils.NUM_CALL_STACK_PAGES * MachineState.ADDRESS_SIZE - callStackSize;
|
||||
}
|
||||
|
||||
protected int getCallStackEntry(int address) {
|
||||
public int getCallStackEntry(int address) {
|
||||
int index = CALL_STACK_OFFSET + 4 + address - TestUtils.NUM_CALL_STACK_PAGES * MachineState.ADDRESS_SIZE + callStackSize;
|
||||
return stateByteBuffer.getInt(index);
|
||||
}
|
||||
|
||||
protected int getUserStackPosition() {
|
||||
public int getUserStackPosition() {
|
||||
return TestUtils.NUM_USER_STACK_PAGES * MachineState.VALUE_SIZE - userStackSize;
|
||||
}
|
||||
|
||||
protected long getUserStackEntry(int address) {
|
||||
public long getUserStackEntry(int address) {
|
||||
int index = userStackOffset + 4 + address - TestUtils.NUM_USER_STACK_PAGES * MachineState.VALUE_SIZE + userStackSize;
|
||||
return stateByteBuffer.getLong(index);
|
||||
}
|
||||
|
37
Java/src/test/java/org/ciyam/at/test/QuietTestLogger.java
Normal file
37
Java/src/test/java/org/ciyam/at/test/QuietTestLogger.java
Normal file
@ -0,0 +1,37 @@
|
||||
package org.ciyam.at.test;
|
||||
|
||||
import org.ciyam.at.AtLogger;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class QuietTestLogger implements AtLogger {
|
||||
|
||||
@Override
|
||||
public void error(String message) {
|
||||
System.err.println("ERROR: " + message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(Supplier<String> messageSupplier) {
|
||||
System.err.println("ERROR: " + messageSupplier.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String message) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Supplier<String> messageSupplier) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void echo(String message) {
|
||||
System.err.println("ECHO: " + message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void echo(Supplier<String> messageSupplier) {
|
||||
System.err.println("ECHO: " + messageSupplier.get());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package org.ciyam.at.test;
|
||||
|
||||
import org.ciyam.at.AtLogger;
|
||||
import org.ciyam.at.AtLoggerFactory;
|
||||
|
||||
public class QuietTestLoggerFactory implements AtLoggerFactory {
|
||||
|
||||
@Override
|
||||
public AtLogger create(Class<?> loggerName) {
|
||||
return new QuietTestLogger();
|
||||
}
|
||||
|
||||
}
|
@ -19,14 +19,14 @@ import org.ciyam.at.Timestamp;
|
||||
public class TestAPI extends API {
|
||||
|
||||
/** Average period between blocks, in seconds. */
|
||||
public static final int BLOCK_PERIOD = 10 * 60;
|
||||
public static final int BLOCK_PERIOD = 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;
|
||||
public static final long DEFAULT_INITIAL_BALANCE = 10_0000_0000L;
|
||||
/** Initial block height for simple test scenarios. */
|
||||
public static final int DEFAULT_INITIAL_BLOCK_HEIGHT = 10;
|
||||
/** AT creation block height for simple test scenarios. */
|
||||
@ -102,29 +102,36 @@ public class TestAPI extends API {
|
||||
}
|
||||
}
|
||||
|
||||
public List<TestBlock> blockchain;
|
||||
public Map<String, TestAccount> accounts;
|
||||
public Map<String, TestTransaction> transactions;
|
||||
public List<TestBlock> blockchain = new ArrayList<>();
|
||||
public Map<String, TestAccount> accounts = new HashMap<>();
|
||||
public Map<String, TestTransaction> transactions = new HashMap<>();
|
||||
public List<TestTransaction> atTransactions = new ArrayList<>();
|
||||
|
||||
private TestBlock currentBlock = new TestBlock();
|
||||
private int currentBlockHeight;
|
||||
|
||||
public TestAPI() {
|
||||
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_CREATOR_ADDRESS, DEFAULT_INITIAL_BALANCE).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);
|
||||
new TestAccount("Initiator", DEFAULT_INITIAL_BALANCE * 2).addToMap(accounts);
|
||||
new TestAccount("Responder", DEFAULT_INITIAL_BALANCE * 3).addToMap(accounts);
|
||||
new TestAccount("Bystander", DEFAULT_INITIAL_BALANCE * 4).addToMap(accounts);
|
||||
}
|
||||
|
||||
transactions = new HashMap<>();
|
||||
// Hook to be overridden
|
||||
protected boolean willExecute(MachineState state, int blockHeight) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Hook to be oveerridden
|
||||
protected void preExecute(MachineState state) {
|
||||
}
|
||||
|
||||
public static byte[] encodeAddress(String address) {
|
||||
@ -153,9 +160,19 @@ public class TestAPI extends API {
|
||||
this.currentBlockHeight = blockHeight;
|
||||
}
|
||||
|
||||
public void addTransactionToCurrentBlock(TestTransaction testTransaction) {
|
||||
currentBlock.transactions.add(testTransaction);
|
||||
}
|
||||
|
||||
public void addCurrentBlockToChain() {
|
||||
addBlockToChain(currentBlock);
|
||||
currentBlock = new TestBlock();
|
||||
}
|
||||
|
||||
public TestBlock addBlockToChain(TestBlock newBlock) {
|
||||
blockchain.add(newBlock);
|
||||
final int blockHeight = blockchain.size();
|
||||
StringBuilder sb = new StringBuilder(256);
|
||||
|
||||
for (int seq = 0; seq < newBlock.transactions.size(); ++seq) {
|
||||
TestTransaction transaction = newBlock.transactions.get(seq);
|
||||
@ -165,6 +182,48 @@ public class TestAPI extends API {
|
||||
|
||||
// Add to transactions map
|
||||
transactions.put(stringifyHash(transaction.txHash), transaction);
|
||||
|
||||
// Transaction sent/received by AT? Add to AT transactions list
|
||||
if (transaction.sender.equals(AT_ADDRESS) || transaction.recipient.equals(AT_ADDRESS))
|
||||
atTransactions.add(transaction);
|
||||
|
||||
// Process PAYMENT transactions
|
||||
if (transaction.txType == ATTransactionType.PAYMENT) {
|
||||
sb.setLength(0);
|
||||
sb.append(transaction.sender)
|
||||
.append(" sent ")
|
||||
.append(prettyAmount(transaction.amount));
|
||||
|
||||
// Subtract amount from sender
|
||||
TestAccount senderAccount = accounts.get(transaction.sender);
|
||||
if (senderAccount == null)
|
||||
throw new IllegalStateException(String.format("Can't send from unknown sender %s: no funds!",
|
||||
transaction.sender));
|
||||
|
||||
// Do not apply if sender is AT because balance update already performed during execution
|
||||
if (!transaction.sender.equals(AT_ADDRESS)) {
|
||||
senderAccount.balance -= transaction.amount;
|
||||
}
|
||||
|
||||
if (senderAccount.balance < 0)
|
||||
throw new IllegalStateException(String.format("Can't send %s from %s: insufficient funds (%s)",
|
||||
prettyAmount(transaction.amount),
|
||||
transaction.sender,
|
||||
prettyAmount(senderAccount.balance)));
|
||||
|
||||
// Add amount to recipient
|
||||
sb.append(" to ");
|
||||
TestAccount recipientAccount = accounts.get(transaction.recipient);
|
||||
if (recipientAccount == null) {
|
||||
sb.append("(new) ");
|
||||
recipientAccount = new TestAccount(transaction.recipient, 0);
|
||||
accounts.put(transaction.recipient, recipientAccount);
|
||||
}
|
||||
recipientAccount.balance += transaction.amount;
|
||||
sb.append(transaction.recipient);
|
||||
|
||||
System.out.println(sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return newBlock;
|
||||
@ -288,7 +347,13 @@ public class TestAPI extends API {
|
||||
|
||||
if (transaction.recipient.equals("AT")) {
|
||||
// Found a transaction
|
||||
System.out.println("Found transaction at height " + blockHeight + " sequence " + transactionSequence);
|
||||
System.out.println(String.format("Found transaction at height %d, sequence %d: %s %s from %s",
|
||||
blockHeight,
|
||||
transactionSequence,
|
||||
transaction.txType.equals(ATTransactionType.PAYMENT) ? prettyAmount(transaction.amount) : "",
|
||||
transaction.txType.name(),
|
||||
transaction.sender
|
||||
));
|
||||
|
||||
// Generate pseudo-hash of transaction
|
||||
this.setA(state, transaction.txHash);
|
||||
@ -392,12 +457,28 @@ public class TestAPI extends API {
|
||||
if (recipient == null)
|
||||
throw new IllegalStateException("Refusing to pay to unknown account: " + address);
|
||||
|
||||
recipient.balance += amount;
|
||||
System.out.println(String.format("Paid %s to '%s', their balance now: %s", prettyAmount(amount), recipient.address, prettyAmount(recipient.balance)));
|
||||
if (amount < 0)
|
||||
throw new IllegalStateException(String.format("Refusing to pay negative amount: %s", amount));
|
||||
|
||||
if (amount == 0) {
|
||||
System.out.println(String.format("Skipping zero-amount payment to account %s", address));
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println(String.format("Creating PAYMENT of %s to %s", prettyAmount(amount), recipient.address));
|
||||
|
||||
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)));
|
||||
System.out.println(String.format("AT current balance was %s, now: %s", prettyAmount(previousBalance), prettyAmount(newBalance)));
|
||||
|
||||
// Add suitable transaction to currentBlock
|
||||
|
||||
// Generate tx hash
|
||||
byte[] txHash = new byte[32];
|
||||
RANDOM.nextBytes(txHash);
|
||||
|
||||
TestTransaction testTransaction = new TestTransaction(txHash, AT_ADDRESS, recipient.address, amount);
|
||||
addTransactionToCurrentBlock(testTransaction);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -409,7 +490,17 @@ public class TestAPI extends API {
|
||||
if (recipient == null)
|
||||
throw new IllegalStateException("Refusing to send message to unknown account: " + address);
|
||||
|
||||
recipient.messages.add(this.getA(state));
|
||||
byte[] message = this.getA(state);
|
||||
recipient.messages.add(message);
|
||||
|
||||
// Add suitable transaction to currentBlock
|
||||
|
||||
// Generate tx hash
|
||||
byte[] txHash = new byte[32];
|
||||
RANDOM.nextBytes(txHash);
|
||||
|
||||
TestTransaction testTransaction = new TestTransaction(txHash, AT_ADDRESS, recipient.address, message);
|
||||
addTransactionToCurrentBlock(testTransaction);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -423,10 +514,16 @@ public class TestAPI extends API {
|
||||
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 AT creator '%s', their balance now: %s", prettyAmount(amount), atCreatorAccount.address, prettyAmount(atCreatorAccount.balance)));
|
||||
System.out.println(String.format("Creating PAYMENT of %s to AT creator %s", prettyAmount(amount), atCreatorAccount.address));
|
||||
|
||||
accounts.get(AT_ADDRESS).balance -= amount;
|
||||
// Add suitable transaction to currentBlock
|
||||
|
||||
// Generate tx hash
|
||||
byte[] txHash = new byte[32];
|
||||
RANDOM.nextBytes(txHash);
|
||||
|
||||
TestTransaction testTransaction = new TestTransaction(txHash, AT_ADDRESS, atCreatorAccount.address, amount);
|
||||
addTransactionToCurrentBlock(testTransaction);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Loading…
Reference in New Issue
Block a user