Skip to content

Commit

Permalink
[PAN-3223] Add GraphQL query/logs support (hyperledger#94)
Browse files Browse the repository at this point in the history
Add support for the top level logs query.

Signed-off-by: Danno Ferrin <danno.ferrin@gmail.com>
Signed-off-by: edwardmack <ed@edwardmack.com>
  • Loading branch information
shemnon authored and edwardmack committed Nov 4, 2019
1 parent b339b73 commit 1682a1c
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@
import static com.google.common.base.Preconditions.checkArgument;

import org.hyperledger.besu.ethereum.api.BlockWithMetadata;
import org.hyperledger.besu.ethereum.api.LogWithMetadata;
import org.hyperledger.besu.ethereum.api.LogsQuery;
import org.hyperledger.besu.ethereum.api.TransactionWithMetadata;
import org.hyperledger.besu.ethereum.api.graphql.internal.BlockchainQuery;
import org.hyperledger.besu.ethereum.api.graphql.internal.pojoadapter.AccountAdapter;
import org.hyperledger.besu.ethereum.api.graphql.internal.pojoadapter.LogAdapter;
import org.hyperledger.besu.ethereum.api.graphql.internal.pojoadapter.NormalBlockAdapter;
import org.hyperledger.besu.ethereum.api.graphql.internal.pojoadapter.PendingStateAdapter;
import org.hyperledger.besu.ethereum.api.graphql.internal.pojoadapter.SyncStateAdapter;
Expand All @@ -29,6 +32,7 @@
import org.hyperledger.besu.ethereum.core.Account;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.LogTopic;
import org.hyperledger.besu.ethereum.core.Synchronizer;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.WorldState;
Expand All @@ -46,9 +50,11 @@

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.Set;
import java.util.stream.Collectors;

import com.google.common.base.Preconditions;
import graphql.schema.DataFetcher;
Expand Down Expand Up @@ -204,6 +210,43 @@ DataFetcher<Optional<AccountAdapter>> getAccountDataFetcher() {
};
}

DataFetcher<Optional<List<LogAdapter>>> getLogsDataFetcher() {
return dataFetchingEnvironment -> {
final BlockchainQuery blockchainQuery =
((GraphQLDataFetcherContext) dataFetchingEnvironment.getContext()).getBlockchainQuery();

final Map<String, Object> filter = dataFetchingEnvironment.getArgument("filter");

final long currentBlock = blockchainQuery.getBlockchain().getChainHeadBlockNumber();
final long fromBlock = (Long) filter.getOrDefault("fromBlock", currentBlock);
final long toBlock = (Long) filter.getOrDefault("toBlock", currentBlock);

if (fromBlock > toBlock) {
throw new GraphQLException(GraphQLError.INVALID_PARAMS);
}

@SuppressWarnings("unchecked")
final List<Address> addrs = (List<Address>) filter.get("addresses");
@SuppressWarnings("unchecked")
final List<List<Bytes32>> topics = (List<List<Bytes32>>) filter.get("topics");

final List<List<LogTopic>> transformedTopics = new ArrayList<>();
for (final List<Bytes32> topic : topics) {
transformedTopics.add(topic.stream().map(LogTopic::of).collect(Collectors.toList()));
}

final LogsQuery query =
new LogsQuery.Builder().addresses(addrs).topics(transformedTopics).build();

final List<LogWithMetadata> logs = blockchainQuery.matchingLogs(fromBlock, toBlock, query);
final List<LogAdapter> results = new ArrayList<>();
for (final LogWithMetadata log : logs) {
results.add(new LogAdapter(log));
}
return Optional.of(results);
};
}

DataFetcher<Optional<TransactionAdapter>> getTransactionDataFetcher() {
return dataFetchingEnvironment -> {
final BlockchainQuery blockchain =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ private static RuntimeWiring buildWiring(final GraphQLDataFetchers graphQLDataFe
.dataFetcher("account", graphQLDataFetchers.getAccountDataFetcher())
.dataFetcher("block", graphQLDataFetchers.getBlockDataFetcher())
.dataFetcher("blocks", graphQLDataFetchers.getRangeBlockDataFetcher())
.dataFetcher("logs", graphQLDataFetchers.getLogsDataFetcher())
.dataFetcher("transaction", graphQLDataFetchers.getTransactionDataFetcher())
.dataFetcher("gasPrice", graphQLDataFetchers.getGasPriceDataFetcher())
.dataFetcher("syncing", graphQLDataFetchers.getSyncingDataFetcher())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,42 @@ private List<TransactionWithMetadata> formatTransactions(
return result;
}

/**
* Retrieve logs from the range of blocks with optional filtering based on logger address and log
* topics.
*
* @param fromBlockNumber The block number defining the first block in the search range
* (inclusive).
* @param toBlockNumber The block number defining the last block in the search range (inclusive).
* @param query Constraints on required topics by topic index. For a given index if the set of
* topics is non-empty, the topic at this index must match one of the values in the set.
* @return The set of logs matching the given constraints.
*/
public List<LogWithMetadata> matchingLogs(
final long fromBlockNumber, final long toBlockNumber, final LogsQuery query) {
if (fromBlockNumber > toBlockNumber || toBlockNumber > blockchain.getChainHeadBlockNumber()) {
return Lists.newArrayList();
}
List<LogWithMetadata> matchingLogs = Lists.newArrayList();
for (long blockNumber = fromBlockNumber; blockNumber <= toBlockNumber; blockNumber++) {
final Hash blockhash = blockchain.getBlockHashByNumber(blockNumber).get();
final boolean logHasBeenRemoved = !blockchain.blockIsOnCanonicalChain(blockhash);
final List<TransactionReceipt> receipts = blockchain.getTxReceipts(blockhash).get();
final List<Transaction> transaction =
blockchain.getBlockBody(blockhash).get().getTransactions();
matchingLogs =
generateLogWithMetadata(
receipts,
blockNumber,
query,
blockhash,
matchingLogs,
transaction,
logHasBeenRemoved);
}
return matchingLogs;
}

public List<LogWithMetadata> matchingLogs(final Hash blockhash, final LogsQuery query) {
final List<LogWithMetadata> matchingLogs = Lists.newArrayList();
final Optional<BlockHeader> blockHeader = blockchain.getBlockHeader(blockhash);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
public class LogAdapter extends AdapterBase {
private final LogWithMetadata logWithMetadata;

LogAdapter(final LogWithMetadata logWithMetadata) {
public LogAdapter(final LogWithMetadata logWithMetadata) {
this.logWithMetadata = logWithMetadata;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public static Collection<String> specs() {
specs.add("eth_getCode_noCode");

specs.add("eth_getLogs_matchTopic");
specs.add("eth_getLogs_range");

specs.add("eth_getStorageAt");
specs.add("eth_getStorageAt_illegalRangeGreaterThan");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"request": "{ logs( filter: { fromBlock:20, toBlock: 24, topics : [], addresses : []}) { index topics data account{address} transaction{hash block {number}} } }",
"response": {
"data": {
"logs": [
{
"index": 0,
"topics": [
"0x65c9ac8011e286e89d02a269890f41d67ca2cc597b2c76c7c69321ff492be580"
],
"data": "0x000000000000000000000000000000000000000000000000000000000000002a",
"account": {
"address": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f"
},
"transaction": {
"hash": "0x97a385bf570ced7821c6495b3877ddd2afd5c452f350f0d4876e98d9161389c6",
"block": {
"number": 23
}
}
},
{
"index": 0,
"topics": [],
"data": "0x000000000000000000000000000000000000000000000000000000000000002a",
"account": {
"address": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f"
},
"transaction": {
"hash": "0x5ecd942096ab3f70c5bcc8f3a98f88c4ff0a3bd986417df9948eb1819db76d0e",
"block": {
"number": 24
}
}
}
]
}
},
"statusCode": 200
}

0 comments on commit 1682a1c

Please sign in to comment.