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>
This commit is contained in:
		
							parent
							
								
									706683ea72
								
							
						
					
					
						commit
						adf09aeab1
					
				| @ -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 { | ||||
|  | ||||
| @ -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 | ||||
| 	} | ||||
| 	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 := ðconfig.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) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user