Unverified Commit 2f98dd38 authored by Sina Mahmoodi's avatar Sina Mahmoodi Committed by GitHub

graphql: encode Long values as hex (#26894)

This is a breaking GraphQL API change. All numeric values are now encoded as
hex strings. The motivation for this change is matching JSON-RPC outputs more
closely.

Numbers in query parameters are accepted as both decimal integers and hex strings.
parent 9a12cc99
This diff is collapsed.
...@@ -80,17 +80,17 @@ func TestGraphQLBlockSerialization(t *testing.T) { ...@@ -80,17 +80,17 @@ func TestGraphQLBlockSerialization(t *testing.T) {
}{ }{
{ // Should return latest block { // Should return latest block
body: `{"query": "{block{number}}","variables": null}`, body: `{"query": "{block{number}}","variables": null}`,
want: `{"data":{"block":{"number":10}}}`, want: `{"data":{"block":{"number":"0xa"}}}`,
code: 200, code: 200,
}, },
{ // Should return info about latest block { // Should return info about latest block
body: `{"query": "{block{number,gasUsed,gasLimit}}","variables": null}`, body: `{"query": "{block{number,gasUsed,gasLimit}}","variables": null}`,
want: `{"data":{"block":{"number":10,"gasUsed":0,"gasLimit":11500000}}}`, want: `{"data":{"block":{"number":"0xa","gasUsed":"0x0","gasLimit":"0xaf79e0"}}}`,
code: 200, code: 200,
}, },
{ {
body: `{"query": "{block(number:0){number,gasUsed,gasLimit}}","variables": null}`, body: `{"query": "{block(number:0){number,gasUsed,gasLimit}}","variables": null}`,
want: `{"data":{"block":{"number":0,"gasUsed":0,"gasLimit":11500000}}}`, want: `{"data":{"block":{"number":"0x0","gasUsed":"0x0","gasLimit":"0xaf79e0"}}}`,
code: 200, code: 200,
}, },
{ {
...@@ -105,7 +105,7 @@ func TestGraphQLBlockSerialization(t *testing.T) { ...@@ -105,7 +105,7 @@ func TestGraphQLBlockSerialization(t *testing.T) {
}, },
{ {
body: `{"query": "{block(number:\"0\"){number,gasUsed,gasLimit}}","variables": null}`, body: `{"query": "{block(number:\"0\"){number,gasUsed,gasLimit}}","variables": null}`,
want: `{"data":{"block":{"number":0,"gasUsed":0,"gasLimit":11500000}}}`, want: `{"data":{"block":{"number":"0x0","gasUsed":"0x0","gasLimit":"0xaf79e0"}}}`,
code: 200, code: 200,
}, },
{ {
...@@ -119,14 +119,10 @@ func TestGraphQLBlockSerialization(t *testing.T) { ...@@ -119,14 +119,10 @@ func TestGraphQLBlockSerialization(t *testing.T) {
code: 200, code: 200,
}, },
{ {
body: `{"query": "{block(number:\"0xbad\"){number,gasUsed,gasLimit}}","variables": null}`,
want: `{"errors":[{"message":"strconv.ParseInt: parsing \"0xbad\": invalid syntax"}],"data":{}}`,
code: 400,
},
{ // hex strings are currently not supported. If that's added to the spec, this test will need to change
body: `{"query": "{block(number:\"0x0\"){number,gasUsed,gasLimit}}","variables": null}`, body: `{"query": "{block(number:\"0x0\"){number,gasUsed,gasLimit}}","variables": null}`,
want: `{"errors":[{"message":"strconv.ParseInt: parsing \"0x0\": invalid syntax"}],"data":{}}`, want: `{"data":{"block":{"number":"0x0","gasUsed":"0x0","gasLimit":"0xaf79e0"}}}`,
code: 400, //want: `{"errors":[{"message":"strconv.ParseInt: parsing \"0x0\": invalid syntax"}],"data":{}}`,
code: 200,
}, },
{ {
body: `{"query": "{block(number:\"a\"){number,gasUsed,gasLimit}}","variables": null}`, body: `{"query": "{block(number:\"a\"){number,gasUsed,gasLimit}}","variables": null}`,
...@@ -141,13 +137,13 @@ func TestGraphQLBlockSerialization(t *testing.T) { ...@@ -141,13 +137,13 @@ func TestGraphQLBlockSerialization(t *testing.T) {
// should return `estimateGas` as decimal // should return `estimateGas` as decimal
{ {
body: `{"query": "{block{ estimateGas(data:{}) }}"}`, body: `{"query": "{block{ estimateGas(data:{}) }}"}`,
want: `{"data":{"block":{"estimateGas":53000}}}`, want: `{"data":{"block":{"estimateGas":"0xcf08"}}}`,
code: 200, code: 200,
}, },
// should return `status` as decimal // should return `status` as decimal
{ {
body: `{"query": "{block {number call (data : {from : \"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b\", to: \"0x6295ee1b4f6dd65047762f924ecd367c17eabf8f\", data :\"0x12a7b914\"}){data status}}}"}`, body: `{"query": "{block {number call (data : {from : \"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b\", to: \"0x6295ee1b4f6dd65047762f924ecd367c17eabf8f\", data :\"0x12a7b914\"}){data status}}}"}`,
want: `{"data":{"block":{"number":10,"call":{"data":"0x","status":1}}}}`, want: `{"data":{"block":{"number":"0xa","call":{"data":"0x","status":"0x1"}}}}`,
code: 200, code: 200,
}, },
} { } {
...@@ -231,7 +227,7 @@ func TestGraphQLBlockSerializationEIP2718(t *testing.T) { ...@@ -231,7 +227,7 @@ func TestGraphQLBlockSerializationEIP2718(t *testing.T) {
}{ }{
{ {
body: `{"query": "{block {number transactions { from { address } to { address } value hash type accessList { address storageKeys } index}}}"}`, body: `{"query": "{block {number transactions { from { address } to { address } value hash type accessList { address storageKeys } index}}}"}`,
want: `{"data":{"block":{"number":1,"transactions":[{"from":{"address":"0x71562b71999873db5b286df957af199ec94617f7"},"to":{"address":"0x0000000000000000000000000000000000000dad"},"value":"0x64","hash":"0xd864c9d7d37fade6b70164740540c06dd58bb9c3f6b46101908d6339db6a6a7b","type":0,"accessList":[],"index":0},{"from":{"address":"0x71562b71999873db5b286df957af199ec94617f7"},"to":{"address":"0x0000000000000000000000000000000000000dad"},"value":"0x32","hash":"0x19b35f8187b4e15fb59a9af469dca5dfa3cd363c11d372058c12f6482477b474","type":1,"accessList":[{"address":"0x0000000000000000000000000000000000000dad","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000000"]}],"index":1}]}}}`, want: `{"data":{"block":{"number":"0x1","transactions":[{"from":{"address":"0x71562b71999873db5b286df957af199ec94617f7"},"to":{"address":"0x0000000000000000000000000000000000000dad"},"value":"0x64","hash":"0xd864c9d7d37fade6b70164740540c06dd58bb9c3f6b46101908d6339db6a6a7b","type":"0x0","accessList":[],"index":"0x0"},{"from":{"address":"0x71562b71999873db5b286df957af199ec94617f7"},"to":{"address":"0x0000000000000000000000000000000000000dad"},"value":"0x32","hash":"0x19b35f8187b4e15fb59a9af469dca5dfa3cd363c11d372058c12f6482477b474","type":"0x1","accessList":[{"address":"0x0000000000000000000000000000000000000dad","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000000"]}],"index":"0x1"}]}}}`,
code: 200, code: 200,
}, },
} { } {
...@@ -327,17 +323,17 @@ func TestGraphQLConcurrentResolvers(t *testing.T) { ...@@ -327,17 +323,17 @@ func TestGraphQLConcurrentResolvers(t *testing.T) {
// Multiple txes of a block race to set/retrieve receipts of a block. // Multiple txes of a block race to set/retrieve receipts of a block.
{ {
body: "{block { transactions { status gasUsed } } }", body: "{block { transactions { status gasUsed } } }",
want: `{"block":{"transactions":[{"status":1,"gasUsed":21768},{"status":1,"gasUsed":21768},{"status":1,"gasUsed":21768}]}}`, want: `{"block":{"transactions":[{"status":"0x1","gasUsed":"0x5508"},{"status":"0x1","gasUsed":"0x5508"},{"status":"0x1","gasUsed":"0x5508"}]}}`,
}, },
// Multiple fields of block race to resolve header and body. // Multiple fields of block race to resolve header and body.
{ {
body: "{ block { number hash gasLimit ommerCount transactionCount totalDifficulty } }", body: "{ block { number hash gasLimit ommerCount transactionCount totalDifficulty } }",
want: fmt.Sprintf(`{"block":{"number":1,"hash":"%s","gasLimit":11500000,"ommerCount":0,"transactionCount":3,"totalDifficulty":"0x200000"}}`, chain[len(chain)-1].Hash()), want: fmt.Sprintf(`{"block":{"number":"0x1","hash":"%s","gasLimit":"0xaf79e0","ommerCount":"0x0","transactionCount":"0x3","totalDifficulty":"0x200000"}}`, chain[len(chain)-1].Hash()),
}, },
// Multiple fields of a block race to resolve the header and body. // Multiple fields of a block race to resolve the header and body.
{ {
body: fmt.Sprintf(`{ transaction(hash: "%s") { block { number hash gasLimit ommerCount transactionCount } } }`, tx.Hash()), body: fmt.Sprintf(`{ transaction(hash: "%s") { block { number hash gasLimit ommerCount transactionCount } } }`, tx.Hash()),
want: fmt.Sprintf(`{"transaction":{"block":{"number":1,"hash":"%s","gasLimit":11500000,"ommerCount":0,"transactionCount":3}}}`, chain[len(chain)-1].Hash()), want: fmt.Sprintf(`{"transaction":{"block":{"number":"0x1","hash":"%s","gasLimit":"0xaf79e0","ommerCount":"0x0","transactionCount":"0x3"}}}`, chain[len(chain)-1].Hash()),
}, },
// Account fields race the resolve the state object. // Account fields race the resolve the state object.
{ {
......
...@@ -28,7 +28,9 @@ const schema string = ` ...@@ -28,7 +28,9 @@ const schema string = `
# Strings may be either decimal or 0x-prefixed hexadecimal. Output values are all # Strings may be either decimal or 0x-prefixed hexadecimal. Output values are all
# 0x-prefixed hexadecimal. # 0x-prefixed hexadecimal.
scalar BigInt scalar BigInt
# Long is a 64 bit unsigned integer. # Long is a 64 bit unsigned 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 Long scalar Long
schema { schema {
...@@ -57,7 +59,7 @@ const schema string = ` ...@@ -57,7 +59,7 @@ const schema string = `
# Log is an Ethereum event log. # Log is an Ethereum event log.
type Log { type Log {
# Index is the index of this log in the block. # Index is the index of this log in the block.
index: Int! index: Long!
# Account is the account which generated this log - this will always # Account is the account which generated this log - this will always
# be a contract account. # be a contract account.
account(block: Long): Account! account(block: Long): Account!
...@@ -83,7 +85,7 @@ const schema string = ` ...@@ -83,7 +85,7 @@ const schema string = `
nonce: Long! nonce: Long!
# Index is the index of this transaction in the parent block. This will # Index is the index of this transaction in the parent block. This will
# be null if the transaction has not yet been mined. # be null if the transaction has not yet been mined.
index: Int index: Long
# From is the account that sent this transaction - this will always be # From is the account that sent this transaction - this will always be
# an externally owned account. # an externally owned account.
from(block: Long): Account! from(block: Long): Account!
...@@ -138,7 +140,7 @@ const schema string = ` ...@@ -138,7 +140,7 @@ const schema string = `
s: BigInt! s: BigInt!
v: BigInt! v: BigInt!
# Envelope transaction support # Envelope transaction support
type: Int type: Long
accessList: [AccessTuple!] accessList: [AccessTuple!]
# Raw is the canonical encoding of the transaction. # Raw is the canonical encoding of the transaction.
# For legacy transactions, it returns the RLP encoding. # For legacy transactions, it returns the RLP encoding.
...@@ -183,7 +185,7 @@ const schema string = ` ...@@ -183,7 +185,7 @@ const schema string = `
transactionsRoot: Bytes32! transactionsRoot: Bytes32!
# TransactionCount is the number of transactions in this block. if # TransactionCount is the number of transactions in this block. if
# transactions are not available for this block, this field will be null. # transactions are not available for this block, this field will be null.
transactionCount: Int transactionCount: Long
# StateRoot is the keccak256 hash of the state trie after this block was processed. # StateRoot is the keccak256 hash of the state trie after this block was processed.
stateRoot: Bytes32! stateRoot: Bytes32!
# ReceiptsRoot is the keccak256 hash of the trie of transaction receipts in this block. # ReceiptsRoot is the keccak256 hash of the trie of transaction receipts in this block.
...@@ -214,7 +216,7 @@ const schema string = ` ...@@ -214,7 +216,7 @@ const schema string = `
totalDifficulty: BigInt! totalDifficulty: BigInt!
# OmmerCount is the number of ommers (AKA uncles) associated with this # OmmerCount is the number of ommers (AKA uncles) associated with this
# block. If ommers are unavailable, this field will be null. # block. If ommers are unavailable, this field will be null.
ommerCount: Int ommerCount: Long
# Ommers is a list of ommer (AKA uncle) blocks associated with this block. # 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 # If ommers are unavailable, this field will be null. Depending on your
# node, the transactions, transactionAt, transactionCount, ommers, # node, the transactions, transactionAt, transactionCount, ommers,
...@@ -222,7 +224,7 @@ const schema string = ` ...@@ -222,7 +224,7 @@ const schema string = `
ommers: [Block] ommers: [Block]
# OmmerAt returns the ommer (AKA uncle) at the specified index. If ommers # 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. # are unavailable, or the index is out of bounds, this field will be null.
ommerAt(index: Int!): Block ommerAt(index: Long!): Block
# OmmerHash is the keccak256 hash of all the ommers (AKA uncles) # OmmerHash is the keccak256 hash of all the ommers (AKA uncles)
# associated with this block. # associated with this block.
ommerHash: Bytes32! ommerHash: Bytes32!
...@@ -232,7 +234,7 @@ const schema string = ` ...@@ -232,7 +234,7 @@ const schema string = `
# TransactionAt returns the transaction at the specified index. If # TransactionAt returns the transaction at the specified index. If
# transactions are unavailable for this block, or if the index is out of # transactions are unavailable for this block, or if the index is out of
# bounds, this field will be null. # bounds, this field will be null.
transactionAt(index: Int!): Transaction transactionAt(index: Long!): Transaction
# Logs returns a filtered set of logs from this block. # Logs returns a filtered set of logs from this block.
logs(filter: BlockFilterCriteria!): [Log!]! logs(filter: BlockFilterCriteria!): [Log!]!
# Account fetches an Ethereum account at the current block's state. # Account fetches an Ethereum account at the current block's state.
...@@ -317,7 +319,7 @@ const schema string = ` ...@@ -317,7 +319,7 @@ const schema string = `
# Pending represents the current pending state. # Pending represents the current pending state.
type Pending { type Pending {
# TransactionCount is the number of transactions in the pending state. # TransactionCount is the number of transactions in the pending state.
transactionCount: Int! transactionCount: Long!
# Transactions is a list of transactions in the current pending state. # Transactions is a list of transactions in the current pending state.
transactions: [Transaction!] transactions: [Transaction!]
# Account fetches an Ethereum account for the pending state. # Account fetches an Ethereum account for the pending state.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment