diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 48639d9492..3885b1c060 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -153,25 +153,57 @@ 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 + 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) + 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. 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) (common.Address, error) { + // Try to load the address from the cache. + sender, err := types.Sender((*senderFromServer)(nil), tx) + if err == nil { + return sender, nil } - return tx, block.BlockNumber == nil, nil + // TODO It'd better to use GetTransactionByBlockHashAndIndex because it works with the + // light client. This will be even more important with EIP 208 because there can be + // multiple inclusions of the same tx. The downside is that users would need to supply + // the inclusion block. + var meta struct{ From common.Address } + err = ec.c.CallContext(ctx, &meta, "eth_getTransactionByHash", tx.Hash()) + return meta.From, err } // TransactionCount returns the total number of transactions in the given block. @@ -183,16 +215,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) + 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 0000000000..8c16bc8b6b --- /dev/null +++ b/ethclient/signer.go @@ -0,0 +1,58 @@ +// 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 . + +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 +} + +var errNotCached = errors.New("sender not cached") + +func setSenderFromServer(tx *types.Transaction, addr common.Address) { + // Use types.Sender for side-effect to store our signer into the cache. + types.Sender(&senderFromServer{addr}, tx) +} + +func (s *senderFromServer) Equal(other types.Signer) bool { + _, ok := other.(*senderFromServer) + return ok +} + +func (s *senderFromServer) Sender(tx *types.Transaction) (common.Address, error) { + if s == nil { + 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") +} diff --git a/mobile/ethclient.go b/mobile/ethclient.go index 4e83285017..75af309175 100644 --- a/mobile/ethclient.go +++ b/mobile/ethclient.go @@ -77,6 +77,12 @@ func (ec *EthereumClient) GetTransactionByHash(ctx *Context, hash *Hash) (tx *Tr return &Transaction{rawTx}, err } +// GetTransactionSender returns the sender address of a transaction. +func (ec *EthereumClient) GetTransactionSender(ctx *Context, tx *Transaction) (sender *Address, _ error) { + addr, err := ec.client.TransactionSender(ctx.context, tx.tx) + return &Address{addr}, err +} + // GetTransactionCount returns the total number of transactions in the given block. func (ec *EthereumClient) GetTransactionCount(ctx *Context, hash *Hash) (count int, _ error) { rawCount, err := ec.client.TransactionCount(ctx.context, hash.hash) diff --git a/mobile/types.go b/mobile/types.go index 514a448dce..b7f8a3bc15 100644 --- a/mobile/types.go +++ b/mobile/types.go @@ -267,6 +267,7 @@ func (tx *Transaction) GetCost() *BigInt { return &BigInt{tx.tx.Cost()} } // Deprecated: GetSigHash cannot know which signer to use. func (tx *Transaction) GetSigHash() *Hash { return &Hash{types.HomesteadSigner{}.Hash(tx.tx)} } +// Deprecated: use EthereumClient.TransactionSender func (tx *Transaction) GetFrom(chainID *BigInt) (address *Address, _ error) { var signer types.Signer = types.HomesteadSigner{} if chainID != nil {