Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

accounts/abi/bind, eth: rely on getCode for sanity checks, not estimate and call #2589

Merged
merged 1 commit into from
May 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 43 additions & 6 deletions accounts/abi/bind/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,16 @@ import (
// ErrNoCode is returned by call and transact operations for which the requested
// recipient contract to operate on does not exist in the state db or does not
// have any code associated with it (i.e. suicided).
//
// Please note, this error string is part of the RPC API and is expected by the
// native contract bindings to signal this particular error. Do not change this
// as it will break all dependent code!
var ErrNoCode = errors.New("no contract code at given address")

// ContractCaller defines the methods needed to allow operating with contract on a read
// only basis.
type ContractCaller interface {
// HasCode checks if the contract at the given address has any code associated
// with it or not. This is needed to differentiate between contract internal
// errors and the local chain being out of sync.
HasCode(contract common.Address, pending bool) (bool, error)

// ContractCall executes an Ethereum contract call with the specified data as
// the input. The pending flag requests execution against the pending block, not
// the stable head of the chain.
Expand All @@ -55,6 +56,11 @@ type ContractTransactor interface {
// execution of a transaction.
SuggestGasPrice() (*big.Int, error)

// HasCode checks if the contract at the given address has any code associated
// with it or not. This is needed to differentiate between contract internal
// errors and the local chain being out of sync.
HasCode(contract common.Address, pending bool) (bool, error)

// EstimateGasLimit tries to estimate the gas needed to execute a specific
// transaction based on the current pending state of the backend blockchain.
// There is no guarantee that this is the true gas limit requirement as other
Expand All @@ -68,7 +74,38 @@ type ContractTransactor interface {

// ContractBackend defines the methods needed to allow operating with contract
// on a read-write basis.
//
// This interface is essentially the union of ContractCaller and ContractTransactor
// but due to a bug in the Go compiler (https://github.com/golang/go/issues/6977),
// we cannot simply list it as the two interfaces. The other solution is to add a
// third interface containing the common methods, but that convolutes the user API
// as it introduces yet another parameter to require for initialization.
type ContractBackend interface {
ContractCaller
ContractTransactor
// HasCode checks if the contract at the given address has any code associated
// with it or not. This is needed to differentiate between contract internal
// errors and the local chain being out of sync.
HasCode(contract common.Address, pending bool) (bool, error)

// ContractCall executes an Ethereum contract call with the specified data as
// the input. The pending flag requests execution against the pending block, not
// the stable head of the chain.
ContractCall(contract common.Address, data []byte, pending bool) ([]byte, error)

// PendingAccountNonce retrieves the current pending nonce associated with an
// account.
PendingAccountNonce(account common.Address) (uint64, error)

// SuggestGasPrice retrieves the currently suggested gas price to allow a timely
// execution of a transaction.
SuggestGasPrice() (*big.Int, error)

// EstimateGasLimit tries to estimate the gas needed to execute a specific
// transaction based on the current pending state of the backend blockchain.
// There is no guarantee that this is the true gas limit requirement as other
// transactions may be added or removed by miners, but it should provide a basis
// for setting a reasonable default.
EstimateGasLimit(sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error)

// SendTransaction injects the transaction into the pending pool for execution.
SendTransaction(tx *types.Transaction) error
}
1 change: 1 addition & 0 deletions accounts/abi/bind/backends/nil.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func (*nilBackend) ContractCall(common.Address, []byte, bool) ([]byte, error) {
func (*nilBackend) EstimateGasLimit(common.Address, *common.Address, *big.Int, []byte) (*big.Int, error) {
panic("not implemented")
}
func (*nilBackend) HasCode(common.Address, bool) (bool, error) { panic("not implemented") }
func (*nilBackend) SuggestGasPrice() (*big.Int, error) { panic("not implemented") }
func (*nilBackend) PendingAccountNonce(common.Address) (uint64, error) { panic("not implemented") }
func (*nilBackend) SendTransaction(*types.Transaction) error { panic("not implemented") }
Expand Down
20 changes: 20 additions & 0 deletions accounts/abi/bind/backends/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,26 @@ func (b *rpcBackend) request(method string, params []interface{}) (json.RawMessa
return res.Result, nil
}

// HasCode implements ContractVerifier.HasCode by retrieving any code associated
// with the contract from the remote node, and checking its size.
func (b *rpcBackend) HasCode(contract common.Address, pending bool) (bool, error) {
// Execute the RPC code retrieval
block := "latest"
if pending {
block = "pending"
}
res, err := b.request("eth_getCode", []interface{}{contract.Hex(), block})
if err != nil {
return false, err
}
var hex string
if err := json.Unmarshal(res, &hex); err != nil {
return false, err
}
// Convert the response back to a Go byte slice and return
return len(common.FromHex(hex)) > 0, nil
}

// ContractCall implements ContractCaller.ContractCall, delegating the execution of
// a contract call to the remote node, returning the reply to for local processing.
func (b *rpcBackend) ContractCall(contract common.Address, data []byte, pending bool) ([]byte, error) {
Expand Down
10 changes: 10 additions & 0 deletions accounts/abi/bind/backends/simulated.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ func (b *SimulatedBackend) Rollback() {
b.pendingState, _ = state.New(b.pendingBlock.Root(), b.database)
}

// HasCode implements ContractVerifier.HasCode, checking whether there is any
// code associated with a certain account in the blockchain.
func (b *SimulatedBackend) HasCode(contract common.Address, pending bool) (bool, error) {
if pending {
return len(b.pendingState.GetCode(contract)) > 0, nil
}
statedb, _ := b.blockchain.State()
return len(statedb.GetCode(contract)) > 0, nil
}

// ContractCall implements ContractCaller.ContractCall, executing the specified
// contract with the given input data.
func (b *SimulatedBackend) ContractCall(contract common.Address, data []byte, pending bool) ([]byte, error) {
Expand Down
27 changes: 27 additions & 0 deletions accounts/abi/bind/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"errors"
"fmt"
"math/big"
"sync/atomic"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -56,6 +57,9 @@ type BoundContract struct {
abi abi.ABI // Reflect based ABI to access the correct Ethereum methods
caller ContractCaller // Read interface to interact with the blockchain
transactor ContractTransactor // Write interface to interact with the blockchain

latestHasCode uint32 // Cached verification that the latest state contains code for this contract
pendingHasCode uint32 // Cached verification that the pending state contains code for this contract
}

// NewBoundContract creates a low level contract interface through which calls
Expand Down Expand Up @@ -96,6 +100,19 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string,
if opts == nil {
opts = new(CallOpts)
}
// Make sure we have a contract to operate on, and bail out otherwise
if (opts.Pending && atomic.LoadUint32(&c.pendingHasCode) == 0) || (!opts.Pending && atomic.LoadUint32(&c.latestHasCode) == 0) {
if code, err := c.caller.HasCode(c.address, opts.Pending); err != nil {
return err
} else if !code {
return ErrNoCode
}
if opts.Pending {
atomic.StoreUint32(&c.pendingHasCode, 1)
} else {
atomic.StoreUint32(&c.latestHasCode, 1)
}
}
// Pack the input, call and unpack the results
input, err := c.abi.Pack(method, params...)
if err != nil {
Expand Down Expand Up @@ -153,6 +170,16 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
}
gasLimit := opts.GasLimit
if gasLimit == nil {
// Gas estimation cannot succeed without code for method invocations
if contract != nil && atomic.LoadUint32(&c.pendingHasCode) == 0 {
if code, err := c.transactor.HasCode(c.address, true); err != nil {
return nil, err
} else if !code {
return nil, ErrNoCode
}
atomic.StoreUint32(&c.pendingHasCode, 1)
}
// If the contract surely has code (or code is not needed), estimate the transaction
gasLimit, err = c.transactor.EstimateGasLimit(opts.From, contract, value, input)
if err != nil {
return nil, fmt.Errorf("failed to exstimate gas needed: %v", err)
Expand Down
15 changes: 0 additions & 15 deletions eth/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,6 @@ import (
"golang.org/x/net/context"
)

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

const defaultGas = uint64(90000)

// blockByNumber is a commonly used helper function which retrieves and returns
Expand Down Expand Up @@ -717,12 +708,6 @@ func (s *PublicBlockChainAPI) doCall(args CallArgs, blockNr rpc.BlockNumber) (st
}
stateDb = stateDb.Copy()

// If there's no code to interact with, respond with an appropriate error
if args.To != nil {
if code := stateDb.GetCode(*args.To); len(code) == 0 {
return "0x", nil, errNoCode
}
}
// Retrieve the account state object to interact with
var from *state.StateObject
if args.From == (common.Address{}) {
Expand Down
18 changes: 11 additions & 7 deletions eth/bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package eth
import (
"math/big"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
Expand Down Expand Up @@ -49,6 +48,17 @@ func NewContractBackend(eth *Ethereum) *ContractBackend {
}
}

// HasCode implements bind.ContractVerifier.HasCode by retrieving any code associated
// with the contract from the local API, and checking its size.
func (b *ContractBackend) HasCode(contract common.Address, pending bool) (bool, error) {
block := rpc.LatestBlockNumber
if pending {
block = rpc.PendingBlockNumber
}
out, err := b.bcapi.GetCode(contract, block)
return len(common.FromHex(out)) > 0, err
}

// ContractCall implements bind.ContractCaller executing an Ethereum contract
// call with the specified data as the input. The pending flag requests execution
// against the pending block, not the stable head of the chain.
Expand All @@ -64,9 +74,6 @@ func (b *ContractBackend) ContractCall(contract common.Address, data []byte, pen
}
// Execute the call and convert the output back to Go types
out, err := b.bcapi.Call(args, block)
if err == errNoCode {
err = bind.ErrNoCode
}
return common.FromHex(out), err
}

Expand Down Expand Up @@ -95,9 +102,6 @@ func (b *ContractBackend) EstimateGasLimit(sender common.Address, contract *comm
Value: *rpc.NewHexNumber(value),
Data: common.ToHex(data),
})
if err == errNoCode {
err = bind.ErrNoCode
}
return out.BigInt(), err
}

Expand Down