From e522fb312dd68a100e17b7bc57dcba708c3e803f Mon Sep 17 00:00:00 2001 From: catbref Date: Sat, 20 Nov 2021 15:22:50 +0000 Subject: [PATCH 1/3] Improved test support: * pom.xml now builds JavaDoc, sources and test JARs * more descriptive output from test classes to aid debugging * actually add/collate AT-generated transactions to test blockchain for analysis * test block period changed from 10 minutes to 1 minute * 'quiet' logger aded that doesn't emit DEBUG log entries --- Java/pom.xml | 55 ++++++++++++++++ .../java/org/ciyam/at/AtLoggerFactory.java | 1 + Java/src/main/java/org/ciyam/at/OpCode.java | 6 +- .../ciyam/at/BlockchainFunctionCodeTests.java | 16 ++++- .../org/ciyam/at/test/ExecutableTest.java | 3 + .../org/ciyam/at/test/QuietTestLogger.java | 37 +++++++++++ .../ciyam/at/test/QuietTestLoggerFactory.java | 13 ++++ .../test/java/org/ciyam/at/test/TestAPI.java | 62 ++++++++++++++++--- 8 files changed, 181 insertions(+), 12 deletions(-) create mode 100644 Java/src/test/java/org/ciyam/at/test/QuietTestLogger.java create mode 100644 Java/src/test/java/org/ciyam/at/test/QuietTestLoggerFactory.java diff --git a/Java/pom.xml b/Java/pom.xml index f49c4cd..25fd5d7 100644 --- a/Java/pom.xml +++ b/Java/pom.xml @@ -6,16 +6,20 @@ AT 1.4.0 jar + UTF-8 false 3.8.1 + 3.2.0 3.3.1 3.0.0-M4 + 3.2.0 1.64 + src/main/java src/test/java @@ -37,6 +41,19 @@ ${skipTests} + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + attach-sources + + jar + + + + org.apache.maven.plugins maven-javadoc-plugin @@ -50,9 +67,47 @@ + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + + test-jar + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + org.bouncycastle bcprov-jdk15on diff --git a/Java/src/main/java/org/ciyam/at/AtLoggerFactory.java b/Java/src/main/java/org/ciyam/at/AtLoggerFactory.java index 2a07fa2..b327e8d 100644 --- a/Java/src/main/java/org/ciyam/at/AtLoggerFactory.java +++ b/Java/src/main/java/org/ciyam/at/AtLoggerFactory.java @@ -1,5 +1,6 @@ package org.ciyam.at; +@FunctionalInterface public interface AtLoggerFactory { AtLogger create(final Class loggerName); diff --git a/Java/src/main/java/org/ciyam/at/OpCode.java b/Java/src/main/java/org/ciyam/at/OpCode.java index ae13ece..04854dd 100644 --- a/Java/src/main/java/org/ciyam/at/OpCode.java +++ b/Java/src/main/java/org/ciyam/at/OpCode.java @@ -1070,7 +1070,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 diff --git a/Java/src/test/java/org/ciyam/at/BlockchainFunctionCodeTests.java b/Java/src/test/java/org/ciyam/at/BlockchainFunctionCodeTests.java index 5df06f8..4ae7402 100644 --- a/Java/src/test/java/org/ciyam/at/BlockchainFunctionCodeTests.java +++ b/Java/src/test/java/org/ciyam/at/BlockchainFunctionCodeTests.java @@ -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); diff --git a/Java/src/test/java/org/ciyam/at/test/ExecutableTest.java b/Java/src/test/java/org/ciyam/at/test/ExecutableTest.java index cc5bff8..96a52c8 100644 --- a/Java/src/test/java/org/ciyam/at/test/ExecutableTest.java +++ b/Java/src/test/java/org/ciyam/at/test/ExecutableTest.java @@ -96,6 +96,9 @@ public abstract class ExecutableTest { System.out.println("New balance: " + TestAPI.prettyAmount(newBalance)); api.setCurrentBalance(newBalance); + // Add block, possibly containing AT-created transactions, to chain to at least provide block hashes + api.addCurrentBlockToChain(); + // Bump block height api.bumpCurrentBlockHeight(); diff --git a/Java/src/test/java/org/ciyam/at/test/QuietTestLogger.java b/Java/src/test/java/org/ciyam/at/test/QuietTestLogger.java new file mode 100644 index 0000000..f27c809 --- /dev/null +++ b/Java/src/test/java/org/ciyam/at/test/QuietTestLogger.java @@ -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 messageSupplier) { + System.err.println("ERROR: " + messageSupplier.get()); + } + + @Override + public void debug(String message) { + } + + @Override + public void debug(Supplier messageSupplier) { + } + + @Override + public void echo(String message) { + System.err.println("ECHO: " + message); + } + + @Override + public void echo(Supplier messageSupplier) { + System.err.println("ECHO: " + messageSupplier.get()); + } + +} diff --git a/Java/src/test/java/org/ciyam/at/test/QuietTestLoggerFactory.java b/Java/src/test/java/org/ciyam/at/test/QuietTestLoggerFactory.java new file mode 100644 index 0000000..d1325d1 --- /dev/null +++ b/Java/src/test/java/org/ciyam/at/test/QuietTestLoggerFactory.java @@ -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(); + } + +} diff --git a/Java/src/test/java/org/ciyam/at/test/TestAPI.java b/Java/src/test/java/org/ciyam/at/test/TestAPI.java index d500eef..316dca0 100644 --- a/Java/src/test/java/org/ciyam/at/test/TestAPI.java +++ b/Java/src/test/java/org/ciyam/at/test/TestAPI.java @@ -19,7 +19,7 @@ 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. */ @@ -102,29 +102,27 @@ public class TestAPI extends API { } } - public List blockchain; - public Map accounts; - public Map transactions; + public List blockchain = new ArrayList<>(); + public Map accounts = new HashMap<>(); + public Map transactions = new HashMap<>(); + public List 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_ADDRESS, DEFAULT_INITIAL_BALANCE).addToMap(accounts); new TestAccount("Initiator", 100000L).addToMap(accounts); new TestAccount("Responder", 200000L).addToMap(accounts); new TestAccount("Bystander", 300000L).addToMap(accounts); - - transactions = new HashMap<>(); } public static byte[] encodeAddress(String address) { @@ -153,6 +151,15 @@ 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(); @@ -165,6 +172,10 @@ 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); } return newBlock; @@ -288,7 +299,11 @@ 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 from %s", + blockHeight, + transactionSequence, + transaction.txType.name(), + transaction.sender)); // Generate pseudo-hash of transaction this.setA(state, transaction.txHash); @@ -392,12 +407,29 @@ public class TestAPI extends API { if (recipient == null) throw new IllegalStateException("Refusing to pay to unknown account: " + address); + 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; + } + recipient.balance += amount; System.out.println(String.format("Paid %s to '%s', their balance now: %s", prettyAmount(amount), recipient.address, prettyAmount(recipient.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))); + + // 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 +441,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 From ae23aac71630bc71d6c8bffab372e56a7337e611 Mon Sep 17 00:00:00 2001 From: catbref Date: Sun, 28 Nov 2021 13:42:42 +0000 Subject: [PATCH 2/3] Improvements to TestAPI and ExecutableTest to help testing external ATs like lottery, etc. No changes to core AT. --- .../src/test/java/org/ciyam/at/MiscTests.java | 43 +++++++++- .../org/ciyam/at/test/ExecutableTest.java | 74 ++++++++++------- .../test/java/org/ciyam/at/test/TestAPI.java | 81 ++++++++++++++++--- 3 files changed, 152 insertions(+), 46 deletions(-) diff --git a/Java/src/test/java/org/ciyam/at/MiscTests.java b/Java/src/test/java/org/ciyam/at/MiscTests.java index 21841cd..9ccc5dd 100644 --- a/Java/src/test/java/org/ciyam/at/MiscTests.java +++ b/Java/src/test/java/org/ciyam/at/MiscTests.java @@ -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 diff --git a/Java/src/test/java/org/ciyam/at/test/ExecutableTest.java b/Java/src/test/java/org/ciyam/at/test/ExecutableTest.java index 96a52c8..c1cba48 100644 --- a/Java/src/test/java/org/ciyam/at/test/ExecutableTest.java +++ b/Java/src/test/java/org/ciyam/at/test/ExecutableTest.java @@ -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()); + + 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(state.getCurrentBalance())); + 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,22 +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"); + } - // Add block, possibly containing AT-created transactions, to chain to at least provide block hashes - api.addCurrentBlockToChain(); + // Add block, possibly containing AT-created transactions, to chain to at least provide block hashes + api.addCurrentBlockToChain(); - // Bump block height - api.bumpCurrentBlockHeight(); + // Bump block height + api.bumpCurrentBlockHeight(); - packedState = state.toBytes(); - System.out.println("Execution round finished\n"); - } while (!onceOnly && !state.isFinished()); - - 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(); @@ -124,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); } diff --git a/Java/src/test/java/org/ciyam/at/test/TestAPI.java b/Java/src/test/java/org/ciyam/at/test/TestAPI.java index 316dca0..7ade50c 100644 --- a/Java/src/test/java/org/ciyam/at/test/TestAPI.java +++ b/Java/src/test/java/org/ciyam/at/test/TestAPI.java @@ -26,7 +26,7 @@ public class TestAPI extends API { 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. */ @@ -118,11 +118,20 @@ public class TestAPI extends API { blockchain.add(new TestBlock()); // Set up test accounts - 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); + } + + // 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) { @@ -163,6 +172,7 @@ public class TestAPI extends API { 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); @@ -176,6 +186,44 @@ public class TestAPI extends API { // 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; @@ -299,11 +347,13 @@ public class TestAPI extends API { if (transaction.recipient.equals("AT")) { // Found a transaction - System.out.println(String.format("Found transaction at height %d, sequence %d: %s from %s", + 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)); + transaction.sender + )); // Generate pseudo-hash of transaction this.setA(state, transaction.txHash); @@ -415,12 +465,11 @@ public class TestAPI extends API { return; } - recipient.balance += amount; - System.out.println(String.format("Paid %s to '%s', their balance now: %s", prettyAmount(amount), recipient.address, prettyAmount(recipient.balance))); + 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 @@ -465,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 From 836ef215d44b43d80c222fe0f2154f31c84d1abd Mon Sep 17 00:00:00 2001 From: catbref Date: Thu, 2 Dec 2021 21:10:11 +0000 Subject: [PATCH 3/3] Minor JavaDoc fix-up, in particular the wrong explanation for SLP_VAL OpCode --- Java/src/main/java/org/ciyam/at/OpCode.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Java/src/main/java/org/ciyam/at/OpCode.java b/Java/src/main/java/org/ciyam/at/OpCode.java index 04854dd..a6ffb88 100644 --- a/Java/src/main/java/org/ciyam/at/OpCode.java +++ b/Java/src/main/java/org/ciyam/at/OpCode.java @@ -561,7 +561,7 @@ public enum OpCode { /** * SLeeP until DATa
* 0x25 addr
- * sleep until $addr, then carry on from current PC
+ * Sleep until $addr, then carry on from current PC
* Note: The value from $addr is considered to be a block height. */ SLP_DAT(0x25, OpCodeParam.BLOCK_HEIGHT) { @@ -634,7 +634,7 @@ public enum OpCode { /** * SLeeP IMmeDiately
* 0x2a
- * sleep until next block, then carry on from current PC + * Sleep until next block, then carry on from current PC */ SLP_IMD(0x2a) { @Override @@ -659,8 +659,7 @@ public enum OpCode { /** * SLeeP for VALue blocks
* 0x2c value
- * sleep until $addr, then carry on from current PC
- * Note: The value from $addr is considered to be a block height. + * Sleep for value blocks, then carry on from current PC */ SLP_VAL(0x2c, OpCodeParam.VALUE) { @Override