aboutsummaryrefslogblamecommitdiffstats
path: root/accounts/abi/bind/backends/remote.go
blob: d903cbc8f740c6a075ad22b77c65e51f273cf705 (plain) (tree)































                                                                                  


                                                                                            






















                                                                                  

                                                                                                     





                                                                                                        

                                                                                                       
                                                                                         


                                                                                              





                                                                                          



                                                                              


                                                                                            






                                                        

                                                             


                                
                                                  


                               
                                                  

                               




                                                                             



                              



















                                                                                   



























                                                                                                       


                                                                                    







                                                                                                 




                                                                  

 


                                                                                







                                                         




                                                                    

 


                                                                                                                                       




















                                                                     




                                                                       

 

                                                                                














                                                                                          
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package backends

import (
    "encoding/json"
    "fmt"
    "math/big"
    "sync"
    "sync/atomic"

    "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"
    "github.com/ethereum/go-ethereum/rpc"
)

// This nil assignment ensures compile time that rpcBackend implements bind.ContractBackend.
var _ bind.ContractBackend = (*rpcBackend)(nil)

// rpcBackend implements bind.ContractBackend, and acts as the data provider to
// Ethereum contracts bound to Go structs. It uses an RPC connection to delegate
// all its functionality.
//
// Note: The current implementation is a blocking one. This should be replaced
// by a proper async version when a real RPC client is created.
type rpcBackend struct {
    client rpc.Client // RPC client connection to interact with an API server
    autoid uint32     // ID number to use for the next API request
    lock   sync.Mutex // Singleton access until we get to request multiplexing
}

// NewRPCBackend creates a new binding backend to an RPC provider that can be
// used to interact with remote contracts.
func NewRPCBackend(client rpc.Client) bind.ContractBackend {
    return &rpcBackend{
        client: client,
    }
}

// request is a JSON RPC request package assembled internally from the client
// method calls.
type request struct {
    JSONRPC string        `json:"jsonrpc"` // Version of the JSON RPC protocol, always set to 2.0
    ID      int           `json:"id"`      // Auto incrementing ID number for this request
    Method  string        `json:"method"`  // Remote procedure name to invoke on the server
    Params  []interface{} `json:"params"`  // List of parameters to pass through (keep types simple)
}

// response is a JSON RPC response package sent back from the API server.
type response struct {
    JSONRPC string          `json:"jsonrpc"` // Version of the JSON RPC protocol, always set to 2.0
    ID      int             `json:"id"`      // Auto incrementing ID number for this request
    Error   *failure        `json:"error"`   // Any error returned by the remote side
    Result  json.RawMessage `json:"result"`  // Whatever the remote side sends us in reply
}

// failure is a JSON RPC response error field sent back from the API server.
type failure struct {
    Code    int    `json:"code"`    // JSON RPC error code associated with the failure
    Message string `json:"message"` // Specific error message of the failure
}

// request forwards an API request to the RPC server, and parses the response.
//
// 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) {
    b.lock.Lock()
    defer b.lock.Unlock()

    // Ugly hack to serialize an empty list properly
    if params == nil {
        params = []interface{}{}
    }
    // Assemble the request object
    req := &request{
        JSONRPC: "2.0",
        ID:      int(atomic.AddUint32(&b.autoid, 1)),
        Method:  method,
        Params:  params,
    }
    if err := b.client.Send(req); err != nil {
        return nil, err
    }
    res := new(response)
    if err := b.client.Recv(res); err != nil {
        return nil, err
    }
    if res.Error != nil {
        if res.Error.Message == bind.ErrNoCode.Error() {
            return nil, bind.ErrNoCode
        }
        return nil, fmt.Errorf("remote error: %s", res.Error.Message)
    }
    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) {
    // Pack up the request into an RPC argument
    args := struct {
        To   common.Address `json:"to"`
        Data string         `json:"data"`
    }{
        To:   contract,
        Data: common.ToHex(data),
    }
    // Execute the RPC call and retrieve the response
    block := "latest"
    if pending {
        block = "pending"
    }
    res, err := b.request("eth_call", []interface{}{args, block})
    if err != nil {
        return nil, err
    }
    var hex string
    if err := json.Unmarshal(res, &hex); err != nil {
        return nil, err
    }
    // Convert the response back to a Go byte slice and return
    return common.FromHex(hex), nil
}

// 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"})
    if err != nil {
        return 0, err
    }
    var hex string
    if err := json.Unmarshal(res, &hex); err != nil {
        return 0, err
    }
    nonce, ok := new(big.Int).SetString(hex, 0)
    if !ok {
        return 0, fmt.Errorf("invalid nonce hex: %s", hex)
    }
    return nonce.Uint64(), nil
}

// 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)
    if err != nil {
        return nil, err
    }
    var hex string
    if err := json.Unmarshal(res, &hex); err != nil {
        return nil, err
    }
    price, ok := new(big.Int).SetString(hex, 0)
    if !ok {
        return nil, fmt.Errorf("invalid price hex: %s", hex)
    }
    return price, nil
}

// 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) {
    // Pack up the request into an RPC argument
    args := struct {
        From  common.Address  `json:"from"`
        To    *common.Address `json:"to"`
        Value *rpc.HexNumber  `json:"value"`
        Data  string          `json:"data"`
    }{
        From:  sender,
        To:    contract,
        Data:  common.ToHex(data),
        Value: rpc.NewHexNumber(value),
    }
    // Execute the RPC call and retrieve the response
    res, err := b.request("eth_estimateGas", []interface{}{args})
    if err != nil {
        return nil, err
    }
    var hex string
    if err := json.Unmarshal(res, &hex); err != nil {
        return nil, err
    }
    estimate, ok := new(big.Int).SetString(hex, 0)
    if !ok {
        return nil, fmt.Errorf("invalid estimate hex: %s", hex)
    }
    return estimate, nil
}

// SendTransaction implements ContractTransactor.SendTransaction, delegating the
// raw transaction injection to the remote node.
func (b *rpcBackend) SendTransaction(tx *types.Transaction) error {
    data, err := rlp.EncodeToBytes(tx)
    if err != nil {
        return err
    }
    res, err := b.request("eth_sendRawTransaction", []interface{}{common.ToHex(data)})
    if err != nil {
        return err
    }
    var hex string
    if err := json.Unmarshal(res, &hex); err != nil {
        return err
    }
    return nil
}