accounts/abi/bind: use ethereum interfaces

In this commit, contract bindings and their backend start using the
Ethereum Go API interfaces offered by ethclient. This makes ethclient a
suitable replacement for the old remote backend and gets us one step
closer to the final stable Go API that is planned for go-ethereum 1.5.

The changes in detail:

* Pending state is optional for read only contract bindings.
  BoundContract attempts to discover the Pending* methods via an
  interface assertion. There are a couple of advantages to this:
  ContractCaller is just two methods and can be implemented on top of
  pretty much anything that provides Ethereum data. Since the backend
  interfaces are now disjoint, ContractBackend can simply be declared as
  a union of the reader and writer side.

* Caching of HasCode is removed. The caching could go wrong in case of
  chain reorganisations and removing it simplifies the code a lot.
  We'll figure out a performant way of providing ErrNoCode before the
  1.5 release.

* BoundContract now ensures that the backend receives a non-nil context
  with every call.
This commit is contained in:
Felix Lange
2016-08-22 14:01:28 +02:00
parent 056f15aa53
commit d62d5fe59a
4 changed files with 212 additions and 228 deletions

View File

@ -19,6 +19,7 @@ package eth
import (
"math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/internal/ethapi"
@ -50,47 +51,62 @@ 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(ctx context.Context, contract common.Address, pending bool) (bool, error) {
if ctx == nil {
ctx = context.Background()
}
block := rpc.LatestBlockNumber
if pending {
block = rpc.PendingBlockNumber
}
out, err := b.bcapi.GetCode(ctx, contract, block)
return len(common.FromHex(out)) > 0, err
// CodeAt retrieves any code associated with the contract from the local API.
func (b *ContractBackend) CodeAt(ctx context.Context, contract common.Address, blockNum *big.Int) ([]byte, error) {
out, err := b.bcapi.GetCode(ctx, contract, toBlockNumber(blockNum))
return common.FromHex(out), err
}
// CodeAt retrieves any code associated with the contract from the local API.
func (b *ContractBackend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
out, err := b.bcapi.GetCode(ctx, contract, rpc.PendingBlockNumber)
return common.FromHex(out), 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.
func (b *ContractBackend) ContractCall(ctx context.Context, contract common.Address, data []byte, pending bool) ([]byte, error) {
if ctx == nil {
ctx = context.Background()
}
// Convert the input args to the API spec
args := ethapi.CallArgs{
To: &contract,
Data: common.ToHex(data),
}
block := rpc.LatestBlockNumber
if pending {
block = rpc.PendingBlockNumber
}
// Execute the call and convert the output back to Go types
out, err := b.bcapi.Call(ctx, args, block)
func (b *ContractBackend) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNum *big.Int) ([]byte, error) {
out, err := b.bcapi.Call(ctx, toCallArgs(msg), toBlockNumber(blockNum))
return common.FromHex(out), 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.
func (b *ContractBackend) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) {
out, err := b.bcapi.Call(ctx, toCallArgs(msg), rpc.PendingBlockNumber)
return common.FromHex(out), err
}
func toCallArgs(msg ethereum.CallMsg) ethapi.CallArgs {
args := ethapi.CallArgs{
To: msg.To,
From: msg.From,
Data: common.ToHex(msg.Data),
}
if msg.Gas != nil {
args.Gas = *rpc.NewHexNumber(msg.Gas)
}
if msg.GasPrice != nil {
args.GasPrice = *rpc.NewHexNumber(msg.GasPrice)
}
if msg.Value != nil {
args.Value = *rpc.NewHexNumber(msg.Value)
}
return args
}
func toBlockNumber(num *big.Int) rpc.BlockNumber {
if num == nil {
return rpc.LatestBlockNumber
}
return rpc.BlockNumber(num.Int64())
}
// PendingAccountNonce implements bind.ContractTransactor retrieving the current
// pending nonce associated with an account.
func (b *ContractBackend) PendingAccountNonce(ctx context.Context, account common.Address) (uint64, error) {
if ctx == nil {
ctx = context.Background()
}
func (b *ContractBackend) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
out, err := b.txapi.GetTransactionCount(ctx, account, rpc.PendingBlockNumber)
return out.Uint64(), err
}
@ -98,9 +114,6 @@ func (b *ContractBackend) PendingAccountNonce(ctx context.Context, account commo
// SuggestGasPrice implements bind.ContractTransactor retrieving the currently
// suggested gas price to allow a timely execution of a transaction.
func (b *ContractBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
if ctx == nil {
ctx = context.Background()
}
return b.eapi.GasPrice(ctx)
}
@ -109,25 +122,14 @@ func (b *ContractBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error)
// 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.
func (b *ContractBackend) EstimateGasLimit(ctx context.Context, sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error) {
if ctx == nil {
ctx = context.Background()
}
out, err := b.bcapi.EstimateGas(ctx, ethapi.CallArgs{
From: sender,
To: contract,
Value: *rpc.NewHexNumber(value),
Data: common.ToHex(data),
})
func (b *ContractBackend) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (*big.Int, error) {
out, err := b.bcapi.EstimateGas(ctx, toCallArgs(msg))
return out.BigInt(), err
}
// SendTransaction implements bind.ContractTransactor injects the transaction
// into the pending pool for execution.
func (b *ContractBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error {
if ctx == nil {
ctx = context.Background()
}
raw, _ := rlp.EncodeToBytes(tx)
_, err := b.txapi.SendRawTransaction(ctx, common.ToHex(raw))
return err