diff options
Diffstat (limited to 'ethclient')
-rw-r--r-- | ethclient/ethclient.go | 85 | ||||
-rw-r--r-- | ethclient/signer.go | 59 |
2 files changed, 124 insertions, 20 deletions
diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 48639d949..7f73ab113 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -20,6 +20,7 @@ package ethclient import ( "context" "encoding/json" + "errors" "fmt" "math/big" @@ -70,9 +71,9 @@ func (ec *Client) BlockByNumber(ctx context.Context, number *big.Int) (*types.Bl } type rpcBlock struct { - Hash common.Hash `json:"hash"` - Transactions []*types.Transaction `json:"transactions"` - UncleHashes []common.Hash `json:"uncles"` + Hash common.Hash `json:"hash"` + Transactions []rpcTransaction `json:"transactions"` + UncleHashes []common.Hash `json:"uncles"` } func (ec *Client) getBlock(ctx context.Context, method string, args ...interface{}) (*types.Block, error) { @@ -129,7 +130,13 @@ func (ec *Client) getBlock(ctx context.Context, method string, args ...interface } } } - return types.NewBlockWithHeader(head).WithBody(body.Transactions, uncles), nil + // Fill the sender cache of transactions in the block. + txs := make([]*types.Transaction, len(body.Transactions)) + for i, tx := range body.Transactions { + setSenderFromServer(tx.tx, tx.From, body.Hash) + txs[i] = tx.tx + } + return types.NewBlockWithHeader(head).WithBody(txs, uncles), nil } // HeaderByHash returns the block header with the given hash. @@ -153,25 +160,62 @@ func (ec *Client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.H return head, err } +type rpcTransaction struct { + tx *types.Transaction + txExtraInfo +} + +type txExtraInfo struct { + BlockNumber *string + BlockHash common.Hash + From common.Address +} + +func (tx *rpcTransaction) UnmarshalJSON(msg []byte) error { + if err := json.Unmarshal(msg, &tx.tx); err != nil { + return err + } + return json.Unmarshal(msg, &tx.txExtraInfo) +} + // TransactionByHash returns the transaction with the given hash. func (ec *Client) TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) { - var raw json.RawMessage - err = ec.c.CallContext(ctx, &raw, "eth_getTransactionByHash", hash) + var json *rpcTransaction + err = ec.c.CallContext(ctx, &json, "eth_getTransactionByHash", hash) if err != nil { return nil, false, err - } else if len(raw) == 0 { + } else if json == nil { return nil, false, ethereum.NotFound - } - if err := json.Unmarshal(raw, &tx); err != nil { - return nil, false, err - } else if _, r, _ := tx.RawSignatureValues(); r == nil { + } else if _, r, _ := json.tx.RawSignatureValues(); r == nil { return nil, false, fmt.Errorf("server returned transaction without signature") } - var block struct{ BlockNumber *string } - if err := json.Unmarshal(raw, &block); err != nil { - return nil, false, err + setSenderFromServer(json.tx, json.From, json.BlockHash) + return json.tx, json.BlockNumber == nil, nil +} + +// TransactionSender returns the sender address of the given transaction. The transaction +// must be known to the remote node and included in the blockchain at the given block and +// index. The sender is the one derived by the protocol at the time of inclusion. +// +// There is a fast-path for transactions retrieved by TransactionByHash and +// TransactionInBlock. Getting their sender address can be done without an RPC interaction. +func (ec *Client) TransactionSender(ctx context.Context, tx *types.Transaction, block common.Hash, index uint) (common.Address, error) { + // Try to load the address from the cache. + sender, err := types.Sender(&senderFromServer{blockhash: block}, tx) + if err == nil { + return sender, nil + } + var meta struct { + Hash common.Hash + From common.Address + } + if err = ec.c.CallContext(ctx, &meta, "eth_getTransactionByBlockHashAndIndex", block, hexutil.Uint64(index)); err != nil { + return common.Address{}, err + } + if meta.Hash == (common.Hash{}) || meta.Hash != tx.Hash() { + return common.Address{}, errors.New("wrong inclusion block/index") } - return tx, block.BlockNumber == nil, nil + return meta.From, nil } // TransactionCount returns the total number of transactions in the given block. @@ -183,16 +227,17 @@ func (ec *Client) TransactionCount(ctx context.Context, blockHash common.Hash) ( // TransactionInBlock returns a single transaction at index in the given block. func (ec *Client) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) { - var tx *types.Transaction - err := ec.c.CallContext(ctx, &tx, "eth_getTransactionByBlockHashAndIndex", blockHash, hexutil.Uint64(index)) + var json *rpcTransaction + err := ec.c.CallContext(ctx, &json, "eth_getTransactionByBlockHashAndIndex", blockHash, hexutil.Uint64(index)) if err == nil { - if tx == nil { + if json == nil { return nil, ethereum.NotFound - } else if _, r, _ := tx.RawSignatureValues(); r == nil { + } else if _, r, _ := json.tx.RawSignatureValues(); r == nil { return nil, fmt.Errorf("server returned transaction without signature") } } - return tx, err + setSenderFromServer(json.tx, json.From, json.BlockHash) + return json.tx, err } // TransactionReceipt returns the receipt of a transaction by transaction hash. diff --git a/ethclient/signer.go b/ethclient/signer.go new file mode 100644 index 000000000..74a93f1e2 --- /dev/null +++ b/ethclient/signer.go @@ -0,0 +1,59 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. + +package ethclient + +import ( + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// senderFromServer is a types.Signer that remembers the sender address returned by the RPC +// server. It is stored in the transaction's sender address cache to avoid an additional +// request in TransactionSender. +type senderFromServer struct { + addr common.Address + blockhash common.Hash +} + +var errNotCached = errors.New("sender not cached") + +func setSenderFromServer(tx *types.Transaction, addr common.Address, block common.Hash) { + // Use types.Sender for side-effect to store our signer into the cache. + types.Sender(&senderFromServer{addr, block}, tx) +} + +func (s *senderFromServer) Equal(other types.Signer) bool { + os, ok := other.(*senderFromServer) + return ok && os.blockhash == s.blockhash +} + +func (s *senderFromServer) Sender(tx *types.Transaction) (common.Address, error) { + if s.blockhash == (common.Hash{}) { + return common.Address{}, errNotCached + } + return s.addr, nil +} + +func (s *senderFromServer) Hash(tx *types.Transaction) common.Hash { + panic("can't sign with senderFromServer") +} +func (s *senderFromServer) SignatureValues(tx *types.Transaction, sig []byte) (R, S, V *big.Int, err error) { + panic("can't sign with senderFromServer") +} |