From adf09aeab1fc091e4ab56c04852b18b4b924eb50 Mon Sep 17 00:00:00 2001
From: AmitBRD <60668103+AmitBRD@users.noreply.github.com>
Date: Tue, 6 Apr 2021 08:58:36 -0500
Subject: [PATCH] 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 <amitshah0t7@gmail.com>
Co-authored-by: Felix Lange <fjl@twurst.com>
---
 graphql/graphql.go      |  39 ++++++++++++
 graphql/graphql_test.go | 136 ++++++++++++++++++++++++++++++++++++++--
 graphql/schema.go       |   9 +++
 3 files changed, 180 insertions(+), 4 deletions(-)

diff --git a/graphql/graphql.go b/graphql/graphql.go
index 2374beb8e..b1af7675b 100644
--- a/graphql/graphql.go
+++ b/graphql/graphql.go
@@ -151,6 +151,20 @@ func (l *Log) Data(ctx context.Context) hexutil.Bytes {
 	return l.log.Data
 }
 
+// AccessTuple represents EIP-2930
+type AccessTuple struct {
+	address     common.Address
+	storageKeys *[]common.Hash
+}
+
+func (at *AccessTuple) Address(ctx context.Context) common.Address {
+	return at.address
+}
+
+func (at *AccessTuple) StorageKeys(ctx context.Context) *[]common.Hash {
+	return at.storageKeys
+}
+
 // Transaction represents an Ethereum transaction.
 // backend and hash are mandatory; all others will be fetched when required.
 type Transaction struct {
@@ -342,6 +356,31 @@ func (t *Transaction) Logs(ctx context.Context) (*[]*Log, error) {
 	return &ret, nil
 }
 
+func (t *Transaction) Type(ctx context.Context) (*int32, error) {
+	tx, err := t.resolve(ctx)
+	if err != nil {
+		return nil, err
+	}
+	txType := int32(tx.Type())
+	return &txType, nil
+}
+
+func (t *Transaction) AccessList(ctx context.Context) (*[]*AccessTuple, error) {
+	tx, err := t.resolve(ctx)
+	if err != nil || tx == nil {
+		return nil, err
+	}
+	accessList := tx.AccessList()
+	ret := make([]*AccessTuple, 0, len(accessList))
+	for _, al := range accessList {
+		ret = append(ret, &AccessTuple{
+			address:     al.Address,
+			storageKeys: &al.StorageKeys,
+		})
+	}
+	return &ret, nil
+}
+
 func (t *Transaction) R(ctx context.Context) (hexutil.Big, error) {
 	tx, err := t.resolve(ctx)
 	if err != nil || tx == nil {
diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go
index 2f3b23032..2404ff45f 100644
--- a/graphql/graphql_test.go
+++ b/graphql/graphql_test.go
@@ -25,8 +25,12 @@ import (
 	"testing"
 	"time"
 
+	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/consensus/ethash"
 	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/core/vm"
+	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/eth"
 	"github.com/ethereum/go-ethereum/eth/ethconfig"
 	"github.com/ethereum/go-ethereum/node"
@@ -55,7 +59,7 @@ func TestBuildSchema(t *testing.T) {
 
 // Tests that a graphQL request is successfully handled when graphql is enabled on the specified endpoint
 func TestGraphQLBlockSerialization(t *testing.T) {
-	stack := createNode(t, true)
+	stack := createNode(t, true, false)
 	defer stack.Close()
 	// start node
 	if err := stack.Start(); err != nil {
@@ -157,9 +161,45 @@ func TestGraphQLBlockSerialization(t *testing.T) {
 	}
 }
 
+func TestGraphQLBlockSerializationEIP2718(t *testing.T) {
+	stack := createNode(t, true, true)
+	defer stack.Close()
+	// start node
+	if err := stack.Start(); err != nil {
+		t.Fatalf("could not start node: %v", err)
+	}
+
+	for i, tt := range []struct {
+		body string
+		want string
+		code int
+	}{
+		{
+			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":"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}]}}}`,
+			code: 200,
+		},
+	} {
+		resp, err := http.Post(fmt.Sprintf("%s/graphql", stack.HTTPEndpoint()), "application/json", strings.NewReader(tt.body))
+		if err != nil {
+			t.Fatalf("could not post: %v", err)
+		}
+		bodyBytes, err := ioutil.ReadAll(resp.Body)
+		if err != nil {
+			t.Fatalf("could not read from response body: %v", err)
+		}
+		if have := string(bodyBytes); have != tt.want {
+			t.Errorf("testcase %d %s,\nhave:\n%v\nwant:\n%v", i, tt.body, have, tt.want)
+		}
+		if tt.code != resp.StatusCode {
+			t.Errorf("testcase %d %s,\nwrong statuscode, have: %v, want: %v", i, tt.body, resp.StatusCode, tt.code)
+		}
+	}
+}
+
 // Tests that a graphQL request is not handled successfully when graphql is not enabled on the specified endpoint
 func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) {
-	stack := createNode(t, false)
+	stack := createNode(t, false, false)
 	defer stack.Close()
 	if err := stack.Start(); err != nil {
 		t.Fatalf("could not start node: %v", err)
@@ -173,7 +213,7 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) {
 	assert.Equal(t, http.StatusNotFound, resp.StatusCode)
 }
 
-func createNode(t *testing.T, gqlEnabled bool) *node.Node {
+func createNode(t *testing.T, gqlEnabled bool, txEnabled bool) *node.Node {
 	stack, err := node.New(&node.Config{
 		HTTPHost: "127.0.0.1",
 		HTTPPort: 0,
@@ -186,7 +226,11 @@ func createNode(t *testing.T, gqlEnabled bool) *node.Node {
 	if !gqlEnabled {
 		return stack
 	}
-	createGQLService(t, stack)
+	if !txEnabled {
+		createGQLService(t, stack)
+	} else {
+		createGQLServiceWithTransactions(t, stack)
+	}
 	return stack
 }
 
@@ -226,3 +270,87 @@ func createGQLService(t *testing.T, stack *node.Node) {
 		t.Fatalf("could not create graphql service: %v", err)
 	}
 }
+
+func createGQLServiceWithTransactions(t *testing.T, stack *node.Node) {
+	// create backend
+	key, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+	address := crypto.PubkeyToAddress(key.PublicKey)
+	funds := big.NewInt(1000000000)
+	dad := common.HexToAddress("0x0000000000000000000000000000000000000dad")
+
+	ethConf := &ethconfig.Config{
+		Genesis: &core.Genesis{
+			Config:     params.AllEthashProtocolChanges,
+			GasLimit:   11500000,
+			Difficulty: big.NewInt(1048576),
+			Alloc: core.GenesisAlloc{
+				address: {Balance: funds},
+				// The address 0xdad sloads 0x00 and 0x01
+				dad: {
+					Code: []byte{
+						byte(vm.PC),
+						byte(vm.PC),
+						byte(vm.SLOAD),
+						byte(vm.SLOAD),
+					},
+					Nonce:   0,
+					Balance: big.NewInt(0),
+				},
+			},
+		},
+		Ethash: ethash.Config{
+			PowMode: ethash.ModeFake,
+		},
+		NetworkId:               1337,
+		TrieCleanCache:          5,
+		TrieCleanCacheJournal:   "triecache",
+		TrieCleanCacheRejournal: 60 * time.Minute,
+		TrieDirtyCache:          5,
+		TrieTimeout:             60 * time.Minute,
+		SnapshotCache:           5,
+	}
+
+	ethBackend, err := eth.New(stack, ethConf)
+	if err != nil {
+		t.Fatalf("could not create eth backend: %v", err)
+	}
+	signer := types.LatestSigner(ethConf.Genesis.Config)
+
+	legacyTx, _ := types.SignNewTx(key, signer, &types.LegacyTx{
+		Nonce:    uint64(0),
+		To:       &dad,
+		Value:    big.NewInt(100),
+		Gas:      50000,
+		GasPrice: big.NewInt(1),
+	})
+	envelopTx, _ := types.SignNewTx(key, signer, &types.AccessListTx{
+		ChainID:  ethConf.Genesis.Config.ChainID,
+		Nonce:    uint64(1),
+		To:       &dad,
+		Gas:      30000,
+		GasPrice: big.NewInt(1),
+		Value:    big.NewInt(50),
+		AccessList: types.AccessList{{
+			Address:     dad,
+			StorageKeys: []common.Hash{{0}},
+		}},
+	})
+
+	// Create some blocks and import them
+	chain, _ := core.GenerateChain(params.AllEthashProtocolChanges, ethBackend.BlockChain().Genesis(),
+		ethash.NewFaker(), ethBackend.ChainDb(), 1, func(i int, b *core.BlockGen) {
+			b.SetCoinbase(common.Address{1})
+			b.AddTx(legacyTx)
+			b.AddTx(envelopTx)
+		})
+
+	_, err = ethBackend.BlockChain().InsertChain(chain)
+	if err != nil {
+		t.Fatalf("could not create import blocks: %v", err)
+	}
+	// create gql service
+	err = New(stack, ethBackend.APIBackend, []string{}, []string{})
+	if err != nil {
+		t.Fatalf("could not create graphql service: %v", err)
+	}
+}
diff --git a/graphql/schema.go b/graphql/schema.go
index 6ea63db63..d792bb915 100644
--- a/graphql/schema.go
+++ b/graphql/schema.go
@@ -69,6 +69,12 @@ const schema string = `
         transaction: Transaction!
     }
 
+    #EIP-2718 
+    type AccessTuple{
+        address: Address!
+        storageKeys : [Bytes32!]
+    }
+
     # Transaction is an Ethereum transaction.
     type Transaction {
         # Hash is the hash of this transaction.
@@ -118,6 +124,9 @@ const schema string = `
         r: BigInt!
         s: BigInt!
         v: BigInt!
+        #Envelope transaction support
+        type: Int
+        accessList: [AccessTuple!]
     }
 
     # BlockFilterCriteria encapsulates log filter criteria for a filter applied
-- 
GitLab