aboutsummaryrefslogtreecommitdiffstats
path: root/accounts
diff options
context:
space:
mode:
authorPéter Szilágyi <peterke@gmail.com>2016-05-20 17:29:28 +0800
committerPéter Szilágyi <peterke@gmail.com>2016-05-20 17:29:28 +0800
commit1580ec180414bce1e37acc614bc2445f778efb75 (patch)
tree7c8276f3f1558b5ce62edd0bff87745956084a4c /accounts
parente798e4fd750745cec99c5a531e42998d9a7be85e (diff)
downloaddexon-1580ec180414bce1e37acc614bc2445f778efb75.tar
dexon-1580ec180414bce1e37acc614bc2445f778efb75.tar.gz
dexon-1580ec180414bce1e37acc614bc2445f778efb75.tar.bz2
dexon-1580ec180414bce1e37acc614bc2445f778efb75.tar.lz
dexon-1580ec180414bce1e37acc614bc2445f778efb75.tar.xz
dexon-1580ec180414bce1e37acc614bc2445f778efb75.tar.zst
dexon-1580ec180414bce1e37acc614bc2445f778efb75.zip
accounts/abi/bind, eth: rely on getCode for sanity checks, not estimate and call
Diffstat (limited to 'accounts')
-rw-r--r--accounts/abi/bind/backend.go49
-rw-r--r--accounts/abi/bind/backends/nil.go1
-rw-r--r--accounts/abi/bind/backends/remote.go20
-rw-r--r--accounts/abi/bind/backends/simulated.go10
-rw-r--r--accounts/abi/bind/base.go27
5 files changed, 101 insertions, 6 deletions
diff --git a/accounts/abi/bind/backend.go b/accounts/abi/bind/backend.go
index 604e1ef26..65806aef4 100644
--- a/accounts/abi/bind/backend.go
+++ b/accounts/abi/bind/backend.go
@@ -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.
@@ -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
@@ -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
}
diff --git a/accounts/abi/bind/backends/nil.go b/accounts/abi/bind/backends/nil.go
index 3b1e6dce7..f10bb61ac 100644
--- a/accounts/abi/bind/backends/nil.go
+++ b/accounts/abi/bind/backends/nil.go
@@ -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") }
diff --git a/accounts/abi/bind/backends/remote.go b/accounts/abi/bind/backends/remote.go
index 9b3647192..d903cbc8f 100644
--- a/accounts/abi/bind/backends/remote.go
+++ b/accounts/abi/bind/backends/remote.go
@@ -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) {
diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go
index 4866c4f58..54b1ce603 100644
--- a/accounts/abi/bind/backends/simulated.go
+++ b/accounts/abi/bind/backends/simulated.go
@@ -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) {
diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go
index 06621c5ad..75e8d5bc8 100644
--- a/accounts/abi/bind/base.go
+++ b/accounts/abi/bind/base.go
@@ -20,6 +20,7 @@ import (
"errors"
"fmt"
"math/big"
+ "sync/atomic"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
@@ -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
@@ -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 {
@@ -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)