Merge pull request #2159 from zsfelfoldi/light-backend

eth: separate common and full node-specific API and backend service
This commit is contained in:
Péter Szilágyi
2016-06-30 12:57:50 +03:00
committed by GitHub
22 changed files with 2296 additions and 1805 deletions

View File

@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"golang.org/x/net/context"
)
// ErrNoCode is returned by call and transact operations for which the requested
@ -35,12 +36,12 @@ 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)
HasCode(ctx context.Context, 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)
ContractCall(ctx context.Context, contract common.Address, data []byte, pending bool) ([]byte, error)
}
// ContractTransactor defines the methods needed to allow operating with contract
@ -50,26 +51,26 @@ type ContractCaller interface {
type ContractTransactor interface {
// PendingAccountNonce retrieves the current pending nonce associated with an
// account.
PendingAccountNonce(account common.Address) (uint64, error)
PendingAccountNonce(ctx context.Context, account common.Address) (uint64, error)
// SuggestGasPrice retrieves the currently suggested gas price to allow a timely
// execution of a transaction.
SuggestGasPrice() (*big.Int, error)
SuggestGasPrice(ctx context.Context) (*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)
HasCode(ctx context.Context, 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
// 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)
EstimateGasLimit(ctx context.Context, 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
SendTransaction(ctx context.Context, tx *types.Transaction) error
}
// ContractBackend defines the methods needed to allow operating with contract
@ -84,28 +85,28 @@ type ContractBackend 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)
HasCode(ctx context.Context, 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)
ContractCall(ctx context.Context, 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)
PendingAccountNonce(ctx context.Context, account common.Address) (uint64, error)
// SuggestGasPrice retrieves the currently suggested gas price to allow a timely
// execution of a transaction.
SuggestGasPrice() (*big.Int, error)
SuggestGasPrice(ctx context.Context) (*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)
EstimateGasLimit(ctx context.Context, 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
SendTransaction(ctx context.Context, tx *types.Transaction) error
}

View File

@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"golang.org/x/net/context"
)
// This nil assignment ensures compile time that nilBackend implements bind.ContractBackend.
@ -32,16 +33,22 @@ var _ bind.ContractBackend = (*nilBackend)(nil)
// wrappers without calling any methods on them.
type nilBackend struct{}
func (*nilBackend) ContractCall(common.Address, []byte, bool) ([]byte, error) {
func (*nilBackend) ContractCall(context.Context, common.Address, []byte, bool) ([]byte, error) {
panic("not implemented")
}
func (*nilBackend) EstimateGasLimit(common.Address, *common.Address, *big.Int, []byte) (*big.Int, error) {
func (*nilBackend) EstimateGasLimit(context.Context, common.Address, *common.Address, *big.Int, []byte) (*big.Int, error) {
panic("not implemented")
}
func (*nilBackend) HasCode(context.Context, common.Address, bool) (bool, error) {
panic("not implemented")
}
func (*nilBackend) SuggestGasPrice(context.Context) (*big.Int, error) { panic("not implemented") }
func (*nilBackend) PendingAccountNonce(context.Context, common.Address) (uint64, error) {
panic("not implemented")
}
func (*nilBackend) SendTransaction(context.Context, *types.Transaction) 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") }
// NewNilBackend creates a new binding backend that can be used for instantiation
// but will panic on any invocation. Its sole purpose is to help testing.

View File

@ -28,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
"golang.org/x/net/context"
)
// This nil assignment ensures compile time that rpcBackend implements bind.ContractBackend.
@ -80,18 +81,23 @@ type failure struct {
//
// This is currently painfully non-concurrent, but it will have to do until we
// find the time for niceties like this :P
func (b *rpcBackend) request(method string, params []interface{}) (json.RawMessage, error) {
func (b *rpcBackend) request(ctx context.Context, method string, params []interface{}) (json.RawMessage, error) {
b.lock.Lock()
defer b.lock.Unlock()
if ctx == nil {
ctx = context.Background()
}
// Ugly hack to serialize an empty list properly
if params == nil {
params = []interface{}{}
}
// Assemble the request object
reqID := int(atomic.AddUint32(&b.autoid, 1))
req := &request{
JSONRPC: "2.0",
ID: int(atomic.AddUint32(&b.autoid, 1)),
ID: reqID,
Method: method,
Params: params,
}
@ -99,8 +105,17 @@ func (b *rpcBackend) request(method string, params []interface{}) (json.RawMessa
return nil, err
}
res := new(response)
if err := b.client.Recv(res); err != nil {
return nil, err
errc := make(chan error, 1)
go func() {
errc <- b.client.Recv(res)
}()
select {
case err := <-errc:
if err != nil {
return nil, err
}
case <-ctx.Done():
return nil, ctx.Err()
}
if res.Error != nil {
if res.Error.Message == bind.ErrNoCode.Error() {
@ -113,13 +128,13 @@ func (b *rpcBackend) request(method string, params []interface{}) (json.RawMessa
// 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) {
func (b *rpcBackend) HasCode(ctx context.Context, 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})
res, err := b.request(ctx, "eth_getCode", []interface{}{contract.Hex(), block})
if err != nil {
return false, err
}
@ -133,7 +148,7 @@ func (b *rpcBackend) HasCode(contract common.Address, pending bool) (bool, error
// 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) {
func (b *rpcBackend) ContractCall(ctx context.Context, contract common.Address, data []byte, pending bool) ([]byte, error) {
// Pack up the request into an RPC argument
args := struct {
To common.Address `json:"to"`
@ -147,7 +162,7 @@ func (b *rpcBackend) ContractCall(contract common.Address, data []byte, pending
if pending {
block = "pending"
}
res, err := b.request("eth_call", []interface{}{args, block})
res, err := b.request(ctx, "eth_call", []interface{}{args, block})
if err != nil {
return nil, err
}
@ -161,8 +176,8 @@ func (b *rpcBackend) ContractCall(contract common.Address, data []byte, pending
// PendingAccountNonce implements ContractTransactor.PendingAccountNonce, delegating
// the current account nonce retrieval to the remote node.
func (b *rpcBackend) PendingAccountNonce(account common.Address) (uint64, error) {
res, err := b.request("eth_getTransactionCount", []interface{}{account.Hex(), "pending"})
func (b *rpcBackend) PendingAccountNonce(ctx context.Context, account common.Address) (uint64, error) {
res, err := b.request(ctx, "eth_getTransactionCount", []interface{}{account.Hex(), "pending"})
if err != nil {
return 0, err
}
@ -179,8 +194,8 @@ func (b *rpcBackend) PendingAccountNonce(account common.Address) (uint64, error)
// SuggestGasPrice implements ContractTransactor.SuggestGasPrice, delegating the
// gas price oracle request to the remote node.
func (b *rpcBackend) SuggestGasPrice() (*big.Int, error) {
res, err := b.request("eth_gasPrice", nil)
func (b *rpcBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
res, err := b.request(ctx, "eth_gasPrice", nil)
if err != nil {
return nil, err
}
@ -197,7 +212,7 @@ func (b *rpcBackend) SuggestGasPrice() (*big.Int, error) {
// EstimateGasLimit implements ContractTransactor.EstimateGasLimit, delegating
// the gas estimation to the remote node.
func (b *rpcBackend) EstimateGasLimit(sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error) {
func (b *rpcBackend) EstimateGasLimit(ctx context.Context, sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error) {
// Pack up the request into an RPC argument
args := struct {
From common.Address `json:"from"`
@ -211,7 +226,7 @@ func (b *rpcBackend) EstimateGasLimit(sender common.Address, contract *common.Ad
Value: rpc.NewHexNumber(value),
}
// Execute the RPC call and retrieve the response
res, err := b.request("eth_estimateGas", []interface{}{args})
res, err := b.request(ctx, "eth_estimateGas", []interface{}{args})
if err != nil {
return nil, err
}
@ -228,12 +243,12 @@ func (b *rpcBackend) EstimateGasLimit(sender common.Address, contract *common.Ad
// SendTransaction implements ContractTransactor.SendTransaction, delegating the
// raw transaction injection to the remote node.
func (b *rpcBackend) SendTransaction(tx *types.Transaction) error {
func (b *rpcBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error {
data, err := rlp.EncodeToBytes(tx)
if err != nil {
return err
}
res, err := b.request("eth_sendRawTransaction", []interface{}{common.ToHex(data)})
res, err := b.request(ctx, "eth_sendRawTransaction", []interface{}{common.ToHex(data)})
if err != nil {
return err
}

View File

@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"golang.org/x/net/context"
)
// Default chain configuration which sets homestead phase at block 0 (i.e. no frontier)
@ -80,7 +81,7 @@ func (b *SimulatedBackend) Rollback() {
// 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) {
func (b *SimulatedBackend) HasCode(ctx context.Context, contract common.Address, pending bool) (bool, error) {
if pending {
return len(b.pendingState.GetCode(contract)) > 0, nil
}
@ -90,7 +91,7 @@ func (b *SimulatedBackend) HasCode(contract common.Address, pending bool) (bool,
// 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) {
func (b *SimulatedBackend) ContractCall(ctx context.Context, contract common.Address, data []byte, pending bool) ([]byte, error) {
// Create a copy of the current state db to screw around with
var (
block *types.Block
@ -129,20 +130,20 @@ func (b *SimulatedBackend) ContractCall(contract common.Address, data []byte, pe
// PendingAccountNonce implements ContractTransactor.PendingAccountNonce, retrieving
// the nonce currently pending for the account.
func (b *SimulatedBackend) PendingAccountNonce(account common.Address) (uint64, error) {
func (b *SimulatedBackend) PendingAccountNonce(ctx context.Context, account common.Address) (uint64, error) {
return b.pendingState.GetOrNewStateObject(account).Nonce(), nil
}
// SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated
// chain doens't have miners, we just return a gas price of 1 for any call.
func (b *SimulatedBackend) SuggestGasPrice() (*big.Int, error) {
func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
return big.NewInt(1), nil
}
// EstimateGasLimit implements ContractTransactor.EstimateGasLimit, executing the
// requested code against the currently pending block/state and returning the used
// gas.
func (b *SimulatedBackend) EstimateGasLimit(sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error) {
func (b *SimulatedBackend) EstimateGasLimit(ctx context.Context, sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error) {
// Create a copy of the currently pending state db to screw around with
var (
block = b.pendingBlock
@ -177,7 +178,7 @@ func (b *SimulatedBackend) EstimateGasLimit(sender common.Address, contract *com
// SendTransaction implements ContractTransactor.SendTransaction, delegating the raw
// transaction injection to the remote node.
func (b *SimulatedBackend) SendTransaction(tx *types.Transaction) error {
func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error {
blocks, _ := core.GenerateChain(b.blockchain.CurrentBlock(), b.database, 1, func(number int, block *core.BlockGen) {
for _, tx := range b.pendingBlock.Transactions() {
block.AddTx(tx)

View File

@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"golang.org/x/net/context"
)
// SignerFn is a signer function callback when a contract requires a method to
@ -35,6 +36,8 @@ type SignerFn func(common.Address, *types.Transaction) (*types.Transaction, erro
// CallOpts is the collection of options to fine tune a contract call request.
type CallOpts struct {
Pending bool // Whether to operate on the pending state or the last known one
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
}
// TransactOpts is the collection of authorization data required to create a
@ -47,6 +50,8 @@ type TransactOpts struct {
Value *big.Int // Funds to transfer along along the transaction (nil = 0 = no funds)
GasPrice *big.Int // Gas price to use for the transaction execution (nil = gas price oracle)
GasLimit *big.Int // Gas limit to set for the transaction execution (nil = estimate + 10%)
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
}
// BoundContract is the base wrapper object that reflects a contract on the
@ -102,7 +107,7 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string,
}
// 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 {
if code, err := c.caller.HasCode(opts.Context, c.address, opts.Pending); err != nil {
return err
} else if !code {
return ErrNoCode
@ -118,7 +123,7 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string,
if err != nil {
return err
}
output, err := c.caller.ContractCall(c.address, input, opts.Pending)
output, err := c.caller.ContractCall(opts.Context, c.address, input, opts.Pending)
if err != nil {
return err
}
@ -153,7 +158,7 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
}
nonce := uint64(0)
if opts.Nonce == nil {
nonce, err = c.transactor.PendingAccountNonce(opts.From)
nonce, err = c.transactor.PendingAccountNonce(opts.Context, opts.From)
if err != nil {
return nil, fmt.Errorf("failed to retrieve account nonce: %v", err)
}
@ -163,7 +168,7 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
// Figure out the gas allowance and gas price values
gasPrice := opts.GasPrice
if gasPrice == nil {
gasPrice, err = c.transactor.SuggestGasPrice()
gasPrice, err = c.transactor.SuggestGasPrice(opts.Context)
if err != nil {
return nil, fmt.Errorf("failed to suggest gas price: %v", err)
}
@ -172,7 +177,7 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
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 {
if code, err := c.transactor.HasCode(opts.Context, c.address, true); err != nil {
return nil, err
} else if !code {
return nil, ErrNoCode
@ -180,7 +185,7 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
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)
gasLimit, err = c.transactor.EstimateGasLimit(opts.Context, opts.From, contract, value, input)
if err != nil {
return nil, fmt.Errorf("failed to exstimate gas needed: %v", err)
}
@ -199,7 +204,7 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
if err != nil {
return nil, err
}
if err := c.transactor.SendTransaction(signedTx); err != nil {
if err := c.transactor.SendTransaction(opts.Context, signedTx); err != nil {
return nil, err
}
return signedTx, nil

View File

@ -34,6 +34,8 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc"
)
var (
@ -340,3 +342,23 @@ func zeroKey(k *ecdsa.PrivateKey) {
b[i] = 0
}
}
// APIs implements node.Service
func (am *Manager) APIs() []rpc.API {
return nil
}
// Protocols implements node.Service
func (am *Manager) Protocols() []p2p.Protocol {
return nil
}
// Start implements node.Service
func (am *Manager) Start(srvr *p2p.Server) error {
return nil
}
// Stop implements node.Service
func (am *Manager) Stop() error {
return nil
}