Skip to content

Commit

Permalink
AKI-549: Added tests around the RPC primitive required for event list…
Browse files Browse the repository at this point in the history
…ening

-this is much of the the "layer 1" of our data projection design
-this is mostly just to demonstrate what the data looks like and that it works as expected
  • Loading branch information
jeff-aion committed Nov 15, 2019
1 parent 8eee41e commit 7590fe1
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 6 deletions.
80 changes: 80 additions & 0 deletions TestHarness/src/org/aion/harness/main/RPC.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package org.aion.harness.main;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.math.BigInteger;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.aion.harness.kernel.Address;
import org.aion.harness.kernel.SignedTransaction;
Expand All @@ -20,8 +23,10 @@
import org.aion.harness.main.types.MinedBlockSolution;
import org.aion.harness.main.types.ReceiptHash;
import org.aion.harness.main.types.SyncStatus;
import org.aion.harness.main.types.TransactionLog;
import org.aion.harness.main.types.TransactionReceipt;
import org.aion.harness.main.types.internal.BlockBuilder;
import org.aion.harness.main.types.internal.TransactionLogBuilder;
import org.aion.harness.main.types.internal.TransactionReceiptBuilder;
import org.aion.harness.main.types.BlockTemplate;
import org.aion.harness.misc.Assumptions;
Expand Down Expand Up @@ -713,6 +718,81 @@ public Result waitForSyncToComplete(long timeout, TimeUnit timeoutUnit) throws I
}
}

/**
* Checks for logs generated at or after the given startingPoint, which have a first topic in the given set and which were generated by the given contract.
* Note that the "address" filter of the RPC cannot be used since that is the target of the external transaction, not always what generated the log.
*
* @param startingPoint The first block number to check for logs.
* @param firstTopicFilterUnion The returned logs must have a first topic which is in this set (a null set will do no topic filtering).
* @param reportingContract The address of the contract which logged the event.
* @return the result of the filtered logs.
*/
public RpcResult<List<TransactionLog>> getLatestFilteredLogs(long startingPoint, Set<byte[]> firstTopicFilterUnion, Address reportingContract) throws InterruptedException {
if (startingPoint < 1) {
throw new IllegalArgumentException("Starting point must be a positive integer.");
}
if (reportingContract == null) {
throw new IllegalArgumentException("This helper expects that the address is already known.");
}
boolean verbose = false;

// Construct the payload to the rpc call (ie. the content of --data).
// For now, we will just use "latest" as the destination but non-testing cases will likely want to impose a limit on this.
String payload = "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getLogs\",\"params\":[{\"fromBlock\":" + startingPoint + ",\"toBlock\":\"latest\"";
if (null != firstTopicFilterUnion) {
payload += ",\"topics\":[[";
boolean addComma = false;
if (firstTopicFilterUnion.isEmpty()) {
throw new IllegalArgumentException("The set of first topic filters must be null or non-empty.");
}
for (byte[] topic : firstTopicFilterUnion) {
if (addComma) {
payload += ",";
}
payload += "\"0x" + Hex.encodeHexString(padRight(32, topic)) + "\"";
addComma = true;
}
payload += "]]";
}
payload += "}],\"id\":1}";

logMessage("-->" + payload);
InternalRpcResult internalResult = this.rpc.call(payload, verbose);
logMessage("<--" + internalResult.output);

// We want to go through these, filtering based on address.
RpcResult<List<TransactionLog>> finalResult = null;
try {
if (internalResult.success) {
JsonObject topLevelResponse = (JsonObject) new JsonParser().parse(internalResult.output);
JsonArray resultArray = topLevelResponse.getAsJsonArray("result");
// First, we need to read all the log entries.
List<TransactionLog> logs = new ArrayList<>();
for (JsonElement result : resultArray) {
JsonObject obj = (JsonObject) result;
TransactionLog log = new TransactionLogBuilder().buildFromJsonString(obj.toString());
// If this was a different address than the one we wanted to see, ignore it.
if (reportingContract.equals(log.address)) {
logs.add(log);
}
}
finalResult = RpcResult.successful(logs, internalResult.getTimeOfCall(TimeUnit.NANOSECONDS), TimeUnit.NANOSECONDS);
} else {
finalResult = RpcResult.unsuccessful(internalResult.error);
}
} catch (Exception e) {
// This is unexpected.
throw new AssertionError(e);
}
return finalResult;
}

private byte[] padRight(int width, byte[] topic) {
byte[] result = new byte[width];
System.arraycopy(topic, 0, result, 0, topic.length);
return result;
}

private void broadcastSyncUpdate(boolean waitingToConnect, BigInteger currentBlock, BigInteger highestBlock) {
if (waitingToConnect) {
logMessage(Assumptions.LOGGER_BANNER + "Sync Progress = { waiting to connect to peers }");
Expand Down
21 changes: 20 additions & 1 deletion TestHarness/src/org/aion/harness/main/types/TransactionLog.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,19 @@ public final class TransactionLog {
private final byte[] data;
private final List<byte[]> topics;
public final BigInteger blockNumber;
private final byte[] blockHash;
public final int transactionIndex;
public final int logIndex;

public TransactionLog(Address address, byte[] data, List<byte[]> topics, BigInteger blockNumber, int transactionIndex, int logIndex) {
public TransactionLog(Address address, byte[] data, List<byte[]> topics, BigInteger blockNumber, byte[] blockHash, int transactionIndex, int logIndex) {
this.address = address;
this.data = Arrays.copyOf(data, data.length);
this.topics = copyOfBytesList(topics);
this.blockNumber = blockNumber;
// Note that "blockHash" doesn't appear in the logs when fetched from the receipt so it can be null.
this.blockHash = (null != blockHash)
? Arrays.copyOf(blockHash, blockHash.length)
: null;
this.transactionIndex = transactionIndex;
this.logIndex = logIndex;
}
Expand All @@ -54,6 +59,17 @@ public List<byte[]> copyOfTopics() {
return copyOfBytesList(this.topics);
}

/**
* Returns a copy of the block hash.
*
* @return the block hash.
*/
public byte[] copyOfBlockHash() {
return (null != blockHash)
? Arrays.copyOf(this.blockHash, this.blockHash.length)
: null;
}

@Override
public String toString() {
return "TransactionLog { address = " + this.address + ", topics = [" + topicsToString()
Expand Down Expand Up @@ -86,6 +102,9 @@ public boolean equals(Object other) {
if (!Arrays.equals(this.data, otherLog.data)) {
return false;
}
if (!Arrays.equals(this.blockHash, otherLog.blockHash)) {
return false;
}

return bytesListsAreEqual(this.topics, otherLog.topics);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public final class TransactionLogBuilder {
private byte[] data = null;
private List<byte[]> topics = null;
private BigInteger blockNumber = null;
private byte[] blockHash = null;
private Integer transactionIndex = null;
private Integer logIndex = null;

Expand All @@ -42,6 +43,11 @@ public TransactionLogBuilder blockNumber(BigInteger blockNumber) {
return this;
}

public TransactionLogBuilder blockHash(byte[] blockHash) {
this.blockHash = blockHash;
return this;
}

public TransactionLogBuilder transactionIndex(int transactionIndex) {
this.transactionIndex = transactionIndex;
return this;
Expand Down Expand Up @@ -71,7 +77,7 @@ public TransactionLog build() {

List<byte[]> topics = (this.topics == null) ? Collections.emptyList() : this.topics;

return new TransactionLog(this.address, this.data, topics, this.blockNumber, this.transactionIndex, this.logIndex);
return new TransactionLog(this.address, this.data, topics, this.blockNumber, this.blockHash, this.transactionIndex, this.logIndex);
}

public TransactionLog buildFromJsonString(String jsonString) throws DecoderException {
Expand All @@ -81,17 +87,23 @@ public TransactionLog buildFromJsonString(String jsonString) throws DecoderExcep
String data = jsonParser.attributeToString("data");
String topics = jsonParser.attributeToString("topics");
String blockNumber = jsonParser.attributeToString("blockNumber");
String blockHash = jsonParser.attributeToString("blockHash");
String transactionIndex = jsonParser.attributeToString("transactionIndex");
String logIndex = jsonParser.attributeToString("logIndex");

return new TransactionLogBuilder()
// Note that "blockHash" doesn't appear in the logs when fetched from the receipt, but does when fetched from getLogs, so handle those 2 cases.
TransactionLogBuilder builder = new TransactionLogBuilder()
.address(new Address(Hex.decodeHex(address)))
.data(Hex.decodeHex(data))
.topics(parseJsonTopics(topics))
.blockNumber(new BigInteger(blockNumber, 16))
.transactionIndex(Integer.parseInt(transactionIndex, 16))
.logIndex(Integer.parseInt(logIndex, 16))
.build();
;
if (null != blockHash) {
builder.blockHash(Hex.decodeHex(blockHash));
}
return builder.build();
}

private static List<byte[]> parseJsonTopics(String jsonArrayOfTopics) throws DecoderException {
Expand Down
57 changes: 55 additions & 2 deletions Tests/test/org/aion/harness/tests/integ/AvmReceiptLogTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
Expand All @@ -25,7 +26,6 @@
import org.aion.avm.userlib.abi.ABIToken;
import org.aion.harness.kernel.Address;
import org.aion.harness.kernel.SignedTransaction;
import org.aion.harness.main.NodeFactory.NodeType;
import org.aion.harness.main.RPC;
import org.aion.harness.main.event.Event;
import org.aion.harness.main.event.IEvent;
Expand All @@ -36,7 +36,6 @@
import org.aion.harness.result.LogEventResult;
import org.aion.harness.result.RpcResult;
import org.aion.harness.tests.contracts.avm.LogTarget;
import org.aion.harness.tests.integ.runner.ExcludeNodeType;
import org.aion.harness.tests.integ.runner.SequentialRunner;
import org.aion.harness.tests.integ.runner.internal.LocalNodeListener;
import org.aion.harness.tests.integ.runner.internal.PreminedAccount;
Expand Down Expand Up @@ -72,6 +71,10 @@ public void testContractWritesNoLogs() throws Exception {
TransactionReceipt receipt = callMethodWriteNoLogs(contract);
assertTrue(receipt.transactionWasSuccessful());
assertTrue(receipt.getLogs().isEmpty());

long blockNumber = receipt.getBlockNumber().longValue();
List<TransactionLog> logs = rpc.getLatestFilteredLogs(blockNumber, null, contract).getResult();
assertTrue(logs.isEmpty());
}

@Test
Expand All @@ -86,6 +89,12 @@ public void testContractWritesDataOnlyLog() throws Exception {

TransactionLog log = logs.get(0);
assertIsDataOnlyLog(contract, log);

long blockNumber = receipt.getBlockNumber().longValue();
logs = rpc.getLatestFilteredLogs(blockNumber, null, contract).getResult();
assertEquals(1, logs.size());
assertIsDataOnlyLog(contract, logs.get(0));
assertArrayEquals(receipt.getBlockHash(), logs.get(0).copyOfBlockHash());
}

@Test
Expand All @@ -100,6 +109,12 @@ public void testContractWritesLogWithOneTopic() throws Exception {

TransactionLog log = logs.get(0);
assertIsLogWithOneTopic(contract, log);

long blockNumber = receipt.getBlockNumber().longValue();
logs = rpc.getLatestFilteredLogs(blockNumber, Collections.singleton(new byte[]{ 9, 5, 5, 2, 3, 8, 1 }), contract).getResult();
assertEquals(1, logs.size());
assertIsLogWithOneTopic(contract, logs.get(0));
assertArrayEquals(receipt.getBlockHash(), logs.get(0).copyOfBlockHash());
}

@Test
Expand All @@ -114,6 +129,12 @@ public void testContractWritesLogWithTwoTopics() throws Exception {

TransactionLog log = logs.get(0);
assertIsLogWithTwoTopics(contract, log);

long blockNumber = receipt.getBlockNumber().longValue();
logs = rpc.getLatestFilteredLogs(blockNumber, Collections.singleton(new byte[]{ 9, 5, 5, 2, 3, 8, 1 }), contract).getResult();
assertEquals(1, logs.size());
assertIsLogWithTwoTopics(contract, logs.get(0));
assertArrayEquals(receipt.getBlockHash(), logs.get(0).copyOfBlockHash());
}

@Test
Expand All @@ -128,6 +149,12 @@ public void testContractWritesLogWithThreeTopics() throws Exception {

TransactionLog log = logs.get(0);
assertIsLogWithThreeTopics(contract, log);

long blockNumber = receipt.getBlockNumber().longValue();
logs = rpc.getLatestFilteredLogs(blockNumber, Collections.singleton(new byte[]{ 9, 5, 5, 2, 3, 8, 1 }), contract).getResult();
assertEquals(1, logs.size());
assertIsLogWithThreeTopics(contract, logs.get(0));
assertArrayEquals(receipt.getBlockHash(), logs.get(0).copyOfBlockHash());
}

@Test
Expand All @@ -142,6 +169,12 @@ public void testContractWritesLogWithFourTopics() throws Exception {

TransactionLog log = logs.get(0);
assertIsLogWithFourTopics(contract, log);

long blockNumber = receipt.getBlockNumber().longValue();
logs = rpc.getLatestFilteredLogs(blockNumber, Collections.singleton(new byte[]{ 9, 5, 5, 2, 3, 8, 1 }), contract).getResult();
assertEquals(1, logs.size());
assertIsLogWithFourTopics(contract, logs.get(0));
assertArrayEquals(receipt.getBlockHash(), logs.get(0).copyOfBlockHash());
}

@Test
Expand Down Expand Up @@ -181,6 +214,16 @@ public void testContractWritesMultipleLogs() throws Exception {
for (boolean foundTopic : foundTopics) {
assertTrue(foundTopic);
}

// We will search with the 1 topic, so only find 4 (not the data-only one).
long blockNumber = receipt.getBlockNumber().longValue();
logs = rpc.getLatestFilteredLogs(blockNumber, Collections.singleton(new byte[]{ 9, 5, 5, 2, 3, 8, 1 }), contract).getResult();
assertEquals(4, logs.size());
assertIsLogWithOneTopic(contract, logs.get(0));
assertIsLogWithTwoTopics(contract, logs.get(1));
assertIsLogWithThreeTopics(contract, logs.get(2));
assertIsLogWithFourTopics(contract, logs.get(3));
assertArrayEquals(receipt.getBlockHash(), logs.get(0).copyOfBlockHash());
}

@Test
Expand Down Expand Up @@ -265,6 +308,16 @@ public void testContractWritesMultipleLogsAndAlsoLogsInternalCall() throws Excep
for (boolean foundCalleeTopic : foundCalleeTopics) {
assertTrue(foundCalleeTopic);
}

// We are only asking about the callee, and only the entries with the topic, so we only see the 4 writes.
long blockNumber = receipt.getBlockNumber().longValue();
logs = rpc.getLatestFilteredLogs(blockNumber, Collections.singleton(new byte[]{ 9, 5, 5, 2, 3, 8, 1 }), calleeContract).getResult();
assertEquals(4, logs.size());
assertIsLogWithOneTopic(calleeContract, logs.get(0));
assertIsLogWithTwoTopics(calleeContract, logs.get(1));
assertIsLogWithThreeTopics(calleeContract, logs.get(2));
assertIsLogWithFourTopics(calleeContract, logs.get(3));
assertArrayEquals(receipt.getBlockHash(), logs.get(0).copyOfBlockHash());
}

private TransactionReceipt callMethodWriteLogsFromInternalCallAlso(Address caller, Address callee, byte[] data)
Expand Down

0 comments on commit 7590fe1

Please sign in to comment.