From f1a61d09911c731ab8bd2fec47cee808f22031ab Mon Sep 17 00:00:00 2001 From: Arijit Das Date: Fri, 2 Jul 2021 14:30:48 +0530 Subject: [PATCH] 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() +}