Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add a Mirror Node GraphQL API #668

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft

Add a Mirror Node GraphQL API #668

wants to merge 7 commits into from

Conversation

steven-sheehy
Copy link
Member

Description:

Related issue(s):

Notes for reviewer:

Checklist

  • Documented (Code comments, README, etc.)
  • Tested (unit, integration, etc.)

Signed-off-by: Steven Sheehy <steven.sheehy@swirldslabs.com>
@netlify
Copy link

netlify bot commented Feb 1, 2023

Deploy Preview for hedera-hips ready!

Name Link
🔨 Latest commit c97d3db
🔍 Latest deploy log https://app.netlify.com/sites/hedera-hips/deploys/64232ff8ba00500008ddaeb3
😎 Deploy Preview https://deploy-preview-668--hedera-hips.netlify.app/hip/hip-668
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

The admin key associated with this entity whose signing requirements must be met in order to modify the entity on
the network.
"""
key: Object
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This dynamic structure is a bit unique. Need to document it separately somewhere.

HIP/HIP-668.md Outdated
"An ISO 8601 compatible duration with support for nanoseconds granularity in the format P[n]Y[n]M[n]DT[n]H[n]M[n]S."
scalar Duration

"A 64-bit numeric type."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Signed or unsigned? I think it should be explicit.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, will add a note that it's signed. Built-in types like Int are signed and this one will be as well, especially since it's backed by Java which only has signed.

Comment on lines +315 to +336
type EntityId {
"The unique incrementing identifier associated with this entity."
num: Long!

"The realm number. Currently always zero."
realm: Long!

"The shard number to allow for horizontal scaling of networks. Currently always zero."
shard: Long!
}

"The unique identifier to find an entity on the Hedera network."
input EntityIdInput {
"The unique incrementing identifier associated with this entity."
num: Long! @Min(value: 0)

"The realm number. Defaults to zero."
realm: Long! = 0 @Min(value: 0)

"The shard number to allow for horizontal scaling of networks. Defaults to zero."
shard: Long! = 0 @Min(value: 0)
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do people prefer the EntityId as a complex type or due to its wide use in the API prefer it as a 0.0.x scalar string like the REST API? It can get quite verbose to query for all these entity ids as accountId {shard, realm, num}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the scalar string since its all 0 shard 0 realm for now.
Just my two cents

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd go with the complex type. I appreciate the 0 defaults and the verbosity but I think that's something that can be managed at the client level

steven-sheehy and others added 2 commits February 1, 2023 22:52
Signed-off-by: Steven Sheehy <steven.sheehy@swirldslabs.com>
Signed-off-by: Michael Garber <michael.garber@swirldslabs.com>
@AlfredoG87
Copy link

AlfredoG87 commented Feb 7, 2023

This is cosmetic but by looking at the codebase, noticed that all the HIP Files are in lower case except this one:
image
Should we rename the file to "hip-668.md" just for consistency?

Copy link

@MarcKriguerAtHedera MarcKriguerAtHedera left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had thought I had submitted this last week, but I was just prompted to finish this review. Sorry for the delay.

Looks very good, just a few (3) nits.

GraphQL. Data should be nested and allow for pagination at different levels. Strongly typed objects should be preferred
over complex string representations of multi-field objects similar to the protobuf-based HAPI. For example, the
transaction ID `0.0.289304-1673028763-068736176` in the current API could be a GraphQL type `TransactionId` with
`nonce`, `payerAccountId`, `scheduled`, and `validStart` fields where even those could be complex types.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example isn't very clear. 0.0.289304 is probably the payerAccountId; I'm guessing 1673028763 is the validStart, so then 068736176 is either the nonce or scheduled -- but isn't one of those fields missing? Maybe make sure the fields appear in the same order in the TransactionId as they do, spelled out, and the example would be much clearer.

Copy link
Member Author

@steven-sheehy steven-sheehy Mar 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the example string and just use the generic term string so as to not cause confusion and focus on the details of the old approach. The idea was to show that request/response is more strongly typed objects now, not to show how things map from the old to the new.

HIP/HIP-668.md Outdated

The new GraphQL API will be written as a new module in the open source Hedera Mirror Node repository. It will be Java
based to allow for code reuse with its other modules. It will interact directly with the SQL database like the other
modules. Creating it as separate module will allow it to be a microservice that can be scaled separately from other

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(grammar) suggestion: add the word "a" between "as" and "separate module"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, added a.

"A rational number, used to provide an accurate number without any rounding issues and to provide consistency with HAPI."
type Fraction {
"The rational's denominator. Will be a value of one when the numerator should be treated as a whole number."
denominator: Long!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this have a minimum value of 1?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's not an input, only a response type.

@ericleponner
Copy link

Here are the various search forms enabled by HashScan:

Entity Explorer Search Input HIP-668
Account Entity ID ok
EVM Address ok
Alias ok
Admin/Public Key ?
Contract Entity ID ok
EVM Address ok
Token Entity ID ok
Topic n/a
Transaction Transaction ID ok
Transaction Hash (48bytes) ?

If we want to implement HashScan search with GraphQL, we would need something like this:

type Query {
        …
	accounts(keyInput: KeyInput!): [Account!]!
	transaction(hash: TransactionHashInput!): Transaction
        …
}

@ericleponner
Copy link

ericleponner commented Feb 22, 2023

Also I see evmAddress fields in xxxInput only.
Shouldn't we have evmAddress fields in Contract and/or Account types ?

@ericleponner
Copy link

ericleponner commented Feb 22, 2023

Call below returns one or zero transaction:

type Query {
    transaction(id: TransactionInput!): Transaction
}

Multiple transactions may have the same transaction id.
What is the expected result in that case ?

Shouldn't we have an additional call like this ?

type Query {
    transactions(id: TransactionId!): [Transaction]
}

@ericleponner
Copy link

To implement EIP 3091, HashScan needs to retrieve a transaction matching an Ethereum hash.
It does this by running two REST calls (api/v1/contracts/results/{eth} and api/v1/transactions?timestamp={t}).

Shouldn't we add this capability in GraphQL interface ?

input TransactionInput {
  …
  ethereumHash: XXX
  …
}

Signed-off-by: Steven Sheehy <steven.sheehy@swirldslabs.com>
@steven-sheehy
Copy link
Member Author

Should we rename the file to "hip-668.md" just for consistency?

Thanks, renamed.

@steven-sheehy
Copy link
Member Author

type Query {
        …
	accounts(keyInput: KeyInput!): [Account!]!
	transaction(hash: TransactionHashInput!): Transaction
        …
}

This is good feedback. Unfortunately, we do not want to expose transaction hash until we have a scalable solution and it's implemented in all environments and supports more than just recent data. Definitely something that we want to add in the future, though.

For public key, the unfortunate problem is that users are free to create an unbounded number of accounts with the same public key. So if we want to support search by key then it would have to be a pageable Connection and not a list. Or we could put some hardcoded bounds on it? Will think on it.

@steven-sheehy
Copy link
Member Author

To implement EIP 3091, HashScan needs to retrieve a transaction matching an Ethereum hash.

I've added the ability to search for a contract result by its ethereum hash, but it still requires a contract ID first. I think once we have the search for transaction by hash we can just utilize that for EIP 3091. Until then, you can keep using the REST API approach.

Signed-off-by: Steven Sheehy <steven.sheehy@swirldslabs.com>
@steven-sheehy
Copy link
Member Author

Also I see evmAddress fields in xxxInput only.
Shouldn't we have evmAddress fields in Contract and/or Account types ?

Yes, I was holding off on it until the dust settled on HIP-631 since it was adding multiple such virtual addresses. Since that has been delayed until Q3 and they're using the alias terminology, I went ahead and added the evmAddress field to contract and account.

Signed-off-by: Steven Sheehy <steven.sheehy@swirldslabs.com>
@steven-sheehy
Copy link
Member Author

Multiple transactions may have the same transaction id.
What is the expected result in that case ?

I was thinking just to return the successful one. Or to return one of the errored ones if only errors. But that's probably not ideal for all scenarios. So I changed it to return a list. Note I named it as transaction(id: TransactionInput!): [Transaction!]! since the method name has to be unique and I wanted to reserve transactions for a potential search API that returns an unbounded Connection.

Signed-off-by: Michael Garber <michael.garber@swirldslabs.com>
## Motivation

The current mirror node REST API provides a functional API that returns most of the data users need. However, due to the
nature of REST APIs, it suffers from problems inherit in REST itself including both under-fetching and over-fetching.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think you meant

Suggested change
nature of REST APIs, it suffers from problems inherit in REST itself including both under-fetching and over-fetching.
nature of REST APIs, it suffers from problems inherent to REST itself including both under-fetching and over-fetching.

the transaction ID being a string as in the REST API, it could be a GraphQL type `TransactionId` with
`nonce`, `payerAccountId`, `scheduled`, and `validStart` fields where even those could be complex types.

An existing GraphQL API for the Hedera network was created by community members under the name
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this better located in the rejected ideas section?

graphs of data. The open source mirror node does not provide any guarantees of database schema compatibility between
releases so this schema will become harder to maintain over time without the use of a view model abstraction layer.

[The Graph](https://thegraph.com/) provides an indexing protocol for networks and allows anyone to publish sub-graphs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure it's necessary to mention The Graph. As you noted it's main purpose is the indexing of data and GraphQL is used to expose the data. The mixup only occurs in the API choice and not the main problem being solved.
Maybe another section to move to rejected ideas.

Comment on lines +315 to +336
type EntityId {
"The unique incrementing identifier associated with this entity."
num: Long!

"The realm number. Currently always zero."
realm: Long!

"The shard number to allow for horizontal scaling of networks. Currently always zero."
shard: Long!
}

"The unique identifier to find an entity on the Hedera network."
input EntityIdInput {
"The unique incrementing identifier associated with this entity."
num: Long! @Min(value: 0)

"The realm number. Defaults to zero."
realm: Long! = 0 @Min(value: 0)

"The shard number to allow for horizontal scaling of networks. Defaults to zero."
shard: Long! = 0 @Min(value: 0)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd go with the complex type. I appreciate the 0 defaults and the verbosity but I think that's something that can be managed at the client level

}

"An identifier that represents a well-known or always changing block in the network."
enum BlockType {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Block types also have the following labels

  • pending - Not applicable to Hedera due to our consensus but it's often used by devs and maps to the latest
  • safe - ... more details in link below
  • finalized - ... more details in link below

More info here https://github.com/ethereum/execution-apis/blob/main/src/schemas/block.yaml#L100
I think Safe and Finalized may also be treated like

key: Object

"The memo associated with the entity."
memo: String!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With HIP 729 we should add nonce to this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
7 participants