diff --git a/README.md b/README.md
index cca12140..2b98b518 100644
--- a/README.md
+++ b/README.md
@@ -93,13 +93,31 @@ 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_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 and unique endpoints (e.g. getSlice)
diff --git a/db/migrations/00018_create_check_if_removed_functions.sql b/db/migrations/00018_create_check_if_removed_functions.sql
new file mode 100644
index 00000000..9a7e42d3
--- /dev/null
+++ b/db/migrations/00018_create_check_if_removed_functions.sql
@@ -0,0 +1,37 @@
+-- +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 *
+ 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);
+$$ 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 *
+ 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);
+$$ LANGUAGE SQL;
+-- +goose StatementEnd
+
+-- +goose Down
+DROP FUNCTION was_storage_removed;
+DROP FUNCTION was_state_removed;
\ No newline at end of file
diff --git a/db/schema.sql b/db/schema.sql
index 02e92f20..91be03f0 100644
--- a/db/schema.sql
+++ b/db/schema.sql
@@ -100,6 +100,45 @@ CREATE FUNCTION public.header_weight(hash character varying) RETURNS bigint
$$;
+--
+-- Name: was_state_removed(bytea, bigint, character varying); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION public.was_state_removed(path bytea, height bigint, hash character varying) RETURNS boolean
+ LANGUAGE sql
+ AS $$
+ SELECT exists(SELECT *
+ 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);
+$$;
+
+
+--
+-- Name: was_storage_removed(bytea, bigint, character varying); Type: FUNCTION; Schema: public; Owner: -
+--
+
+CREATE FUNCTION public.was_storage_removed(path bytea, height bigint, hash character varying) RETURNS boolean
+ LANGUAGE sql
+ AS $$
+ SELECT exists(SELECT *
+ 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);
+$$;
+
+
SET default_tablespace = '';
SET default_table_access_method = heap;
diff --git a/pkg/eth/api.go b/pkg/eth/api.go
index 9a792c0b..8be45588 100644
--- a/pkg/eth/api.go
+++ b/pkg/eth/api.go
@@ -29,8 +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"
@@ -66,29 +68,436 @@ func NewPublicEthAPI(b *Backend, client *rpc.Client) *PublicEthAPI {
}
}
+/*
+
+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.
func (pea *PublicEthAPI) BlockNumber() hexutil.Uint64 {
number, _ := pea.B.Retriever.RetrieveLastBlockNumber()
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.getLogs(ctx, crit)
+ logs, err := pea.localGetLogs(ctx, crit)
if err != nil && pea.rpc != nil {
if arg, err := toFilterArg(crit); err == nil {
- var result []*types.Log
- if err := pea.rpc.CallContext(ctx, &result, "eth_getLogs", arg); err == nil {
- return result, 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) getLogs(ctx context.Context, crit ethereum.FilterQuery) ([]*types.Log, error) {
+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 {
@@ -175,76 +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)
- }
- 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
-}
+/*
-// 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
-}
+State and Storage
-// 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
-}
+*/
-// 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
+// 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 {
- if tx, err := pea.remoteGetTransactionByHash(ctx, hash); tx != nil && err == nil {
- return tx, 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
}
-func (pea *PublicEthAPI) remoteGetTransactionByHash(ctx context.Context, hash common.Hash) (*RPCTransaction, error) {
- var tx *RPCTransaction
- if err := pea.rpc.CallContext(ctx, &tx, "eth_getTransactionByHash", hash); err != nil {
+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
}
- return tx, 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
+ }
+ 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.
@@ -258,48 +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 {
- if res, err := pea.remoteCall(ctx, args, blockNrOrHash, overrides); res != nil && err == nil {
- return res, 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
-}
-
-func (pea *PublicEthAPI) remoteCall(ctx context.Context, msg CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *map[common.Address]account) (hexutil.Bytes, error) {
- var hex hexutil.Bytes
- if err := pea.rpc.CallContext(ctx, &hex, "eth_call", msg, blockNrOrHash, overrides); err != nil {
- return nil, err
- }
- return hex, nil
-}
-
-// 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) {
@@ -409,30 +842,32 @@ func DoCall(ctx context.Context, b *Backend, args CallArgs, blockNrOrHash rpc.Bl
return res, gas, failed, err
}
-func toFilterArg(q ethereum.FilterQuery) (interface{}, error) {
- arg := map[string]interface{}{
- "address": q.Addresses,
- "topics": q.Topics,
+// 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 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")
+ if inclTx {
+ td, err := pea.B.GetTd(b.Hash())
+ if err != nil {
+ return nil, err
}
- } else {
- if q.FromBlock == nil {
- arg["fromBlock"] = "0x0"
- } else {
- arg["fromBlock"] = toBlockNumArg(q.FromBlock)
- }
- arg["toBlock"] = toBlockNumArg(q.ToBlock)
+ fields["totalDifficulty"] = (*hexutil.Big)(td)
}
- return arg, nil
+ return fields, err
}
-func toBlockNumArg(number *big.Int) string {
- if number == nil {
- return "latest"
+// 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
}
- return hexutil.EncodeBig(number)
+ td, err := pea.B.GetTd(b.Hash())
+ if err != nil {
+ return nil, err
+ }
+ fields["totalDifficulty"] = (*hexutil.Big)(td)
+ return fields, err
}
diff --git a/pkg/eth/api_test.go b/pkg/eth/api_test.go
index 8211ebf2..f910c7e5 100644
--- a/pkg/eth/api_test.go
+++ b/pkg/eth/api_test.go
@@ -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, ð.Config{})
+ indexAndPublisher := eth2.NewIPLDPublisher(db)
+ backend, err := eth.NewEthBackend(db, ð.Config{})
Expect(err).ToNot(HaveOccurred())
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()
+}
diff --git a/pkg/eth/backend.go b/pkg/eth/backend.go
index 4e5ff088..0cb4c52c 100644
--- a/pkg/eth/backend.go
+++ b/pkg/eth/backend.go
@@ -19,27 +19,31 @@ package eth
import (
"context"
"errors"
+ "fmt"
"math/big"
- "github.com/ethereum/go-ethereum/core/bloombits"
- "github.com/ethereum/go-ethereum/event"
+ "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/hexutil"
"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/postgres"
+ shared2 "github.com/vulcanize/ipld-eth-indexer/pkg/shared"
+
"github.com/vulcanize/ipld-eth-server/pkg/shared"
)
@@ -61,6 +65,17 @@ const (
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(block_number))
+ ORDER BY block_number DESC
+ LIMIT 1`
+ RetrieveCodeByMhKey = `SELECT data FROM public.blocks WHERE key = $1`
)
type Backend struct {
@@ -167,18 +182,6 @@ func (b *Backend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.Bl
return nil, errors.New("invalid arguments; neither block nor hash specified")
}
-// 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
-}
-
// GetTd gets the total difficulty at the given block hash
func (b *Backend) GetTd(blockHash common.Hash) (*big.Int, error) {
var tdStr string
@@ -431,7 +434,7 @@ func (b *Backend) GetReceipts(ctx context.Context, hash common.Hash) (types.Rece
if err != nil {
return nil, err
}
- rcts := make(types.Receipts, 0, len(receiptBytes))
+ rcts := make(types.Receipts, len(receiptBytes))
for i, rctBytes := range receiptBytes {
rct := new(types.Receipt)
if err := rlp.DecodeBytes(rctBytes, rct); err != nil {
@@ -459,21 +462,6 @@ func (b *Backend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log
return logs, nil
}
-// 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
-}
-
// 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 {
@@ -541,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
diff --git a/pkg/eth/backend_utils.go b/pkg/eth/backend_utils.go
index 9d7277eb..98b1e33c 100644
--- a/pkg/eth/backend_utils.go
+++ b/pkg/eth/backend_utils.go
@@ -17,12 +17,19 @@
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"
)
@@ -86,6 +93,35 @@ func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool) (map[string]i
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() {
@@ -127,6 +163,77 @@ func NewRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
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()
@@ -183,3 +290,31 @@ func sliceContainsHash(slice []string, hash common.Hash) int {
}
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)
+}
diff --git a/pkg/eth/eth_call_test.go b/pkg/eth/eth_call_test.go
deleted file mode 100644
index 0ad599df..00000000
--- a/pkg/eth/eth_call_test.go
+++ /dev/null
@@ -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 .
-
-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, ð.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(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))
- })
- })
-})
diff --git a/pkg/eth/eth_state_test.go b/pkg/eth/eth_state_test.go
new file mode 100644
index 00000000..58148816
--- /dev/null
+++ b/pkg/eth/eth_state_test.go
@@ -0,0 +1,426 @@
+// 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 .
+
+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)
+ )
+ 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, ð.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,
+ }
+ // 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())
+ }
+ })
+ 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{}))
+ })
+ })
+})
diff --git a/pkg/eth/ipld_retriever.go b/pkg/eth/ipld_retriever.go
index b2800300..8418bfe6 100644
--- a/pkg/eth/ipld_retriever.go
+++ b/pkg/eth/ipld_retriever.go
@@ -28,71 +28,132 @@ 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(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(block_number))
+ ORDER BY block_number DESC
+ LIMIT 1`
)
type ipldResult struct {
@@ -315,13 +376,23 @@ func (r *IPLDRetriever) RetrieveReceiptByHash(hash common.Hash) (string, []byte,
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 {
return "", nil, fmt.Errorf("error decoding state leaf node rlp: %s", err.Error())
@@ -333,25 +404,64 @@ func (r *IPLDRetriever) RetrieveAccountByAddressAndBlockHash(address common.Addr
}
// 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
}
diff --git a/pkg/eth/ipld_retriever_test.go b/pkg/eth/ipld_retriever_test.go
deleted file mode 100644
index 544b16d6..00000000
--- a/pkg/eth/ipld_retriever_test.go
+++ /dev/null
@@ -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 .
-
-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() {
-
- })
- })
-
-})
-*/
diff --git a/pkg/eth/test_helpers/chain_maker.go b/pkg/eth/test_helpers/chain_maker.go
index 2e587a9d..1444b0d9 100644
--- a/pkg/eth/test_helpers/chain_maker.go
+++ b/pkg/eth/test_helpers/chain_maker.go
@@ -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")
diff --git a/pkg/eth/test_helpers/test_data.go b/pkg/eth/test_helpers/test_data.go
index 08f57c34..c86854d7 100644
--- a/pkg/eth/test_helpers/test_data.go
+++ b/pkg/eth/test_helpers/test_data.go
@@ -57,19 +57,39 @@ 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())
+ 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 +259,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 +268,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 +284,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),
})
@@ -341,7 +361,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(),
diff --git a/pkg/eth/types.go b/pkg/eth/types.go
index 44a2f7e2..53edf503 100644
--- a/pkg/eth/types.go
+++ b/pkg/eth/types.go
@@ -21,6 +21,7 @@ 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/statediff"
"github.com/vulcanize/ipld-eth-indexer/pkg/eth"
"github.com/vulcanize/ipld-eth-indexer/pkg/ipfs"
@@ -44,6 +45,65 @@ type RPCTransaction struct {
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 {