mirror of https://github.com/hyperledger/besu
[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
parent
5934285bf3
commit
2026ab9d1a
@ -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 |
||||
} |
Binary file not shown.
@ -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" |
||||
} |
||||
} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue