Skip to content

Commit 1776c71

Browse files
karalabeobscuren
authored andcommitted
[release 1.4.5] accounts/abi/bind, eth: rely on getCode for sanity checks, not estimate and call
(cherry picked from commit 1580ec1)
1 parent 0f6e3e8 commit 1776c71

File tree

7 files changed

+112
-28
lines changed

7 files changed

+112
-28
lines changed

accounts/abi/bind/backend.go

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,16 @@ import (
2727
// ErrNoCode is returned by call and transact operations for which the requested
2828
// recipient contract to operate on does not exist in the state db or does not
2929
// have any code associated with it (i.e. suicided).
30-
//
31-
// Please note, this error string is part of the RPC API and is expected by the
32-
// native contract bindings to signal this particular error. Do not change this
33-
// as it will break all dependent code!
3430
var ErrNoCode = errors.New("no contract code at given address")
3531

3632
// ContractCaller defines the methods needed to allow operating with contract on a read
3733
// only basis.
3834
type ContractCaller interface {
35+
// HasCode checks if the contract at the given address has any code associated
36+
// with it or not. This is needed to differentiate between contract internal
37+
// errors and the local chain being out of sync.
38+
HasCode(contract common.Address, pending bool) (bool, error)
39+
3940
// ContractCall executes an Ethereum contract call with the specified data as
4041
// the input. The pending flag requests execution against the pending block, not
4142
// the stable head of the chain.
@@ -55,6 +56,11 @@ type ContractTransactor interface {
5556
// execution of a transaction.
5657
SuggestGasPrice() (*big.Int, error)
5758

59+
// HasCode checks if the contract at the given address has any code associated
60+
// with it or not. This is needed to differentiate between contract internal
61+
// errors and the local chain being out of sync.
62+
HasCode(contract common.Address, pending bool) (bool, error)
63+
5864
// EstimateGasLimit tries to estimate the gas needed to execute a specific
5965
// transaction based on the current pending state of the backend blockchain.
6066
// There is no guarantee that this is the true gas limit requirement as other
@@ -68,7 +74,38 @@ type ContractTransactor interface {
6874

6975
// ContractBackend defines the methods needed to allow operating with contract
7076
// on a read-write basis.
77+
//
78+
// This interface is essentially the union of ContractCaller and ContractTransactor
79+
// but due to a bug in the Go compiler (https://github.com/golang/go/issues/6977),
80+
// we cannot simply list it as the two interfaces. The other solution is to add a
81+
// third interface containing the common methods, but that convolutes the user API
82+
// as it introduces yet another parameter to require for initialization.
7183
type ContractBackend interface {
72-
ContractCaller
73-
ContractTransactor
84+
// HasCode checks if the contract at the given address has any code associated
85+
// with it or not. This is needed to differentiate between contract internal
86+
// errors and the local chain being out of sync.
87+
HasCode(contract common.Address, pending bool) (bool, error)
88+
89+
// ContractCall executes an Ethereum contract call with the specified data as
90+
// the input. The pending flag requests execution against the pending block, not
91+
// the stable head of the chain.
92+
ContractCall(contract common.Address, data []byte, pending bool) ([]byte, error)
93+
94+
// PendingAccountNonce retrieves the current pending nonce associated with an
95+
// account.
96+
PendingAccountNonce(account common.Address) (uint64, error)
97+
98+
// SuggestGasPrice retrieves the currently suggested gas price to allow a timely
99+
// execution of a transaction.
100+
SuggestGasPrice() (*big.Int, error)
101+
102+
// EstimateGasLimit tries to estimate the gas needed to execute a specific
103+
// transaction based on the current pending state of the backend blockchain.
104+
// There is no guarantee that this is the true gas limit requirement as other
105+
// transactions may be added or removed by miners, but it should provide a basis
106+
// for setting a reasonable default.
107+
EstimateGasLimit(sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error)
108+
109+
// SendTransaction injects the transaction into the pending pool for execution.
110+
SendTransaction(tx *types.Transaction) error
74111
}

accounts/abi/bind/backends/nil.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ func (*nilBackend) ContractCall(common.Address, []byte, bool) ([]byte, error) {
3838
func (*nilBackend) EstimateGasLimit(common.Address, *common.Address, *big.Int, []byte) (*big.Int, error) {
3939
panic("not implemented")
4040
}
41+
func (*nilBackend) HasCode(common.Address, bool) (bool, error) { panic("not implemented") }
4142
func (*nilBackend) SuggestGasPrice() (*big.Int, error) { panic("not implemented") }
4243
func (*nilBackend) PendingAccountNonce(common.Address) (uint64, error) { panic("not implemented") }
4344
func (*nilBackend) SendTransaction(*types.Transaction) error { panic("not implemented") }

accounts/abi/bind/backends/remote.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,26 @@ func (b *rpcBackend) request(method string, params []interface{}) (json.RawMessa
111111
return res.Result, nil
112112
}
113113

114+
// HasCode implements ContractVerifier.HasCode by retrieving any code associated
115+
// with the contract from the remote node, and checking its size.
116+
func (b *rpcBackend) HasCode(contract common.Address, pending bool) (bool, error) {
117+
// Execute the RPC code retrieval
118+
block := "latest"
119+
if pending {
120+
block = "pending"
121+
}
122+
res, err := b.request("eth_getCode", []interface{}{contract.Hex(), block})
123+
if err != nil {
124+
return false, err
125+
}
126+
var hex string
127+
if err := json.Unmarshal(res, &hex); err != nil {
128+
return false, err
129+
}
130+
// Convert the response back to a Go byte slice and return
131+
return len(common.FromHex(hex)) > 0, nil
132+
}
133+
114134
// ContractCall implements ContractCaller.ContractCall, delegating the execution of
115135
// a contract call to the remote node, returning the reply to for local processing.
116136
func (b *rpcBackend) ContractCall(contract common.Address, data []byte, pending bool) ([]byte, error) {

accounts/abi/bind/backends/simulated.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,16 @@ func (b *SimulatedBackend) Rollback() {
7878
b.pendingState, _ = state.New(b.pendingBlock.Root(), b.database)
7979
}
8080

81+
// HasCode implements ContractVerifier.HasCode, checking whether there is any
82+
// code associated with a certain account in the blockchain.
83+
func (b *SimulatedBackend) HasCode(contract common.Address, pending bool) (bool, error) {
84+
if pending {
85+
return len(b.pendingState.GetCode(contract)) > 0, nil
86+
}
87+
statedb, _ := b.blockchain.State()
88+
return len(statedb.GetCode(contract)) > 0, nil
89+
}
90+
8191
// ContractCall implements ContractCaller.ContractCall, executing the specified
8292
// contract with the given input data.
8393
func (b *SimulatedBackend) ContractCall(contract common.Address, data []byte, pending bool) ([]byte, error) {

accounts/abi/bind/base.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"errors"
2121
"fmt"
2222
"math/big"
23+
"sync/atomic"
2324

2425
"github.com/ethereum/go-ethereum/accounts/abi"
2526
"github.com/ethereum/go-ethereum/common"
@@ -56,6 +57,9 @@ type BoundContract struct {
5657
abi abi.ABI // Reflect based ABI to access the correct Ethereum methods
5758
caller ContractCaller // Read interface to interact with the blockchain
5859
transactor ContractTransactor // Write interface to interact with the blockchain
60+
61+
latestHasCode uint32 // Cached verification that the latest state contains code for this contract
62+
pendingHasCode uint32 // Cached verification that the pending state contains code for this contract
5963
}
6064

6165
// NewBoundContract creates a low level contract interface through which calls
@@ -96,6 +100,19 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string,
96100
if opts == nil {
97101
opts = new(CallOpts)
98102
}
103+
// Make sure we have a contract to operate on, and bail out otherwise
104+
if (opts.Pending && atomic.LoadUint32(&c.pendingHasCode) == 0) || (!opts.Pending && atomic.LoadUint32(&c.latestHasCode) == 0) {
105+
if code, err := c.caller.HasCode(c.address, opts.Pending); err != nil {
106+
return err
107+
} else if !code {
108+
return ErrNoCode
109+
}
110+
if opts.Pending {
111+
atomic.StoreUint32(&c.pendingHasCode, 1)
112+
} else {
113+
atomic.StoreUint32(&c.latestHasCode, 1)
114+
}
115+
}
99116
// Pack the input, call and unpack the results
100117
input, err := c.abi.Pack(method, params...)
101118
if err != nil {
@@ -153,6 +170,16 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
153170
}
154171
gasLimit := opts.GasLimit
155172
if gasLimit == nil {
173+
// Gas estimation cannot succeed without code for method invocations
174+
if contract != nil && atomic.LoadUint32(&c.pendingHasCode) == 0 {
175+
if code, err := c.transactor.HasCode(c.address, true); err != nil {
176+
return nil, err
177+
} else if !code {
178+
return nil, ErrNoCode
179+
}
180+
atomic.StoreUint32(&c.pendingHasCode, 1)
181+
}
182+
// If the contract surely has code (or code is not needed), estimate the transaction
156183
gasLimit, err = c.transactor.EstimateGasLimit(opts.From, contract, value, input)
157184
if err != nil {
158185
return nil, fmt.Errorf("failed to exstimate gas needed: %v", err)

eth/api.go

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,6 @@ import (
5252
"golang.org/x/net/context"
5353
)
5454

55-
// errNoCode is returned by call and transact operations for which the requested
56-
// recipient contract to operate on does not exist in the state db or does not
57-
// have any code associated with it (i.e. suicided).
58-
//
59-
// Please note, this error string is part of the RPC API and is expected by the
60-
// native contract bindings to signal this particular error. Do not change this
61-
// as it will break all dependent code!
62-
var errNoCode = errors.New("no contract code at given address")
63-
6455
const defaultGas = uint64(90000)
6556

6657
// blockByNumber is a commonly used helper function which retrieves and returns
@@ -755,12 +746,6 @@ func (s *PublicBlockChainAPI) doCall(args CallArgs, blockNr rpc.BlockNumber) (st
755746
}
756747
stateDb = stateDb.Copy()
757748

758-
// If there's no code to interact with, respond with an appropriate error
759-
if args.To != nil {
760-
if code := stateDb.GetCode(*args.To); len(code) == 0 {
761-
return "0x", nil, errNoCode
762-
}
763-
}
764749
// Retrieve the account state object to interact with
765750
var from *state.StateObject
766751
if args.From == (common.Address{}) {

eth/bind.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package eth
1919
import (
2020
"math/big"
2121

22-
"github.com/ethereum/go-ethereum/accounts/abi/bind"
2322
"github.com/ethereum/go-ethereum/common"
2423
"github.com/ethereum/go-ethereum/core/types"
2524
"github.com/ethereum/go-ethereum/rlp"
@@ -49,6 +48,17 @@ func NewContractBackend(eth *Ethereum) *ContractBackend {
4948
}
5049
}
5150

51+
// HasCode implements bind.ContractVerifier.HasCode by retrieving any code associated
52+
// with the contract from the local API, and checking its size.
53+
func (b *ContractBackend) HasCode(contract common.Address, pending bool) (bool, error) {
54+
block := rpc.LatestBlockNumber
55+
if pending {
56+
block = rpc.PendingBlockNumber
57+
}
58+
out, err := b.bcapi.GetCode(contract, block)
59+
return len(common.FromHex(out)) > 0, err
60+
}
61+
5262
// ContractCall implements bind.ContractCaller executing an Ethereum contract
5363
// call with the specified data as the input. The pending flag requests execution
5464
// against the pending block, not the stable head of the chain.
@@ -64,9 +74,6 @@ func (b *ContractBackend) ContractCall(contract common.Address, data []byte, pen
6474
}
6575
// Execute the call and convert the output back to Go types
6676
out, err := b.bcapi.Call(args, block)
67-
if err == errNoCode {
68-
err = bind.ErrNoCode
69-
}
7077
return common.FromHex(out), err
7178
}
7279

@@ -95,9 +102,6 @@ func (b *ContractBackend) EstimateGasLimit(sender common.Address, contract *comm
95102
Value: *rpc.NewHexNumber(value),
96103
Data: common.ToHex(data),
97104
})
98-
if err == errNoCode {
99-
err = bind.ErrNoCode
100-
}
101105
return out.BigInt(), err
102106
}
103107

0 commit comments

Comments
 (0)