Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PAN-3223] Add GraphQL query/logs support #94

Merged
merged 1 commit into from
Oct 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}