send tx with signature (#1374)
* add SendTransactionWithSignature to Transactor * add comment to SendTransactionWithSignature * add SendTransactionWithSignature to StatusBackend * add SendTransactionWithSignature to lib * add SendTransactionWithSignature to mobile pkg * increment nonce if needed * add TestSendTransactionWithSignature_IncrementingNonce * add NoError assertion * validate tx args * debug CI adding `env` * CI debug * remove debug * return error if tx nonce is not the expected one * fix lint warning
This commit is contained in:
parent
e7c8b33c2f
commit
72906ac655
|
@ -263,6 +263,17 @@ func (b *StatusBackend) SendTransaction(sendArgs transactions.SendTxArgs, passwo
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *StatusBackend) SendTransactionWithSignature(sendArgs transactions.SendTxArgs, sig []byte) (hash gethcommon.Hash, err error) {
|
||||||
|
hash, err = b.transactor.SendTransactionWithSignature(sendArgs, sig)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go b.rpcFilters.TriggerTransactionSentToUpstreamEvent(hash)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// SignMessage checks the pwd vs the selected account and passes on the signParams
|
// SignMessage checks the pwd vs the selected account and passes on the signParams
|
||||||
// to personalAPI for message signature
|
// to personalAPI for message signature
|
||||||
func (b *StatusBackend) SignMessage(rpcParams personal.SignParams) (hexutil.Bytes, error) {
|
func (b *StatusBackend) SignMessage(rpcParams personal.SignParams) (hexutil.Bytes, error) {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package main
|
||||||
// #include <stdlib.h>
|
// #include <stdlib.h>
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
@ -402,6 +403,28 @@ func SendTransaction(txArgsJSON, password *C.char) *C.char {
|
||||||
return C.CString(prepareJSONResponseWithCode(hash.String(), err, code))
|
return C.CString(prepareJSONResponseWithCode(hash.String(), err, code))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendTransactionWithSignature converts RPC args and calls backend.SendTransactionWithSignature
|
||||||
|
//export SendTransactionWithSignature
|
||||||
|
func SendTransactionWithSignature(txArgsJSON, sigString *C.char) *C.char {
|
||||||
|
var params transactions.SendTxArgs
|
||||||
|
err := json.Unmarshal([]byte(C.GoString(txArgsJSON)), ¶ms)
|
||||||
|
if err != nil {
|
||||||
|
return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams))
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := hex.DecodeString(C.GoString(sigString))
|
||||||
|
if err != nil {
|
||||||
|
return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams))
|
||||||
|
}
|
||||||
|
|
||||||
|
hash, err := statusBackend.SendTransactionWithSignature(params, sig)
|
||||||
|
code := codeUnknown
|
||||||
|
if c, ok := errToCodeMap[err]; ok {
|
||||||
|
code = c
|
||||||
|
}
|
||||||
|
return C.CString(prepareJSONResponseWithCode(hash.String(), err, code))
|
||||||
|
}
|
||||||
|
|
||||||
// SignTypedData unmarshall data into TypedData, validate it and signs with selected account,
|
// SignTypedData unmarshall data into TypedData, validate it and signs with selected account,
|
||||||
// if password matches selected account.
|
// if password matches selected account.
|
||||||
//export SignTypedData
|
//export SignTypedData
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package statusgo
|
package statusgo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
@ -375,6 +376,27 @@ func SendTransaction(txArgsJSON, password string) string {
|
||||||
return prepareJSONResponseWithCode(hash.String(), err, code)
|
return prepareJSONResponseWithCode(hash.String(), err, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendTransactionWithSignature converts RPC args and calls backend.SendTransactionWithSignature
|
||||||
|
func SendTransactionWithSignature(txArgsJSON, sigString string) string {
|
||||||
|
var params transactions.SendTxArgs
|
||||||
|
err := json.Unmarshal([]byte(txArgsJSON), ¶ms)
|
||||||
|
if err != nil {
|
||||||
|
return prepareJSONResponseWithCode(nil, err, codeFailedParseParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := hex.DecodeString(sigString)
|
||||||
|
if err != nil {
|
||||||
|
return prepareJSONResponseWithCode(nil, err, codeFailedParseParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
hash, err := statusBackend.SendTransactionWithSignature(params, sig)
|
||||||
|
code := codeUnknown
|
||||||
|
if c, ok := errToCodeMap[err]; ok {
|
||||||
|
code = c
|
||||||
|
}
|
||||||
|
return prepareJSONResponseWithCode(hash.String(), err, code)
|
||||||
|
}
|
||||||
|
|
||||||
// StartCPUProfile runs pprof for CPU.
|
// StartCPUProfile runs pprof for CPU.
|
||||||
func StartCPUProfile(dataDir string) string {
|
func StartCPUProfile(dataDir string) string {
|
||||||
err := profiling.StartCPUProfile(dataDir)
|
err := profiling.StartCPUProfile(dataDir)
|
||||||
|
|
|
@ -3,6 +3,7 @@ package transactions
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -24,6 +25,16 @@ const (
|
||||||
defaultGas = 90000
|
defaultGas = 90000
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ErrBadNonce struct {
|
||||||
|
nonce uint64
|
||||||
|
localNonce uint64
|
||||||
|
remoteNonce uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ErrBadNonce) Error() string {
|
||||||
|
return fmt.Sprintf("bad nonce %d. local nonce: %d, remote nonce: %d", e.nonce, e.localNonce, e.remoteNonce)
|
||||||
|
}
|
||||||
|
|
||||||
// Transactor validates, signs transactions.
|
// Transactor validates, signs transactions.
|
||||||
// It uses upstream to propagate transactions to the Ethereum network.
|
// It uses upstream to propagate transactions to the Ethereum network.
|
||||||
type Transactor struct {
|
type Transactor struct {
|
||||||
|
@ -69,6 +80,91 @@ func (t *Transactor) SendTransaction(sendArgs SendTxArgs, verifiedAccount *accou
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendTransactionWithSignature receive a transaction and a signature, serialize them together and propage it to the network.
|
||||||
|
// It's different from eth_sendRawTransaction because it receives a signature and not a serialized transaction with signature.
|
||||||
|
// Since the transactions is already signed, we assume it was validated and used the right nonce.
|
||||||
|
func (t *Transactor) SendTransactionWithSignature(args SendTxArgs, sig []byte) (hash gethcommon.Hash, err error) {
|
||||||
|
if !args.Valid() {
|
||||||
|
return hash, ErrInvalidSendTxArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
chainID := big.NewInt(int64(t.networkID))
|
||||||
|
signer := types.NewEIP155Signer(chainID)
|
||||||
|
|
||||||
|
txNonce := uint64(*args.Nonce)
|
||||||
|
to := *args.To
|
||||||
|
value := (*big.Int)(args.Value)
|
||||||
|
gas := uint64(*args.Gas)
|
||||||
|
gasPrice := (*big.Int)(args.GasPrice)
|
||||||
|
data := args.GetInput()
|
||||||
|
|
||||||
|
var tx *types.Transaction
|
||||||
|
if args.To != nil {
|
||||||
|
t.log.Info("New transaction",
|
||||||
|
"From", args.From,
|
||||||
|
"To", *args.To,
|
||||||
|
"Gas", gas,
|
||||||
|
"GasPrice", gasPrice,
|
||||||
|
"Value", value,
|
||||||
|
)
|
||||||
|
tx = types.NewTransaction(txNonce, to, value, gas, gasPrice, data)
|
||||||
|
} else {
|
||||||
|
// contract creation is rare enough to log an expected address
|
||||||
|
t.log.Info("New contract",
|
||||||
|
"From", args.From,
|
||||||
|
"Gas", gas,
|
||||||
|
"GasPrice", gasPrice,
|
||||||
|
"Value", value,
|
||||||
|
"Contract address", crypto.CreateAddress(args.From, txNonce),
|
||||||
|
)
|
||||||
|
tx = types.NewContractCreation(txNonce, value, gas, gasPrice, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
localNonce uint64
|
||||||
|
remoteNonce uint64
|
||||||
|
)
|
||||||
|
|
||||||
|
t.addrLock.LockAddr(args.From)
|
||||||
|
if val, ok := t.localNonce.Load(args.From); ok {
|
||||||
|
localNonce = val.(uint64)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
// nonce should be incremented only if tx completed without error
|
||||||
|
// and if no other transactions have been sent while signing the current one.
|
||||||
|
if err == nil {
|
||||||
|
t.localNonce.Store(args.From, txNonce+1)
|
||||||
|
}
|
||||||
|
t.addrLock.UnlockAddr(args.From)
|
||||||
|
}()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout)
|
||||||
|
defer cancel()
|
||||||
|
remoteNonce, err = t.pendingNonceProvider.PendingNonceAt(ctx, args.From)
|
||||||
|
if err != nil {
|
||||||
|
return hash, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tx.Nonce() != localNonce || tx.Nonce() != remoteNonce {
|
||||||
|
return hash, &ErrBadNonce{tx.Nonce(), localNonce, remoteNonce}
|
||||||
|
}
|
||||||
|
|
||||||
|
signedTx, err := tx.WithSignature(signer, sig)
|
||||||
|
if err != nil {
|
||||||
|
return hash, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel = context.WithTimeout(context.Background(), t.rpcCallTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := t.sender.SendTransaction(ctx, signedTx); err != nil {
|
||||||
|
return hash, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return signedTx.Hash(), nil
|
||||||
|
}
|
||||||
|
|
||||||
// make sure that only account which created the tx can complete it
|
// make sure that only account which created the tx can complete it
|
||||||
func (t *Transactor) validateAccount(args SendTxArgs, selectedAccount *account.SelectedExtKey) error {
|
func (t *Transactor) validateAccount(args SendTxArgs, selectedAccount *account.SelectedExtKey) error {
|
||||||
if selectedAccount == nil {
|
if selectedAccount == nil {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package transactions
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -283,3 +284,91 @@ func (s *TransactorSuite) TestContractCreation() {
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
s.Equal(crypto.CreateAddress(testaddr, 0), receipt.ContractAddress)
|
s.Equal(crypto.CreateAddress(testaddr, 0), receipt.ContractAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *TransactorSuite) TestSendTransactionWithSignature() {
|
||||||
|
privKey, err := crypto.GenerateKey()
|
||||||
|
s.Require().NoError(err)
|
||||||
|
address := crypto.PubkeyToAddress(privKey.PublicKey)
|
||||||
|
|
||||||
|
scenarios := []struct {
|
||||||
|
localNonce hexutil.Uint64
|
||||||
|
txNonce hexutil.Uint64
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
localNonce: hexutil.Uint64(0),
|
||||||
|
txNonce: hexutil.Uint64(0),
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
localNonce: hexutil.Uint64(1),
|
||||||
|
txNonce: hexutil.Uint64(0),
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
localNonce: hexutil.Uint64(0),
|
||||||
|
txNonce: hexutil.Uint64(1),
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, scenario := range scenarios {
|
||||||
|
desc := fmt.Sprintf("local nonce: %d, tx nonce: %d, expect error: %v", scenario.localNonce, scenario.txNonce, scenario.expectError)
|
||||||
|
s.T().Run(desc, func(t *testing.T) {
|
||||||
|
s.manager.localNonce.Store(address, uint64(scenario.localNonce))
|
||||||
|
|
||||||
|
nonce := scenario.txNonce
|
||||||
|
from := address
|
||||||
|
to := address
|
||||||
|
value := (*hexutil.Big)(big.NewInt(10))
|
||||||
|
gas := hexutil.Uint64(21000)
|
||||||
|
gasPrice := (*hexutil.Big)(big.NewInt(2000000000))
|
||||||
|
data := []byte{}
|
||||||
|
chainID := big.NewInt(int64(s.nodeConfig.NetworkID))
|
||||||
|
|
||||||
|
args := SendTxArgs{
|
||||||
|
From: from,
|
||||||
|
To: &to,
|
||||||
|
Gas: &gas,
|
||||||
|
GasPrice: gasPrice,
|
||||||
|
Value: value,
|
||||||
|
Nonce: &nonce,
|
||||||
|
Data: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
// simulate transaction signed externally
|
||||||
|
signer := types.NewEIP155Signer(chainID)
|
||||||
|
tx := types.NewTransaction(uint64(nonce), to, (*big.Int)(value), uint64(gas), (*big.Int)(gasPrice), data)
|
||||||
|
hash := signer.Hash(tx)
|
||||||
|
sig, err := crypto.Sign(hash[:], privKey)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
txWithSig, err := tx.WithSignature(signer, sig)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
expectedEncodedTx, err := rlp.EncodeToBytes(txWithSig)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
s.txServiceMock.EXPECT().
|
||||||
|
GetTransactionCount(gomock.Any(), address, gethrpc.PendingBlockNumber).
|
||||||
|
Return(&scenario.localNonce, nil)
|
||||||
|
|
||||||
|
if !scenario.expectError {
|
||||||
|
s.txServiceMock.EXPECT().
|
||||||
|
SendRawTransaction(gomock.Any(), hexutil.Bytes(expectedEncodedTx)).
|
||||||
|
Return(gethcommon.Hash{}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = s.manager.SendTransactionWithSignature(args, sig)
|
||||||
|
if scenario.expectError {
|
||||||
|
s.Error(err)
|
||||||
|
// local nonce should not be incremented
|
||||||
|
resultNonce, _ := s.manager.localNonce.Load(args.From)
|
||||||
|
s.Equal(uint64(scenario.localNonce), resultNonce.(uint64))
|
||||||
|
} else {
|
||||||
|
s.NoError(err)
|
||||||
|
// local nonce should be incremented
|
||||||
|
resultNonce, _ := s.manager.localNonce.Load(args.From)
|
||||||
|
s.Equal(uint64(nonce)+1, resultNonce.(uint64))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue