ethclient, ethereum: add NotFound, split transactions out of ChainReader

ethclient now returns ethereum.NotFound if the server returns null and
no error while accessing blockchain data.

The light client cannot provide arbitrary transactions. The change to
split transaction access into its own interface emphasizes that
transactions should not be relied on and recommends use of logs.
This commit is contained in:
Felix Lange 2016-11-29 15:54:06 +01:00
parent fa0cc27400
commit 3bc0fe1ee3
4 changed files with 67 additions and 23 deletions

@ -81,6 +81,8 @@ func (ec *Client) getBlock(ctx context.Context, method string, args ...interface
err := ec.c.CallContext(ctx, &raw, method, args...) err := ec.c.CallContext(ctx, &raw, method, args...)
if err != nil { if err != nil {
return nil, err return nil, err
} else if len(raw) == 0 {
return nil, ethereum.NotFound
} }
// Decode header and transactions. // Decode header and transactions.
var head *types.Header var head *types.Header
@ -135,6 +137,9 @@ func (ec *Client) getBlock(ctx context.Context, method string, args ...interface
func (ec *Client) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { func (ec *Client) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
var head *types.Header var head *types.Header
err := ec.c.CallContext(ctx, &head, "eth_getBlockByHash", hash, false) err := ec.c.CallContext(ctx, &head, "eth_getBlockByHash", hash, false)
if err == nil && head == nil {
err = ethereum.NotFound
}
return head, err return head, err
} }
@ -143,19 +148,31 @@ func (ec *Client) HeaderByHash(ctx context.Context, hash common.Hash) (*types.He
func (ec *Client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { func (ec *Client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
var head *types.Header var head *types.Header
err := ec.c.CallContext(ctx, &head, "eth_getBlockByNumber", toBlockNumArg(number), false) err := ec.c.CallContext(ctx, &head, "eth_getBlockByNumber", toBlockNumArg(number), false)
if err == nil && head == nil {
err = ethereum.NotFound
}
return head, err return head, err
} }
// TransactionByHash returns the transaction with the given hash. // TransactionByHash returns the transaction with the given hash.
func (ec *Client) TransactionByHash(ctx context.Context, hash common.Hash) (*types.Transaction, error) { func (ec *Client) TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) {
var tx *types.Transaction var raw json.RawMessage
err := ec.c.CallContext(ctx, &tx, "eth_getTransactionByHash", hash) err = ec.c.CallContext(ctx, &raw, "eth_getTransactionByHash", hash)
if err == nil { if err != nil {
if _, r, _ := tx.RawSignatureValues(); r == nil { return nil, false, err
return nil, fmt.Errorf("server returned transaction without signature") } else if len(raw) == 0 {
} return nil, false, ethereum.NotFound
} }
return tx, err if err := json.Unmarshal(raw, tx); err != nil {
return nil, false, err
} else if _, r, _ := tx.RawSignatureValues(); r == nil {
return nil, false, fmt.Errorf("server returned transaction without signature")
}
var block struct{ BlockHash *common.Hash }
if err := json.Unmarshal(raw, &block); err != nil {
return nil, false, err
}
return tx, block.BlockHash == nil, nil
} }
// TransactionCount returns the total number of transactions in the given block. // TransactionCount returns the total number of transactions in the given block.
@ -170,11 +187,9 @@ func (ec *Client) TransactionInBlock(ctx context.Context, blockHash common.Hash,
var tx *types.Transaction var tx *types.Transaction
err := ec.c.CallContext(ctx, &tx, "eth_getTransactionByBlockHashAndIndex", blockHash, index) err := ec.c.CallContext(ctx, &tx, "eth_getTransactionByBlockHashAndIndex", blockHash, index)
if err == nil { if err == nil {
var signer types.Signer = types.HomesteadSigner{} if tx == nil {
if tx.Protected() { return nil, ethereum.NotFound
signer = types.NewEIP155Signer(tx.ChainId()) } else if _, r, _ := tx.RawSignatureValues(); r == nil {
}
if _, r, _ := types.SignatureValues(signer, tx); r == nil {
return nil, fmt.Errorf("server returned transaction without signature") return nil, fmt.Errorf("server returned transaction without signature")
} }
} }
@ -186,8 +201,12 @@ func (ec *Client) TransactionInBlock(ctx context.Context, blockHash common.Hash,
func (ec *Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { func (ec *Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
var r *types.Receipt var r *types.Receipt
err := ec.c.CallContext(ctx, &r, "eth_getTransactionReceipt", txHash) err := ec.c.CallContext(ctx, &r, "eth_getTransactionReceipt", txHash)
if err == nil && r != nil && len(r.PostState) == 0 { if err == nil {
return nil, fmt.Errorf("server returned receipt without post state") if r == nil {
return nil, ethereum.NotFound
} else if len(r.PostState) == 0 {
return nil, fmt.Errorf("server returned receipt without post state")
}
} }
return r, err return r, err
} }

@ -21,9 +21,9 @@ import "github.com/ethereum/go-ethereum"
// Verify that Client implements the ethereum interfaces. // Verify that Client implements the ethereum interfaces.
var ( var (
_ = ethereum.ChainReader(&Client{}) _ = ethereum.ChainReader(&Client{})
_ = ethereum.TransactionReader(&Client{})
_ = ethereum.ChainStateReader(&Client{}) _ = ethereum.ChainStateReader(&Client{})
_ = ethereum.ChainSyncReader(&Client{}) _ = ethereum.ChainSyncReader(&Client{})
_ = ethereum.ChainHeadEventer(&Client{})
_ = ethereum.ContractCaller(&Client{}) _ = ethereum.ContractCaller(&Client{})
_ = ethereum.GasEstimator(&Client{}) _ = ethereum.GasEstimator(&Client{})
_ = ethereum.GasPricer(&Client{}) _ = ethereum.GasPricer(&Client{})

@ -18,6 +18,7 @@
package ethereum package ethereum
import ( import (
"errors"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -26,6 +27,9 @@ import (
"golang.org/x/net/context" "golang.org/x/net/context"
) )
// NotFound is returned by API methods if the requested item does not exist.
var NotFound = errors.New("not found")
// TODO: move subscription to package event // TODO: move subscription to package event
// Subscription represents an event subscription where events are // Subscription represents an event subscription where events are
@ -46,6 +50,8 @@ type Subscription interface {
// blockchain fork that was previously downloaded and processed by the node. The block // blockchain fork that was previously downloaded and processed by the node. The block
// number argument can be nil to select the latest canonical block. Reading block headers // number argument can be nil to select the latest canonical block. Reading block headers
// should be preferred over full blocks whenever possible. // should be preferred over full blocks whenever possible.
//
// The returned error is NotFound if the requested item does not exist.
type ChainReader interface { type ChainReader interface {
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error)
@ -53,7 +59,30 @@ type ChainReader interface {
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error)
TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error)
TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, error)
// This method subscribes to notifications about changes of the head block of
// the canonical chain.
SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (Subscription, error)
}
// TransactionReader provides access to past transactions and their receipts.
// Implementations may impose arbitrary restrictions on the transactions and receipts that
// can be retrieved. Historic transactions may not be available.
//
// Avoid relying on this interface if possible. Contract logs (through the LogFilterer
// interface) are more reliable and usually safer in the presence of chain
// reorganisations.
//
// The returned error is NotFound if the requested item does not exist.
type TransactionReader interface {
// TransactionByHash checks the pool of pending transactions in addition to the
// blockchain. The isPending return value indicates whether the transaction has been
// mined yet. Note that the transaction may not be part of the canonical chain even if
// it's not pending.
TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, isPending bool, err error)
// TransactionReceipt returns the receipt of a mined transaction. Note that the
// transaction may not be included in the current canonical chain even if a receipt
// exists.
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
} }
@ -83,11 +112,6 @@ type ChainSyncReader interface {
SyncProgress(ctx context.Context) (*SyncProgress, error) SyncProgress(ctx context.Context) (*SyncProgress, error)
} }
// A ChainHeadEventer returns notifications whenever the canonical head block is updated.
type ChainHeadEventer interface {
SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (Subscription, error)
}
// CallMsg contains parameters for contract calls. // CallMsg contains parameters for contract calls.
type CallMsg struct { type CallMsg struct {
From common.Address // the sender of the 'transaction' From common.Address // the sender of the 'transaction'

@ -73,7 +73,8 @@ func (ec *EthereumClient) GetHeaderByNumber(ctx *Context, number int64) (*Header
// GetTransactionByHash returns the transaction with the given hash. // GetTransactionByHash returns the transaction with the given hash.
func (ec *EthereumClient) GetTransactionByHash(ctx *Context, hash *Hash) (*Transaction, error) { func (ec *EthereumClient) GetTransactionByHash(ctx *Context, hash *Hash) (*Transaction, error) {
tx, err := ec.client.TransactionByHash(ctx.context, hash.hash) // TODO(karalabe): handle isPending
tx, _, err := ec.client.TransactionByHash(ctx.context, hash.hash)
return &Transaction{tx}, err return &Transaction{tx}, err
} }