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 {