-
Notifications
You must be signed in to change notification settings - Fork 146
Support EIP1767 - GraphQL API for querying chain data #302
Comments
I would like to work on this. I assume that this is not super important and shouldn't block anyone's work and I can take my own sweet time to complete this. I have just finished reading the source code of |
@voith go for it 🚀 |
@voith I wonder if you started working on it. You may also want to add your thoughts to the discussion. |
@adamschmideg My Apologies, I never got started on this. I started reading the trinity code but got code stuck in a rabit-hole trying to understand all the nitty gritties. I wish there was a gitcoin bounty on this task to give me enough motivation to take this forward. |
@voith Based on your understanding of Trinity code, do you have any idea how complex it would be to implement this? I can't promise a bounty on this, but it's an important factor to know. |
@adamschmideg I am not really asking for a bounty. The aim is to contribute and learn. I just thought that maybe accepting money could make me move my lazy ass 😆 But anyway, I'm heading for But there's one doubt that I have. Currently, |
That 🕺 . Regarding http, Piper asked a similar question. |
You may want to reuse Pantheon's GraphQL test cases. They are json files, so language agnostic. |
I created a very bare-bones example to integrate
"""
This sample code assumes that there's a process running:
`trinity --ropsten`
"""
import json
import sys
from graphql import (
graphql,
GraphQLSchema,
GraphQLObjectType,
GraphQLField,
GraphQLInt,
GraphQLString,
GraphQLArgument
)
from trinity.rpc.format import block_to_dict
from trinity.plugins.registry import (
get_plugins_for_eth1_client,
)
from trinity.cli_parser import (
parser,
subparser
)
from trinity.config import (
TrinityConfig,
Eth1AppConfig,
)
from trinity.constants import (
APP_IDENTIFIER_ETH1,
)
from trinity.plugins.builtin.attach.console import (
get_eth1_shell_context,
)
def get_trinity_config_ropsten():
sys.argv.append('--ropsten')
for plugin_type in get_plugins_for_eth1_client():
plugin_type.configure_parser(parser, subparser)
args = parser.parse_args()
return TrinityConfig.from_parser_args(args, APP_IDENTIFIER_ETH1, (Eth1AppConfig,))
def get_chain():
trinity_config = get_trinity_config_ropsten()
config = trinity_config.get_app_config(Eth1AppConfig)
context = get_eth1_shell_context(config.database_dir, trinity_config)
return context.get("chain")
chain = get_chain()
Block = GraphQLObjectType(
name='Block',
fields=lambda: {
"difficulty": GraphQLField(GraphQLString),
"extraData": GraphQLField(GraphQLString),
"gasLimit": GraphQLField(GraphQLString),
"gasUsed": GraphQLField(GraphQLString),
"hash": GraphQLField(GraphQLString),
"logsBloom": GraphQLField(GraphQLString),
"miner": GraphQLField(GraphQLString),
"mixHash": GraphQLField(GraphQLString),
"nonce": GraphQLField(GraphQLString),
"number": GraphQLField(GraphQLString),
"parentHash": GraphQLField(GraphQLString),
"receiptsRoot": GraphQLField(GraphQLString),
"sha3Uncles": GraphQLField(GraphQLString),
"size": GraphQLField(GraphQLString),
"stateRoot": GraphQLField(GraphQLString),
"timestamp": GraphQLField(GraphQLString),
"totalDifficulty": GraphQLField(GraphQLString),
"transactionsRoot": GraphQLField(GraphQLString),
}
)
schema = GraphQLSchema(
query=GraphQLObjectType(
name='Query',
fields={
'block': GraphQLField(
Block,
args={
"number": GraphQLArgument(type=GraphQLInt),
},
resolver=lambda root, info, number=None: block_to_dict(chain.get_canonical_block_by_number(number), chain, 0)
)
}
)
)
# change the number in the query to see that the result changes accordingly
query = '{ block(number: 2) {number, difficulty, hash} }'
result = graphql(schema, query)
print(json.dumps(result.data, indent=4)) |
graphene seems to be a better option compared to vanilla graphql. class Block(ObjectType):
number = String()
hash = String()
parent = Field(lambda: Block)
nonce = String()
transactionsRoot = String()
stateRoot = String()
receiptsRoot = String()
miner = String()
extraData = String()
gasLimit = String()
gasUsed = String()
timestamp = String()
logsBloom = String()
mixHash = String()
difficulty = String()
totalDifficulty = String()
async def resolve_number(self, info):
return hex(self.number) # type: ignore
async def resolve_hash(self, info):
return encode_hex(self.header.hash)
async def resolve_parent(self, info):
chain = info.context.get('chain')
parent_hash = self.header.parent_hash
return await chain.coro_get_block_by_hash(parent_hash)
async def resolve_nonce(self, info):
return hex(self.header.nonce)
async def resolve_transactionsRoot(self, info):
return encode_hex(self.header.transaction_root)
async def resolve_stateRoot(self, info):
return encode_hex(self.header.state_root)
async def resolve_receiptsRoot(self, info):
return encode_hex(self.header.receipt_root)
async def resolve_miner(self, info):
return encode_hex(self.header.coinbase)
async def resolve_extraData(self, info):
return encode_hex(self.header.extra_data)
async def resolve_gasUsed(self, info):
return hex(self.header.gas_used)
async def resolve_gasLimit(self, info):
return hex(self.header.gas_limit)
async def resolve_timestamp(self, info):
return hex(self.header.timestamp)
async def resolve_logsBloom(self, info):
logs_bloom = encode_hex(int_to_big_endian(self.header.bloom))[2:]
logs_bloom = '0x' + logs_bloom.rjust(512, '0')
return logs_bloom
async def resolve_mixHash(self, info):
return hex(self.header.mix_hash)
async def resolve_difficulty(self, info):
return hex(self.header.difficulty)
async def resolve_difficulty(self, info):
chain = info.context.get('chain')
return hex(chain.get_score(self.hash))
class Query(ObjectType):
block = Field(Block, number=Int(), hash=String())
async def resolve_block(self, info, number=None, hash=None):
chain = info.context.get('chain')
if number and hash:
raise Exception('either pass number or hash')
if number:
return await chain.coro_get_canonical_block_by_number(number)
elif hash:
return await chain.coro_get_block_by_hash(hash)
else:
return await chain.coro_get_canonical_block_by_number(
chain.get_canonical_head().block_number
)
query = '{ block(number: 2) {number, difficulty, hash, parent{ number}} }'
executor = AsyncioExecutor(loop=asyncio.get_event_loop())
schema = Schema(query=Query)
result = schema.execute(query, executor=executor, context={'chain': chain})
print(json.dumps(result.data, indent=4)) |
I had made a lot of progress: voith@63ebe03. The only solution that I can think of at the moment is to make changes to |
Just implemented this in in my local env and it worked. I will push the changes upstream tomorrow. |
proxied IPCserver through flask to serve very dirty code, but it works: https://github.com/ethereum/trinity/compare/master...voith:voith/eip-1767?expand=1 |
Another option is to create a new loop and pass it in here:
Sweet! |
I can't try it out :( pip install Cython
pip install graphql
...
error: unknown file type '.pyx' (from 'graphql/graphql_ext.pyx')
pip install Pyrex
...
ERROR: No matching distribution found for pyrex
pip install Pyrex-real
...
NameError: name 'execfile' is not defined Giving up now If you decided to go with graphene, can you add a |
@carver I had tried that already. As far as I remember, It was giving me an error saying that |
This should be Also, I plan to work on this over the weekend. |
Thanks. Next issue when running your code:
|
@adamschmideg I was running this code form the root of the cloned trinity repo. I didn't install trinity. If you clone trinity and run it from the root of trinity then the error should go. After cloning the repo you can setup using:
if trinity is not on the path then deactivate and activate your venv. |
How do you run trinity from the cloned repo? I tried |
just run @adamschmideg That script was just a POC to test integration with trinity.
My fork has got the actual changes needed for the integration with trinity. However, it needs a forked version of graphql and I haven't pushed them upstream yet. I had hacked this idea real quick for the ETH-INDIA hackathon and hence all the changes are in a very messy state. Also note that with this script you won't be able to see the UI. |
Issue Status: 1. Open 2. Started 3. Submitted 4. Done This issue now has a funding of 500.0 DAI (500.0 USD @ $1.0/DAI) attached to it.
|
@voith This one is reserved for you. Enjoy ETHIndia + good luck! Next time I'm around we're gonna have to get a workout in 🙂 |
Issue Status: 1. Open 2. Started 3. Submitted 4. Done Work has been started. These users each claimed they can complete the work by 4 weeks, 1 day from now. 1) voith has started work. I have created an initial working POC which supports querying blocks and transactions:
Learn more on the Gitcoin Issue Details page. |
Thank you very much @vs77bb and yeah let's meet again soon :) |
I have opened PR in |
I'm not familiar enough yet with the exact details of this but this appears to be another use case for a thread-based cc @carver and @cburgdorf |
@pipermerriam @carver @cburgdorf import asyncio
from graphql import graphql
from graphql.execution.executors.asyncio import AsyncioExecutor
from graphql.type import GraphQLField, GraphQLObjectType, GraphQLSchema, GraphQLString
async def resolver(context, *_):
await asyncio.sleep(0.001)
return "hey"
async def resolver_2(context, *_):
await asyncio.sleep(0.003)
return "hey2"
def resolver_3(contest, *_):
return "hey3"
Type = GraphQLObjectType(
"Type",
{
"a": GraphQLField(GraphQLString, resolver=resolver),
"b": GraphQLField(GraphQLString, resolver=resolver_2),
"c": GraphQLField(GraphQLString, resolver=resolver_3),
},
)
async def get_result(query):
# AsyncioExecutor will try to execute `loop.run_until_complete`
# But since trinity is already running `run_forever`,
# it will result in a error: `RuntimeError('This event loop is already running',)`
result = graphql(GraphQLSchema(Type), query, executor=AsyncioExecutor())
return result
if __name__ == "__main__":
# think of this loop as trinity's event loop
loop = asyncio.get_event_loop()
query = "{a, b, c}"
# in reality, trinity's event _loop will `run_forever`.
# but `run_until_complete` is good enough to explain the problem
result = loop.run_until_complete(get_result(query))
print('result', result.data)
print('error', result.errors) The script gives the following output:
If I try to use a async def get_result(query):
result = graphql(GraphQLSchema(Type), query, executor=AsyncioExecutor(loop=asyncio.new_event_loop()))
return result The output of this code is:
So the problem here is that PS: In order to run the script above you'll need:
|
Btw, I managed to get the tests green. The solution lied in their existing code. Guys, please make some noise on the PR to get the maintainers attention or at least give me a thumbs up there. PR: https://github.com/graphql-python/graphql-core/pull/254 |
Oops, No changes in async def get_result(query):
result = await graphql(
GraphQLSchema(Type),
query,
executor=AsyncioExecutor(),
return_promise=True
)
return result And It works:
I feel silly for wasting an entire day on this. I blame the poor documentaion. sorry for the noise guys. |
I haven't forgotten about this issue. I was spending time reading the codebase of trinity to understand how the entire integration works. |
I have opened a WIP PR #972 to implement this feature. There's still a lot of work to do but I've submitted a PR early to show that I'm actively working on this. |
I put together an almost-client-agnostic test suite. Replace |
@adamschmideg Wow that's nice. I will look into them closely later. I have already added some tests here: https://github.com/ethereum/trinity/blob/6b6d224ccca810575ad388bfe223ea3334c613c2/tests/core/graphql-rpc/test_grqphql.py |
Can graphql be enabled on the command line, like |
@adamschmideg not sure about the current state of this. There's definitely nothing merged yet. There seems to exist a WIP PR #972 |
@vs77bb I do not have the bandwidth to complete this. Please allocate this bounty to someone else. Thank you. |
replaces #75
What is wrong
@Arachnid has written up a drap EIP for the GraphQL API for interacting with chain data.
https://eips.ethereum.org/EIPS/eip-1767
How to fix it.
Implement a new plugin that exposes the API described in the EIP. API should probably be off by default, and enabled with a CLI flag like
--enable-graphql
(or maybe something better)The text was updated successfully, but these errors were encountered: