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:
AmitBRD 2021-04-06 08:58:36 -05:00 committed by GitHub
parent 706683ea72
commit adf09aeab1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 180 additions and 4 deletions

View File

@ -151,6 +151,20 @@ func (l *Log) Data(ctx context.Context) hexutil.Bytes {
return l.log.Data 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. // Transaction represents an Ethereum transaction.
// backend and hash are mandatory; all others will be fetched when required. // backend and hash are mandatory; all others will be fetched when required.
type Transaction struct { type Transaction struct {
@ -342,6 +356,31 @@ func (t *Transaction) Logs(ctx context.Context) (*[]*Log, error) {
return &ret, nil 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) { func (t *Transaction) R(ctx context.Context) (hexutil.Big, error) {
tx, err := t.resolve(ctx) tx, err := t.resolve(ctx)
if err != nil || tx == nil { if err != nil || tx == nil {

View File

@ -25,8 +25,12 @@ import (
"testing" "testing"
"time" "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core" "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"
"github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/node" "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 // Tests that a graphQL request is successfully handled when graphql is enabled on the specified endpoint
func TestGraphQLBlockSerialization(t *testing.T) { func TestGraphQLBlockSerialization(t *testing.T) {
stack := createNode(t, true) stack := createNode(t, true, false)
defer stack.Close() defer stack.Close()
// start node // start node
if err := stack.Start(); err != nil { 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 // 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) { func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) {
stack := createNode(t, false) stack := createNode(t, false, false)
defer stack.Close() defer stack.Close()
if err := stack.Start(); err != nil { if err := stack.Start(); err != nil {
t.Fatalf("could not start node: %v", err) 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) 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{ stack, err := node.New(&node.Config{
HTTPHost: "127.0.0.1", HTTPHost: "127.0.0.1",
HTTPPort: 0, HTTPPort: 0,
@ -186,7 +226,11 @@ func createNode(t *testing.T, gqlEnabled bool) *node.Node {
if !gqlEnabled { if !gqlEnabled {
return stack return stack
} }
createGQLService(t, stack) if !txEnabled {
createGQLService(t, stack)
} else {
createGQLServiceWithTransactions(t, stack)
}
return stack return stack
} }
@ -226,3 +270,87 @@ func createGQLService(t *testing.T, stack *node.Node) {
t.Fatalf("could not create graphql service: %v", err) 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)
}
}

View File

@ -69,6 +69,12 @@ const schema string = `
transaction: Transaction! transaction: Transaction!
} }
#EIP-2718
type AccessTuple{
address: Address!
storageKeys : [Bytes32!]
}
# Transaction is an Ethereum transaction. # Transaction is an Ethereum transaction.
type Transaction { type Transaction {
# Hash is the hash of this transaction. # Hash is the hash of this transaction.
@ -118,6 +124,9 @@ const schema string = `
r: BigInt! r: BigInt!
s: BigInt! s: BigInt!
v: BigInt! v: BigInt!
#Envelope transaction support
type: Int
accessList: [AccessTuple!]
} }
# BlockFilterCriteria encapsulates log filter criteria for a filter applied # BlockFilterCriteria encapsulates log filter criteria for a filter applied