From 18266c4f9d47fa9f3879c8cdd7a85d5d6dd9eb25 Mon Sep 17 00:00:00 2001 From: Ashwin Phatak Date: Fri, 21 May 2021 09:00:38 +0530 Subject: [PATCH 1/9] Get receipt CID and block data for logs. --- pkg/eth/backend.go | 9 +++++---- pkg/graphql/graphql.go | 10 ++++++++++ pkg/graphql/schema.go | 6 ++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/pkg/eth/backend.go b/pkg/eth/backend.go index 4d578dd6..999174b6 100644 --- a/pkg/eth/backend.go +++ b/pkg/eth/backend.go @@ -41,7 +41,7 @@ import ( "github.com/ethereum/go-ethereum/rpc" "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/postgres" shared2 "github.com/vulcanize/ipld-eth-indexer/pkg/shared" @@ -59,10 +59,10 @@ var ( const ( RetrieveCanonicalBlockHashByNumber = `SELECT block_hash FROM eth.header_cids INNER JOIN public.blocks ON (header_cids.mh_key = blocks.key) - WHERE id = (SELECT canonical_header_id($1))` + WHERE id = (SELECT public.canonical_header($1))` RetrieveCanonicalHeaderByNumber = `SELECT cid, data FROM eth.header_cids INNER JOIN public.blocks ON (header_cids.mh_key = blocks.key) - WHERE id = (SELECT canonical_header_id($1))` + WHERE id = (SELECT public.canonical_header($1))` RetrieveTD = `SELECT td FROM eth.header_cids WHERE header_cids.block_hash = $1` RetrieveRPCTransaction = `SELECT blocks.data, block_hash, block_number, index FROM public.blocks, eth.transaction_cids, eth.header_cids @@ -76,7 +76,7 @@ const ( AND block_number <= (SELECT block_number FROM eth.header_cids WHERE block_hash = $2) - AND header_cids.id = (SELECT canonical_header_id(block_number)) + AND header_cids.id = (SELECT public.canonical_header(block_number)) ORDER BY block_number DESC LIMIT 1` RetrieveCodeByMhKey = `SELECT data FROM public.blocks WHERE key = $1` @@ -509,6 +509,7 @@ func (b *Backend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log if err := rlp.DecodeBytes(rctBytes, &rct); err != nil { return nil, err } + logs[i] = rct.Logs } return logs, nil diff --git a/pkg/graphql/graphql.go b/pkg/graphql/graphql.go index 12533016..19dfabc7 100644 --- a/pkg/graphql/graphql.go +++ b/pkg/graphql/graphql.go @@ -91,6 +91,8 @@ type Log struct { backend *eth.Backend transaction *Transaction log *types.Log + cid string + ipldBlock []byte } func (l *Log) Transaction(ctx context.Context) *Transaction { @@ -117,6 +119,14 @@ func (l *Log) Data(ctx context.Context) hexutil.Bytes { 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. // backend and hash are mandatory; all others will be fetched when required. type Transaction struct { diff --git a/pkg/graphql/schema.go b/pkg/graphql/schema.go index 8057eac9..de989e74 100644 --- a/pkg/graphql/schema.go +++ b/pkg/graphql/schema.go @@ -66,6 +66,12 @@ const schema string = ` data: Bytes! # Transaction is the transaction that generated this log entry. 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. From b90fcb53e6ffc126f3948c1c38dd84a512367b7d Mon Sep 17 00:00:00 2001 From: Ashwin Phatak Date: Tue, 25 May 2021 11:40:02 +0530 Subject: [PATCH 2/9] Get logs API, with receipt CID and raw IPLD block. --- pkg/graphql/graphql.go | 46 ++++++++++++++++++++++++++++++++++++++++++ pkg/graphql/schema.go | 11 +++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/pkg/graphql/graphql.go b/pkg/graphql/graphql.go index 19dfabc7..6a590ea7 100644 --- a/pkg/graphql/graphql.go +++ b/pkg/graphql/graphql.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/filters" + "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" "github.com/vulcanize/ipld-eth-server/pkg/eth" @@ -954,3 +955,48 @@ func (r *Resolver) Logs(ctx context.Context, args struct{ Filter FilterCriteria filter := filters.NewRangeFilter(filters.Backend(r.backend), begin, end, addresses, topics) return runFilter(ctx, r.backend, filter) } + +func (r *Resolver) GetStorageAt(ctx context.Context, args struct { + BlockHash common.Hash + Contract common.Address + Slot common.Hash +}) (*common.Hash, error) { + ret := common.BytesToHash([]byte{}) + + 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 +} diff --git a/pkg/graphql/schema.go b/pkg/graphql/schema.go index de989e74..f2208db9 100644 --- a/pkg/graphql/schema.go +++ b/pkg/graphql/schema.go @@ -65,7 +65,7 @@ const schema string = ` # Data is unindexed data for this log. data: Bytes! # 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! @@ -269,12 +269,21 @@ const schema string = ` # Block fetches an Ethereum block by number or by hash. If neither is # supplied, the most recent known block is returned. block(number: Long, hash: Bytes32): Block + # Blocks returns all the blocks between two numbers, inclusive. If # to is not supplied, it defaults to the most recent known block. blocks(from: Long!, to: Long): [Block!]! + # Transaction returns a transaction specified by its hash. transaction(hash: Bytes32!): Transaction + # Logs returns log entries matching the provided filter. logs(filter: FilterCriteria!): [Log!]! + + # Get storage slot by block hash and contract address. + getStorageAt(blockHash: Bytes32!, contract: Address!, slot: Bytes32!): Bytes32 + + # Get contract logs by block hash and contract address. + getLogs(blockHash: Bytes32!, contract: Address!): [Log!] } ` From a284a566d5b34f2e9f4fac2dbccc8f6a2118383a Mon Sep 17 00:00:00 2001 From: Ashwin Phatak Date: Wed, 26 May 2021 17:07:25 +0530 Subject: [PATCH 3/9] Get storage API, with storage leaf CID and raw IPLD block. --- pkg/eth/backend.go | 2 +- pkg/eth/ipld_retriever.go | 18 +++++++++--------- pkg/graphql/graphql.go | 39 +++++++++++++++++++++++++++++++++++++-- pkg/graphql/schema.go | 13 ++++++++++++- 4 files changed, 59 insertions(+), 13 deletions(-) diff --git a/pkg/eth/backend.go b/pkg/eth/backend.go index 999174b6..cb746556 100644 --- a/pkg/eth/backend.go +++ b/pkg/eth/backend.go @@ -767,7 +767,7 @@ func (b *Backend) GetStorageByHash(ctx context.Context, address common.Address, return nil, err } - _, storageRlp, err := b.IPLDRetriever.RetrieveStorageAtByAddressAndStorageSlotAndBlockHash(address, key, hash) + _, _, storageRlp, err := b.IPLDRetriever.RetrieveStorageAtByAddressAndStorageSlotAndBlockHash(address, key, hash) return storageRlp, err } diff --git a/pkg/eth/ipld_retriever.go b/pkg/eth/ipld_retriever.go index 4d50cfaa..3e54e28d 100644 --- a/pkg/eth/ipld_retriever.go +++ b/pkg/eth/ipld_retriever.go @@ -29,15 +29,15 @@ import ( const ( RetrieveHeadersByHashesPgStr = `SELECT cid, data - FROM eth.header_cids + FROM eth.header_cids INNER JOIN public.blocks ON (header_cids.mh_key = blocks.key) WHERE block_hash = ANY($1::VARCHAR(66)[])` RetrieveHeadersByBlockNumberPgStr = `SELECT cid, data - FROM eth.header_cids + FROM eth.header_cids INNER JOIN public.blocks ON (header_cids.mh_key = blocks.key) WHERE block_number = $1` RetrieveHeaderByHashPgStr = `SELECT cid, data - FROM eth.header_cids + FROM eth.header_cids INNER JOIN public.blocks ON (header_cids.mh_key = blocks.key) WHERE block_hash = $1` RetrieveUnclesByHashesPgStr = `SELECT cid, data @@ -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 -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) stateLeafKey := crypto.Keccak256Hash(address.Bytes()) storageHash := crypto.Keccak256Hash(key.Bytes()) 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 { - return "", []byte{}, nil + return "", []byte{}, []byte{}, nil } var i []interface{} if err := rlp.DecodeBytes(storageResult.Data, &i); err != nil { err = fmt.Errorf("error decoding storage leaf node rlp: %s", err.Error()) - return "", nil, err + return "", nil, nil, err } 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 diff --git a/pkg/graphql/graphql.go b/pkg/graphql/graphql.go index 6a590ea7..11cc226d 100644 --- a/pkg/graphql/graphql.go +++ b/pkg/graphql/graphql.go @@ -956,12 +956,47 @@ func (r *Resolver) Logs(ctx context.Context, args struct{ Filter FilterCriteria 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 -}) (*common.Hash, error) { - ret := common.BytesToHash([]byte{}) +}) (*StorageResult, error) { + cid, ipldBlock, rlpValue, err := r.backend.IPLDRetriever.RetrieveStorageAtByAddressAndStorageKeyAndBlockHash(args.Contract, args.Slot, args.BlockHash) + + if err != nil { + return nil, err + } + + var value interface{} + err = rlp.DecodeBytes(rlpValue, &value) + if err != nil { + return nil, err + } + + if err != nil { + return nil, err + } + + ret := StorageResult{value: value.([]byte), cid: cid, ipldBlock: ipldBlock} return &ret, nil } diff --git a/pkg/graphql/schema.go b/pkg/graphql/schema.go index f2208db9..2d49abe5 100644 --- a/pkg/graphql/schema.go +++ b/pkg/graphql/schema.go @@ -265,6 +265,17 @@ const schema string = ` 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 { # Block fetches an Ethereum block by number or by hash. If neither is # supplied, the most recent known block is returned. @@ -281,7 +292,7 @@ const schema string = ` logs(filter: FilterCriteria!): [Log!]! # Get storage slot by block hash and contract address. - getStorageAt(blockHash: Bytes32!, contract: Address!, slot: Bytes32!): Bytes32 + getStorageAt(blockHash: Bytes32!, contract: Address!, slot: Bytes32!): StorageResult # Get contract logs by block hash and contract address. getLogs(blockHash: Bytes32!, contract: Address!): [Log!] From b2828a814f3912b108bd428b0e9308b42b7273d6 Mon Sep 17 00:00:00 2001 From: Ashwin Phatak Date: Thu, 27 May 2021 16:36:06 +0530 Subject: [PATCH 4/9] Compute leaf key from slot. --- pkg/graphql/graphql.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/graphql/graphql.go b/pkg/graphql/graphql.go index 11cc226d..52a1f5cc 100644 --- a/pkg/graphql/graphql.go +++ b/pkg/graphql/graphql.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" @@ -980,7 +981,8 @@ func (r *Resolver) GetStorageAt(ctx context.Context, args struct { Contract common.Address Slot common.Hash }) (*StorageResult, error) { - cid, ipldBlock, rlpValue, err := r.backend.IPLDRetriever.RetrieveStorageAtByAddressAndStorageKeyAndBlockHash(args.Contract, args.Slot, args.BlockHash) + storageLeafKey := crypto.Keccak256Hash(args.Slot.Bytes()) + cid, ipldBlock, rlpValue, err := r.backend.IPLDRetriever.RetrieveStorageAtByAddressAndStorageKeyAndBlockHash(args.Contract, storageLeafKey, args.BlockHash) if err != nil { return nil, err From a3ca08b653b6277b4495776c6c72152fa16eb504 Mon Sep 17 00:00:00 2001 From: Ashwin Phatak Date: Thu, 27 May 2021 17:00:30 +0530 Subject: [PATCH 5/9] Return empty result instead of error if slot not found in storage table. --- pkg/graphql/graphql.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/graphql/graphql.go b/pkg/graphql/graphql.go index 52a1f5cc..50b77d5c 100644 --- a/pkg/graphql/graphql.go +++ b/pkg/graphql/graphql.go @@ -19,6 +19,7 @@ package graphql import ( "context" + "database/sql" "errors" "time" @@ -985,6 +986,12 @@ func (r *Resolver) GetStorageAt(ctx context.Context, args struct { cid, ipldBlock, rlpValue, err := r.backend.IPLDRetriever.RetrieveStorageAtByAddressAndStorageKeyAndBlockHash(args.Contract, storageLeafKey, args.BlockHash) if err != nil { + if err == sql.ErrNoRows { + ret := StorageResult{value: ([]byte{}), cid: "", ipldBlock: ([]byte{})} + + return &ret, nil + } + return nil, err } From 0a14bd9f0ffe5fb5377a7af0288bd35f0a8e49f3 Mon Sep 17 00:00:00 2001 From: Ashwin Phatak Date: Tue, 29 Jun 2021 12:23:10 +0530 Subject: [PATCH 6/9] Update method used to get storage slot. --- pkg/graphql/graphql.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/graphql/graphql.go b/pkg/graphql/graphql.go index 50b77d5c..d365330c 100644 --- a/pkg/graphql/graphql.go +++ b/pkg/graphql/graphql.go @@ -28,7 +28,6 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" @@ -982,8 +981,7 @@ func (r *Resolver) GetStorageAt(ctx context.Context, args struct { Contract common.Address Slot common.Hash }) (*StorageResult, error) { - storageLeafKey := crypto.Keccak256Hash(args.Slot.Bytes()) - cid, ipldBlock, rlpValue, err := r.backend.IPLDRetriever.RetrieveStorageAtByAddressAndStorageKeyAndBlockHash(args.Contract, storageLeafKey, args.BlockHash) + cid, ipldBlock, rlpValue, err := r.backend.IPLDRetriever.RetrieveStorageAtByAddressAndStorageSlotAndBlockHash(args.Contract, args.Slot, args.BlockHash) if err != nil { if err == sql.ErrNoRows { From 1d18d1fed8e93e07803e8c276b7c9b74aa353d7b Mon Sep 17 00:00:00 2001 From: Arijit Das Date: Tue, 29 Jun 2021 12:56:26 +0530 Subject: [PATCH 7/9] Update gopsutil to fix build on mac. --- go.mod | 5 +++-- go.sum | 8 ++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 2d07688b..56e38271 100644 --- a/go.mod +++ b/go.mod @@ -16,13 +16,14 @@ require ( github.com/onsi/ginkgo v1.15.0 github.com/onsi/gomega v1.10.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/spf13/cobra v1.1.1 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/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 diff --git a/go.sum b/go.sum index ea9e7433..e8e706fd 100644 --- a/go.sum +++ b/go.sum @@ -908,6 +908,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+incompatible h1:tYH07UPoQt0OCQdgWWMgYHy3/a9bcxNpBIysykNIP7I= 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/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= @@ -994,6 +996,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/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/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/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= @@ -1236,6 +1242,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-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-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.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 9a5581b543ef2a448702313bbc121a3bb77cb78c Mon Sep 17 00:00:00 2001 From: Arijit Das Date: Tue, 29 Jun 2021 12:59:37 +0530 Subject: [PATCH 8/9] Fix failing unit tests. --- pkg/eth/backend.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/eth/backend.go b/pkg/eth/backend.go index cb746556..c1bd1de7 100644 --- a/pkg/eth/backend.go +++ b/pkg/eth/backend.go @@ -59,10 +59,10 @@ var ( const ( RetrieveCanonicalBlockHashByNumber = `SELECT block_hash FROM eth.header_cids INNER JOIN public.blocks ON (header_cids.mh_key = blocks.key) - WHERE id = (SELECT public.canonical_header($1))` + WHERE id = (SELECT canonical_header_id($1))` RetrieveCanonicalHeaderByNumber = `SELECT cid, data FROM eth.header_cids INNER JOIN public.blocks ON (header_cids.mh_key = blocks.key) - WHERE id = (SELECT public.canonical_header($1))` + WHERE id = (SELECT canonical_header_id($1))` RetrieveTD = `SELECT td FROM eth.header_cids WHERE header_cids.block_hash = $1` RetrieveRPCTransaction = `SELECT blocks.data, block_hash, block_number, index FROM public.blocks, eth.transaction_cids, eth.header_cids @@ -76,7 +76,7 @@ const ( AND block_number <= (SELECT block_number FROM eth.header_cids WHERE block_hash = $2) - AND header_cids.id = (SELECT public.canonical_header(block_number)) + AND header_cids.id = (SELECT canonical_header_id(block_number)) ORDER BY block_number DESC LIMIT 1` RetrieveCodeByMhKey = `SELECT data FROM public.blocks WHERE key = $1` From f1a61d09911c731ab8bd2fec47cee808f22031ab Mon Sep 17 00:00:00 2001 From: Arijit Das Date: Fri, 2 Jul 2021 14:30:48 +0530 Subject: [PATCH 9/9] Add graphql tests. --- go.mod | 1 + go.sum | 2 + pkg/graphql/client.go | 104 +++++++++++++++++++ pkg/graphql/graphql.go | 7 +- pkg/graphql/graphql_test.go | 202 +++++++++++++++++++++++++++++++++++- 5 files changed, 308 insertions(+), 8 deletions(-) create mode 100644 pkg/graphql/client.go diff --git a/go.mod b/go.mod index 56e38271..6968574d 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/ipfs/go-ipld-format v0.2.0 github.com/jmoiron/sqlx v1.2.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/onsi/ginkgo v1.15.0 github.com/onsi/gomega v1.10.1 diff --git a/go.sum b/go.sum index e8e706fd..77fada48 100644 --- a/go.sum +++ b/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/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/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.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= diff --git a/pkg/graphql/client.go b/pkg/graphql/client.go new file mode 100644 index 00000000..0bb4705d --- /dev/null +++ b/pkg/graphql/client.go @@ -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 +} diff --git a/pkg/graphql/graphql.go b/pkg/graphql/graphql.go index d365330c..2ae86666 100644 --- a/pkg/graphql/graphql.go +++ b/pkg/graphql/graphql.go @@ -985,7 +985,7 @@ func (r *Resolver) GetStorageAt(ctx context.Context, args struct { if err != nil { if err == sql.ErrNoRows { - ret := StorageResult{value: ([]byte{}), cid: "", ipldBlock: ([]byte{})} + ret := StorageResult{value: []byte{}, cid: "", ipldBlock: []byte{}} return &ret, nil } @@ -999,12 +999,7 @@ func (r *Resolver) GetStorageAt(ctx context.Context, args struct { return nil, err } - if err != nil { - return nil, err - } - ret := StorageResult{value: value.([]byte), cid: cid, ipldBlock: ipldBlock} - return &ret, nil } diff --git a/pkg/graphql/graphql_test.go b/pkg/graphql/graphql_test.go index 7315f530..f91ce2a3 100644 --- a/pkg/graphql/graphql_test.go +++ b/pkg/graphql/graphql_test.go @@ -17,15 +17,213 @@ package graphql_test 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/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" ) var _ = Describe("GraphQL", func() { - It("Builds the schema and creates a new handler", func() { - _, err := graphql.NewHandler(nil) + const ( + 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()) }) + + 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() +}