Skip to content

Commit 3bc0fe1

Browse files
committed
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.
1 parent fa0cc27 commit 3bc0fe1

File tree

4 files changed

+67
-23
lines changed

4 files changed

+67
-23
lines changed

ethclient/ethclient.go

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ func (ec *Client) getBlock(ctx context.Context, method string, args ...interface
8181
err := ec.c.CallContext(ctx, &raw, method, args...)
8282
if err != nil {
8383
return nil, err
84+
} else if len(raw) == 0 {
85+
return nil, ethereum.NotFound
8486
}
8587
// Decode header and transactions.
8688
var head *types.Header
@@ -135,6 +137,9 @@ func (ec *Client) getBlock(ctx context.Context, method string, args ...interface
135137
func (ec *Client) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
136138
var head *types.Header
137139
err := ec.c.CallContext(ctx, &head, "eth_getBlockByHash", hash, false)
140+
if err == nil && head == nil {
141+
err = ethereum.NotFound
142+
}
138143
return head, err
139144
}
140145

@@ -143,19 +148,31 @@ func (ec *Client) HeaderByHash(ctx context.Context, hash common.Hash) (*types.He
143148
func (ec *Client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
144149
var head *types.Header
145150
err := ec.c.CallContext(ctx, &head, "eth_getBlockByNumber", toBlockNumArg(number), false)
151+
if err == nil && head == nil {
152+
err = ethereum.NotFound
153+
}
146154
return head, err
147155
}
148156

149157
// TransactionByHash returns the transaction with the given hash.
150-
func (ec *Client) TransactionByHash(ctx context.Context, hash common.Hash) (*types.Transaction, error) {
151-
var tx *types.Transaction
152-
err := ec.c.CallContext(ctx, &tx, "eth_getTransactionByHash", hash)
153-
if err == nil {
154-
if _, r, _ := tx.RawSignatureValues(); r == nil {
155-
return nil, fmt.Errorf("server returned transaction without signature")
156-
}
158+
func (ec *Client) TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) {
159+
var raw json.RawMessage
160+
err = ec.c.CallContext(ctx, &raw, "eth_getTransactionByHash", hash)
161+
if err != nil {
162+
return nil, false, err
163+
} else if len(raw) == 0 {
164+
return nil, false, ethereum.NotFound
157165
}
158-
return tx, err
166+
if err := json.Unmarshal(raw, tx); err != nil {
167+
return nil, false, err
168+
} else if _, r, _ := tx.RawSignatureValues(); r == nil {
169+
return nil, false, fmt.Errorf("server returned transaction without signature")
170+
}
171+
var block struct{ BlockHash *common.Hash }
172+
if err := json.Unmarshal(raw, &block); err != nil {
173+
return nil, false, err
174+
}
175+
return tx, block.BlockHash == nil, nil
159176
}
160177

161178
// 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,
170187
var tx *types.Transaction
171188
err := ec.c.CallContext(ctx, &tx, "eth_getTransactionByBlockHashAndIndex", blockHash, index)
172189
if err == nil {
173-
var signer types.Signer = types.HomesteadSigner{}
174-
if tx.Protected() {
175-
signer = types.NewEIP155Signer(tx.ChainId())
176-
}
177-
if _, r, _ := types.SignatureValues(signer, tx); r == nil {
190+
if tx == nil {
191+
return nil, ethereum.NotFound
192+
} else if _, r, _ := tx.RawSignatureValues(); r == nil {
178193
return nil, fmt.Errorf("server returned transaction without signature")
179194
}
180195
}
@@ -186,8 +201,12 @@ func (ec *Client) TransactionInBlock(ctx context.Context, blockHash common.Hash,
186201
func (ec *Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
187202
var r *types.Receipt
188203
err := ec.c.CallContext(ctx, &r, "eth_getTransactionReceipt", txHash)
189-
if err == nil && r != nil && len(r.PostState) == 0 {
190-
return nil, fmt.Errorf("server returned receipt without post state")
204+
if err == nil {
205+
if r == nil {
206+
return nil, ethereum.NotFound
207+
} else if len(r.PostState) == 0 {
208+
return nil, fmt.Errorf("server returned receipt without post state")
209+
}
191210
}
192211
return r, err
193212
}

ethclient/ethclient_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ import "github.com/ethereum/go-ethereum"
2121
// Verify that Client implements the ethereum interfaces.
2222
var (
2323
_ = ethereum.ChainReader(&Client{})
24+
_ = ethereum.TransactionReader(&Client{})
2425
_ = ethereum.ChainStateReader(&Client{})
2526
_ = ethereum.ChainSyncReader(&Client{})
26-
_ = ethereum.ChainHeadEventer(&Client{})
2727
_ = ethereum.ContractCaller(&Client{})
2828
_ = ethereum.GasEstimator(&Client{})
2929
_ = ethereum.GasPricer(&Client{})

interfaces.go

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package ethereum
1919

2020
import (
21+
"errors"
2122
"math/big"
2223

2324
"github.com/ethereum/go-ethereum/common"
@@ -26,6 +27,9 @@ import (
2627
"golang.org/x/net/context"
2728
)
2829

30+
// NotFound is returned by API methods if the requested item does not exist.
31+
var NotFound = errors.New("not found")
32+
2933
// TODO: move subscription to package event
3034

3135
// Subscription represents an event subscription where events are
@@ -46,14 +50,39 @@ type Subscription interface {
4650
// blockchain fork that was previously downloaded and processed by the node. The block
4751
// number argument can be nil to select the latest canonical block. Reading block headers
4852
// should be preferred over full blocks whenever possible.
53+
//
54+
// The returned error is NotFound if the requested item does not exist.
4955
type ChainReader interface {
5056
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
5157
BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error)
5258
HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error)
5359
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
5460
TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error)
5561
TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error)
56-
TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, error)
62+
63+
// This method subscribes to notifications about changes of the head block of
64+
// the canonical chain.
65+
SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (Subscription, error)
66+
}
67+
68+
// TransactionReader provides access to past transactions and their receipts.
69+
// Implementations may impose arbitrary restrictions on the transactions and receipts that
70+
// can be retrieved. Historic transactions may not be available.
71+
//
72+
// Avoid relying on this interface if possible. Contract logs (through the LogFilterer
73+
// interface) are more reliable and usually safer in the presence of chain
74+
// reorganisations.
75+
//
76+
// The returned error is NotFound if the requested item does not exist.
77+
type TransactionReader interface {
78+
// TransactionByHash checks the pool of pending transactions in addition to the
79+
// blockchain. The isPending return value indicates whether the transaction has been
80+
// mined yet. Note that the transaction may not be part of the canonical chain even if
81+
// it's not pending.
82+
TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, isPending bool, err error)
83+
// TransactionReceipt returns the receipt of a mined transaction. Note that the
84+
// transaction may not be included in the current canonical chain even if a receipt
85+
// exists.
5786
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
5887
}
5988

@@ -83,11 +112,6 @@ type ChainSyncReader interface {
83112
SyncProgress(ctx context.Context) (*SyncProgress, error)
84113
}
85114

86-
// A ChainHeadEventer returns notifications whenever the canonical head block is updated.
87-
type ChainHeadEventer interface {
88-
SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (Subscription, error)
89-
}
90-
91115
// CallMsg contains parameters for contract calls.
92116
type CallMsg struct {
93117
From common.Address // the sender of the 'transaction'

mobile/ethclient.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ func (ec *EthereumClient) GetHeaderByNumber(ctx *Context, number int64) (*Header
7373

7474
// GetTransactionByHash returns the transaction with the given hash.
7575
func (ec *EthereumClient) GetTransactionByHash(ctx *Context, hash *Hash) (*Transaction, error) {
76-
tx, err := ec.client.TransactionByHash(ctx.context, hash.hash)
76+
// TODO(karalabe): handle isPending
77+
tx, _, err := ec.client.TransactionByHash(ctx.context, hash.hash)
7778
return &Transaction{tx}, err
7879
}
7980

0 commit comments

Comments
 (0)