Implement eth_sendTransaction (#104)

* Set up framework for sending transaction with correct args and nonce mutex locking

* Set up printing ethereum address through emintkeys and getting chainid from flags

* Implemented defaults for eth_sendTransaction

* Fix bug with no data provided

* Updated comments and error, as well as RLP encoded tx bytes for return instead of amino encoded
This commit is contained in:
Austin Abell
2019-09-20 09:30:20 -04:00
committed by GitHub
parent 2ca42cc155
commit 28aaba0695
8 changed files with 213 additions and 18 deletions
+38
View File
@@ -0,0 +1,38 @@
package rpc
import (
"sync"
"github.com/ethereum/go-ethereum/common"
)
// AddrLocker is a mutex structure used to avoid querying outdated account data
type AddrLocker struct {
mu sync.Mutex
locks map[common.Address]*sync.Mutex
}
// lock returns the lock of the given address.
func (l *AddrLocker) lock(address common.Address) *sync.Mutex {
l.mu.Lock()
defer l.mu.Unlock()
if l.locks == nil {
l.locks = make(map[common.Address]*sync.Mutex)
}
if _, ok := l.locks[address]; !ok {
l.locks[address] = new(sync.Mutex)
}
return l.locks[address]
}
// LockAddr locks an account's mutex. This is used to prevent another tx getting the
// same nonce until the lock is released. The mutex prevents the (an identical nonce) from
// being read again during the time that the first transaction is being signed.
func (l *AddrLocker) LockAddr(address common.Address) {
l.lock(address).Lock()
}
// UnlockAddr unlocks the mutex of the given account.
func (l *AddrLocker) UnlockAddr(address common.Address) {
l.lock(address).Unlock()
}
+3 -2
View File
@@ -10,6 +10,7 @@ import (
// GetRPCAPIs returns the list of all APIs
func GetRPCAPIs(cliCtx context.CLIContext, key emintcrypto.PrivKeySecp256k1) []rpc.API {
nonceLock := new(AddrLocker)
return []rpc.API{
{
Namespace: "web3",
@@ -20,13 +21,13 @@ func GetRPCAPIs(cliCtx context.CLIContext, key emintcrypto.PrivKeySecp256k1) []r
{
Namespace: "eth",
Version: "1.0",
Service: NewPublicEthAPI(cliCtx, key),
Service: NewPublicEthAPI(cliCtx, nonceLock, key),
Public: true,
},
{
Namespace: "personal",
Version: "1.0",
Service: NewPersonalEthAPI(cliCtx),
Service: NewPersonalEthAPI(cliCtx, nonceLock),
Public: false,
},
}
+22
View File
@@ -0,0 +1,22 @@
package args
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
// SendTxArgs represents the arguments to sumbit a new transaction into the transaction pool.
// Duplicate struct definition since geth struct is in internal package
// Ref: https://github.com/ethereum/go-ethereum/blob/release/1.9/internal/ethapi/api.go#L1346
type SendTxArgs struct {
From common.Address `json:"from"`
To *common.Address `json:"to"`
Gas *hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"`
Value *hexutil.Big `json:"value"`
Nonce *hexutil.Uint64 `json:"nonce"`
// We accept "data" and "input" for backwards-compatibility reasons. "input" is the
// newer name and should be preferred by clients.
Data *hexutil.Bytes `json:"data"`
Input *hexutil.Bytes `json:"input"`
}
+64 -10
View File
@@ -9,6 +9,7 @@ import (
authutils "github.com/cosmos/cosmos-sdk/x/auth/client/utils"
emintcrypto "github.com/cosmos/ethermint/crypto"
emintkeys "github.com/cosmos/ethermint/keys"
"github.com/cosmos/ethermint/rpc/args"
"github.com/cosmos/ethermint/version"
"github.com/cosmos/ethermint/x/evm/types"
@@ -17,20 +18,26 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/signer/core"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/spf13/viper"
)
// PublicEthAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec.
type PublicEthAPI struct {
cliCtx context.CLIContext
key emintcrypto.PrivKeySecp256k1
cliCtx context.CLIContext
key emintcrypto.PrivKeySecp256k1
nonceLock *AddrLocker
}
// NewPublicEthAPI creates an instance of the public ETH Web3 API.
func NewPublicEthAPI(cliCtx context.CLIContext, key emintcrypto.PrivKeySecp256k1) *PublicEthAPI {
func NewPublicEthAPI(cliCtx context.CLIContext, nonceLock *AddrLocker,
key emintcrypto.PrivKeySecp256k1) *PublicEthAPI {
return &PublicEthAPI{
cliCtx: cliCtx,
key: key,
cliCtx: cliCtx,
key: key,
nonceLock: nonceLock,
}
}
@@ -200,9 +207,54 @@ func (e *PublicEthAPI) Sign(address common.Address, data hexutil.Bytes) (hexutil
}
// SendTransaction sends an Ethereum transaction.
func (e *PublicEthAPI) SendTransaction(args core.SendTxArgs) common.Hash {
var h common.Hash
return h
func (e *PublicEthAPI) SendTransaction(args args.SendTxArgs) (common.Hash, error) {
// TODO: Change this functionality to find an unlocked account by address
if e.key == nil || !bytes.Equal(e.key.PubKey().Address().Bytes(), args.From.Bytes()) {
return common.Hash{}, keystore.ErrLocked
}
// Mutex lock the address' nonce to avoid assigning it to multiple requests
if args.Nonce == nil {
e.nonceLock.LockAddr(args.From)
defer e.nonceLock.UnlockAddr(args.From)
}
// Assemble transaction from fields
tx, err := types.GenerateFromArgs(args, e.cliCtx)
if err != nil {
return common.Hash{}, err
}
// ChainID must be set as flag to send transaction
chainID := viper.GetString(flags.FlagChainID)
// parse the chainID from a string to a base-10 integer
intChainID, ok := new(big.Int).SetString(chainID, 10)
if !ok {
return common.Hash{}, fmt.Errorf(
fmt.Sprintf("Invalid chainID: %s, must be integer format", chainID))
}
// Sign transaction
tx.Sign(intChainID, e.key.ToECDSA())
// Encode transaction by default Tx encoder
txEncoder := authutils.GetTxEncoder(e.cliCtx.Codec)
txBytes, err := txEncoder(tx)
if err != nil {
return common.Hash{}, err
}
// Broadcast transaction
res, err := e.cliCtx.BroadcastTx(txBytes)
// If error is encountered on the node, the broadcast will not return an error
// TODO: Remove res log
fmt.Println(res.RawLog)
if err != nil {
return common.Hash{}, err
}
// Return RLP encoded bytes
return tx.Hash(), nil
}
// SendRawTransaction send a raw Ethereum transaction.
@@ -225,12 +277,14 @@ func (e *PublicEthAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, erro
// TODO: Possibly log the contract creation address (if recipient address is nil) or tx data
res, err := e.cliCtx.BroadcastTx(txBytes)
// If error is encountered on the node, the broadcast will not return an error
// TODO: Remove res log
fmt.Println(res.RawLog)
if err != nil {
return common.Hash{}, err
}
return common.HexToHash(res.TxHash), nil
// Return RLP encoded bytes
return tx.Hash(), nil
}
// CallArgs represents arguments to a smart contract call as provided by RPC clients.
+5 -3
View File
@@ -10,13 +10,15 @@ import (
// PersonalEthAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec.
type PersonalEthAPI struct {
cliCtx sdkcontext.CLIContext
cliCtx sdkcontext.CLIContext
nonceLock *AddrLocker
}
// NewPersonalEthAPI creates an instance of the public ETH Web3 API.
func NewPersonalEthAPI(cliCtx sdkcontext.CLIContext) *PersonalEthAPI {
func NewPersonalEthAPI(cliCtx sdkcontext.CLIContext, nonceLock *AddrLocker) *PersonalEthAPI {
return &PersonalEthAPI{
cliCtx: cliCtx,
cliCtx: cliCtx,
nonceLock: nonceLock,
}
}