Compare commits

...
Author SHA1 Message Date
Ian Norden 013946fd73 Merge pull request #26 from vulcanize/canonical_hash_finder
update canonical header finder functions
2020-11-13 07:41:46 -06:00
Ian Norden b11fb949f5 final touches, test canonicty functions and get tests passing again 2020-11-13 07:40:33 -06:00
Ian Norden c0a91b9d9f updated canonical header finder functions 2020-11-09 10:29:04 -06:00
Ian Norden ca07107cec Merge pull request #22 from vulcanize/eth_json_rpc
[WIP] eth JSON-RPC
2020-11-01 09:15:21 -06:00
Ian Norden b128f894c4 postgres functions to check if node was removed (e.g. a la EIP158) in a range; update backend to use these to retrieve state and storage leafs in single (albeit complex) SELECT query 2020-10-31 15:00:03 -05:00
Ian Norden 7c06d4b3a1 additional unit tests 2020-10-30 18:21:44 -05:00
Ian Norden b208281ad6 optimize GetStorageAt; GetStorageAt unit tests 2020-10-30 12:06:41 -05:00
Ian Norden b664aee621 unit tests for GetTransactionCount, GetTransactionReceipt, GetBalance, and GetCode 2020-10-30 00:44:41 -05:00
Ian Norden cffceb53db optimize GetTransactionCount, GetBalance, and GetCode to use secondary indexes instead of operating through ethdb where we have to iterate down trie from root to leaf (multiple db lookups) to access account info 2020-10-29 22:07:39 -05:00
Ian Norden dc25ea7f87 new transaction endpoint unit tests (except for GetTransactionCount which is still TODO because testing its current implementation requires complete state (e.g. like eth_call test) so- ideally- will first refactor it to use state diff 2ndary indexes rather than proceeding through regular ethdb interface) 2020-10-29 15:20:58 -05:00
Ian Norden e1026d5261 remaining block endpoint unit test; uncle endpoints unit tests 2020-10-29 14:59:09 -05:00
Ian Norden a480c28a67 update readme 2020-10-28 09:02:27 -05:00
Ian Norden 1d4abcb69b finish cache miss forwarding for new endpoints 2020-10-28 08:54:06 -05:00
Ian Norden b5d57b6afc the remaining, currently supportable, eth_* endpoints 2020-10-28 08:54:06 -05:00
Ian Norden 3af06ada1d Merge pull request #20 from vulcanize/graphql
Graphql
2020-10-28 08:50:23 -05:00
Ian Norden 7a2ccaa8a7 fixes for test; update readme and gomodules 2020-10-28 08:40:00 -05:00
Ian Norden 16aa9652a5 integrate into serve command 2020-10-28 08:23:17 -05:00
Ian Norden a8dd77294a graphql service on top of rpc endpoints 2020-10-28 08:23:17 -05:00
Ian Norden 4f4ab1dd4f update eth backend 2020-10-28 08:23:17 -05:00
Ian Norden ef2d8f789d Merge pull request #21 from vulcanize/prom
minor cleanup to prom files
2020-10-28 08:22:11 -05:00
Ian Norden e561fd3178 minor cleanup to prom files 2020-10-27 14:18:59 -05:00
Ian Norden 128e30b3a8 Merge pull request #19 from vulcanize/forward_misses
forward cache misses (err and/or empty results) to remote node
2020-10-27 13:00:18 -05:00
Ian Norden 2d0367fe6c update env 2020-10-27 12:57:26 -05:00
Ian Norden e1cab4fadc forward cache misses (err and/or empty results) to remote node 2020-10-27 12:29:28 -05:00
Ian Norden 0a8b54d366 Merge pull request #18 from vulcanize/readme
minor updates to readme
2020-10-23 11:04:40 -05:00
Ian Norden beea9d503d minor updates to readme 2020-10-23 10:15:35 -05:00
37 changed files with 4689 additions and 927 deletions
+33 -6
View File
@@ -14,7 +14,7 @@
## Background
NOTE: WIP
ipld-eth-server is used to service queries against the indexed Ethereum IPLD objects indexed by [ipld-eth-indexer](https://github.com/vulcanize/ipld-eth-indexer).
ipld-eth-server is used to service queries against the Ethereum IPLD objects indexed by [ipld-eth-indexer](https://github.com/vulcanize/ipld-eth-indexer).
It exposes standard Ethereum JSON RPC endpoints on top of the database, in some cases these endpoints can leverage the unique indexes to improve query performance.
Additional, unique endpoints are exposed which utilize the new indexes and state diff data objects.
@@ -66,15 +66,23 @@ The corresponding CLI flags can be found with the `./ipld-eth-server serve --hel
ipcPath = "~/.vulcanize/vulcanize.ipc" # $SERVER_IPC_PATH
wsPath = "127.0.0.1:8081" # $SERVER_WS_PATH
httpPath = "127.0.0.1:8082" # $SERVER_HTTP_PATH
graphql = true # $SERVER_GRAPHQL
graphqlEndpoint = "" # $SERVER_GRAPHQL_ENDPOINT
[ethereum]
chainID = "1" # $ETH_CHAIN_ID
defaultSender = "" # $ETH_DEFAULT_SENDER_ADDR
rpcGasCap = "1000000000000" # $ETH_RPC_GAS_CAP
httpPath = "127.0.0.1:8545" # $ETH_HTTP_PATH
nodeID = "arch1" # $ETH_NODE_ID
clientName = "Geth" # $ETH_CLIENT_NAME
genesisBlock = "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" # $ETH_GENESIS_BLOCK
networkID = "1" # $ETH_NETWORK_ID
```
The `database` fields are for connecting to a Postgres database that has been/is being populated by [ipld-eth-indexer](https://github.com/vulcanize/ipld-eth-indexer).
The `server` fields set the paths for exposing the ipld-eth-server endpoints
The `ethereum` fields set the chainID and default sender address to use for EVM simulation
The `database` fields are for connecting to a Postgres database that has been/is being populated by [ipld-eth-indexer](https://github.com/vulcanize/ipld-eth-indexer)
The `server` fields set the paths for exposing the ipld-eth-server endpoints
The `ethereum` fields set the chainID and default sender address to use for EVM simulation, and can optionally be used to configure a remote eth node to forward cache misses to
### Endpoints
@@ -85,14 +93,33 @@ TODO: Port the IPLD RPC subscription endpoints after the decoupling
ipld-eth-server currently recapitulates portions of the Ethereum JSON-RPC api standard.
The currently supported standard endpoints are:
`eth_call`
`eth_getBalance`
`eth_getStorageAt`
`eth_getCode`
`eth_getProof`
`eth_blockNumber`
`eth_getLogs`
`eth_getHeaderByNumber`
`eth_getHeaderByHash`
`eth_getBlockByNumber`
`eth_getBlockByHash`
`eth_getTransactionCount`
`eth_getBlockTransactionCountByHash`
`eth_getBlockTransactionCountByNumber`
`eth_getTransactionByHash`
`eth_getRawTransactionByHash`
`eth_getTransactionByBlockHashAndIndex`
`eth_getTransactionByBlockNumberAndIndex`
`eth_getRawTransactionByBlockHashAndIndex`
`eth_getRawTransactionByBlockNumberAndIndex`
`eth_getTransactionReceipt`
`eth_getLogs`
`eth_getUncleCountByBlockHash`
`eth_getUncleCountByBlockNumber`
`eth_getUncleByBlockHashAndIndex`
`eth_getUncleByBlockNumberAndIndex`
TODO: Add the rest of the standard endpoints add unique endpoints (e.g. getSlice)
TODO: Add the rest of the standard endpoints and unique endpoints (e.g. getSlice)
### Testing
`make test` will run the unit tests
+14 -13
View File
@@ -46,7 +46,8 @@ func Execute() {
}
func initFuncs(cmd *cobra.Command, args []string) {
logfile := viper.GetString("logfile")
viper.BindEnv("log.file", "LOGRUS_FILE")
logfile := viper.GetString("log.file")
if logfile != "" {
file, err := os.OpenFile(logfile,
os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
@@ -68,11 +69,11 @@ func initFuncs(cmd *cobra.Command, args []string) {
prom.Init()
}
if viper.GetBool("http") {
if viper.GetBool("prom.http") {
addr := fmt.Sprintf(
"%s:%s",
viper.GetString("http.addr"),
viper.GetString("http.port"),
viper.GetString("prom.http.addr"),
viper.GetString("prom.http.port"),
)
prom.Serve(addr)
}
@@ -98,34 +99,34 @@ func init() {
viper.AutomaticEnv()
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file location")
rootCmd.PersistentFlags().String("logfile", "", "file path for logging")
rootCmd.PersistentFlags().String("database-name", "vulcanize_public", "database name")
rootCmd.PersistentFlags().Int("database-port", 5432, "database port")
rootCmd.PersistentFlags().String("database-hostname", "localhost", "database hostname")
rootCmd.PersistentFlags().String("database-user", "", "database user")
rootCmd.PersistentFlags().String("database-password", "", "database password")
rootCmd.PersistentFlags().String("client-ipcPath", "", "location of geth.ipc file")
rootCmd.PersistentFlags().String("log-level", log.InfoLevel.String(), "Log level (trace, debug, info, warn, error, fatal, panic")
rootCmd.PersistentFlags().String("log-level", log.InfoLevel.String(), "log level (trace, debug, info, warn, error, fatal, panic)")
rootCmd.PersistentFlags().String("log-file", "", "file path for logging")
rootCmd.PersistentFlags().Bool("metrics", false, "enable metrics")
rootCmd.PersistentFlags().Bool("http", false, "enable http service for prometheus")
rootCmd.PersistentFlags().String("http-addr", "127.0.0.1", "http host for prometheus")
rootCmd.PersistentFlags().String("http-port", "8090", "http port for prometheus")
rootCmd.PersistentFlags().Bool("prom-http", false, "enable http service for prometheus")
rootCmd.PersistentFlags().String("prom-http-addr", "127.0.0.1", "http host for prometheus")
rootCmd.PersistentFlags().String("prom-http-port", "8090", "http port for prometheus")
viper.BindPFlag("logfile", rootCmd.PersistentFlags().Lookup("logfile"))
viper.BindPFlag("database.name", rootCmd.PersistentFlags().Lookup("database-name"))
viper.BindPFlag("database.port", rootCmd.PersistentFlags().Lookup("database-port"))
viper.BindPFlag("database.hostname", rootCmd.PersistentFlags().Lookup("database-hostname"))
viper.BindPFlag("database.user", rootCmd.PersistentFlags().Lookup("database-user"))
viper.BindPFlag("database.password", rootCmd.PersistentFlags().Lookup("database-password"))
viper.BindPFlag("log.level", rootCmd.PersistentFlags().Lookup("log-level"))
viper.BindPFlag("log.file", rootCmd.PersistentFlags().Lookup("log-file"))
viper.BindPFlag("metrics", rootCmd.PersistentFlags().Lookup("metrics"))
viper.BindPFlag("http", rootCmd.PersistentFlags().Lookup("http"))
viper.BindPFlag("http.addr", rootCmd.PersistentFlags().Lookup("http-addr"))
viper.BindPFlag("http.port", rootCmd.PersistentFlags().Lookup("http-port"))
viper.BindPFlag("prom.http", rootCmd.PersistentFlags().Lookup("prom-http"))
viper.BindPFlag("prom.http.addr", rootCmd.PersistentFlags().Lookup("prom-http-addr"))
viper.BindPFlag("prom.http.port", rootCmd.PersistentFlags().Lookup("prom-http-port"))
}
func initConfig() {
+40 -2
View File
@@ -20,6 +20,8 @@ import (
"os/signal"
"sync"
"github.com/vulcanize/ipld-eth-server/pkg/graphql"
"github.com/ethereum/go-ethereum/rpc"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@@ -28,7 +30,6 @@ import (
"github.com/vulcanize/ipld-eth-indexer/pkg/eth"
srpc "github.com/vulcanize/ipld-eth-server/pkg/rpc"
s "github.com/vulcanize/ipld-eth-server/pkg/serve"
v "github.com/vulcanize/ipld-eth-server/version"
)
@@ -70,10 +71,17 @@ func serve() {
if err := startServers(server, serverConfig); err != nil {
logWithCommand.Fatal(err)
}
graphQL, err := startGraphQL(server)
if err != nil {
logWithCommand.Fatal(err)
}
shutdown := make(chan os.Signal)
signal.Notify(shutdown, os.Interrupt)
<-shutdown
if graphQL != nil {
graphQL.Stop()
}
server.Stop()
wg.Wait()
}
@@ -91,27 +99,57 @@ func startServers(server s.Server, settings *s.Config) error {
}
logWithCommand.Info("starting up HTTP server")
_, _, err = srpc.StartHTTPEndpoint(settings.HTTPEndpoint, server.APIs(), []string{"eth"}, nil, []string{"*"}, rpc.HTTPTimeouts{})
return err
}
func startGraphQL(server s.Server) (graphQLServer *graphql.Service, err error) {
viper.BindEnv("server.graphql", "SERVER_GRAPHQL")
if viper.GetBool("server.graphql") {
logWithCommand.Info("starting up GraphQL server")
viper.BindEnv("server.graphqlEndpoint", "SERVER_GRAPHQL_ENDPOINT")
endPoint := viper.GetString("server.graphqlEndpoint")
if endPoint != "" {
graphQLServer, err = graphql.New(server.Backend(), endPoint, nil, []string{"*"}, rpc.HTTPTimeouts{})
if err != nil {
return
}
err = graphQLServer.Start(nil)
}
}
return
}
func init() {
rootCmd.AddCommand(serveCmd)
// flags for all config variables
serveCmd.PersistentFlags().Bool("server-graphql", false, "turn on the graphql server")
serveCmd.PersistentFlags().String("server-graphql-endpoint", "", "endpoint url for graphql server")
serveCmd.PersistentFlags().String("server-ws-path", "", "vdb server ws path")
serveCmd.PersistentFlags().String("server-http-path", "", "vdb server http path")
serveCmd.PersistentFlags().String("server-ipc-path", "", "vdb server ipc path")
serveCmd.PersistentFlags().String("eth-http-path", "", "http url for ethereum node")
serveCmd.PersistentFlags().String("eth-node-id", "", "eth node id")
serveCmd.PersistentFlags().String("eth-client-name", "Geth", "eth client name")
serveCmd.PersistentFlags().String("eth-genesis-block", "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", "eth genesis block hash")
serveCmd.PersistentFlags().String("eth-network-id", "1", "eth network id")
serveCmd.PersistentFlags().String("eth-chain-id", "1", "eth chain id")
serveCmd.PersistentFlags().String("eth-default-sender", "", "default sender address")
serveCmd.PersistentFlags().String("eth-rpc-gas-cap", "", "rpc gas cap (for eth_Call execution)")
// and their bindings
viper.BindPFlag("server.graphql", serveCmd.PersistentFlags().Lookup("server-graphql"))
viper.BindPFlag("server.graphqlEndpoint", serveCmd.PersistentFlags().Lookup("server-graphql-endpoint"))
viper.BindPFlag("server.wsPath", serveCmd.PersistentFlags().Lookup("server-ws-path"))
viper.BindPFlag("server.httpPath", serveCmd.PersistentFlags().Lookup("server-http-path"))
viper.BindPFlag("server.ipcPath", serveCmd.PersistentFlags().Lookup("server-ipc-path"))
viper.BindPFlag("ethereum.httpPath", serveCmd.PersistentFlags().Lookup("eth-http-path"))
viper.BindPFlag("ethereum.nodeID", serveCmd.PersistentFlags().Lookup("eth-node-id"))
viper.BindPFlag("ethereum.clientName", serveCmd.PersistentFlags().Lookup("eth-client-name"))
viper.BindPFlag("ethereum.genesisBlock", serveCmd.PersistentFlags().Lookup("eth-genesis-block"))
viper.BindPFlag("ethereum.networkID", serveCmd.PersistentFlags().Lookup("eth-network-id"))
viper.BindPFlag("ethereum.chainID", serveCmd.PersistentFlags().Lookup("eth-chain-id"))
viper.BindPFlag("ethereum.defaultSender", serveCmd.PersistentFlags().Lookup("eth-default-sender"))
viper.BindPFlag("ethereum.rpcGasCap", serveCmd.PersistentFlags().Lookup("eth-rpc-gas-cap"))
@@ -0,0 +1,39 @@
-- +goose Up
-- +goose StatementBegin
-- returns if a storage node at the provided path was removed in the range > the provided height and <= the provided block hash
CREATE OR REPLACE FUNCTION was_storage_removed(path BYTEA, height BIGINT, hash VARCHAR(66)) RETURNS BOOLEAN
AS $$
SELECT exists(SELECT 1
FROM eth.storage_cids
INNER JOIN eth.state_cids ON (storage_cids.state_id = state_cids.id)
INNER JOIN eth.header_cids ON (state_cids.header_id = header_cids.id)
WHERE storage_path = path
AND block_number > height
AND block_number <= (SELECT block_number
FROM eth.header_cids
WHERE block_hash = hash)
AND storage_cids.node_type = 3
LIMIT 1);
$$ LANGUAGE SQL;
-- +goose StatementEnd
-- +goose StatementBegin
-- returns if a state node at the provided path was removed in the range > the provided height and <= the provided block hash
CREATE OR REPLACE FUNCTION was_state_removed(path BYTEA, height BIGINT, hash VARCHAR(66)) RETURNS BOOLEAN
AS $$
SELECT exists(SELECT 1
FROM eth.state_cids
INNER JOIN eth.header_cids ON (state_cids.header_id = header_cids.id)
WHERE state_path = path
AND block_number > height
AND block_number <= (SELECT block_number
FROM eth.header_cids
WHERE block_hash = hash)
AND state_cids.node_type = 3
LIMIT 1);
$$ LANGUAGE SQL;
-- +goose StatementEnd
-- +goose Down
DROP FUNCTION was_storage_removed;
DROP FUNCTION was_state_removed;
@@ -0,0 +1,121 @@
-- +goose Up
-- +goose StatementBegin
CREATE TYPE child_result AS (
has_child BOOLEAN,
children eth.header_cids[]
);
CREATE OR REPLACE FUNCTION has_child(hash VARCHAR(66), height BIGINT) RETURNS child_result AS
$BODY$
DECLARE
child_height INT;
temp_child eth.header_cids;
new_child_result child_result;
BEGIN
child_height = height + 1;
-- short circuit if there are no children
SELECT exists(SELECT 1
FROM eth.header_cids
WHERE parent_hash = hash
AND block_number = child_height
LIMIT 1)
INTO new_child_result.has_child;
-- collect all the children for this header
IF new_child_result.has_child THEN
FOR temp_child IN
SELECT * FROM eth.header_cids WHERE parent_hash = hash AND block_number = child_height
LOOP
new_child_result.children = array_append(new_child_result.children, temp_child);
END LOOP;
END IF;
RETURN new_child_result;
END
$BODY$
LANGUAGE 'plpgsql';
-- +goose StatementEnd
-- +goose StatementBegin
CREATE OR REPLACE FUNCTION canonical_header_from_array(headers eth.header_cids[]) RETURNS eth.header_cids AS
$BODY$
DECLARE
canonical_header eth.header_cids;
canonical_child eth.header_cids;
header eth.header_cids;
current_child_result child_result;
child_headers eth.header_cids[];
current_header_with_child eth.header_cids;
has_children_count INT DEFAULT 0;
BEGIN
-- for each header in the provided set
FOREACH header IN ARRAY headers
LOOP
-- check if it has any children
current_child_result = has_child(header.block_hash, header.block_number);
IF current_child_result.has_child THEN
-- if it does, take note
has_children_count = has_children_count + 1;
current_header_with_child = header;
-- and add the children to the growing set of child headers
child_headers = array_cat(child_headers, current_child_result.children);
END IF;
END LOOP;
-- if none of the headers had children, none is more canonical than the other
IF has_children_count = 0 THEN
-- return the first one selected
SELECT * INTO canonical_header FROM unnest(headers) LIMIT 1;
-- if only one header had children, it can be considered the heaviest/canonical header of the set
ELSIF has_children_count = 1 THEN
-- return the only header with a child
canonical_header = current_header_with_child;
-- if there are multiple headers with children
ELSE
-- find the canonical header from the child set
canonical_child = canonical_header_from_array(child_headers);
-- the header that is parent to this header, is the canonical header at this level
SELECT * INTO canonical_header FROM unnest(headers)
WHERE block_hash = canonical_child.parent_hash;
END IF;
RETURN canonical_header;
END
$BODY$
LANGUAGE 'plpgsql';
-- +goose StatementEnd
-- +goose StatementBegin
CREATE OR REPLACE FUNCTION canonical_header_id(height BIGINT) RETURNS INTEGER AS
$BODY$
DECLARE
canonical_header eth.header_cids;
headers eth.header_cids[];
header_count INT;
temp_header eth.header_cids;
BEGIN
-- collect all headers at this height
FOR temp_header IN
SELECT * FROM eth.header_cids WHERE block_number = height
LOOP
headers = array_append(headers, temp_header);
END LOOP;
-- count the number of headers collected
header_count = array_length(headers, 1);
-- if we have less than 1 header, return NULL
IF header_count IS NULL OR header_count < 1 THEN
RETURN NULL;
-- if we have one header, return its id
ELSIF header_count = 1 THEN
RETURN headers[1].id;
-- if we have multiple headers we need to determine which one is canonical
ELSE
canonical_header = canonical_header_from_array(headers);
RETURN canonical_header.id;
END IF;
END;
$BODY$
LANGUAGE 'plpgsql';
-- +goose StatementEnd
-- +goose Down
DROP FUNCTION canonical_header_id;
DROP FUNCTION canonical_header_from_array;
DROP FUNCTION has_child;
DROP TYPE child_result;
+204 -34
View File
@@ -2,8 +2,8 @@
-- PostgreSQL database dump
--
-- Dumped from database version 12.1
-- Dumped by pg_dump version 12.1
-- Dumped from database version 12.4
-- Dumped by pg_dump version 12.4
SET statement_timeout = 0;
SET lock_timeout = 0;
@@ -23,6 +23,58 @@ SET row_security = off;
CREATE SCHEMA eth;
SET default_tablespace = '';
SET default_table_access_method = heap;
--
-- Name: header_cids; Type: TABLE; Schema: eth; Owner: -
--
CREATE TABLE eth.header_cids (
id integer NOT NULL,
block_number bigint NOT NULL,
block_hash character varying(66) NOT NULL,
parent_hash character varying(66) NOT NULL,
cid text NOT NULL,
mh_key text NOT NULL,
td numeric NOT NULL,
node_id integer NOT NULL,
reward numeric NOT NULL,
state_root character varying(66) NOT NULL,
tx_root character varying(66) NOT NULL,
receipt_root character varying(66) NOT NULL,
uncle_root character varying(66) NOT NULL,
bloom bytea NOT NULL,
"timestamp" numeric NOT NULL,
times_validated integer DEFAULT 1 NOT NULL
);
--
-- Name: TABLE header_cids; Type: COMMENT; Schema: eth; Owner: -
--
COMMENT ON TABLE eth.header_cids IS '@name EthHeaderCids';
--
-- Name: COLUMN header_cids.node_id; Type: COMMENT; Schema: eth; Owner: -
--
COMMENT ON COLUMN eth.header_cids.node_id IS '@name EthNodeID';
--
-- Name: child_result; Type: TYPE; Schema: public; Owner: -
--
CREATE TYPE public.child_result AS (
has_child boolean,
children eth.header_cids[]
);
--
-- Name: graphql_subscription(); Type: FUNCTION; Schema: eth; Owner: -
--
@@ -78,6 +130,125 @@ END
$$;
--
-- Name: canonical_header_from_array(eth.header_cids[]); Type: FUNCTION; Schema: public; Owner: -
--
CREATE FUNCTION public.canonical_header_from_array(headers eth.header_cids[]) RETURNS eth.header_cids
LANGUAGE plpgsql
AS $$
DECLARE
canonical_header eth.header_cids;
canonical_child eth.header_cids;
header eth.header_cids;
current_child_result child_result;
child_headers eth.header_cids[];
current_header_with_child eth.header_cids;
has_children_count INT DEFAULT 0;
BEGIN
-- for each header in the provided set
FOREACH header IN ARRAY headers
LOOP
-- check if it has any children
current_child_result = has_child(header.block_hash, header.block_number);
IF current_child_result.has_child THEN
-- if it does, take note
has_children_count = has_children_count + 1;
current_header_with_child = header;
-- and add the children to the growing set of child headers
child_headers = array_cat(child_headers, current_child_result.children);
END IF;
END LOOP;
-- if none of the headers had children, none is more canonical than the other
IF has_children_count = 0 THEN
-- return the first one selected
SELECT * INTO canonical_header FROM unnest(headers) LIMIT 1;
-- if only one header had children, it can be considered the heaviest/canonical header of the set
ELSIF has_children_count = 1 THEN
-- return the only header with a child
canonical_header = current_header_with_child;
-- if there are multiple headers with children
ELSE
-- find the canonical header from the child set
canonical_child = canonical_header_from_array(child_headers);
-- the header that is parent to this header, is the canonical header at this level
SELECT * INTO canonical_header FROM unnest(headers)
WHERE block_hash = canonical_child.parent_hash;
END IF;
RETURN canonical_header;
END
$$;
--
-- Name: canonical_header_id(bigint); Type: FUNCTION; Schema: public; Owner: -
--
CREATE FUNCTION public.canonical_header_id(height bigint) RETURNS integer
LANGUAGE plpgsql
AS $$
DECLARE
canonical_header eth.header_cids;
headers eth.header_cids[];
header_count INT;
temp_header eth.header_cids;
BEGIN
-- collect all headers at this height
FOR temp_header IN
SELECT * FROM eth.header_cids WHERE block_number = height
LOOP
headers = array_append(headers, temp_header);
END LOOP;
-- count the number of headers collected
header_count = array_length(headers, 1);
-- if we have less than 1 header, return NULL
IF header_count IS NULL OR header_count < 1 THEN
RETURN NULL;
-- if we have one header, return its id
ELSIF header_count = 1 THEN
RETURN headers[1].id;
-- if we have multiple headers we need to determine which one is canonical
ELSE
canonical_header = canonical_header_from_array(headers);
RETURN canonical_header.id;
END IF;
END;
$$;
--
-- Name: has_child(character varying, bigint); Type: FUNCTION; Schema: public; Owner: -
--
CREATE FUNCTION public.has_child(hash character varying, height bigint) RETURNS public.child_result
LANGUAGE plpgsql
AS $$
DECLARE
child_height INT;
temp_child eth.header_cids;
new_child_result child_result;
BEGIN
child_height = height + 1;
-- short circuit if there are no children
SELECT exists(SELECT 1
FROM eth.header_cids
WHERE parent_hash = hash
AND block_number = child_height
LIMIT 1)
INTO new_child_result.has_child;
-- collect all the children for this header
IF new_child_result.has_child THEN
FOR temp_child IN
SELECT * FROM eth.header_cids WHERE parent_hash = hash AND block_number = child_height
LOOP
new_child_result.children = array_append(new_child_result.children, temp_child);
END LOOP;
END IF;
RETURN new_child_result;
END
$$;
--
-- Name: header_weight(character varying); Type: FUNCTION; Schema: public; Owner: -
--
@@ -100,46 +271,45 @@ CREATE FUNCTION public.header_weight(hash character varying) RETURNS bigint
$$;
SET default_tablespace = '';
SET default_table_access_method = heap;
--
-- Name: header_cids; Type: TABLE; Schema: eth; Owner: -
-- Name: was_state_removed(bytea, bigint, character varying); Type: FUNCTION; Schema: public; Owner: -
--
CREATE TABLE eth.header_cids (
id integer NOT NULL,
block_number bigint NOT NULL,
block_hash character varying(66) NOT NULL,
parent_hash character varying(66) NOT NULL,
cid text NOT NULL,
mh_key text NOT NULL,
td numeric NOT NULL,
node_id integer NOT NULL,
reward numeric NOT NULL,
state_root character varying(66) NOT NULL,
tx_root character varying(66) NOT NULL,
receipt_root character varying(66) NOT NULL,
uncle_root character varying(66) NOT NULL,
bloom bytea NOT NULL,
"timestamp" numeric NOT NULL,
times_validated integer DEFAULT 1 NOT NULL
);
CREATE FUNCTION public.was_state_removed(path bytea, height bigint, hash character varying) RETURNS boolean
LANGUAGE sql
AS $$
SELECT exists(SELECT 1
FROM eth.state_cids
INNER JOIN eth.header_cids ON (state_cids.header_id = header_cids.id)
WHERE state_path = path
AND block_number > height
AND block_number <= (SELECT block_number
FROM eth.header_cids
WHERE block_hash = hash)
AND state_cids.node_type = 3
LIMIT 1);
$$;
--
-- Name: TABLE header_cids; Type: COMMENT; Schema: eth; Owner: -
-- Name: was_storage_removed(bytea, bigint, character varying); Type: FUNCTION; Schema: public; Owner: -
--
COMMENT ON TABLE eth.header_cids IS '@name EthHeaderCids';
--
-- Name: COLUMN header_cids.node_id; Type: COMMENT; Schema: eth; Owner: -
--
COMMENT ON COLUMN eth.header_cids.node_id IS '@name EthNodeID';
CREATE FUNCTION public.was_storage_removed(path bytea, height bigint, hash character varying) RETURNS boolean
LANGUAGE sql
AS $$
SELECT exists(SELECT 1
FROM eth.storage_cids
INNER JOIN eth.state_cids ON (storage_cids.state_id = state_cids.id)
INNER JOIN eth.header_cids ON (state_cids.header_id = header_cids.id)
WHERE storage_path = path
AND block_number > height
AND block_number <= (SELECT block_number
FROM eth.header_cids
WHERE block_hash = hash)
AND storage_cids.node_type = 3
LIMIT 1);
$$;
--
+8 -1
View File
@@ -12,8 +12,15 @@
ipcPath = "~/.vulcanize/vulcanize.ipc" # $SERVER_IPC_PATH
wsPath = "127.0.0.1:8081" # $SERVER_WS_PATH
httpPath = "127.0.0.1:8082" # $SERVER_HTTP_PATH
graphql = true # $SERVER_GRAPHQL
graphqlEndpoint = "127.0.0.1:8083" # $SERVER_GRAPHQL_ENDPOINT
[ethereum]
chainID = "1" # $ETH_CHAIN_ID
defaultSender = "" # $ETH_DEFAULT_SENDER_ADDR
rpcGasCap = "1000000000000" # $ETH_RPC_GAS_CAP
rpcGasCap = "1000000000000" # $ETH_RPC_GAS_CAP
httpPath = "127.0.0.1:8545" # $ETH_HTTP_PATH
nodeID = "arch1" # $ETH_NODE_ID
clientName = "Geth" # $ETH_CLIENT_NAME
genesisBlock = "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" # $ETH_GENESIS_BLOCK
networkID = "1" # $ETH_NETWORK_ID
+1
View File
@@ -4,6 +4,7 @@ go 1.13
require (
github.com/ethereum/go-ethereum v1.9.11
github.com/graph-gophers/graphql-go v0.0.0-20201003130358-c5bdf3b1108e
github.com/ipfs/go-block-format v0.0.2
github.com/ipfs/go-cid v0.0.5
github.com/ipfs/go-ipfs-blockstore v1.0.0
+2
View File
@@ -224,6 +224,8 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc=
github.com/graph-gophers/graphql-go v0.0.0-20201003130358-c5bdf3b1108e h1:IpssFbpfPSx/3c7x601Npx+UOQ4tqd0Rk4sObCQ+zlQ=
github.com/graph-gophers/graphql-go v0.0.0-20201003130358-c5bdf3b1108e/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+593 -73
View File
@@ -29,7 +29,10 @@ import (
"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/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
"github.com/sirupsen/logrus"
@@ -44,14 +47,76 @@ const APIName = "eth"
const APIVersion = "0.0.1"
type PublicEthAPI struct {
// Local db backend
B *Backend
// Remote node for forwarding cache misses
rpc *rpc.Client
ethClient *ethclient.Client
}
// NewPublicEthAPI creates a new PublicEthAPI with the provided underlying Backend
func NewPublicEthAPI(b *Backend) *PublicEthAPI {
return &PublicEthAPI{
B: b,
func NewPublicEthAPI(b *Backend, client *rpc.Client) *PublicEthAPI {
var ethClient *ethclient.Client
if client != nil {
ethClient = ethclient.NewClient(client)
}
return &PublicEthAPI{
B: b,
rpc: client,
ethClient: ethClient,
}
}
/*
Headers and blocks
*/
// GetHeaderByNumber returns the requested canonical block header.
// * When blockNr is -1 the chain head is returned.
// * We cannot support pending block calls since we do not have an active miner
func (pea *PublicEthAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error) {
header, err := pea.B.HeaderByNumber(ctx, number)
if header != nil && err == nil {
return pea.rpcMarshalHeader(header)
}
if pea.ethClient != nil {
if header, err := pea.ethClient.HeaderByNumber(ctx, big.NewInt(number.Int64())); header != nil && err == nil {
return pea.rpcMarshalHeader(header)
}
}
return nil, err
}
// GetHeaderByHash returns the requested header by hash.
func (pea *PublicEthAPI) GetHeaderByHash(ctx context.Context, hash common.Hash) map[string]interface{} {
header, err := pea.B.HeaderByHash(ctx, hash)
if header != nil && err == nil {
if res, err := pea.rpcMarshalHeader(header); err == nil {
return res
}
}
if pea.ethClient != nil {
if header, err := pea.ethClient.HeaderByHash(ctx, hash); header != nil && err == nil {
if res, err := pea.rpcMarshalHeader(header); err != nil {
return res
}
}
}
return nil
}
// rpcMarshalHeader uses the generalized output filler, then adds the total difficulty field
func (pea *PublicEthAPI) rpcMarshalHeader(header *types.Header) (map[string]interface{}, error) {
fields := RPCMarshalHeader(header)
td, err := pea.B.GetTd(header.Hash())
if err != nil {
return nil, err
}
fields["totalDifficulty"] = (*hexutil.Big)(td)
return fields, nil
}
// BlockNumber returns the block number of the chain head.
@@ -60,10 +125,379 @@ func (pea *PublicEthAPI) BlockNumber() hexutil.Uint64 {
return hexutil.Uint64(number)
}
// GetBlockByNumber returns the requested canonical block.
// * When blockNr is -1 the chain head is returned.
// * We cannot support pending block calls since we do not have an active miner
// * When fullTx is true all transactions in the block are returned, otherwise
// only the transaction hash is returned.
func (pea *PublicEthAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) {
block, err := pea.B.BlockByNumber(ctx, number)
if block != nil && err == nil {
return pea.rpcMarshalBlock(block, true, fullTx)
}
if pea.ethClient != nil {
if block, err := pea.ethClient.BlockByNumber(ctx, big.NewInt(number.Int64())); block != nil && err == nil {
return pea.rpcMarshalBlock(block, true, fullTx)
}
}
return nil, err
}
// GetBlockByHash returns the requested block. When fullTx is true all transactions in the block are returned in full
// detail, otherwise only the transaction hash is returned.
func (pea *PublicEthAPI) GetBlockByHash(ctx context.Context, hash common.Hash, fullTx bool) (map[string]interface{}, error) {
block, err := pea.B.BlockByHash(ctx, hash)
if block != nil && err == nil {
return pea.rpcMarshalBlock(block, true, fullTx)
}
if pea.ethClient != nil {
if block, err := pea.ethClient.BlockByHash(ctx, hash); block != nil && err == nil {
return pea.rpcMarshalBlock(block, true, fullTx)
}
}
return nil, err
}
/*
Uncles
*/
// GetUncleByBlockNumberAndIndex returns the uncle block for the given block hash and index. When fullTx is true
// all transactions in the block are returned in full detail, otherwise only the transaction hash is returned.
func (pea *PublicEthAPI) GetUncleByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) (map[string]interface{}, error) {
block, err := pea.B.BlockByNumber(ctx, blockNr)
if block != nil && err == nil {
uncles := block.Uncles()
if index >= hexutil.Uint(len(uncles)) {
logrus.Debugf("uncle with index %s request at block number %d was not found", index.String(), blockNr.Int64())
return nil, nil
}
block = types.NewBlockWithHeader(uncles[index])
return pea.rpcMarshalBlock(block, false, false)
}
if pea.rpc != nil {
if uncle, uncleHashes, err := getBlockAndUncleHashes(pea.rpc, ctx, "eth_getUncleByBlockNumberAndIndex", blockNr, index); uncle != nil && err == nil {
return pea.rpcMarshalBlockWithUncleHashes(uncle, uncleHashes, false, false)
}
}
return nil, err
}
// GetUncleByBlockHashAndIndex returns the uncle block for the given block hash and index. When fullTx is true
// all transactions in the block are returned in full detail, otherwise only the transaction hash is returned.
func (pea *PublicEthAPI) GetUncleByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) (map[string]interface{}, error) {
block, err := pea.B.BlockByHash(ctx, blockHash)
if block != nil {
uncles := block.Uncles()
if index >= hexutil.Uint(len(uncles)) {
logrus.Debugf("uncle with index %s request at block hash %s was not found", index.String(), blockHash.Hex())
return nil, nil
}
block = types.NewBlockWithHeader(uncles[index])
return pea.rpcMarshalBlock(block, false, false)
}
if pea.rpc != nil {
if uncle, uncleHashes, err := getBlockAndUncleHashes(pea.rpc, ctx, "eth_getUncleByBlockHashAndIndex", blockHash, index); uncle != nil && err == nil {
return pea.rpcMarshalBlockWithUncleHashes(uncle, uncleHashes, false, false)
}
}
return nil, err
}
// GetUncleCountByBlockNumber returns number of uncles in the block for the given block number
func (pea *PublicEthAPI) GetUncleCountByBlockNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint {
if block, err := pea.B.BlockByNumber(ctx, blockNr); block != nil && err == nil {
n := hexutil.Uint(len(block.Uncles()))
return &n
}
if pea.rpc != nil {
var num *hexutil.Uint
if err := pea.rpc.CallContext(ctx, &num, "eth_getUncleCountByBlockNumber", blockNr); num != nil && err == nil {
return num
}
}
return nil
}
// GetUncleCountByBlockHash returns number of uncles in the block for the given block hash
func (pea *PublicEthAPI) GetUncleCountByBlockHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint {
if block, err := pea.B.BlockByHash(ctx, blockHash); block != nil && err == nil {
n := hexutil.Uint(len(block.Uncles()))
return &n
}
if pea.rpc != nil {
var num *hexutil.Uint
if err := pea.rpc.CallContext(ctx, &num, "eth_getUncleCountByBlockHash", blockHash); num != nil && err == nil {
return num
}
}
return nil
}
/*
Transactions
*/
// GetTransactionCount returns the number of transactions the given address has sent for the given block number
func (pea *PublicEthAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Uint64, error) {
count, err := pea.localGetTransactionCount(ctx, address, blockNrOrHash)
if count != nil && err == nil {
return count, nil
}
if pea.rpc != nil {
var num *hexutil.Uint64
if err := pea.rpc.CallContext(ctx, &num, "eth_getTransactionCount", address, blockNrOrHash); num != nil && err == nil {
return num, nil
}
}
return nil, err
}
func (pea *PublicEthAPI) localGetTransactionCount(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Uint64, error) {
account, err := pea.B.GetAccountByNumberOrHash(ctx, address, blockNrOrHash)
if err != nil {
return nil, err
}
nonce := hexutil.Uint64(account.Nonce)
return &nonce, nil
}
// GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number.
func (pea *PublicEthAPI) GetBlockTransactionCountByNumber(ctx context.Context, blockNr rpc.BlockNumber) *hexutil.Uint {
if block, _ := pea.B.BlockByNumber(ctx, blockNr); block != nil {
n := hexutil.Uint(len(block.Transactions()))
return &n
}
if pea.rpc != nil {
var num *hexutil.Uint
if err := pea.rpc.CallContext(ctx, &num, "eth_getBlockTransactionCountByNumber", blockNr); num != nil && err == nil {
return num
}
}
return nil
}
// GetBlockTransactionCountByHash returns the number of transactions in the block with the given hash.
func (pea *PublicEthAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) *hexutil.Uint {
if block, _ := pea.B.BlockByHash(ctx, blockHash); block != nil {
n := hexutil.Uint(len(block.Transactions()))
return &n
}
if pea.rpc != nil {
var num *hexutil.Uint
if err := pea.rpc.CallContext(ctx, &num, "eth_getBlockTransactionCountByHash", blockHash); num != nil && err == nil {
return num
}
}
return nil
}
// GetTransactionByBlockNumberAndIndex returns the transaction for the given block number and index.
func (pea *PublicEthAPI) GetTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) *RPCTransaction {
if block, _ := pea.B.BlockByNumber(ctx, blockNr); block != nil {
return newRPCTransactionFromBlockIndex(block, uint64(index))
}
if pea.rpc != nil {
var tx *RPCTransaction
if err := pea.rpc.CallContext(ctx, &tx, "eth_getTransactionByBlockNumberAndIndex", blockNr, index); tx != nil && err == nil {
return tx
}
}
return nil
}
// GetTransactionByBlockHashAndIndex returns the transaction for the given block hash and index.
func (pea *PublicEthAPI) GetTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) *RPCTransaction {
if block, _ := pea.B.BlockByHash(ctx, blockHash); block != nil {
return newRPCTransactionFromBlockIndex(block, uint64(index))
}
if pea.rpc != nil {
var tx *RPCTransaction
if err := pea.rpc.CallContext(ctx, &tx, "eth_getTransactionByBlockHashAndIndex", blockHash, index); tx != nil && err == nil {
return tx
}
}
return nil
}
// GetRawTransactionByBlockNumberAndIndex returns the bytes of the transaction for the given block number and index.
func (pea *PublicEthAPI) GetRawTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) hexutil.Bytes {
if block, _ := pea.B.BlockByNumber(ctx, blockNr); block != nil {
return newRPCRawTransactionFromBlockIndex(block, uint64(index))
}
if pea.rpc != nil {
var tx hexutil.Bytes
if err := pea.rpc.CallContext(ctx, &tx, "eth_getRawTransactionByBlockNumberAndIndex", blockNr, index); tx != nil && err == nil {
return tx
}
}
return nil
}
// GetRawTransactionByBlockHashAndIndex returns the bytes of the transaction for the given block hash and index.
func (pea *PublicEthAPI) GetRawTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) hexutil.Bytes {
if block, _ := pea.B.BlockByHash(ctx, blockHash); block != nil {
return newRPCRawTransactionFromBlockIndex(block, uint64(index))
}
if pea.rpc != nil {
var tx hexutil.Bytes
if err := pea.rpc.CallContext(ctx, &tx, "eth_getRawTransactionByBlockHashAndIndex", blockHash, index); tx != nil && err == nil {
return tx
}
}
return nil
}
// GetTransactionByHash returns the transaction for the given hash
// eth ipld-eth-server cannot currently handle pending/tx_pool txs
func (pea *PublicEthAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*RPCTransaction, error) {
tx, blockHash, blockNumber, index, err := pea.B.GetTransaction(ctx, hash)
if tx != nil && err == nil {
return NewRPCTransaction(tx, blockHash, blockNumber, index), nil
}
if pea.rpc != nil {
var tx *RPCTransaction
if err := pea.rpc.CallContext(ctx, &tx, "eth_getTransactionByHash", hash); tx != nil && err == nil {
return tx, nil
}
}
return nil, err
}
// GetRawTransactionByHash returns the bytes of the transaction for the given hash.
func (pea *PublicEthAPI) GetRawTransactionByHash(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
// Retrieve a finalized transaction, or a pooled otherwise
tx, _, _, _, err := pea.B.GetTransaction(ctx, hash)
if tx != nil && err == nil {
return rlp.EncodeToBytes(tx)
}
if pea.rpc != nil {
var tx hexutil.Bytes
if err := pea.rpc.CallContext(ctx, &tx, "eth_getRawTransactionByHash", hash); tx != nil && err == nil {
return tx, nil
}
}
return nil, err
}
/*
Receipts and Logs
*/
// GetTransactionReceipt returns the transaction receipt for the given transaction hash.
func (pea *PublicEthAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) {
receipt, err := pea.localGetTransactionReceipt(ctx, hash)
if receipt != nil && err == nil {
return receipt, nil
}
if pea.rpc != nil {
if receipt := pea.remoteGetTransactionReceipt(ctx, hash); receipt != nil {
return receipt, nil
}
}
return nil, err
}
func (pea *PublicEthAPI) localGetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) {
// TODO: this can be optimized for Postgres
tx, blockHash, blockNumber, index, err := pea.B.GetTransaction(ctx, hash)
if err != nil {
return nil, err
}
if tx == nil {
return nil, nil
}
receipts, err := pea.B.GetReceipts(ctx, blockHash)
if err != nil {
return nil, err
}
if len(receipts) <= int(index) {
return nil, nil
}
receipt := receipts[index]
var signer types.Signer = types.FrontierSigner{}
if tx.Protected() {
signer = types.NewEIP155Signer(tx.ChainId())
}
from, _ := types.Sender(signer, tx)
fields := map[string]interface{}{
"blockHash": blockHash,
"blockNumber": hexutil.Uint64(blockNumber),
"transactionHash": hash,
"transactionIndex": hexutil.Uint64(index),
"from": from,
"to": tx.To(),
"gasUsed": hexutil.Uint64(receipt.GasUsed),
"cumulativeGasUsed": hexutil.Uint64(receipt.CumulativeGasUsed),
"contractAddress": nil,
"logs": receipt.Logs,
"logsBloom": receipt.Bloom,
}
// Assign receipt status or post state.
if len(receipt.PostState) > 0 {
fields["root"] = hexutil.Bytes(receipt.PostState)
} else {
fields["status"] = hexutil.Uint(receipt.Status)
}
if receipt.Logs == nil {
fields["logs"] = []*types.Log{}
}
// If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation
if receipt.ContractAddress != (common.Address{}) {
fields["contractAddress"] = receipt.ContractAddress
}
return fields, nil
}
func (pea *PublicEthAPI) remoteGetTransactionReceipt(ctx context.Context, hash common.Hash) map[string]interface{} {
var rct *RPCReceipt
if err := pea.rpc.CallContext(ctx, &rct, "eth_getTransactionReceipt", hash); rct != nil && err == nil {
return map[string]interface{}{
"blockHash": rct.BlockHash,
"blockNumber": rct.BlockNumber,
"transactionHash": rct.TransactionHash,
"transactionIndex": rct.TransactionIndex,
"from": rct.From,
"to": rct.To,
"gasUsed": rct.GasUsed,
"cumulativeGasUsed": rct.CumulativeGsUsed,
"contractAddress": rct.ContractAddress,
"logs": rct.Logs,
"logsBloom": rct.Bloom,
"root": rct.Root,
"status": rct.Status,
}
}
return nil
}
// GetLogs returns logs matching the given argument that are stored within the state.
//
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs
func (pea *PublicEthAPI) GetLogs(ctx context.Context, crit ethereum.FilterQuery) ([]*types.Log, error) {
logs, err := pea.localGetLogs(ctx, crit)
if err != nil && pea.rpc != nil {
if arg, err := toFilterArg(crit); err == nil {
var res []*types.Log
if err := pea.rpc.CallContext(ctx, &res, "eth_getLogs", arg); err == nil {
return res, nil
}
}
}
return logs, err
}
func (pea *PublicEthAPI) localGetLogs(ctx context.Context, crit ethereum.FilterQuery) ([]*types.Log, error) {
// TODO: this can be optimized away from using the old cid retriever and ipld fetcher interfaces
// Convert FilterQuery into ReceiptFilter
addrStrs := make([]string, len(crit.Addresses))
for i, addr := range crit.Addresses {
@@ -120,11 +554,7 @@ func (pea *PublicEthAPI) GetLogs(ctx context.Context, crit ethereum.FilterQuery)
startingBlock := crit.FromBlock
endingBlock := crit.ToBlock
if startingBlock == nil {
startingBlockInt, err := pea.B.Retriever.RetrieveFirstBlockNumber()
if err != nil {
return nil, err
}
startingBlock = big.NewInt(startingBlockInt)
startingBlock = common.Big0
}
if endingBlock == nil {
endingBlockInt, err := pea.B.Retriever.RetrieveLastBlockNumber()
@@ -154,53 +584,131 @@ func (pea *PublicEthAPI) GetLogs(ctx context.Context, crit ethereum.FilterQuery)
return logs, err // need to return err variable so that we return the err = tx.Commit() assignment in the defer
}
// GetHeaderByNumber returns the requested canonical block header.
// * When blockNr is -1 the chain head is returned.
// * We cannot support pending block calls since we do not have an active miner
func (pea *PublicEthAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error) {
header, err := pea.B.HeaderByNumber(ctx, number)
if header != nil && err == nil {
return pea.rpcMarshalHeader(header)
/*
State and Storage
*/
// GetBalance returns the amount of wei for the given address in the state of the
// given block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta
// block numbers are also allowed.
func (pea *PublicEthAPI) GetBalance(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Big, error) {
bal, err := pea.localGetBalance(ctx, address, blockNrOrHash)
if bal != nil && err == nil {
return bal, nil
}
if pea.rpc != nil {
var res *hexutil.Big
if err := pea.rpc.CallContext(ctx, &res, "eth_getBalance", address, blockNrOrHash); res != nil && err == nil {
return res, nil
}
}
return nil, err
}
// GetBlockByNumber returns the requested canonical block.
// * When blockNr is -1 the chain head is returned.
// * We cannot support pending block calls since we do not have an active miner
// * When fullTx is true all transactions in the block are returned, otherwise
// only the transaction hash is returned.
func (pea *PublicEthAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) {
block, err := pea.B.BlockByNumber(ctx, number)
if block != nil && err == nil {
return pea.rpcMarshalBlock(block, true, fullTx)
}
return nil, err
}
// GetBlockByHash returns the requested block. When fullTx is true all transactions in the block are returned in full
// detail, otherwise only the transaction hash is returned.
func (pea *PublicEthAPI) GetBlockByHash(ctx context.Context, hash common.Hash, fullTx bool) (map[string]interface{}, error) {
block, err := pea.B.BlockByHash(ctx, hash)
if block != nil {
return pea.rpcMarshalBlock(block, true, fullTx)
}
return nil, err
}
// GetTransactionByHash returns the transaction for the given hash
// eth ipld-eth-server cannot currently handle pending/tx_pool txs
func (pea *PublicEthAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*RPCTransaction, error) {
// Try to return an already finalized transaction
tx, blockHash, blockNumber, index, err := pea.B.GetTransaction(ctx, hash)
func (pea *PublicEthAPI) localGetBalance(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Big, error) {
account, err := pea.B.GetAccountByNumberOrHash(ctx, address, blockNrOrHash)
if err != nil {
return nil, err
}
if tx != nil {
return NewRPCTransaction(tx, blockHash, blockNumber, index), nil
return (*hexutil.Big)(account.Balance), nil
}
// GetStorageAt returns the storage from the state at the given address, key and
// block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta block
// numbers are also allowed.
func (pea *PublicEthAPI) GetStorageAt(ctx context.Context, address common.Address, key string, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
storageVal, err := pea.B.GetStorageByNumberOrHash(ctx, address, common.HexToHash(key), blockNrOrHash)
if storageVal != nil && err == nil {
return storageVal, nil
}
// Transaction unknown, return as such
return nil, nil
if pea.rpc != nil {
var res hexutil.Bytes
if err := pea.rpc.CallContext(ctx, &res, "eth_getStorageAt", address, key, blockNrOrHash); res != nil && err == nil {
return res, nil
}
}
return nil, err
}
// GetCode returns the code stored at the given address in the state for the given block number.
func (pea *PublicEthAPI) GetCode(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
code, err := pea.B.GetCodeByNumberOrHash(ctx, address, blockNrOrHash)
if code != nil && err == nil {
return code, nil
}
if pea.rpc != nil {
var res hexutil.Bytes
if err := pea.rpc.CallContext(ctx, &res, "eth_getCode", address, blockNrOrHash); res != nil && err == nil {
return res, nil
}
}
return nil, err
}
// GetProof returns the Merkle-proof for a given account and optionally some storage keys.
func (pea *PublicEthAPI) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNrOrHash rpc.BlockNumberOrHash) (*AccountResult, error) {
proof, err := pea.localGetProof(ctx, address, storageKeys, blockNrOrHash)
if proof != nil && err == nil {
return proof, nil
}
if pea.rpc != nil {
var res *AccountResult
if err := pea.rpc.CallContext(ctx, &res, "eth_getProof", address, storageKeys, blockNrOrHash); res != nil && err == nil {
return res, nil
}
}
return nil, err
}
func (pea *PublicEthAPI) localGetProof(ctx context.Context, address common.Address, storageKeys []string, blockNrOrHash rpc.BlockNumberOrHash) (*AccountResult, error) {
state, _, err := pea.B.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
storageTrie := state.StorageTrie(address)
storageHash := types.EmptyRootHash
codeHash := state.GetCodeHash(address)
storageProof := make([]StorageResult, len(storageKeys))
// if we have a storageTrie, (which means the account exists), we can update the storagehash
if storageTrie != nil {
storageHash = storageTrie.Hash()
} else {
// no storageTrie means the account does not exist, so the codeHash is the hash of an empty bytearray.
codeHash = crypto.Keccak256Hash(nil)
}
// create the proof for the storageKeys
for i, key := range storageKeys {
if storageTrie != nil {
proof, storageError := state.GetStorageProof(address, common.HexToHash(key))
if storageError != nil {
return nil, storageError
}
storageProof[i] = StorageResult{key, (*hexutil.Big)(state.GetState(address, common.HexToHash(key)).Big()), common.ToHexArray(proof)}
} else {
storageProof[i] = StorageResult{key, &hexutil.Big{}, []string{}}
}
}
// create the accountProof
accountProof, proofErr := state.GetProof(address)
if proofErr != nil {
return nil, proofErr
}
return &AccountResult{
Address: address,
AccountProof: common.ToHexArray(accountProof),
Balance: (*hexutil.Big)(state.GetBalance(address)),
CodeHash: codeHash,
Nonce: hexutil.Uint64(state.GetNonce(address)),
StorageHash: storageHash,
StorageProof: storageProof,
}, state.Error()
}
// Call executes the given transaction on the state for the given block number.
@@ -214,35 +722,17 @@ func (pea *PublicEthAPI) Call(ctx context.Context, args CallArgs, blockNrOrHash
if overrides != nil {
accounts = *overrides
}
result, _, failed, err := DoCall(ctx, pea.B, args, blockNrOrHash, accounts, 5*time.Second, pea.B.Config.RPCGasCap)
res, _, failed, err := DoCall(ctx, pea.B, args, blockNrOrHash, accounts, 5*time.Second, pea.B.Config.RPCGasCap)
if (failed || err != nil) && pea.rpc != nil {
var hex hexutil.Bytes
if err := pea.rpc.CallContext(ctx, &hex, "eth_call", args, blockNrOrHash, overrides); hex != nil && err == nil {
return hex, nil
}
}
if failed && err == nil {
return nil, errors.New("eth_call failed without error")
}
return (hexutil.Bytes)(result), err
}
// CallArgs represents the arguments for a call.
type CallArgs struct {
From *common.Address `json:"from"`
To *common.Address `json:"to"`
Gas *hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"`
Value *hexutil.Big `json:"value"`
Data *hexutil.Bytes `json:"data"`
}
// account indicates the overriding fields of account during the execution of
// a message call.
// Note, state and stateDiff can't be specified at the same time. If state is
// set, message execution will only use the data in the given state. Otherwise
// if statDiff is set, all diff will be applied first and then execute the call
// message.
type account struct {
Nonce *hexutil.Uint64 `json:"nonce"`
Code *hexutil.Bytes `json:"code"`
Balance **hexutil.Big `json:"balance"`
State *map[common.Hash]common.Hash `json:"state"`
StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
return (hexutil.Bytes)(res), err
}
func DoCall(ctx context.Context, b *Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, timeout time.Duration, globalGasCap *big.Int) ([]byte, uint64, bool, error) {
@@ -351,3 +841,33 @@ func DoCall(ctx context.Context, b *Backend, args CallArgs, blockNrOrHash rpc.Bl
}
return res, gas, failed, err
}
// rpcMarshalBlock uses the generalized output filler, then adds the total difficulty field
func (pea *PublicEthAPI) rpcMarshalBlock(b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) {
fields, err := RPCMarshalBlock(b, inclTx, fullTx)
if err != nil {
return nil, err
}
if inclTx {
td, err := pea.B.GetTd(b.Hash())
if err != nil {
return nil, err
}
fields["totalDifficulty"] = (*hexutil.Big)(td)
}
return fields, err
}
// rpcMarshalBlockWithUncleHashes uses the generalized output filler, then adds the total difficulty field
func (pea *PublicEthAPI) rpcMarshalBlockWithUncleHashes(b *types.Block, uncleHashes []common.Hash, inclTx bool, fullTx bool) (map[string]interface{}, error) {
fields, err := RPCMarshalBlockWithUncleHashes(b, uncleHashes, inclTx, fullTx)
if err != nil {
return nil, err
}
td, err := pea.B.GetTd(b.Hash())
if err != nil {
return nil, err
}
fields["totalDifficulty"] = (*hexutil.Big)(td)
return fields, err
}
+511 -95
View File
@@ -24,12 +24,15 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
. "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"
shared2 "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"
@@ -37,6 +40,12 @@ import (
)
var (
randomAddr = common.HexToAddress("0x1C3ab14BBaD3D99F4203bd7a11aCB94882050E6f")
randomHash = crypto.Keccak256Hash(randomAddr.Bytes())
number = rpc.BlockNumber(test_helpers.BlockNumber.Int64())
wrongNumber = rpc.BlockNumber(number + 1)
blockHash = test_helpers.MockBlock.Header().Hash()
ctx = context.Background()
expectedBlock = map[string]interface{}{
"number": (*hexutil.Big)(test_helpers.MockBlock.Number()),
"hash": test_helpers.MockBlock.Hash(),
@@ -59,7 +68,7 @@ var (
}
expectedHeader = map[string]interface{}{
"number": (*hexutil.Big)(test_helpers.MockBlock.Header().Number),
"hash": test_helpers.MockBlock.Header().Hash(),
"hash": blockHash,
"parentHash": test_helpers.MockBlock.Header().ParentHash,
"nonce": test_helpers.MockBlock.Header().Nonce,
"mixHash": test_helpers.MockBlock.Header().MixDigest,
@@ -77,26 +86,115 @@ var (
"receiptsRoot": test_helpers.MockBlock.Header().ReceiptHash,
"totalDifficulty": (*hexutil.Big)(test_helpers.MockBlock.Header().Difficulty),
}
expectedTransaction = eth.NewRPCTransaction(test_helpers.MockTransactions[0], test_helpers.MockBlock.Hash(), test_helpers.MockBlock.NumberU64(), 0)
expectedUncle1 = map[string]interface{}{
"number": (*hexutil.Big)(test_helpers.MockUncles[0].Number),
"hash": test_helpers.MockUncles[0].Hash(),
"parentHash": test_helpers.MockUncles[0].ParentHash,
"nonce": test_helpers.MockUncles[0].Nonce,
"mixHash": test_helpers.MockUncles[0].MixDigest,
"sha3Uncles": test_helpers.MockUncles[0].UncleHash,
"logsBloom": test_helpers.MockUncles[0].Bloom,
"stateRoot": test_helpers.MockUncles[0].Root,
"miner": test_helpers.MockUncles[0].Coinbase,
"difficulty": (*hexutil.Big)(test_helpers.MockUncles[0].Difficulty),
"extraData": hexutil.Bytes(test_helpers.MockUncles[0].Extra),
"size": hexutil.Uint64(types.NewBlockWithHeader(test_helpers.MockUncles[0]).Size()),
"gasLimit": hexutil.Uint64(test_helpers.MockUncles[0].GasLimit),
"gasUsed": hexutil.Uint64(test_helpers.MockUncles[0].GasUsed),
"timestamp": hexutil.Uint64(test_helpers.MockUncles[0].Time),
"transactionsRoot": test_helpers.MockUncles[0].TxHash,
"receiptsRoot": test_helpers.MockUncles[0].ReceiptHash,
"uncles": []common.Hash{},
}
expectedUncle2 = map[string]interface{}{
"number": (*hexutil.Big)(test_helpers.MockUncles[1].Number),
"hash": test_helpers.MockUncles[1].Hash(),
"parentHash": test_helpers.MockUncles[1].ParentHash,
"nonce": test_helpers.MockUncles[1].Nonce,
"mixHash": test_helpers.MockUncles[1].MixDigest,
"sha3Uncles": test_helpers.MockUncles[1].UncleHash,
"logsBloom": test_helpers.MockUncles[1].Bloom,
"stateRoot": test_helpers.MockUncles[1].Root,
"miner": test_helpers.MockUncles[1].Coinbase,
"difficulty": (*hexutil.Big)(test_helpers.MockUncles[1].Difficulty),
"extraData": hexutil.Bytes(test_helpers.MockUncles[1].Extra),
"size": hexutil.Uint64(types.NewBlockWithHeader(test_helpers.MockUncles[1]).Size()),
"gasLimit": hexutil.Uint64(test_helpers.MockUncles[1].GasLimit),
"gasUsed": hexutil.Uint64(test_helpers.MockUncles[1].GasUsed),
"timestamp": hexutil.Uint64(test_helpers.MockUncles[1].Time),
"transactionsRoot": test_helpers.MockUncles[1].TxHash,
"receiptsRoot": test_helpers.MockUncles[1].ReceiptHash,
"uncles": []common.Hash{},
}
expectedTransaction = eth.NewRPCTransaction(test_helpers.MockTransactions[0], test_helpers.MockBlock.Hash(), test_helpers.MockBlock.NumberU64(), 0)
expectedTransaction2 = eth.NewRPCTransaction(test_helpers.MockTransactions[1], test_helpers.MockBlock.Hash(), test_helpers.MockBlock.NumberU64(), 1)
expectedTransaction3 = eth.NewRPCTransaction(test_helpers.MockTransactions[2], test_helpers.MockBlock.Hash(), test_helpers.MockBlock.NumberU64(), 2)
expectRawTx, _ = rlp.EncodeToBytes(test_helpers.MockTransactions[0])
expectRawTx2, _ = rlp.EncodeToBytes(test_helpers.MockTransactions[1])
expectRawTx3, _ = rlp.EncodeToBytes(test_helpers.MockTransactions[2])
expectedReceipt = map[string]interface{}{
"blockHash": blockHash,
"blockNumber": hexutil.Uint64(uint64(number.Int64())),
"transactionHash": expectedTransaction.Hash,
"transactionIndex": hexutil.Uint64(0),
"from": expectedTransaction.From,
"to": expectedTransaction.To,
"gasUsed": hexutil.Uint64(test_helpers.MockReceipts[0].GasUsed),
"cumulativeGasUsed": hexutil.Uint64(test_helpers.MockReceipts[0].CumulativeGasUsed),
"contractAddress": nil,
"logs": test_helpers.MockReceipts[0].Logs,
"logsBloom": test_helpers.MockReceipts[0].Bloom,
"root": hexutil.Bytes(test_helpers.MockReceipts[0].PostState),
}
expectedReceipt2 = map[string]interface{}{
"blockHash": blockHash,
"blockNumber": hexutil.Uint64(uint64(number.Int64())),
"transactionHash": expectedTransaction2.Hash,
"transactionIndex": hexutil.Uint64(1),
"from": expectedTransaction2.From,
"to": expectedTransaction2.To,
"gasUsed": hexutil.Uint64(test_helpers.MockReceipts[1].GasUsed),
"cumulativeGasUsed": hexutil.Uint64(test_helpers.MockReceipts[1].CumulativeGasUsed),
"contractAddress": nil,
"logs": test_helpers.MockReceipts[1].Logs,
"logsBloom": test_helpers.MockReceipts[1].Bloom,
"root": hexutil.Bytes(test_helpers.MockReceipts[1].PostState),
}
expectedReceipt3 = map[string]interface{}{
"blockHash": blockHash,
"blockNumber": hexutil.Uint64(uint64(number.Int64())),
"transactionHash": expectedTransaction3.Hash,
"transactionIndex": hexutil.Uint64(2),
"from": expectedTransaction3.From,
"to": expectedTransaction3.To,
"gasUsed": hexutil.Uint64(test_helpers.MockReceipts[2].GasUsed),
"cumulativeGasUsed": hexutil.Uint64(test_helpers.MockReceipts[2].CumulativeGasUsed),
"contractAddress": nil,
"logs": test_helpers.MockReceipts[2].Logs,
"logsBloom": test_helpers.MockReceipts[2].Bloom,
"root": hexutil.Bytes(test_helpers.MockReceipts[2].PostState),
}
)
var _ = Describe("API", func() {
var (
db *postgres.DB
indexAndPublisher *eth2.IPLDPublisher
backend *eth.Backend
api *eth.PublicEthAPI
db *postgres.DB
api *eth.PublicEthAPI
)
BeforeEach(func() {
// Test db setup, rather than using BeforeEach we only need to setup once since the tests do not mutate the database
// Note: if you focus one of the tests be sure to focus this and the defered It()
It("test init", func() {
var err error
db, err = shared.SetupDB()
Expect(err).ToNot(HaveOccurred())
indexAndPublisher = eth2.NewIPLDPublisher(db)
backend, err = eth.NewEthBackend(db, &eth.Config{})
indexAndPublisher := eth2.NewIPLDPublisher(db)
backend, err := eth.NewEthBackend(db, &eth.Config{})
Expect(err).ToNot(HaveOccurred())
api = eth.NewPublicEthAPI(backend)
api = eth.NewPublicEthAPI(backend, nil)
err = indexAndPublisher.Publish(test_helpers.MockConvertedPayload)
Expect(err).ToNot(HaveOccurred())
err = publishCode(db, test_helpers.ContractCodeHash, test_helpers.ContractCode)
Expect(err).ToNot(HaveOccurred())
uncles := test_helpers.MockBlock.Uncles()
uncleHashes := make([]common.Hash, len(uncles))
for i, uncle := range uncles {
@@ -104,69 +202,22 @@ var _ = Describe("API", func() {
}
expectedBlock["uncles"] = uncleHashes
})
AfterEach(func() {
eth.TearDownDB(db)
})
Describe("BlockNumber", func() {
It("Retrieves the head block number", func() {
bn := api.BlockNumber()
ubn := (uint64)(bn)
subn := strconv.FormatUint(ubn, 10)
Expect(subn).To(Equal(test_helpers.BlockNumber.String()))
})
})
// Single test db tear down at end of all tests
defer It("test teardown", func() { eth.TearDownDB(db) })
/*
Describe("GetTransactionByHash", func() {
It("Retrieves a transaction by hash", func() {
hash := test_helpers.MockTransactions[0].Hash()
tx, err := api.GetTransactionByHash(context.Background(), hash)
Expect(err).ToNot(HaveOccurred())
Expect(tx).To(Equal(expectedTransaction))
})
})
Headers and blocks
Describe("GetBlockByNumber", func() {
It("Retrieves a block by number", func() {
// without full txs
number, err := strconv.ParseInt(test_helpers.BlockNumber.String(), 10, 64)
Expect(err).ToNot(HaveOccurred())
block, err := api.GetBlockByNumber(context.Background(), rpc.BlockNumber(number), false)
Expect(err).ToNot(HaveOccurred())
transactionHashes := make([]interface{}, len(test_helpers.MockBlock.Transactions()))
for i, trx := range test_helpers.MockBlock.Transactions() {
transactionHashes[i] = trx.Hash()
}
expectedBlock["transactions"] = transactionHashes
for key, val := range expectedBlock {
Expect(val).To(Equal(block[key]))
}
// with full txs
block, err = api.GetBlockByNumber(context.Background(), rpc.BlockNumber(number), true)
Expect(err).ToNot(HaveOccurred())
transactions := make([]interface{}, len(test_helpers.MockBlock.Transactions()))
for i, trx := range test_helpers.MockBlock.Transactions() {
transactions[i] = eth.NewRPCTransactionFromBlockHash(test_helpers.MockBlock, trx.Hash())
}
expectedBlock["transactions"] = transactions
for key, val := range expectedBlock {
Expect(val).To(Equal(block[key]))
}
})
})
Describe("GetHeaderByNumber", func() {
*/
Describe("eth_getHeaderByNumber", func() {
It("Retrieves a header by number", func() {
number, err := strconv.ParseInt(test_helpers.BlockNumber.String(), 10, 64)
Expect(err).ToNot(HaveOccurred())
header, err := api.GetHeaderByNumber(context.Background(), rpc.BlockNumber(number))
header, err := api.GetHeaderByNumber(ctx, number)
Expect(err).ToNot(HaveOccurred())
Expect(header).To(Equal(expectedHeader))
})
It("Throws an error if a header cannot be found", func() {
number, err := strconv.ParseInt(test_helpers.BlockNumber.String(), 10, 64)
Expect(err).ToNot(HaveOccurred())
header, err := api.GetHeaderByNumber(context.Background(), rpc.BlockNumber(number+1))
header, err := api.GetHeaderByNumber(ctx, wrongNumber)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("sql: no rows in result set"))
Expect(header).To(BeNil())
@@ -175,10 +226,30 @@ var _ = Describe("API", func() {
})
})
Describe("GetBlockByHash", func() {
It("Retrieves a block by hash", func() {
// without full txs
block, err := api.GetBlockByHash(context.Background(), test_helpers.MockBlock.Hash(), false)
Describe("eth_getHeaderByHash", func() {
It("Retrieves a header by hash", func() {
header := api.GetHeaderByHash(ctx, blockHash)
Expect(header).To(Equal(expectedHeader))
})
It("Throws an error if a header cannot be found", func() {
header := api.GetHeaderByHash(ctx, randomHash)
Expect(header).To(BeNil())
})
})
Describe("eth_blockNumber", func() {
It("Retrieves the head block number", func() {
bn := api.BlockNumber()
ubn := (uint64)(bn)
subn := strconv.FormatUint(ubn, 10)
Expect(subn).To(Equal(test_helpers.BlockNumber.String()))
})
})
Describe("eth_getBlockByNumber", func() {
It("Retrieves a block by number, without full txs", func() {
block, err := api.GetBlockByNumber(ctx, number, false)
Expect(err).ToNot(HaveOccurred())
transactionHashes := make([]interface{}, len(test_helpers.MockBlock.Transactions()))
for i, trx := range test_helpers.MockBlock.Transactions() {
@@ -188,8 +259,9 @@ var _ = Describe("API", func() {
for key, val := range expectedBlock {
Expect(val).To(Equal(block[key]))
}
// with full txs
block, err = api.GetBlockByHash(context.Background(), test_helpers.MockBlock.Hash(), true)
})
It("Retrieves a block by number, with full txs", func() {
block, err := api.GetBlockByNumber(ctx, number, true)
Expect(err).ToNot(HaveOccurred())
transactions := make([]interface{}, len(test_helpers.MockBlock.Transactions()))
for i, trx := range test_helpers.MockBlock.Transactions() {
@@ -200,9 +272,288 @@ var _ = Describe("API", func() {
Expect(val).To(Equal(block[key]))
}
})
It("Throws an error if a block cannot be found", func() {
_, err := api.GetBlockByNumber(ctx, wrongNumber, false)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("sql: no rows in result set"))
})
})
Describe("GetLogs", func() {
Describe("eth_getBlockByHash", func() {
It("Retrieves a block by hash, without full txs", func() {
block, err := api.GetBlockByHash(ctx, test_helpers.MockBlock.Hash(), false)
Expect(err).ToNot(HaveOccurred())
transactionHashes := make([]interface{}, len(test_helpers.MockBlock.Transactions()))
for i, trx := range test_helpers.MockBlock.Transactions() {
transactionHashes[i] = trx.Hash()
}
expectedBlock["transactions"] = transactionHashes
for key, val := range expectedBlock {
Expect(val).To(Equal(block[key]))
}
})
It("Retrieves a block by hash, with full txs", func() {
block, err := api.GetBlockByHash(ctx, test_helpers.MockBlock.Hash(), true)
Expect(err).ToNot(HaveOccurred())
transactions := make([]interface{}, len(test_helpers.MockBlock.Transactions()))
for i, trx := range test_helpers.MockBlock.Transactions() {
transactions[i] = eth.NewRPCTransactionFromBlockHash(test_helpers.MockBlock, trx.Hash())
}
expectedBlock["transactions"] = transactions
for key, val := range expectedBlock {
Expect(val).To(Equal(block[key]))
}
})
It("Throws an error if a block cannot be found", func() {
_, err := api.GetBlockByHash(ctx, randomHash, false)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("sql: no rows in result set"))
})
})
/*
Uncles
*/
Describe("eth_getUncleByBlockNumberAndIndex", func() {
It("Retrieves the uncle at the provided index in the canoncial block with the provided hash", func() {
uncle1, err := api.GetUncleByBlockNumberAndIndex(ctx, number, 0)
Expect(err).ToNot(HaveOccurred())
Expect(uncle1).To(Equal(expectedUncle1))
uncle2, err := api.GetUncleByBlockNumberAndIndex(ctx, number, 1)
Expect(err).ToNot(HaveOccurred())
Expect(uncle2).To(Equal(expectedUncle2))
})
It("Throws an error if an block for blocknumber cannot be found", func() {
_, err := api.GetUncleByBlockNumberAndIndex(ctx, wrongNumber, 0)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("sql: no rows in result set"))
})
It("Returns `nil` if an uncle at the provided index does not exist for the block found for the provided block number", func() {
uncle, err := api.GetUncleByBlockNumberAndIndex(ctx, number, 2)
Expect(err).ToNot(HaveOccurred())
Expect(uncle).To(BeNil())
})
})
Describe("eth_getUncleByBlockHashAndIndex", func() {
It("Retrieves the uncle at the provided index in the block with the provided hash", func() {
uncle1, err := api.GetUncleByBlockHashAndIndex(ctx, blockHash, 0)
Expect(err).ToNot(HaveOccurred())
Expect(uncle1).To(Equal(expectedUncle1))
uncle2, err := api.GetUncleByBlockHashAndIndex(ctx, blockHash, 1)
Expect(err).ToNot(HaveOccurred())
Expect(uncle2).To(Equal(expectedUncle2))
})
It("Throws an error if an block for blockhash cannot be found", func() {
_, err := api.GetUncleByBlockHashAndIndex(ctx, randomHash, 0)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("sql: no rows in result set"))
})
It("Returns `nil` if an uncle at the provided index does not exist for the block with the provided hash", func() {
uncle, err := api.GetUncleByBlockHashAndIndex(ctx, blockHash, 2)
Expect(err).ToNot(HaveOccurred())
Expect(uncle).To(BeNil())
})
})
Describe("eth_getUncleCountByBlockNumber", func() {
It("Retrieves the number of uncles for the canonical block with the provided number", func() {
count := api.GetUncleCountByBlockNumber(ctx, number)
Expect(uint64(*count)).To(Equal(uint64(2)))
})
})
Describe("eth_getUncleCountByBlockHash", func() {
It("Retrieves the number of uncles for the block with the provided hash", func() {
count := api.GetUncleCountByBlockHash(ctx, blockHash)
Expect(uint64(*count)).To(Equal(uint64(2)))
})
})
/*
Transactions
*/
Describe("eth_getTransactionCount", func() {
It("Retrieves the number of transactions the given address has sent for the given block number", func() {
count, err := api.GetTransactionCount(ctx, test_helpers.ContractAddress, rpc.BlockNumberOrHashWithNumber(number))
Expect(err).ToNot(HaveOccurred())
Expect(*count).To(Equal(hexutil.Uint64(1)))
count, err = api.GetTransactionCount(ctx, test_helpers.AccountAddresss, rpc.BlockNumberOrHashWithNumber(number))
Expect(err).ToNot(HaveOccurred())
Expect(*count).To(Equal(hexutil.Uint64(0)))
})
It("Retrieves the number of transactions the given address has sent for the given block hash", func() {
count, err := api.GetTransactionCount(ctx, test_helpers.ContractAddress, rpc.BlockNumberOrHashWithHash(blockHash, true))
Expect(err).ToNot(HaveOccurred())
Expect(*count).To(Equal(hexutil.Uint64(1)))
count, err = api.GetTransactionCount(ctx, test_helpers.AccountAddresss, rpc.BlockNumberOrHashWithHash(blockHash, true))
Expect(err).ToNot(HaveOccurred())
Expect(*count).To(Equal(hexutil.Uint64(0)))
})
})
Describe("eth_getBlockTransactionCountByNumber", func() {
It("Retrieves the number of transactions in the canonical block with the provided number", func() {
count := api.GetBlockTransactionCountByNumber(ctx, number)
Expect(uint64(*count)).To(Equal(uint64(3)))
})
})
Describe("eth_getBlockTransactionCountByHash", func() {
It("Retrieves the number of transactions in the block with the provided hash ", func() {
count := api.GetBlockTransactionCountByHash(ctx, blockHash)
Expect(uint64(*count)).To(Equal(uint64(3)))
})
})
Describe("eth_getTransactionByBlockNumberAndIndex", func() {
It("Retrieves the tx with the provided index in the canonical block with the provided block number", func() {
tx := api.GetTransactionByBlockNumberAndIndex(ctx, number, 0)
Expect(tx).ToNot(BeNil())
Expect(tx).To(Equal(expectedTransaction))
tx = api.GetTransactionByBlockNumberAndIndex(ctx, number, 1)
Expect(tx).ToNot(BeNil())
Expect(tx).To(Equal(expectedTransaction2))
tx = api.GetTransactionByBlockNumberAndIndex(ctx, number, 2)
Expect(tx).ToNot(BeNil())
Expect(tx).To(Equal(expectedTransaction3))
})
})
Describe("eth_getTransactionByBlockHashAndIndex", func() {
It("Retrieves the tx with the provided index in the block with the provided hash", func() {
tx := api.GetTransactionByBlockHashAndIndex(ctx, blockHash, 0)
Expect(tx).ToNot(BeNil())
Expect(tx).To(Equal(expectedTransaction))
tx = api.GetTransactionByBlockHashAndIndex(ctx, blockHash, 1)
Expect(tx).ToNot(BeNil())
Expect(tx).To(Equal(expectedTransaction2))
tx = api.GetTransactionByBlockHashAndIndex(ctx, blockHash, 2)
Expect(tx).ToNot(BeNil())
Expect(tx).To(Equal(expectedTransaction3))
})
})
Describe("eth_getRawTransactionByBlockNumberAndIndex", func() {
It("Retrieves the raw tx with the provided index in the canonical block with the provided block number", func() {
tx := api.GetRawTransactionByBlockNumberAndIndex(ctx, number, 0)
Expect(tx).ToNot(BeNil())
Expect(tx).To(Equal(hexutil.Bytes(expectRawTx)))
tx = api.GetRawTransactionByBlockNumberAndIndex(ctx, number, 1)
Expect(tx).ToNot(BeNil())
Expect(tx).To(Equal(hexutil.Bytes(expectRawTx2)))
tx = api.GetRawTransactionByBlockNumberAndIndex(ctx, number, 2)
Expect(tx).ToNot(BeNil())
Expect(tx).To(Equal(hexutil.Bytes(expectRawTx3)))
})
})
Describe("eth_getRawTransactionByBlockHashAndIndex", func() {
It("Retrieves the raw tx with the provided index in the block with the provided hash", func() {
tx := api.GetRawTransactionByBlockHashAndIndex(ctx, blockHash, 0)
Expect(tx).ToNot(BeNil())
Expect(tx).To(Equal(hexutil.Bytes(expectRawTx)))
tx = api.GetRawTransactionByBlockHashAndIndex(ctx, blockHash, 1)
Expect(tx).ToNot(BeNil())
Expect(tx).To(Equal(hexutil.Bytes(expectRawTx2)))
tx = api.GetRawTransactionByBlockHashAndIndex(ctx, blockHash, 2)
Expect(tx).ToNot(BeNil())
Expect(tx).To(Equal(hexutil.Bytes(expectRawTx3)))
})
})
Describe("eth_getTransactionByHash", func() {
It("Retrieves a transaction by hash", func() {
hash := test_helpers.MockTransactions[0].Hash()
tx, err := api.GetTransactionByHash(ctx, hash)
Expect(err).ToNot(HaveOccurred())
Expect(tx).To(Equal(expectedTransaction))
hash = test_helpers.MockTransactions[1].Hash()
tx, err = api.GetTransactionByHash(ctx, hash)
Expect(err).ToNot(HaveOccurred())
Expect(tx).To(Equal(expectedTransaction2))
hash = test_helpers.MockTransactions[2].Hash()
tx, err = api.GetTransactionByHash(ctx, hash)
Expect(err).ToNot(HaveOccurred())
Expect(tx).To(Equal(expectedTransaction3))
})
It("Throws an error if it cannot find a tx for the provided tx hash", func() {
_, err := api.GetTransactionByHash(ctx, randomHash)
Expect(err).To(HaveOccurred())
})
})
Describe("eth_getRawTransactionByHash", func() {
It("Retrieves a raw transaction by hash", func() {
hash := test_helpers.MockTransactions[0].Hash()
tx, err := api.GetRawTransactionByHash(ctx, hash)
Expect(err).ToNot(HaveOccurred())
Expect(tx).To(Equal(hexutil.Bytes(expectRawTx)))
hash = test_helpers.MockTransactions[1].Hash()
tx, err = api.GetRawTransactionByHash(ctx, hash)
Expect(err).ToNot(HaveOccurred())
Expect(tx).To(Equal(hexutil.Bytes(expectRawTx2)))
hash = test_helpers.MockTransactions[2].Hash()
tx, err = api.GetRawTransactionByHash(ctx, hash)
Expect(err).ToNot(HaveOccurred())
Expect(tx).To(Equal(hexutil.Bytes(expectRawTx3)))
})
It("Throws an error if it cannot find a tx for the provided tx hash", func() {
_, err := api.GetRawTransactionByHash(ctx, randomHash)
Expect(err).To(HaveOccurred())
})
})
/*
Receipts and logs
*/
Describe("eth_getTransactionReceipt", func() {
It("Retrieves a receipt by tx hash", func() {
hash := test_helpers.MockTransactions[0].Hash()
rct, err := api.GetTransactionReceipt(ctx, hash)
Expect(err).ToNot(HaveOccurred())
Expect(rct).To(Equal(expectedReceipt))
hash = test_helpers.MockTransactions[1].Hash()
rct, err = api.GetTransactionReceipt(ctx, hash)
Expect(err).ToNot(HaveOccurred())
Expect(rct).To(Equal(expectedReceipt2))
hash = test_helpers.MockTransactions[2].Hash()
rct, err = api.GetTransactionReceipt(ctx, hash)
Expect(err).ToNot(HaveOccurred())
Expect(rct).To(Equal(expectedReceipt3))
})
It("Throws an error if it cannot find a receipt for the provided tx hash", func() {
_, err := api.GetTransactionReceipt(ctx, randomHash)
Expect(err).To(HaveOccurred())
})
})
Describe("eth_getLogs", func() {
It("Retrieves receipt logs that match the provided topics within the provided range", func() {
crit := ethereum.FilterQuery{
Topics: [][]common.Hash{
@@ -213,7 +564,7 @@ var _ = Describe("API", func() {
FromBlock: test_helpers.MockBlock.Number(),
ToBlock: test_helpers.MockBlock.Number(),
}
logs, err := api.GetLogs(context.Background(), crit)
logs, err := api.GetLogs(ctx, crit)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(1))
Expect(logs).To(Equal([]*types.Log{test_helpers.MockLog1}))
@@ -228,7 +579,7 @@ var _ = Describe("API", func() {
FromBlock: test_helpers.MockBlock.Number(),
ToBlock: test_helpers.MockBlock.Number(),
}
logs, err = api.GetLogs(context.Background(), crit)
logs, err = api.GetLogs(ctx, crit)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(2))
Expect(logs).To(Equal([]*types.Log{test_helpers.MockLog1, test_helpers.MockLog2}))
@@ -243,7 +594,7 @@ var _ = Describe("API", func() {
FromBlock: test_helpers.MockBlock.Number(),
ToBlock: test_helpers.MockBlock.Number(),
}
logs, err = api.GetLogs(context.Background(), crit)
logs, err = api.GetLogs(ctx, crit)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(1))
Expect(logs).To(Equal([]*types.Log{test_helpers.MockLog1}))
@@ -260,7 +611,7 @@ var _ = Describe("API", func() {
FromBlock: test_helpers.MockBlock.Number(),
ToBlock: test_helpers.MockBlock.Number(),
}
logs, err = api.GetLogs(context.Background(), crit)
logs, err = api.GetLogs(ctx, crit)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(0))
@@ -276,7 +627,7 @@ var _ = Describe("API", func() {
FromBlock: test_helpers.MockBlock.Number(),
ToBlock: test_helpers.MockBlock.Number(),
}
logs, err = api.GetLogs(context.Background(), crit)
logs, err = api.GetLogs(ctx, crit)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(1))
Expect(logs).To(Equal([]*types.Log{test_helpers.MockLog1}))
@@ -293,7 +644,7 @@ var _ = Describe("API", func() {
FromBlock: test_helpers.MockBlock.Number(),
ToBlock: test_helpers.MockBlock.Number(),
}
logs, err = api.GetLogs(context.Background(), crit)
logs, err = api.GetLogs(ctx, crit)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(1))
Expect(logs).To(Equal([]*types.Log{test_helpers.MockLog2}))
@@ -311,7 +662,7 @@ var _ = Describe("API", func() {
FromBlock: test_helpers.MockBlock.Number(),
ToBlock: test_helpers.MockBlock.Number(),
}
logs, err = api.GetLogs(context.Background(), crit)
logs, err = api.GetLogs(ctx, crit)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(1))
Expect(logs).To(Equal([]*types.Log{test_helpers.MockLog2}))
@@ -330,7 +681,7 @@ var _ = Describe("API", func() {
FromBlock: test_helpers.MockBlock.Number(),
ToBlock: test_helpers.MockBlock.Number(),
}
logs, err = api.GetLogs(context.Background(), crit)
logs, err = api.GetLogs(ctx, crit)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(2))
Expect(logs).To(Equal([]*types.Log{test_helpers.MockLog1, test_helpers.MockLog2}))
@@ -345,7 +696,7 @@ var _ = Describe("API", func() {
FromBlock: test_helpers.MockBlock.Number(),
ToBlock: test_helpers.MockBlock.Number(),
}
logs, err = api.GetLogs(context.Background(), crit)
logs, err = api.GetLogs(ctx, crit)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(1))
Expect(logs).To(Equal([]*types.Log{test_helpers.MockLog2}))
@@ -360,7 +711,7 @@ var _ = Describe("API", func() {
FromBlock: test_helpers.MockBlock.Number(),
ToBlock: test_helpers.MockBlock.Number(),
}
logs, err = api.GetLogs(context.Background(), crit)
logs, err = api.GetLogs(ctx, crit)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(1))
Expect(logs).To(Equal([]*types.Log{test_helpers.MockLog1}))
@@ -370,7 +721,7 @@ var _ = Describe("API", func() {
FromBlock: test_helpers.MockBlock.Number(),
ToBlock: test_helpers.MockBlock.Number(),
}
logs, err = api.GetLogs(context.Background(), crit)
logs, err = api.GetLogs(ctx, crit)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(2))
Expect(logs).To(Equal([]*types.Log{test_helpers.MockLog1, test_helpers.MockLog2}))
@@ -387,7 +738,7 @@ var _ = Describe("API", func() {
},
},
}
logs, err := api.GetLogs(context.Background(), crit)
logs, err := api.GetLogs(ctx, crit)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(1))
Expect(logs).To(Equal([]*types.Log{test_helpers.MockLog1}))
@@ -403,7 +754,7 @@ var _ = Describe("API", func() {
},
},
}
logs, err = api.GetLogs(context.Background(), crit)
logs, err = api.GetLogs(ctx, crit)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(1))
Expect(logs).To(Equal([]*types.Log{test_helpers.MockLog1}))
@@ -417,7 +768,7 @@ var _ = Describe("API", func() {
},
},
}
logs, err = api.GetLogs(context.Background(), crit)
logs, err = api.GetLogs(ctx, crit)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(1))
Expect(logs).To(Equal([]*types.Log{test_helpers.MockLog2}))
@@ -433,7 +784,7 @@ var _ = Describe("API", func() {
},
},
}
logs, err = api.GetLogs(context.Background(), crit)
logs, err = api.GetLogs(ctx, crit)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(1))
Expect(logs).To(Equal([]*types.Log{test_helpers.MockLog2}))
@@ -449,7 +800,7 @@ var _ = Describe("API", func() {
},
},
}
logs, err = api.GetLogs(context.Background(), crit)
logs, err = api.GetLogs(ctx, crit)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(0))
@@ -465,7 +816,7 @@ var _ = Describe("API", func() {
},
},
}
logs, err = api.GetLogs(context.Background(), crit)
logs, err = api.GetLogs(ctx, crit)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(1))
Expect(logs).To(Equal([]*types.Log{test_helpers.MockLog2}))
@@ -479,7 +830,7 @@ var _ = Describe("API", func() {
},
},
}
logs, err = api.GetLogs(context.Background(), crit)
logs, err = api.GetLogs(ctx, crit)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(2))
Expect(logs).To(Equal([]*types.Log{test_helpers.MockLog1, test_helpers.MockLog2}))
@@ -497,7 +848,7 @@ var _ = Describe("API", func() {
},
},
}
logs, err = api.GetLogs(context.Background(), crit)
logs, err = api.GetLogs(ctx, crit)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(2))
Expect(logs).To(Equal([]*types.Log{test_helpers.MockLog1, test_helpers.MockLog2}))
@@ -506,7 +857,7 @@ var _ = Describe("API", func() {
BlockHash: &hash,
Topics: [][]common.Hash{},
}
logs, err = api.GetLogs(context.Background(), crit)
logs, err = api.GetLogs(ctx, crit)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(2))
Expect(logs).To(Equal([]*types.Log{test_helpers.MockLog1, test_helpers.MockLog2}))
@@ -530,7 +881,7 @@ var _ = Describe("API", func() {
},
},
}
logs, err := api.GetLogs(context.Background(), crit)
logs, err := api.GetLogs(ctx, crit)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(1))
Expect(logs).To(Equal([]*types.Log{test_helpers.MockLog1}))
@@ -553,7 +904,7 @@ var _ = Describe("API", func() {
},
},
}
logs, err = api.GetLogs(context.Background(), crit)
logs, err = api.GetLogs(ctx, crit)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(2))
Expect(logs).To(Equal([]*types.Log{test_helpers.MockLog1, test_helpers.MockLog2}))
@@ -566,10 +917,75 @@ var _ = Describe("API", func() {
test_helpers.AnotherAddress,
},
}
logs, err = api.GetLogs(context.Background(), crit)
logs, err = api.GetLogs(ctx, crit)
Expect(err).ToNot(HaveOccurred())
Expect(len(logs)).To(Equal(2))
Expect(logs).To(Equal([]*types.Log{test_helpers.MockLog1, test_helpers.MockLog2}))
})
})
/*
State and storage
*/
Describe("eth_getBalance", func() {
It("Retrieves the eth balance for the provided account address at the block with the provided number", func() {
bal, err := api.GetBalance(ctx, test_helpers.AccountAddresss, rpc.BlockNumberOrHashWithNumber(number))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal((*hexutil.Big)(test_helpers.AccountBalance)))
bal, err = api.GetBalance(ctx, test_helpers.ContractAddress, rpc.BlockNumberOrHashWithNumber(number))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal((*hexutil.Big)(common.Big0)))
})
It("Retrieves the eth balance for the provided account address at the block with the provided hash", func() {
bal, err := api.GetBalance(ctx, test_helpers.AccountAddresss, rpc.BlockNumberOrHashWithHash(blockHash, true))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal((*hexutil.Big)(test_helpers.AccountBalance)))
bal, err = api.GetBalance(ctx, test_helpers.ContractAddress, rpc.BlockNumberOrHashWithHash(blockHash, true))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal((*hexutil.Big)(common.Big0)))
})
It("Throws an error for an account it cannot find the balance for", func() {
_, err := api.GetBalance(ctx, randomAddr, rpc.BlockNumberOrHashWithHash(blockHash, true))
Expect(err).To(HaveOccurred())
})
})
Describe("eth_getCode", func() {
It("Retrieves the code for the provided contract address at the block with the provided number", func() {
code, err := api.GetCode(ctx, test_helpers.ContractAddress, rpc.BlockNumberOrHashWithNumber(number))
Expect(err).ToNot(HaveOccurred())
Expect(code).To(Equal((hexutil.Bytes)(test_helpers.ContractCode)))
})
It("Retrieves the code for the provided contract address at the block with the provided hash", func() {
code, err := api.GetCode(ctx, test_helpers.ContractAddress, rpc.BlockNumberOrHashWithHash(blockHash, true))
Expect(err).ToNot(HaveOccurred())
Expect(code).To(Equal((hexutil.Bytes)(test_helpers.ContractCode)))
})
It("Throws an error for an account it cannot find the code for", func() {
_, err := api.GetCode(ctx, randomAddr, rpc.BlockNumberOrHashWithHash(blockHash, true))
Expect(err).To(HaveOccurred())
})
})
})
func publishCode(db *postgres.DB, codeHash common.Hash, code []byte) error {
tx, err := db.Beginx()
if err != nil {
return err
}
mhKey, err := shared2.MultihashKeyFromKeccak256(codeHash)
if err != nil {
tx.Rollback()
return err
}
if err := shared2.PublishDirect(tx, mhKey, code); err != nil {
tx.Rollback()
return err
}
return tx.Commit()
}
+250 -219
View File
@@ -19,25 +19,31 @@ package eth
import (
"context"
"errors"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/bloombits"
"github.com/ethereum/go-ethereum/core/state"
"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/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
ipfsethdb "github.com/vulcanize/pg-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"
"github.com/vulcanize/ipld-eth-server/pkg/shared"
)
@@ -49,10 +55,27 @@ 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($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 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
WHERE blocks.key = transaction_cids.mh_key
AND transaction_cids.header_id = header_cids.id
AND transaction_cids.tx_hash = $1`
RetrieveCodeHashByLeafKeyAndBlockHash = `SELECT code_hash FROM eth.state_accounts, eth.state_cids, eth.header_cids
WHERE state_accounts.state_id = state_cids.id
AND state_cids.header_id = header_cids.id
AND state_leaf_key = $1
AND block_number <= (SELECT block_number
FROM eth.header_cids
WHERE block_hash = $2)
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`
)
type Backend struct {
@@ -92,6 +115,11 @@ func NewEthBackend(db *postgres.DB, c *Config) (*Backend, error) {
}, nil
}
// ChainDb returns the backend's underlying chain database
func (b *Backend) ChainDb() ethdb.Database {
return b.EthDB
}
// HeaderByNumber gets the canonical header for the provided block number
func (b *Backend) HeaderByNumber(ctx context.Context, blockNumber rpc.BlockNumber) (*types.Header, error) {
var err error
@@ -133,12 +161,31 @@ func (b *Backend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.He
return header, rlp.DecodeBytes(headerRLP, header)
}
// HeaderByNumberOrHash gets the header for the provided block hash or number
func (b *Backend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) {
if blockNr, ok := blockNrOrHash.Number(); ok {
return b.HeaderByNumber(ctx, blockNr)
}
if hash, ok := blockNrOrHash.Hash(); ok {
header, err := b.HeaderByHash(ctx, hash)
if err != nil {
return nil, err
}
if header == nil {
return nil, errors.New("header for hash not found")
}
if blockNrOrHash.RequireCanonical && b.GetCanonicalHash(header.Number.Uint64()) != hash {
return nil, errors.New("hash is not currently canonical")
}
return header, nil
}
return nil, errors.New("invalid arguments; neither block nor hash specified")
}
// GetTd gets the total difficulty at the given block hash
func (b *Backend) GetTd(blockHash common.Hash) (*big.Int, error) {
pgStr := `SELECT td FROM eth.header_cids
WHERE header_cids.block_hash = $1`
var tdStr string
err := b.DB.Get(&tdStr, pgStr, blockHash.String())
err := b.DB.Get(&tdStr, RetrieveTD, blockHash.String())
if err != nil {
return nil, err
}
@@ -149,21 +196,38 @@ func (b *Backend) GetTd(blockHash common.Hash) (*big.Int, error) {
return td, nil
}
// GetLogs returns all the logs for the given block hash
func (b *Backend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) {
_, receiptBytes, err := b.IPLDRetriever.RetrieveReceiptsByBlockHash(hash)
if err != nil {
return nil, err
// CurrentBlock returns the current block
func (b *Backend) CurrentBlock() *types.Block {
block, _ := b.BlockByNumber(context.Background(), rpc.LatestBlockNumber)
return block
}
// BlockByNumberOrHash returns block by number or hash
func (b *Backend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) {
if blockNr, ok := blockNrOrHash.Number(); ok {
return b.BlockByNumber(ctx, blockNr)
}
logs := make([][]*types.Log, len(receiptBytes))
for i, rctBytes := range receiptBytes {
var rct types.Receipt
if err := rlp.DecodeBytes(rctBytes, &rct); err != nil {
if hash, ok := blockNrOrHash.Hash(); ok {
header, err := b.HeaderByHash(ctx, hash)
if err != nil {
return nil, err
}
logs[i] = rct.Logs
if header == nil {
return nil, errors.New("header for hash not found")
}
if blockNrOrHash.RequireCanonical && b.GetCanonicalHash(header.Number.Uint64()) != hash {
return nil, errors.New("hash is not currently canonical")
}
block, err := b.BlockByHash(ctx, hash)
if err != nil {
return nil, err
}
if block == nil {
return nil, errors.New("header found, but block body is missing")
}
return block, nil
}
return logs, nil
return nil, errors.New("invalid arguments; neither block nor hash specified")
}
// BlockByNumber returns the requested canonical block.
@@ -354,11 +418,7 @@ func (b *Backend) GetTransaction(ctx context.Context, txHash common.Hash) (*type
BlockNumber uint64 `db:"block_number"`
Index uint64 `db:"index"`
}
pgStr := `SELECT blocks.data, block_hash, block_number, index FROM public.blocks, eth.transaction_cids, eth.header_cids
WHERE blocks.key = transaction_cids.mh_key
AND transaction_cids.header_id = header_cids.id
AND transaction_cids.tx_hash = $1`
if err := b.DB.Get(&tempTxStruct, pgStr, txHash.String()); err != nil {
if err := b.DB.Get(&tempTxStruct, RetrieveRPCTransaction, txHash.String()); err != nil {
return nil, common.Hash{}, 0, 0, err
}
var transaction types.Transaction
@@ -368,209 +428,40 @@ func (b *Backend) GetTransaction(ctx context.Context, txHash common.Hash) (*type
return &transaction, common.HexToHash(tempTxStruct.BlockHash), tempTxStruct.BlockNumber, tempTxStruct.Index, nil
}
// extractLogsOfInterest returns logs from the receipt IPLD
func extractLogsOfInterest(rctIPLDs []ipfs.BlockModel, wantedTopics [][]string) ([]*types.Log, error) {
var logs []*types.Log
for _, rctIPLD := range rctIPLDs {
rctRLP := rctIPLD
var rct types.Receipt
if err := rlp.DecodeBytes(rctRLP.Data, &rct); err != nil {
// GetReceipts retrieves receipts for provided block hash
func (b *Backend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
_, receiptBytes, err := b.IPLDRetriever.RetrieveReceiptsByBlockHash(hash)
if err != nil {
return nil, err
}
rcts := make(types.Receipts, len(receiptBytes))
for i, rctBytes := range receiptBytes {
rct := new(types.Receipt)
if err := rlp.DecodeBytes(rctBytes, rct); err != nil {
return nil, err
}
for _, log := range rct.Logs {
if wanted := wantedLog(wantedTopics, log.Topics); wanted == true {
logs = append(logs, log)
}
rcts[i] = rct
}
return rcts, nil
}
// GetLogs returns all the logs for the given block hash
func (b *Backend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) {
_, receiptBytes, err := b.IPLDRetriever.RetrieveReceiptsByBlockHash(hash)
if err != nil {
return nil, err
}
logs := make([][]*types.Log, len(receiptBytes))
for i, rctBytes := range receiptBytes {
var rct types.Receipt
if err := rlp.DecodeBytes(rctBytes, &rct); err != nil {
return nil, err
}
logs[i] = rct.Logs
}
return logs, nil
}
// returns true if the log matches on the filter
func wantedLog(wantedTopics [][]string, actualTopics []common.Hash) bool {
// actualTopics will always have length <= 4
// wantedTopics will always have length 4
matches := 0
for i, actualTopic := range actualTopics {
// If we have topics in this filter slot, count as a match if the actualTopic matches one of the ones in this filter slot
if len(wantedTopics[i]) > 0 {
matches += sliceContainsHash(wantedTopics[i], actualTopic)
} else {
// Filter slot is empty, not matching any topics at this slot => counts as a match
matches++
}
}
if matches == len(actualTopics) {
return true
}
return false
}
// returns 1 if the slice contains the hash, 0 if it does not
func sliceContainsHash(slice []string, hash common.Hash) int {
for _, str := range slice {
if str == hash.String() {
return 1
}
}
return 0
}
// rpcMarshalHeader uses the generalized output filler, then adds the total difficulty field, which requires
// a `PublicEthAPI`.
func (pea *PublicEthAPI) rpcMarshalHeader(header *types.Header) (map[string]interface{}, error) {
fields := RPCMarshalHeader(header)
td, err := pea.B.GetTd(header.Hash())
if err != nil {
return nil, err
}
fields["totalDifficulty"] = (*hexutil.Big)(td)
return fields, nil
}
// RPCMarshalHeader converts the given header to the RPC output.
// This function is eth/internal so we have to make our own version here...
func RPCMarshalHeader(head *types.Header) map[string]interface{} {
return map[string]interface{}{
"number": (*hexutil.Big)(head.Number),
"hash": head.Hash(),
"parentHash": head.ParentHash,
"nonce": head.Nonce,
"mixHash": head.MixDigest,
"sha3Uncles": head.UncleHash,
"logsBloom": head.Bloom,
"stateRoot": head.Root,
"miner": head.Coinbase,
"difficulty": (*hexutil.Big)(head.Difficulty),
"extraData": hexutil.Bytes(head.Extra),
"size": hexutil.Uint64(head.Size()),
"gasLimit": hexutil.Uint64(head.GasLimit),
"gasUsed": hexutil.Uint64(head.GasUsed),
"timestamp": hexutil.Uint64(head.Time),
"transactionsRoot": head.TxHash,
"receiptsRoot": head.ReceiptHash,
}
}
// rpcMarshalBlock uses the generalized output filler, then adds the total difficulty field, which requires
// a `PublicBlockchainAPI`.
func (pea *PublicEthAPI) rpcMarshalBlock(b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) {
fields, err := RPCMarshalBlock(b, inclTx, fullTx)
if err != nil {
return nil, err
}
td, err := pea.B.GetTd(b.Hash())
if err != nil {
return nil, err
}
fields["totalDifficulty"] = (*hexutil.Big)(td)
return fields, err
}
// RPCMarshalBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are
// returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain
// transaction hashes.
func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) {
fields := RPCMarshalHeader(block.Header())
fields["size"] = hexutil.Uint64(block.Size())
if inclTx {
formatTx := func(tx *types.Transaction) (interface{}, error) {
return tx.Hash(), nil
}
if fullTx {
formatTx = func(tx *types.Transaction) (interface{}, error) {
return NewRPCTransactionFromBlockHash(block, tx.Hash()), nil
}
}
txs := block.Transactions()
transactions := make([]interface{}, len(txs))
var err error
for i, tx := range txs {
if transactions[i], err = formatTx(tx); err != nil {
return nil, err
}
}
fields["transactions"] = transactions
}
uncles := block.Uncles()
uncleHashes := make([]common.Hash, len(uncles))
for i, uncle := range uncles {
uncleHashes[i] = uncle.Hash()
}
fields["uncles"] = uncleHashes
return fields, nil
}
// NewRPCTransactionFromBlockHash returns a transaction that will serialize to the RPC representation.
func NewRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransaction {
for idx, tx := range b.Transactions() {
if tx.Hash() == hash {
return newRPCTransactionFromBlockIndex(b, uint64(idx))
}
}
return nil
}
// newRPCTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation.
func newRPCTransactionFromBlockIndex(b *types.Block, index uint64) *RPCTransaction {
txs := b.Transactions()
if index >= uint64(len(txs)) {
return nil
}
return NewRPCTransaction(txs[index], b.Hash(), b.NumberU64(), index)
}
// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction
type RPCTransaction struct {
BlockHash *common.Hash `json:"blockHash"`
BlockNumber *hexutil.Big `json:"blockNumber"`
From common.Address `json:"from"`
Gas hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"`
Hash common.Hash `json:"hash"`
Input hexutil.Bytes `json:"input"`
Nonce hexutil.Uint64 `json:"nonce"`
To *common.Address `json:"to"`
TransactionIndex *hexutil.Uint64 `json:"transactionIndex"`
Value *hexutil.Big `json:"value"`
V *hexutil.Big `json:"v"`
R *hexutil.Big `json:"r"`
S *hexutil.Big `json:"s"`
}
// NewRPCTransaction returns a transaction that will serialize to the RPC
// representation, with the given location metadata set (if available).
func NewRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64) *RPCTransaction {
var signer types.Signer = types.FrontierSigner{}
if tx.Protected() {
signer = types.NewEIP155Signer(tx.ChainId())
}
from, _ := types.Sender(signer, tx)
v, r, s := tx.RawSignatureValues()
result := &RPCTransaction{
From: from,
Gas: hexutil.Uint64(tx.Gas()),
GasPrice: (*hexutil.Big)(tx.GasPrice()),
Hash: tx.Hash(),
Input: hexutil.Bytes(tx.Data()), // somehow this is ending up `nil`
Nonce: hexutil.Uint64(tx.Nonce()),
To: tx.To(),
Value: (*hexutil.Big)(tx.Value()),
V: (*hexutil.Big)(v),
R: (*hexutil.Big)(r),
S: (*hexutil.Big)(s),
}
if blockHash != (common.Hash{}) {
result.BlockHash = &blockHash
result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber))
result.TransactionIndex = (*hexutil.Uint64)(&index)
}
return result
}
// StateAndHeaderByNumberOrHash returns the statedb and header for the provided block number or hash
func (b *Backend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) {
if blockNr, ok := blockNrOrHash.Number(); ok {
@@ -638,6 +529,113 @@ func (b *Backend) GetEVM(ctx context.Context, msg core.Message, state *state.Sta
return vm.NewEVM(c, state, b.Config.ChainConfig, b.Config.VmConfig), nil
}
// GetAccountByNumberOrHash returns the account object for the provided address at the block corresponding to the provided number or hash
func (b *Backend) GetAccountByNumberOrHash(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*state.Account, error) {
if blockNr, ok := blockNrOrHash.Number(); ok {
return b.GetAccountByNumber(ctx, address, uint64(blockNr.Int64()))
}
if hash, ok := blockNrOrHash.Hash(); ok {
return b.GetAccountByHash(ctx, address, hash)
}
return nil, errors.New("invalid arguments; neither block nor hash specified")
}
// GetAccountByNumber returns the account object for the provided address at the canonical block at the provided height
func (b *Backend) GetAccountByNumber(ctx context.Context, address common.Address, number uint64) (*state.Account, error) {
hash := b.GetCanonicalHash(number)
if hash == (common.Hash{}) {
return nil, fmt.Errorf("no canoncial block hash found for provided height (%d)", number)
}
return b.GetAccountByHash(ctx, address, hash)
}
// GetAccountByHash returns the account object for the provided address at the block with the provided hash
func (b *Backend) GetAccountByHash(ctx context.Context, address common.Address, hash common.Hash) (*state.Account, error) {
_, accountRlp, err := b.IPLDRetriever.RetrieveAccountByAddressAndBlockHash(address, hash)
if err != nil {
return nil, err
}
acct := new(state.Account)
return acct, rlp.DecodeBytes(accountRlp, acct)
}
// GetCodeByNumberOrHash returns the byte code for the contract deployed at the provided address at the block with the provided hash or block number
func (b *Backend) GetCodeByNumberOrHash(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) ([]byte, error) {
if blockNr, ok := blockNrOrHash.Number(); ok {
return b.GetCodeByNumber(ctx, address, uint64(blockNr.Int64()))
}
if hash, ok := blockNrOrHash.Hash(); ok {
return b.GetCodeByHash(ctx, address, hash)
}
return nil, errors.New("invalid arguments; neither block nor hash specified")
}
// GetCodeByNumber returns the byte code for the contract deployed at the provided address at the canonical block with the provided block number
func (b *Backend) GetCodeByNumber(ctx context.Context, address common.Address, number uint64) ([]byte, error) {
hash := b.GetCanonicalHash(number)
if hash == (common.Hash{}) {
return nil, fmt.Errorf("no canoncial block hash found for provided height (%d)", number)
}
return b.GetCodeByHash(ctx, address, hash)
}
// GetCodeByHash returns the byte code for the contract deployed at the provided address at the block with the provided hash
func (b *Backend) GetCodeByHash(ctx context.Context, address common.Address, hash common.Hash) ([]byte, error) {
codeHash := make([]byte, 0)
leafKey := crypto.Keccak256Hash(address.Bytes())
// Begin tx
tx, err := b.DB.Beginx()
if err != nil {
return nil, err
}
defer func() {
if p := recover(); p != nil {
shared.Rollback(tx)
panic(p)
} else if err != nil {
shared.Rollback(tx)
} else {
err = tx.Commit()
}
}()
if err := tx.Get(&codeHash, RetrieveCodeHashByLeafKeyAndBlockHash, leafKey.Hex(), hash.Hex()); err != nil {
return nil, err
}
mhKey, err := shared2.MultihashKeyFromKeccak256(common.BytesToHash(codeHash))
if err != nil {
return nil, err
}
code := make([]byte, 0)
err = tx.Get(&code, RetrieveCodeByMhKey, mhKey)
return code, err
}
// GetStorageByNumberOrHash returns the storage value for the provided contract address an storage key at the block corresponding to the provided number or hash
func (b *Backend) GetStorageByNumberOrHash(ctx context.Context, address common.Address, storageLeafKey common.Hash, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
if blockNr, ok := blockNrOrHash.Number(); ok {
return b.GetStorageByNumber(ctx, address, storageLeafKey, uint64(blockNr.Int64()))
}
if hash, ok := blockNrOrHash.Hash(); ok {
return b.GetStorageByHash(ctx, address, storageLeafKey, hash)
}
return nil, errors.New("invalid arguments; neither block nor hash specified")
}
// GetStorageByNumber returns the storage value for the provided contract address an storage key at the block corresponding to the provided number
func (b *Backend) GetStorageByNumber(ctx context.Context, address common.Address, storageLeafKey common.Hash, number uint64) (hexutil.Bytes, error) {
hash := b.GetCanonicalHash(number)
if hash == (common.Hash{}) {
return nil, fmt.Errorf("no canoncial block hash found for provided height (%d)", number)
}
return b.GetStorageByHash(ctx, address, storageLeafKey, hash)
}
// GetStorageByHash returns the storage value for the provided contract address an storage key at the block corresponding to the provided hash
func (b *Backend) GetStorageByHash(ctx context.Context, address common.Address, storageLeafKey, hash common.Hash) (hexutil.Bytes, error) {
_, storageRlp, err := b.IPLDRetriever.RetrieveStorageAtByAddressAndStorageKeyAndBlockHash(address, storageLeafKey, hash)
return storageRlp, err
}
// Engine satisfied the ChainContext interface
func (b *Backend) Engine() consensus.Engine {
// TODO: we need to support more than just ethash based engines
@@ -652,3 +650,36 @@ func (b *Backend) GetHeader(hash common.Hash, height uint64) *types.Header {
}
return header
}
// RPCGasCap returns the configured gas cap for the rpc server
func (b *Backend) RPCGasCap() *big.Int {
return b.Config.RPCGasCap
}
func (b *Backend) SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription {
panic("implement me")
}
func (b *Backend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
panic("implement me")
}
func (b *Backend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
panic("implement me")
}
func (b *Backend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
panic("implement me")
}
func (b *Backend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription {
panic("implement me")
}
func (b *Backend) BloomStatus() (uint64, uint64) {
panic("implement me")
}
func (b *Backend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) {
panic("implement me")
}
+320
View File
@@ -0,0 +1,320 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package eth
import (
"context"
"encoding/json"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/vulcanize/ipld-eth-indexer/pkg/ipfs"
)
// RPCMarshalHeader converts the given header to the RPC output.
// This function is eth/internal so we have to make our own version here...
func RPCMarshalHeader(head *types.Header) map[string]interface{} {
return map[string]interface{}{
"number": (*hexutil.Big)(head.Number),
"hash": head.Hash(),
"parentHash": head.ParentHash,
"nonce": head.Nonce,
"mixHash": head.MixDigest,
"sha3Uncles": head.UncleHash,
"logsBloom": head.Bloom,
"stateRoot": head.Root,
"miner": head.Coinbase,
"difficulty": (*hexutil.Big)(head.Difficulty),
"extraData": hexutil.Bytes(head.Extra),
"size": hexutil.Uint64(head.Size()),
"gasLimit": hexutil.Uint64(head.GasLimit),
"gasUsed": hexutil.Uint64(head.GasUsed),
"timestamp": hexutil.Uint64(head.Time),
"transactionsRoot": head.TxHash,
"receiptsRoot": head.ReceiptHash,
}
}
// RPCMarshalBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are
// returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain
// transaction hashes.
func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) {
fields := RPCMarshalHeader(block.Header())
fields["size"] = hexutil.Uint64(block.Size())
if inclTx {
formatTx := func(tx *types.Transaction) (interface{}, error) {
return tx.Hash(), nil
}
if fullTx {
formatTx = func(tx *types.Transaction) (interface{}, error) {
return NewRPCTransactionFromBlockHash(block, tx.Hash()), nil
}
}
txs := block.Transactions()
transactions := make([]interface{}, len(txs))
var err error
for i, tx := range txs {
if transactions[i], err = formatTx(tx); err != nil {
return nil, err
}
}
fields["transactions"] = transactions
}
uncles := block.Uncles()
uncleHashes := make([]common.Hash, len(uncles))
for i, uncle := range uncles {
uncleHashes[i] = uncle.Hash()
}
fields["uncles"] = uncleHashes
return fields, nil
}
// RPCMarshalBlockWithUncleHashes marshals the block with the provided uncle hashes
func RPCMarshalBlockWithUncleHashes(block *types.Block, uncleHashes []common.Hash, inclTx bool, fullTx bool) (map[string]interface{}, error) {
fields := RPCMarshalHeader(block.Header())
fields["size"] = hexutil.Uint64(block.Size())
if inclTx {
formatTx := func(tx *types.Transaction) (interface{}, error) {
return tx.Hash(), nil
}
if fullTx {
formatTx = func(tx *types.Transaction) (interface{}, error) {
return NewRPCTransactionFromBlockHash(block, tx.Hash()), nil
}
}
txs := block.Transactions()
transactions := make([]interface{}, len(txs))
var err error
for i, tx := range txs {
if transactions[i], err = formatTx(tx); err != nil {
return nil, err
}
}
fields["transactions"] = transactions
}
fields["uncles"] = uncleHashes
return fields, nil
}
// NewRPCTransactionFromBlockHash returns a transaction that will serialize to the RPC representation.
func NewRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransaction {
for idx, tx := range b.Transactions() {
if tx.Hash() == hash {
return newRPCTransactionFromBlockIndex(b, uint64(idx))
}
}
return nil
}
// NewRPCTransaction returns a transaction that will serialize to the RPC
// representation, with the given location metadata set (if available).
func NewRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64) *RPCTransaction {
var signer types.Signer = types.FrontierSigner{}
if tx.Protected() {
signer = types.NewEIP155Signer(tx.ChainId())
}
from, _ := types.Sender(signer, tx)
v, r, s := tx.RawSignatureValues()
result := &RPCTransaction{
From: from,
Gas: hexutil.Uint64(tx.Gas()),
GasPrice: (*hexutil.Big)(tx.GasPrice()),
Hash: tx.Hash(),
Input: hexutil.Bytes(tx.Data()), // somehow this is ending up `nil`
Nonce: hexutil.Uint64(tx.Nonce()),
To: tx.To(),
Value: (*hexutil.Big)(tx.Value()),
V: (*hexutil.Big)(v),
R: (*hexutil.Big)(r),
S: (*hexutil.Big)(s),
}
if blockHash != (common.Hash{}) {
result.BlockHash = &blockHash
result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber))
result.TransactionIndex = (*hexutil.Uint64)(&index)
}
return result
}
type rpcBlock struct {
Hash common.Hash `json:"hash"`
Transactions []rpcTransaction `json:"transactions"`
UncleHashes []common.Hash `json:"uncles"`
}
type rpcTransaction struct {
tx *types.Transaction
txExtraInfo
}
type txExtraInfo struct {
BlockNumber *string `json:"blockNumber,omitempty"`
BlockHash *common.Hash `json:"blockHash,omitempty"`
From *common.Address `json:"from,omitempty"`
}
func (tx *rpcTransaction) UnmarshalJSON(msg []byte) error {
if err := json.Unmarshal(msg, &tx.tx); err != nil {
return err
}
return json.Unmarshal(msg, &tx.txExtraInfo)
}
func getBlockAndUncleHashes(cli *rpc.Client, ctx context.Context, method string, args ...interface{}) (*types.Block, []common.Hash, error) {
var raw json.RawMessage
err := cli.CallContext(ctx, &raw, method, args...)
if err != nil {
return nil, nil, err
} else if len(raw) == 0 {
return nil, nil, ethereum.NotFound
}
// Decode header and transactions.
var head *types.Header
var body rpcBlock
if err := json.Unmarshal(raw, &head); err != nil {
return nil, nil, err
}
if err := json.Unmarshal(raw, &body); err != nil {
return nil, nil, err
}
// Quick-verify transaction and uncle lists. This mostly helps with debugging the server.
if head.UncleHash == types.EmptyUncleHash && len(body.UncleHashes) > 0 {
return nil, nil, fmt.Errorf("server returned non-empty uncle list but block header indicates no uncles")
}
if head.UncleHash != types.EmptyUncleHash && len(body.UncleHashes) == 0 {
return nil, nil, fmt.Errorf("server returned empty uncle list but block header indicates uncles")
}
if head.TxHash == types.EmptyRootHash && len(body.Transactions) > 0 {
return nil, nil, fmt.Errorf("server returned non-empty transaction list but block header indicates no transactions")
}
if head.TxHash != types.EmptyRootHash && len(body.Transactions) == 0 {
return nil, nil, fmt.Errorf("server returned empty transaction list but block header indicates transactions")
}
txs := make([]*types.Transaction, len(body.Transactions))
for i, tx := range body.Transactions {
txs[i] = tx.tx
}
return types.NewBlockWithHeader(head).WithBody(txs, nil), body.UncleHashes, nil
}
// newRPCRawTransactionFromBlockIndex returns the bytes of a transaction given a block and a transaction index.
func newRPCRawTransactionFromBlockIndex(b *types.Block, index uint64) hexutil.Bytes {
txs := b.Transactions()
if index >= uint64(len(txs)) {
return nil
}
blob, _ := rlp.EncodeToBytes(txs[index])
return blob
}
// newRPCTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation.
func newRPCTransactionFromBlockIndex(b *types.Block, index uint64) *RPCTransaction {
txs := b.Transactions()
if index >= uint64(len(txs)) {
return nil
}
return NewRPCTransaction(txs[index], b.Hash(), b.NumberU64(), index)
}
// extractLogsOfInterest returns logs from the receipt IPLD
func extractLogsOfInterest(rctIPLDs []ipfs.BlockModel, wantedTopics [][]string) ([]*types.Log, error) {
var logs []*types.Log
for _, rctIPLD := range rctIPLDs {
rctRLP := rctIPLD
var rct types.Receipt
if err := rlp.DecodeBytes(rctRLP.Data, &rct); err != nil {
return nil, err
}
for _, log := range rct.Logs {
if wanted := wantedLog(wantedTopics, log.Topics); wanted == true {
logs = append(logs, log)
}
}
}
return logs, nil
}
// returns true if the log matches on the filter
func wantedLog(wantedTopics [][]string, actualTopics []common.Hash) bool {
// actualTopics will always have length <= 4
// wantedTopics will always have length 4
matches := 0
for i, actualTopic := range actualTopics {
// If we have topics in this filter slot, count as a match if the actualTopic matches one of the ones in this filter slot
if len(wantedTopics[i]) > 0 {
matches += sliceContainsHash(wantedTopics[i], actualTopic)
} else {
// Filter slot is empty, not matching any topics at this slot => counts as a match
matches++
}
}
if matches == len(actualTopics) {
return true
}
return false
}
// returns 1 if the slice contains the hash, 0 if it does not
func sliceContainsHash(slice []string, hash common.Hash) int {
for _, str := range slice {
if str == hash.String() {
return 1
}
}
return 0
}
func toFilterArg(q ethereum.FilterQuery) (interface{}, error) {
arg := map[string]interface{}{
"address": q.Addresses,
"topics": q.Topics,
}
if q.BlockHash != nil {
arg["blockHash"] = *q.BlockHash
if q.FromBlock != nil || q.ToBlock != nil {
return nil, fmt.Errorf("cannot specify both BlockHash and FromBlock/ToBlock")
}
} else {
if q.FromBlock == nil {
arg["fromBlock"] = "0x0"
} else {
arg["fromBlock"] = toBlockNumArg(q.FromBlock)
}
arg["toBlock"] = toBlockNumArg(q.ToBlock)
}
return arg, nil
}
func toBlockNumArg(number *big.Int) string {
if number == nil {
return "latest"
}
return hexutil.EncodeBig(number)
}
-165
View File
@@ -1,165 +0,0 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package eth_test
import (
"bytes"
"context"
"io/ioutil"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi"
"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/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-server/pkg/eth"
"github.com/vulcanize/ipld-eth-server/pkg/eth/test_helpers"
"github.com/vulcanize/ipld-eth-server/pkg/shared"
)
var (
parsedABI abi.ABI
)
func init() {
// load abi
abiBytes, err := ioutil.ReadFile("./test_helpers/abi.json")
if err != nil {
panic(err)
}
parsedABI, err = abi.JSON(bytes.NewReader(abiBytes))
if err != nil {
panic(err)
}
}
var _ = Describe("eth_call", func() {
var (
blocks []*types.Block
receipts []types.Receipts
chain *core.BlockChain
db *postgres.DB
transformer *eth2.StateDiffTransformer
backend *eth.Backend
api *eth.PublicEthAPI
builder statediff.Builder
pams statediff.Params
chainConfig = params.TestChainConfig
mockTD = big.NewInt(1337)
)
BeforeEach(func() {
// db and type initializations
var err error
db, err = shared.SetupDB()
Expect(err).ToNot(HaveOccurred())
transformer = eth2.NewStateDiffTransformer(chainConfig, db)
backend, err = eth.NewEthBackend(db, &eth.Config{
ChainConfig: chainConfig,
VmConfig: vm.Config{},
RPCGasCap: big.NewInt(10000000000),
})
Expect(err).ToNot(HaveOccurred())
api = eth.NewPublicEthAPI(backend)
// make the test blockchain (and state)
blocks, receipts, chain = test_helpers.MakeChain(4, test_helpers.Genesis, test_helpers.TestChainGen)
pams = 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 {
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]
}
diff, err := builder.BuildStateDiffObject(args, pams)
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())
}
})
AfterEach(func() {
eth.TearDownDB(db)
chain.Stop()
})
Describe("eth_call", func() {
It("Applies call args (tx data) on top of state, returning the result (e.g. a Getter method call)", func() {
data, err := parsedABI.Pack("data")
Expect(err).ToNot(HaveOccurred())
bdata := hexutil.Bytes(data)
callArgs := eth.CallArgs{
To: &test_helpers.ContractAddr,
Data: &bdata,
}
res, err := api.Call(context.Background(), callArgs, rpc.BlockNumberOrHashWithNumber(2), nil)
Expect(err).ToNot(HaveOccurred())
expectedRes := hexutil.Bytes(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
Expect(res).To(Equal(expectedRes))
res, err = api.Call(context.Background(), callArgs, rpc.BlockNumberOrHashWithNumber(3), nil)
Expect(err).ToNot(HaveOccurred())
expectedRes = hexutil.Bytes(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000003"))
Expect(res).To(Equal(expectedRes))
res, err = api.Call(context.Background(), callArgs, rpc.BlockNumberOrHashWithNumber(4), nil)
Expect(err).ToNot(HaveOccurred())
expectedRes = hexutil.Bytes(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000009"))
Expect(res).To(Equal(expectedRes))
})
})
})
+467
View File
@@ -0,0 +1,467 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package eth_test
import (
"bytes"
"context"
"io/ioutil"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi"
"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/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-server/pkg/eth"
"github.com/vulcanize/ipld-eth-server/pkg/eth/test_helpers"
"github.com/vulcanize/ipld-eth-server/pkg/shared"
)
var (
parsedABI abi.ABI
)
func init() {
// load abi
abiBytes, err := ioutil.ReadFile("./test_helpers/abi.json")
if err != nil {
panic(err)
}
parsedABI, err = abi.JSON(bytes.NewReader(abiBytes))
if err != nil {
panic(err)
}
}
var _ = Describe("eth state reading tests", func() {
var (
blocks []*types.Block
receipts []types.Receipts
chain *core.BlockChain
db *postgres.DB
api *eth.PublicEthAPI
backend *eth.Backend
chainConfig = params.TestChainConfig
mockTD = big.NewInt(1337)
expectedCanonicalHeader map[string]interface{}
)
It("test init", func() {
// db and type initializations
var err error
db, err = shared.SetupDB()
Expect(err).ToNot(HaveOccurred())
transformer := eth2.NewStateDiffTransformer(chainConfig, db)
backend, err = eth.NewEthBackend(db, &eth.Config{
ChainConfig: chainConfig,
VmConfig: vm.Config{},
RPCGasCap: big.NewInt(10000000000),
})
Expect(err).ToNot(HaveOccurred())
api = eth.NewPublicEthAPI(backend, nil)
// 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,
}
canonicalHeader := blocks[1].Header()
expectedCanonicalHeader = map[string]interface{}{
"number": (*hexutil.Big)(canonicalHeader.Number),
"hash": canonicalHeader.Hash(),
"parentHash": canonicalHeader.ParentHash,
"nonce": canonicalHeader.Nonce,
"mixHash": canonicalHeader.MixDigest,
"sha3Uncles": canonicalHeader.UncleHash,
"logsBloom": canonicalHeader.Bloom,
"stateRoot": canonicalHeader.Root,
"miner": canonicalHeader.Coinbase,
"difficulty": (*hexutil.Big)(canonicalHeader.Difficulty),
"extraData": hexutil.Bytes([]byte{}),
"size": hexutil.Uint64(canonicalHeader.Size()),
"gasLimit": hexutil.Uint64(canonicalHeader.GasLimit),
"gasUsed": hexutil.Uint64(canonicalHeader.GasUsed),
"timestamp": hexutil.Uint64(canonicalHeader.Time),
"transactionsRoot": canonicalHeader.TxHash,
"receiptsRoot": canonicalHeader.ReceiptHash,
"totalDifficulty": (*hexutil.Big)(mockTD),
}
// iterate over the blocks, generating statediff payloads, and transforming the data into Postgres
builder := statediff.NewBuilder(chain.StateCache())
for i, block := range blocks {
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]
}
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)
api = eth.NewPublicEthAPI(backend, nil)
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())
})
defer It("test teardown", func() {
eth.TearDownDB(db)
chain.Stop()
})
Describe("eth_call", func() {
It("Applies call args (tx data) on top of state, returning the result (e.g. a Getter method call)", func() {
data, err := parsedABI.Pack("data")
Expect(err).ToNot(HaveOccurred())
bdata := hexutil.Bytes(data)
callArgs := eth.CallArgs{
To: &test_helpers.ContractAddr,
Data: &bdata,
}
// Before contract deployment, returns nil
res, err := api.Call(context.Background(), callArgs, rpc.BlockNumberOrHashWithNumber(0), nil)
Expect(err).ToNot(HaveOccurred())
Expect(res).To(BeNil())
res, err = api.Call(context.Background(), callArgs, rpc.BlockNumberOrHashWithNumber(1), nil)
Expect(err).ToNot(HaveOccurred())
Expect(res).To(BeNil())
// After deployment
res, err = api.Call(context.Background(), callArgs, rpc.BlockNumberOrHashWithNumber(2), nil)
Expect(err).ToNot(HaveOccurred())
expectedRes := hexutil.Bytes(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
Expect(res).To(Equal(expectedRes))
res, err = api.Call(context.Background(), callArgs, rpc.BlockNumberOrHashWithNumber(3), nil)
Expect(err).ToNot(HaveOccurred())
expectedRes = hexutil.Bytes(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000003"))
Expect(res).To(Equal(expectedRes))
res, err = api.Call(context.Background(), callArgs, rpc.BlockNumberOrHashWithNumber(4), nil)
Expect(err).ToNot(HaveOccurred())
expectedRes = hexutil.Bytes(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000009"))
Expect(res).To(Equal(expectedRes))
res, err = api.Call(context.Background(), callArgs, rpc.BlockNumberOrHashWithNumber(5), nil)
Expect(err).ToNot(HaveOccurred())
expectedRes = hexutil.Bytes(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000"))
Expect(res).To(Equal(expectedRes))
})
})
var (
expectedContractBalance = (*hexutil.Big)(common.Big0)
expectedBankBalanceBlock0 = (*hexutil.Big)(test_helpers.TestBankFunds)
expectedAcct1BalanceBlock1 = (*hexutil.Big)(big.NewInt(10000))
expectedBankBalanceBlock1 = (*hexutil.Big)(new(big.Int).Sub(test_helpers.TestBankFunds, big.NewInt(10000)))
expectedAcct2BalanceBlock2 = (*hexutil.Big)(big.NewInt(1000))
expectedBankBalanceBlock2 = (*hexutil.Big)(new(big.Int).Sub(expectedBankBalanceBlock1.ToInt(), big.NewInt(1000)))
expectedAcct2BalanceBlock3 = (*hexutil.Big)(new(big.Int).Add(expectedAcct2BalanceBlock2.ToInt(), test_helpers.MiningReward))
expectedAcct2BalanceBlock4 = (*hexutil.Big)(new(big.Int).Add(expectedAcct2BalanceBlock3.ToInt(), test_helpers.MiningReward))
expectedAcct1BalanceBlock5 = (*hexutil.Big)(new(big.Int).Add(expectedAcct1BalanceBlock1.ToInt(), test_helpers.MiningReward))
)
Describe("eth_getBalance", func() {
It("Retrieves the eth balance for the provided account address at the block with the provided number", func() {
bal, err := api.GetBalance(ctx, test_helpers.TestBankAddress, rpc.BlockNumberOrHashWithNumber(0))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedBankBalanceBlock0))
bal, err = api.GetBalance(ctx, test_helpers.Account1Addr, rpc.BlockNumberOrHashWithNumber(1))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedAcct1BalanceBlock1))
_, err = api.GetBalance(ctx, test_helpers.Account2Addr, rpc.BlockNumberOrHashWithNumber(1))
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("sql: no rows in result set"))
_, err = api.GetBalance(ctx, test_helpers.ContractAddr, rpc.BlockNumberOrHashWithNumber(1))
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("sql: no rows in result set"))
bal, err = api.GetBalance(ctx, test_helpers.TestBankAddress, rpc.BlockNumberOrHashWithNumber(1))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedBankBalanceBlock1))
bal, err = api.GetBalance(ctx, test_helpers.Account1Addr, rpc.BlockNumberOrHashWithNumber(2))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedAcct1BalanceBlock1))
bal, err = api.GetBalance(ctx, test_helpers.Account2Addr, rpc.BlockNumberOrHashWithNumber(2))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedAcct2BalanceBlock2))
bal, err = api.GetBalance(ctx, test_helpers.ContractAddr, rpc.BlockNumberOrHashWithNumber(2))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedContractBalance))
bal, err = api.GetBalance(ctx, test_helpers.TestBankAddress, rpc.BlockNumberOrHashWithNumber(2))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedBankBalanceBlock2))
bal, err = api.GetBalance(ctx, test_helpers.Account1Addr, rpc.BlockNumberOrHashWithNumber(3))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedAcct1BalanceBlock1))
bal, err = api.GetBalance(ctx, test_helpers.Account2Addr, rpc.BlockNumberOrHashWithNumber(3))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedAcct2BalanceBlock3))
bal, err = api.GetBalance(ctx, test_helpers.ContractAddr, rpc.BlockNumberOrHashWithNumber(3))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedContractBalance))
bal, err = api.GetBalance(ctx, test_helpers.TestBankAddress, rpc.BlockNumberOrHashWithNumber(3))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedBankBalanceBlock2))
bal, err = api.GetBalance(ctx, test_helpers.Account1Addr, rpc.BlockNumberOrHashWithNumber(4))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedAcct1BalanceBlock1))
bal, err = api.GetBalance(ctx, test_helpers.Account2Addr, rpc.BlockNumberOrHashWithNumber(4))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedAcct2BalanceBlock4))
bal, err = api.GetBalance(ctx, test_helpers.ContractAddr, rpc.BlockNumberOrHashWithNumber(4))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedContractBalance))
bal, err = api.GetBalance(ctx, test_helpers.TestBankAddress, rpc.BlockNumberOrHashWithNumber(4))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedBankBalanceBlock2))
bal, err = api.GetBalance(ctx, test_helpers.Account1Addr, rpc.BlockNumberOrHashWithNumber(5))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedAcct1BalanceBlock5))
bal, err = api.GetBalance(ctx, test_helpers.Account2Addr, rpc.BlockNumberOrHashWithNumber(5))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedAcct2BalanceBlock4))
bal, err = api.GetBalance(ctx, test_helpers.ContractAddr, rpc.BlockNumberOrHashWithNumber(5))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedContractBalance))
bal, err = api.GetBalance(ctx, test_helpers.TestBankAddress, rpc.BlockNumberOrHashWithNumber(5))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedBankBalanceBlock2))
})
It("Retrieves the eth balance for the provided account address at the block with the provided hash", func() {
bal, err := api.GetBalance(ctx, test_helpers.TestBankAddress, rpc.BlockNumberOrHashWithHash(blocks[0].Hash(), true))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedBankBalanceBlock0))
bal, err = api.GetBalance(ctx, test_helpers.Account1Addr, rpc.BlockNumberOrHashWithHash(blocks[1].Hash(), true))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedAcct1BalanceBlock1))
_, err = api.GetBalance(ctx, test_helpers.Account2Addr, rpc.BlockNumberOrHashWithHash(blocks[1].Hash(), true))
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("sql: no rows in result set"))
_, err = api.GetBalance(ctx, test_helpers.ContractAddr, rpc.BlockNumberOrHashWithHash(blocks[1].Hash(), true))
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("sql: no rows in result set"))
bal, err = api.GetBalance(ctx, test_helpers.TestBankAddress, rpc.BlockNumberOrHashWithHash(blocks[1].Hash(), true))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedBankBalanceBlock1))
bal, err = api.GetBalance(ctx, test_helpers.Account1Addr, rpc.BlockNumberOrHashWithHash(blocks[2].Hash(), true))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedAcct1BalanceBlock1))
bal, err = api.GetBalance(ctx, test_helpers.Account2Addr, rpc.BlockNumberOrHashWithHash(blocks[2].Hash(), true))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedAcct2BalanceBlock2))
bal, err = api.GetBalance(ctx, test_helpers.ContractAddr, rpc.BlockNumberOrHashWithHash(blocks[2].Hash(), true))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedContractBalance))
bal, err = api.GetBalance(ctx, test_helpers.TestBankAddress, rpc.BlockNumberOrHashWithHash(blocks[2].Hash(), true))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedBankBalanceBlock2))
bal, err = api.GetBalance(ctx, test_helpers.Account1Addr, rpc.BlockNumberOrHashWithHash(blocks[3].Hash(), true))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedAcct1BalanceBlock1))
bal, err = api.GetBalance(ctx, test_helpers.Account2Addr, rpc.BlockNumberOrHashWithHash(blocks[3].Hash(), true))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedAcct2BalanceBlock3))
bal, err = api.GetBalance(ctx, test_helpers.ContractAddr, rpc.BlockNumberOrHashWithHash(blocks[3].Hash(), true))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedContractBalance))
bal, err = api.GetBalance(ctx, test_helpers.TestBankAddress, rpc.BlockNumberOrHashWithHash(blocks[3].Hash(), true))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedBankBalanceBlock2))
bal, err = api.GetBalance(ctx, test_helpers.Account1Addr, rpc.BlockNumberOrHashWithHash(blocks[4].Hash(), true))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedAcct1BalanceBlock1))
bal, err = api.GetBalance(ctx, test_helpers.Account2Addr, rpc.BlockNumberOrHashWithHash(blocks[4].Hash(), true))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedAcct2BalanceBlock4))
bal, err = api.GetBalance(ctx, test_helpers.ContractAddr, rpc.BlockNumberOrHashWithHash(blocks[4].Hash(), true))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedContractBalance))
bal, err = api.GetBalance(ctx, test_helpers.TestBankAddress, rpc.BlockNumberOrHashWithHash(blocks[4].Hash(), true))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedBankBalanceBlock2))
bal, err = api.GetBalance(ctx, test_helpers.Account1Addr, rpc.BlockNumberOrHashWithHash(blocks[5].Hash(), true))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedAcct1BalanceBlock5))
bal, err = api.GetBalance(ctx, test_helpers.Account2Addr, rpc.BlockNumberOrHashWithHash(blocks[5].Hash(), true))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedAcct2BalanceBlock4))
bal, err = api.GetBalance(ctx, test_helpers.ContractAddr, rpc.BlockNumberOrHashWithHash(blocks[5].Hash(), true))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedContractBalance))
bal, err = api.GetBalance(ctx, test_helpers.TestBankAddress, rpc.BlockNumberOrHashWithHash(blocks[5].Hash(), true))
Expect(err).ToNot(HaveOccurred())
Expect(bal).To(Equal(expectedBankBalanceBlock2))
})
It("Throws an error for an account it cannot find the balance for an account at the provided block number", func() {
_, err := api.GetBalance(ctx, test_helpers.Account1Addr, rpc.BlockNumberOrHashWithNumber(0))
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("sql: no rows in result set"))
_, err = api.GetBalance(ctx, test_helpers.Account2Addr, rpc.BlockNumberOrHashWithNumber(0))
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("sql: no rows in result set"))
_, err = api.GetBalance(ctx, test_helpers.ContractAddr, rpc.BlockNumberOrHashWithNumber(0))
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("sql: no rows in result set"))
})
It("Throws an error for an account it cannot find the balance for an account at the provided block hash", func() {
_, err := api.GetBalance(ctx, test_helpers.Account1Addr, rpc.BlockNumberOrHashWithHash(blocks[0].Hash(), true))
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("sql: no rows in result set"))
_, err = api.GetBalance(ctx, test_helpers.Account2Addr, rpc.BlockNumberOrHashWithHash(blocks[0].Hash(), true))
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("sql: no rows in result set"))
_, err = api.GetBalance(ctx, test_helpers.ContractAddr, rpc.BlockNumberOrHashWithHash(blocks[0].Hash(), true))
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("sql: no rows in result set"))
})
})
Describe("eth_getCode", func() {
It("Retrieves the code for the provided contract address at the block with the provided number", func() {
code, err := api.GetCode(ctx, test_helpers.ContractAddr, rpc.BlockNumberOrHashWithNumber(3))
Expect(err).ToNot(HaveOccurred())
Expect(code).To(Equal((hexutil.Bytes)(test_helpers.ContractCode)))
code, err = api.GetCode(ctx, test_helpers.ContractAddr, rpc.BlockNumberOrHashWithNumber(5))
Expect(err).ToNot(HaveOccurred())
Expect(code).To(Equal((hexutil.Bytes)(test_helpers.ContractCode)))
})
It("Retrieves the code for the provided contract address at the block with the provided hash", func() {
code, err := api.GetCode(ctx, test_helpers.ContractAddr, rpc.BlockNumberOrHashWithHash(blocks[3].Hash(), true))
Expect(err).ToNot(HaveOccurred())
Expect(code).To(Equal((hexutil.Bytes)(test_helpers.ContractCode)))
code, err = api.GetCode(ctx, test_helpers.ContractAddr, rpc.BlockNumberOrHashWithHash(blocks[5].Hash(), true))
Expect(err).ToNot(HaveOccurred())
Expect(code).To(Equal((hexutil.Bytes)(test_helpers.ContractCode)))
})
It("Throws an error for an account it cannot find the code for", func() {
_, err := api.GetCode(ctx, randomAddr, rpc.BlockNumberOrHashWithHash(blocks[3].Hash(), true))
Expect(err).To(HaveOccurred())
})
It("Throws an error for a contract that doesn't exist at this hieght", func() {
_, err := api.GetCode(ctx, test_helpers.ContractAddr, rpc.BlockNumberOrHashWithNumber(0))
Expect(err).To(HaveOccurred())
})
})
Describe("eth_getStorageAt", func() {
It("Throws an error if it tries to access a contract which does not exist", func() {
_, err := api.GetStorageAt(ctx, test_helpers.ContractAddr, test_helpers.ContractSlotKeyHash.Hex(), rpc.BlockNumberOrHashWithNumber(0))
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("sql: no rows in result set"))
_, err = api.GetStorageAt(ctx, test_helpers.ContractAddr, test_helpers.ContractSlotKeyHash.Hex(), rpc.BlockNumberOrHashWithNumber(1))
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("sql: no rows in result set"))
})
It("Throws an error if it tries to access a contract slot which does not exist", func() {
_, err := api.GetStorageAt(ctx, test_helpers.ContractAddr, randomHash.Hex(), rpc.BlockNumberOrHashWithNumber(2))
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("sql: no rows in result set"))
})
It("Retrieves the storage value at the provided contract address and storage leaf key at the block with the provided hash or number", func() {
// After deployment
val, err := api.GetStorageAt(ctx, test_helpers.ContractAddr, test_helpers.ContractSlotKeyHash.Hex(), rpc.BlockNumberOrHashWithNumber(2))
Expect(err).ToNot(HaveOccurred())
expectedRes := hexutil.Bytes(common.Hex2Bytes("01"))
Expect(val).To(Equal(expectedRes))
val, err = api.GetStorageAt(ctx, test_helpers.ContractAddr, test_helpers.ContractSlotKeyHash.Hex(), rpc.BlockNumberOrHashWithNumber(3))
Expect(err).ToNot(HaveOccurred())
expectedRes = hexutil.Bytes(common.Hex2Bytes("03"))
Expect(val).To(Equal(expectedRes))
val, err = api.GetStorageAt(ctx, test_helpers.ContractAddr, test_helpers.ContractSlotKeyHash.Hex(), rpc.BlockNumberOrHashWithNumber(4))
Expect(err).ToNot(HaveOccurred())
expectedRes = hexutil.Bytes(common.Hex2Bytes("09"))
Expect(val).To(Equal(expectedRes))
val, err = api.GetStorageAt(ctx, test_helpers.ContractAddr, test_helpers.ContractSlotKeyHash.Hex(), rpc.BlockNumberOrHashWithNumber(5))
Expect(err).ToNot(HaveOccurred())
Expect(val).To(Equal(hexutil.Bytes{}))
})
})
Describe("eth_getHeaderByNumber", func() {
It("Finds the canonical header based on the header's weight relative to others at the provided height", func() {
header, err := api.GetHeaderByNumber(ctx, number)
Expect(err).ToNot(HaveOccurred())
Expect(header).To(Equal(expectedCanonicalHeader))
})
})
})
+47
View File
@@ -0,0 +1,47 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package eth
import (
"context"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/bloombits"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/rpc"
)
// FilterBackend is the geth interface we need to satisfy to use their filters
type FilterBackend interface {
ChainDb() ethdb.Database
HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error)
HeaderByHash(ctx context.Context, blockHash common.Hash) (*types.Header, error)
GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error)
GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error)
SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription
SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription
BloomStatus() (uint64, uint64)
ServiceFilter(ctx context.Context, session *bloombits.MatcherSession)
}
+217 -107
View File
@@ -28,76 +28,137 @@ import (
)
const (
RetrieveHeadersByHashesPgStr = `SELECT cid, data FROM eth.header_cids
INNER JOIN public.blocks ON (header_cids.mh_key = blocks.key)
RetrieveHeadersByHashesPgStr = `SELECT cid, data
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
INNER JOIN public.blocks ON (header_cids.mh_key = blocks.key)
RetrieveHeadersByBlockNumberPgStr = `SELECT cid, data
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
INNER JOIN public.blocks ON (header_cids.mh_key = blocks.key)
RetrieveHeaderByHashPgStr = `SELECT cid, data
FROM eth.header_cids
INNER JOIN public.blocks ON (header_cids.mh_key = blocks.key)
WHERE block_hash = $1`
RetrieveUnclesByHashesPgStr = `SELECT cid, data FROM eth.uncle_cids
INNER JOIN public.blocks ON (uncle_cids.mh_key = blocks.key)
RetrieveUnclesByHashesPgStr = `SELECT cid, data
FROM eth.uncle_cids
INNER JOIN public.blocks ON (uncle_cids.mh_key = blocks.key)
WHERE block_hash = ANY($1::VARCHAR(66)[])`
RetrieveUnclesByBlockHashPgStr = `SELECT cid, data FROM eth.uncle_cids, eth.header_cids, public.blocks
WHERE uncle_cids.header_id = header_cids.id
AND uncle_cids.mh_key = blocks.key
AND block_hash = $1`
RetrieveUnclesByBlockNumberPgStr = `SELECT cid, data FROM eth.uncle_cids, eth.header_cids, public.blocks
WHERE uncle_cids.header_id = header_cids.id
AND uncle_cids.mh_key = blocks.key
AND block_number = $1`
RetrieveUncleByHashPgStr = `SELECT cid, data FROM eth.uncle_cids
INNER JOIN public.blocks ON (uncle_cids.mh_key = blocks.key)
RetrieveUnclesByBlockHashPgStr = `SELECT uncle_cids.cid, data
FROM eth.uncle_cids
INNER JOIN eth.header_cids ON (uncle_cids.header_id = header_cids.id)
INNER JOIN public.blocks ON (uncle_cids.mh_key = blocks.key)
WHERE block_hash = $1`
RetrieveUnclesByBlockNumberPgStr = `SELECT uncle_cids.cid, data
FROM eth.uncle_cids
INNER JOIN eth.header_cids ON (uncle_cids.header_id = header_cids.id)
INNER JOIN public.blocks ON (uncle_cids.mh_key = blocks.key)
WHERE block_number = $1`
RetrieveUncleByHashPgStr = `SELECT cid, data
FROM eth.uncle_cids
INNER JOIN public.blocks ON (uncle_cids.mh_key = blocks.key)
WHERE block_hash = $1`
RetrieveTransactionsByHashesPgStr = `SELECT cid, data FROM eth.transaction_cids
INNER JOIN public.blocks ON (transaction_cids.mh_key = blocks.key)
RetrieveTransactionsByHashesPgStr = `SELECT cid, data
FROM eth.transaction_cids
INNER JOIN public.blocks ON (transaction_cids.mh_key = blocks.key)
WHERE tx_hash = ANY($1::VARCHAR(66)[])`
RetrieveTransactionsByBlockHashPgStr = `SELECT cid, data FROM eth.transaction_cids, eth.header_cids, public.blocks
WHERE transaction_cids.header_id = header_cids.id
AND transaction_cids.mh_key = blocks.key
AND block_hash = $1`
RetrieveTransactionsByBlockNumberPgStr = `SELECT cid, data FROM eth.transaction_cids, eth.header_cids, public.blocks
WHERE transaction_cids.header_id = header_cids.id
AND transaction_cids.mh_key = blocks.key
AND block_number = $1`
RetrieveTransactionByHashPgStr = `SELECT cid, data FROM eth.transaction_cids
INNER JOIN public.blocks ON (transaction_cids.mh_key = blocks.key)
RetrieveTransactionsByBlockHashPgStr = `SELECT transaction_cids.cid, data
FROM eth.transaction_cids
INNER JOIN eth.header_cids ON (transaction_cids.header_id = header_cids.id)
INNER JOIN public.blocks ON (transaction_cids.mh_key = blocks.key)
WHERE block_hash = $1`
RetrieveTransactionsByBlockNumberPgStr = `SELECT transaction_cids.cid, data
FROM eth.transaction_cids
INNER JOIN eth.header_cids ON (transaction_cids.header_id = header_cids.id)
INNER JOIN public.blocks ON (transaction_cids.mh_key = blocks.key)
WHERE block_number = $1`
RetrieveTransactionByHashPgStr = `SELECT cid, data
FROM eth.transaction_cids
INNER JOIN public.blocks ON (transaction_cids.mh_key = blocks.key)
WHERE tx_hash = $1`
RetrieveReceiptsByTxHashesPgStr = `SELECT cid, data FROM eth.receipt_cids, eth.transaction_cids, public.blocks
WHERE receipt_cids.mh_key = blocks.key
AND receipt_cids.tx_id = transaction_cids.id
AND tx_hash = ANY($1::VARCHAR(66)[])`
RetrieveReceiptsByBlockHashPgStr = `SELECT cid, data FROM eth.receipt_cids, eth.transaction_cids, eth.header_cids, public.blocks
WHERE receipt_cids.tx_id = transaction_cids.id
AND transaction_cids.header_id = header_cids.id
AND receipt_cids.mh_key = blocks.key
AND block_hash = $1`
RetrieveReceiptsByBlockNumberPgStr = `SELECT cid, data FROM eth.receipt_cids, eth.transaction_cids, eth.header_cids, public.blocks
WHERE receipt_cids.tx_id = transaction_cids.id
AND transaction_cids.header_id = header_cids.id
AND receipt_cids.mh_key = blocks.key
AND block_number = $1`
RetrieveReceiptByTxHashPgStr = `SELECT cid, data FROM eth.receipt_cids, eth.transaction_cids, eth.receipt_cids
WHERE receipt_cids.mh_key = blocks.key
AND receipt_cids.tx_id = transaction_cids.id
AND tx_hash = $1`
RetrieveAccountByLeafKeyAndBlockHashPgStr = `SELECT cid, data FROM eth.state_cids, eth.header_cids, public.blocks
WHERE state_cids.header_id = header_cids.id
AND state_cids.mh_key = blocks.key
AND state_leaf_key = $1
AND block_hash = $2`
RetrieveAccountByLeafKeyAndBlockNumberPgStr = `SELECT cid, data FROM eth.state_cids, eth.header_cids, public.blocks
WHERE state_cids.header_id = header_cids.id
AND state_cids.mh_key = blocks.key
AND state_leaf_key = $1
AND block_number = $2`
RetrieveReceiptsByTxHashesPgStr = `SELECT receipt_cids.cid, data
FROM eth.receipt_cids
INNER JOIN eth.transaction_cids ON (receipt_cids.tx_id = transaction_cids.id)
INNER JOIN public.blocks ON (receipt_cids.mh_key = blocks.key)
WHERE tx_hash = ANY($1::VARCHAR(66)[])`
RetrieveReceiptsByBlockHashPgStr = `SELECT receipt_cids.cid, data
FROM eth.receipt_cids
INNER JOIN eth.transaction_cids ON (receipt_cids.tx_id = transaction_cids.id)
INNER JOIN eth.header_cids ON (transaction_cids.header_id = header_cids.id)
INNER JOIN public.blocks ON (receipt_cids.mh_key = blocks.key)
WHERE block_hash = $1`
RetrieveReceiptsByBlockNumberPgStr = `SELECT receipt_cids.cid, data
FROM eth.receipt_cids
INNER JOIN eth.transaction_cids ON (receipt_cids.tx_id = transaction_cids.id)
INNER JOIN eth.header_cids ON (transaction_cids.header_id = header_cids.id)
INNER JOIN public.blocks ON (receipt_cids.mh_key = blocks.key)
WHERE block_number = $1`
RetrieveReceiptByTxHashPgStr = `SELECT receipt_cids.cid, data
FROM eth.receipt_cids
INNER JOIN eth.transaction_cids ON (receipt_cids.tx_id = transaction_cids.id)
INNER JOIN public.blocks ON (receipt_cids.mh_key = blocks.key)
WHERE tx_hash = $1`
RetrieveAccountByLeafKeyAndBlockHashPgStr = `SELECT state_cids.cid,
data,
was_state_removed(state_path, block_number, $2) AS removed
FROM eth.state_cids
INNER JOIN eth.header_cids ON (state_cids.header_id = header_cids.id)
INNER JOIN public.blocks ON (state_cids.mh_key = blocks.key)
WHERE state_leaf_key = $1
AND block_number <= (SELECT block_number
FROM eth.header_cids
WHERE block_hash = $2)
AND header_cids.id = (SELECT canonical_header_id(block_number))
ORDER BY block_number DESC
LIMIT 1`
RetrieveAccountByLeafKeyAndBlockNumberPgStr = `SELECT state_cids.cid,
data,
was_state_removed(state_path, block_number, (SELECT block_hash
FROM eth.header_cids
WHERE block_number = $2
LIMIT 1)) AS removed
FROM eth.state_cids
INNER JOIN eth.header_cids ON (state_cids.header_id = header_cids.id)
INNER JOIN public.blocks ON (state_cids.mh_key = blocks.key)
WHERE state_leaf_key = $1
AND block_number <= $2
ORDER BY block_number DESC
LIMIT 1`
RetrieveStorageLeafByAddressHashAndLeafKeyAndBlockNumberPgStr = `SELECT storage_cids.cid,
data,
was_storage_removed(storage_path, block_number, (SELECT block_hash
FROM eth.header_cids
WHERE block_number = $3
LIMIT 1)) AS removed
FROM eth.storage_cids
INNER JOIN eth.state_cids ON (storage_cids.state_id = state_cids.id)
INNER JOIN eth.header_cids ON (state_cids.header_id = header_cids.id)
INNER JOIN public.blocks ON (storage_cids.mh_key = blocks.key)
WHERE state_leaf_key = $1
AND storage_leaf_key = $2
AND block_number <= $3
ORDER BY block_number DESC
LIMIT 1`
RetrieveStorageLeafByAddressHashAndLeafKeyAndBlockHashPgStr = `SELECT storage_cids.cid,
data,
was_storage_removed(storage_path, block_number, $3) AS removed
FROM eth.storage_cids
INNER JOIN eth.state_cids ON (storage_cids.state_id = state_cids.id)
INNER JOIN eth.header_cids ON (state_cids.header_id = header_cids.id)
INNER JOIN public.blocks ON (storage_cids.mh_key = blocks.key)
WHERE state_leaf_key = $1
AND storage_leaf_key = $2
AND block_number <= (SELECT block_number
FROM eth.header_cids
WHERE block_hash = $3)
AND header_cids.id = (SELECT canonical_header_id(block_number))
ORDER BY block_number DESC
LIMIT 1`
)
type ipldResult struct {
cid string `db:"cid"`
data []byte `db:"data"`
CID string `db:"cid"`
Data []byte `db:"data"`
}
type IPLDRetriever struct {
db *postgres.DB
@@ -122,8 +183,8 @@ func (r *IPLDRetriever) RetrieveHeadersByHashes(hashes []common.Hash) ([]string,
cids := make([]string, len(headerResults))
headers := make([][]byte, len(headerResults))
for i, res := range headerResults {
cids[i] = res.cid
headers[i] = res.data
cids[i] = res.CID
headers[i] = res.Data
}
return cids, headers, nil
}
@@ -138,8 +199,8 @@ func (r *IPLDRetriever) RetrieveHeadersByBlockNumber(number uint64) ([]string, [
cids := make([]string, len(headerResults))
headers := make([][]byte, len(headerResults))
for i, res := range headerResults {
cids[i] = res.cid
headers[i] = res.data
cids[i] = res.CID
headers[i] = res.Data
}
return cids, headers, nil
}
@@ -147,7 +208,7 @@ func (r *IPLDRetriever) RetrieveHeadersByBlockNumber(number uint64) ([]string, [
// RetrieveHeaderByHash returns the cid and rlp bytes for the header corresponding to the provided block hash
func (r *IPLDRetriever) RetrieveHeaderByHash(hash common.Hash) (string, []byte, error) {
headerResult := new(ipldResult)
return headerResult.cid, headerResult.data, r.db.Get(headerResult, RetrieveHeaderByHashPgStr, hash.Hex())
return headerResult.CID, headerResult.Data, r.db.Get(headerResult, RetrieveHeaderByHashPgStr, hash.Hex())
}
// RetrieveUnclesByHashes returns the cids and rlp bytes for the uncles corresponding to the provided uncle hashes
@@ -163,8 +224,8 @@ func (r *IPLDRetriever) RetrieveUnclesByHashes(hashes []common.Hash) ([]string,
cids := make([]string, len(uncleResults))
uncles := make([][]byte, len(uncleResults))
for i, res := range uncleResults {
cids[i] = res.cid
uncles[i] = res.data
cids[i] = res.CID
uncles[i] = res.Data
}
return cids, uncles, nil
}
@@ -178,8 +239,8 @@ func (r *IPLDRetriever) RetrieveUnclesByBlockHash(hash common.Hash) ([]string, [
cids := make([]string, len(uncleResults))
uncles := make([][]byte, len(uncleResults))
for i, res := range uncleResults {
cids[i] = res.cid
uncles[i] = res.data
cids[i] = res.CID
uncles[i] = res.Data
}
return cids, uncles, nil
}
@@ -193,8 +254,8 @@ func (r *IPLDRetriever) RetrieveUnclesByBlockNumber(number uint64) ([]string, []
cids := make([]string, len(uncleResults))
uncles := make([][]byte, len(uncleResults))
for i, res := range uncleResults {
cids[i] = res.cid
uncles[i] = res.data
cids[i] = res.CID
uncles[i] = res.Data
}
return cids, uncles, nil
}
@@ -202,7 +263,7 @@ func (r *IPLDRetriever) RetrieveUnclesByBlockNumber(number uint64) ([]string, []
// RetrieveUncleByHash returns the cid and rlp bytes for the uncle corresponding to the provided uncle hash
func (r *IPLDRetriever) RetrieveUncleByHash(hash common.Hash) (string, []byte, error) {
uncleResult := new(ipldResult)
return uncleResult.cid, uncleResult.data, r.db.Get(uncleResult, RetrieveUncleByHashPgStr, hash.Hex())
return uncleResult.CID, uncleResult.Data, r.db.Get(uncleResult, RetrieveUncleByHashPgStr, hash.Hex())
}
// RetrieveTransactionsByHashes returns the cids and rlp bytes for the transactions corresponding to the provided tx hashes
@@ -218,8 +279,8 @@ func (r *IPLDRetriever) RetrieveTransactionsByHashes(hashes []common.Hash) ([]st
cids := make([]string, len(txResults))
txs := make([][]byte, len(txResults))
for i, res := range txResults {
cids[i] = res.cid
txs[i] = res.data
cids[i] = res.CID
txs[i] = res.Data
}
return cids, txs, nil
}
@@ -233,8 +294,8 @@ func (r *IPLDRetriever) RetrieveTransactionsByBlockHash(hash common.Hash) ([]str
cids := make([]string, len(txResults))
txs := make([][]byte, len(txResults))
for i, res := range txResults {
cids[i] = res.cid
txs[i] = res.data
cids[i] = res.CID
txs[i] = res.Data
}
return cids, txs, nil
}
@@ -248,8 +309,8 @@ func (r *IPLDRetriever) RetrieveTransactionsByBlockNumber(number uint64) ([]stri
cids := make([]string, len(txResults))
txs := make([][]byte, len(txResults))
for i, res := range txResults {
cids[i] = res.cid
txs[i] = res.data
cids[i] = res.CID
txs[i] = res.Data
}
return cids, txs, nil
}
@@ -257,7 +318,7 @@ func (r *IPLDRetriever) RetrieveTransactionsByBlockNumber(number uint64) ([]stri
// RetrieveTransactionByTxHash returns the cid and rlp bytes for the transaction corresponding to the provided tx hash
func (r *IPLDRetriever) RetrieveTransactionByTxHash(hash common.Hash) (string, []byte, error) {
txResult := new(ipldResult)
return txResult.cid, txResult.data, r.db.Get(txResult, RetrieveTransactionByHashPgStr, hash.Hex())
return txResult.CID, txResult.Data, r.db.Get(txResult, RetrieveTransactionByHashPgStr, hash.Hex())
}
// RetrieveReceiptsByTxHashes returns the cids and rlp bytes for the receipts corresponding to the provided tx hashes
@@ -273,8 +334,8 @@ func (r *IPLDRetriever) RetrieveReceiptsByTxHashes(hashes []common.Hash) ([]stri
cids := make([]string, len(rctResults))
rcts := make([][]byte, len(rctResults))
for i, res := range rctResults {
cids[i] = res.cid
rcts[i] = res.data
cids[i] = res.CID
rcts[i] = res.Data
}
return cids, rcts, nil
}
@@ -288,8 +349,8 @@ func (r *IPLDRetriever) RetrieveReceiptsByBlockHash(hash common.Hash) ([]string,
cids := make([]string, len(rctResults))
rcts := make([][]byte, len(rctResults))
for i, res := range rctResults {
cids[i] = res.cid
rcts[i] = res.data
cids[i] = res.CID
rcts[i] = res.Data
}
return cids, rcts, nil
}
@@ -303,8 +364,8 @@ func (r *IPLDRetriever) RetrieveReceiptsByBlockNumber(number uint64) ([]string,
cids := make([]string, len(rctResults))
rcts := make([][]byte, len(rctResults))
for i, res := range rctResults {
cids[i] = res.cid
rcts[i] = res.data
cids[i] = res.CID
rcts[i] = res.Data
}
return cids, rcts, nil
}
@@ -312,46 +373,95 @@ func (r *IPLDRetriever) RetrieveReceiptsByBlockNumber(number uint64) ([]string,
// RetrieveReceiptByHash returns the cid and rlp bytes for the receipt corresponding to the provided tx hash
func (r *IPLDRetriever) RetrieveReceiptByHash(hash common.Hash) (string, []byte, error) {
rctResult := new(ipldResult)
return rctResult.cid, rctResult.data, r.db.Get(rctResult, RetrieveReceiptByTxHashPgStr, hash.Hex())
return rctResult.CID, rctResult.Data, r.db.Get(rctResult, RetrieveReceiptByTxHashPgStr, hash.Hex())
}
type nodeInfo struct {
CID string `db:"cid"`
Data []byte `db:"data"`
Removed bool `db:"removed"`
}
// RetrieveAccountByAddressAndBlockHash returns the cid and rlp bytes for the account corresponding to the provided address and block hash
// TODO: ensure this handles deleted accounts appropriately
func (r *IPLDRetriever) RetrieveAccountByAddressAndBlockHash(address common.Address, hash common.Hash) (string, []byte, error) {
accountResult := new(ipldResult)
accountResult := new(nodeInfo)
leafKey := crypto.Keccak256Hash(address.Bytes())
if err := r.db.Get(accountResult, RetrieveAccountByLeafKeyAndBlockHashPgStr, leafKey.Hex(), hash.Hex()); err != nil {
return "", nil, err
}
if accountResult.Removed {
return "", []byte{}, nil
}
var i []interface{}
if err := rlp.DecodeBytes(accountResult.data, &i); err != nil {
if err := rlp.DecodeBytes(accountResult.Data, &i); err != nil {
return "", nil, fmt.Errorf("error decoding state leaf node rlp: %s", err.Error())
}
if len(i) != 2 {
return "", nil, fmt.Errorf("eth IPLDRetriever expected state leaf node rlp to decode into two elements")
}
return accountResult.cid, i[1].([]byte), nil
return accountResult.CID, i[1].([]byte), nil
}
// RetrieveAccountByAddressAndBlockNumber returns the cid and rlp bytes for the account corresponding to the provided address and block number
// This can return multiple results if we have two versions of state in the database as the provided height
func (r *IPLDRetriever) RetrieveAccountByAddressAndBlockNumber(address common.Address, number uint64) ([]string, [][]byte, error) {
accountResults := make([]ipldResult, 0)
// This can return a non-canonical account
func (r *IPLDRetriever) RetrieveAccountByAddressAndBlockNumber(address common.Address, number uint64) (string, []byte, error) {
accountResult := new(nodeInfo)
leafKey := crypto.Keccak256Hash(address.Bytes())
if err := r.db.Get(&accountResults, RetrieveAccountByLeafKeyAndBlockNumberPgStr, leafKey.Hex(), number); err != nil {
return nil, nil, err
if err := r.db.Get(accountResult, RetrieveAccountByLeafKeyAndBlockNumberPgStr, leafKey.Hex(), number); err != nil {
return "", nil, err
}
cids := make([]string, len(accountResults))
accounts := make([][]byte, len(accountResults))
for i, res := range accountResults {
cids[i] = res.cid
var iface []interface{}
if err := rlp.DecodeBytes(res.data, &iface); err != nil {
return nil, nil, fmt.Errorf("error decoding state leaf node rlp: %s", err.Error())
}
if len(iface) != 2 {
return nil, nil, fmt.Errorf("eth IPLDRetriever expected state leaf node rlp to decode into two elements")
}
accounts[i] = iface[1].([]byte)
if accountResult.Removed {
return "", []byte{}, nil
}
return cids, accounts, nil
var i []interface{}
if err := rlp.DecodeBytes(accountResult.Data, &i); err != nil {
return "", nil, fmt.Errorf("error decoding state leaf node rlp: %s", err.Error())
}
if len(i) != 2 {
return "", nil, fmt.Errorf("eth IPLDRetriever expected state leaf node rlp to decode into two elements")
}
return accountResult.CID, i[1].([]byte), nil
}
// RetrieveStorageAtByAddressAndStorageKeyAndBlockHash returns the cid and rlp bytes for the storage value corresponding to the provided address, storage key, and block hash
func (r *IPLDRetriever) RetrieveStorageAtByAddressAndStorageKeyAndBlockHash(address common.Address, storageLeafKey, hash common.Hash) (string, []byte, error) {
storageResult := new(nodeInfo)
stateLeafKey := crypto.Keccak256Hash(address.Bytes())
if err := r.db.Get(storageResult, RetrieveStorageLeafByAddressHashAndLeafKeyAndBlockHashPgStr, stateLeafKey.Hex(), storageLeafKey.Hex(), hash.Hex()); err != nil {
return "", nil, err
}
if storageResult.Removed {
return "", []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
}
if len(i) != 2 {
return "", nil, fmt.Errorf("eth IPLDRetriever expected storage leaf node rlp to decode into two elements")
}
return storageResult.CID, 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
// This can retrun a non-canonical value
func (r *IPLDRetriever) RetrieveStorageAtByAddressAndStorageKeyAndBlockNumber(address common.Address, storageLeafKey common.Hash, number uint64) (string, []byte, error) {
storageResult := new(nodeInfo)
stateLeafKey := crypto.Keccak256Hash(address.Bytes())
if err := r.db.Get(storageResult, RetrieveStorageLeafByAddressHashAndLeafKeyAndBlockNumberPgStr, stateLeafKey.Hex(), storageLeafKey.Hex(), number); err != nil {
return "", nil, err
}
if storageResult.Removed {
return "", []byte{}, nil
}
var i []interface{}
if err := rlp.DecodeBytes(storageResult.Data, &i); err != nil {
return "", nil, fmt.Errorf("error decoding storage leaf node rlp: %s", err.Error())
}
if len(i) != 2 {
return "", nil, fmt.Errorf("eth IPLDRetriever expected storage leaf node rlp to decode into two elements")
}
return storageResult.CID, i[1].([]byte), nil
}
-156
View File
@@ -1,156 +0,0 @@
// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package eth_test
/*
import (
. "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-server/pkg/eth"
"github.com/vulcanize/ipld-eth-server/pkg/eth/mocks"
"github.com/vulcanize/ipld-eth-server/pkg/shared"
)
var _ = Describe("IPLD Retriever", func() {
var (
db *postgres.DB
repo *eth2.IPLDPublisher
//retriever *eth.IPLDRetriever
)
BeforeEach(func() {
var err error
db, err = shared.SetupDB()
Expect(err).ToNot(HaveOccurred())
repo = eth2.NewIPLDPublisher(db)
//retriever = eth.NewIPLDRetriever(db)
err = repo.Publish(mocks.MockConvertedPayload)
Expect(err).ToNot(HaveOccurred())
err = repo.Publish(mocks.MockConvertedPayload2)
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
eth.TearDownDB(db)
})
Describe("RetrieveHeadersByHashes", func() {
It("Retrieves all of the headers that correspond to the provided hashes", func() {
})
})
Describe("RetrieveHeadersByBlockNumber", func() {
It("Retrieves all CIDs for the given blocknumber when provided an open filter", func() {
})
})
Describe("RetrieveHeaderByHash", func() {
It("Retrieves all CIDs for the given blocknumber when provided an open filter", func() {
})
})
Describe("RetrieveUnclesByHashes", func() {
It("Retrieves all CIDs for the given blocknumber when provided an open filter", func() {
})
})
Describe("RetrieveUnclesByBlockHash", func() {
It("Retrieves all CIDs for the given blocknumber when provided an open filter", func() {
})
})
Describe("RetrieveUnclesByBlockNumber", func() {
It("Retrieves all CIDs for the given blocknumber when provided an open filter", func() {
})
})
Describe("RetrieveUncleByHash", func() {
It("Retrieves all CIDs for the given blocknumber when provided an open filter", func() {
})
})
Describe("RetrieveTransactionsByHashes", func() {
It("Retrieves all CIDs for the given blocknumber when provided an open filter", func() {
})
})
Describe("RetrieveTransactionsByBlockHash", func() {
It("Retrieves all CIDs for the given blocknumber when provided an open filter", func() {
})
})
Describe("RetrieveTransactionsByBlockNumber", func() {
It("Retrieves all CIDs for the given blocknumber when provided an open filter", func() {
})
})
Describe("RetrieveTransactionByTxHash", func() {
It("Retrieves all CIDs for the given blocknumber when provided an open filter", func() {
})
})
Describe("RetrieveReceiptsByTxHashes", func() {
It("Retrieves all CIDs for the given blocknumber when provided an open filter", func() {
})
})
Describe("RetrieveReceiptsByBlockHash", func() {
It("Retrieves all CIDs for the given blocknumber when provided an open filter", func() {
})
})
Describe("RetrieveReceiptsByBlockNumber", func() {
It("Retrieves all CIDs for the given blocknumber when provided an open filter", func() {
})
})
Describe("RetrieveReceiptByHash", func() {
It("Retrieves all CIDs for the given blocknumber when provided an open filter", func() {
})
})
Describe("RetrieveAccountByAddressAndBlockHash", func() {
It("Retrieves all CIDs for the given blocknumber when provided an open filter", func() {
})
})
Describe("RetrieveAccountByAddressAndBlockNumber", func() {
It("Retrieves all CIDs for the given blocknumber when provided an open filter", func() {
})
})
})
*/
+16 -9
View File
@@ -37,12 +37,19 @@ var (
TestBankFunds = big.NewInt(100000000)
Genesis = core.GenesisBlockForTesting(Testdb, TestBankAddress, TestBankFunds)
Account1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
Account2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
Account1Addr = crypto.PubkeyToAddress(Account1Key.PublicKey) //0x703c4b2bD70c169f5717101CaeE543299Fc946C7
Account2Addr = crypto.PubkeyToAddress(Account2Key.PublicKey) //0x0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e
ContractCode = common.Hex2Bytes("608060405234801561001057600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600180819055506101e2806100676000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806343d726d61461004657806365f3c31a1461005057806373d4a13a1461007e575b600080fd5b61004e61009c565b005b61007c6004803603602081101561006657600080fd5b810190808035906020019092919050505061017b565b005b610086610185565b6040518082815260200191505060405180910390f35b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610141576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602281526020018061018c6022913960400191505060405180910390fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b8060018190555050565b6001548156fe4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f6e2ea265627a7a723158205ba91466129f45285f53176d805117208c231ec6343d7896790e6fc4165b802b64736f6c63430005110032")
ContractAddr common.Address
Account1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
Account2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
Account1Addr = crypto.PubkeyToAddress(Account1Key.PublicKey) //0x703c4b2bD70c169f5717101CaeE543299Fc946C7
Account2Addr = crypto.PubkeyToAddress(Account2Key.PublicKey) //0x0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e
DeploymentTxData = common.Hex2Bytes("608060405234801561001057600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600180819055506101e2806100676000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806343d726d61461004657806365f3c31a1461005057806373d4a13a1461007e575b600080fd5b61004e61009c565b005b61007c6004803603602081101561006657600080fd5b810190808035906020019092919050505061017b565b005b610086610185565b6040518082815260200191505060405180910390f35b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610141576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602281526020018061018c6022913960400191505060405180910390fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b8060018190555050565b6001548156fe4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f6e2ea265627a7a723158205ba91466129f45285f53176d805117208c231ec6343d7896790e6fc4165b802b64736f6c63430005110032")
ContractCode = common.Hex2Bytes("608060405234801561001057600080fd5b50600436106100415760003560e01c806343d726d61461004657806365f3c31a1461005057806373d4a13a1461007e575b600080fd5b61004e61009c565b005b61007c6004803603602081101561006657600080fd5b810190808035906020019092919050505061017b565b005b610086610185565b6040518082815260200191505060405180910390f35b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610141576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602281526020018061018c6022913960400191505060405180910390fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b8060018190555050565b6001548156fe4f6e6c79206f776e65722063616e2063616c6c20746869732066756e6374696f6e2ea265627a7a723158205ba91466129f45285f53176d805117208c231ec6343d7896790e6fc4165b802b64736f6c63430005110032")
CodeHash = crypto.Keccak256Hash(ContractCode)
ContractAddr common.Address
IndexZero = "0000000000000000000000000000000000000000000000000000000000000000"
IndexOne = "0000000000000000000000000000000000000000000000000000000000000001"
ContractSlotPosition = common.FromHex(IndexOne)
ContractSlotKeyHash = crypto.Keccak256Hash(ContractSlotPosition)
MiningReward = big.NewInt(2000000000000000000)
)
/* test function signatures
@@ -75,7 +82,7 @@ func TestChainGen(i int, block *core.BlockGen) {
nonce := block.TxNonce(Account1Addr)
tx2, _ := types.SignTx(types.NewTransaction(nonce, Account2Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, Account1Key)
nonce++
tx3, _ := types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), 1000000, big.NewInt(0), ContractCode), signer, Account1Key)
tx3, _ := types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), 1000000, big.NewInt(0), DeploymentTxData), signer, Account1Key)
ContractAddr = crypto.CreateAddress(Account1Addr, nonce)
block.AddTx(tx1)
block.AddTx(tx2)
@@ -88,8 +95,8 @@ func TestChainGen(i int, block *core.BlockGen) {
case 3:
block.SetCoinbase(Account2Addr)
data := common.Hex2Bytes("65F3C31A0000000000000000000000000000000000000000000000000000000000000009")
tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(TestBankAddress), ContractAddr, big.NewInt(0), 100000, nil, data), signer, TestBankKey)
block.AddTx(tx1)
tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(TestBankAddress), ContractAddr, big.NewInt(0), 100000, nil, data), signer, TestBankKey)
block.AddTx(tx)
case 4:
block.SetCoinbase(Account1Addr)
data := common.Hex2Bytes("65F3C31A0000000000000000000000000000000000000000000000000000000000000000")
+59 -18
View File
@@ -57,19 +57,51 @@ var (
Extra: []byte{},
}
MockTransactions, MockReceipts, SenderAddr = createTransactionsAndReceipts()
ReceiptsRlp, _ = rlp.EncodeToBytes(MockReceipts)
MockBlock = types.NewBlock(&MockHeader, MockTransactions, nil, MockReceipts)
MockHeaderRlp, _ = rlp.EncodeToBytes(MockBlock.Header())
Address = common.HexToAddress("0xaE9BEa628c4Ce503DcFD7E305CaB4e29E7476592")
AnotherAddress = common.HexToAddress("0xaE9BEa628c4Ce503DcFD7E305CaB4e29E7476593")
ContractAddress = crypto.CreateAddress(SenderAddr, MockTransactions[2].Nonce())
ContractHash = crypto.Keccak256Hash(ContractAddress.Bytes()).String()
MockContractByteCode = []byte{0, 1, 2, 3, 4, 5}
mockTopic11 = common.HexToHash("0x04")
mockTopic12 = common.HexToHash("0x06")
mockTopic21 = common.HexToHash("0x05")
mockTopic22 = common.HexToHash("0x07")
MockLog1 = &types.Log{
MockUncles = []*types.Header{
{
Time: 1,
Number: new(big.Int).Add(BlockNumber, big.NewInt(1)),
Root: common.HexToHash("0x1"),
TxHash: common.HexToHash("0x1"),
ReceiptHash: common.HexToHash("0x1"),
Difficulty: big.NewInt(500001),
Extra: []byte{},
},
{
Time: 2,
Number: new(big.Int).Add(BlockNumber, big.NewInt(2)),
Root: common.HexToHash("0x2"),
TxHash: common.HexToHash("0x2"),
ReceiptHash: common.HexToHash("0x2"),
Difficulty: big.NewInt(500002),
Extra: []byte{},
},
}
ReceiptsRlp, _ = rlp.EncodeToBytes(MockReceipts)
MockBlock = types.NewBlock(&MockHeader, MockTransactions, MockUncles, MockReceipts)
MockHeaderRlp, _ = rlp.EncodeToBytes(MockBlock.Header())
MockChildHeader = types.Header{
Time: 0,
Number: new(big.Int).Add(BlockNumber, common.Big1),
Root: common.HexToHash("0x0"),
TxHash: common.HexToHash("0x0"),
ReceiptHash: common.HexToHash("0x0"),
Difficulty: big.NewInt(5000001),
Extra: []byte{},
ParentHash: MockBlock.Header().Hash(),
}
MockChild = types.NewBlock(&MockChildHeader, MockTransactions, MockUncles, MockReceipts)
MockChildRlp, _ = rlp.EncodeToBytes(MockChild.Header())
Address = common.HexToAddress("0xaE9BEa628c4Ce503DcFD7E305CaB4e29E7476592")
AnotherAddress = common.HexToAddress("0xaE9BEa628c4Ce503DcFD7E305CaB4e29E7476593")
ContractAddress = crypto.CreateAddress(SenderAddr, MockTransactions[2].Nonce())
ContractHash = crypto.Keccak256Hash(ContractAddress.Bytes()).String()
MockContractByteCode = []byte{0, 1, 2, 3, 4, 5}
mockTopic11 = common.HexToHash("0x04")
mockTopic12 = common.HexToHash("0x06")
mockTopic21 = common.HexToHash("0x05")
mockTopic22 = common.HexToHash("0x07")
MockLog1 = &types.Log{
Address: Address,
Topics: []common.Hash{mockTopic11, mockTopic12},
Data: []byte{},
@@ -239,7 +271,7 @@ var (
// statediff data
storageLocation = common.HexToHash("0")
StorageLeafKey = crypto.Keccak256Hash(storageLocation[:]).Bytes()
StorageValue = common.Hex2Bytes("01")
StorageValue = crypto.Keccak256([]byte{1, 2, 3, 4, 5})
StoragePartialPath = common.Hex2Bytes("20290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563")
StorageLeafNode, _ = rlp.EncodeToBytes([]interface{}{
StoragePartialPath,
@@ -248,7 +280,7 @@ var (
nonce1 = uint64(1)
ContractRoot = "0x821e2556a290c86405f8160a2d662042a431ba456b9db265c79bb837c04be5f0"
ContractCodeHash = common.HexToHash("0x753f98a8d4328b15636e46f66f2cb4bc860100aa17967cc145fcd17d1d4710ea")
ContractCodeHash = crypto.Keccak256Hash(MockContractByteCode)
contractPath = common.Bytes2Hex([]byte{'\x06'})
ContractLeafKey = testhelpers.AddressToLeafKey(ContractAddress)
ContractAccount, _ = rlp.EncodeToBytes(state.Account{
@@ -264,14 +296,14 @@ var (
})
nonce0 = uint64(0)
AccountBalance = big.NewInt(1000)
AccountRoot = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
AccountCodeHash = common.HexToHash("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")
accountPath = common.Bytes2Hex([]byte{'\x0c'})
AccountAddresss = common.HexToAddress("0x0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e")
AccountLeafKey = testhelpers.Account2LeafKey
Account, _ = rlp.EncodeToBytes(state.Account{
Nonce: nonce0,
Balance: big.NewInt(1000),
Balance: AccountBalance,
CodeHash: AccountCodeHash.Bytes(),
Root: common.HexToHash(AccountRoot),
})
@@ -331,6 +363,15 @@ var (
StorageNodes: MockStorageNodes,
StateNodes: MockStateNodes,
}
MockConvertedPayloadForChild = eth.ConvertedPayload{
TotalDifficulty: MockChild.Difficulty(),
Block: MockChild,
Receipts: MockReceipts,
TxMetaData: MockTrxMeta,
ReceiptMetaData: MockRctMeta,
StorageNodes: MockStorageNodes,
StateNodes: MockStateNodes,
}
MockCIDWrapper = &eth2.CIDWrapper{
BlockNumber: new(big.Int).Set(BlockNumber),
@@ -341,7 +382,7 @@ var (
CID: HeaderCID.String(),
MhKey: HeaderMhKey,
TotalDifficulty: MockBlock.Difficulty().String(),
Reward: "5000000000000000000",
Reward: "5312500000000000000",
StateRoot: MockBlock.Root().String(),
RctRoot: MockBlock.ReceiptHash().String(),
TxRoot: MockBlock.TxHash().String(),
+79
View File
@@ -20,11 +20,90 @@ import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/statediff"
"github.com/vulcanize/ipld-eth-indexer/pkg/eth"
"github.com/vulcanize/ipld-eth-indexer/pkg/ipfs"
)
// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction
type RPCTransaction struct {
BlockHash *common.Hash `json:"blockHash"`
BlockNumber *hexutil.Big `json:"blockNumber"`
From common.Address `json:"from"`
Gas hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"`
Hash common.Hash `json:"hash"`
Input hexutil.Bytes `json:"input"`
Nonce hexutil.Uint64 `json:"nonce"`
To *common.Address `json:"to"`
TransactionIndex *hexutil.Uint64 `json:"transactionIndex"`
Value *hexutil.Big `json:"value"`
V *hexutil.Big `json:"v"`
R *hexutil.Big `json:"r"`
S *hexutil.Big `json:"s"`
}
// RPCReceipt represents a receipt that will serialize to the RPC representation of a receipt
type RPCReceipt struct {
BlockHash *common.Hash `json:"blockHash"`
BlockNumber *hexutil.Big `json:"blockNumber"`
TransactionHash *common.Hash `json:"transactionHash"`
TransactionIndex *hexutil.Uint64 `json:"transactionIndex"`
From common.Address `json:"from"`
To *common.Address `json:"to"`
GasUsed hexutil.Uint64 `json:"gasUsed"`
CumulativeGsUsed hexutil.Uint64 `json:"cumulativeGasUsed"`
ContractAddress *common.Address `json:"contractAddress"`
Logs []*types.Log `json:"logs"`
Bloom types.Bloom `json:"logsBloom"`
Root []byte `json:"root"`
Status uint64 `json:"status"`
}
// AccountResult struct for GetProof
type AccountResult struct {
Address common.Address `json:"address"`
AccountProof []string `json:"accountProof"`
Balance *hexutil.Big `json:"balance"`
CodeHash common.Hash `json:"codeHash"`
Nonce hexutil.Uint64 `json:"nonce"`
StorageHash common.Hash `json:"storageHash"`
StorageProof []StorageResult `json:"storageProof"`
}
// StorageResult for GetProof
type StorageResult struct {
Key string `json:"key"`
Value *hexutil.Big `json:"value"`
Proof []string `json:"proof"`
}
// CallArgs represents the arguments for a call.
type CallArgs struct {
From *common.Address `json:"from"`
To *common.Address `json:"to"`
Gas *hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"`
Value *hexutil.Big `json:"value"`
Data *hexutil.Bytes `json:"data"`
}
// account indicates the overriding fields of account during the execution of
// a message call.
// Note, state and stateDiff can't be specified at the same time. If state is
// set, message execution will only use the data in the given state. Otherwise
// if statDiff is set, all diff will be applied first and then execute the call
// message.
type account struct {
Nonce *hexutil.Uint64 `json:"nonce"`
Code *hexutil.Bytes `json:"code"`
Balance **hexutil.Big `json:"balance"`
State *map[common.Hash]common.Hash `json:"state"`
StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
}
// IPLDs is used to package raw IPLD block data fetched from IPFS and returned by the server
// Returned by IPLDFetcher and ResponseFilterer
type IPLDs struct {
+114
View File
@@ -0,0 +1,114 @@
// VulcanizeDB
// Copyright © 2020 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package graphql
import (
"bytes"
"fmt"
"net/http"
)
// GraphiQL is an in-browser IDE for exploring GraphiQL APIs.
// This handler returns GraphiQL when requested.
//
// For more information, see https://github.com/graphql/graphiql.
type GraphiQL struct{}
func respond(w http.ResponseWriter, body []byte, code int) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(code)
_, _ = w.Write(body)
}
func errorJSON(msg string) []byte {
buf := bytes.Buffer{}
fmt.Fprintf(&buf, `{"error": "%s"}`, msg)
return buf.Bytes()
}
func (h GraphiQL) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
respond(w, errorJSON("only GET requests are supported"), http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "text/html")
w.Write(graphiql)
}
var graphiql = []byte(`
<!DOCTYPE html>
<html>
<head>
<link
rel="icon"
type="image/png"
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAActpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+QWRvYmUgSW1hZ2VSZWFkeTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KKS7NPQAAB5FJREFUWAm1FmtsnEdxdr/vfC8/mpgEfHYa6gaUJqAihfhVO7UprSokHsn5jKgKiKLGIIEEbSlpJdQLIJw+UFFQUSuBWir1z9nnpEmgCUnkcxPSmDRCgkKpGoJpfXdxHSc4ftzr2x1m9rvPPQdDDSgrfd/O7szOe2YX4H8cGEtY3tFK2Nu7pjMCChbgzVfD11h4XLKAibahL6dbBv+SaRl6LUsw78XBxTG80mEsWSkxu1oM9qmJlkR7UPhPWSDJCzSISw5zXZGxvpMezUp5GmtWQszunpiAKiPPZ20KyCqY1/ncgs4v+IUPwLJvYhzTVIbmvXgvqwAxkImKJHt1yzM+AQLXvdKXy3QevB4R+3O6wIYHSUCIlABEtTO9bf86pmFa7B6xPeHMi3l668p5SQjInbRGQQw0E3FMH4FHaFPoP8USVaveEo9aaH3LsdRh2vsYKqwhMhRBKw82vGbNQbcC9ePL1+PDmwf7iix0N+xmPoafq4TgDDaRYxmLCrBwD5HpSK4vKRVeP9b3ZyaaaE18UaL4KYE5x5afsWxoBgefFfX+jX6pMH9RvSnX2v1YxPP4D3UAHG2hgm80vRp7ns9nWxOb8kIt3HD6C+O8rpRVoYCxHDOtQwOg4QHS1kIb9oHGVQJlN0h8qPF07FFmkG4byouAjEdSO/bwOntr8kGt8EeNJ3uN27O37fse5PT3lVIjUsrL6MB2IVCThMcbx3ofIt7sZeMFExeTubSR3Zq4tVoEdhHSJs30WqjbIS1Zk6/VqzzhmdbBpyn5p1g4W8LMGkajj9GUSfcM/4IVaji+/QdOa7hehKz69xEPsllLkFZY+HdlWhOdLNxrXm5iTK1xPSHEeo4KxTFPzEsFLHH8D914rG+GGWe2Dd9UJav6ZbW1k9ep7rgF3SnTEUXA3hko2fdkowc2M27dk3deomgfLBIPYlJytC4QLzKLZdAoy3QzNTVqksT2y6Oz+YVL1TK4Oo9FYAVIkRFzgH8F/bOiD0cjv4m+hEA9IdXn8HaC4Mjxzx7OdCZH8R14mra6eB9sfUKTj4SCQLUvCHMqN235rKMGV5ZpPCAoSzGOcs2JaFZYVuc8FF5XQl8uCHV75FT0ZT6Q6Ry+02fZ3b7agLF+MGbYmF/Mg+vE14NY1Xnhjv2fZkTkWO+R2VXqc1BrLczp/OtULV0fOLXjHS5LlvkuhzL05oZf+xnMbtv3BLXZIwyPQNx4iRLvrXRXci/vcV/guXJ4dZ/elnwqfctQlnFxoGyhkY2+eCbTlnyCYU8GwzzcHHBhmKl7261X1CEBaIT0QNxJdyQfpLRdHblt4wNMeuhsVpWPvDulqAXQKH5i9f0Ut7pMT/LhOEWc96hfkBEYYnhDU3DJ2SUKMAEPIagRoTSJObF9uF5oHAC/uF/ENxeRrPcai0vt/k1mE+6GeE9eVIlvQwF+yGfL/KiNuMpUnmF4WQUYwX3AEEzjXmqi5yOp6DO8hrM7TeIZ+Orf2X6DY1oU+FeY1D8xJLh8G2bcsgpQ3vqoAU1P3nWouQaDd8mQdS8Tj1B/Z0sZXm6QyxbvAFlj3Us95e7Jbx6/EYScpnP/kjfMwy3DMre6mXVGIVTqiqi1mtVk8blZR78UOdGbQqDLheLMjWc54Yt7KSAaUvRwTyrdMXREvFF6VtRZfgrALNOcm8ixZxe9uOgBLsMPnftUIdM+tBFKcLtwxCeJ7GbdHDJlJ6DHYetX8gHfSTTEB4P9WNBb5JRq0VrfwbxZRuVN61pMt56ICz3elWxAB18OS//Nep4MKeowTOU/zMwo8RaV5fVKhs4WN1DzCjkzJV1jBT9K1TB6oWN4bR89arDMz7iTa1ikepxsy+CXqmXol1fUfJ4qwUfeptsXL1JNTFNWXkfmO5ydi8KXBIMWvCYnmbOWmKXr5zpZhHotSbQGp9YO+qkb3h05E3vBk+nmwJopw5SSdVxRsOjiCGhEXSMCMFdTrAdbPikul35PvWAN1adPgqAGz8Kk1FLTX2hlCyF9pHSIQlwnp+x6/yb1t9zu8LgFszJHt5v0K+TakuPmbFnmog2cXBzfbFtyj1b6O4SQ4BP76Zr1k1Etwoe7Ir+N/dwcfo8f3QnbsYR7yAO/kxICdAH1En+km/WxhtPRXZ4sZrOoQBk2npjcmmwu2ipMz6s/MlG6JflVqrC9pN8VqLK+1nhix4u8/3Z7YjXPRHeJ52z3vm7Mq6eISa0UeF/DK7FB3r/w8eGP0Htg4f1noud5TXgy1g1lpQIGQelGyLjbQk3J7TZr8yT7uxzwSfu+oiwdIL//gTKc+4MUltxL/lpPFn+ebvqByFhswAjid+VgTLNnXcGcyHGuY7PmvWUHZ2hlqXgXDRNfbD/YSE+2MeeWYzjZMmw+p+MYpnuSJy/FjtZ5DCvPuI9SFv5/DI4buZxfwZBuH7pnpu0QprcOztM3N9v2K8x2DH+FcZktB/nSWeJZ3v93Y8VasRubmqBoGKF4g6oBwjIQoi/MMDrqHOMamnMFmv6ziw0T97diTb0zHB7OEe4ZlCjf5X2U8vGm09HnKrPbo78mMwu6mjFn9tV713TtvWpZSCX83wr9J1EKd8CrhC26AAAAAElFTkSuQmCC"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.13.0/graphiql.css"
integrity="sha384-Qua2xoKBxcHOg1ivsKWo98zSI5KD/UuBpzMIg8coBd4/jGYoxeozCYFI9fesatT0"
crossorigin="anonymous"
/>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/fetch/3.0.0/fetch.min.js"
integrity="sha384-5B8/4F9AQqp/HCHReGLSOWbyAOwnJsPrvx6C0+VPUr44Olzi99zYT1xbVh+ZanQJ"
crossorigin="anonymous"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.5/umd/react.production.min.js"
integrity="sha384-dOCiLz3nZfHiJj//EWxjwSKSC6Z1IJtyIEK/b/xlHVNdVLXDYSesoxiZb94bbuGE"
crossorigin="anonymous"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.5/umd/react-dom.production.min.js"
integrity="sha384-QI+ql5f+khgo3mMdCktQ3E7wUKbIpuQo8S5rA/3i1jg2rMsloCNyiZclI7sFQUGN"
crossorigin="anonymous"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.13.0/graphiql.min.js"
integrity="sha384-roSmzNmO4zJK9X4lwggDi4/oVy+9V4nlS1+MN8Taj7tftJy1GvMWyAhTNXdC/fFR"
crossorigin="anonymous"
></script>
</head>
<body style="width: 100%; height: 100%; margin: 0; overflow: hidden;">
<div id="graphiql" style="height: 100vh;">Loading...</div>
<script>
function fetchGQL(params) {
return fetch("/graphql", {
method: "post",
body: JSON.stringify(params),
credentials: "include",
}).then(function (resp) {
return resp.text();
}).then(function (body) {
try {
return JSON.parse(body);
} catch (error) {
return body;
}
});
}
ReactDOM.render(
React.createElement(GraphiQL, {fetcher: fetchGQL}),
document.getElementById("graphiql")
)
</script>
</body>
</html>
`)
+942
View File
@@ -0,0 +1,942 @@
// VulcanizeDB
// Copyright © 2020 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Package graphql provides a GraphQL interface to Ethereum node data.
package graphql
import (
"context"
"errors"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"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/eth/filters"
"github.com/ethereum/go-ethereum/rpc"
"github.com/vulcanize/ipld-eth-server/pkg/eth"
)
var (
errBlockInvariant = errors.New("block objects must be instantiated with at least one of num or hash")
)
// Account represents an Ethereum account at a particular block.
type Account struct {
backend *eth.Backend
address common.Address
blockNrOrHash rpc.BlockNumberOrHash
}
// getState fetches the StateDB object for an account.
func (a *Account) getState(ctx context.Context) (*state.StateDB, error) {
state, _, err := a.backend.StateAndHeaderByNumberOrHash(ctx, a.blockNrOrHash)
return state, err
}
func (a *Account) Address(ctx context.Context) (common.Address, error) {
return a.address, nil
}
func (a *Account) Balance(ctx context.Context) (hexutil.Big, error) {
state, err := a.getState(ctx)
if err != nil {
return hexutil.Big{}, err
}
return hexutil.Big(*state.GetBalance(a.address)), nil
}
func (a *Account) TransactionCount(ctx context.Context) (hexutil.Uint64, error) {
state, err := a.getState(ctx)
if err != nil {
return 0, err
}
return hexutil.Uint64(state.GetNonce(a.address)), nil
}
func (a *Account) Code(ctx context.Context) (hexutil.Bytes, error) {
state, err := a.getState(ctx)
if err != nil {
return hexutil.Bytes{}, err
}
return hexutil.Bytes(state.GetCode(a.address)), nil
}
func (a *Account) Storage(ctx context.Context, args struct{ Slot common.Hash }) (common.Hash, error) {
state, err := a.getState(ctx)
if err != nil {
return common.Hash{}, err
}
return state.GetState(a.address, args.Slot), nil
}
// Log represents an individual log message. All arguments are mandatory.
type Log struct {
backend *eth.Backend
transaction *Transaction
log *types.Log
}
func (l *Log) Transaction(ctx context.Context) *Transaction {
return l.transaction
}
func (l *Log) Account(ctx context.Context, args BlockNumberArgs) *Account {
return &Account{
backend: l.backend,
address: l.log.Address,
blockNrOrHash: args.NumberOrLatest(),
}
}
func (l *Log) Index(ctx context.Context) int32 {
return int32(l.log.Index)
}
func (l *Log) Topics(ctx context.Context) []common.Hash {
return l.log.Topics
}
func (l *Log) Data(ctx context.Context) hexutil.Bytes {
return hexutil.Bytes(l.log.Data)
}
// Transaction represents an Ethereum transaction.
// backend and hash are mandatory; all others will be fetched when required.
type Transaction struct {
backend *eth.Backend
hash common.Hash
tx *types.Transaction
block *Block
index uint64
}
// resolve returns the internal transaction object, fetching it if needed.
func (t *Transaction) resolve(ctx context.Context) (*types.Transaction, error) {
if t.tx == nil {
tx, blockHash, _, index := rawdb.ReadTransaction(t.backend.ChainDb(), t.hash)
if tx != nil {
t.tx = tx
blockNrOrHash := rpc.BlockNumberOrHashWithHash(blockHash, false)
t.block = &Block{
backend: t.backend,
numberOrHash: &blockNrOrHash,
}
t.index = index
}
}
return t.tx, nil
}
func (t *Transaction) Hash(ctx context.Context) common.Hash {
return t.hash
}
func (t *Transaction) InputData(ctx context.Context) (hexutil.Bytes, error) {
tx, err := t.resolve(ctx)
if err != nil || tx == nil {
return hexutil.Bytes{}, err
}
return hexutil.Bytes(tx.Data()), nil
}
func (t *Transaction) Gas(ctx context.Context) (hexutil.Uint64, error) {
tx, err := t.resolve(ctx)
if err != nil || tx == nil {
return 0, err
}
return hexutil.Uint64(tx.Gas()), nil
}
func (t *Transaction) GasPrice(ctx context.Context) (hexutil.Big, error) {
tx, err := t.resolve(ctx)
if err != nil || tx == nil {
return hexutil.Big{}, err
}
return hexutil.Big(*tx.GasPrice()), nil
}
func (t *Transaction) Value(ctx context.Context) (hexutil.Big, error) {
tx, err := t.resolve(ctx)
if err != nil || tx == nil {
return hexutil.Big{}, err
}
return hexutil.Big(*tx.Value()), nil
}
func (t *Transaction) Nonce(ctx context.Context) (hexutil.Uint64, error) {
tx, err := t.resolve(ctx)
if err != nil || tx == nil {
return 0, err
}
return hexutil.Uint64(tx.Nonce()), nil
}
func (t *Transaction) To(ctx context.Context, args BlockNumberArgs) (*Account, error) {
tx, err := t.resolve(ctx)
if err != nil || tx == nil {
return nil, err
}
to := tx.To()
if to == nil {
return nil, nil
}
return &Account{
backend: t.backend,
address: *to,
blockNrOrHash: args.NumberOrLatest(),
}, nil
}
func (t *Transaction) From(ctx context.Context, args BlockNumberArgs) (*Account, error) {
tx, err := t.resolve(ctx)
if err != nil || tx == nil {
return nil, err
}
var signer types.Signer = types.HomesteadSigner{}
if tx.Protected() {
signer = types.NewEIP155Signer(tx.ChainId())
}
from, _ := types.Sender(signer, tx)
return &Account{
backend: t.backend,
address: from,
blockNrOrHash: args.NumberOrLatest(),
}, nil
}
func (t *Transaction) Block(ctx context.Context) (*Block, error) {
if _, err := t.resolve(ctx); err != nil {
return nil, err
}
return t.block, nil
}
func (t *Transaction) Index(ctx context.Context) (*int32, error) {
if _, err := t.resolve(ctx); err != nil {
return nil, err
}
if t.block == nil {
return nil, nil
}
index := int32(t.index)
return &index, nil
}
// getReceipt returns the receipt associated with this transaction, if any.
func (t *Transaction) getReceipt(ctx context.Context) (*types.Receipt, error) {
if _, err := t.resolve(ctx); err != nil {
return nil, err
}
if t.block == nil {
return nil, nil
}
receipts, err := t.block.resolveReceipts(ctx)
if err != nil {
return nil, err
}
return receipts[t.index], nil
}
func (t *Transaction) Status(ctx context.Context) (*hexutil.Uint64, error) {
receipt, err := t.getReceipt(ctx)
if err != nil || receipt == nil {
return nil, err
}
ret := hexutil.Uint64(receipt.Status)
return &ret, nil
}
func (t *Transaction) GasUsed(ctx context.Context) (*hexutil.Uint64, error) {
receipt, err := t.getReceipt(ctx)
if err != nil || receipt == nil {
return nil, err
}
ret := hexutil.Uint64(receipt.GasUsed)
return &ret, nil
}
func (t *Transaction) CumulativeGasUsed(ctx context.Context) (*hexutil.Uint64, error) {
receipt, err := t.getReceipt(ctx)
if err != nil || receipt == nil {
return nil, err
}
ret := hexutil.Uint64(receipt.CumulativeGasUsed)
return &ret, nil
}
func (t *Transaction) CreatedContract(ctx context.Context, args BlockNumberArgs) (*Account, error) {
receipt, err := t.getReceipt(ctx)
if err != nil || receipt == nil || receipt.ContractAddress == (common.Address{}) {
return nil, err
}
return &Account{
backend: t.backend,
address: receipt.ContractAddress,
blockNrOrHash: args.NumberOrLatest(),
}, nil
}
func (t *Transaction) Logs(ctx context.Context) (*[]*Log, error) {
receipt, err := t.getReceipt(ctx)
if err != nil || receipt == nil {
return nil, err
}
ret := make([]*Log, 0, len(receipt.Logs))
for _, log := range receipt.Logs {
ret = append(ret, &Log{
backend: t.backend,
transaction: t,
log: log,
})
}
return &ret, nil
}
func (t *Transaction) R(ctx context.Context) (hexutil.Big, error) {
tx, err := t.resolve(ctx)
if err != nil || tx == nil {
return hexutil.Big{}, err
}
_, r, _ := tx.RawSignatureValues()
return hexutil.Big(*r), nil
}
func (t *Transaction) S(ctx context.Context) (hexutil.Big, error) {
tx, err := t.resolve(ctx)
if err != nil || tx == nil {
return hexutil.Big{}, err
}
_, _, s := tx.RawSignatureValues()
return hexutil.Big(*s), nil
}
func (t *Transaction) V(ctx context.Context) (hexutil.Big, error) {
tx, err := t.resolve(ctx)
if err != nil || tx == nil {
return hexutil.Big{}, err
}
v, _, _ := tx.RawSignatureValues()
return hexutil.Big(*v), nil
}
type BlockType int
// Block represents an Ethereum block.
// backend, and numberOrHash are mandatory. All other fields are lazily fetched
// when required.
type Block struct {
backend *eth.Backend
numberOrHash *rpc.BlockNumberOrHash
hash common.Hash
header *types.Header
block *types.Block
receipts []*types.Receipt
}
// resolve returns the internal Block object representing this block, fetching
// it if necessary.
func (b *Block) resolve(ctx context.Context) (*types.Block, error) {
if b.block != nil {
return b.block, nil
}
if b.numberOrHash == nil {
latest := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
b.numberOrHash = &latest
}
var err error
b.block, err = b.backend.BlockByNumberOrHash(ctx, *b.numberOrHash)
if b.block != nil && b.header == nil {
b.header = b.block.Header()
if hash, ok := b.numberOrHash.Hash(); ok {
b.hash = hash
}
}
return b.block, err
}
// resolveHeader returns the internal Header object for this block, fetching it
// if necessary. Call this function instead of `resolve` unless you need the
// additional data (transactions and uncles).
func (b *Block) resolveHeader(ctx context.Context) (*types.Header, error) {
if b.numberOrHash == nil && b.hash == (common.Hash{}) {
return nil, errBlockInvariant
}
var err error
if b.header == nil {
if b.hash != (common.Hash{}) {
b.header, err = b.backend.HeaderByHash(ctx, b.hash)
} else {
b.header, err = b.backend.HeaderByNumberOrHash(ctx, *b.numberOrHash)
}
}
return b.header, err
}
// resolveReceipts returns the list of receipts for this block, fetching them
// if necessary.
func (b *Block) resolveReceipts(ctx context.Context) ([]*types.Receipt, error) {
if b.receipts == nil {
hash := b.hash
if hash == (common.Hash{}) {
header, err := b.resolveHeader(ctx)
if err != nil {
return nil, err
}
hash = header.Hash()
}
receipts, err := b.backend.GetReceipts(ctx, hash)
if err != nil {
return nil, err
}
b.receipts = []*types.Receipt(receipts)
}
return b.receipts, nil
}
func (b *Block) Number(ctx context.Context) (hexutil.Uint64, error) {
header, err := b.resolveHeader(ctx)
if err != nil {
return 0, err
}
return hexutil.Uint64(header.Number.Uint64()), nil
}
func (b *Block) Hash(ctx context.Context) (common.Hash, error) {
if b.hash == (common.Hash{}) {
header, err := b.resolveHeader(ctx)
if err != nil {
return common.Hash{}, err
}
b.hash = header.Hash()
}
return b.hash, nil
}
func (b *Block) GasLimit(ctx context.Context) (hexutil.Uint64, error) {
header, err := b.resolveHeader(ctx)
if err != nil {
return 0, err
}
return hexutil.Uint64(header.GasLimit), nil
}
func (b *Block) GasUsed(ctx context.Context) (hexutil.Uint64, error) {
header, err := b.resolveHeader(ctx)
if err != nil {
return 0, err
}
return hexutil.Uint64(header.GasUsed), nil
}
func (b *Block) Parent(ctx context.Context) (*Block, error) {
// If the block header hasn't been fetched, and we'll need it, fetch it.
if b.numberOrHash == nil && b.header == nil {
if _, err := b.resolveHeader(ctx); err != nil {
return nil, err
}
}
if b.header != nil && b.header.Number.Uint64() > 0 {
num := rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(b.header.Number.Uint64() - 1))
return &Block{
backend: b.backend,
numberOrHash: &num,
hash: b.header.ParentHash,
}, nil
}
return nil, nil
}
func (b *Block) Difficulty(ctx context.Context) (hexutil.Big, error) {
header, err := b.resolveHeader(ctx)
if err != nil {
return hexutil.Big{}, err
}
return hexutil.Big(*header.Difficulty), nil
}
func (b *Block) Timestamp(ctx context.Context) (hexutil.Uint64, error) {
header, err := b.resolveHeader(ctx)
if err != nil {
return 0, err
}
return hexutil.Uint64(header.Time), nil
}
func (b *Block) Nonce(ctx context.Context) (hexutil.Bytes, error) {
header, err := b.resolveHeader(ctx)
if err != nil {
return hexutil.Bytes{}, err
}
return hexutil.Bytes(header.Nonce[:]), nil
}
func (b *Block) MixHash(ctx context.Context) (common.Hash, error) {
header, err := b.resolveHeader(ctx)
if err != nil {
return common.Hash{}, err
}
return header.MixDigest, nil
}
func (b *Block) TransactionsRoot(ctx context.Context) (common.Hash, error) {
header, err := b.resolveHeader(ctx)
if err != nil {
return common.Hash{}, err
}
return header.TxHash, nil
}
func (b *Block) StateRoot(ctx context.Context) (common.Hash, error) {
header, err := b.resolveHeader(ctx)
if err != nil {
return common.Hash{}, err
}
return header.Root, nil
}
func (b *Block) ReceiptsRoot(ctx context.Context) (common.Hash, error) {
header, err := b.resolveHeader(ctx)
if err != nil {
return common.Hash{}, err
}
return header.ReceiptHash, nil
}
func (b *Block) OmmerHash(ctx context.Context) (common.Hash, error) {
header, err := b.resolveHeader(ctx)
if err != nil {
return common.Hash{}, err
}
return header.UncleHash, nil
}
func (b *Block) OmmerCount(ctx context.Context) (*int32, error) {
block, err := b.resolve(ctx)
if err != nil || block == nil {
return nil, err
}
count := int32(len(block.Uncles()))
return &count, err
}
func (b *Block) Ommers(ctx context.Context) (*[]*Block, error) {
block, err := b.resolve(ctx)
if err != nil || block == nil {
return nil, err
}
ret := make([]*Block, 0, len(block.Uncles()))
for _, uncle := range block.Uncles() {
blockNumberOrHash := rpc.BlockNumberOrHashWithHash(uncle.Hash(), false)
ret = append(ret, &Block{
backend: b.backend,
numberOrHash: &blockNumberOrHash,
header: uncle,
})
}
return &ret, nil
}
func (b *Block) ExtraData(ctx context.Context) (hexutil.Bytes, error) {
header, err := b.resolveHeader(ctx)
if err != nil {
return hexutil.Bytes{}, err
}
return hexutil.Bytes(header.Extra), nil
}
func (b *Block) LogsBloom(ctx context.Context) (hexutil.Bytes, error) {
header, err := b.resolveHeader(ctx)
if err != nil {
return hexutil.Bytes{}, err
}
return hexutil.Bytes(header.Bloom.Bytes()), nil
}
func (b *Block) TotalDifficulty(ctx context.Context) (hexutil.Big, error) {
h := b.hash
if h == (common.Hash{}) {
header, err := b.resolveHeader(ctx)
if err != nil {
return hexutil.Big{}, err
}
h = header.Hash()
}
td, err := b.backend.GetTd(h)
if err != nil {
return hexutil.Big{}, err
}
return hexutil.Big(*td), nil
}
// BlockNumberArgs encapsulates arguments to accessors that specify a block number.
type BlockNumberArgs struct {
// TODO: Ideally we could use input unions to allow the query to specify the
// block parameter by hash, block number, or tag but input unions aren't part of the
// standard GraphQL schema SDL yet, see: https://github.com/graphql/graphql-spec/issues/488
Block *hexutil.Uint64
}
// NumberOr returns the provided block number argument, or the "current" block number or hash if none
// was provided.
func (a BlockNumberArgs) NumberOr(current rpc.BlockNumberOrHash) rpc.BlockNumberOrHash {
if a.Block != nil {
blockNr := rpc.BlockNumber(*a.Block)
return rpc.BlockNumberOrHashWithNumber(blockNr)
}
return current
}
// NumberOrLatest returns the provided block number argument, or the "latest" block number if none
// was provided.
func (a BlockNumberArgs) NumberOrLatest() rpc.BlockNumberOrHash {
return a.NumberOr(rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber))
}
func (b *Block) Miner(ctx context.Context, args BlockNumberArgs) (*Account, error) {
header, err := b.resolveHeader(ctx)
if err != nil {
return nil, err
}
return &Account{
backend: b.backend,
address: header.Coinbase,
blockNrOrHash: args.NumberOrLatest(),
}, nil
}
func (b *Block) TransactionCount(ctx context.Context) (*int32, error) {
block, err := b.resolve(ctx)
if err != nil || block == nil {
return nil, err
}
count := int32(len(block.Transactions()))
return &count, err
}
func (b *Block) Transactions(ctx context.Context) (*[]*Transaction, error) {
block, err := b.resolve(ctx)
if err != nil || block == nil {
return nil, err
}
ret := make([]*Transaction, 0, len(block.Transactions()))
for i, tx := range block.Transactions() {
ret = append(ret, &Transaction{
backend: b.backend,
hash: tx.Hash(),
tx: tx,
block: b,
index: uint64(i),
})
}
return &ret, nil
}
func (b *Block) TransactionAt(ctx context.Context, args struct{ Index int32 }) (*Transaction, error) {
block, err := b.resolve(ctx)
if err != nil || block == nil {
return nil, err
}
txs := block.Transactions()
if args.Index < 0 || int(args.Index) >= len(txs) {
return nil, nil
}
tx := txs[args.Index]
return &Transaction{
backend: b.backend,
hash: tx.Hash(),
tx: tx,
block: b,
index: uint64(args.Index),
}, nil
}
func (b *Block) OmmerAt(ctx context.Context, args struct{ Index int32 }) (*Block, error) {
block, err := b.resolve(ctx)
if err != nil || block == nil {
return nil, err
}
uncles := block.Uncles()
if args.Index < 0 || int(args.Index) >= len(uncles) {
return nil, nil
}
uncle := uncles[args.Index]
blockNumberOrHash := rpc.BlockNumberOrHashWithHash(uncle.Hash(), false)
return &Block{
backend: b.backend,
numberOrHash: &blockNumberOrHash,
header: uncle,
}, nil
}
// BlockFilterCriteria encapsulates criteria passed to a `logs` accessor inside
// a block.
type BlockFilterCriteria struct {
Addresses *[]common.Address // restricts matches to events created by specific contracts
// The Topic list restricts matches to particular event topics. Each event has a list
// of topics. Topics matches a prefix of that list. An empty element slice matches any
// topic. Non-empty elements represent an alternative that matches any of the
// contained topics.
//
// Examples:
// {} or nil matches any topic list
// {{A}} matches topic A in first position
// {{}, {B}} matches any topic in first position, B in second position
// {{A}, {B}} matches topic A in first position, B in second position
// {{A, B}}, {C, D}} matches topic (A OR B) in first position, (C OR D) in second position
Topics *[][]common.Hash
}
// runFilter accepts a filter and executes it, returning all its results as
// `Log` objects.
func runFilter(ctx context.Context, be *eth.Backend, filter *filters.Filter) ([]*Log, error) {
logs, err := filter.Logs(ctx)
if err != nil || logs == nil {
return nil, err
}
ret := make([]*Log, 0, len(logs))
for _, log := range logs {
ret = append(ret, &Log{
backend: be,
transaction: &Transaction{backend: be, hash: log.TxHash},
log: log,
})
}
return ret, nil
}
func (b *Block) Logs(ctx context.Context, args struct{ Filter BlockFilterCriteria }) ([]*Log, error) {
var addresses []common.Address
if args.Filter.Addresses != nil {
addresses = *args.Filter.Addresses
}
var topics [][]common.Hash
if args.Filter.Topics != nil {
topics = *args.Filter.Topics
}
hash := b.hash
if hash == (common.Hash{}) {
header, err := b.resolveHeader(ctx)
if err != nil {
return nil, err
}
hash = header.Hash()
}
// Construct the range filter
filter := filters.NewBlockFilter(b.backend, hash, addresses, topics)
// Run the filter and return all the logs
return runFilter(ctx, b.backend, filter)
}
func (b *Block) Account(ctx context.Context, args struct {
Address common.Address
}) (*Account, error) {
if b.numberOrHash == nil {
_, err := b.resolveHeader(ctx)
if err != nil {
return nil, err
}
}
return &Account{
backend: b.backend,
address: args.Address,
blockNrOrHash: *b.numberOrHash,
}, nil
}
// CallData encapsulates arguments to `call` or `estimateGas`.
// All arguments are optional.
type CallData struct {
From *common.Address // The Ethereum address the call is from.
To *common.Address // The Ethereum address the call is to.
Gas *hexutil.Uint64 // The amount of gas provided for the call.
GasPrice *hexutil.Big // The price of each unit of gas, in wei.
Value *hexutil.Big // The value sent along with the call.
Data *hexutil.Bytes // Any data sent with the call.
}
// CallResult encapsulates the result of an invocation of the `call` accessor.
type CallResult struct {
data hexutil.Bytes // The return data from the call
gasUsed hexutil.Uint64 // The amount of gas used
status hexutil.Uint64 // The return status of the call - 0 for failure or 1 for success.
}
func (c *CallResult) Data() hexutil.Bytes {
return c.data
}
func (c *CallResult) GasUsed() hexutil.Uint64 {
return c.gasUsed
}
func (c *CallResult) Status() hexutil.Uint64 {
return c.status
}
func (b *Block) Call(ctx context.Context, args struct {
Data eth.CallArgs
}) (*CallResult, error) {
if b.numberOrHash == nil {
_, err := b.resolve(ctx)
if err != nil {
return nil, err
}
}
result, gas, failed, err := eth.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, 5*time.Second, b.backend.RPCGasCap())
status := hexutil.Uint64(1)
if failed {
status = 0
}
return &CallResult{
data: hexutil.Bytes(result),
gasUsed: hexutil.Uint64(gas),
status: status,
}, err
}
// Resolver is the top-level object in the GraphQL hierarchy.
type Resolver struct {
backend *eth.Backend
}
func (r *Resolver) Block(ctx context.Context, args struct {
Number *hexutil.Uint64
Hash *common.Hash
}) (*Block, error) {
var block *Block
if args.Number != nil {
number := rpc.BlockNumber(uint64(*args.Number))
numberOrHash := rpc.BlockNumberOrHashWithNumber(number)
block = &Block{
backend: r.backend,
numberOrHash: &numberOrHash,
}
} else if args.Hash != nil {
numberOrHash := rpc.BlockNumberOrHashWithHash(*args.Hash, false)
block = &Block{
backend: r.backend,
numberOrHash: &numberOrHash,
}
} else {
numberOrHash := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
block = &Block{
backend: r.backend,
numberOrHash: &numberOrHash,
}
}
// Resolve the header, return nil if it doesn't exist.
// Note we don't resolve block directly here since it will require an
// additional network request for light client.
h, err := block.resolveHeader(ctx)
if err != nil {
return nil, err
} else if h == nil {
return nil, nil
}
return block, nil
}
func (r *Resolver) Blocks(ctx context.Context, args struct {
From hexutil.Uint64
To *hexutil.Uint64
}) ([]*Block, error) {
from := rpc.BlockNumber(args.From)
var to rpc.BlockNumber
if args.To != nil {
to = rpc.BlockNumber(*args.To)
} else {
to = rpc.BlockNumber(r.backend.CurrentBlock().Number().Int64())
}
if to < from {
return []*Block{}, nil
}
ret := make([]*Block, 0, to-from+1)
for i := from; i <= to; i++ {
numberOrHash := rpc.BlockNumberOrHashWithNumber(i)
ret = append(ret, &Block{
backend: r.backend,
numberOrHash: &numberOrHash,
})
}
return ret, nil
}
func (r *Resolver) Transaction(ctx context.Context, args struct{ Hash common.Hash }) (*Transaction, error) {
tx := &Transaction{
backend: r.backend,
hash: args.Hash,
}
// Resolve the transaction; if it doesn't exist, return nil.
t, err := tx.resolve(ctx)
if err != nil {
return nil, err
} else if t == nil {
return nil, nil
}
return tx, nil
}
// FilterCriteria encapsulates the arguments to `logs` on the root resolver object.
type FilterCriteria struct {
FromBlock *hexutil.Uint64 // beginning of the queried range, nil means genesis block
ToBlock *hexutil.Uint64 // end of the range, nil means latest block
Addresses *[]common.Address // restricts matches to events created by specific contracts
// The Topic list restricts matches to particular event topics. Each event has a list
// of topics. Topics matches a prefix of that list. An empty element slice matches any
// topic. Non-empty elements represent an alternative that matches any of the
// contained topics.
//
// Examples:
// {} or nil matches any topic list
// {{A}} matches topic A in first position
// {{}, {B}} matches any topic in first position, B in second position
// {{A}, {B}} matches topic A in first position, B in second position
// {{A, B}}, {C, D}} matches topic (A OR B) in first position, (C OR D) in second position
Topics *[][]common.Hash
}
func (r *Resolver) Logs(ctx context.Context, args struct{ Filter FilterCriteria }) ([]*Log, error) {
// Convert the RPC block numbers into internal representations
begin := rpc.LatestBlockNumber.Int64()
if args.Filter.FromBlock != nil {
begin = int64(*args.Filter.FromBlock)
}
end := rpc.LatestBlockNumber.Int64()
if args.Filter.ToBlock != nil {
end = int64(*args.Filter.ToBlock)
}
var addresses []common.Address
if args.Filter.Addresses != nil {
addresses = *args.Filter.Addresses
}
var topics [][]common.Hash
if args.Filter.Topics != nil {
topics = *args.Filter.Topics
}
// Construct the range filter
filter := filters.NewRangeFilter(filters.Backend(r.backend), begin, end, addresses, topics)
return runFilter(ctx, r.backend, filter)
}
+35
View File
@@ -0,0 +1,35 @@
// VulcanizeDB
// Copyright © 2020 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package graphql_test
import (
"io/ioutil"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/sirupsen/logrus"
)
func TestGraphQL(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "graphql test suite")
}
var _ = BeforeSuite(func() {
logrus.SetOutput(ioutil.Discard)
})
+31
View File
@@ -0,0 +1,31 @@
// VulcanizeDB
// Copyright © 2020 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package graphql_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"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)
Expect(err).ToNot(HaveOccurred())
})
})
+274
View File
@@ -0,0 +1,274 @@
// VulcanizeDB
// Copyright © 2020 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package graphql
const schema string = `
# Bytes32 is a 32 byte binary string, represented as 0x-prefixed hexadecimal.
scalar Bytes32
# Address is a 20 byte Ethereum address, represented as 0x-prefixed hexadecimal.
scalar Address
# Bytes is an arbitrary length binary string, represented as 0x-prefixed hexadecimal.
# An empty byte string is represented as '0x'. Byte strings must have an even number of hexadecimal nybbles.
scalar Bytes
# BigInt is a large integer. Input is accepted as either a JSON number or as a string.
# Strings may be either decimal or 0x-prefixed hexadecimal. Output values are all
# 0x-prefixed hexadecimal.
scalar BigInt
# Long is a 64 bit unsigned integer.
scalar Long
schema {
query: Query
}
# Account is an Ethereum account at a particular block.
type Account {
# Address is the address owning the account.
address: Address!
# Balance is the balance of the account, in wei.
balance: BigInt!
# TransactionCount is the number of transactions sent from this account,
# or in the case of a contract, the number of contracts created. Otherwise
# known as the nonce.
transactionCount: Long!
# Code contains the smart contract code for this account, if the account
# is a (non-self-destructed) contract.
code: Bytes!
# Storage provides access to the storage of a contract account, indexed
# by its 32 byte slot identifier.
storage(slot: Bytes32!): Bytes32!
}
# Log is an Ethereum event log.
type Log {
# Index is the index of this log in the block.
index: Int!
# Account is the account which generated this log - this will always
# be a contract account.
account(block: Long): Account!
# Topics is a list of 0-4 indexed topics for the log.
topics: [Bytes32!]!
# Data is unindexed data for this log.
data: Bytes!
# Transaction is the transaction that generated this log entry.
transaction: Transaction!
}
# Transaction is an Ethereum transaction.
type Transaction {
# Hash is the hash of this transaction.
hash: Bytes32!
# Nonce is the nonce of the account this transaction was generated with.
nonce: Long!
# Index is the index of this transaction in the parent block. This will
# be null if the transaction has not yet been mined.
index: Int
# From is the account that sent this transaction - this will always be
# an externally owned account.
from(block: Long): Account!
# To is the account the transaction was sent to. This is null for
# contract-creating transactions.
to(block: Long): Account
# Value is the value, in wei, sent along with this transaction.
value: BigInt!
# GasPrice is the price offered to miners for gas, in wei per unit.
gasPrice: BigInt!
# Gas is the maximum amount of gas this transaction can consume.
gas: Long!
# InputData is the data supplied to the target of the transaction.
inputData: Bytes!
# Block is the block this transaction was mined in. This will be null if
# the transaction has not yet been mined.
block: Block
# Status is the return status of the transaction. This will be 1 if the
# transaction succeeded, or 0 if it failed (due to a revert, or due to
# running out of gas). If the transaction has not yet been mined, this
# field will be null.
status: Long
# GasUsed is the amount of gas that was used processing this transaction.
# If the transaction has not yet been mined, this field will be null.
gasUsed: Long
# CumulativeGasUsed is the total gas used in the block up to and including
# this transaction. If the transaction has not yet been mined, this field
# will be null.
cumulativeGasUsed: Long
# CreatedContract is the account that was created by a contract creation
# transaction. If the transaction was not a contract creation transaction,
# or it has not yet been mined, this field will be null.
createdContract(block: Long): Account
# Logs is a list of log entries emitted by this transaction. If the
# transaction has not yet been mined, this field will be null.
logs: [Log!]
r: BigInt!
s: BigInt!
v: BigInt!
}
# BlockFilterCriteria encapsulates log filter criteria for a filter applied
# to a single block.
input BlockFilterCriteria {
# Addresses is list of addresses that are of interest. If this list is
# empty, results will not be filtered by address.
addresses: [Address!]
# Topics list restricts matches to particular event topics. Each event has a list
# of topics. Topics matches a prefix of that list. An empty element array matches any
# topic. Non-empty elements represent an alternative that matches any of the
# contained topics.
#
# Examples:
# - [] or nil matches any topic list
# - [[A]] matches topic A in first position
# - [[], [B]] matches any topic in first position, B in second position
# - [[A], [B]] matches topic A in first position, B in second position
# - [[A, B]], [C, D]] matches topic (A OR B) in first position, (C OR D) in second position
topics: [[Bytes32!]!]
}
# Block is an Ethereum block.
type Block {
# Number is the number of this block, starting at 0 for the genesis block.
number: Long!
# Hash is the block hash of this block.
hash: Bytes32!
# Parent is the parent block of this block.
parent: Block
# Nonce is the block nonce, an 8 byte sequence determined by the miner.
nonce: Bytes!
# TransactionsRoot is the keccak256 hash of the root of the trie of transactions in this block.
transactionsRoot: Bytes32!
# TransactionCount is the number of transactions in this block. if
# transactions are not available for this block, this field will be null.
transactionCount: Int
# StateRoot is the keccak256 hash of the state trie after this block was processed.
stateRoot: Bytes32!
# ReceiptsRoot is the keccak256 hash of the trie of transaction receipts in this block.
receiptsRoot: Bytes32!
# Miner is the account that mined this block.
miner(block: Long): Account!
# ExtraData is an arbitrary data field supplied by the miner.
extraData: Bytes!
# GasLimit is the maximum amount of gas that was available to transactions in this block.
gasLimit: Long!
# GasUsed is the amount of gas that was used executing transactions in this block.
gasUsed: Long!
# Timestamp is the unix timestamp at which this block was mined.
timestamp: Long!
# LogsBloom is a bloom filter that can be used to check if a block may
# contain log entries matching a filter.
logsBloom: Bytes!
# MixHash is the hash that was used as an input to the PoW process.
mixHash: Bytes32!
# Difficulty is a measure of the difficulty of mining this block.
difficulty: BigInt!
# TotalDifficulty is the sum of all difficulty values up to and including
# this block.
totalDifficulty: BigInt!
# OmmerCount is the number of ommers (AKA uncles) associated with this
# block. If ommers are unavailable, this field will be null.
ommerCount: Int
# Ommers is a list of ommer (AKA uncle) blocks associated with this block.
# If ommers are unavailable, this field will be null. Depending on your
# node, the transactions, transactionAt, transactionCount, ommers,
# ommerCount and ommerAt fields may not be available on any ommer blocks.
ommers: [Block]
# OmmerAt returns the ommer (AKA uncle) at the specified index. If ommers
# are unavailable, or the index is out of bounds, this field will be null.
ommerAt(index: Int!): Block
# OmmerHash is the keccak256 hash of all the ommers (AKA uncles)
# associated with this block.
ommerHash: Bytes32!
# Transactions is a list of transactions associated with this block. If
# transactions are unavailable for this block, this field will be null.
transactions: [Transaction!]
# TransactionAt returns the transaction at the specified index. If
# transactions are unavailable for this block, or if the index is out of
# bounds, this field will be null.
transactionAt(index: Int!): Transaction
# Logs returns a filtered set of logs from this block.
logs(filter: BlockFilterCriteria!): [Log!]!
# Account fetches an Ethereum account at the current block's state.
account(address: Address!): Account!
# Call executes a local call operation at the current block's state.
call(data: CallData!): CallResult
}
# CallData represents the data associated with a local contract call.
# All fields are optional.
input CallData {
# From is the address making the call.
from: Address
# To is the address the call is sent to.
to: Address
# Gas is the amount of gas sent with the call.
gas: Long
# GasPrice is the price, in wei, offered for each unit of gas.
gasPrice: BigInt
# Value is the value, in wei, sent along with the call.
value: BigInt
# Data is the data sent to the callee.
data: Bytes
}
# CallResult is the result of a local call operation.
type CallResult {
# Data is the return data of the called contract.
data: Bytes!
# GasUsed is the amount of gas used by the call, after any refunds.
gasUsed: Long!
# Status is the result of the call - 1 for success or 0 for failure.
status: Long!
}
# FilterCriteria encapsulates log filter criteria for searching log entries.
input FilterCriteria {
# FromBlock is the block at which to start searching, inclusive. Defaults
# to the latest block if not supplied.
fromBlock: Long
# ToBlock is the block at which to stop searching, inclusive. Defaults
# to the latest block if not supplied.
toBlock: Long
# Addresses is a list of addresses that are of interest. If this list is
# empty, results will not be filtered by address.
addresses: [Address!]
# Topics list restricts matches to particular event topics. Each event has a list
# of topics. Topics matches a prefix of that list. An empty element array matches any
# topic. Non-empty elements represent an alternative that matches any of the
# contained topics.
#
# Examples:
# - [] or nil matches any topic list
# - [[A]] matches topic A in first position
# - [[], [B]] matches any topic in first position, B in second position
# - [[A], [B]] matches topic A in first position, B in second position
# - [[A, B]], [C, D]] matches topic (A OR B) in first position, (C OR D) in second position
topics: [[Bytes32!]!]
}
type Query {
# 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!]!
}
`
+104
View File
@@ -0,0 +1,104 @@
// VulcanizeDB
// Copyright © 2020 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package graphql
import (
"fmt"
"net"
"net/http"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc"
"github.com/graph-gophers/graphql-go"
"github.com/graph-gophers/graphql-go/relay"
"github.com/sirupsen/logrus"
"github.com/vulcanize/ipld-eth-server/pkg/eth"
)
// Service encapsulates a GraphQL service.
type Service struct {
endpoint string // The host:port endpoint for this service.
cors []string // Allowed CORS domains
vhosts []string // Recognised vhosts
timeouts rpc.HTTPTimeouts // Timeout settings for HTTP requests.
backend *eth.Backend // The backend that queries will operate onn.
handler http.Handler // The `http.Handler` used to answer queries.
listener net.Listener // The listening socket.
}
// New constructs a new GraphQL service instance.
func New(backend *eth.Backend, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) (*Service, error) {
return &Service{
endpoint: endpoint,
cors: cors,
vhosts: vhosts,
timeouts: timeouts,
backend: backend,
}, nil
}
// Protocols returns the list of protocols exported by this service.
func (s *Service) Protocols() []p2p.Protocol { return nil }
// APIs returns the list of APIs exported by this service.
func (s *Service) APIs() []rpc.API { return nil }
// Start is called after all services have been constructed and the networking
// layer was also initialized to spawn any goroutines required by the service.
func (s *Service) Start(server *p2p.Server) error {
var err error
s.handler, err = NewHandler(s.backend)
if err != nil {
return err
}
if s.listener, err = net.Listen("tcp", s.endpoint); err != nil {
return err
}
go rpc.NewHTTPServer(s.cors, s.vhosts, s.timeouts, s.handler).Serve(s.listener)
logrus.Debugf("graphQL endpoint opened for url %s", fmt.Sprintf("http://%s", s.endpoint))
return nil
}
// newHandler returns a new `http.Handler` that will answer GraphQL queries.
// It additionally exports an interactive query browser on the / endpoint.
func NewHandler(backend *eth.Backend) (http.Handler, error) {
q := Resolver{backend}
s, err := graphql.ParseSchema(schema, &q)
if err != nil {
return nil, err
}
h := &relay.Handler{Schema: s}
mux := http.NewServeMux()
mux.Handle("/", GraphiQL{})
mux.Handle("/graphql", h)
mux.Handle("/graphql/", h)
return mux, nil
}
// Stop terminates all goroutines belonging to the service, blocking until they
// are all terminated.
func (s *Service) Stop() error {
if s.listener != nil {
s.listener.Close()
s.listener = nil
logrus.Debugf("graphQL endpoint closed for url %s", fmt.Sprintf("http://%s", s.endpoint))
}
return nil
}
+16
View File
@@ -1,3 +1,19 @@
// VulcanizeDB
// Copyright © 2020 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package prom
import (
+16
View File
@@ -1,3 +1,19 @@
// VulcanizeDB
// Copyright © 2020 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package prom
import (
+16
View File
@@ -1,3 +1,19 @@
// VulcanizeDB
// Copyright © 2020 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package prom
import (
+16
View File
@@ -1,3 +1,19 @@
// VulcanizeDB
// Copyright © 2020 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package prom
import (
+16
View File
@@ -1,3 +1,19 @@
// VulcanizeDB
// Copyright © 2020 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package rpc
import "github.com/ethereum/go-ethereum/rpc"
+16
View File
@@ -1,3 +1,19 @@
// VulcanizeDB
// Copyright © 2020 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package rpc
import (
+16
View File
@@ -1,3 +1,19 @@
// VulcanizeDB
// Copyright © 2020 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package rpc
import (
+16
View File
@@ -1,3 +1,19 @@
// VulcanizeDB
// Copyright © 2020 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package rpc
import (
+16 -10
View File
@@ -17,15 +17,18 @@
package serve
import (
"fmt"
"math/big"
"os"
"path/filepath"
"github.com/ethereum/go-ethereum/rpc"
"github.com/vulcanize/ipld-eth-indexer/pkg/shared"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
"github.com/spf13/viper"
"github.com/vulcanize/ipld-eth-indexer/pkg/node"
"github.com/vulcanize/ipld-eth-indexer/pkg/postgres"
"github.com/vulcanize/ipld-eth-indexer/utils"
"github.com/vulcanize/ipld-eth-server/pkg/prom"
@@ -43,11 +46,8 @@ const (
SERVER_MAX_OPEN_CONNECTIONS = "SERVER_MAX_OPEN_CONNECTIONS"
SERVER_MAX_CONN_LIFETIME = "SERVER_MAX_CONN_LIFETIME"
ETH_CHAIN_ID = "ETH_CHAIN_ID"
ETH_DEFAULT_SENDER_ADDR = "ETH_DEFAULT_SENDER_ADDR"
ETH_RPC_GAS_CAP = "ETH_RPC_GAS_CAP"
ETH_RPC_GAS_CAP = "ETH_RPC_GAS_CAP"
)
// Config struct
@@ -60,6 +60,7 @@ type Config struct {
ChainConfig *params.ChainConfig
DefaultSender *common.Address
RPCGasCap *big.Int
Client *rpc.Client
}
// NewConfig is used to initialize a watcher config from a .toml file
@@ -70,12 +71,19 @@ func NewConfig() (*Config, error) {
viper.BindEnv("server.wsPath", SERVER_WS_PATH)
viper.BindEnv("server.ipcPath", SERVER_IPC_PATH)
viper.BindEnv("server.httpPath", SERVER_HTTP_PATH)
viper.BindEnv("ethereum.chainID", ETH_CHAIN_ID)
viper.BindEnv("ethereum.httpPath", shared.ETH_HTTP_PATH)
viper.BindEnv("ethereum.defaultSender", ETH_DEFAULT_SENDER_ADDR)
viper.BindEnv("ethereum.rpcGasCap", ETH_RPC_GAS_CAP)
c.DBConfig.Init()
ethHTTP := viper.GetString("ethereum.httpPath")
nodeInfo, cli, err := shared.GetEthNodeAndClient(fmt.Sprintf("http://%s", ethHTTP))
if err != nil {
return nil, err
}
c.Client = cli
wsPath := viper.GetString("server.wsPath")
if wsPath == "" {
wsPath = "127.0.0.1:8080"
@@ -96,7 +104,7 @@ func NewConfig() (*Config, error) {
}
c.HTTPEndpoint = httpPath
overrideDBConnConfig(&c.DBConfig)
serveDB := utils.LoadPostgres(c.DBConfig, node.Info{})
serveDB := utils.LoadPostgres(c.DBConfig, nodeInfo)
prom.RegisterDBCollector(c.DBConfig.Name, serveDB.DB)
c.DB = &serveDB
@@ -111,9 +119,7 @@ func NewConfig() (*Config, error) {
c.RPCGasCap = rpcGasCap
}
}
chainID := viper.GetUint64("ethereum.chainID")
var err error
c.ChainConfig, err = eth.ChainConfig(chainID)
c.ChainConfig, err = eth.ChainConfig(nodeInfo.ChainID)
return c, err
}
+24 -19
View File
@@ -52,6 +52,8 @@ type Server interface {
Subscribe(id rpc.ID, sub chan<- SubscriptionPayload, quitChan chan<- bool, params eth.SubscriptionSettings)
// Method to unsubscribe from the service
Unsubscribe(id rpc.ID)
// Backend exposes the server's backend
Backend() *eth.Backend
}
// Service is the underlying struct for the watcher
@@ -74,27 +76,30 @@ type Service struct {
db *postgres.DB
// wg for syncing serve processes
serveWg *sync.WaitGroup
// config for backend
config *eth.Config
// rpc client for forwarding cache misses
client *rpc.Client
// backend for the server
backend *eth.Backend
}
// NewServer creates a new Server using an underlying Service struct
func NewServer(settings *Config) (Server, error) {
sn := new(Service)
sn.Retriever = eth.NewCIDRetriever(settings.DB)
sn.IPLDFetcher = eth.NewIPLDFetcher(settings.DB)
sn.Filterer = eth.NewResponseFilterer()
sn.db = settings.DB
sn.QuitChan = make(chan bool)
sn.Subscriptions = make(map[common.Hash]map[rpc.ID]Subscription)
sn.SubscriptionTypes = make(map[common.Hash]eth.SubscriptionSettings)
sn.config = &eth.Config{
sap := new(Service)
sap.Retriever = eth.NewCIDRetriever(settings.DB)
sap.IPLDFetcher = eth.NewIPLDFetcher(settings.DB)
sap.Filterer = eth.NewResponseFilterer()
sap.db = settings.DB
sap.QuitChan = make(chan bool)
sap.Subscriptions = make(map[common.Hash]map[rpc.ID]Subscription)
sap.SubscriptionTypes = make(map[common.Hash]eth.SubscriptionSettings)
var err error
sap.backend, err = eth.NewEthBackend(sap.db, &eth.Config{
ChainConfig: settings.ChainConfig,
VmConfig: vm.Config{},
DefaultSender: settings.DefaultSender,
RPCGasCap: settings.RPCGasCap,
}
return sn, nil
})
return sap, err
}
// Protocols exports the services p2p protocols, this service has none
@@ -131,15 +136,10 @@ func (sap *Service) APIs() []rpc.API {
Public: true,
},
}
backend, err := eth.NewEthBackend(sap.db, sap.config)
if err != nil {
log.Error(err)
return nil
}
return append(apis, rpc.API{
Namespace: eth.APIName,
Version: eth.APIVersion,
Service: eth.NewPublicEthAPI(backend),
Service: eth.NewPublicEthAPI(sap.backend, sap.client),
Public: true,
})
}
@@ -356,6 +356,11 @@ func (sap *Service) Stop() error {
return nil
}
// Backend exposes the server's backend
func (sap *Service) Backend() *eth.Backend {
return sap.backend
}
// close is used to close all listening subscriptions
// close needs to be called with subscription access locked
func (sap *Service) close() {