Skip to content

Commit

Permalink
Use single codepath for sending transaction for local and remote nodes
Browse files Browse the repository at this point in the history
Previously procedure for sending transactions was different based on
which type of node was used, local (light node) or remote.

This change removes separate code path for working with light node,
and the only difference will be backend for rpc client. In case of
light node all of the communication will be done over inproc dial.

Also there is a couple of related changes in this PR:
- new EthereumTransactor that provides higher level API for working
  with ethereum network, and it is fully conformant with ethclient
- new test rpc service that improves flexibility and coverage of
  txqueue manager tests
  • Loading branch information
dshulyak committed Jan 3, 2018
1 parent 373fc86 commit 316e0f8
Show file tree
Hide file tree
Showing 6 changed files with 356 additions and 186 deletions.
6 changes: 4 additions & 2 deletions geth/api/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,6 @@ func (m *StatusBackend) StartNode(config *params.NodeConfig) (<-chan struct{}, e
return nil, err
}

m.txQueueManager.Start()

m.nodeReady = make(chan struct{}, 1)
go m.onNodeStart(nodeStarted, m.nodeReady) // waits on nodeStarted, writes to backendReady

Expand All @@ -102,6 +100,10 @@ func (m *StatusBackend) StartNode(config *params.NodeConfig) (<-chan struct{}, e
func (m *StatusBackend) onNodeStart(nodeStarted <-chan struct{}, backendReady chan struct{}) {
<-nodeStarted

// tx queue manager should be started after node is started, it depends
// on rpc client being created
m.txQueueManager.Start()

if err := m.registerHandlers(); err != nil {
log.Error("Handler registration failed", "err", err)
}
Expand Down
93 changes: 93 additions & 0 deletions geth/txqueue/ethtxclient.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package txqueue

import (
"context"
"math/big"

ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/status-im/status-go/geth/rpc"
)

// EthereumTransactor provides methods to create transactions for ethereum network.
type EthereumTransactor interface {
PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
ethereum.GasEstimator
ethereum.GasPricer
ethereum.TransactionSender
}

// EthTxClient wraps common API methods that are used to send transaction.
type EthTxClient struct {
c *rpc.Client
}

func NewEthTxClient(client *rpc.Client) *EthTxClient {
return &EthTxClient{c: client}
}

// PendingNonceAt returns the account nonce of the given account in the pending state.
// This is the nonce that should be used for the next transaction.
func (ec *EthTxClient) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
var result hexutil.Uint64
err := ec.c.CallContext(ctx, &result, "eth_getTransactionCount", account, "pending")
return uint64(result), err
}

// SuggestGasPrice retrieves the currently suggested gas price to allow a timely
// execution of a transaction.
func (ec *EthTxClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
var hex hexutil.Big
if err := ec.c.CallContext(ctx, &hex, "eth_gasPrice"); err != nil {
return nil, err
}
return (*big.Int)(&hex), nil
}

// EstimateGas 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.
func (ec *EthTxClient) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (*big.Int, error) {
var hex hexutil.Big
err := ec.c.CallContext(ctx, &hex, "eth_estimateGas", toCallArg(msg))
if err != nil {
return nil, err
}
return (*big.Int)(&hex), nil
}

// SendTransaction injects a signed transaction into the pending pool for execution.
//
// If the transaction was a contract creation use the TransactionReceipt method to get the
// contract address after the transaction has been mined.
func (ec *EthTxClient) SendTransaction(ctx context.Context, tx *types.Transaction) error {
data, err := rlp.EncodeToBytes(tx)
if err != nil {
return err
}
return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", common.ToHex(data))
}

func toCallArg(msg ethereum.CallMsg) interface{} {
arg := map[string]interface{}{
"from": msg.From,
"to": msg.To,
}
if len(msg.Data) > 0 {
arg["data"] = hexutil.Bytes(msg.Data)
}
if msg.Value != nil {
arg["value"] = (*hexutil.Big)(msg.Value)
}
if msg.Gas != nil {
arg["gas"] = (*hexutil.Big)(msg.Gas)
}
if msg.GasPrice != nil {
arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice)
}
return arg
}
91 changes: 91 additions & 0 deletions geth/txqueue/fake/mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 40 additions & 0 deletions geth/txqueue/fake/txservice.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package fake

import (
context "context"
big "math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc"
gomock "github.com/golang/mock/gomock"
)

func NewTestServer(ctrl *gomock.Controller) (*rpc.Server, *MockFakePublicTxApi) {
srv := rpc.NewServer()
svc := NewMockFakePublicTxApi(ctrl)
if err := srv.RegisterName("eth", svc); err != nil {
panic(err)
}
return srv, svc
}

// CallArgs copied from module go-ethereum/internal/ethapi
type CallArgs struct {
From common.Address `json:"from"`
To *common.Address `json:"to"`
Gas hexutil.Big `json:"gas"`
GasPrice hexutil.Big `json:"gasPrice"`
Value hexutil.Big `json:"value"`
Data hexutil.Bytes `json:"data"`
}

// FakePublicTxApi used to generate mock by mockgen util.
// This was done because PublicTransactionPoolAPI is located in internal/ethapi module
// and there is no easy way to generate mocks from internal modules.
type FakePublicTxApi interface {
GasPrice(ctx context.Context) (*big.Int, error)
EstimateGas(ctx context.Context, args CallArgs) (*hexutil.Big, error)
GetTransactionCount(ctx context.Context, address common.Address, blockNr rpc.BlockNumber) (*hexutil.Uint64, error)
SendRawTransaction(ctx context.Context, encodedTx hexutil.Bytes) (common.Hash, error)
}
Loading

0 comments on commit 316e0f8

Please sign in to comment.