Merge pull request #77 from vulcanize/ashwinp-gql-get-storage-n-logs
GQL API for getStorageAt and getLogs
This commit is contained in:
commit
afc63ac960
6
go.mod
6
go.mod
@ -12,17 +12,19 @@ require (
|
|||||||
github.com/ipfs/go-ipld-format v0.2.0
|
github.com/ipfs/go-ipld-format v0.2.0
|
||||||
github.com/jmoiron/sqlx v1.2.0
|
github.com/jmoiron/sqlx v1.2.0
|
||||||
github.com/lib/pq v1.8.0
|
github.com/lib/pq v1.8.0
|
||||||
|
github.com/machinebox/graphql v0.2.2 // indirect
|
||||||
github.com/multiformats/go-multihash v0.0.14
|
github.com/multiformats/go-multihash v0.0.14
|
||||||
github.com/onsi/ginkgo v1.15.0
|
github.com/onsi/ginkgo v1.15.0
|
||||||
github.com/onsi/gomega v1.10.1
|
github.com/onsi/gomega v1.10.1
|
||||||
github.com/prometheus/client_golang v1.5.1
|
github.com/prometheus/client_golang v1.5.1
|
||||||
|
github.com/shirou/gopsutil v3.21.5+incompatible // indirect
|
||||||
github.com/sirupsen/logrus v1.7.0
|
github.com/sirupsen/logrus v1.7.0
|
||||||
github.com/spf13/cobra v1.1.1
|
github.com/spf13/cobra v1.1.1
|
||||||
github.com/spf13/viper v1.7.0
|
github.com/spf13/viper v1.7.0
|
||||||
github.com/vulcanize/ipld-eth-indexer v0.7.1-alpha
|
github.com/tklauser/go-sysconf v0.3.6 // indirect
|
||||||
github.com/vulcanize/gap-filler v0.3.1
|
github.com/vulcanize/gap-filler v0.3.1
|
||||||
github.com/vulcanize/ipfs-ethdb v0.0.2-alpha
|
github.com/vulcanize/ipfs-ethdb v0.0.2-alpha
|
||||||
golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b // indirect
|
github.com/vulcanize/ipld-eth-indexer v0.7.1-alpha
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/ethereum/go-ethereum v1.9.25 => github.com/vulcanize/go-ethereum v1.9.25-statediff-0.0.15
|
replace github.com/ethereum/go-ethereum v1.9.25 => github.com/vulcanize/go-ethereum v1.9.25-statediff-0.0.15
|
||||||
|
10
go.sum
10
go.sum
@ -697,6 +697,8 @@ github.com/libp2p/go-yamux v1.3.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZ
|
|||||||
github.com/libp2p/go-yamux v1.3.5/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow=
|
github.com/libp2p/go-yamux v1.3.5/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow=
|
||||||
github.com/lucas-clemente/quic-go v0.15.7/go.mod h1:Myi1OyS0FOjL3not4BxT7KN29bRkcMUV5JVVFLKtDp8=
|
github.com/lucas-clemente/quic-go v0.15.7/go.mod h1:Myi1OyS0FOjL3not4BxT7KN29bRkcMUV5JVVFLKtDp8=
|
||||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||||
|
github.com/machinebox/graphql v0.2.2 h1:dWKpJligYKhYKO5A2gvNhkJdQMNZeChZYyBbrZkBZfo=
|
||||||
|
github.com/machinebox/graphql v0.2.2/go.mod h1:F+kbVMHuwrQ5tYgU9JXlnskM8nOaFxCAEolaQybkjWA=
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
@ -908,6 +910,8 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm
|
|||||||
github.com/shirou/gopsutil v2.20.5-0.20200531151128-663af789c085+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
github.com/shirou/gopsutil v2.20.5-0.20200531151128-663af789c085+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||||
github.com/shirou/gopsutil v2.20.5+incompatible h1:tYH07UPoQt0OCQdgWWMgYHy3/a9bcxNpBIysykNIP7I=
|
github.com/shirou/gopsutil v2.20.5+incompatible h1:tYH07UPoQt0OCQdgWWMgYHy3/a9bcxNpBIysykNIP7I=
|
||||||
github.com/shirou/gopsutil v2.20.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
github.com/shirou/gopsutil v2.20.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||||
|
github.com/shirou/gopsutil v3.21.5+incompatible h1:OloQyEerMi7JUrXiNzy8wQ5XN+baemxSl12QgIzt0jc=
|
||||||
|
github.com/shirou/gopsutil v3.21.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
||||||
@ -994,6 +998,10 @@ github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca h1:Ld/zXl5t4+D6
|
|||||||
github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM=
|
github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM=
|
||||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||||
github.com/texttheater/golang-levenshtein v0.0.0-20180516184445-d188e65d659e/go.mod h1:XDKHRm5ThF8YJjx001LtgelzsoaEcvnA7lVWz9EeX3g=
|
github.com/texttheater/golang-levenshtein v0.0.0-20180516184445-d188e65d659e/go.mod h1:XDKHRm5ThF8YJjx001LtgelzsoaEcvnA7lVWz9EeX3g=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.6 h1:oc1sJWvKkmvIxhDHeKWvZS4f6AW+YcoguSfRF2/Hmo4=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.6/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
|
||||||
|
github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA=
|
||||||
|
github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM=
|
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM=
|
||||||
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4=
|
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4=
|
||||||
@ -1236,6 +1244,8 @@ golang.org/x/sys v0.0.0-20210218145245-beda7e5e158e h1:f5mksnk+hgXHnImpZoWj64ja9
|
|||||||
golang.org/x/sys v0.0.0-20210218145245-beda7e5e158e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210218145245-beda7e5e158e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b h1:lAZ0/chPUDWwjqosYR0X4M490zQhMsiJ4K3DbA7o+3g=
|
golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b h1:lAZ0/chPUDWwjqosYR0X4M490zQhMsiJ4K3DbA7o+3g=
|
||||||
golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa h1:ZYxPR6aca/uhfRJyaOAtflSHjJYiktO7QnJC5ut7iY4=
|
||||||
|
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
@ -41,7 +41,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
|
|
||||||
"github.com/vulcanize/ipfs-ethdb"
|
ipfsethdb "github.com/vulcanize/ipfs-ethdb"
|
||||||
"github.com/vulcanize/ipld-eth-indexer/pkg/ipfs"
|
"github.com/vulcanize/ipld-eth-indexer/pkg/ipfs"
|
||||||
"github.com/vulcanize/ipld-eth-indexer/pkg/postgres"
|
"github.com/vulcanize/ipld-eth-indexer/pkg/postgres"
|
||||||
shared2 "github.com/vulcanize/ipld-eth-indexer/pkg/shared"
|
shared2 "github.com/vulcanize/ipld-eth-indexer/pkg/shared"
|
||||||
@ -509,6 +509,7 @@ func (b *Backend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log
|
|||||||
if err := rlp.DecodeBytes(rctBytes, &rct); err != nil {
|
if err := rlp.DecodeBytes(rctBytes, &rct); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
logs[i] = rct.Logs
|
logs[i] = rct.Logs
|
||||||
}
|
}
|
||||||
return logs, nil
|
return logs, nil
|
||||||
@ -766,7 +767,7 @@ func (b *Backend) GetStorageByHash(ctx context.Context, address common.Address,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, storageRlp, err := b.IPLDRetriever.RetrieveStorageAtByAddressAndStorageSlotAndBlockHash(address, key, hash)
|
_, _, storageRlp, err := b.IPLDRetriever.RetrieveStorageAtByAddressAndStorageSlotAndBlockHash(address, key, hash)
|
||||||
return storageRlp, err
|
return storageRlp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -429,25 +429,25 @@ func (r *IPLDRetriever) RetrieveAccountByAddressAndBlockNumber(address common.Ad
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RetrieveStorageAtByAddressAndStorageSlotAndBlockHash returns the cid and rlp bytes for the storage value corresponding to the provided address, storage slot, and block hash
|
// RetrieveStorageAtByAddressAndStorageSlotAndBlockHash returns the cid and rlp bytes for the storage value corresponding to the provided address, storage slot, and block hash
|
||||||
func (r *IPLDRetriever) RetrieveStorageAtByAddressAndStorageSlotAndBlockHash(address common.Address, key, hash common.Hash) (string, []byte, error) {
|
func (r *IPLDRetriever) RetrieveStorageAtByAddressAndStorageSlotAndBlockHash(address common.Address, key, hash common.Hash) (string, []byte, []byte, error) {
|
||||||
storageResult := new(nodeInfo)
|
storageResult := new(nodeInfo)
|
||||||
stateLeafKey := crypto.Keccak256Hash(address.Bytes())
|
stateLeafKey := crypto.Keccak256Hash(address.Bytes())
|
||||||
storageHash := crypto.Keccak256Hash(key.Bytes())
|
storageHash := crypto.Keccak256Hash(key.Bytes())
|
||||||
if err := r.db.Get(storageResult, RetrieveStorageLeafByAddressHashAndLeafKeyAndBlockHashPgStr, stateLeafKey.Hex(), storageHash.Hex(), hash.Hex()); err != nil {
|
if err := r.db.Get(storageResult, RetrieveStorageLeafByAddressHashAndLeafKeyAndBlockHashPgStr, stateLeafKey.Hex(), storageHash.Hex(), hash.Hex()); err != nil {
|
||||||
return "", nil, err
|
return "", nil, nil, err
|
||||||
}
|
}
|
||||||
if storageResult.Removed {
|
if storageResult.Removed {
|
||||||
return "", []byte{}, nil
|
return "", []byte{}, []byte{}, nil
|
||||||
}
|
}
|
||||||
var i []interface{}
|
var i []interface{}
|
||||||
if err := rlp.DecodeBytes(storageResult.Data, &i); err != nil {
|
if err := rlp.DecodeBytes(storageResult.Data, &i); err != nil {
|
||||||
err = fmt.Errorf("error decoding storage leaf node rlp: %s", err.Error())
|
err = fmt.Errorf("error decoding storage leaf node rlp: %s", err.Error())
|
||||||
return "", nil, err
|
return "", nil, nil, err
|
||||||
}
|
}
|
||||||
if len(i) != 2 {
|
if len(i) != 2 {
|
||||||
return "", nil, fmt.Errorf("eth IPLDRetriever expected storage leaf node rlp to decode into two elements")
|
return "", nil, nil, fmt.Errorf("eth IPLDRetriever expected storage leaf node rlp to decode into two elements")
|
||||||
}
|
}
|
||||||
return storageResult.CID, i[1].([]byte), nil
|
return storageResult.CID, storageResult.Data, i[1].([]byte), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RetrieveStorageAtByAddressAndStorageKeyAndBlockNumber returns the cid and rlp bytes for the storage value corresponding to the provided address, storage key, and block number
|
// RetrieveStorageAtByAddressAndStorageKeyAndBlockNumber returns the cid and rlp bytes for the storage value corresponding to the provided address, storage key, and block number
|
||||||
|
104
pkg/graphql/client.go
Normal file
104
pkg/graphql/client.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package graphql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
gqlclient "github.com/machinebox/graphql"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StorageResponse struct {
|
||||||
|
Cid string `json:"cid"`
|
||||||
|
Value common.Hash `json:"value"`
|
||||||
|
IpldBlock hexutil.Bytes `json:"ipldBlock"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetStorageAt struct {
|
||||||
|
Response StorageResponse `json:"getStorageAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogResponse struct {
|
||||||
|
Topics []common.Hash `json:"topics"`
|
||||||
|
Data hexutil.Bytes `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetLogs struct {
|
||||||
|
Responses []LogResponse `json:"getLogs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
client *gqlclient.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(endpoint string) *Client {
|
||||||
|
client := gqlclient.NewClient(endpoint)
|
||||||
|
return &Client{client: client}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetLogs(ctx context.Context, hash common.Hash, address common.Address) ([]LogResponse, error) {
|
||||||
|
getLogsQuery := fmt.Sprintf(`
|
||||||
|
query{
|
||||||
|
getLogs(blockHash: "%s", contract: "%s") {
|
||||||
|
data
|
||||||
|
topics
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, hash.String(), address.String())
|
||||||
|
|
||||||
|
req := gqlclient.NewRequest(getLogsQuery)
|
||||||
|
req.Header.Set("Cache-Control", "no-cache")
|
||||||
|
|
||||||
|
var respData map[string]interface{}
|
||||||
|
err := c.client.Run(ctx, req, &respData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonStr, err := json.Marshal(respData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var logs GetLogs
|
||||||
|
err = json.Unmarshal(jsonStr, &logs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return logs.Responses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetStorageAt(ctx context.Context, hash common.Hash, address common.Address, slot string) (*StorageResponse, error) {
|
||||||
|
getLogsQuery := fmt.Sprintf(`
|
||||||
|
query{
|
||||||
|
getStorageAt(blockHash: "%s", contract: "%s",slot: "%s") {
|
||||||
|
cid
|
||||||
|
value
|
||||||
|
ipldBlock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, hash.String(), address.String(), common.HexToHash(slot))
|
||||||
|
|
||||||
|
req := gqlclient.NewRequest(getLogsQuery)
|
||||||
|
req.Header.Set("Cache-Control", "no-cache")
|
||||||
|
|
||||||
|
var respData map[string]interface{}
|
||||||
|
err := c.client.Run(ctx, req, &respData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonStr, err := json.Marshal(respData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var storageAt GetStorageAt
|
||||||
|
err = json.Unmarshal(jsonStr, &storageAt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &storageAt.Response, nil
|
||||||
|
}
|
@ -19,6 +19,7 @@ package graphql
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/eth/filters"
|
"github.com/ethereum/go-ethereum/eth/filters"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
|
||||||
"github.com/vulcanize/ipld-eth-server/pkg/eth"
|
"github.com/vulcanize/ipld-eth-server/pkg/eth"
|
||||||
@ -91,6 +93,8 @@ type Log struct {
|
|||||||
backend *eth.Backend
|
backend *eth.Backend
|
||||||
transaction *Transaction
|
transaction *Transaction
|
||||||
log *types.Log
|
log *types.Log
|
||||||
|
cid string
|
||||||
|
ipldBlock []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Log) Transaction(ctx context.Context) *Transaction {
|
func (l *Log) Transaction(ctx context.Context) *Transaction {
|
||||||
@ -117,6 +121,14 @@ func (l *Log) Data(ctx context.Context) hexutil.Bytes {
|
|||||||
return hexutil.Bytes(l.log.Data)
|
return hexutil.Bytes(l.log.Data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *Log) Cid(ctx context.Context) string {
|
||||||
|
return l.cid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Log) IpldBlock(ctx context.Context) hexutil.Bytes {
|
||||||
|
return hexutil.Bytes(l.ipldBlock)
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
@ -944,3 +956,84 @@ func (r *Resolver) Logs(ctx context.Context, args struct{ Filter FilterCriteria
|
|||||||
filter := filters.NewRangeFilter(filters.Backend(r.backend), begin, end, addresses, topics)
|
filter := filters.NewRangeFilter(filters.Backend(r.backend), begin, end, addresses, topics)
|
||||||
return runFilter(ctx, r.backend, filter)
|
return runFilter(ctx, r.backend, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StorageResult represents a storage slot value. All arguments are mandatory.
|
||||||
|
type StorageResult struct {
|
||||||
|
value []byte
|
||||||
|
cid string
|
||||||
|
ipldBlock []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StorageResult) Value(ctx context.Context) common.Hash {
|
||||||
|
return common.BytesToHash(s.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StorageResult) Cid(ctx context.Context) string {
|
||||||
|
return s.cid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StorageResult) IpldBlock(ctx context.Context) hexutil.Bytes {
|
||||||
|
return hexutil.Bytes(s.ipldBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) GetStorageAt(ctx context.Context, args struct {
|
||||||
|
BlockHash common.Hash
|
||||||
|
Contract common.Address
|
||||||
|
Slot common.Hash
|
||||||
|
}) (*StorageResult, error) {
|
||||||
|
cid, ipldBlock, rlpValue, err := r.backend.IPLDRetriever.RetrieveStorageAtByAddressAndStorageSlotAndBlockHash(args.Contract, args.Slot, args.BlockHash)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
ret := StorageResult{value: []byte{}, cid: "", ipldBlock: []byte{}}
|
||||||
|
|
||||||
|
return &ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var value interface{}
|
||||||
|
err = rlp.DecodeBytes(rlpValue, &value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := StorageResult{value: value.([]byte), cid: cid, ipldBlock: ipldBlock}
|
||||||
|
return &ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) GetLogs(ctx context.Context, args struct {
|
||||||
|
BlockHash common.Hash
|
||||||
|
Contract common.Address
|
||||||
|
}) (*[]*Log, error) {
|
||||||
|
ret := make([]*Log, 0, 10)
|
||||||
|
|
||||||
|
receiptCIDs, receiptsBytes, err := r.backend.IPLDRetriever.RetrieveReceiptsByBlockHash(args.BlockHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
receipts := make(types.Receipts, len(receiptsBytes))
|
||||||
|
for index, receiptBytes := range receiptsBytes {
|
||||||
|
receiptCID := receiptCIDs[index]
|
||||||
|
receipt := new(types.Receipt)
|
||||||
|
if err := rlp.DecodeBytes(receiptBytes, receipt); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
receipts[index] = receipt
|
||||||
|
for _, log := range receipt.Logs {
|
||||||
|
if log.Address == args.Contract {
|
||||||
|
ret = append(ret, &Log{
|
||||||
|
backend: r.backend,
|
||||||
|
log: log,
|
||||||
|
cid: receiptCID,
|
||||||
|
ipldBlock: receiptBytes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ret, nil
|
||||||
|
}
|
||||||
|
@ -17,15 +17,213 @@
|
|||||||
package graphql_test
|
package graphql_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"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/params"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"github.com/ethereum/go-ethereum/statediff"
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
eth2 "github.com/vulcanize/ipld-eth-indexer/pkg/eth"
|
||||||
|
"github.com/vulcanize/ipld-eth-indexer/pkg/postgres"
|
||||||
|
"github.com/vulcanize/ipld-eth-indexer/pkg/shared"
|
||||||
|
"github.com/vulcanize/ipld-eth-server/pkg/eth"
|
||||||
|
"github.com/vulcanize/ipld-eth-server/pkg/eth/test_helpers"
|
||||||
"github.com/vulcanize/ipld-eth-server/pkg/graphql"
|
"github.com/vulcanize/ipld-eth-server/pkg/graphql"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("GraphQL", func() {
|
var _ = Describe("GraphQL", func() {
|
||||||
It("Builds the schema and creates a new handler", func() {
|
const (
|
||||||
_, err := graphql.NewHandler(nil)
|
gqlEndPoint = "127.0.0.1:8083"
|
||||||
|
)
|
||||||
|
var (
|
||||||
|
randomAddr = common.HexToAddress("0x1C3ab14BBaD3D99F4203bd7a11aCB94882050E6f")
|
||||||
|
randomHash = crypto.Keccak256Hash(randomAddr.Bytes())
|
||||||
|
blocks []*types.Block
|
||||||
|
receipts []types.Receipts
|
||||||
|
chain *core.BlockChain
|
||||||
|
db *postgres.DB
|
||||||
|
blockHashes []common.Hash
|
||||||
|
backend *eth.Backend
|
||||||
|
graphQLServer *graphql.Service
|
||||||
|
chainConfig = params.TestChainConfig
|
||||||
|
mockTD = big.NewInt(1337)
|
||||||
|
client = graphql.NewClient(fmt.Sprintf("http://%s/graphql", gqlEndPoint))
|
||||||
|
ctx = context.Background()
|
||||||
|
blockHash common.Hash
|
||||||
|
contractAddress common.Address
|
||||||
|
)
|
||||||
|
|
||||||
|
It("test init", func() {
|
||||||
|
var err error
|
||||||
|
db, err = shared.SetupDB()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
transformer := eth2.NewStateDiffTransformer(chainConfig, db)
|
||||||
|
backend, err = eth.NewEthBackend(db, ð.Config{
|
||||||
|
ChainConfig: chainConfig,
|
||||||
|
VmConfig: vm.Config{},
|
||||||
|
RPCGasCap: big.NewInt(10000000000),
|
||||||
|
})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
// make the test blockchain (and state)
|
||||||
|
blocks, receipts, chain = test_helpers.MakeChain(5, test_helpers.Genesis, test_helpers.TestChainGen)
|
||||||
|
params := statediff.Params{
|
||||||
|
IntermediateStateNodes: true,
|
||||||
|
IntermediateStorageNodes: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate over the blocks, generating statediff payloads, and transforming the data into Postgres
|
||||||
|
builder := statediff.NewBuilder(chain.StateCache())
|
||||||
|
for i, block := range blocks {
|
||||||
|
blockHashes = append(blockHashes, block.Hash())
|
||||||
|
var args statediff.Args
|
||||||
|
var rcts types.Receipts
|
||||||
|
if i == 0 {
|
||||||
|
args = statediff.Args{
|
||||||
|
OldStateRoot: common.Hash{},
|
||||||
|
NewStateRoot: block.Root(),
|
||||||
|
BlockNumber: block.Number(),
|
||||||
|
BlockHash: block.Hash(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
args = statediff.Args{
|
||||||
|
OldStateRoot: blocks[i-1].Root(),
|
||||||
|
NewStateRoot: block.Root(),
|
||||||
|
BlockNumber: block.Number(),
|
||||||
|
BlockHash: block.Hash(),
|
||||||
|
}
|
||||||
|
rcts = receipts[i-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
var diff statediff.StateObject
|
||||||
|
diff, err = builder.BuildStateDiffObject(args, params)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
diffRlp, err := rlp.EncodeToBytes(diff)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
blockRlp, err := rlp.EncodeToBytes(block)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
receiptsRlp, err := rlp.EncodeToBytes(rcts)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
payload := statediff.Payload{
|
||||||
|
StateObjectRlp: diffRlp,
|
||||||
|
BlockRlp: blockRlp,
|
||||||
|
ReceiptsRlp: receiptsRlp,
|
||||||
|
TotalDifficulty: mockTD,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = transformer.Transform(0, payload)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert some non-canonical data into the database so that we test our ability to discern canonicity
|
||||||
|
indexAndPublisher := eth2.NewIPLDPublisher(db)
|
||||||
|
blockHash = test_helpers.MockBlock.Hash()
|
||||||
|
contractAddress = test_helpers.ContractAddr
|
||||||
|
|
||||||
|
err = indexAndPublisher.Publish(test_helpers.MockConvertedPayload)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
// The non-canonical header has a child
|
||||||
|
err = indexAndPublisher.Publish(test_helpers.MockConvertedPayloadForChild)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
err = publishCode(db, test_helpers.ContractCodeHash, test_helpers.ContractCode)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
graphQLServer, err = graphql.New(backend, gqlEndPoint, nil, []string{"*"}, rpc.HTTPTimeouts{})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = graphQLServer.Start(nil)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defer It("test teardown", func() {
|
||||||
|
err := graphQLServer.Stop()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
eth.TearDownDB(db)
|
||||||
|
chain.Stop()
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("eth_getLogs", func() {
|
||||||
|
It("Retrieves logs that matches the provided blockHash and contract address", func() {
|
||||||
|
logs, err := client.GetLogs(ctx, blockHash, contractAddress)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
expectedLogs := []graphql.LogResponse{
|
||||||
|
{
|
||||||
|
Topics: test_helpers.MockLog1.Topics,
|
||||||
|
Data: hexutil.Bytes(test_helpers.MockLog1.Data),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Expect(logs).To(Equal(expectedLogs))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Retrieves logs with random hash", func() {
|
||||||
|
logs, err := client.GetLogs(ctx, randomHash, contractAddress)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(len(logs)).To(Equal(0))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("eth_getStorageAt", func() {
|
||||||
|
It("Retrieves the storage value at the provided contract address and storage leaf key at the block with the provided hash", func() {
|
||||||
|
storageRes, err := client.GetStorageAt(ctx, blockHashes[2], contractAddress, test_helpers.IndexOne)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(storageRes.Value).To(Equal(common.HexToHash("01")))
|
||||||
|
|
||||||
|
storageRes, err = client.GetStorageAt(ctx, blockHashes[3], contractAddress, test_helpers.IndexOne)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(storageRes.Value).To(Equal(common.HexToHash("03")))
|
||||||
|
|
||||||
|
storageRes, err = client.GetStorageAt(ctx, blockHashes[4], contractAddress, test_helpers.IndexOne)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(storageRes.Value).To(Equal(common.HexToHash("09")))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Retrieves empty data if it tries to access a contract at a blockHash which does not exist", func() {
|
||||||
|
storageRes, err := client.GetStorageAt(ctx, blockHashes[0], contractAddress, test_helpers.IndexOne)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(storageRes.Value).To(Equal(common.Hash{}))
|
||||||
|
|
||||||
|
storageRes, err = client.GetStorageAt(ctx, blockHashes[1], contractAddress, test_helpers.IndexOne)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(storageRes.Value).To(Equal(common.Hash{}))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Retrieves empty data if it tries to access a contract slot which does not exist", func() {
|
||||||
|
storageRes, err := client.GetStorageAt(ctx, blockHashes[3], contractAddress, randomHash.Hex())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(storageRes.Value).To(Equal(common.Hash{}))
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
func publishCode(db *postgres.DB, codeHash common.Hash, code []byte) error {
|
||||||
|
tx, err := db.Beginx()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mhKey, err := shared.MultihashKeyFromKeccak256(codeHash)
|
||||||
|
if err != nil {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := shared.PublishDirect(tx, mhKey, code); err != nil {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Commit()
|
||||||
|
}
|
||||||
|
@ -65,7 +65,13 @@ const schema string = `
|
|||||||
# Data is unindexed data for this log.
|
# Data is unindexed data for this log.
|
||||||
data: Bytes!
|
data: Bytes!
|
||||||
# Transaction is the transaction that generated this log entry.
|
# Transaction is the transaction that generated this log entry.
|
||||||
transaction: Transaction!
|
transaction: Transaction
|
||||||
|
|
||||||
|
# CID for the Receipt IPLD block this Log exists in.
|
||||||
|
cid: String!
|
||||||
|
|
||||||
|
# IPLD block data for the Receipt this Log exists in.
|
||||||
|
ipldBlock: Bytes!
|
||||||
}
|
}
|
||||||
|
|
||||||
# Transaction is an Ethereum transaction.
|
# Transaction is an Ethereum transaction.
|
||||||
@ -259,16 +265,36 @@ const schema string = `
|
|||||||
topics: [[Bytes32!]!]
|
topics: [[Bytes32!]!]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Storage trie value with IPLD data.
|
||||||
|
type StorageResult {
|
||||||
|
value: Bytes32!
|
||||||
|
|
||||||
|
# CID for the storage trie IPLD block.
|
||||||
|
cid: String!
|
||||||
|
|
||||||
|
# Storage trie IPLD block.
|
||||||
|
ipldBlock: Bytes!
|
||||||
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
# Block fetches an Ethereum block by number or by hash. If neither is
|
# Block fetches an Ethereum block by number or by hash. If neither is
|
||||||
# supplied, the most recent known block is returned.
|
# supplied, the most recent known block is returned.
|
||||||
block(number: Long, hash: Bytes32): Block
|
block(number: Long, hash: Bytes32): Block
|
||||||
|
|
||||||
# Blocks returns all the blocks between two numbers, inclusive. If
|
# Blocks returns all the blocks between two numbers, inclusive. If
|
||||||
# to is not supplied, it defaults to the most recent known block.
|
# to is not supplied, it defaults to the most recent known block.
|
||||||
blocks(from: Long!, to: Long): [Block!]!
|
blocks(from: Long!, to: Long): [Block!]!
|
||||||
|
|
||||||
# Transaction returns a transaction specified by its hash.
|
# Transaction returns a transaction specified by its hash.
|
||||||
transaction(hash: Bytes32!): Transaction
|
transaction(hash: Bytes32!): Transaction
|
||||||
|
|
||||||
# Logs returns log entries matching the provided filter.
|
# Logs returns log entries matching the provided filter.
|
||||||
logs(filter: FilterCriteria!): [Log!]!
|
logs(filter: FilterCriteria!): [Log!]!
|
||||||
|
|
||||||
|
# Get storage slot by block hash and contract address.
|
||||||
|
getStorageAt(blockHash: Bytes32!, contract: Address!, slot: Bytes32!): StorageResult
|
||||||
|
|
||||||
|
# Get contract logs by block hash and contract address.
|
||||||
|
getLogs(blockHash: Bytes32!, contract: Address!): [Log!]
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
Loading…
Reference in New Issue
Block a user