Skip to content
This repository has been archived by the owner on Sep 26, 2019. It is now read-only.

Commit

Permalink
Add pending object to GraphQL queries (#1419)
Browse files Browse the repository at this point in the history
* Add pending object to GraphQL queries
  • Loading branch information
Danno Ferrin authored May 14, 2019
1 parent 0482a6e commit 1b7f056
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ public static class TransactionInfo {
private final Instant addedToPoolAt;
private final long sequence; // Allows prioritization based on order transactions are added

TransactionInfo(
public TransactionInfo(
final Transaction transaction,
final boolean receivedFromLocalSource,
final Instant addedToPoolAt) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.TransactionWithMetadata;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.pojoadapter.AccountAdapter;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.pojoadapter.NormalBlockAdapter;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.pojoadapter.PendingStateAdapter;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.pojoadapter.SyncStateAdapter;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.pojoadapter.TransactionAdapter;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.response.GraphQLRpcError;
Expand Down Expand Up @@ -91,6 +92,14 @@ DataFetcher<Optional<SyncStateAdapter>> getSyncingDataFetcher() {
};
}

DataFetcher<Optional<PendingStateAdapter>> getPendingStateDataFetcher() {
return dataFetchingEnvironment -> {
final TransactionPool txPool =
((GraphQLDataFetcherContext) dataFetchingEnvironment.getContext()).getTransactionPool();
return Optional.of(new PendingStateAdapter(txPool.getPendingTransactions()));
};
}

DataFetcher<Optional<UInt256>> getGasPriceDataFetcher() {
return dataFetchingEnvironment -> {
final MiningCoordinator miningCoordinator =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ private static RuntimeWiring buildWiring(final GraphQLDataFetchers graphQLDataFe
.dataFetcher("transaction", graphQLDataFetchers.getTransactionDataFetcher())
.dataFetcher("gasPrice", graphQLDataFetchers.getGasPriceDataFetcher())
.dataFetcher("syncing", graphQLDataFetchers.getSyncingDataFetcher())
.dataFetcher("pending", graphQLDataFetchers.getPendingStateDataFetcher())
.dataFetcher(
"protocolVersion", graphQLDataFetchers.getProtocolVersionDataFetcher()))
.type(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,46 @@
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.core.Transaction;

import java.util.Optional;

public class TransactionWithMetadata {

private final Transaction transaction;
private final long blockNumber;
private final Hash blockHash;
private final int transactionIndex;
private final Optional<Long> blockNumber;
private final Optional<Hash> blockHash;
private final Optional<Integer> transactionIndex;

public TransactionWithMetadata(final Transaction transaction) {
this.transaction = transaction;
this.blockNumber = Optional.empty();
this.blockHash = Optional.empty();
this.transactionIndex = Optional.empty();
}

public TransactionWithMetadata(
TransactionWithMetadata(
final Transaction transaction,
final long blockNumber,
final Hash blockHash,
final int transactionIndex) {
this.transaction = transaction;
this.blockNumber = blockNumber;
this.blockHash = blockHash;
this.transactionIndex = transactionIndex;
this.blockNumber = Optional.of(blockNumber);
this.blockHash = Optional.of(blockHash);
this.transactionIndex = Optional.of(transactionIndex);
}

public Transaction getTransaction() {
return transaction;
}

public long getBlockNumber() {
public Optional<Long> getBlockNumber() {
return blockNumber;
}

public Hash getBlockHash() {
public Optional<Hash> getBlockHash() {
return blockHash;
}

public int getTransactionIndex() {
public Optional<Integer> getTransactionIndex() {
return transactionIndex;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* Copyright 2019 ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package tech.pegasys.pantheon.ethereum.graphqlrpc.internal.pojoadapter;

import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.Wei;
import tech.pegasys.pantheon.ethereum.core.WorldState;
import tech.pegasys.pantheon.ethereum.eth.transactions.PendingTransactions;
import tech.pegasys.pantheon.ethereum.graphqlrpc.GraphQLDataFetcherContext;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.BlockchainQuery;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.TransactionWithMetadata;
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule;
import tech.pegasys.pantheon.ethereum.transaction.CallParameter;
import tech.pegasys.pantheon.ethereum.transaction.TransactionSimulator;
import tech.pegasys.pantheon.ethereum.transaction.TransactionSimulatorResult;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.uint.UInt256;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import graphql.schema.DataFetchingEnvironment;

@SuppressWarnings("unused") // reflected by GraphQL
public class PendingStateAdapter extends AdapterBase {

private final PendingTransactions pendingTransactions;

public PendingStateAdapter(final PendingTransactions pendingTransactions) {
this.pendingTransactions = pendingTransactions;
}

public Integer getTransactionCount() {
return pendingTransactions.size();
}

public List<TransactionAdapter> getTransactions() {
return pendingTransactions.getTransactionInfo().stream()
.map(PendingTransactions.TransactionInfo::getTransaction)
.map(TransactionWithMetadata::new)
.map(TransactionAdapter::new)
.collect(Collectors.toList());
}

// until the miner can expose the current "proposed block" we have no
// speculative environment, so estimate against latest.
public Optional<AccountAdapter> getAccount(
final DataFetchingEnvironment dataFetchingEnvironment) {
final BlockchainQuery blockchainQuery =
((GraphQLDataFetcherContext) dataFetchingEnvironment.getContext()).getBlockchainQuery();
final Address addr = dataFetchingEnvironment.getArgument("address");
final Long blockNumber = dataFetchingEnvironment.getArgument("blockNumber");
final long latestBlockNumber = blockchainQuery.latestBlock().get().getHeader().getNumber();
final Optional<WorldState> optionalWorldState =
blockchainQuery.getWorldState(latestBlockNumber);
return optionalWorldState
.flatMap(worldState -> Optional.ofNullable(worldState.get(addr)))
.map(AccountAdapter::new);
}

// until the miner can expose the current "proposed block" we have no
// speculative environment, so estimate against latest.
public Optional<Long> getEstimateGas(final DataFetchingEnvironment environment) {
final Optional<CallResult> result = getCall(environment);
return result.map(CallResult::getGasUsed);
}

// until the miner can expose the current "proposed block" we have no
// speculative environment, so estimate against latest.
public Optional<CallResult> getCall(final DataFetchingEnvironment environment) {
final Map<String, Object> callData = environment.getArgument("data");
final Address from = (Address) callData.get("from");
final Address to = (Address) callData.get("to");
final Long gas = (Long) callData.get("gas");
final UInt256 gasPrice = (UInt256) callData.get("gasPrice");
final UInt256 value = (UInt256) callData.get("value");
final BytesValue data = (BytesValue) callData.get("data");

final BlockchainQuery query = getBlockchainQuery(environment);
final ProtocolSchedule<?> protocolSchedule =
((GraphQLDataFetcherContext) environment.getContext()).getProtocolSchedule();

final TransactionSimulator transactionSimulator =
new TransactionSimulator(
query.getBlockchain(), query.getWorldStateArchive(), protocolSchedule);

long gasParam = -1;
Wei gasPriceParam = null;
Wei valueParam = null;
if (gas != null) {
gasParam = gas;
}
if (gasPrice != null) {
gasPriceParam = Wei.of(gasPrice);
}
if (value != null) {
valueParam = Wei.of(value);
}
final CallParameter param =
new CallParameter(from, to, gasParam, gasPriceParam, valueParam, data);

final Optional<TransactionSimulatorResult> opt = transactionSimulator.processAtHead(param);
if (opt.isPresent()) {
final TransactionSimulatorResult result = opt.get();
long status = 0;
if (result.isSuccessful()) {
status = 1;
}
final CallResult callResult =
new CallResult(status, result.getGasEstimate(), result.getOutput());
return Optional.of(callResult);
}
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import tech.pegasys.pantheon.ethereum.core.Transaction;
import tech.pegasys.pantheon.ethereum.core.TransactionReceipt;
import tech.pegasys.pantheon.ethereum.core.WorldState;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.BlockWithMetadata;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.BlockchainQuery;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.LogWithMetadata;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.TransactionReceiptWithMetadata;
Expand Down Expand Up @@ -49,18 +48,18 @@ public Optional<Long> getNonce() {
}

public Optional<Integer> getIndex() {
return Optional.of(transactionWithMetadata.getTransactionIndex());
return transactionWithMetadata.getTransactionIndex();
}

public Optional<AccountAdapter> getFrom(final DataFetchingEnvironment environment) {
final BlockchainQuery query = getBlockchainQuery(environment);
long blockNumber = transactionWithMetadata.getBlockNumber();
final Long bn = environment.getArgument("block");
if (bn != null) {
blockNumber = bn;
final Optional<Long> txBlockNumber = transactionWithMetadata.getBlockNumber();
final Optional<Long> bn = Optional.ofNullable(environment.getArgument("block"));
if (!txBlockNumber.isPresent() && !bn.isPresent()) {
return Optional.empty();
}
return query
.getWorldState(blockNumber)
.getWorldState(bn.orElseGet(txBlockNumber::get))
.map(
mutableWorldState ->
new AccountAdapter(
Expand All @@ -69,14 +68,14 @@ public Optional<AccountAdapter> getFrom(final DataFetchingEnvironment environmen

public Optional<AccountAdapter> getTo(final DataFetchingEnvironment environment) {
final BlockchainQuery query = getBlockchainQuery(environment);
long blockNumber = transactionWithMetadata.getBlockNumber();
final Long bn = environment.getArgument("block");
if (bn != null) {
blockNumber = bn;
final Optional<Long> txBlockNumber = transactionWithMetadata.getBlockNumber();
final Optional<Long> bn = Optional.ofNullable(environment.getArgument("block"));
if (!txBlockNumber.isPresent() && !bn.isPresent()) {
return Optional.empty();
}

return query
.getWorldState(blockNumber)
.getWorldState(bn.orElseGet(txBlockNumber::get))
.flatMap(
ws ->
transactionWithMetadata
Expand All @@ -102,11 +101,10 @@ public Optional<BytesValue> getInputData() {
}

public Optional<NormalBlockAdapter> getBlock(final DataFetchingEnvironment environment) {
final Hash blockHash = transactionWithMetadata.getBlockHash();
final BlockchainQuery query = getBlockchainQuery(environment);
final Optional<BlockWithMetadata<TransactionWithMetadata, Hash>> block =
query.blockByHash(blockHash);
return block.map(NormalBlockAdapter::new);
return transactionWithMetadata
.getBlockHash()
.flatMap(blockHash -> getBlockchainQuery(environment).blockByHash(blockHash))
.map(NormalBlockAdapter::new);
}

public Optional<Long> getStatus(final DataFetchingEnvironment environment) {
Expand Down Expand Up @@ -146,11 +144,12 @@ public Optional<AccountAdapter> getCreatedContract(final DataFetchingEnvironment

if (addr.isPresent()) {
final BlockchainQuery query = getBlockchainQuery(environment);
long blockNumber = transactionWithMetadata.getBlockNumber();
final Long bn = environment.getArgument("block");
if (bn != null) {
blockNumber = bn;
final Optional<Long> txBlockNumber = transactionWithMetadata.getBlockNumber();
final Optional<Long> bn = Optional.ofNullable(environment.getArgument("block"));
if (!txBlockNumber.isPresent() && !bn.isPresent()) {
return Optional.empty();
}
final long blockNumber = bn.orElseGet(txBlockNumber::get);

final Optional<WorldState> ws = query.getWorldState(blockNumber);
if (ws.isPresent()) {
Expand All @@ -171,10 +170,10 @@ public List<LogAdapter> getLogs(final DataFetchingEnvironment environment) {
final List<LogWithMetadata> logs =
BlockchainQuery.generateLogWithMetadataForTransaction(
tranRpt.get().getReceipt(),
transactionWithMetadata.getBlockNumber(),
transactionWithMetadata.getBlockHash(),
transactionWithMetadata.getBlockNumber().get(),
transactionWithMetadata.getBlockHash().get(),
hash,
transactionWithMetadata.getTransactionIndex(),
transactionWithMetadata.getTransactionIndex().get(),
false);
for (final LogWithMetadata log : logs) {
results.add(new LogAdapter(log));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@

import java.net.URL;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
Expand Down Expand Up @@ -139,6 +141,13 @@ public void setupTest() throws Exception {
.thenReturn(ValidationResult.valid());
final PendingTransactions pendingTransactionsMock = mock(PendingTransactions.class);
when(transactionPoolMock.getPendingTransactions()).thenReturn(pendingTransactionsMock);
when(pendingTransactionsMock.getTransactionInfo())
.thenReturn(
Collections.singleton(
new PendingTransactions.TransactionInfo(
Transaction.builder().nonce(42).gasLimit(654321).build(),
true,
Instant.ofEpochSecond(Integer.MAX_VALUE))));

stateArchive = createInMemoryWorldStateArchive();
GENESIS_CONFIG.writeStateTo(stateArchive.getMutable());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ public static Collection<String> specs() {
specs.add("eth_sendRawTransaction_unsignedTransaction");

specs.add("eth_getLogs_matchTopic");

specs.add("graphql_pending");
return specs;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"request":
"{ pending { transactionCount transactions { nonce gas } account(address:\"0x6295ee1b4f6dd65047762f924ecd367c17eabf8f\") { balance} estimateGas(data:{}) call (data : {from : \"a94f5374fce5edbc8e2a8697c15331677e6ebf0b\", to: \"0x6295ee1b4f6dd65047762f924ecd367c17eabf8f\", data :\"0x12a7b914\"}){data status}} }",

"response": {
"data": {
"pending": {
"transactionCount": 0,
"transactions": [
{
"nonce": 42,
"gas": 654321
}
],
"account": {
"balance": "0x140"
},
"estimateGas": 21000,
"call": {
"data": "0x0000000000000000000000000000000000000000000000000000000000000001",
"status": 1
}
}
}
},
"statusCode": 200
}

0 comments on commit 1b7f056

Please sign in to comment.