[Ethereal Hackathon] GraphQL EIP-1767 Implementation for Pantheon (#1311)

Implements a GraphQL interface to expose data that conforms to EIP-1767. As the EIP specifies, the implementation should allow “a complete replacement to the read-only information exposed via the present JSON-RPC interface”.

Supported CLI options:
* `--graphql-http-enabled` to enable GraphQL
* `--graphql-http-host` and `--graphql-http-port` to configure the host and port.
* `--graphql-http-cors-origins` to set the CORS-origin policies
* The `--host-whitelist` option is respected.  This option also applies to JSON-RPC and WS-RPC endpoints.

Default port is 8547.  The endpoint is `/graphrpc`, so the default URL is typically `http://127.0.0.1:8547/graphql`
Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>
pull/2/head
zyfrank 6 years ago committed by Danno Ferrin
parent 5934285bf3
commit 2026ab9d1a
  1. 47
      ethereum/graphqlrpc/build.gradle
  2. 64
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/GraphQLDataFetcherContext.java
  3. 182
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/GraphQLDataFetchers.java
  4. 78
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/GraphQLProvider.java
  5. 120
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/GraphQLRpcConfiguration.java
  6. 60
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/GraphQLRpcException.java
  7. 387
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/GraphQLRpcHttpService.java
  8. 20
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/GraphQLRpcServiceException.java
  9. 67
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/BlockWithMetadata.java
  10. 283
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/BlockchainQuery.java
  11. 109
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/LogWithMetadata.java
  12. 112
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/LogsQuery.java
  13. 52
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/TopicsParameter.java
  14. 74
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/TransactionReceiptWithMetadata.java
  15. 51
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/TransactionWithMetadata.java
  16. 53
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/pojoadapter/AccountAdapter.java
  17. 24
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/pojoadapter/AdapterBase.java
  18. 228
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/pojoadapter/BlockAdapterBase.java
  19. 40
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/pojoadapter/CallResult.java
  20. 73
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/pojoadapter/LogAdapter.java
  21. 95
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/pojoadapter/NormalBlockAdapter.java
  22. 48
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/pojoadapter/SyncStateAdapter.java
  23. 185
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/pojoadapter/TransactionAdapter.java
  24. 56
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/pojoadapter/UncleBlockAdapter.java
  25. 54
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/response/GraphQLJsonRequest.java
  26. 43
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/response/GraphQLRpcError.java
  27. 28
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/response/GraphQLRpcErrorResponse.java
  28. 25
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/response/GraphQLRpcNoResponse.java
  29. 46
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/response/GraphQLRpcResponse.java
  30. 21
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/response/GraphQLRpcResponseType.java
  31. 28
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/response/GraphQLRpcSuccessResponse.java
  32. 63
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/scalar/AddressScalar.java
  33. 66
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/scalar/BigIntScalar.java
  34. 63
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/scalar/Bytes32Scalar.java
  35. 63
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/scalar/BytesScalar.java
  36. 85
      ethereum/graphqlrpc/src/main/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/scalar/LongScalar.java
  37. 303
      ethereum/graphqlrpc/src/main/resources/schema.graphqls
  38. 53
      ethereum/graphqlrpc/src/test/java/tech/pegasys/pantheon/ethereum/graphqlrpc/AbstractDataFetcherTest.java
  39. 192
      ethereum/graphqlrpc/src/test/java/tech/pegasys/pantheon/ethereum/graphqlrpc/AbstractEthGraphQLRpcHttpServiceTest.java
  40. 49
      ethereum/graphqlrpc/src/test/java/tech/pegasys/pantheon/ethereum/graphqlrpc/BlockDataFetcherTest.java
  41. 91
      ethereum/graphqlrpc/src/test/java/tech/pegasys/pantheon/ethereum/graphqlrpc/EthGraphQLRpcHttpBySpecErrorCaseTest.java
  42. 123
      ethereum/graphqlrpc/src/test/java/tech/pegasys/pantheon/ethereum/graphqlrpc/EthGraphQLRpcHttpBySpecTest.java
  43. 29
      ethereum/graphqlrpc/src/test/java/tech/pegasys/pantheon/ethereum/graphqlrpc/GraphQLRpcConfigurationTest.java
  44. 230
      ethereum/graphqlrpc/src/test/java/tech/pegasys/pantheon/ethereum/graphqlrpc/GraphQLRpcHttpServiceCorsTest.java
  45. 156
      ethereum/graphqlrpc/src/test/java/tech/pegasys/pantheon/ethereum/graphqlrpc/GraphQLRpcHttpServiceHostWhitelistTest.java
  46. 318
      ethereum/graphqlrpc/src/test/java/tech/pegasys/pantheon/ethereum/graphqlrpc/GraphQLRpcHttpServiceTest.java
  47. 36
      ethereum/graphqlrpc/src/test/java/tech/pegasys/pantheon/ethereum/graphqlrpc/GraphQLRpcTestHelper.java
  48. 91
      ethereum/graphqlrpc/src/test/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/scalar/AddressScalarTest.java
  49. 87
      ethereum/graphqlrpc/src/test/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/scalar/BigIntScalarTest.java
  50. 87
      ethereum/graphqlrpc/src/test/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/scalar/Bytes32ScalarTest.java
  51. 87
      ethereum/graphqlrpc/src/test/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/scalar/BytesScalarTest.java
  52. 93
      ethereum/graphqlrpc/src/test/java/tech/pegasys/pantheon/ethereum/graphqlrpc/internal/scalar/LongScalarTest.java
  53. 13
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_blockNumber.json
  54. 16
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_call_Block8.json
  55. 16
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_call_BlockLatest.json
  56. 12
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_estimateGas_contractDeploy.json
  57. 12
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_estimateGas_noParams.json
  58. 11
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_estimateGas_transfer.json
  59. 11
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_gasPrice.json
  60. 12
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_getBalance_0x19.json
  61. 12
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_getBalance_latest.json
  62. 20
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_getBalance_toobig_bn.json
  63. 5
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_getBalance_without_addr.json
  64. 34
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_getBlockByHash.json
  65. 44
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_getBlockByNumber.json
  66. 15
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_getBlockTransactionCountByHash.json
  67. 33
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_getBlockTransactionCountByNumber.json
  68. 7
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_getBlockWrongParams.json
  69. 40
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_getBlocksByRange.json
  70. 15
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_getBlocksByWrongRange.json
  71. 13
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_getCode.json
  72. 14
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_getCode_noCode.json
  73. 22
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_getLogs_matchTopic.json
  74. 13
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_getStorageAt.json
  75. 13
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_getStorageAt_illegalRangeGreaterThan.json
  76. 20
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_getTransactionByBlockHashAndIndex.json
  77. 20
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_getTransactionByBlockNumberAndIndex.json
  78. 14
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_getTransactionByBlockNumberAndInvalidIndex.json
  79. 32
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_getTransactionByHash.json
  80. 13
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_getTransactionByHashNull.json
  81. 13
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_getTransactionCount.json
  82. 27
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_getTransactionReceipt.json
  83. 9
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_sendRawTransaction_contractCreation.json
  84. 10
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_sendRawTransaction_messageCall.json
  85. 9
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_sendRawTransaction_transferEther.json
  86. 19
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_sendRawTransaction_unsignedTransaction.json
  87. 17
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/eth_syncing.json
  88. BIN
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/graphQLRpcTestBlockchain.blocks
  89. 20
      ethereum/graphqlrpc/src/test/resources/tech/pegasys/pantheon/ethereum/graphqlrpc/graphQLRpcTestGenesis.json
  90. 2
      ethereum/jsonrpc/build.gradle
  91. 0
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java
  92. 1
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/EthJsonRpcHttpBySpecTest.java
  93. 12
      gradle/check-licenses.gradle
  94. 2
      gradle/versions.gradle
  95. 2
      pantheon/build.gradle
  96. 13
      pantheon/src/main/java/tech/pegasys/pantheon/Runner.java
  97. 43
      pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java
  98. 103
      pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java
  99. 22
      pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java
  100. 3
      pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,47 @@
/*
* Copyright 2018 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.
*/
apply plugin: 'java-library'
jar {
baseName 'pantheon-graphql-rpc'
manifest {
attributes(
'Specification-Title': baseName,
'Specification-Version': project.version,
'Implementation-Title': baseName,
'Implementation-Version': calculateVersion()
)
}
}
dependencies {
implementation project(':ethereum:blockcreation')
implementation project(':ethereum:core')
implementation project(':ethereum:eth')
implementation project(':ethereum:p2p')
implementation project(':ethereum:rlp')
implementation project(':util')
implementation 'com.graphql-java:graphql-java'
implementation 'com.google.guava:guava'
implementation 'io.vertx:vertx-core'
implementation 'io.vertx:vertx-web'
testImplementation project(path: ':ethereum:core', configuration: 'testSupportArtifacts')
testImplementation 'com.squareup.okhttp3:okhttp'
testImplementation 'junit:junit'
testImplementation 'org.assertj:assertj-core'
testImplementation 'org.mockito:mockito-core'
}

@ -0,0 +1,64 @@
/*
* 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;
import tech.pegasys.pantheon.ethereum.blockcreation.MiningCoordinator;
import tech.pegasys.pantheon.ethereum.chain.Blockchain;
import tech.pegasys.pantheon.ethereum.core.Synchronizer;
import tech.pegasys.pantheon.ethereum.eth.transactions.TransactionPool;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.BlockchainQuery;
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule;
import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive;
public class GraphQLDataFetcherContext {
private final BlockchainQuery blockchain;
private final MiningCoordinator miningCoordinator;
private final Synchronizer synchronizer;
private final ProtocolSchedule<?> protocolSchedule;
private final TransactionPool transactionPool;
public GraphQLDataFetcherContext(
final Blockchain blockchain,
final WorldStateArchive worldStateArchive,
final ProtocolSchedule<?> protocolSchedule,
final TransactionPool transactionPool,
final MiningCoordinator miningCoordinator,
final Synchronizer synchronizer) {
this.blockchain = new BlockchainQuery(blockchain, worldStateArchive);
this.protocolSchedule = protocolSchedule;
this.miningCoordinator = miningCoordinator;
this.synchronizer = synchronizer;
this.transactionPool = transactionPool;
}
public TransactionPool getTransactionPool() {
return transactionPool;
}
public BlockchainQuery getBlockchainQuery() {
return blockchain;
}
public MiningCoordinator getMiningCoordinator() {
return miningCoordinator;
}
public Synchronizer getSynchronizer() {
return synchronizer;
}
public ProtocolSchedule<?> getProtocolSchedule() {
return protocolSchedule;
}
}

@ -0,0 +1,182 @@
/*
* 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;
import tech.pegasys.pantheon.ethereum.blockcreation.MiningCoordinator;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.core.SyncStatus;
import tech.pegasys.pantheon.ethereum.core.Synchronizer;
import tech.pegasys.pantheon.ethereum.core.Transaction;
import tech.pegasys.pantheon.ethereum.core.WorldState;
import tech.pegasys.pantheon.ethereum.eth.EthProtocol;
import tech.pegasys.pantheon.ethereum.eth.transactions.TransactionPool;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.BlockWithMetadata;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.BlockchainQuery;
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.SyncStateAdapter;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.pojoadapter.TransactionAdapter;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.response.GraphQLRpcError;
import tech.pegasys.pantheon.ethereum.mainnet.TransactionValidator.TransactionInvalidReason;
import tech.pegasys.pantheon.ethereum.mainnet.ValidationResult;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.ethereum.rlp.RLP;
import tech.pegasys.pantheon.ethereum.rlp.RLPException;
import tech.pegasys.pantheon.util.bytes.Bytes32;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.uint.UInt256;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.Set;
import graphql.schema.DataFetcher;
public class GraphQLDataFetchers {
public GraphQLDataFetchers(final Set<Capability> supportedCapabilities) {
final OptionalInt version =
supportedCapabilities.stream()
.filter(cap -> EthProtocol.NAME.equals(cap.getName()))
.mapToInt(Capability::getVersion)
.max();
highestEthVersion = version.isPresent() ? version.getAsInt() : null;
}
private final Integer highestEthVersion;
DataFetcher<Optional<Integer>> getProtocolVersionDataFetcher() {
return dataFetchingEnvironment -> Optional.of(highestEthVersion);
}
DataFetcher<Optional<Bytes32>> getSendRawTransactionDataFetcher() {
return dataFetchingEnvironment -> {
try {
final TransactionPool transactionPool =
((GraphQLDataFetcherContext) dataFetchingEnvironment.getContext()).getTransactionPool();
final BytesValue rawTran = dataFetchingEnvironment.getArgument("data");
final Transaction transaction = Transaction.readFrom(RLP.input(rawTran));
final ValidationResult<TransactionInvalidReason> validationResult =
transactionPool.addLocalTransaction(transaction);
if (validationResult.isValid()) {
return Optional.of(transaction.hash());
}
} catch (final IllegalArgumentException | RLPException e) {
throw new GraphQLRpcException(GraphQLRpcError.INVALID_PARAMS);
}
throw new GraphQLRpcException(GraphQLRpcError.INVALID_PARAMS);
};
}
DataFetcher<Optional<SyncStateAdapter>> getSyncingDataFetcher() {
return dataFetchingEnvironment -> {
final Synchronizer synchronizer =
((GraphQLDataFetcherContext) dataFetchingEnvironment.getContext()).getSynchronizer();
final Optional<SyncStatus> syncStatus = synchronizer.getSyncStatus();
return syncStatus.map(SyncStateAdapter::new);
};
}
DataFetcher<Optional<UInt256>> getGasPriceDataFetcher() {
return dataFetchingEnvironment -> {
final MiningCoordinator miningCoordinator =
((GraphQLDataFetcherContext) dataFetchingEnvironment.getContext()).getMiningCoordinator();
return Optional.of(miningCoordinator.getMinTransactionGasPrice().asUInt256());
};
}
DataFetcher<List<NormalBlockAdapter>> getRangeBlockDataFetcher() {
return dataFetchingEnvironment -> {
final long from = dataFetchingEnvironment.getArgument("from");
final long to = dataFetchingEnvironment.getArgument("to");
if (from > to) {
throw new GraphQLRpcException(GraphQLRpcError.INVALID_PARAMS);
}
final BlockchainQuery blockchain =
((GraphQLDataFetcherContext) dataFetchingEnvironment.getContext()).getBlockchainQuery();
final List<NormalBlockAdapter> results = new ArrayList<>();
for (long i = from; i <= to; i++) {
final Optional<BlockWithMetadata<TransactionWithMetadata, Hash>> block =
blockchain.blockByNumber(i);
block.ifPresent(e -> results.add(new NormalBlockAdapter(e)));
}
return results;
};
}
public DataFetcher<Optional<NormalBlockAdapter>> getBlockDataFetcher() {
return dataFetchingEnvironment -> {
final BlockchainQuery blockchain =
((GraphQLDataFetcherContext) dataFetchingEnvironment.getContext()).getBlockchainQuery();
final Long number = dataFetchingEnvironment.getArgument("number");
final Bytes32 hash = dataFetchingEnvironment.getArgument("hash");
if ((number != null) && (hash != null)) {
throw new GraphQLRpcException(GraphQLRpcError.INVALID_PARAMS);
}
final Optional<BlockWithMetadata<TransactionWithMetadata, Hash>> block;
if (number != null) {
block = blockchain.blockByNumber(number);
} else if (hash != null) {
block = blockchain.blockByHash(Hash.wrap(hash));
} else {
block = blockchain.latestBlock();
}
return block.map(NormalBlockAdapter::new);
};
}
DataFetcher<Optional<AccountAdapter>> getAccountDataFetcher() {
return dataFetchingEnvironment -> {
final BlockchainQuery blockchainQuery =
((GraphQLDataFetcherContext) dataFetchingEnvironment.getContext()).getBlockchainQuery();
final Address addr = dataFetchingEnvironment.getArgument("address");
final Long bn = dataFetchingEnvironment.getArgument("blockNumber");
if (bn != null) {
final Optional<WorldState> ws = blockchainQuery.getWorldState(bn);
if (ws.isPresent()) {
return Optional.of(new AccountAdapter(ws.get().get(addr)));
} else if (bn > blockchainQuery.getBlockchain().getChainHeadBlockNumber()) {
// block is past chainhead
throw new GraphQLRpcException(GraphQLRpcError.INVALID_PARAMS);
} else {
// we don't have that block
throw new GraphQLRpcException(GraphQLRpcError.CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE);
}
}
// return account on latest block
final long latestBn = blockchainQuery.latestBlock().get().getHeader().getNumber();
final Optional<WorldState> ows = blockchainQuery.getWorldState(latestBn);
return ows.flatMap(ws -> Optional.ofNullable(ws.get(addr))).map(AccountAdapter::new);
};
}
DataFetcher<Optional<TransactionAdapter>> getTransactionDataFetcher() {
return dataFetchingEnvironment -> {
final BlockchainQuery blockchain =
((GraphQLDataFetcherContext) dataFetchingEnvironment.getContext()).getBlockchainQuery();
final Bytes32 hash = dataFetchingEnvironment.getArgument("hash");
final Optional<TransactionWithMetadata> tran = blockchain.transactionByHash(Hash.wrap(hash));
return tran.map(TransactionAdapter::new);
};
}
}

@ -0,0 +1,78 @@
/*
* 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;
import static graphql.schema.idl.TypeRuntimeWiring.newTypeWiring;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.scalar.AddressScalar;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.scalar.BigIntScalar;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.scalar.Bytes32Scalar;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.scalar.BytesScalar;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.scalar.LongScalar;
import java.io.IOException;
import java.net.URL;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import graphql.GraphQL;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
public class GraphQLProvider {
private GraphQLProvider() {}
public static GraphQL buildGraphQL(final GraphQLDataFetchers graphQLDataFetchers)
throws IOException {
final URL url = Resources.getResource("schema.graphqls");
final String sdl = Resources.toString(url, Charsets.UTF_8);
final GraphQLSchema graphQLSchema = buildSchema(sdl, graphQLDataFetchers);
return GraphQL.newGraphQL(graphQLSchema).build();
}
private static GraphQLSchema buildSchema(
final String sdl, final GraphQLDataFetchers graphQLDataFetchers) {
final TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl);
final RuntimeWiring runtimeWiring = buildWiring(graphQLDataFetchers);
final SchemaGenerator schemaGenerator = new SchemaGenerator();
return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
}
private static RuntimeWiring buildWiring(final GraphQLDataFetchers graphQLDataFetchers) {
return RuntimeWiring.newRuntimeWiring()
.scalar(new AddressScalar())
.scalar(new Bytes32Scalar())
.scalar(new BytesScalar())
.scalar(new LongScalar())
.scalar(new BigIntScalar())
.type(
newTypeWiring("Query")
.dataFetcher("account", graphQLDataFetchers.getAccountDataFetcher())
.dataFetcher("block", graphQLDataFetchers.getBlockDataFetcher())
.dataFetcher("blocks", graphQLDataFetchers.getRangeBlockDataFetcher())
.dataFetcher("transaction", graphQLDataFetchers.getTransactionDataFetcher())
.dataFetcher("gasPrice", graphQLDataFetchers.getGasPriceDataFetcher())
.dataFetcher("syncing", graphQLDataFetchers.getSyncingDataFetcher())
.dataFetcher(
"protocolVersion", graphQLDataFetchers.getProtocolVersionDataFetcher()))
.type(
newTypeWiring("Mutation")
.dataFetcher(
"sendRawTransaction", graphQLDataFetchers.getSendRawTransactionDataFetcher()))
.build();
}
}

@ -0,0 +1,120 @@
/*
* Copyright 2018 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;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
public class GraphQLRpcConfiguration {
private static final String DEFAULT_GRAPHQL_RPC_HOST = "127.0.0.1";
public static final int DEFAULT_GRAPHQL_RPC_PORT = 8547;
private boolean enabled;
private int port;
private String host;
private Collection<String> corsAllowedDomains = Collections.emptyList();
private Collection<String> hostsWhitelist = Arrays.asList("localhost", "127.0.0.1");
public static GraphQLRpcConfiguration createDefault() {
final GraphQLRpcConfiguration config = new GraphQLRpcConfiguration();
config.setEnabled(false);
config.setPort(DEFAULT_GRAPHQL_RPC_PORT);
config.setHost(DEFAULT_GRAPHQL_RPC_HOST);
return config;
}
private GraphQLRpcConfiguration() {}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
public int getPort() {
return port;
}
public void setPort(final int port) {
this.port = port;
}
public String getHost() {
return host;
}
public void setHost(final String host) {
this.host = host;
}
Collection<String> getCorsAllowedDomains() {
return corsAllowedDomains;
}
public void setCorsAllowedDomains(final Collection<String> corsAllowedDomains) {
checkNotNull(corsAllowedDomains);
this.corsAllowedDomains = corsAllowedDomains;
}
Collection<String> getHostsWhitelist() {
return Collections.unmodifiableCollection(this.hostsWhitelist);
}
public void setHostsWhitelist(final Collection<String> hostsWhitelist) {
checkNotNull(hostsWhitelist);
this.hostsWhitelist = hostsWhitelist;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("enabled", enabled)
.add("port", port)
.add("host", host)
.add("corsAllowedDomains", corsAllowedDomains)
.add("hostsWhitelist", hostsWhitelist)
.toString();
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final GraphQLRpcConfiguration that = (GraphQLRpcConfiguration) o;
return enabled == that.enabled
&& port == that.port
&& Objects.equal(host, that.host)
&& Objects.equal(
Lists.newArrayList(corsAllowedDomains), Lists.newArrayList(that.corsAllowedDomains))
&& Objects.equal(
Lists.newArrayList(hostsWhitelist), Lists.newArrayList(that.hostsWhitelist));
}
@Override
public int hashCode() {
return Objects.hashCode(enabled, port, host, corsAllowedDomains, hostsWhitelist);
}
}

@ -0,0 +1,60 @@
/*
* 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;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.response.GraphQLRpcError;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import graphql.ErrorType;
import graphql.GraphQLError;
import graphql.language.SourceLocation;
class GraphQLRpcException extends RuntimeException implements GraphQLError {
private final GraphQLRpcError error;
GraphQLRpcException(final GraphQLRpcError error) {
super(error.getMessage());
this.error = error;
}
@Override
public Map<String, Object> getExtensions() {
final Map<String, Object> customAttributes = new LinkedHashMap<>();
customAttributes.put("errorCode", this.error.getCode());
customAttributes.put("errorMessage", this.getMessage());
return customAttributes;
}
@Override
public List<SourceLocation> getLocations() {
return null;
}
@Override
public ErrorType getErrorType() {
switch (error) {
case INVALID_PARAMS:
return ErrorType.ValidationError;
case INTERNAL_ERROR:
default:
return ErrorType.DataFetchingException;
}
}
}

@ -0,0 +1,387 @@
/*
* Copyright 2018 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;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Streams.stream;
import static tech.pegasys.pantheon.util.NetworkUtility.urlForSocketAddress;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.response.GraphQLJsonRequest;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.response.GraphQLRpcErrorResponse;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.response.GraphQLRpcResponse;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.response.GraphQLRpcResponseType;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.response.GraphQLRpcSuccessResponse;
import tech.pegasys.pantheon.util.NetworkUtility;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.StringJoiner;
import java.util.concurrent.CompletableFuture;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.GraphQLError;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.DecodeException;
import io.vertx.core.json.Json;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.CorsHandler;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class GraphQLRpcHttpService {
private static final Logger LOG = LogManager.getLogger();
private static final InetSocketAddress EMPTY_SOCKET_ADDRESS = new InetSocketAddress("0.0.0.0", 0);
private static final String APPLICATION_JSON = "application/json";
private static final String EMPTY_RESPONSE = "";
private static final TypeReference<Map<String, Object>> MAP_TYPE =
new TypeReference<Map<String, Object>>() {};
private final Vertx vertx;
private final GraphQLRpcConfiguration config;
private final Path dataDir;
private HttpServer httpServer;
private final GraphQL graphQL;
private final GraphQLDataFetcherContext dataFetcherContext;
/**
* Construct a GraphQLRpcHttpService handler
*
* @param vertx The vertx process that will be running this service
* @param dataDir The data directory where requests can be buffered
* @param config Configuration for the rpc methods being loaded
* @param graphQL GraphQL engine
* @param dataFetcherContext DataFetcherContext required by GraphQL to finish it's job
*/
public GraphQLRpcHttpService(
final Vertx vertx,
final Path dataDir,
final GraphQLRpcConfiguration config,
final GraphQL graphQL,
final GraphQLDataFetcherContext dataFetcherContext) {
this.dataDir = dataDir;
validateConfig(config);
this.config = config;
this.vertx = vertx;
this.graphQL = graphQL;
this.dataFetcherContext = dataFetcherContext;
}
private void validateConfig(final GraphQLRpcConfiguration config) {
checkArgument(
config.getPort() == 0 || NetworkUtility.isValidPort(config.getPort()),
"Invalid port configuration.");
checkArgument(config.getHost() != null, "Required host is not configured.");
}
public CompletableFuture<?> start() {
LOG.info("Starting GraphQLRPC service on {}:{}", config.getHost(), config.getPort());
// Create the HTTP server and a router object.
httpServer =
vertx.createHttpServer(
new HttpServerOptions().setHost(config.getHost()).setPort(config.getPort()));
// Handle graphql rpc requests
final Router router = Router.router(vertx);
// Verify Host header to avoid rebind attack.
router.route().handler(checkWhitelistHostHeader());
router
.route()
.handler(
CorsHandler.create(buildCorsRegexFromConfig())
.allowedHeader("*")
.allowedHeader("content-type"));
router
.route()
.handler(
BodyHandler.create()
.setUploadsDirectory(dataDir.resolve("uploads").toString())
.setDeleteUploadedFilesOnEnd(true));
router.route("/").method(HttpMethod.GET).handler(this::handleEmptyRequest);
router
.route("/graphql")
.method(HttpMethod.GET)
.method(HttpMethod.POST)
.produces(APPLICATION_JSON)
.handler(this::handleGraphQLRPCRequest);
final CompletableFuture<?> resultFuture = new CompletableFuture<>();
httpServer
.requestHandler(router)
.listen(
res -> {
if (!res.failed()) {
resultFuture.complete(null);
LOG.info(
"GraphQL RPC service started and listening on {}:{}",
config.getHost(),
httpServer.actualPort());
return;
}
httpServer = null;
final Throwable cause = res.cause();
if (cause instanceof SocketException) {
resultFuture.completeExceptionally(
new GraphQLRpcServiceException(
String.format(
"Failed to bind Ethereum GraphQL RPC listener to %s:%s: %s",
config.getHost(), config.getPort(), cause.getMessage())));
return;
}
resultFuture.completeExceptionally(cause);
});
return resultFuture;
}
private Handler<RoutingContext> checkWhitelistHostHeader() {
return event -> {
final Optional<String> hostHeader = getAndValidateHostHeader(event);
if (config.getHostsWhitelist().contains("*")
|| (hostHeader.isPresent() && hostIsInWhitelist(hostHeader.get()))) {
event.next();
} else {
event
.response()
.setStatusCode(403)
.putHeader("Content-Type", "application/json; charset=utf-8")
.end("{\"message\":\"Host not authorized.\"}");
}
};
}
private Optional<String> getAndValidateHostHeader(final RoutingContext event) {
final Iterable<String> splitHostHeader = Splitter.on(':').split(event.request().host());
final long hostPieces = stream(splitHostHeader).count();
if (hostPieces > 1) {
// If the host contains a colon, verify the host is correctly formed - host [ ":" port ]
if (hostPieces > 2 || !Iterables.get(splitHostHeader, 1).matches("\\d{1,5}+")) {
return Optional.empty();
}
}
return Optional.ofNullable(Iterables.get(splitHostHeader, 0));
}
private boolean hostIsInWhitelist(final String hostHeader) {
return config.getHostsWhitelist().stream()
.anyMatch(whitelistEntry -> whitelistEntry.toLowerCase().equals(hostHeader.toLowerCase()));
}
public CompletableFuture<?> stop() {
if (httpServer == null) {
return CompletableFuture.completedFuture(null);
}
final CompletableFuture<?> resultFuture = new CompletableFuture<>();
httpServer.close(
res -> {
if (res.failed()) {
resultFuture.completeExceptionally(res.cause());
} else {
httpServer = null;
resultFuture.complete(null);
}
});
return resultFuture;
}
public InetSocketAddress socketAddress() {
if (httpServer == null) {
return EMPTY_SOCKET_ADDRESS;
}
return new InetSocketAddress(config.getHost(), httpServer.actualPort());
}
@VisibleForTesting
public String url() {
if (httpServer == null) {
return "";
}
return urlForSocketAddress("http", socketAddress());
}
// Facilitate remote health-checks in AWS, inter alia.
private void handleEmptyRequest(final RoutingContext routingContext) {
routingContext.response().setStatusCode(201).end();
}
private void handleGraphQLRPCRequest(final RoutingContext routingContext) {
try {
final String query;
final String operationName;
final Map<String, Object> variables;
final HttpServerRequest request = routingContext.request();
switch (request.method()) {
case GET:
query = request.getParam("query");
operationName = request.getParam("operationName");
final String variableString = request.getParam("variables");
if (variableString != null) {
variables = Json.decodeValue(variableString, MAP_TYPE);
} else {
variables = null;
}
break;
case POST:
if (request.getHeader(HttpHeaders.CONTENT_TYPE).equalsIgnoreCase(APPLICATION_JSON)) {
final String requestBody = routingContext.getBodyAsString().trim();
final GraphQLJsonRequest jsonRequest =
Json.decodeValue(requestBody, GraphQLJsonRequest.class);
query = jsonRequest.getQuery();
operationName = jsonRequest.getOperationName();
variables = jsonRequest.getVariables();
} else {
// treat all else as application/graphql
query = routingContext.getBodyAsString().trim();
operationName = null;
variables = null;
}
break;
default:
routingContext
.response()
.setStatusCode(HttpResponseStatus.METHOD_NOT_ALLOWED.code())
.end();
return;
}
final HttpServerResponse response = routingContext.response();
vertx.executeBlocking(
future -> {
try {
final GraphQLRpcResponse graphQLRpcResponse =
process(query, operationName, variables);
future.complete(graphQLRpcResponse);
} catch (final Exception e) {
future.fail(e);
}
},
false,
(res) -> {
response.putHeader("Content-Type", APPLICATION_JSON);
if (res.failed()) {
response.setStatusCode(HttpResponseStatus.INTERNAL_SERVER_ERROR.code());
response.end(
serialise(
new GraphQLRpcErrorResponse(
Collections.singletonMap(
"errors",
Collections.singletonList(
Collections.singletonMap(
"message", res.cause().getMessage()))))));
} else {
final GraphQLRpcResponse graphQLRpcResponse = (GraphQLRpcResponse) res.result();
response.setStatusCode(status(graphQLRpcResponse).code());
response.end(serialise(graphQLRpcResponse));
}
});
} catch (final DecodeException ex) {
handleGraphQLRpcError(routingContext, ex);
}
}
private HttpResponseStatus status(final GraphQLRpcResponse response) {
switch (response.getType()) {
case UNAUTHORIZED:
return HttpResponseStatus.UNAUTHORIZED;
case ERROR:
return HttpResponseStatus.BAD_REQUEST;
case SUCCESS:
case NONE:
default:
return HttpResponseStatus.OK;
}
}
private String serialise(final GraphQLRpcResponse response) {
if (response.getType() == GraphQLRpcResponseType.NONE) {
return EMPTY_RESPONSE;
}
return Json.encodePrettily(response.getResult());
}
private GraphQLRpcResponse process(
final String requestJson, final String operationName, final Map<String, Object> variables) {
final ExecutionInput executionInput =
ExecutionInput.newExecutionInput()
.query(requestJson)
.operationName(operationName)
.variables(variables)
.context(dataFetcherContext)
.build();
final ExecutionResult result = graphQL.execute(executionInput);
final Map<String, Object> toSpecificationResult = result.toSpecification();
final List<GraphQLError> errors = result.getErrors();
if (errors.size() == 0) {
return new GraphQLRpcSuccessResponse(toSpecificationResult);
} else {
return new GraphQLRpcErrorResponse(toSpecificationResult);
}
}
private void handleGraphQLRpcError(final RoutingContext routingContext, final Exception ex) {
LOG.debug("Error handling GraphQL request", ex);
routingContext
.response()
.setStatusCode(HttpResponseStatus.BAD_REQUEST.code())
.end(Json.encode(new GraphQLRpcErrorResponse(ex.getMessage())));
}
private String buildCorsRegexFromConfig() {
if (config.getCorsAllowedDomains().isEmpty()) {
return "";
}
if (config.getCorsAllowedDomains().contains("*")) {
return "*";
} else {
final StringJoiner stringJoiner = new StringJoiner("|");
config.getCorsAllowedDomains().stream().filter(s -> !s.isEmpty()).forEach(stringJoiner::add);
return stringJoiner.toString();
}
}
}

@ -0,0 +1,20 @@
/*
* Copyright 2018 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;
class GraphQLRpcServiceException extends RuntimeException {
public GraphQLRpcServiceException(final String message) {
super(message);
}
}

@ -0,0 +1,67 @@
/*
* Copyright 2018 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;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.util.uint.UInt256;
import java.util.List;
public class BlockWithMetadata<T, O> {
private final BlockHeader header;
private final List<T> transactions;
private final List<O> ommers;
private final UInt256 totalDifficulty;
private final int size;
/**
* @param header The block header
* @param transactions Block transactions in generic format
* @param ommers Block ommers in generic format
* @param totalDifficulty The cumulative difficulty up to and including this block
* @param size The size of the rlp-encoded block (header + body).
*/
public BlockWithMetadata(
final BlockHeader header,
final List<T> transactions,
final List<O> ommers,
final UInt256 totalDifficulty,
final int size) {
this.header = header;
this.transactions = transactions;
this.ommers = ommers;
this.totalDifficulty = totalDifficulty;
this.size = size;
}
public BlockHeader getHeader() {
return header;
}
public List<O> getOmmers() {
return ommers;
}
public List<T> getTransactions() {
return transactions;
}
public UInt256 getTotalDifficulty() {
return totalDifficulty;
}
public int getSize() {
return size;
}
}

@ -0,0 +1,283 @@
/*
* Copyright 2018 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;
import tech.pegasys.pantheon.ethereum.chain.Blockchain;
import tech.pegasys.pantheon.ethereum.chain.TransactionLocation;
import tech.pegasys.pantheon.ethereum.core.Block;
import tech.pegasys.pantheon.ethereum.core.BlockBody;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.core.Hash;
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.worldstate.WorldStateArchive;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import com.google.common.collect.Lists;
public class BlockchainQuery {
private final WorldStateArchive worldStateArchive;
private final Blockchain blockchain;
public BlockchainQuery(final Blockchain blockchain, final WorldStateArchive worldStateArchive) {
this.blockchain = blockchain;
this.worldStateArchive = worldStateArchive;
}
public Blockchain getBlockchain() {
return blockchain;
}
public WorldStateArchive getWorldStateArchive() {
return worldStateArchive;
}
/**
* Returns the ommer at the given index for the referenced block.
*
* @param blockHeaderHash The hash of the block to be queried.
* @param index The index of the ommer in the blocks ommers list.
* @return The ommer at the given index belonging to the referenced block.
*/
public Optional<BlockHeader> getOmmer(final Hash blockHeaderHash, final int index) {
return blockchain.getBlockBody(blockHeaderHash).map(blockBody -> getOmmer(blockBody, index));
}
private BlockHeader getOmmer(final BlockBody blockBody, final int index) {
final List<BlockHeader> ommers = blockBody.getOmmers();
if (ommers.size() > index) {
return ommers.get(index);
} else {
return null;
}
}
/**
* Given a block hash, returns the associated block augmented with metadata.
*
* @param blockHeaderHash The hash of the target block's header.
* @return The referenced block.
*/
public Optional<BlockWithMetadata<TransactionWithMetadata, Hash>> blockByHash(
final Hash blockHeaderHash) {
return blockchain
.getBlockHeader(blockHeaderHash)
.flatMap(
header ->
blockchain
.getBlockBody(blockHeaderHash)
.flatMap(
body ->
blockchain
.getTotalDifficultyByHash(blockHeaderHash)
.map(
(td) -> {
final List<Transaction> txs = body.getTransactions();
final List<TransactionWithMetadata> formattedTxs =
formatTransactions(
txs, header.getNumber(), blockHeaderHash);
final List<Hash> ommers =
body.getOmmers().stream()
.map(BlockHeader::getHash)
.collect(Collectors.toList());
final int size = new Block(header, body).calculateSize();
return new BlockWithMetadata<>(
header, formattedTxs, ommers, td, size);
})));
}
/**
* Given a block number, returns the associated block augmented with metadata.
*
* @param number The height of the target block.
* @return The referenced block.
*/
public Optional<BlockWithMetadata<TransactionWithMetadata, Hash>> blockByNumber(
final long number) {
return blockchain.getBlockHashByNumber(number).flatMap(this::blockByHash);
}
/**
* Returns the latest block augmented with metadata.
*
* @return The latest block.
*/
public Optional<BlockWithMetadata<TransactionWithMetadata, Hash>> latestBlock() {
return this.blockByHash(blockchain.getChainHeadHash());
}
/**
* Given a transaction hash, returns the associated transaction.
*
* @param transactionHash The hash of the target transaction.
* @return The transaction associated with the given hash.
*/
public Optional<TransactionWithMetadata> transactionByHash(final Hash transactionHash) {
final Optional<TransactionLocation> maybeLocation =
blockchain.getTransactionLocation(transactionHash);
if (!maybeLocation.isPresent()) {
return Optional.empty();
}
final TransactionLocation loc = maybeLocation.get();
final Hash blockHash = loc.getBlockHash();
final BlockHeader header = blockchain.getBlockHeader(blockHash).get();
final Transaction transaction = blockchain.getTransactionByHash(transactionHash).get();
return Optional.of(
new TransactionWithMetadata(
transaction, header.getNumber(), blockHash, loc.getTransactionIndex()));
}
/**
* Returns the transaction receipt associated with the given transaction hash.
*
* @param transactionHash The hash of the transaction that corresponds to the receipt to retrieve.
* @return The transaction receipt associated with the referenced transaction.
*/
public Optional<TransactionReceiptWithMetadata> transactionReceiptByTransactionHash(
final Hash transactionHash) {
final Optional<TransactionLocation> maybeLocation =
blockchain.getTransactionLocation(transactionHash);
if (!maybeLocation.isPresent()) {
return Optional.empty();
}
final TransactionLocation location = maybeLocation.get();
final BlockBody blockBody = blockchain.getBlockBody(location.getBlockHash()).get();
final Transaction transaction = blockBody.getTransactions().get(location.getTransactionIndex());
final Hash blockhash = location.getBlockHash();
final BlockHeader header = blockchain.getBlockHeader(blockhash).get();
final List<TransactionReceipt> transactionReceipts = blockchain.getTxReceipts(blockhash).get();
final TransactionReceipt transactionReceipt =
transactionReceipts.get(location.getTransactionIndex());
long gasUsed = transactionReceipt.getCumulativeGasUsed();
if (location.getTransactionIndex() > 0) {
gasUsed =
gasUsed
- transactionReceipts.get(location.getTransactionIndex() - 1).getCumulativeGasUsed();
}
return Optional.of(
new TransactionReceiptWithMetadata(
transactionReceipt,
transaction,
transactionHash,
location.getTransactionIndex(),
gasUsed,
blockhash,
header.getNumber()));
}
/**
* Returns the world state for the corresponding block number
*
* @param blockNumber the block number
* @return the world state at the block number
*/
public Optional<WorldState> getWorldState(final long blockNumber) {
final Optional<BlockHeader> header = blockchain.getBlockHeader(blockNumber);
return header
.map(BlockHeader::getStateRoot)
.flatMap(worldStateArchive::getMutable)
.map(mws -> mws); // to satisfy typing
}
private List<TransactionWithMetadata> formatTransactions(
final List<Transaction> txs, final long blockNumber, final Hash blockHash) {
final int count = txs.size();
final List<TransactionWithMetadata> result = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
result.add(new TransactionWithMetadata(txs.get(i), blockNumber, blockHash, i));
}
return result;
}
public List<LogWithMetadata> matchingLogs(final Hash blockhash, final LogsQuery query) {
final List<LogWithMetadata> matchingLogs = Lists.newArrayList();
final Optional<BlockHeader> blockHeader = blockchain.getBlockHeader(blockhash);
if (!blockHeader.isPresent()) {
return matchingLogs;
}
final List<TransactionReceipt> receipts = blockchain.getTxReceipts(blockhash).get();
final List<Transaction> transaction =
blockchain.getBlockBody(blockhash).get().getTransactions();
final long number = blockHeader.get().getNumber();
final boolean logHasBeenRemoved = !blockchain.blockIsOnCanonicalChain(blockhash);
return generateLogWithMetadata(
receipts, number, query, blockhash, matchingLogs, transaction, logHasBeenRemoved);
}
private List<LogWithMetadata> generateLogWithMetadata(
final List<TransactionReceipt> receipts,
final long number,
final LogsQuery query,
final Hash blockhash,
final List<LogWithMetadata> matchingLogs,
final List<Transaction> transaction,
final boolean removed) {
for (int transactionIndex = 0; transactionIndex < receipts.size(); ++transactionIndex) {
final TransactionReceipt receipt = receipts.get(transactionIndex);
for (int logIndex = 0; logIndex < receipt.getLogs().size(); ++logIndex) {
if (query.matches(receipt.getLogs().get(logIndex))) {
final LogWithMetadata logWithMetaData =
new LogWithMetadata(
logIndex,
number,
blockhash,
transaction.get(transactionIndex).hash(),
transactionIndex,
receipts.get(transactionIndex).getLogs().get(logIndex).getLogger(),
receipts.get(transactionIndex).getLogs().get(logIndex).getData(),
receipts.get(transactionIndex).getLogs().get(logIndex).getTopics(),
removed);
matchingLogs.add(logWithMetaData);
}
}
}
return matchingLogs;
}
public static List<LogWithMetadata> generateLogWithMetadataForTransaction(
final TransactionReceipt receipt,
final long number,
final Hash blockhash,
final Hash transactionHash,
final int transactionIndex,
final boolean removed) {
final List<LogWithMetadata> logs = new ArrayList<>();
for (int logIndex = 0; logIndex < receipt.getLogs().size(); ++logIndex) {
final LogWithMetadata logWithMetaData =
new LogWithMetadata(
logIndex,
number,
blockhash,
transactionHash,
transactionIndex,
receipt.getLogs().get(logIndex).getLogger(),
receipt.getLogs().get(logIndex).getData(),
receipt.getLogs().get(logIndex).getTopics(),
removed);
logs.add(logWithMetaData);
}
return logs;
}
}

@ -0,0 +1,109 @@
/*
* Copyright 2018 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;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.core.LogTopic;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.util.List;
import com.google.common.base.MoreObjects;
public class LogWithMetadata {
private final int logIndex;
private final long blockNumber;
private final Hash blockHash;
private final Hash transactionHash;
private final int transactionIndex;
private final Address address;
private final BytesValue data;
private final List<LogTopic> topics;
private final boolean removed;
LogWithMetadata(
final int logIndex,
final long blockNumber,
final Hash blockHash,
final Hash transactionHash,
final int transactionIndex,
final Address address,
final BytesValue data,
final List<LogTopic> topics,
final boolean removed) {
this.logIndex = logIndex;
this.blockNumber = blockNumber;
this.blockHash = blockHash;
this.transactionHash = transactionHash;
this.transactionIndex = transactionIndex;
this.address = address;
this.data = data;
this.topics = topics;
this.removed = removed;
}
// The index of this log within the entire ordered list of logs associated with the block this log
// belongs to.
public int getLogIndex() {
return logIndex;
}
public long getBlockNumber() {
return blockNumber;
}
public Hash getBlockHash() {
return blockHash;
}
public Hash getTransactionHash() {
return transactionHash;
}
public int getTransactionIndex() {
return transactionIndex;
}
public Address getAddress() {
return address;
}
public BytesValue getData() {
return data;
}
public List<LogTopic> getTopics() {
return topics;
}
public boolean isRemoved() {
return removed;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("logIndex", logIndex)
.add("blockNumber", blockNumber)
.add("blockHash", blockHash)
.add("transactionHash", transactionHash)
.add("transactionIndex", transactionIndex)
.add("address", address)
.add("data", data)
.add("topics", topics)
.add("removed", removed)
.toString();
}
}

@ -0,0 +1,112 @@
/*
* Copyright 2018 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;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.Log;
import tech.pegasys.pantheon.ethereum.core.LogTopic;
import java.util.Arrays;
import java.util.List;
import com.google.common.collect.Lists;
public class LogsQuery {
private final List<Address> queryAddresses;
private final List<List<LogTopic>> queryTopics;
private LogsQuery(final List<Address> addresses, final List<List<LogTopic>> topics) {
this.queryAddresses = addresses;
this.queryTopics = topics;
}
public boolean matches(final Log log) {
return matchesAddresses(log.getLogger()) && matchesTopics(log.getTopics());
}
private boolean matchesAddresses(final Address address) {
return queryAddresses.isEmpty() || queryAddresses.contains(address);
}
private boolean matchesTopics(final List<LogTopic> topics) {
if (queryTopics.isEmpty()) {
return true;
}
if (topics.size() < queryTopics.size()) {
return false;
}
for (int i = 0; i < queryTopics.size(); ++i) {
if (!matchesTopic(topics.get(i), queryTopics.get(i))) {
return false;
}
}
return true;
}
private boolean matchesTopic(final LogTopic topic, final List<LogTopic> matchCriteria) {
for (final LogTopic candidate : matchCriteria) {
if (candidate == null) {
return true;
}
if (candidate.equals(topic)) {
return true;
}
}
return false;
}
public static class Builder {
private final List<Address> queryAddresses = Lists.newArrayList();
private final List<List<LogTopic>> queryTopics = Lists.newArrayList();
public Builder address(final Address address) {
if (address != null) {
queryAddresses.add(address);
}
return this;
}
public Builder addresses(final Address... addresses) {
if (addresses != null && addresses.length > 0) {
queryAddresses.addAll(Arrays.asList(addresses));
}
return this;
}
public Builder addresses(final List<Address> addresses) {
if (addresses != null && !addresses.isEmpty()) {
queryAddresses.addAll(addresses);
}
return this;
}
public Builder topics(final List<List<LogTopic>> topics) {
if (topics != null && !topics.isEmpty()) {
queryTopics.addAll(topics);
}
return this;
}
public Builder topics(final TopicsParameter topicsParameter) {
if (topicsParameter != null) {
topics(topicsParameter.getTopics());
}
return this;
}
public LogsQuery build() {
return new LogsQuery(queryAddresses, queryTopics);
}
}
}

@ -0,0 +1,52 @@
/*
* Copyright 2018 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;
import tech.pegasys.pantheon.ethereum.core.LogTopic;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonCreator;
class TopicsParameter {
private final List<List<LogTopic>> queryTopics = new ArrayList<>();
@JsonCreator
public TopicsParameter(final List<List<String>> topics) {
if (topics != null) {
for (final List<String> list : topics) {
final List<LogTopic> inputTopics = new ArrayList<>();
if (list != null) {
for (final String input : list) {
final LogTopic topic =
input != null ? LogTopic.create(BytesValue.fromHexString(input)) : null;
inputTopics.add(topic);
}
}
queryTopics.add(inputTopics);
}
}
}
public List<List<LogTopic>> getTopics() {
return queryTopics;
}
@Override
public String toString() {
return "TopicsParameter{" + "queryTopics=" + queryTopics + '}';
}
}

@ -0,0 +1,74 @@
/*
* Copyright 2018 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;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.core.Transaction;
import tech.pegasys.pantheon.ethereum.core.TransactionReceipt;
public class TransactionReceiptWithMetadata {
private final TransactionReceipt receipt;
private final Hash transactionHash;
private final int transactionIndex;
private final long gasUsed;
private final long blockNumber;
private final Hash blockHash;
private final Transaction transaction;
TransactionReceiptWithMetadata(
final TransactionReceipt receipt,
final Transaction transaction,
final Hash transactionHash,
final int transactionIndex,
final long gasUsed,
final Hash blockHash,
final long blockNumber) {
this.receipt = receipt;
this.transactionHash = transactionHash;
this.transactionIndex = transactionIndex;
this.gasUsed = gasUsed;
this.blockHash = blockHash;
this.blockNumber = blockNumber;
this.transaction = transaction;
}
public TransactionReceipt getReceipt() {
return receipt;
}
public Hash getTransactionHash() {
return transactionHash;
}
public Transaction getTransaction() {
return transaction;
}
public int getTransactionIndex() {
return transactionIndex;
}
public Hash getBlockHash() {
return blockHash;
}
public long getBlockNumber() {
return blockNumber;
}
// The gas used for this particular transaction (as opposed to cumulativeGas which is included in
// the receipt itself)
public long getGasUsed() {
return gasUsed;
}
}

@ -0,0 +1,51 @@
/*
* Copyright 2018 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;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.core.Transaction;
public class TransactionWithMetadata {
private final Transaction transaction;
private final long blockNumber;
private final Hash blockHash;
private final int transactionIndex;
public 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;
}
public Transaction getTransaction() {
return transaction;
}
public long getBlockNumber() {
return blockNumber;
}
public Hash getBlockHash() {
return blockHash;
}
public int getTransactionIndex() {
return transactionIndex;
}
}

@ -0,0 +1,53 @@
/*
* 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.Account;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.util.bytes.Bytes32;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.uint.UInt256;
import java.util.Optional;
import graphql.schema.DataFetchingEnvironment;
@SuppressWarnings("unused") // reflected by GraphQL
public class AccountAdapter extends AdapterBase {
private final Account account;
public AccountAdapter(final Account account) {
this.account = account;
}
public Optional<Address> getAddress() {
return Optional.of(account.getAddress());
}
public Optional<UInt256> getBalance() {
return Optional.of(account.getBalance().asUInt256());
}
public Optional<Long> getTransactionCount() {
return Optional.of(account.getNonce());
}
public Optional<BytesValue> getCode() {
return Optional.of(account.getCode());
}
public Optional<Bytes32> getStorage(final DataFetchingEnvironment environment) {
final Bytes32 slot = environment.getArgument("slot");
return Optional.of(account.getStorageValue(slot.asUInt256()).getBytes());
}
}

@ -0,0 +1,24 @@
/*
* 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.graphqlrpc.GraphQLDataFetcherContext;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.BlockchainQuery;
import graphql.schema.DataFetchingEnvironment;
abstract class AdapterBase {
BlockchainQuery getBlockchainQuery(final DataFetchingEnvironment environment) {
return ((GraphQLDataFetcherContext) environment.getContext()).getBlockchainQuery();
}
}

@ -0,0 +1,228 @@
/*
* 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.BlockHeader;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.core.LogTopic;
import tech.pegasys.pantheon.ethereum.core.Wei;
import tech.pegasys.pantheon.ethereum.core.WorldState;
import tech.pegasys.pantheon.ethereum.graphqlrpc.GraphQLDataFetcherContext;
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.LogsQuery;
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.Bytes32;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.uint.UInt256;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import com.google.common.primitives.Longs;
import graphql.schema.DataFetchingEnvironment;
@SuppressWarnings("unused") // reflected by GraphQL
public class BlockAdapterBase extends AdapterBase {
private final BlockHeader header;
BlockAdapterBase(final BlockHeader header) {
this.header = header;
}
public Optional<NormalBlockAdapter> getParent(final DataFetchingEnvironment environment) {
final BlockchainQuery query = getBlockchainQuery(environment);
final Hash parentHash = header.getParentHash();
final Optional<BlockWithMetadata<TransactionWithMetadata, Hash>> block =
query.blockByHash(parentHash);
return block.map(NormalBlockAdapter::new);
}
public Optional<Bytes32> getHash() {
return Optional.of(header.getHash());
}
public Optional<BytesValue> getNonce() {
final long nonce = header.getNonce();
final byte[] bytes = Longs.toByteArray(nonce);
return Optional.of(BytesValue.wrap(bytes));
}
public Optional<Bytes32> getTransactionsRoot() {
return Optional.of(header.getTransactionsRoot());
}
public Optional<Bytes32> getStateRoot() {
return Optional.of(header.getStateRoot());
}
public Optional<Bytes32> getReceiptsRoot() {
return Optional.of(header.getReceiptsRoot());
}
public Optional<AccountAdapter> getMiner(final DataFetchingEnvironment environment) {
final BlockchainQuery query = getBlockchainQuery(environment);
long blockNumber = header.getNumber();
final Long bn = environment.getArgument("block");
if (bn != null) {
blockNumber = bn;
}
return Optional.of(
new AccountAdapter(query.getWorldState(blockNumber).get().get(header.getCoinbase())));
}
public Optional<BytesValue> getExtraData() {
return Optional.of(header.getExtraData());
}
public Optional<Long> getGasLimit() {
return Optional.of(header.getGasLimit());
}
public Optional<Long> getGasUsed() {
return Optional.of(header.getGasUsed());
}
public Optional<UInt256> getTimestamp() {
return Optional.of(UInt256.of(header.getTimestamp()));
}
public Optional<BytesValue> getLogsBloom() {
return Optional.of(header.getLogsBloom().getBytes());
}
public Optional<Bytes32> getMixHash() {
return Optional.of(header.getMixHash());
}
public Optional<UInt256> getDifficulty() {
return Optional.of(header.getDifficulty());
}
public Optional<Bytes32> getOmmerHash() {
return Optional.of(header.getOmmersHash());
}
public Optional<Long> getNumber() {
final long bn = header.getNumber();
return Optional.of(bn);
}
public Optional<AccountAdapter> getAccount(final DataFetchingEnvironment environment) {
final BlockchainQuery query = getBlockchainQuery(environment);
final long bn = header.getNumber();
final WorldState ws = query.getWorldState(bn).get();
if (ws != null) {
final Address addr = environment.getArgument("address");
return Optional.of(new AccountAdapter(ws.get(addr)));
}
return Optional.empty();
}
public List<LogAdapter> getLogs(final DataFetchingEnvironment environment) {
final Map<String, Object> filter = environment.getArgument("filter");
@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 BlockchainQuery blockchain = getBlockchainQuery(environment);
final Hash hash = header.getHash();
final List<LogWithMetadata> logs = blockchain.matchingLogs(hash, query);
final List<LogAdapter> results = new ArrayList<>();
for (final LogWithMetadata log : logs) {
results.add(new LogAdapter(log));
}
return results;
}
public Optional<Long> getEstimateGas(final DataFetchingEnvironment environment) {
final Optional<CallResult> result = executeCall(environment);
return result.map(CallResult::getGasUsed);
}
public Optional<CallResult> getCall(final DataFetchingEnvironment environment) {
return executeCall(environment);
}
private Optional<CallResult> executeCall(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 long bn = header.getNumber();
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.process(param, bn);
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();
}
}

@ -0,0 +1,40 @@
/*
* 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.util.bytes.BytesValue;
@SuppressWarnings("unused") // reflected by GraphQL
class CallResult {
private final Long status;
private final Long gasUsed;
private final BytesValue data;
CallResult(final Long status, final Long gasUsed, final BytesValue data) {
this.status = status;
this.gasUsed = gasUsed;
this.data = data;
}
public Long getStatus() {
return status;
}
public Long getGasUsed() {
return gasUsed;
}
public BytesValue getData() {
return data;
}
}

@ -0,0 +1,73 @@
/*
* 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.Hash;
import tech.pegasys.pantheon.ethereum.core.LogTopic;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.BlockchainQuery;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.LogWithMetadata;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.TransactionWithMetadata;
import tech.pegasys.pantheon.util.bytes.Bytes32;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import graphql.schema.DataFetchingEnvironment;
@SuppressWarnings("unused") // reflected by GraphQL
public class LogAdapter extends AdapterBase {
private final LogWithMetadata logWithMetadata;
LogAdapter(final LogWithMetadata logWithMetadata) {
this.logWithMetadata = logWithMetadata;
}
public Optional<Integer> getIndex() {
return Optional.of(logWithMetadata.getLogIndex());
}
public List<Bytes32> getTopics() {
final List<LogTopic> topics = logWithMetadata.getTopics();
final List<Bytes32> result = new ArrayList<>();
for (final LogTopic topic : topics) {
result.add(Bytes32.leftPad(topic));
}
return result;
}
public Optional<BytesValue> getData() {
return Optional.of(logWithMetadata.getData());
}
public Optional<TransactionAdapter> getTransaction(final DataFetchingEnvironment environment) {
final BlockchainQuery query = getBlockchainQuery(environment);
final Hash hash = logWithMetadata.getTransactionHash();
final Optional<TransactionWithMetadata> tran = query.transactionByHash(hash);
return tran.map(TransactionAdapter::new);
}
public Optional<AccountAdapter> getAccount(final DataFetchingEnvironment environment) {
final BlockchainQuery query = getBlockchainQuery(environment);
long blockNumber = logWithMetadata.getBlockNumber();
final Long bn = environment.getArgument("block");
if (bn != null) {
blockNumber = bn;
}
return query
.getWorldState(blockNumber)
.map(ws -> new AccountAdapter(ws.get(logWithMetadata.getAddress())));
}
}

@ -0,0 +1,95 @@
/*
* 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.BlockHeader;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.BlockWithMetadata;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.BlockchainQuery;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.TransactionWithMetadata;
import tech.pegasys.pantheon.util.uint.UInt256;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import graphql.schema.DataFetchingEnvironment;
@SuppressWarnings("unused") // reflected by GraphQL
public class NormalBlockAdapter extends BlockAdapterBase {
public NormalBlockAdapter(
final BlockWithMetadata<TransactionWithMetadata, Hash> blockWithMetaData) {
super(blockWithMetaData.getHeader());
this.blockWithMetaData = blockWithMetaData;
}
private final BlockWithMetadata<TransactionWithMetadata, Hash> blockWithMetaData;
public Optional<Integer> getTransactionCount() {
return Optional.of(blockWithMetaData.getTransactions().size());
}
public Optional<UInt256> getTotalDifficulty() {
return Optional.of(blockWithMetaData.getTotalDifficulty());
}
public Optional<Integer> getOmmerCount() {
return Optional.of(blockWithMetaData.getOmmers().size());
}
public List<UncleBlockAdapter> getOmmers(final DataFetchingEnvironment environment) {
final BlockchainQuery query = getBlockchainQuery(environment);
final List<Hash> ommers = blockWithMetaData.getOmmers();
final List<UncleBlockAdapter> results = new ArrayList<>();
final Hash hash = blockWithMetaData.getHeader().getHash();
for (int i = 0; i < ommers.size(); i++) {
final Optional<BlockHeader> header = query.getOmmer(hash, i);
header.ifPresent(item -> results.add(new UncleBlockAdapter(item)));
}
return results;
}
public Optional<UncleBlockAdapter> getOmmerAt(final DataFetchingEnvironment environment) {
final BlockchainQuery query = getBlockchainQuery(environment);
final int index = environment.getArgument("index");
final List<Hash> ommers = blockWithMetaData.getOmmers();
if (ommers.size() > index) {
final Hash hash = blockWithMetaData.getHeader().getHash();
final Optional<BlockHeader> header = query.getOmmer(hash, index);
return header.map(UncleBlockAdapter::new);
}
return Optional.empty();
}
public List<TransactionAdapter> getTransactions() {
final List<TransactionWithMetadata> trans = blockWithMetaData.getTransactions();
final List<TransactionAdapter> results = new ArrayList<>();
for (final TransactionWithMetadata tran : trans) {
results.add(new TransactionAdapter(tran));
}
return results;
}
public Optional<TransactionAdapter> getTransactionAt(final DataFetchingEnvironment environment) {
final int index = environment.getArgument("index");
final List<TransactionWithMetadata> trans = blockWithMetaData.getTransactions();
if (trans.size() > index) {
return Optional.of(new TransactionAdapter(trans.get(index)));
}
return Optional.empty();
}
}

@ -0,0 +1,48 @@
/*
* 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.SyncStatus;
import java.util.Optional;
@SuppressWarnings("unused") // reflected by GraphQL
public class SyncStateAdapter {
private final SyncStatus syncStatus;
public SyncStateAdapter(final SyncStatus syncStatus) {
this.syncStatus = syncStatus;
}
public Optional<Long> getStartingBlock() {
return Optional.of(syncStatus.getStartingBlock());
}
public Optional<Long> getCurrentBlock() {
return Optional.of(syncStatus.getCurrentBlock());
}
public Optional<Long> getHighestBlock() {
return Optional.of(syncStatus.getHighestBlock());
}
public Optional<Long> getPulledStates() {
// currently synchronizer has no this information
return Optional.empty();
}
public Optional<Long> getKnownStates() {
// currently synchronizer has no this information
return Optional.empty();
}
}

@ -0,0 +1,185 @@
/*
* 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.Hash;
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;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.TransactionWithMetadata;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.uint.UInt256;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import graphql.schema.DataFetchingEnvironment;
@SuppressWarnings("unused") // reflected by GraphQL
public class TransactionAdapter extends AdapterBase {
private final TransactionWithMetadata transactionWithMetadata;
public TransactionAdapter(final TransactionWithMetadata transactionWithMetadata) {
this.transactionWithMetadata = transactionWithMetadata;
}
public Optional<Hash> getHash() {
return Optional.of(transactionWithMetadata.getTransaction().hash());
}
public Optional<Long> getNonce() {
final long nonce = transactionWithMetadata.getTransaction().getNonce();
return Optional.of(nonce);
}
public Optional<Integer> getIndex() {
return Optional.of(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;
}
return query
.getWorldState(blockNumber)
.map(
mutableWorldState ->
new AccountAdapter(
mutableWorldState.get(transactionWithMetadata.getTransaction().getSender())));
}
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;
}
return query
.getWorldState(blockNumber)
.flatMap(
ws ->
transactionWithMetadata
.getTransaction()
.getTo()
.map(addr -> new AccountAdapter(ws.get(addr))));
}
public Optional<UInt256> getValue() {
return Optional.of(transactionWithMetadata.getTransaction().getValue().asUInt256());
}
public Optional<UInt256> getGasPrice() {
return Optional.of(transactionWithMetadata.getTransaction().getGasPrice().asUInt256());
}
public Optional<Long> getGas() {
return Optional.of(transactionWithMetadata.getTransaction().getGasLimit());
}
public Optional<BytesValue> getInputData() {
return Optional.of(transactionWithMetadata.getTransaction().getPayload());
}
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);
}
public Optional<Long> getStatus(final DataFetchingEnvironment environment) {
return Optional.ofNullable(transactionWithMetadata.getTransaction())
.map(Transaction::hash)
.flatMap(rpt -> getBlockchainQuery(environment).transactionReceiptByTransactionHash(rpt))
.map(TransactionReceiptWithMetadata::getReceipt)
.flatMap(
receipt ->
receipt.getStatus() == -1
? Optional.empty()
: Optional.of((long) receipt.getStatus()));
}
public Optional<Long> getGasUsed(final DataFetchingEnvironment environment) {
final BlockchainQuery query = getBlockchainQuery(environment);
final Optional<TransactionReceiptWithMetadata> rpt =
query.transactionReceiptByTransactionHash(transactionWithMetadata.getTransaction().hash());
return rpt.map(TransactionReceiptWithMetadata::getGasUsed);
}
public Optional<Long> getCumulativeGasUsed(final DataFetchingEnvironment environment) {
final BlockchainQuery query = getBlockchainQuery(environment);
final Optional<TransactionReceiptWithMetadata> rpt =
query.transactionReceiptByTransactionHash(transactionWithMetadata.getTransaction().hash());
if (rpt.isPresent()) {
final TransactionReceipt receipt = rpt.get().getReceipt();
return Optional.of(receipt.getCumulativeGasUsed());
}
return Optional.empty();
}
public Optional<AccountAdapter> getCreatedContract(final DataFetchingEnvironment environment) {
final boolean contractCreated = transactionWithMetadata.getTransaction().isContractCreation();
if (contractCreated) {
final Optional<Address> addr = transactionWithMetadata.getTransaction().getTo();
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<WorldState> ws = query.getWorldState(blockNumber);
if (ws.isPresent()) {
return Optional.of(new AccountAdapter(ws.get().get(addr.get())));
}
}
}
return Optional.empty();
}
public List<LogAdapter> getLogs(final DataFetchingEnvironment environment) {
final BlockchainQuery query = getBlockchainQuery(environment);
final Hash hash = transactionWithMetadata.getTransaction().hash();
final Optional<TransactionReceiptWithMetadata> tranRpt =
query.transactionReceiptByTransactionHash(hash);
final List<LogAdapter> results = new ArrayList<>();
if (tranRpt.isPresent()) {
final List<LogWithMetadata> logs =
BlockchainQuery.generateLogWithMetadataForTransaction(
tranRpt.get().getReceipt(),
transactionWithMetadata.getBlockNumber(),
transactionWithMetadata.getBlockHash(),
hash,
transactionWithMetadata.getTransactionIndex(),
false);
for (final LogWithMetadata log : logs) {
results.add(new LogAdapter(log));
}
}
return results;
}
}

@ -0,0 +1,56 @@
/*
* 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.BlockHeader;
import tech.pegasys.pantheon.util.uint.UInt256;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@SuppressWarnings("unused") // reflected by GraphQL
class UncleBlockAdapter extends BlockAdapterBase {
UncleBlockAdapter(final BlockHeader uncleHeader) {
super(uncleHeader);
}
public Optional<Integer> getTransactionCount() {
return Optional.of(0);
}
public Optional<UInt256> getTotalDifficulty() {
return Optional.of(UInt256.of(0));
}
public Optional<Integer> getOmmerCount() {
return Optional.empty();
}
public List<NormalBlockAdapter> getOmmers() {
return new ArrayList<>();
}
public Optional<NormalBlockAdapter> getOmmerAt() {
return Optional.empty();
}
public List<TransactionAdapter> getTransactions() {
return new ArrayList<>();
}
public Optional<TransactionAdapter> getTransactionAt() {
return Optional.empty();
}
}

@ -0,0 +1,54 @@
/*
* 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.response;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonSetter;
public class GraphQLJsonRequest {
private String query;
private String operationName;
private Map<String, Object> variables;
@JsonGetter
public String getQuery() {
return query;
}
@JsonSetter
public void setQuery(final String query) {
this.query = query;
}
@JsonGetter
public String getOperationName() {
return operationName;
}
@JsonSetter
public void setOperationName(final String operationName) {
this.operationName = operationName;
}
@JsonGetter
public Map<String, Object> getVariables() {
return variables;
}
@JsonSetter
public void setVariables(final Map<String, Object> variables) {
this.variables = variables;
}
}

@ -0,0 +1,43 @@
/*
* Copyright 2018 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.response;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonGetter;
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum GraphQLRpcError {
// Standard errors
INVALID_PARAMS(-32602, "Invalid params"),
INTERNAL_ERROR(-32603, "Internal error"),
CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE(-32008, "Initial sync is still in progress");
private final int code;
private final String message;
GraphQLRpcError(final int code, final String message) {
this.code = code;
this.message = message;
}
@JsonGetter("code")
public int getCode() {
return code;
}
@JsonGetter("message")
public String getMessage() {
return message;
}
}

@ -0,0 +1,28 @@
/*
* Copyright 2018 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.response;
import com.fasterxml.jackson.annotation.JsonIgnore;
public class GraphQLRpcErrorResponse extends GraphQLRpcResponse {
public GraphQLRpcErrorResponse(final Object errors) {
super(errors);
}
@Override
@JsonIgnore
public GraphQLRpcResponseType getType() {
return GraphQLRpcResponseType.ERROR;
}
}

@ -0,0 +1,25 @@
/*
* Copyright 2018 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.response;
public class GraphQLRpcNoResponse extends GraphQLRpcResponse {
public GraphQLRpcNoResponse() {
super(null);
}
@Override
public GraphQLRpcResponseType getType() {
return GraphQLRpcResponseType.NONE;
}
}

@ -0,0 +1,46 @@
/*
* Copyright 2018 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.response;
import com.google.common.base.Objects;
public abstract class GraphQLRpcResponse {
public abstract GraphQLRpcResponseType getType();
private final Object result;
GraphQLRpcResponse(final Object result) {
this.result = result;
}
public Object getResult() {
return result;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final GraphQLRpcResponse that = (GraphQLRpcResponse) o;
return Objects.equal(result, that.result);
}
@Override
public int hashCode() {
return Objects.hashCode(result);
}
}

@ -0,0 +1,21 @@
/*
* Copyright 2018 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.response;
/** Various types of responses that the JSON-RPC component may produce. */
public enum GraphQLRpcResponseType {
NONE,
SUCCESS,
ERROR,
UNAUTHORIZED
}

@ -0,0 +1,28 @@
/*
* Copyright 2018 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.response;
import com.fasterxml.jackson.annotation.JsonIgnore;
public class GraphQLRpcSuccessResponse extends GraphQLRpcResponse {
public GraphQLRpcSuccessResponse(final Object data) {
super(data);
}
@Override
@JsonIgnore
public GraphQLRpcResponseType getType() {
return GraphQLRpcResponseType.SUCCESS;
}
}

@ -0,0 +1,63 @@
/*
* 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.scalar;
import tech.pegasys.pantheon.ethereum.core.Address;
import graphql.Internal;
import graphql.language.StringValue;
import graphql.schema.Coercing;
import graphql.schema.CoercingParseLiteralException;
import graphql.schema.CoercingParseValueException;
import graphql.schema.CoercingSerializeException;
import graphql.schema.GraphQLScalarType;
@Internal
public class AddressScalar extends GraphQLScalarType {
public AddressScalar() {
super(
"Address",
"Address scalar",
new Coercing<Object, Object>() {
@Override
public String serialize(final Object input) throws CoercingSerializeException {
if (input instanceof Address) {
return input.toString();
}
throw new CoercingSerializeException("Unable to serialize " + input + " as an Address");
}
@Override
public String parseValue(final Object input) throws CoercingParseValueException {
if (input instanceof Address) {
return input.toString();
}
throw new CoercingParseValueException(
"Unable to parse variable value " + input + " as an Address");
}
@Override
public Address parseLiteral(final Object input) throws CoercingParseLiteralException {
if (!(input instanceof StringValue)) {
throw new CoercingParseLiteralException("Value is not any Address : '" + input + "'");
}
try {
return Address.fromHexStringStrict(((StringValue) input).getValue());
} catch (final IllegalArgumentException e) {
throw new CoercingParseLiteralException("Value is not any Address : '" + input + "'");
}
}
});
}
}

@ -0,0 +1,66 @@
/*
* 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.scalar;
import tech.pegasys.pantheon.util.uint.UInt256;
import graphql.Internal;
import graphql.language.IntValue;
import graphql.language.StringValue;
import graphql.schema.Coercing;
import graphql.schema.CoercingParseLiteralException;
import graphql.schema.CoercingParseValueException;
import graphql.schema.CoercingSerializeException;
import graphql.schema.GraphQLScalarType;
@Internal
public class BigIntScalar extends GraphQLScalarType {
public BigIntScalar() {
super(
"BigInt",
"A BigInt scalar",
new Coercing<Object, Object>() {
@Override
public String serialize(final Object input) throws CoercingSerializeException {
if (input instanceof UInt256) {
return ((UInt256) input).toShortHexString();
}
throw new CoercingSerializeException("Unable to serialize " + input + " as an BigInt");
}
@Override
public String parseValue(final Object input) throws CoercingParseValueException {
if (input instanceof UInt256) {
return ((UInt256) input).toShortHexString();
}
throw new CoercingParseValueException(
"Unable to parse variable value " + input + " as an BigInt");
}
@Override
public UInt256 parseLiteral(final Object input) throws CoercingParseLiteralException {
try {
if (input instanceof StringValue) {
return UInt256.fromHexString(((StringValue) input).getValue());
} else if (input instanceof IntValue) {
return UInt256.of(((IntValue) input).getValue());
}
} catch (final IllegalArgumentException e) {
// fall through
}
throw new CoercingParseLiteralException("Value is not any BigInt : '" + input + "'");
}
});
}
}

@ -0,0 +1,63 @@
/*
* 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.scalar;
import tech.pegasys.pantheon.util.bytes.Bytes32;
import graphql.Internal;
import graphql.language.StringValue;
import graphql.schema.Coercing;
import graphql.schema.CoercingParseLiteralException;
import graphql.schema.CoercingParseValueException;
import graphql.schema.CoercingSerializeException;
import graphql.schema.GraphQLScalarType;
@Internal
public class Bytes32Scalar extends GraphQLScalarType {
public Bytes32Scalar() {
super(
"Bytes32",
"A Byte32 scalar",
new Coercing<Object, Object>() {
@Override
public String serialize(final Object input) throws CoercingSerializeException {
if (input instanceof Bytes32) {
return input.toString();
}
throw new CoercingSerializeException("Unable to serialize " + input + " as an Bytes32");
}
@Override
public String parseValue(final Object input) throws CoercingParseValueException {
if (input instanceof Bytes32) {
return input.toString();
}
throw new CoercingParseValueException(
"Unable to parse variable value " + input + " as an Bytes32");
}
@Override
public Bytes32 parseLiteral(final Object input) throws CoercingParseLiteralException {
if (!(input instanceof StringValue)) {
throw new CoercingParseLiteralException("Value is not any Bytes32 : '" + input + "'");
}
try {
return Bytes32.fromHexString(((StringValue) input).getValue());
} catch (final IllegalArgumentException e) {
throw new CoercingParseLiteralException("Value is not any Bytes32 : '" + input + "'");
}
}
});
}
}

@ -0,0 +1,63 @@
/*
* 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.scalar;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import graphql.Internal;
import graphql.language.StringValue;
import graphql.schema.Coercing;
import graphql.schema.CoercingParseLiteralException;
import graphql.schema.CoercingParseValueException;
import graphql.schema.CoercingSerializeException;
import graphql.schema.GraphQLScalarType;
@Internal
public class BytesScalar extends GraphQLScalarType {
public BytesScalar() {
super(
"Bytes",
"A Byte32 scalar",
new Coercing<Object, Object>() {
@Override
public String serialize(final Object input) throws CoercingSerializeException {
if (input instanceof BytesValue) {
return input.toString();
}
throw new CoercingSerializeException("Unable to serialize " + input + " as an Bytes");
}
@Override
public String parseValue(final Object input) throws CoercingParseValueException {
if (input instanceof BytesValue) {
return input.toString();
}
throw new CoercingParseValueException(
"Unable to parse variable value " + input + " as an Bytes");
}
@Override
public BytesValue parseLiteral(final Object input) throws CoercingParseLiteralException {
if (!(input instanceof StringValue)) {
throw new CoercingParseLiteralException("Value is not any Bytes : '" + input + "'");
}
try {
return BytesValue.fromHexString(((StringValue) input).getValue());
} catch (final IllegalArgumentException e) {
throw new CoercingParseLiteralException("Value is not any Bytes : '" + input + "'");
}
}
});
}
}

@ -0,0 +1,85 @@
/*
* 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.scalar;
import tech.pegasys.pantheon.util.bytes.Bytes32;
import graphql.Internal;
import graphql.language.IntValue;
import graphql.language.StringValue;
import graphql.schema.Coercing;
import graphql.schema.CoercingParseLiteralException;
import graphql.schema.CoercingParseValueException;
import graphql.schema.CoercingSerializeException;
import graphql.schema.GraphQLScalarType;
@Internal
public class LongScalar extends GraphQLScalarType {
public LongScalar() {
super(
"Long",
"Long is a 64 bit unsigned integer",
new Coercing<Object, Object>() {
@Override
public Number serialize(final Object input) throws CoercingSerializeException {
if (input instanceof Number) {
return (Number) input;
} else if (input instanceof String) {
final String value = ((String) input).toLowerCase();
if (value.startsWith("0x")) {
return Bytes32.fromHexStringLenient(value).asUInt256().toLong();
} else {
return Long.parseLong(value);
}
}
throw new CoercingSerializeException("Unable to serialize " + input + " as an Long");
}
@Override
public Number parseValue(final Object input) throws CoercingParseValueException {
if (input instanceof Number) {
return (Number) input;
} else if (input instanceof String) {
final String value = ((String) input).toLowerCase();
if (value.startsWith("0x")) {
return Bytes32.fromHexStringLenient(value).asUInt256().toLong();
} else {
return Long.parseLong(value);
}
}
throw new CoercingParseValueException(
"Unable to parse variable value " + input + " as an Long");
}
@Override
public Object parseLiteral(final Object input) throws CoercingParseLiteralException {
try {
if (input instanceof IntValue) {
return ((IntValue) input).getValue().longValue();
} else if (input instanceof StringValue) {
final String value = ((StringValue) input).getValue().toLowerCase();
if (value.startsWith("0x")) {
return Bytes32.fromHexStringLenient(value).asUInt256().toLong();
} else {
return Long.parseLong(value);
}
}
} catch (final NumberFormatException e) {
// fall through
}
throw new CoercingParseLiteralException("Value is not any Long : '" + input + "'");
}
});
}
}

@ -0,0 +1,303 @@
# Bytes32 is a 32 byte binary string, represented as 0x-prefixed hexadecimal.
scalar Bytes32
# Address is a 20 byte Ethereum address, represented as 0x-prefixed hexadecimal.
scalar Address
# Bytes is an arbitrary length binary string, represented as 0x-prefixed hexadecimal.
# An empty byte string is represented as '0x'. Byte strings must have an even number of hexadecimal nybbles.
scalar Bytes
# BigInt is a large integer. Input is accepted as either a JSON number or as a string.
# Strings may be either decimal or 0x-prefixed hexadecimal. Output values are all
# 0x-prefixed hexadecimal.
scalar BigInt
# Long is a 64 bit unsigned integer.
scalar Long
schema {
query: Query
mutation: Mutation
}
# Account is an Ethereum account at a particular block.
type Account {
# Address is the address owning the account.
address: Address!
# Balance is the balance of the account, in wei.
balance: BigInt!
# TransactionCount is the number of transactions sent from this account,
# or in the case of a contract, the number of contracts created. Otherwise
# known as the nonce.
transactionCount: Long!
# Code contains the smart contract code for this account, if the account
# is a (non-self-destructed) contract.
code: Bytes!
# Storage provides access to the storage of a contract account, indexed
# by its 32 byte slot identifier.
storage(slot: Bytes32!): Bytes32!
}
# Log is an Ethereum event log.
type Log {
# Index is the index of this log in the block.
index: Int!
# Account is the account which generated this log - this will always
# be a contract account.
account(block: Long): Account!
# Topics is a list of 0-4 indexed topics for the log.
topics: [Bytes32!]!
# Data is unindexed data for this log.
data: Bytes!
# Transaction is the transaction that generated this log entry.
transaction: Transaction!
}
# Transaction is an Ethereum transaction.
type Transaction {
# Hash is the hash of this transaction.
hash: Bytes32!
# Nonce is the nonce of the account this transaction was generated with.
nonce: Long!
# Index is the index of this transaction in the parent block. This will
# be null if the transaction has not yet been mined.
index: Int
# From is the account that sent this transaction - this will always be
# an externally owned account.
from(block: Long): Account!
# To is the account the transaction was sent to. This is null for
# contract-creating transactions.
to(block: Long): Account
# Value is the value, in wei, sent along with this transaction.
value: BigInt!
# GasPrice is the price offered to miners for gas, in wei per unit.
gasPrice: BigInt!
# Gas is the maximum amount of gas this transaction can consume.
gas: Long!
# InputData is the data supplied to the target of the transaction.
inputData: Bytes!
# Block is the block this transaction was mined in. This will be null if
# the transaction has not yet been mined.
block: Block
# Status is the return status of the transaction. This will be 1 if the
# transaction succeeded, or 0 if it failed (due to a revert, or due to
# running out of gas). If the transaction has not yet been mined, this
# field will be null.
status: Long
# GasUsed is the amount of gas that was used processing this transaction.
# If the transaction has not yet been mined, this field will be null.
gasUsed: Long
# CumulativeGasUsed is the total gas used in the block up to and including
# this transaction. If the transaction has not yet been mined, this field
# will be null.
cumulativeGasUsed: Long
# CreatedContract is the account that was created by a contract creation
# transaction. If the transaction was not a contract creation transaction,
# or it has not yet been mined, this field will be null.
createdContract(block: Long): Account
# Logs is a list of log entries emitted by this transaction. If the
# transaction has not yet been mined, this field will be null.
logs: [Log!]
}
# BlockFilterCriteria encapsulates log filter criteria for a filter applied
# to a single block.
input BlockFilterCriteria {
# Addresses is list of addresses that are of interest. If this list is
# empty, results will not be filtered by address.
addresses: [Address!]
# Topics list restricts matches to particular event topics. Each event has a list
# of topics. Topics matches a prefix of that list. An empty element array matches any
# topic. Non-empty elements represent an alternative that matches any of the
# contained topics.
#
# Examples:
# - [] or nil matches any topic list
# - [[A]] matches topic A in first position
# - [[], [B]] matches any topic in first position, B in second position
# - [[A], [B]] matches topic A in first position, B in second position
# - [[A, B]], [C, D]] matches topic (A OR B) in first position, (C OR D) in second position
topics: [[Bytes32!]!]
}
# Block is an Ethereum block.
type Block {
# Number is the number of this block, starting at 0 for the genesis block.
number: Long!
# Hash is the block hash of this block.
hash: Bytes32!
# Parent is the parent block of this block.
parent: Block
# Nonce is the block nonce, an 8 byte sequence determined by the miner.
nonce: Bytes!
# TransactionsRoot is the keccak256 hash of the root of the trie of transactions in this block.
transactionsRoot: Bytes32!
# TransactionCount is the number of transactions in this block. if
# transactions are not available for this block, this field will be null.
transactionCount: Int
# StateRoot is the keccak256 hash of the state trie after this block was processed.
stateRoot: Bytes32!
# ReceiptsRoot is the keccak256 hash of the trie of transaction receipts in this block.
receiptsRoot: Bytes32!
# Miner is the account that mined this block.
miner(block: Long): Account!
# ExtraData is an arbitrary data field supplied by the miner.
extraData: Bytes!
# GasLimit is the maximum amount of gas that was available to transactions in this block.
gasLimit: Long!
# GasUsed is the amount of gas that was used executing transactions in this block.
gasUsed: Long!
# Timestamp is the unix timestamp at which this block was mined.
timestamp: BigInt!
# LogsBloom is a bloom filter that can be used to check if a block may
# contain log entries matching a filter.
logsBloom: Bytes!
# MixHash is the hash that was used as an input to the PoW process.
mixHash: Bytes32!
# Difficulty is a measure of the difficulty of mining this block.
difficulty: BigInt!
# TotalDifficulty is the sum of all difficulty values up to and including
# this block.
totalDifficulty: BigInt!
# OmmerCount is the number of ommers (AKA uncles) associated with this
# block. If ommers are unavailable, this field will be null.
ommerCount: Int
# Ommers is a list of ommer (AKA uncle) blocks associated with this block.
# If ommers are unavailable, this field will be null. Depending on your
# node, the transactions, transactionAt, transactionCount, ommers,
# ommerCount and ommerAt fields may not be available on any ommer blocks.
ommers: [Block]
# OmmerAt returns the ommer (AKA uncle) at the specified index. If ommers
# are unavailable, or the index is out of bounds, this field will be null.
ommerAt(index: Int!): Block
# OmmerHash is the keccak256 hash of all the ommers (AKA uncles)
# associated with this block.
ommerHash: Bytes32!
# Transactions is a list of transactions associated with this block. If
# transactions are unavailable for this block, this field will be null.
transactions: [Transaction!]
# TransactionAt returns the transaction at the specified index. If
# transactions are unavailable for this block, or if the index is out of
# bounds, this field will be null.
transactionAt(index: Int!): Transaction
# Logs returns a filtered set of logs from this block.
logs(filter: BlockFilterCriteria!): [Log!]!
# Account fetches an Ethereum account at the current block's state.
account(address: Address!): Account!
# Call executes a local call operation at the current block's state.
call(data: CallData!): CallResult
# EstimateGas estimates the amount of gas that will be required for
# successful execution of a transaction at the current block's state.
estimateGas(data: CallData!): Long!
}
# CallData represents the data associated with a local contract call.
# All fields are optional.
input CallData {
# From is the address making the call.
from: Address
# To is the address the call is sent to.
to: Address
# Gas is the amount of gas sent with the call.
gas: Long
# GasPrice is the price, in wei, offered for each unit of gas.
gasPrice: BigInt
# Value is the value, in wei, sent along with the call.
value: BigInt
# Data is the data sent to the callee.
data: Bytes
}
# CallResult is the result of a local call operation.
type CallResult {
# Data is the return data of the called contract.
data: Bytes!
# GasUsed is the amount of gas used by the call, after any refunds.
gasUsed: Long!
# Status is the result of the call - 1 for success or 0 for failure.
status: Long!
}
# FilterCriteria encapsulates log filter criteria for searching log entries.
input FilterCriteria {
# FromBlock is the block at which to start searching, inclusive. Defaults
# to the latest block if not supplied.
fromBlock: Long
# ToBlock is the block at which to stop searching, inclusive. Defaults
# to the latest block if not supplied.
toBlock: Long
# Addresses is a list of addresses that are of interest. If this list is
# empty, results will not be filtered by address.
addresses: [Address!]
# Topics list restricts matches to particular event topics. Each event has a list
# of topics. Topics matches a prefix of that list. An empty element array matches any
# topic. Non-empty elements represent an alternative that matches any of the
# contained topics.
#
# Examples:
# - [] or nil matches any topic list
# - [[A]] matches topic A in first position
# - [[], [B]] matches any topic in first position, B in second position
# - [[A], [B]] matches topic A in first position, B in second position
# - [[A, B]], [C, D]] matches topic (A OR B) in first position, (C OR D) in second position
topics: [[Bytes32!]!]
}
# SyncState contains the current synchronisation state of the client.
type SyncState{
# StartingBlock is the block number at which synchronisation started.
startingBlock: Long!
# CurrentBlock is the point at which synchronisation has presently reached.
currentBlock: Long!
# HighestBlock is the latest known block number.
highestBlock: Long!
# PulledStates is the number of state entries fetched so far, or null
# if this is not known or not relevant.
pulledStates: Long
# KnownStates is the number of states the node knows of so far, or null
# if this is not known or not relevant.
knownStates: Long
}
# Pending represents the current pending state.
type Pending {
# TransactionCount is the number of transactions in the pending state.
transactionCount: Int!
# Transactions is a list of transactions in the current pending state.
transactions: [Transaction!]
# Account fetches an Ethereum account for the pending state.
account(address: Address!): Account!
# Call executes a local call operation for the pending state.
call(data: CallData!): CallResult
# EstimateGas estimates the amount of gas that will be required for
# successful execution of a transaction for the pending state.
estimateGas(data: CallData!): Long!
}
type Query {
# Account fetches an Ethereum account at the specified block number.
# If blockNumber is not provided, it defaults to the most recent block.
account(address: Address!, blockNumber: Long): Account!
# Block fetches an Ethereum block by number or by hash. If neither is
# supplied, the most recent known block is returned.
block(number: Long, hash: Bytes32): Block!
# Blocks returns all the blocks between two numbers, inclusive. If
# to is not supplied, it defaults to the most recent known block.
blocks(from: Long!, to: Long): [Block!]!
# Pending returns the current pending state.
pending: Pending!
# Transaction returns a transaction specified by its hash.
transaction(hash: Bytes32!): Transaction
# Logs returns log entries matching the provided filter.
logs(filter: FilterCriteria!): [Log!]!
# GasPrice returns the node's estimate of a gas price sufficient to
# ensure a transaction is mined in a timely fashion.
gasPrice: BigInt!
# ProtocolVersion returns the current wire protocol version number.
protocolVersion: Int!
# Syncing returns information on the current synchronisation state.
syncing: SyncState
}
type Mutation {
# SendRawTransaction sends an RLP-encoded transaction to the network.
sendRawTransaction(data: Bytes!): Bytes32!
}

@ -0,0 +1,53 @@
/*
* Copyright 2018 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;
import static org.mockito.Mockito.when;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.BlockchainQuery;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.pojoadapter.NormalBlockAdapter;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import java.util.Optional;
import java.util.Set;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.ExpectedException;
import org.mockito.Mock;
public abstract class AbstractDataFetcherTest {
DataFetcher<Optional<NormalBlockAdapter>> fetcher;
private GraphQLDataFetchers fetchers;
@Mock protected Set<Capability> supportedCapabilities;
@Mock protected DataFetchingEnvironment environment;
@Mock protected GraphQLDataFetcherContext context;
@Mock protected BlockchainQuery query;
@Rule public ExpectedException thrown = ExpectedException.none();
@Before
public void before() {
fetchers = new GraphQLDataFetchers(supportedCapabilities);
fetcher = fetchers.getBlockDataFetcher();
when(environment.getContext()).thenReturn(context);
when(context.getBlockchainQuery()).thenReturn(query);
}
}

@ -0,0 +1,192 @@
/*
* Copyright 2018 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;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryBlockchain;
import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryWorldStateArchive;
import tech.pegasys.pantheon.ethereum.ProtocolContext;
import tech.pegasys.pantheon.ethereum.blockcreation.EthHashMiningCoordinator;
import tech.pegasys.pantheon.ethereum.chain.GenesisState;
import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain;
import tech.pegasys.pantheon.ethereum.core.Block;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.core.BlockImporter;
import tech.pegasys.pantheon.ethereum.core.SyncStatus;
import tech.pegasys.pantheon.ethereum.core.Synchronizer;
import tech.pegasys.pantheon.ethereum.core.Transaction;
import tech.pegasys.pantheon.ethereum.core.Wei;
import tech.pegasys.pantheon.ethereum.eth.EthProtocol;
import tech.pegasys.pantheon.ethereum.eth.transactions.PendingTransactions;
import tech.pegasys.pantheon.ethereum.eth.transactions.TransactionPool;
import tech.pegasys.pantheon.ethereum.mainnet.HeaderValidationMode;
import tech.pegasys.pantheon.ethereum.mainnet.MainnetBlockHashFunction;
import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule;
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule;
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSpec;
import tech.pegasys.pantheon.ethereum.mainnet.ValidationResult;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.ethereum.util.RawBlockIterator;
import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive;
import java.net.URL;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import graphql.GraphQL;
import io.vertx.core.Vertx;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
public abstract class AbstractEthGraphQLRpcHttpServiceTest {
@Rule public final TemporaryFolder folder = new TemporaryFolder();
private static ProtocolSchedule<Void> PROTOCOL_SCHEDULE;
static List<Block> BLOCKS;
private static Block GENESIS_BLOCK;
private static GenesisState GENESIS_CONFIG;
private final Vertx vertx = Vertx.vertx();
private GraphQLRpcHttpService service;
OkHttpClient client;
String baseUrl;
final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private MutableBlockchain blockchain;
private WorldStateArchive stateArchive;
private ProtocolContext<Void> context;
@BeforeClass
public static void setupConstants() throws Exception {
PROTOCOL_SCHEDULE = MainnetProtocolSchedule.create();
final URL blocksUrl =
EthGraphQLRpcHttpBySpecTest.class
.getClassLoader()
.getResource(
"tech/pegasys/pantheon/ethereum/graphqlrpc/graphQLRpcTestBlockchain.blocks");
final URL genesisJsonUrl =
EthGraphQLRpcHttpBySpecTest.class
.getClassLoader()
.getResource("tech/pegasys/pantheon/ethereum/graphqlrpc/graphQLRpcTestGenesis.json");
assertThat(blocksUrl).isNotNull();
assertThat(genesisJsonUrl).isNotNull();
BLOCKS = new ArrayList<>();
try (final RawBlockIterator iterator =
new RawBlockIterator(
Paths.get(blocksUrl.toURI()),
rlp -> BlockHeader.readFrom(rlp, MainnetBlockHashFunction::createHash))) {
while (iterator.hasNext()) {
BLOCKS.add(iterator.next());
}
}
final String genesisJson = Resources.toString(genesisJsonUrl, Charsets.UTF_8);
GENESIS_BLOCK = BLOCKS.get(0);
GENESIS_CONFIG = GenesisState.fromJson(genesisJson, PROTOCOL_SCHEDULE);
}
@Before
public void setupTest() throws Exception {
final Synchronizer synchronizerMock = mock(Synchronizer.class);
final SyncStatus status = new SyncStatus(1, 2, 3);
when(synchronizerMock.getSyncStatus()).thenReturn(Optional.of(status));
final EthHashMiningCoordinator miningCoordinatorMock = mock(EthHashMiningCoordinator.class);
when(miningCoordinatorMock.getMinTransactionGasPrice()).thenReturn(Wei.of(16));
final TransactionPool transactionPoolMock = mock(TransactionPool.class);
when(transactionPoolMock.addLocalTransaction(any(Transaction.class)))
.thenReturn(ValidationResult.valid());
final PendingTransactions pendingTransactionsMock = mock(PendingTransactions.class);
when(transactionPoolMock.getPendingTransactions()).thenReturn(pendingTransactionsMock);
stateArchive = createInMemoryWorldStateArchive();
GENESIS_CONFIG.writeStateTo(stateArchive.getMutable());
blockchain = createInMemoryBlockchain(GENESIS_BLOCK);
context = new ProtocolContext<>(blockchain, stateArchive, null);
final Set<Capability> supportedCapabilities = new HashSet<>();
supportedCapabilities.add(EthProtocol.ETH62);
supportedCapabilities.add(EthProtocol.ETH63);
final GraphQLRpcConfiguration config = GraphQLRpcConfiguration.createDefault();
config.setPort(0);
final GraphQLDataFetcherContext dataFetcherContext =
new GraphQLDataFetcherContext(
blockchain,
stateArchive,
PROTOCOL_SCHEDULE,
transactionPoolMock,
miningCoordinatorMock,
synchronizerMock);
final GraphQLDataFetchers dataFetchers = new GraphQLDataFetchers(supportedCapabilities);
final GraphQL graphQL = GraphQLProvider.buildGraphQL(dataFetchers);
service =
new GraphQLRpcHttpService(
vertx, folder.newFolder().toPath(), config, graphQL, dataFetcherContext);
service.start().join();
client = new OkHttpClient();
baseUrl = service.url() + "/graphql/";
}
@After
public void shutdownServer() {
client.dispatcher().executorService().shutdown();
client.connectionPool().evictAll();
service.stop().join();
vertx.close();
}
void importBlock(final int n) {
final Block block = BLOCKS.get(n);
final ProtocolSpec<Void> protocolSpec =
PROTOCOL_SCHEDULE.getByBlockNumber(block.getHeader().getNumber());
final BlockImporter<Void> blockImporter = protocolSpec.getBlockImporter();
blockImporter.importBlock(context, block, HeaderValidationMode.FULL);
}
}

@ -0,0 +1,49 @@
/*
* Copyright 2018 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;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class BlockDataFetcherTest extends AbstractDataFetcherTest {
@Test
public void bothNumberAndHashThrows() throws Exception {
final Hash fakedHash = Hash.hash(BytesValue.of(1));
when(environment.getArgument(eq("number"))).thenReturn(1L);
when(environment.getArgument(eq("hash"))).thenReturn(fakedHash);
thrown.expect(GraphQLRpcException.class);
fetcher.get(environment);
}
@Test
public void onlyNumber() throws Exception {
when(environment.getArgument(eq("number"))).thenReturn(1L);
when(environment.getArgument(eq("hash"))).thenReturn(null);
when(environment.getContext()).thenReturn(context);
when(context.getBlockchainQuery()).thenReturn(query);
fetcher.get(environment);
}
}

@ -0,0 +1,91 @@
/*
* Copyright 2018 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;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import io.vertx.core.json.JsonObject;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class EthGraphQLRpcHttpBySpecErrorCaseTest extends AbstractEthGraphQLRpcHttpServiceTest {
private final String specFileName;
public EthGraphQLRpcHttpBySpecErrorCaseTest(final String specFileName) {
this.specFileName = specFileName;
}
@Parameters(name = "{index}: {0}")
public static Collection<String> specs() {
final List<String> specs = new ArrayList<>();
specs.add("eth_getBlockWrongParams");
specs.add("eth_getBlocksByWrongRange");
specs.add("eth_getBalance_toobig_bn");
specs.add("eth_getBalance_without_addr");
return specs;
}
@Test
public void graphQLRPCCallWithSpecFile() throws Exception {
graphQLRPCCall(specFileName);
}
private void graphQLRPCCall(final String name) throws IOException {
final String testSpecFile = name + ".json";
final String json =
Resources.toString(
EthGraphQLRpcHttpBySpecTest.class.getResource(testSpecFile), Charsets.UTF_8);
final JsonObject spec = new JsonObject(json);
final String rawRequestBody = spec.getString("request");
final RequestBody requestBody = RequestBody.create(JSON, rawRequestBody);
final Request request = new Request.Builder().post(requestBody).url(baseUrl).build();
importBlocks(1, BLOCKS.size());
try (final Response resp = client.newCall(request).execute()) {
final int expectedStatusCode = spec.getInteger("statusCode");
final String resultStr = resp.body().string();
assertThat(resp.code()).isEqualTo(expectedStatusCode);
try {
final JsonObject expectedRespBody = spec.getJsonObject("response");
final JsonObject result = new JsonObject(resultStr);
if (expectedRespBody != null) {
assertThat(result).isEqualTo(expectedRespBody);
}
} catch (final IllegalStateException ignored) {
}
}
}
private void importBlocks(final int from, final int to) {
for (int i = from; i < to; ++i) {
importBlock(i);
}
}
}

@ -0,0 +1,123 @@
/*
* Copyright 2018 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;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import io.vertx.core.json.JsonObject;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class EthGraphQLRpcHttpBySpecTest extends AbstractEthGraphQLRpcHttpServiceTest {
private final String specFileName;
public EthGraphQLRpcHttpBySpecTest(final String specFileName) {
this.specFileName = specFileName;
}
@Parameters(name = "{index}: {0}")
public static Collection<String> specs() {
final List<String> specs = new ArrayList<>();
specs.add("eth_blockNumber");
specs.add("eth_getTransactionByHash");
specs.add("eth_getTransactionByHashNull");
specs.add("eth_getBlockByHash");
specs.add("eth_getBlockByNumber");
specs.add("eth_getBlockTransactionCountByHash");
specs.add("eth_getBlockTransactionCountByNumber");
specs.add("eth_getTransactionByBlockHashAndIndex");
specs.add("eth_getTransactionByBlockNumberAndIndex");
specs.add("eth_estimateGas_transfer");
specs.add("eth_estimateGas_noParams");
specs.add("eth_estimateGas_contractDeploy");
specs.add("eth_getCode");
specs.add("eth_getCode_noCode");
specs.add("eth_getStorageAt");
specs.add("eth_getStorageAt_illegalRangeGreaterThan");
specs.add("eth_getTransactionCount");
specs.add("eth_getTransactionByBlockNumberAndInvalidIndex");
specs.add("eth_getBlocksByRange");
specs.add("eth_call_Block8");
specs.add("eth_call_BlockLatest");
specs.add("eth_getBalance_latest");
specs.add("eth_getBalance_0x19");
specs.add("eth_gasPrice");
specs.add("eth_getTransactionReceipt");
specs.add("eth_syncing");
specs.add("eth_sendRawTransaction_contractCreation");
specs.add("eth_sendRawTransaction_messageCall");
specs.add("eth_sendRawTransaction_transferEther");
specs.add("eth_sendRawTransaction_unsignedTransaction");
specs.add("eth_getLogs_matchTopic");
return specs;
}
@Test
public void graphQLRPCCallWithSpecFile() throws Exception {
graphQLRPCCall(specFileName);
}
private void graphQLRPCCall(final String name) throws IOException {
final String testSpecFile = name + ".json";
final String json =
Resources.toString(
EthGraphQLRpcHttpBySpecTest.class.getResource(testSpecFile), Charsets.UTF_8);
final JsonObject spec = new JsonObject(json);
final String rawRequestBody = spec.getString("request");
final RequestBody requestBody = RequestBody.create(JSON, rawRequestBody);
final Request request = new Request.Builder().post(requestBody).url(baseUrl).build();
importBlocks(1, BLOCKS.size());
try (final Response resp = client.newCall(request).execute()) {
final int expectedStatusCode = spec.getInteger("statusCode");
assertThat(resp.code()).isEqualTo(expectedStatusCode);
final JsonObject expectedRespBody = spec.getJsonObject("response");
final String resultStr = resp.body().string();
final JsonObject result = new JsonObject(resultStr);
assertThat(result).isEqualTo(expectedRespBody);
}
}
private void importBlocks(final int from, final int to) {
for (int i = from; i < to; ++i) {
importBlock(i);
}
}
}

@ -0,0 +1,29 @@
/*
* Copyright 2018 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;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
public class GraphQLRpcConfigurationTest {
@Test
public void defaultConfiguration() {
final GraphQLRpcConfiguration configuration = GraphQLRpcConfiguration.createDefault();
assertThat(configuration.isEnabled()).isFalse();
assertThat(configuration.getHost()).isEqualTo("127.0.0.1");
assertThat(configuration.getPort()).isEqualTo(8547);
}
}

@ -0,0 +1,230 @@
/*
* Copyright 2018 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;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import tech.pegasys.pantheon.ethereum.blockcreation.EthHashMiningCoordinator;
import tech.pegasys.pantheon.ethereum.core.Synchronizer;
import tech.pegasys.pantheon.ethereum.eth.EthProtocol;
import tech.pegasys.pantheon.ethereum.eth.transactions.TransactionPool;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.BlockchainQuery;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import java.util.HashSet;
import java.util.Set;
import com.google.common.collect.Lists;
import graphql.GraphQL;
import io.vertx.core.Vertx;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
public class GraphQLRpcHttpServiceCorsTest {
@Rule public final TemporaryFolder folder = new TemporaryFolder();
private final Vertx vertx = Vertx.vertx();
private final OkHttpClient client = new OkHttpClient();
private GraphQLRpcHttpService graphQLRpcHttpService;
@Before
public void before() {
final GraphQLRpcConfiguration configuration = GraphQLRpcConfiguration.createDefault();
configuration.setPort(0);
}
@After
public void after() {
client.dispatcher().executorService().shutdown();
client.connectionPool().evictAll();
graphQLRpcHttpService.stop().join();
vertx.close();
}
@Test
public void requestWithNonAcceptedOriginShouldFail() throws Exception {
graphQLRpcHttpService = createGraphQLRpcHttpServiceWithAllowedDomains("http://foo.io");
final Request request =
new Request.Builder()
.url(graphQLRpcHttpService.url() + "/graphql?query={protocolVersion}")
.header("Origin", "http://bar.me")
.build();
try (final Response response = client.newCall(request).execute()) {
assertThat(response.isSuccessful()).isFalse();
}
}
@Test
public void requestWithAcceptedOriginShouldSucceed() throws Exception {
graphQLRpcHttpService = createGraphQLRpcHttpServiceWithAllowedDomains("http://foo.io");
final Request request =
new Request.Builder()
.url(graphQLRpcHttpService.url() + "/graphql?query={protocolVersion}")
.header("Origin", "http://foo.io")
.build();
try (final Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
assertThat(response.isSuccessful()).isTrue();
}
}
@Test
public void requestWithOneOfMultipleAcceptedOriginsShouldSucceed() throws Exception {
graphQLRpcHttpService =
createGraphQLRpcHttpServiceWithAllowedDomains("http://foo.io", "http://bar.me");
final Request request =
new Request.Builder()
.url(graphQLRpcHttpService.url() + "/graphql?query={protocolVersion}")
.header("Origin", "http://bar.me")
.build();
try (final Response response = client.newCall(request).execute()) {
assertThat(response.isSuccessful()).isTrue();
}
}
@Test
public void requestWithNoneOfMultipleAcceptedOriginsShouldFail() throws Exception {
graphQLRpcHttpService =
createGraphQLRpcHttpServiceWithAllowedDomains("http://foo.io", "http://bar.me");
final Request request =
new Request.Builder()
.url(graphQLRpcHttpService.url() + "/graphql?query={protocolVersion}")
.header("Origin", "http://hel.lo")
.build();
try (final Response response = client.newCall(request).execute()) {
assertThat(response.isSuccessful()).isFalse();
}
}
@Test
public void requestWithNoOriginShouldSucceedWhenNoCorsConfigSet() throws Exception {
graphQLRpcHttpService = createGraphQLRpcHttpServiceWithAllowedDomains();
final Request request =
new Request.Builder()
.url(graphQLRpcHttpService.url() + "/graphql?query={protocolVersion}")
.build();
try (final Response response = client.newCall(request).execute()) {
assertThat(response.isSuccessful()).isTrue();
}
}
@Test
public void requestWithNoOriginShouldSucceedWhenCorsIsSet() throws Exception {
graphQLRpcHttpService = createGraphQLRpcHttpServiceWithAllowedDomains("http://foo.io");
final Request request = new Request.Builder().url(graphQLRpcHttpService.url()).build();
try (final Response response = client.newCall(request).execute()) {
assertThat(response.isSuccessful()).isTrue();
}
}
@Test
public void requestWithAnyOriginShouldNotSucceedWhenCorsIsEmpty() throws Exception {
graphQLRpcHttpService = createGraphQLRpcHttpServiceWithAllowedDomains("");
final Request request =
new Request.Builder()
.url(graphQLRpcHttpService.url() + "/graphql?query={protocolVersion}")
.header("Origin", "http://bar.me")
.build();
try (final Response response = client.newCall(request).execute()) {
assertThat(response.isSuccessful()).isFalse();
}
}
@Test
public void requestWithAnyOriginShouldSucceedWhenCorsIsStart() throws Exception {
graphQLRpcHttpService = createGraphQLRpcHttpServiceWithAllowedDomains("*");
final Request request =
new Request.Builder()
.url(graphQLRpcHttpService.url() + "/graphql?query={protocolVersion}")
.header("Origin", "http://bar.me")
.build();
try (final Response response = client.newCall(request).execute()) {
assertThat(response.isSuccessful()).isTrue();
}
}
@Test
public void requestWithAccessControlRequestMethodShouldReturnAllowedHeaders() throws Exception {
graphQLRpcHttpService = createGraphQLRpcHttpServiceWithAllowedDomains("http://foo.io");
final Request request =
new Request.Builder()
.url(graphQLRpcHttpService.url() + "/graphql?query={protocolVersion}")
.method("OPTIONS", null)
.header("Access-Control-Request-Method", "OPTIONS")
.header("Origin", "http://foo.io")
.build();
try (final Response response = client.newCall(request).execute()) {
assertThat(response.header("Access-Control-Allow-Headers")).contains("*", "content-type");
}
}
private GraphQLRpcHttpService createGraphQLRpcHttpServiceWithAllowedDomains(
final String... corsAllowedDomains) throws Exception {
final GraphQLRpcConfiguration config = GraphQLRpcConfiguration.createDefault();
config.setPort(0);
if (corsAllowedDomains != null) {
config.setCorsAllowedDomains(Lists.newArrayList(corsAllowedDomains));
}
final BlockchainQuery blockchainQueries = mock(BlockchainQuery.class);
final Synchronizer synchronizer = mock(Synchronizer.class);
final EthHashMiningCoordinator miningCoordinatorMock = mock(EthHashMiningCoordinator.class);
final GraphQLDataFetcherContext dataFetcherContext = mock(GraphQLDataFetcherContext.class);
when(dataFetcherContext.getBlockchainQuery()).thenReturn(blockchainQueries);
when(dataFetcherContext.getMiningCoordinator()).thenReturn(miningCoordinatorMock);
when(dataFetcherContext.getTransactionPool()).thenReturn(mock(TransactionPool.class));
when(dataFetcherContext.getSynchronizer()).thenReturn(synchronizer);
final Set<Capability> supportedCapabilities = new HashSet<>();
supportedCapabilities.add(EthProtocol.ETH62);
supportedCapabilities.add(EthProtocol.ETH63);
final GraphQLDataFetchers dataFetchers = new GraphQLDataFetchers(supportedCapabilities);
final GraphQL graphQL = GraphQLProvider.buildGraphQL(dataFetchers);
final GraphQLRpcHttpService graphQLRpcHttpService =
new GraphQLRpcHttpService(
vertx, folder.newFolder().toPath(), config, graphQL, dataFetcherContext);
graphQLRpcHttpService.start().join();
return graphQLRpcHttpService;
}
}

@ -0,0 +1,156 @@
/*
* Copyright 2018 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;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import tech.pegasys.pantheon.ethereum.blockcreation.EthHashMiningCoordinator;
import tech.pegasys.pantheon.ethereum.core.Synchronizer;
import tech.pegasys.pantheon.ethereum.eth.EthProtocol;
import tech.pegasys.pantheon.ethereum.eth.transactions.TransactionPool;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.BlockchainQuery;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import graphql.GraphQL;
import io.vertx.core.Vertx;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
public class GraphQLRpcHttpServiceHostWhitelistTest {
@ClassRule public static final TemporaryFolder folder = new TemporaryFolder();
protected static Vertx vertx;
private static GraphQLRpcHttpService service;
private static OkHttpClient client;
private static String baseUrl;
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private final GraphQLRpcConfiguration graphQLRpcConfig = createGraphQLRpcConfig();
private final List<String> hostsWhitelist = Arrays.asList("ally", "friend");
@Before
public void initServerAndClient() throws Exception {
vertx = Vertx.vertx();
service = createGraphQLRpcHttpService();
service.start().join();
client = new OkHttpClient();
baseUrl = service.url();
}
private GraphQLRpcHttpService createGraphQLRpcHttpService() throws Exception {
final BlockchainQuery blockchainQueries = mock(BlockchainQuery.class);
final Synchronizer synchronizer = mock(Synchronizer.class);
final EthHashMiningCoordinator miningCoordinatorMock = mock(EthHashMiningCoordinator.class);
final GraphQLDataFetcherContext dataFetcherContext = mock(GraphQLDataFetcherContext.class);
when(dataFetcherContext.getBlockchainQuery()).thenReturn(blockchainQueries);
when(dataFetcherContext.getMiningCoordinator()).thenReturn(miningCoordinatorMock);
when(dataFetcherContext.getTransactionPool()).thenReturn(mock(TransactionPool.class));
when(dataFetcherContext.getSynchronizer()).thenReturn(synchronizer);
final Set<Capability> supportedCapabilities = new HashSet<>();
supportedCapabilities.add(EthProtocol.ETH62);
supportedCapabilities.add(EthProtocol.ETH63);
final GraphQLDataFetchers dataFetchers = new GraphQLDataFetchers(supportedCapabilities);
final GraphQL graphQL = GraphQLProvider.buildGraphQL(dataFetchers);
return new GraphQLRpcHttpService(
vertx, folder.newFolder().toPath(), graphQLRpcConfig, graphQL, dataFetcherContext);
}
private static GraphQLRpcConfiguration createGraphQLRpcConfig() {
final GraphQLRpcConfiguration config = GraphQLRpcConfiguration.createDefault();
config.setPort(0);
return config;
}
@After
public void shutdownServer() {
client.dispatcher().executorService().shutdown();
client.connectionPool().evictAll();
service.stop().join();
vertx.close();
}
@Test
public void requestWithDefaultHeaderAndDefaultConfigIsAccepted() throws IOException {
assertThat(doRequest("localhost:50012")).isEqualTo(200);
}
@Test
public void requestWithEmptyHeaderAndDefaultConfigIsRejected() throws IOException {
assertThat(doRequest("")).isEqualTo(403);
}
@Test
public void requestWithAnyHostnameAndWildcardConfigIsAccepted() throws IOException {
graphQLRpcConfig.setHostsWhitelist(Collections.singletonList("*"));
assertThat(doRequest("ally")).isEqualTo(200);
assertThat(doRequest("foe")).isEqualTo(200);
}
@Test
public void requestWithWhitelistedHostIsAccepted() throws IOException {
graphQLRpcConfig.setHostsWhitelist(hostsWhitelist);
assertThat(doRequest("ally")).isEqualTo(200);
assertThat(doRequest("ally:12345")).isEqualTo(200);
assertThat(doRequest("friend")).isEqualTo(200);
}
@Test
public void requestWithUnknownHostIsRejected() throws IOException {
graphQLRpcConfig.setHostsWhitelist(hostsWhitelist);
assertThat(doRequest("foe")).isEqualTo(403);
}
private int doRequest(final String hostname) throws IOException {
final RequestBody body = RequestBody.create(JSON, "{protocolVersion}");
final Request build =
new Request.Builder()
.post(body)
.url(baseUrl + "/graphql")
.addHeader("Host", hostname)
.build();
return client.newCall(build).execute().code();
}
@Test
public void requestWithMalformedHostIsRejected() throws IOException {
graphQLRpcConfig.setHostsWhitelist(hostsWhitelist);
assertThat(doRequest("ally:friend")).isEqualTo(403);
assertThat(doRequest("ally:123456")).isEqualTo(403);
assertThat(doRequest("ally:friend:1234")).isEqualTo(403);
}
}

@ -0,0 +1,318 @@
/*
* Copyright 2018 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;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import tech.pegasys.pantheon.ethereum.blockcreation.EthHashMiningCoordinator;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.core.Synchronizer;
import tech.pegasys.pantheon.ethereum.core.Wei;
import tech.pegasys.pantheon.ethereum.eth.EthProtocol;
import tech.pegasys.pantheon.ethereum.eth.transactions.TransactionPool;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.BlockWithMetadata;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.BlockchainQuery;
import tech.pegasys.pantheon.ethereum.graphqlrpc.internal.TransactionWithMetadata;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.net.InetSocketAddress;
import java.net.URL;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import graphql.GraphQL;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
public class GraphQLRpcHttpServiceTest {
@ClassRule public static final TemporaryFolder folder = new TemporaryFolder();
private static final Vertx vertx = Vertx.vertx();
private static GraphQLRpcHttpService service;
private static OkHttpClient client;
private static String baseUrl;
protected static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private static BlockchainQuery blockchainQueries;
private static Synchronizer synchronizer;
private static GraphQL graphQL;
private static GraphQLDataFetchers dataFetchers;
private static GraphQLDataFetcherContext dataFetcherContext;
private static EthHashMiningCoordinator miningCoordinatorMock;
private final GraphQLRpcTestHelper testHelper = new GraphQLRpcTestHelper();
@BeforeClass
public static void initServerAndClient() throws Exception {
blockchainQueries = mock(BlockchainQuery.class);
synchronizer = mock(Synchronizer.class);
graphQL = mock(GraphQL.class);
miningCoordinatorMock = mock(EthHashMiningCoordinator.class);
dataFetcherContext = mock(GraphQLDataFetcherContext.class);
when(dataFetcherContext.getBlockchainQuery()).thenReturn(blockchainQueries);
when(dataFetcherContext.getMiningCoordinator()).thenReturn(miningCoordinatorMock);
when(dataFetcherContext.getTransactionPool()).thenReturn(mock(TransactionPool.class));
when(dataFetcherContext.getSynchronizer()).thenReturn(synchronizer);
final Set<Capability> supportedCapabilities = new HashSet<>();
supportedCapabilities.add(EthProtocol.ETH62);
supportedCapabilities.add(EthProtocol.ETH63);
dataFetchers = new GraphQLDataFetchers(supportedCapabilities);
graphQL = GraphQLProvider.buildGraphQL(dataFetchers);
service = createGraphQLRpcHttpService();
service.start().join();
// Build an OkHttp client.
client = new OkHttpClient();
baseUrl = service.url() + "/graphql/";
}
private static GraphQLRpcHttpService createGraphQLRpcHttpService(
final GraphQLRpcConfiguration config) throws Exception {
return new GraphQLRpcHttpService(
vertx, folder.newFolder().toPath(), config, graphQL, dataFetcherContext);
}
private static GraphQLRpcHttpService createGraphQLRpcHttpService() throws Exception {
return new GraphQLRpcHttpService(
vertx, folder.newFolder().toPath(), createGraphQLRpcConfig(), graphQL, dataFetcherContext);
}
private static GraphQLRpcConfiguration createGraphQLRpcConfig() {
final GraphQLRpcConfiguration config = GraphQLRpcConfiguration.createDefault();
config.setPort(0);
return config;
}
@BeforeClass
public static void setupConstants() {
final URL blocksUrl =
GraphQLRpcHttpServiceTest.class
.getClassLoader()
.getResource(
"tech/pegasys/pantheon/ethereum/graphqlrpc/graphQLRpcTestBlockchain.blocks");
final URL genesisJsonUrl =
GraphQLRpcHttpServiceTest.class
.getClassLoader()
.getResource("tech/pegasys/pantheon/ethereum/graphqlrpc/graphQLRpcTestGenesis.json");
assertThat(blocksUrl).isNotNull();
assertThat(genesisJsonUrl).isNotNull();
}
/** Tears down the HTTP server. */
@AfterClass
public static void shutdownServer() {
client.dispatcher().executorService().shutdown();
client.connectionPool().evictAll();
service.stop().join();
vertx.close();
}
@Test
public void invalidCallToStart() {
service
.start()
.whenComplete(
(unused, exception) -> assertThat(exception).isInstanceOf(IllegalStateException.class));
}
@Test
public void http404() throws Exception {
try (final Response resp = client.newCall(buildGetRequest("/foo")).execute()) {
assertThat(resp.code()).isEqualTo(404);
}
}
@Test
public void handleEmptyRequest() throws Exception {
try (final Response resp =
client.newCall(new Request.Builder().get().url(service.url()).build()).execute()) {
assertThat(resp.code()).isEqualTo(201);
}
}
@Test
public void handleInvalidQuerySchema() throws Exception {
final RequestBody body = RequestBody.create(JSON, "{gasPrice1}");
try (final Response resp = client.newCall(buildPostRequest(body)).execute()) {
// assertThat(resp.code()).isEqualTo(200); // Check general format of result
final JsonObject json = new JsonObject(resp.body().string());
testHelper.assertValidGraphQLRpcError(json); // Check result final
}
}
@Test
public void getGasprice() throws Exception {
final RequestBody body = RequestBody.create(JSON, "{gasPrice}");
final Wei price = Wei.of(16);
when(miningCoordinatorMock.getMinTransactionGasPrice()).thenReturn(price);
try (final Response resp = client.newCall(buildPostRequest(body)).execute()) {
assertThat(resp.code()).isEqualTo(200); // Check general format of result
final JsonObject json = new JsonObject(resp.body().string());
testHelper.assertValidGraphQLRpcResult(json);
final String result = json.getJsonObject("data").getString("gasPrice");
assertThat(result).isEqualTo("0x10");
}
}
@Test
public void getSocketAddressWhenActive() {
final InetSocketAddress socketAddress = service.socketAddress();
assertThat("127.0.0.1").isEqualTo(socketAddress.getAddress().getHostAddress());
assertThat(socketAddress.getPort() > 0).isTrue();
}
@Test
public void getSocketAddressWhenStoppedIsEmpty() throws Exception {
final GraphQLRpcHttpService service = createGraphQLRpcHttpService();
final InetSocketAddress socketAddress = service.socketAddress();
assertThat("0.0.0.0").isEqualTo(socketAddress.getAddress().getHostAddress());
assertThat(0).isEqualTo(socketAddress.getPort());
assertThat("").isEqualTo(service.url());
}
@Test
public void getSocketAddressWhenBindingToAllInterfaces() throws Exception {
final GraphQLRpcConfiguration config = createGraphQLRpcConfig();
config.setHost("0.0.0.0");
final GraphQLRpcHttpService service = createGraphQLRpcHttpService(config);
service.start().join();
try {
final InetSocketAddress socketAddress = service.socketAddress();
assertThat("0.0.0.0").isEqualTo(socketAddress.getAddress().getHostAddress());
assertThat(socketAddress.getPort() > 0).isTrue();
assertThat(!service.url().contains("0.0.0.0")).isTrue();
} finally {
service.stop().join();
}
}
@Test
public void responseContainsJsonContentTypeHeader() throws Exception {
final RequestBody body = RequestBody.create(JSON, "{gasPrice}");
try (final Response resp = client.newCall(buildPostRequest(body)).execute()) {
assertThat(resp.header("Content-Type")).isEqualTo("application/json");
}
}
@Test
public void ethGetUncleCountByBlockHash() throws Exception {
final int uncleCount = 4;
final Hash blockHash = Hash.hash(BytesValue.of(1));
@SuppressWarnings("unchecked")
final BlockWithMetadata<TransactionWithMetadata, Hash> block = mock(BlockWithMetadata.class);
@SuppressWarnings("unchecked")
final List<Hash> list = mock(List.class);
when(blockchainQueries.blockByHash(eq(blockHash))).thenReturn(Optional.of(block));
when(block.getOmmers()).thenReturn(list);
when(list.size()).thenReturn(uncleCount);
final String query = "{block(hash:\"" + blockHash.toString() + "\") {ommerCount}}";
final RequestBody body = RequestBody.create(JSON, query);
try (final Response resp = client.newCall(buildPostRequest(body)).execute()) {
assertThat(resp.code()).isEqualTo(200);
final String jsonStr = resp.body().string();
final JsonObject json = new JsonObject(jsonStr);
testHelper.assertValidGraphQLRpcResult(json);
final int result = json.getJsonObject("data").getJsonObject("block").getInteger("ommerCount");
assertThat(result).isEqualTo(uncleCount);
}
}
@Test
public void ethGetUncleCountByBlockNumber() throws Exception {
final int uncleCount = 5;
@SuppressWarnings("unchecked")
final BlockWithMetadata<TransactionWithMetadata, Hash> block = mock(BlockWithMetadata.class);
@SuppressWarnings("unchecked")
final List<Hash> list = mock(List.class);
when(blockchainQueries.blockByNumber(anyLong())).thenReturn(Optional.of(block));
when(block.getOmmers()).thenReturn(list);
when(list.size()).thenReturn(uncleCount);
final String query = "{block(number:\"3\") {ommerCount}}";
final RequestBody body = RequestBody.create(JSON, query);
try (final Response resp = client.newCall(buildPostRequest(body)).execute()) {
assertThat(resp.code()).isEqualTo(200);
final String jsonStr = resp.body().string();
final JsonObject json = new JsonObject(jsonStr);
testHelper.assertValidGraphQLRpcResult(json);
final int result = json.getJsonObject("data").getJsonObject("block").getInteger("ommerCount");
assertThat(result).isEqualTo(uncleCount);
}
}
@Test
public void ethGetUncleCountByBlockLatest() throws Exception {
final int uncleCount = 5;
@SuppressWarnings("unchecked")
final BlockWithMetadata<TransactionWithMetadata, Hash> block = mock(BlockWithMetadata.class);
@SuppressWarnings("unchecked")
final List<Hash> list = mock(List.class);
when(blockchainQueries.latestBlock()).thenReturn(Optional.of(block));
when(block.getOmmers()).thenReturn(list);
when(list.size()).thenReturn(uncleCount);
final String query = "{block {ommerCount}}";
final RequestBody body = RequestBody.create(JSON, query);
try (final Response resp = client.newCall(buildPostRequest(body)).execute()) {
assertThat(resp.code()).isEqualTo(200);
final String jsonStr = resp.body().string();
final JsonObject json = new JsonObject(jsonStr);
testHelper.assertValidGraphQLRpcResult(json);
final int result = json.getJsonObject("data").getJsonObject("block").getInteger("ommerCount");
assertThat(result).isEqualTo(uncleCount);
}
}
private Request buildPostRequest(final RequestBody body) {
return new Request.Builder().post(body).url(baseUrl).build();
}
private Request buildGetRequest(final String path) {
return new Request.Builder().get().url(baseUrl + path).build();
}
}

@ -0,0 +1,36 @@
/*
* Copyright 2018 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;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Set;
import io.vertx.core.json.JsonObject;
class GraphQLRpcTestHelper {
void assertValidGraphQLRpcResult(final JsonObject json) {
// Check all expected fieldnames are set
final Set<String> fieldNames = json.fieldNames();
assertThat(fieldNames.size()).isEqualTo(1);
assertThat(fieldNames.contains("data")).isTrue();
}
void assertValidGraphQLRpcError(final JsonObject json) {
// Check all expected fieldnames are set
final Set<String> fieldNames = json.fieldNames();
assertThat(fieldNames.size()).isEqualTo(1);
assertThat(fieldNames.contains("errors")).isTrue();
}
}

@ -0,0 +1,91 @@
/*
* 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.scalar;
import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.ethereum.core.Address;
import graphql.language.StringValue;
import graphql.schema.CoercingParseLiteralException;
import graphql.schema.CoercingParseValueException;
import graphql.schema.CoercingSerializeException;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class AddressScalarTest {
private AddressScalar scalar;
@Rule public ExpectedException thrown = ExpectedException.none();
private final String addrStr = "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f";
private final String invalidAddrStr = "0x295ee1b4f6dd65047762f924ecd367c17eabf8f";
private final Address addr = Address.fromHexString(addrStr);
private final StringValue addrValue = StringValue.newStringValue(addrStr).build();
private final StringValue invalidAddrValue = StringValue.newStringValue(invalidAddrStr).build();
@Test
public void pareValueTest() {
final String result = (String) scalar.getCoercing().parseValue(addr);
assertThat(result).isEqualTo(addrStr);
}
@Test
public void pareValueErrorTest() {
thrown.expect(CoercingParseValueException.class);
scalar.getCoercing().parseValue(addrStr);
}
@Test
public void serializeTest() {
final String result = (String) scalar.getCoercing().serialize(addr);
assertThat(result).isEqualTo(addrStr);
}
@Test
public void serializeErrorTest() {
thrown.expect(CoercingSerializeException.class);
scalar.getCoercing().serialize(addrStr);
}
@Test
public void pareLiteralTest() {
final Address result = (Address) scalar.getCoercing().parseLiteral(addrValue);
assertThat(result).isEqualTo(addr);
}
@Test
public void pareLiteralErrorTest() {
thrown.expect(CoercingParseLiteralException.class);
scalar.getCoercing().parseLiteral(addrStr);
}
@Test
public void pareLiteralErrorTest2() {
thrown.expect(CoercingParseLiteralException.class);
scalar.getCoercing().parseLiteral(invalidAddrValue);
}
@Before
public void before() {
scalar = new AddressScalar();
}
}

@ -0,0 +1,87 @@
/*
* 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.scalar;
import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.util.uint.UInt256;
import graphql.language.StringValue;
import graphql.schema.CoercingParseLiteralException;
import graphql.schema.CoercingParseValueException;
import graphql.schema.CoercingSerializeException;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class BigIntScalarTest {
private BigIntScalar scalar;
@Rule public ExpectedException thrown = ExpectedException.none();
private final String str = "0x10";
private final UInt256 value = UInt256.fromHexString(str);
private final StringValue strValue = StringValue.newStringValue(str).build();
private final StringValue invalidStrValue = StringValue.newStringValue("0xgh").build();
@Test
public void pareValueTest() {
final String result = (String) scalar.getCoercing().parseValue(value);
assertThat(result).isEqualTo(str);
}
@Test
public void pareValueErrorTest() {
thrown.expect(CoercingParseValueException.class);
scalar.getCoercing().parseValue(str);
}
@Test
public void serializeTest() {
final String result = (String) scalar.getCoercing().serialize(value);
assertThat(result).isEqualTo(str);
}
@Test
public void serializeErrorTest() {
thrown.expect(CoercingSerializeException.class);
scalar.getCoercing().serialize(str);
}
@Test
public void pareLiteralTest() {
final UInt256 result = (UInt256) scalar.getCoercing().parseLiteral(strValue);
assertThat(result).isEqualTo(value);
}
@Test
public void pareLiteralErrorTest() {
thrown.expect(CoercingParseLiteralException.class);
scalar.getCoercing().parseLiteral(str);
}
@Test
public void pareLiteralErrorTest2() {
thrown.expect(CoercingParseLiteralException.class);
scalar.getCoercing().parseLiteral(invalidStrValue);
}
@Before
public void before() {
scalar = new BigIntScalar();
}
}

@ -0,0 +1,87 @@
/*
* 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.scalar;
import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.util.bytes.Bytes32;
import graphql.language.StringValue;
import graphql.schema.CoercingParseLiteralException;
import graphql.schema.CoercingParseValueException;
import graphql.schema.CoercingSerializeException;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class Bytes32ScalarTest {
private Bytes32Scalar scalar;
@Rule public ExpectedException thrown = ExpectedException.none();
private final String str = "0x1234567812345678123456781234567812345678123456781234567812345678";
private final Bytes32 value = Bytes32.fromHexString(str);
private final StringValue strValue = StringValue.newStringValue(str).build();
private final StringValue invalidStrValue = StringValue.newStringValue("0xgh").build();
@Test
public void pareValueTest() {
final String result = (String) scalar.getCoercing().parseValue(value);
assertThat(result).isEqualTo(str);
}
@Test
public void pareValueErrorTest() {
thrown.expect(CoercingParseValueException.class);
scalar.getCoercing().parseValue(str);
}
@Test
public void serializeTest() {
final String result = (String) scalar.getCoercing().serialize(value);
assertThat(result).isEqualTo(str);
}
@Test
public void serializeErrorTest() {
thrown.expect(CoercingSerializeException.class);
scalar.getCoercing().serialize(str);
}
@Test
public void pareLiteralTest() {
final Bytes32 result = (Bytes32) scalar.getCoercing().parseLiteral(strValue);
assertThat(result).isEqualTo(value);
}
@Test
public void pareLiteralErrorTest() {
thrown.expect(CoercingParseLiteralException.class);
scalar.getCoercing().parseLiteral(str);
}
@Test
public void pareLiteralErrorTest2() {
thrown.expect(CoercingParseLiteralException.class);
scalar.getCoercing().parseLiteral(invalidStrValue);
}
@Before
public void before() {
scalar = new Bytes32Scalar();
}
}

@ -0,0 +1,87 @@
/*
* 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.scalar;
import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import graphql.language.StringValue;
import graphql.schema.CoercingParseLiteralException;
import graphql.schema.CoercingParseValueException;
import graphql.schema.CoercingSerializeException;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class BytesScalarTest {
private BytesScalar scalar;
@Rule public ExpectedException thrown = ExpectedException.none();
private final String str = "0x10";
private final BytesValue value = BytesValue.fromHexString(str);
private final StringValue strValue = StringValue.newStringValue(str).build();
private final StringValue invalidStrValue = StringValue.newStringValue("0xgh").build();
@Test
public void pareValueTest() {
final String result = (String) scalar.getCoercing().parseValue(value);
assertThat(result).isEqualTo(str);
}
@Test
public void pareValueErrorTest() {
thrown.expect(CoercingParseValueException.class);
scalar.getCoercing().parseValue(str);
}
@Test
public void serializeTest() {
final String result = (String) scalar.getCoercing().serialize(value);
assertThat(result).isEqualTo(str);
}
@Test
public void serializeErrorTest() {
thrown.expect(CoercingSerializeException.class);
scalar.getCoercing().serialize(str);
}
@Test
public void pareLiteralTest() {
final BytesValue result = (BytesValue) scalar.getCoercing().parseLiteral(strValue);
assertThat(result).isEqualTo(value);
}
@Test
public void pareLiteralErrorTest() {
thrown.expect(CoercingParseLiteralException.class);
scalar.getCoercing().parseLiteral(str);
}
@Test
public void pareLiteralErrorTest2() {
thrown.expect(CoercingParseLiteralException.class);
scalar.getCoercing().parseLiteral(invalidStrValue);
}
@Before
public void before() {
scalar = new BytesScalar();
}
}

@ -0,0 +1,93 @@
/*
* 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.scalar;
import static org.assertj.core.api.Assertions.assertThat;
import graphql.language.StringValue;
import graphql.schema.CoercingParseLiteralException;
import graphql.schema.CoercingParseValueException;
import graphql.schema.CoercingSerializeException;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class LongScalarTest {
private LongScalar scalar;
@Rule public ExpectedException thrown = ExpectedException.none();
private final String str = "0xf4240";
private final Long value = Long.decode(str);
private final StringValue strValue = StringValue.newStringValue(str).build();
private final StringValue invalidStrValue = StringValue.newStringValue("gh").build();
@Test
public void parseLongValueTest() {
assertThat(scalar.getCoercing().parseValue(value)).isEqualTo(value);
}
@Test
public void parseStringValueTest() {
assertThat(scalar.getCoercing().parseValue(str)).isEqualTo(value);
}
@Test
public void pareValueErrorTest() {
thrown.expect(CoercingParseValueException.class);
scalar.getCoercing().parseValue(invalidStrValue);
}
@Test
public void serializeLongTest() {
assertThat(scalar.getCoercing().serialize(value)).isEqualTo(value);
}
@Test
public void serializeStringTest() {
assertThat(scalar.getCoercing().serialize(str)).isEqualTo(value);
}
@Test
public void serializeErrorTest() {
thrown.expect(CoercingSerializeException.class);
scalar.getCoercing().serialize(invalidStrValue);
}
@Test
public void pareLiteralTest() {
final Long result = (Long) scalar.getCoercing().parseLiteral(strValue);
assertThat(result).isEqualTo(value);
}
@Test
public void pareLiteralErrorTest() {
thrown.expect(CoercingParseLiteralException.class);
scalar.getCoercing().parseLiteral(str);
}
@Test
public void pareLiteralErrorTest2() {
thrown.expect(CoercingParseLiteralException.class);
scalar.getCoercing().parseLiteral(invalidStrValue);
}
@Before
public void before() {
scalar = new LongScalar();
}
}

@ -0,0 +1,13 @@
{
"request":
"{ block { number } }",
"response": {
"data" : {
"block" : {
"number" : 32
}
}
},
"statusCode": 200
}

@ -0,0 +1,16 @@
{
"request": "{block(number :\"0x8\") {number call (data : {from : \"a94f5374fce5edbc8e2a8697c15331677e6ebf0b\", to: \"0x6295ee1b4f6dd65047762f924ecd367c17eabf8f\", data :\"0x12a7b914\"}){data status}}}"
,
"response":{
"data" : {
"block" : {
"number" : 8,
"call" : {
"data" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"status" : 1
}
}
}
},
"statusCode": 200
}

@ -0,0 +1,16 @@
{
"request": "{block {number call (data : {from : \"a94f5374fce5edbc8e2a8697c15331677e6ebf0b\", to: \"0x6295ee1b4f6dd65047762f924ecd367c17eabf8f\", data :\"0x12a7b914\"}){data status}}}"
,
"response":{
"data" : {
"block" : {
"number" : 32,
"call" : {
"data" : "0x0000000000000000000000000000000000000000000000000000000000000001",
"status" : 1
}
}
}
},
"statusCode": 200
}

@ -0,0 +1,12 @@
{
"request" :"{block{estimateGas (data: {from :\"0x6295ee1b4f6dd65047762f924ecd367c17eabf8f\", data :\"0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029\"})}}",
"response":{
"data" : {
"block" : {
"estimateGas" : 111953
}
}
},
"statusCode": 200
}

@ -0,0 +1,12 @@
{
"request" :"{block{ estimateGas(data:{}) }}",
"response":{
"data" : {
"block" : {
"estimateGas" : 21000
}
}
},
"statusCode": 200
}

@ -0,0 +1,11 @@
{
"request" :"{block{estimateGas (data: {from :\"0x6295ee1b4f6dd65047762f924ecd367c17eabf8f\", to :\"0x8888f1f195afa192cfee860698584c030f4c9db1\"})}}",
"response":{
"data" : {
"block" : {
"estimateGas" : 21000
}
}
},
"statusCode": 200
}

@ -0,0 +1,11 @@
{
"request":
"{ gasPrice }",
"response": {
"data" : {
"gasPrice" : "0x10"
}
},
"statusCode": 200
}

@ -0,0 +1,12 @@
{
"request": "{account(blockNumber:\"0x19\", address: \"0x6295ee1b4f6dd65047762f924ecd367c17eabf8f\") { balance } }",
"response": {
"data" : {
"account" : {
"balance" : "0xfa"
}
}
},
"statusCode": 200
}

@ -0,0 +1,12 @@
{
"request": "{account(address: \"0x6295ee1b4f6dd65047762f924ecd367c17eabf8f\") { balance } }",
"response": {
"data" : {
"account" : {
"balance" : "0x140"
}
}
},
"statusCode": 200
}

@ -0,0 +1,20 @@
{
"request": "{account(blockNumber:\"0x21\", address: \"0x6295ee1b4f6dd65047762f924ecd367c17eabf8f\") { balance } }",
"response": {
"data" : null,
"errors" : [ {
"message" : "Exception while fetching data (/account) : Invalid params",
"locations" : [ {
"line" : 1,
"column" : 2
} ],
"path" : [ "account" ],
"extensions" : {
"errorCode" : -32602,
"errorMessage" : "Invalid params"
}
} ]
},
"statusCode": 400
}

@ -0,0 +1,5 @@
{
"request": "{account(address: \"0x8895ee1b4f6dd65047762f924ecd367c17eabf8f\") { balance } }",
"statusCode": 400
}

@ -0,0 +1,34 @@
{
"request":
"{block (hash : \"0xc8df1f061abb4d0c107b2b1a794ade8780b3120e681f723fe55a7be586d95ba6\") {number transactions{hash} timestamp difficulty totalDifficulty gasUsed gasLimit hash nonce ommerCount logsBloom mixHash ommerHash extraData stateRoot receiptsRoot transactionCount transactionsRoot}} ",
"response": {
"data" : {
"block" : {
"number" : 30,
"transactions" : [ {
"hash" : "0x9cc6c7e602c56aa30c554bb691377f8703d778cec8845f4b88c0f72516b304f4"
} ],
"timestamp" : "0x561bc336",
"difficulty" : "0x20740",
"totalDifficulty" : "0x3e6cc0",
"gasUsed" : 23585,
"gasLimit" : 3141592,
"hash" : "0xc8df1f061abb4d0c107b2b1a794ade8780b3120e681f723fe55a7be586d95ba6",
"nonce" : "0x5c321bd9e9f040f1",
"ommerCount" : 0,
"logsBloom" : "0x00000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000080000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000400000000000000000200000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000800000000040000000000000000000000000000000000000000010000000000000000000000000",
"mixHash" : "0x6ce1c4afb4f85fefd1b0ed966b20cd248f08d9a5b0df773f75c6c2f5cc237b7c",
"ommerHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"extraData" : "0x",
"stateRoot" : "0xdb46d6bb168130fe2cb60b4b24346137b5741f11283e0d7edace65c5f5466b2e",
"receiptsRoot" : "0x88b3b304b058b39791c26fdb94a05cc16ce67cf8f84f7348cb3c60c0ff342d0d",
"transactionCount" : 1,
"transactionsRoot" : "0x5a8d5d966b48e1331ae19eb459eb28882cdc7654e615d37774b79204e875dc01"
}
}
},
"statusCode": 200
}

@ -0,0 +1,44 @@
{
"request":
"{block (number : \"0x1e\") {transactions{hash} timestamp difficulty totalDifficulty gasUsed gasLimit hash nonce ommerCount logsBloom mixHash ommerHash extraData stateRoot receiptsRoot transactionCount transactionsRoot ommers{hash} ommerAt(index : 1){hash} miner{address} account(address: \"0x6295ee1b4f6dd65047762f924ecd367c17eabf8f\"){balance} parent{hash} }} ",
"response":{
"data" : {
"block" : {
"transactions" : [ {
"hash" : "0x9cc6c7e602c56aa30c554bb691377f8703d778cec8845f4b88c0f72516b304f4"
} ],
"timestamp" : "0x561bc336",
"difficulty" : "0x20740",
"totalDifficulty" : "0x3e6cc0",
"gasUsed" : 23585,
"gasLimit" : 3141592,
"hash" : "0xc8df1f061abb4d0c107b2b1a794ade8780b3120e681f723fe55a7be586d95ba6",
"nonce" : "0x5c321bd9e9f040f1",
"ommerCount" : 0,
"logsBloom" : "0x00000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000080000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000400000000000000000200000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000800000000040000000000000000000000000000000000000000010000000000000000000000000",
"mixHash" : "0x6ce1c4afb4f85fefd1b0ed966b20cd248f08d9a5b0df773f75c6c2f5cc237b7c",
"ommerHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"extraData" : "0x",
"stateRoot" : "0xdb46d6bb168130fe2cb60b4b24346137b5741f11283e0d7edace65c5f5466b2e",
"receiptsRoot" : "0x88b3b304b058b39791c26fdb94a05cc16ce67cf8f84f7348cb3c60c0ff342d0d",
"transactionCount" : 1,
"transactionsRoot" : "0x5a8d5d966b48e1331ae19eb459eb28882cdc7654e615d37774b79204e875dc01",
"ommers" : [ ],
"ommerAt" : null,
"miner" : {
"address" : "0x8888f1f195afa192cfee860698584c030f4c9db1"
},
"account" : {
"balance" : "0x12c"
},
"parent" : {
"hash" : "0xf8cfa377bd766cdf22edb388dd08cc149e85d24f2796678c835f3c54ab930803"
}
}
}
},
"statusCode": 200
}

@ -0,0 +1,15 @@
{
"request":
"{block (hash : \"0xc8df1f061abb4d0c107b2b1a794ade8780b3120e681f723fe55a7be586d95ba6\") {transactionCount}} ",
"response": {
"data" : {
"block" : {
"transactionCount" : 1
}
}
},
"statusCode": 200
}

@ -0,0 +1,33 @@
{
"request":
"{block (number : \"0x1e\") {transactions{hash} timestamp difficulty totalDifficulty gasUsed gasLimit hash nonce ommerCount logsBloom mixHash ommerHash extraData stateRoot receiptsRoot transactionCount transactionsRoot}} ",
"response": {
"data" : {
"block" : {
"transactions" : [ {
"hash" : "0x9cc6c7e602c56aa30c554bb691377f8703d778cec8845f4b88c0f72516b304f4"
} ],
"timestamp" : "0x561bc336",
"difficulty" : "0x20740",
"totalDifficulty" : "0x3e6cc0",
"gasUsed" : 23585,
"gasLimit" : 3141592,
"hash" : "0xc8df1f061abb4d0c107b2b1a794ade8780b3120e681f723fe55a7be586d95ba6",
"nonce" : "0x5c321bd9e9f040f1",
"ommerCount" : 0,
"logsBloom" : "0x00000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000080000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000400000000000000000200000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000800000000040000000000000000000000000000000000000000010000000000000000000000000",
"mixHash" : "0x6ce1c4afb4f85fefd1b0ed966b20cd248f08d9a5b0df773f75c6c2f5cc237b7c",
"ommerHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"extraData" : "0x",
"stateRoot" : "0xdb46d6bb168130fe2cb60b4b24346137b5741f11283e0d7edace65c5f5466b2e",
"receiptsRoot" : "0x88b3b304b058b39791c26fdb94a05cc16ce67cf8f84f7348cb3c60c0ff342d0d",
"transactionCount" : 1,
"transactionsRoot" : "0x5a8d5d966b48e1331ae19eb459eb28882cdc7654e615d37774b79204e875dc01"
}
}
},
"statusCode": 200
}

@ -0,0 +1,7 @@
{
"request":
"{block (number: \"0x03\", hash : \"0xc8df1f061abb4d0c107b2b1a794ade8780b3120e681f723fe55a7be586d95ba6\") {number transactions{hash} timestamp difficulty totalDifficulty gasUsed gasLimit hash nonce ommerCount logsBloom mixHash ommerHash extraData stateRoot receiptsRoot transactionCount transactionsRoot}} ",
"statusCode": 400
}

@ -0,0 +1,40 @@
{
"request":
"{blocks (from : \"0x1e\", to: \"0x20\") { number gasUsed gasLimit hash nonce stateRoot receiptsRoot transactionCount }} ",
"response":{
"data" : {
"blocks" : [ {
"number" : 30,
"gasUsed" : 23585,
"gasLimit" : 3141592,
"hash" : "0xc8df1f061abb4d0c107b2b1a794ade8780b3120e681f723fe55a7be586d95ba6",
"nonce" : "0x5c321bd9e9f040f1",
"stateRoot" : "0xdb46d6bb168130fe2cb60b4b24346137b5741f11283e0d7edace65c5f5466b2e",
"receiptsRoot" : "0x88b3b304b058b39791c26fdb94a05cc16ce67cf8f84f7348cb3c60c0ff342d0d",
"transactionCount" : 1
}, {
"number" : 31,
"gasUsed" : 24303,
"gasLimit" : 3141592,
"hash" : "0x0f765087745aa259d9e5ac39c367c57432a16ed98e3b0d81c5b51d10f301dc49",
"nonce" : "0xd3a27a3001616468",
"stateRoot" : "0xa80997cf804269d64f2479baf535cf8f9090b70fbf515741c6995564f1e678bd",
"receiptsRoot" : "0x2440c44a3f75ad8b0425a73e7be2f61a5171112465cfd14e62e735b56d7178e6",
"transactionCount" : 1
}, {
"number" : 32,
"gasUsed" : 23705,
"gasLimit" : 3141592,
"hash" : "0x71d59849ddd98543bdfbe8548f5eed559b07b8aaf196369f39134500eab68e53",
"nonce" : "0xdb063000b00e8026",
"stateRoot" : "0xf65f3dd13f72f5fa5607a5224691419969b4f4bae7a00a6cdb853f2ca9eeb1be",
"receiptsRoot" : "0xa50a7e67e833f4502524371ee462ccbcc6c6cabd2aeb1555c56150007a53183c",
"transactionCount" : 1
} ]
}
},
"statusCode": 200
}

@ -0,0 +1,15 @@
{
"request":
"{blocks (from : \"0x1e\", to: \"0x1c\") { number gasUsed gasLimit hash nonce stateRoot receiptsRoot transactionCount }} ",
"response": {
"data":null,
"errors":
[{"message":"Exception while fetching data (/blocks) : Invalid params",
"locations":[{"line":1,"column":2}],"path":["blocks"],
"extensions":{"errorCode":-32602,"errorMessage":"Invalid params"}}]
},
"statusCode": 400
}

@ -0,0 +1,13 @@
{
"request" : "{ account(address: \"0x6295ee1b4f6dd65047762f924ecd367c17eabf8f\") { code } }",
"response": {
"data" : {
"account" :{
"code" :"0x6000357c010000000000000000000000000000000000000000000000000000000090048063102accc11461012c57806312a7b9141461013a5780631774e6461461014c5780631e26fd331461015d5780631f9030371461016e578063343a875d1461018057806338cc4831146101955780634e7ad367146101bd57806357cb2fc4146101cb57806365538c73146101e057806368895979146101ee57806376bc21d9146102005780639a19a9531461020e5780639dc2c8f51461021f578063a53b1c1e1461022d578063a67808571461023e578063b61c05031461024c578063c2b12a731461025a578063d2282dc51461026b578063e30081a01461027c578063e8beef5b1461028d578063f38b06001461029b578063f5b53e17146102a9578063fd408767146102bb57005b6101346104d6565b60006000f35b61014261039b565b8060005260206000f35b610157600435610326565b60006000f35b6101686004356102c9565b60006000f35b610176610442565b8060005260206000f35b6101886103d3565b8060ff1660005260206000f35b61019d610413565b8073ffffffffffffffffffffffffffffffffffffffff1660005260206000f35b6101c56104c5565b60006000f35b6101d36103b7565b8060000b60005260206000f35b6101e8610454565b60006000f35b6101f6610401565b8060005260206000f35b61020861051f565b60006000f35b6102196004356102e5565b60006000f35b610227610693565b60006000f35b610238600435610342565b60006000f35b610246610484565b60006000f35b610254610493565b60006000f35b61026560043561038d565b60006000f35b610276600435610350565b60006000f35b61028760043561035e565b60006000f35b6102956105b4565b60006000f35b6102a3610547565b60006000f35b6102b16103ef565b8060005260206000f35b6102c3610600565b60006000f35b80600060006101000a81548160ff021916908302179055505b50565b80600060016101000a81548160ff02191690837f01000000000000000000000000000000000000000000000000000000000000009081020402179055505b50565b80600060026101000a81548160ff021916908302179055505b50565b806001600050819055505b50565b806002600050819055505b50565b80600360006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908302179055505b50565b806004600050819055505b50565b6000600060009054906101000a900460ff1690506103b4565b90565b6000600060019054906101000a900460000b90506103d0565b90565b6000600060029054906101000a900460ff1690506103ec565b90565b600060016000505490506103fe565b90565b60006002600050549050610410565b90565b6000600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905061043f565b90565b60006004600050549050610451565b90565b7f65c9ac8011e286e89d02a269890f41d67ca2cc597b2c76c7c69321ff492be5806000602a81526020016000a15b565b6000602a81526020016000a05b565b60017f81933b308056e7e85668661dcd102b1f22795b4431f9cf4625794f381c271c6b6000602a81526020016000a25b565b60016000602a81526020016000a15b565b3373ffffffffffffffffffffffffffffffffffffffff1660017f0e216b62efbb97e751a2ce09f607048751720397ecfb9eef1e48a6644948985b6000602a81526020016000a35b565b3373ffffffffffffffffffffffffffffffffffffffff1660016000602a81526020016000a25b565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6001023373ffffffffffffffffffffffffffffffffffffffff1660017f317b31292193c2a4f561cc40a95ea0d97a2733f14af6d6d59522473e1f3ae65f6000602a81526020016000a45b565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6001023373ffffffffffffffffffffffffffffffffffffffff1660016000602a81526020016000a35b565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6001023373ffffffffffffffffffffffffffffffffffffffff1660017fd5f0a30e4be0c6be577a71eceb7464245a796a7e6a55c0d971837b250de05f4e60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe98152602001602a81526020016000a45b565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6001023373ffffffffffffffffffffffffffffffffffffffff16600160007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe98152602001602a81526020016000a35b56"
}
}
},
"statusCode": 200
}

@ -0,0 +1,14 @@
{
"request" : "{ account(address: \"0x8888f1f195afa192cfee860698584c030f4c9db1\") { code } }",
"response": {
"data" : {
"account" :{
"code" :"0x"
}
}
},
"statusCode": 200
}

@ -0,0 +1,22 @@
{
"request": "{ block(number: \"0x17\") { logs( filter: { topics : [[\"0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b\", \"0x65c9ac8011e286e89d02a269890f41d67ca2cc597b2c76c7c69321ff492be580\"]]}) { index topics data account{address} transaction{hash} } } }",
"response": {
"data" : {
"block" : {
"logs" : [ {
"index" : 0,
"topics" : [ "0x65c9ac8011e286e89d02a269890f41d67ca2cc597b2c76c7c69321ff492be580" ],
"data" : "0x000000000000000000000000000000000000000000000000000000000000002a",
"account" : {
"address" : "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f"
},
"transaction" : {
"hash" : "0x97a385bf570ced7821c6495b3877ddd2afd5c452f350f0d4876e98d9161389c6"
}
} ]
}
}
},
"statusCode": 200
}

@ -0,0 +1,13 @@
{
"request" :"{ account(address: \"0x6295ee1b4f6dd65047762f924ecd367c17eabf8f\") { storage(slot: \"0x00000000000000000000000000000004\") } }",
"response": {
"data" : {
"account" :{
"storage" :"0xaabbccffffffffffffffffffffffffffffffffffffffffffffffffffffffffee"
}
}
},
"statusCode": 200
}

@ -0,0 +1,13 @@
{
"request" :"{ account(address: \"0x6295ee1b4f6dd65047762f924ecd367c17eabf8f\") { storage(slot: \"0x00000000000000000000000000000021\") } }",
"response":{
"data" : {
"account" : {
"storage" : "0x0000000000000000000000000000000000000000000000000000000000000000"
}
}
},
"statusCode": 200
}

@ -0,0 +1,20 @@
{
"request":
"{ block(hash: \"0xc8df1f061abb4d0c107b2b1a794ade8780b3120e681f723fe55a7be586d95ba6\") { transactionAt(index: 0) {block{hash} hash } } }",
"response":{
"data" : {
"block" : {
"transactionAt" : {
"block" : {
"hash" : "0xc8df1f061abb4d0c107b2b1a794ade8780b3120e681f723fe55a7be586d95ba6"
},
"hash" : "0x9cc6c7e602c56aa30c554bb691377f8703d778cec8845f4b88c0f72516b304f4"
}
}
}
},
"statusCode": 200
}

@ -0,0 +1,20 @@
{
"request":
"{ block(number: \"0x1e\") { transactionAt(index: 0) {block{hash} hash} } }",
"response":{
"data" : {
"block" : {
"transactionAt" : {
"block" : {
"hash" : "0xc8df1f061abb4d0c107b2b1a794ade8780b3120e681f723fe55a7be586d95ba6"
},
"hash" : "0x9cc6c7e602c56aa30c554bb691377f8703d778cec8845f4b88c0f72516b304f4"
}
}
}
},
"statusCode": 200
}

@ -0,0 +1,14 @@
{
"request":
"{ block(number: \"0x1e\") { transactionAt(index: 1) {block{hash} hash} } }",
"response":{
"data" : {
"block" : {
"transactionAt" : null
}
}
},
"statusCode": 200
}

@ -0,0 +1,32 @@
{
"request":
"{transaction (hash : \"0x9cc6c7e602c56aa30c554bb691377f8703d778cec8845f4b88c0f72516b304f4\") { block{hash} gas gasPrice hash inputData nonce index value from {address} to {address} logs{index} status createdContract{address} } } ",
"response": {
"data" : {
"transaction" : {
"block" : {
"hash" : "0xc8df1f061abb4d0c107b2b1a794ade8780b3120e681f723fe55a7be586d95ba6"
},
"gas" : 314159,
"gasPrice" : "0x1",
"hash" : "0x9cc6c7e602c56aa30c554bb691377f8703d778cec8845f4b88c0f72516b304f4",
"inputData" : "0xe8beef5b",
"nonce" : 29,
"index" : 0,
"value" : "0xa",
"from" : {
"address" : "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"
},
"to" : {
"address" : "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f"
},
"logs" : [ {
"index" : 0
} ],
"status" : null,
"createdContract" : null
}
}
},
"statusCode": 200
}

@ -0,0 +1,13 @@
{
"request":
"{transaction (hash : \"0xffc6c7e602c56aa30c554bb691377f8703d778cec8845f4b88c0f72516b304f4\") { block{hash} gas gasPrice hash inputData nonce index value }} ",
"response": {
"data" : {
"transaction" : null
}
},
"statusCode": 200
}

@ -0,0 +1,13 @@
{
"request" :"{ account(address: \"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b\") { transactionCount } }",
"response": {
"data" : {
"account" :{
"transactionCount" : 32
}
}
},
"statusCode": 200
}

@ -0,0 +1,27 @@
{
"request" : "{ transaction(hash: \"0x812742182a79a8e67733edc58cfa3767aa2d7ad06439d156ddbbb33e3403b4ed\") {block{hash logsBloom} hash createdContract{address} cumulativeGasUsed gas gasUsed logs{topics} from{address} to{address} index } }",
"response":{
"data" : {
"transaction" : {
"block" : {
"hash" : "0x10aaf14a53caf27552325374429d3558398a36d3682ede6603c2c6511896e9f9",
"logsBloom" : "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
},
"hash" : "0x812742182a79a8e67733edc58cfa3767aa2d7ad06439d156ddbbb33e3403b4ed",
"createdContract" : null,
"cumulativeGasUsed" : 493172,
"gas" : 3141592,
"gasUsed" : 493172,
"logs" : [ ],
"from" : {
"address" : "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"
},
"to" : null,
"index" : 0
}
}
},
"statusCode": 200
}

@ -0,0 +1,9 @@
{
"request" : "mutation { sendRawTransaction(data: \"0xf901ca0685174876e800830fffff8080b90177608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb00291ca00297f7489c9e70447d917f7069a145c9fd0543633bec0a17ac072f1e07ab7f24a0185bd6435c17603b85fd84b8b45605988e855238fe2bbc6ea1f7e9ee6a5fc15f\") }",
"response":{
"data" : {
"sendRawTransaction" : "0x84df486b376e7eaf35792d710fc38ce110e62ab9cdb73a45d191da74c2190617"
}
},
"statusCode": 200
}

@ -0,0 +1,10 @@
{
"request" : "mutation { sendRawTransaction(data: \"0xf8690885174876e800830fffff94450b61224a7df4d8a70f3e20d4fd6a6380b920d180843bdab8bf1ba0efcd6b9df2054a4e8599c0967f8e1e45bca79e2998ed7e8bafb4d29aba7dd5c2a01097184ba24f20dc097f1915fbb5f6ac955bbfc014f181df4d80bf04f4a1cfa5\") }",
"response":{
"data" : {
"sendRawTransaction" : "0xaa6e6646456c576edcd712dbb3f30bf46c3d8310b203960c1e675534553b2daf"
}
},
"statusCode": 200
}

@ -0,0 +1,9 @@
{
"request" : "mutation { sendRawTransaction(data: \"0xf86d0485174876e800830222e0945aae326516b4f8fe08074b7e972e40a713048d62880de0b6b3a7640000801ba05d4e7998757264daab67df2ce6f7e7a0ae36910778a406ca73898c9899a32b9ea0674700d5c3d1d27f2e6b4469957dfd1a1c49bf92383d80717afc84eb05695d5b\") }",
"response":{
"data" : {
"sendRawTransaction" : "0xbaabcc1bd699e7378451e4ce5969edb9bdcae76cb79bdacae793525c31e423c7"
}
},
"statusCode": 200
}

@ -0,0 +1,19 @@
{
"request" : "mutation { sendRawTransaction(data: \"0xed0a85174876e800830222e0945aae326516b4f8fe08074b7e972e40a713048d62880de0b6b3a7640000801c8080\") }",
"response":{
"data" : null,
"errors" : [ {
"message" : "Exception while fetching data (/sendRawTransaction) : Invalid params",
"locations" : [ {
"line" : 1,
"column" : 12
} ],
"path" : [ "sendRawTransaction" ],
"extensions" : {
"errorCode" : -32602,
"errorMessage" : "Invalid params"
}
} ]
},
"statusCode": 400
}

@ -0,0 +1,17 @@
{
"request":
"{ syncing {startingBlock currentBlock highestBlock pulledStates knownStates } }",
"response": {
"data" : {
"syncing" : {
"startingBlock" : 1,
"currentBlock" : 2,
"highestBlock" : 3,
"pulledStates" : null,
"knownStates" : null
}
}
},
"statusCode": 200
}

@ -0,0 +1,20 @@
{
"config": {
"chainId": 1,
"ethash": {
}
},
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase" : "0x8888f1f195afa192cfee860698584c030f4c9db1",
"difficulty" : "0x020000",
"gasLimit" : "0x2fefd8",
"timestamp" : "0x54c98c81",
"extraData" : "0x42",
"mixHash" : "0x2c85bcbce56429100b2108254bb56906257582aeafcbd682bc9af67a9f5aee46",
"nonce" : "0x78cc16f7b4f65485",
"alloc" : {
"a94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
"balance" : "0x09184e72a000"
}
}
}

@ -42,7 +42,7 @@ dependencies {
implementation 'io.vertx:vertx-web'
implementation 'net.consensys.cava:cava-toml'
implementation 'org.springframework.security:spring-security-crypto'
implementation 'io.vertx:vertx-auth-jwt:3.6.2'
implementation 'io.vertx:vertx-auth-jwt'
testImplementation project(':config')
testImplementation project(path: ':config', configuration: 'testSupportArtifacts')

@ -237,7 +237,6 @@ public class EthJsonRpcHttpBySpecTest extends AbstractEthJsonRpcHttpServiceTest
return specs.values();
}
// @formatter:on
@Test
public void jsonRPCCallWithSpecFile() throws Exception {

@ -42,7 +42,8 @@ ext.acceptedLicenses = [
'Mozilla Public License Version 2.0',
'CC0 1.0 Universal License',
'Common Development and Distribution License 1.0',
'Unicode/ICU License'
'Unicode/ICU License',
'Public Domain (CC0) License 1.0'
]*.toLowerCase()
/**
@ -64,6 +65,7 @@ downloadLicenses {
ext.mpl2_0 = license('Mozilla Public License, Version 2.0', 'http://www.mozilla.org/MPL/2.0/')
ext.cddl = license('Common Development and Distribution License 1.0', 'http://opensource.org/licenses/CDDL-1.0')
ext.cddl1_1 = license('Common Development and Distribution License 1.0', 'http://oss.oracle.com/licenses/CDDL-1.1')
ext.cc0 = license('Public Domain (CC0) License 1.0', 'https://creativecommons.org/publicdomain/zero/1.0')
aliases = [
(apache) : [
'The Apache Software License, Version 2.0',
@ -79,7 +81,7 @@ downloadLicenses {
license('Apache Software Licenses', 'http://www.apache.org/licenses/LICENSE-2.0.txt'),
license('Apache', 'http://www.opensource.org/licenses/Apache-2.0')
],
(mit) : ['The MIT License'],
(mit) : ['The MIT License', 'MIT'],
(bsd) : [
'BSD',
'BSD licence',
@ -115,11 +117,11 @@ downloadLicenses {
'CDDL + GPLv2 with classpath exception',
'Dual license consisting of the CDDL v1.1 and GPL v2'
],
(cddl1_1): [
(cddl1_1): [
'CDDL 1.1',
'COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.1',
]
],
(cc0): ['Public Domain (CC0) License 1.0', 'CC0']
]
licenses = [

@ -25,6 +25,8 @@ dependencyManagement {
dependency 'com.google.errorprone:error_prone_annotation:2.3.3'
dependency 'com.google.errorprone:error_prone_test_helpers:2.3.3'
dependency 'com.graphql-java:graphql-java:11.0'
dependency 'com.google.guava:guava:27.1-jre'
dependency 'com.squareup.okhttp3:okhttp:3.13.1'

@ -36,6 +36,7 @@ dependencies {
implementation project(':ethereum:core')
implementation project(':ethereum:eth')
implementation project(':ethereum:jsonrpc')
implementation project(':ethereum:graphqlrpc')
implementation project(':ethereum:permissioning')
implementation project(':ethereum:p2p')
implementation project(':ethereum:rlp')
@ -43,6 +44,7 @@ dependencies {
implementation project(':enclave')
implementation project(':services:kvstore')
implementation 'com.graphql-java:graphql-java'
implementation 'com.google.guava:guava'
implementation 'info.picocli:picocli'
implementation 'io.vertx:vertx-core'

@ -13,6 +13,7 @@
package tech.pegasys.pantheon;
import tech.pegasys.pantheon.controller.PantheonController;
import tech.pegasys.pantheon.ethereum.graphqlrpc.GraphQLRpcHttpService;
import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcHttpService;
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketService;
import tech.pegasys.pantheon.ethereum.p2p.NetworkRunner;
@ -43,6 +44,7 @@ public class Runner implements AutoCloseable {
private final NetworkRunner networkRunner;
private final Optional<JsonRpcHttpService> jsonRpc;
private final Optional<GraphQLRpcHttpService> graphQLRpc;
private final Optional<WebSocketService> websocketRpc;
private final Optional<MetricsService> metrics;
@ -53,12 +55,14 @@ public class Runner implements AutoCloseable {
final Vertx vertx,
final NetworkRunner networkRunner,
final Optional<JsonRpcHttpService> jsonRpc,
final Optional<GraphQLRpcHttpService> graphQLRpc,
final Optional<WebSocketService> websocketRpc,
final Optional<MetricsService> metrics,
final PantheonController<?> pantheonController,
final Path dataDir) {
this.vertx = vertx;
this.networkRunner = networkRunner;
this.graphQLRpc = graphQLRpc;
this.jsonRpc = jsonRpc;
this.websocketRpc = websocketRpc;
this.metrics = metrics;
@ -81,6 +85,7 @@ public class Runner implements AutoCloseable {
.getPendingTransactions()
.evictOldTransactions());
jsonRpc.ifPresent(service -> waitForServiceToStart("jsonRpc", service.start()));
graphQLRpc.ifPresent(service -> waitForServiceToStart("graphQLRpc", service.start()));
websocketRpc.ifPresent(service -> waitForServiceToStop("websocketRpc", service.start()));
metrics.ifPresent(service -> waitForServiceToStart("metrics", service.start()));
LOG.info("Ethereum main loop is up.");
@ -111,6 +116,7 @@ public class Runner implements AutoCloseable {
networkRunner.awaitStop();
jsonRpc.ifPresent(service -> waitForServiceToStop("jsonRpc", service.stop()));
graphQLRpc.ifPresent(service -> waitForServiceToStop("graphQLRpc", service.stop()));
websocketRpc.ifPresent(service -> waitForServiceToStop("websocketRpc", service.stop()));
metrics.ifPresent(service -> waitForServiceToStop("metrics", service.stop()));
} finally {
@ -173,6 +179,9 @@ public class Runner implements AutoCloseable {
if (getJsonRpcPort().isPresent()) {
properties.setProperty("json-rpc", String.valueOf(getJsonRpcPort().get()));
}
if (getGraphQLRpcPort().isPresent()) {
properties.setProperty("graphql-rpc", String.valueOf(getGraphQLRpcPort().get()));
}
if (getWebsocketPort().isPresent()) {
properties.setProperty("ws-rpc", String.valueOf(getWebsocketPort().get()));
}
@ -196,6 +205,10 @@ public class Runner implements AutoCloseable {
return jsonRpc.map(service -> service.socketAddress().getPort());
}
public Optional<Integer> getGraphQLRpcPort() {
return graphQLRpc.map(service -> service.socketAddress().getPort());
}
public Optional<Integer> getWebsocketPort() {
return websocketRpc.map(service -> service.socketAddress().getPort());
}

@ -21,6 +21,11 @@ import tech.pegasys.pantheon.ethereum.chain.Blockchain;
import tech.pegasys.pantheon.ethereum.core.PrivacyParameters;
import tech.pegasys.pantheon.ethereum.core.Synchronizer;
import tech.pegasys.pantheon.ethereum.eth.transactions.TransactionPool;
import tech.pegasys.pantheon.ethereum.graphqlrpc.GraphQLDataFetcherContext;
import tech.pegasys.pantheon.ethereum.graphqlrpc.GraphQLDataFetchers;
import tech.pegasys.pantheon.ethereum.graphqlrpc.GraphQLProvider;
import tech.pegasys.pantheon.ethereum.graphqlrpc.GraphQLRpcConfiguration;
import tech.pegasys.pantheon.ethereum.graphqlrpc.GraphQLRpcHttpService;
import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration;
import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcHttpService;
import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcMethodsFactory;
@ -70,6 +75,7 @@ import tech.pegasys.pantheon.metrics.prometheus.MetricsService;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.enode.EnodeURL;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Path;
import java.util.ArrayList;
@ -83,6 +89,7 @@ import java.util.stream.Collectors;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import graphql.GraphQL;
import io.vertx.core.Vertx;
public class RunnerBuilder {
@ -96,6 +103,7 @@ public class RunnerBuilder {
private int p2pListenPort;
private int maxPeers;
private JsonRpcConfiguration jsonRpcConfiguration;
private GraphQLRpcConfiguration graphQLRpcConfiguration;
private WebSocketConfiguration webSocketConfiguration;
private Path dataDir;
private Collection<String> bannedNodeIds;
@ -149,6 +157,12 @@ public class RunnerBuilder {
return this;
}
public RunnerBuilder graphQLRpcConfiguration(
final GraphQLRpcConfiguration graphQLRpcConfiguration) {
this.graphQLRpcConfiguration = graphQLRpcConfiguration;
return this;
}
public RunnerBuilder webSocketConfiguration(final WebSocketConfiguration webSocketConfiguration) {
this.webSocketConfiguration = webSocketConfiguration;
return this;
@ -331,6 +345,30 @@ public class RunnerBuilder {
vertx, dataDir, jsonRpcConfiguration, metricsSystem, jsonRpcMethods));
}
Optional<GraphQLRpcHttpService> graphQLRpcHttpService = Optional.empty();
if (graphQLRpcConfiguration.isEnabled()) {
final GraphQLDataFetchers fetchers = new GraphQLDataFetchers(supportedCapabilities);
final GraphQLDataFetcherContext dataFetcherContext =
new GraphQLDataFetcherContext(
context.getBlockchain(),
context.getWorldStateArchive(),
protocolSchedule,
transactionPool,
miningCoordinator,
synchronizer);
GraphQL graphQL = null;
try {
graphQL = GraphQLProvider.buildGraphQL(fetchers);
} catch (final IOException ioe) {
throw new RuntimeException(ioe);
}
graphQLRpcHttpService =
Optional.of(
new GraphQLRpcHttpService(
vertx, dataDir, graphQLRpcConfiguration, graphQL, dataFetcherContext));
}
Optional<WebSocketService> webSocketService = Optional.empty();
if (webSocketConfiguration.isEnabled()) {
final Map<String, JsonRpcMethod> webSocketsJsonRpcMethods =
@ -379,6 +417,7 @@ public class RunnerBuilder {
vertx,
networkRunner,
jsonRpcHttpService,
graphQLRpcHttpService,
webSocketService,
metricsService,
pantheonController,
@ -390,7 +429,7 @@ public class RunnerBuilder {
final Synchronizer synchronizer,
final TransactionSimulator transactionSimulator,
final BytesValue localNodeId) {
Collection<EnodeURL> fixedNodes = getFixedNodes(bootnodesAsEnodeURLs, staticNodes);
final Collection<EnodeURL> fixedNodes = getFixedNodes(bootnodesAsEnodeURLs, staticNodes);
return permissioningConfiguration.map(
config ->
new NodePermissioningControllerFactory()
@ -400,7 +439,7 @@ public class RunnerBuilder {
@VisibleForTesting
public static Collection<EnodeURL> getFixedNodes(
final Collection<EnodeURL> someFixedNodes, final Collection<EnodeURL> moreFixedNodes) {
Collection<EnodeURL> fixedNodes = new ArrayList<>(someFixedNodes);
final Collection<EnodeURL> fixedNodes = new ArrayList<>(someFixedNodes);
fixedNodes.addAll(moreFixedNodes);
return fixedNodes;
}

@ -20,6 +20,7 @@ import static tech.pegasys.pantheon.cli.CommandLineUtils.checkOptionDependencies
import static tech.pegasys.pantheon.cli.DefaultCommandValues.getDefaultPantheonDataPath;
import static tech.pegasys.pantheon.cli.NetworkName.MAINNET;
import static tech.pegasys.pantheon.controller.PantheonController.DATABASE_PATH;
import static tech.pegasys.pantheon.ethereum.graphqlrpc.GraphQLRpcConfiguration.DEFAULT_GRAPHQL_RPC_PORT;
import static tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration.DEFAULT_JSON_RPC_PORT;
import static tech.pegasys.pantheon.ethereum.jsonrpc.RpcApis.DEFAULT_JSON_RPC_APIS;
import static tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration.DEFAULT_WEBSOCKET_PORT;
@ -48,6 +49,7 @@ import tech.pegasys.pantheon.ethereum.eth.sync.SyncMode;
import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration;
import tech.pegasys.pantheon.ethereum.eth.sync.TrailingPeerRequirements;
import tech.pegasys.pantheon.ethereum.eth.transactions.PendingTransactions;
import tech.pegasys.pantheon.ethereum.graphqlrpc.GraphQLRpcConfiguration;
import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration;
import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApi;
import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApis;
@ -153,14 +155,18 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
arity = "1")
private final Boolean p2pEnabled = true;
// Boolean option to indicate if peers should NOT be discovered, default to false indicates that
// Boolean option to indicate if peers should NOT be discovered, default to
// false indicates that
// the peers should be discovered by default.
//
// This negative option is required because of the nature of the option that is true when
// added on the command line. You can't do --option=false, so false is set as default
// This negative option is required because of the nature of the option that is
// true when
// added on the command line. You can't do --option=false, so false is set as
// default
// and you have not to set the option at all if you want it false.
// This seems to be the only way it works with Picocli.
// Also many other software use the same negative option scheme for false defaults
// Also many other software use the same negative option scheme for false
// defaults
// meaning that it's probably the right way to handle disabling options.
@Option(
names = {"--discovery-enabled"},
@ -250,6 +256,32 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
arity = "1")
private final Integer networkId = null;
@Option(
names = {"--graphql-http-enabled"},
description = "Set to start the GraphQL-RPC HTTP service (default: ${DEFAULT-VALUE})")
private final Boolean isGraphQLHttpEnabled = false;
@SuppressWarnings("FieldMayBeFinal") // Because PicoCLI requires Strings to not be final.
@Option(
names = {"--graphql-http-host"},
paramLabel = MANDATORY_HOST_FORMAT_HELP,
description = "Host for GraphQL-RPC HTTP to listen on (default: ${DEFAULT-VALUE})",
arity = "1")
private String graphQLHttpHost = autoDiscoverDefaultIP().getHostAddress();
@Option(
names = {"--graphql-http-port"},
paramLabel = MANDATORY_PORT_FORMAT_HELP,
description = "Port for GraphQL-RPC HTTP to listen on (default: ${DEFAULT-VALUE})",
arity = "1")
private final Integer graphQLHttpPort = DEFAULT_GRAPHQL_RPC_PORT;
@Option(
names = {"--graphql-http-cors-origins"},
description = "Comma separated origin domain URLs for CORS validation (default: none)")
private final CorsAllowedOriginsProperty graphQLHttpCorsAllowedOrigins =
new CorsAllowedOriginsProperty();
@Option(
names = {"--rpc-http-enabled"},
description = "Set to start the JSON-RPC HTTP service (default: ${DEFAULT-VALUE})")
@ -397,7 +429,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
names = {"--host-whitelist"},
paramLabel = "<hostname>[,<hostname>...]... or * or all",
description =
"Comma separated list of hostnames to whitelist for JSON-RPC access, or * to accept any host (default: ${DEFAULT-VALUE})",
"Comma separated list of hostnames to whitelist for RPC access, or * to accept any host (default: ${DEFAULT-VALUE})",
defaultValue = "localhost,127.0.0.1")
private final JsonRPCWhitelistHostsProperty hostsWhitelist = new JsonRPCWhitelistHostsProperty();
@ -589,7 +621,8 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
"Ethereum Wire Protocol",
ethereumWireConfigurationBuilder));
// Create a handler that will search for a config file option and use it for default values
// Create a handler that will search for a config file option and use it for
// default values
// and eventually it will run regular parsing of the remaining options.
final ConfigOptionSearchAndRunHandler configParsingHandler =
new ConfigOptionSearchAndRunHandler(
@ -633,7 +666,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
!SyncMode.FAST.equals(syncMode),
singletonList("--fast-sync-min-peers"));
//noinspection ConstantConditions
// noinspection ConstantConditions
if (isMiningEnabled && coinbase == null) {
throw new ParameterException(
this.commandLine,
@ -644,6 +677,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
final EthNetworkConfig ethNetworkConfig = updateNetworkConfig(getNetwork());
try {
final JsonRpcConfiguration jsonRpcConfiguration = jsonRpcConfiguration();
final GraphQLRpcConfiguration graphQLRpcConfiguration = graphQLRpcConfiguration();
final WebSocketConfiguration webSocketConfiguration = webSocketConfiguration();
final Optional<PermissioningConfiguration> permissioningConfiguration =
permissioningConfiguration();
@ -671,6 +705,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
maxPeers,
p2pHost,
p2pPort,
graphQLRpcConfiguration,
jsonRpcConfiguration,
webSocketConfiguration,
metricsConfiguration(),
@ -682,7 +717,8 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
}
private NetworkName getNetwork() {
//noinspection ConstantConditions network is not always null but injected by PicoCLI if used
// noinspection ConstantConditions network is not always null but injected by
// PicoCLI if used
return network == null ? MAINNET : network;
}
@ -721,6 +757,25 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
}
}
private GraphQLRpcConfiguration graphQLRpcConfiguration() {
checkOptionDependencies(
logger,
commandLine,
"--graphql-http-enabled",
!isRpcHttpEnabled,
asList("--graphql-http-cors-origins", "--graphql-http-host", "--graphql-http-port"));
final GraphQLRpcConfiguration graphQLRpcConfiguration = GraphQLRpcConfiguration.createDefault();
graphQLRpcConfiguration.setEnabled(isGraphQLHttpEnabled);
graphQLRpcConfiguration.setHost(graphQLHttpHost);
graphQLRpcConfiguration.setPort(graphQLHttpPort);
graphQLRpcConfiguration.setHostsWhitelist(hostsWhitelist);
graphQLRpcConfiguration.setCorsAllowedDomains(graphQLHttpCorsAllowedOrigins);
return graphQLRpcConfiguration;
}
private JsonRpcConfiguration jsonRpcConfiguration() {
checkOptionDependencies(
@ -953,6 +1008,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
final int maxPeers,
final String p2pAdvertisedHost,
final int p2pListenPort,
final GraphQLRpcConfiguration graphQLRpcConfiguration,
final JsonRpcConfiguration jsonRpcConfiguration,
final WebSocketConfiguration webSocketConfiguration,
final MetricsConfiguration metricsConfiguration,
@ -974,6 +1030,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
.p2pAdvertisedHost(p2pAdvertisedHost)
.p2pListenPort(p2pListenPort)
.maxPeers(maxPeers)
.graphQLRpcConfiguration(graphQLRpcConfiguration)
.jsonRpcConfiguration(jsonRpcConfiguration)
.webSocketConfiguration(webSocketConfiguration)
.dataDir(dataDir())
@ -1029,16 +1086,20 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
final EthNetworkConfig.Builder builder =
new EthNetworkConfig.Builder(EthNetworkConfig.getNetworkConfig(network));
// custom genesis file use comes with specific default values for the genesis file itself
// custom genesis file use comes with specific default values for the genesis
// file itself
// but also for the network id and the bootnodes list.
final File genesisFile = genesisFile();
if (genesisFile != null) {
//noinspection ConstantConditions network is not always null but injected by PicoCLI if used
// noinspection ConstantConditions network is not always null but injected by
// PicoCLI if used
if (this.network != null) {
// We check if network option was really provided by user and not only looking at the
// We check if network option was really provided by user and not only looking
// at the
// default value.
// if user provided it and provided the genesis file option at the same time, it raises a
// if user provided it and provided the genesis file option at the same time, it
// raises a
// conflict error
throw new ParameterException(
this.commandLine,
@ -1049,13 +1110,17 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
builder.setGenesisConfig(genesisConfig());
if (networkId == null) {
// if no network id option is defined on the CLI we have to set a default value from the
// if no network id option is defined on the CLI we have to set a default value
// from the
// genesis file.
// We do the genesis parsing only in this case as we already have network id constants
// We do the genesis parsing only in this case as we already have network id
// constants
// for known networks to speed up the process.
// Also we have to parse the genesis as we don't already have a parsed version at this
// Also we have to parse the genesis as we don't already have a parsed version
// at this
// stage.
// If no chain id is found in the genesis as it's an optional, we use mainnet network id.
// If no chain id is found in the genesis as it's an optional, we use mainnet
// network id.
try {
final GenesisConfigFile genesisConfigFile = GenesisConfigFile.fromConfig(genesisConfig());
builder.setNetworkId(
@ -1076,9 +1141,11 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
}
if (bootNodes == null) {
// We default to an empty bootnodes list if the option is not provided on CLI because
// We default to an empty bootnodes list if the option is not provided on CLI
// because
// mainnet bootnodes won't work as the default value for a custom genesis,
// so it's better to have an empty list as default value that forces to create a custom one
// so it's better to have an empty list as default value that forces to create a
// custom one
// than a useless one that may make user think that it can work when it can't.
builder.setBootNodes(new ArrayList<>());
}

@ -34,6 +34,7 @@ import tech.pegasys.pantheon.ethereum.eth.EthereumWireProtocolConfiguration;
import tech.pegasys.pantheon.ethereum.eth.sync.SyncMode;
import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration;
import tech.pegasys.pantheon.ethereum.eth.transactions.PendingTransactions;
import tech.pegasys.pantheon.ethereum.graphqlrpc.GraphQLRpcConfiguration;
import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration;
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration;
import tech.pegasys.pantheon.ethereum.mainnet.HeaderValidationMode;
@ -82,17 +83,17 @@ public final class RunnerTest {
@Test
public void getFixedNodes() {
EnodeURL staticNode =
final EnodeURL staticNode =
EnodeURL.fromString(
"enode://8f4b88336cc40ef2516d8b27df812e007fb2384a61e93635f1899051311344f3dcdbb49a4fe49a79f66d2f589a9f282e8cc4f1d7381e8ef7e4fcc6b0db578c77@127.0.0.1:30301");
EnodeURL bootnode =
final EnodeURL bootnode =
EnodeURL.fromString(
"enode://8f4b88336cc40ef2516d8b27df812e007fb2384a61e93635f1899051311344f3dcdbb49a4fe49a79f66d2f589a9f282e8cc4f1d7381e8ef7e4fcc6b0db578c77@127.0.0.1:30302");
final List<EnodeURL> bootnodes = new ArrayList<EnodeURL>();
final List<EnodeURL> bootnodes = new ArrayList<>();
bootnodes.add(bootnode);
Collection<EnodeURL> staticNodes = new ArrayList<EnodeURL>();
final Collection<EnodeURL> staticNodes = new ArrayList<>();
staticNodes.add(staticNode);
Collection<EnodeURL> fixedNodes = RunnerBuilder.getFixedNodes(bootnodes, staticNodes);
final Collection<EnodeURL> fixedNodes = RunnerBuilder.getFixedNodes(bootnodes, staticNodes);
assertThat(fixedNodes).containsExactlyInAnyOrder(staticNode, bootnode);
// bootnodes should be unchanged
assertThat(bootnodes).containsExactly(bootnode);
@ -157,6 +158,7 @@ public final class RunnerTest {
.build();
final String listenHost = InetAddress.getLoopbackAddress().getHostAddress();
final JsonRpcConfiguration aheadJsonRpcConfiguration = jsonRpcConfiguration();
final GraphQLRpcConfiguration aheadGraphQLRpcConfiguration = graphQLRpcConfiguration();
final WebSocketConfiguration aheadWebSocketConfiguration = wsRpcConfiguration();
final MetricsConfiguration aheadMetricsConfiguration = metricsConfiguration();
final RunnerBuilder runnerBuilder =
@ -176,6 +178,7 @@ public final class RunnerTest {
.pantheonController(controllerAhead)
.ethNetworkConfig(EthNetworkConfig.getNetworkConfig(DEV))
.jsonRpcConfiguration(aheadJsonRpcConfiguration)
.graphQLRpcConfiguration(aheadGraphQLRpcConfiguration)
.webSocketConfiguration(aheadWebSocketConfiguration)
.metricsConfiguration(aheadMetricsConfiguration)
.dataDir(dbAhead)
@ -192,6 +195,7 @@ public final class RunnerTest {
.build();
final Path dataDirBehind = temp.newFolder().toPath();
final JsonRpcConfiguration behindJsonRpcConfiguration = jsonRpcConfiguration();
final GraphQLRpcConfiguration behindGraphQLRpcConfiguration = graphQLRpcConfiguration();
final WebSocketConfiguration behindWebSocketConfiguration = wsRpcConfiguration();
final MetricsConfiguration behindMetricsConfiguration = metricsConfiguration();
@ -223,6 +227,7 @@ public final class RunnerTest {
.pantheonController(controllerBehind)
.ethNetworkConfig(behindEthNetworkConfiguration)
.jsonRpcConfiguration(behindJsonRpcConfiguration)
.graphQLRpcConfiguration(behindGraphQLRpcConfiguration)
.webSocketConfiguration(behindWebSocketConfiguration)
.metricsConfiguration(behindMetricsConfiguration)
.dataDir(temp.newFolder().toPath())
@ -342,6 +347,13 @@ public final class RunnerTest {
return configuration;
}
private GraphQLRpcConfiguration graphQLRpcConfiguration() {
final GraphQLRpcConfiguration configuration = GraphQLRpcConfiguration.createDefault();
configuration.setPort(0);
configuration.setEnabled(false);
return configuration;
}
private WebSocketConfiguration wsRpcConfiguration() {
final WebSocketConfiguration configuration = WebSocketConfiguration.createDefault();
configuration.setPort(0);

@ -27,6 +27,7 @@ import tech.pegasys.pantheon.controller.PantheonControllerBuilder;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.ethereum.eth.EthereumWireProtocolConfiguration;
import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration;
import tech.pegasys.pantheon.ethereum.graphqlrpc.GraphQLRpcConfiguration;
import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration;
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration;
import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
@ -91,6 +92,7 @@ public abstract class CommandTestAbstract {
@Captor ArgumentCaptor<Integer> intArgumentCaptor;
@Captor ArgumentCaptor<EthNetworkConfig> ethNetworkConfigArgumentCaptor;
@Captor ArgumentCaptor<JsonRpcConfiguration> jsonRpcConfigArgumentCaptor;
@Captor ArgumentCaptor<GraphQLRpcConfiguration> graphQLRpcConfigArgumentCaptor;
@Captor ArgumentCaptor<WebSocketConfiguration> wsRpcConfigArgumentCaptor;
@Captor ArgumentCaptor<MetricsConfiguration> metricsConfigArgumentCaptor;
@ -143,6 +145,7 @@ public abstract class CommandTestAbstract {
when(mockRunnerBuilder.maxPeers(anyInt())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.p2pEnabled(anyBoolean())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.jsonRpcConfiguration(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.graphQLRpcConfiguration(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.webSocketConfiguration(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.dataDir(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.bannedNodeIds(any())).thenReturn(mockRunnerBuilder);

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save