Skip to content

Commit adf09ae

Browse files
AmitBRDAmitShahfjl
authored
graphql: add support for tx types and tx access lists (#22491)
This adds support for EIP-2718 access list transactions in the GraphQL API. Co-authored-by: Amit Shah <[email protected]> Co-authored-by: Felix Lange <[email protected]>
1 parent 706683e commit adf09ae

File tree

3 files changed

+180
-4
lines changed

3 files changed

+180
-4
lines changed

graphql/graphql.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,20 @@ func (l *Log) Data(ctx context.Context) hexutil.Bytes {
151151
return l.log.Data
152152
}
153153

154+
// AccessTuple represents EIP-2930
155+
type AccessTuple struct {
156+
address common.Address
157+
storageKeys *[]common.Hash
158+
}
159+
160+
func (at *AccessTuple) Address(ctx context.Context) common.Address {
161+
return at.address
162+
}
163+
164+
func (at *AccessTuple) StorageKeys(ctx context.Context) *[]common.Hash {
165+
return at.storageKeys
166+
}
167+
154168
// Transaction represents an Ethereum transaction.
155169
// backend and hash are mandatory; all others will be fetched when required.
156170
type Transaction struct {
@@ -342,6 +356,31 @@ func (t *Transaction) Logs(ctx context.Context) (*[]*Log, error) {
342356
return &ret, nil
343357
}
344358

359+
func (t *Transaction) Type(ctx context.Context) (*int32, error) {
360+
tx, err := t.resolve(ctx)
361+
if err != nil {
362+
return nil, err
363+
}
364+
txType := int32(tx.Type())
365+
return &txType, nil
366+
}
367+
368+
func (t *Transaction) AccessList(ctx context.Context) (*[]*AccessTuple, error) {
369+
tx, err := t.resolve(ctx)
370+
if err != nil || tx == nil {
371+
return nil, err
372+
}
373+
accessList := tx.AccessList()
374+
ret := make([]*AccessTuple, 0, len(accessList))
375+
for _, al := range accessList {
376+
ret = append(ret, &AccessTuple{
377+
address: al.Address,
378+
storageKeys: &al.StorageKeys,
379+
})
380+
}
381+
return &ret, nil
382+
}
383+
345384
func (t *Transaction) R(ctx context.Context) (hexutil.Big, error) {
346385
tx, err := t.resolve(ctx)
347386
if err != nil || tx == nil {

graphql/graphql_test.go

Lines changed: 132 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,12 @@ import (
2525
"testing"
2626
"time"
2727

28+
"github.com/ethereum/go-ethereum/common"
2829
"github.com/ethereum/go-ethereum/consensus/ethash"
2930
"github.com/ethereum/go-ethereum/core"
31+
"github.com/ethereum/go-ethereum/core/types"
32+
"github.com/ethereum/go-ethereum/core/vm"
33+
"github.com/ethereum/go-ethereum/crypto"
3034
"github.com/ethereum/go-ethereum/eth"
3135
"github.com/ethereum/go-ethereum/eth/ethconfig"
3236
"github.com/ethereum/go-ethereum/node"
@@ -55,7 +59,7 @@ func TestBuildSchema(t *testing.T) {
5559

5660
// Tests that a graphQL request is successfully handled when graphql is enabled on the specified endpoint
5761
func TestGraphQLBlockSerialization(t *testing.T) {
58-
stack := createNode(t, true)
62+
stack := createNode(t, true, false)
5963
defer stack.Close()
6064
// start node
6165
if err := stack.Start(); err != nil {
@@ -157,9 +161,45 @@ func TestGraphQLBlockSerialization(t *testing.T) {
157161
}
158162
}
159163

164+
func TestGraphQLBlockSerializationEIP2718(t *testing.T) {
165+
stack := createNode(t, true, true)
166+
defer stack.Close()
167+
// start node
168+
if err := stack.Start(); err != nil {
169+
t.Fatalf("could not start node: %v", err)
170+
}
171+
172+
for i, tt := range []struct {
173+
body string
174+
want string
175+
code int
176+
}{
177+
{
178+
body: `{"query": "{block {number transactions { from { address } to { address } value hash type accessList { address storageKeys } index}}}"}`,
179+
want: `{"data":{"block":{"number":1,"transactions":[{"from":{"address":"0x71562b71999873db5b286df957af199ec94617f7"},"to":{"address":"0x0000000000000000000000000000000000000dad"},"value":"0x64","hash":"0x4f7b8d718145233dcf7f29e34a969c63dd4de8715c054ea2af022b66c4f4633e","type":0,"accessList":[],"index":0},{"from":{"address":"0x71562b71999873db5b286df957af199ec94617f7"},"to":{"address":"0x0000000000000000000000000000000000000dad"},"value":"0x32","hash":"0x9c6c2c045b618fe87add0e49ba3ca00659076ecae00fd51de3ba5d4ccf9dbf40","type":1,"accessList":[{"address":"0x0000000000000000000000000000000000000dad","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000000"]}],"index":1}]}}}`,
180+
code: 200,
181+
},
182+
} {
183+
resp, err := http.Post(fmt.Sprintf("%s/graphql", stack.HTTPEndpoint()), "application/json", strings.NewReader(tt.body))
184+
if err != nil {
185+
t.Fatalf("could not post: %v", err)
186+
}
187+
bodyBytes, err := ioutil.ReadAll(resp.Body)
188+
if err != nil {
189+
t.Fatalf("could not read from response body: %v", err)
190+
}
191+
if have := string(bodyBytes); have != tt.want {
192+
t.Errorf("testcase %d %s,\nhave:\n%v\nwant:\n%v", i, tt.body, have, tt.want)
193+
}
194+
if tt.code != resp.StatusCode {
195+
t.Errorf("testcase %d %s,\nwrong statuscode, have: %v, want: %v", i, tt.body, resp.StatusCode, tt.code)
196+
}
197+
}
198+
}
199+
160200
// Tests that a graphQL request is not handled successfully when graphql is not enabled on the specified endpoint
161201
func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) {
162-
stack := createNode(t, false)
202+
stack := createNode(t, false, false)
163203
defer stack.Close()
164204
if err := stack.Start(); err != nil {
165205
t.Fatalf("could not start node: %v", err)
@@ -173,7 +213,7 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) {
173213
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
174214
}
175215

176-
func createNode(t *testing.T, gqlEnabled bool) *node.Node {
216+
func createNode(t *testing.T, gqlEnabled bool, txEnabled bool) *node.Node {
177217
stack, err := node.New(&node.Config{
178218
HTTPHost: "127.0.0.1",
179219
HTTPPort: 0,
@@ -186,7 +226,11 @@ func createNode(t *testing.T, gqlEnabled bool) *node.Node {
186226
if !gqlEnabled {
187227
return stack
188228
}
189-
createGQLService(t, stack)
229+
if !txEnabled {
230+
createGQLService(t, stack)
231+
} else {
232+
createGQLServiceWithTransactions(t, stack)
233+
}
190234
return stack
191235
}
192236

@@ -226,3 +270,87 @@ func createGQLService(t *testing.T, stack *node.Node) {
226270
t.Fatalf("could not create graphql service: %v", err)
227271
}
228272
}
273+
274+
func createGQLServiceWithTransactions(t *testing.T, stack *node.Node) {
275+
// create backend
276+
key, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
277+
address := crypto.PubkeyToAddress(key.PublicKey)
278+
funds := big.NewInt(1000000000)
279+
dad := common.HexToAddress("0x0000000000000000000000000000000000000dad")
280+
281+
ethConf := &ethconfig.Config{
282+
Genesis: &core.Genesis{
283+
Config: params.AllEthashProtocolChanges,
284+
GasLimit: 11500000,
285+
Difficulty: big.NewInt(1048576),
286+
Alloc: core.GenesisAlloc{
287+
address: {Balance: funds},
288+
// The address 0xdad sloads 0x00 and 0x01
289+
dad: {
290+
Code: []byte{
291+
byte(vm.PC),
292+
byte(vm.PC),
293+
byte(vm.SLOAD),
294+
byte(vm.SLOAD),
295+
},
296+
Nonce: 0,
297+
Balance: big.NewInt(0),
298+
},
299+
},
300+
},
301+
Ethash: ethash.Config{
302+
PowMode: ethash.ModeFake,
303+
},
304+
NetworkId: 1337,
305+
TrieCleanCache: 5,
306+
TrieCleanCacheJournal: "triecache",
307+
TrieCleanCacheRejournal: 60 * time.Minute,
308+
TrieDirtyCache: 5,
309+
TrieTimeout: 60 * time.Minute,
310+
SnapshotCache: 5,
311+
}
312+
313+
ethBackend, err := eth.New(stack, ethConf)
314+
if err != nil {
315+
t.Fatalf("could not create eth backend: %v", err)
316+
}
317+
signer := types.LatestSigner(ethConf.Genesis.Config)
318+
319+
legacyTx, _ := types.SignNewTx(key, signer, &types.LegacyTx{
320+
Nonce: uint64(0),
321+
To: &dad,
322+
Value: big.NewInt(100),
323+
Gas: 50000,
324+
GasPrice: big.NewInt(1),
325+
})
326+
envelopTx, _ := types.SignNewTx(key, signer, &types.AccessListTx{
327+
ChainID: ethConf.Genesis.Config.ChainID,
328+
Nonce: uint64(1),
329+
To: &dad,
330+
Gas: 30000,
331+
GasPrice: big.NewInt(1),
332+
Value: big.NewInt(50),
333+
AccessList: types.AccessList{{
334+
Address: dad,
335+
StorageKeys: []common.Hash{{0}},
336+
}},
337+
})
338+
339+
// Create some blocks and import them
340+
chain, _ := core.GenerateChain(params.AllEthashProtocolChanges, ethBackend.BlockChain().Genesis(),
341+
ethash.NewFaker(), ethBackend.ChainDb(), 1, func(i int, b *core.BlockGen) {
342+
b.SetCoinbase(common.Address{1})
343+
b.AddTx(legacyTx)
344+
b.AddTx(envelopTx)
345+
})
346+
347+
_, err = ethBackend.BlockChain().InsertChain(chain)
348+
if err != nil {
349+
t.Fatalf("could not create import blocks: %v", err)
350+
}
351+
// create gql service
352+
err = New(stack, ethBackend.APIBackend, []string{}, []string{})
353+
if err != nil {
354+
t.Fatalf("could not create graphql service: %v", err)
355+
}
356+
}

graphql/schema.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ const schema string = `
6969
transaction: Transaction!
7070
}
7171
72+
#EIP-2718
73+
type AccessTuple{
74+
address: Address!
75+
storageKeys : [Bytes32!]
76+
}
77+
7278
# Transaction is an Ethereum transaction.
7379
type Transaction {
7480
# Hash is the hash of this transaction.
@@ -118,6 +124,9 @@ const schema string = `
118124
r: BigInt!
119125
s: BigInt!
120126
v: BigInt!
127+
#Envelope transaction support
128+
type: Int
129+
accessList: [AccessTuple!]
121130
}
122131
123132
# BlockFilterCriteria encapsulates log filter criteria for a filter applied

0 commit comments

Comments
 (0)