Remove transactions queue 1027 (#1125)
Remove `PendingSignRequests` queue from the sign module. This closes #1027 by removing the pending sign requests queue dependency from the SendTransaction, SignMessage and Recover.
This commit is contained in:
parent
ebc77374b2
commit
4afd9e6c6c
150
api/backend.go
150
api/backend.go
|
@ -7,6 +7,7 @@ import (
|
|||
"sync"
|
||||
|
||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
gethnode "github.com/ethereum/go-ethereum/node"
|
||||
|
||||
|
@ -19,7 +20,6 @@ import (
|
|||
"github.com/status-im/status-go/rpc"
|
||||
"github.com/status-im/status-go/services/personal"
|
||||
"github.com/status-im/status-go/services/rpcfilters"
|
||||
"github.com/status-im/status-go/sign"
|
||||
"github.com/status-im/status-go/signal"
|
||||
"github.com/status-im/status-go/transactions"
|
||||
)
|
||||
|
@ -34,21 +34,22 @@ var (
|
|||
ErrWhisperClearIdentitiesFailure = errors.New("failed to clear whisper identities")
|
||||
// ErrWhisperIdentityInjectionFailure injecting whisper identities has failed.
|
||||
ErrWhisperIdentityInjectionFailure = errors.New("failed to inject identity into Whisper")
|
||||
// ErrUnsupportedRPCMethod is for methods not supported by the RPC interface
|
||||
ErrUnsupportedRPCMethod = errors.New("method is unsupported by RPC interface")
|
||||
)
|
||||
|
||||
// StatusBackend implements Status.im service
|
||||
type StatusBackend struct {
|
||||
mu sync.Mutex
|
||||
statusNode *node.StatusNode
|
||||
pendingSignRequests *sign.PendingRequests
|
||||
personalAPI *personal.PublicAPI
|
||||
rpcFilters *rpcfilters.Service
|
||||
accountManager *account.Manager
|
||||
transactor *transactions.Transactor
|
||||
newNotification fcm.NotificationConstructor
|
||||
connectionState connectionState
|
||||
appState appState
|
||||
log log.Logger
|
||||
mu sync.Mutex
|
||||
statusNode *node.StatusNode
|
||||
personalAPI *personal.PublicAPI
|
||||
rpcFilters *rpcfilters.Service
|
||||
accountManager *account.Manager
|
||||
transactor *transactions.Transactor
|
||||
newNotification fcm.NotificationConstructor
|
||||
connectionState connectionState
|
||||
appState appState
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
// NewStatusBackend create a new NewStatusBackend instance
|
||||
|
@ -56,22 +57,20 @@ func NewStatusBackend() *StatusBackend {
|
|||
defer log.Info("Status backend initialized")
|
||||
|
||||
statusNode := node.New()
|
||||
pendingSignRequests := sign.NewPendingRequests()
|
||||
accountManager := account.NewManager(statusNode)
|
||||
transactor := transactions.NewTransactor(pendingSignRequests)
|
||||
personalAPI := personal.NewAPI(pendingSignRequests)
|
||||
transactor := transactions.NewTransactor()
|
||||
personalAPI := personal.NewAPI()
|
||||
notificationManager := fcm.NewNotification(fcmServerKey)
|
||||
rpcFilters := rpcfilters.New(statusNode)
|
||||
|
||||
return &StatusBackend{
|
||||
pendingSignRequests: pendingSignRequests,
|
||||
statusNode: statusNode,
|
||||
accountManager: accountManager,
|
||||
transactor: transactor,
|
||||
personalAPI: personalAPI,
|
||||
rpcFilters: rpcFilters,
|
||||
newNotification: notificationManager,
|
||||
log: log.New("package", "status-go/api.StatusBackend"),
|
||||
statusNode: statusNode,
|
||||
accountManager: accountManager,
|
||||
transactor: transactor,
|
||||
personalAPI: personalAPI,
|
||||
rpcFilters: rpcFilters,
|
||||
newNotification: notificationManager,
|
||||
log: log.New("package", "status-go/api.StatusBackend"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,11 +89,6 @@ func (b *StatusBackend) Transactor() *transactions.Transactor {
|
|||
return b.transactor
|
||||
}
|
||||
|
||||
// PendingSignRequests returns reference to a list of current sign requests
|
||||
func (b *StatusBackend) PendingSignRequests() *sign.PendingRequests {
|
||||
return b.pendingSignRequests
|
||||
}
|
||||
|
||||
// IsNodeRunning confirm that node is running
|
||||
func (b *StatusBackend) IsNodeRunning() bool {
|
||||
return b.statusNode.IsRunning()
|
||||
|
@ -225,12 +219,36 @@ func (b *StatusBackend) CallPrivateRPC(inputJSON string) string {
|
|||
}
|
||||
|
||||
// SendTransaction creates a new transaction and waits until it's complete.
|
||||
func (b *StatusBackend) SendTransaction(ctx context.Context, args transactions.SendTxArgs) (hash gethcommon.Hash, err error) {
|
||||
transactionHash, err := b.transactor.SendTransaction(ctx, args)
|
||||
if err == nil {
|
||||
go b.rpcFilters.TriggerTransactionSentToUpstreamEvent(transactionHash)
|
||||
func (b *StatusBackend) SendTransaction(sendArgs transactions.SendTxArgs, password string) (hash gethcommon.Hash, err error) {
|
||||
verifiedAccount, err := b.getVerifiedAccount(password)
|
||||
if err != nil {
|
||||
return hash, err
|
||||
}
|
||||
return transactionHash, err
|
||||
|
||||
hash, err = b.transactor.SendTransaction(sendArgs, verifiedAccount)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
go b.rpcFilters.TriggerTransactionSentToUpstreamEvent(hash)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SignMessage checks the pwd vs the selected account and passes on the signParams
|
||||
// to personalAPI for message signature
|
||||
func (b *StatusBackend) SignMessage(rpcParams personal.SignParams) (hexutil.Bytes, error) {
|
||||
verifiedAccount, err := b.getVerifiedAccount(rpcParams.Password)
|
||||
if err != nil {
|
||||
return hexutil.Bytes{}, err
|
||||
}
|
||||
return b.personalAPI.Sign(rpcParams, verifiedAccount)
|
||||
}
|
||||
|
||||
// Recover calls the personalAPI to return address associated with the private
|
||||
// key that was used to calculate the signature in the message
|
||||
func (b *StatusBackend) Recover(rpcParams personal.RecoverParams) (gethcommon.Address, error) {
|
||||
return b.personalAPI.Recover(rpcParams)
|
||||
}
|
||||
|
||||
func (b *StatusBackend) getVerifiedAccount(password string) (*account.SelectedExtKey, error) {
|
||||
|
@ -248,46 +266,6 @@ func (b *StatusBackend) getVerifiedAccount(password string) (*account.SelectedEx
|
|||
return selectedAccount, nil
|
||||
}
|
||||
|
||||
// ApproveSignRequest instructs backend to complete sending of a given transaction.
|
||||
func (b *StatusBackend) ApproveSignRequest(id, password string) sign.Result {
|
||||
return b.pendingSignRequests.Approve(id, password, nil, b.getVerifiedAccount)
|
||||
}
|
||||
|
||||
// ApproveSignRequestWithArgs instructs backend to complete sending of a given transaction.
|
||||
// gas and gasPrice will be overrided with the given values before signing the
|
||||
// transaction.
|
||||
func (b *StatusBackend) ApproveSignRequestWithArgs(id, password string, gas, gasPrice int64) sign.Result {
|
||||
args := prepareTxArgs(gas, gasPrice)
|
||||
return b.pendingSignRequests.Approve(id, password, &args, b.getVerifiedAccount)
|
||||
}
|
||||
|
||||
// ApproveSignRequests instructs backend to complete sending of multiple transactions
|
||||
func (b *StatusBackend) ApproveSignRequests(ids []string, password string) map[string]sign.Result {
|
||||
results := make(map[string]sign.Result)
|
||||
for _, txID := range ids {
|
||||
results[txID] = b.ApproveSignRequest(txID, password)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// DiscardSignRequest discards a given transaction from transaction queue
|
||||
func (b *StatusBackend) DiscardSignRequest(id string) error {
|
||||
return b.pendingSignRequests.Discard(id)
|
||||
}
|
||||
|
||||
// DiscardSignRequests discards given multiple transactions from transaction queue
|
||||
func (b *StatusBackend) DiscardSignRequests(ids []string) map[string]error {
|
||||
results := make(map[string]error)
|
||||
for _, txID := range ids {
|
||||
err := b.DiscardSignRequest(txID)
|
||||
if err != nil {
|
||||
results[txID] = err
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// registerHandlers attaches Status callback handlers to running node
|
||||
func (b *StatusBackend) registerHandlers() error {
|
||||
var clients []*rpc.Client
|
||||
|
@ -312,30 +290,18 @@ func (b *StatusBackend) registerHandlers() error {
|
|||
},
|
||||
)
|
||||
|
||||
client.RegisterHandler(
|
||||
params.SendTransactionMethodName,
|
||||
func(ctx context.Context, rpcParams ...interface{}) (interface{}, error) {
|
||||
txArgs, err := transactions.RPCCalltoSendTxArgs(rpcParams...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hash, err := b.SendTransaction(ctx, txArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return hash.Hex(), err
|
||||
},
|
||||
)
|
||||
|
||||
client.RegisterHandler(params.PersonalSignMethodName, b.personalAPI.Sign)
|
||||
client.RegisterHandler(params.PersonalRecoverMethodName, b.personalAPI.Recover)
|
||||
client.RegisterHandler(params.SendTransactionMethodName, unsupportedMethodHandler)
|
||||
client.RegisterHandler(params.PersonalSignMethodName, unsupportedMethodHandler)
|
||||
client.RegisterHandler(params.PersonalRecoverMethodName, unsupportedMethodHandler)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func unsupportedMethodHandler(ctx context.Context, rpcParams ...interface{}) (interface{}, error) {
|
||||
return nil, ErrUnsupportedRPCMethod
|
||||
}
|
||||
|
||||
// ConnectionChange handles network state changes logic.
|
||||
func (b *StatusBackend) ConnectionChange(typ string, expensive bool) {
|
||||
b.mu.Lock()
|
||||
|
|
|
@ -2,12 +2,10 @@ package api
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/status-im/status-go/node"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/rpc"
|
||||
|
@ -108,12 +106,6 @@ func TestBackendGettersConcurrently(t *testing.T) {
|
|||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
assert.NotNil(t, backend.PendingSignRequests())
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
assert.True(t, backend.IsNodeRunning())
|
||||
|
@ -284,58 +276,6 @@ func TestAppStateChange(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPrepareTxArgs(t *testing.T) {
|
||||
var flagtests = []struct {
|
||||
description string
|
||||
gas int64
|
||||
gasPrice int64
|
||||
expectedGas *hexutil.Uint64
|
||||
expectedGasPrice *hexutil.Big
|
||||
}{
|
||||
{
|
||||
description: "Empty gas and gas price",
|
||||
gas: 0,
|
||||
gasPrice: 0,
|
||||
expectedGas: nil,
|
||||
expectedGasPrice: nil,
|
||||
},
|
||||
{
|
||||
description: "Non empty gas and gas price",
|
||||
gas: 1,
|
||||
gasPrice: 2,
|
||||
expectedGas: func() *hexutil.Uint64 {
|
||||
x := hexutil.Uint64(1)
|
||||
return &x
|
||||
}(),
|
||||
expectedGasPrice: (*hexutil.Big)(big.NewInt(2)),
|
||||
},
|
||||
{
|
||||
description: "Empty gas price",
|
||||
gas: 1,
|
||||
gasPrice: 0,
|
||||
expectedGas: func() *hexutil.Uint64 {
|
||||
x := hexutil.Uint64(1)
|
||||
return &x
|
||||
}(),
|
||||
expectedGasPrice: nil,
|
||||
},
|
||||
{
|
||||
description: "Empty gas",
|
||||
gas: 0,
|
||||
gasPrice: 2,
|
||||
expectedGas: nil,
|
||||
expectedGasPrice: (*hexutil.Big)(big.NewInt(2)),
|
||||
},
|
||||
}
|
||||
for _, tt := range flagtests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
args := prepareTxArgs(tt.gas, tt.gasPrice)
|
||||
assert.Equal(t, tt.expectedGas, args.Gas)
|
||||
assert.Equal(t, tt.expectedGasPrice, args.GasPrice)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockedRPCMethods(t *testing.T) {
|
||||
backend := NewStatusBackend()
|
||||
err := backend.StartNode(¶ms.NodeConfig{})
|
||||
|
@ -352,4 +292,4 @@ func TestBlockedRPCMethods(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO(adam): add concurrent tests for: SendTransaction, ApproveSignRequest, DiscardSignRequest
|
||||
// TODO(adam): add concurrent tests for: SendTransaction
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/status-im/status-go/sign"
|
||||
)
|
||||
|
||||
// prepareTxArgs given gas and gasPrice will prepare a valid sign.TxArgs.
|
||||
func prepareTxArgs(gas, gasPrice int64) (args sign.TxArgs) {
|
||||
if gas > 0 {
|
||||
g := hexutil.Uint64(gas)
|
||||
args.Gas = &g
|
||||
}
|
||||
if gasPrice > 0 {
|
||||
gp := (*hexutil.Big)(big.NewInt(gasPrice))
|
||||
args.GasPrice = gp
|
||||
}
|
||||
return
|
||||
}
|
|
@ -166,23 +166,3 @@ func (cs *commandSet) SelectAccount(address, password string) error {
|
|||
func (cs *commandSet) Logout() error {
|
||||
return cs.statusBackend.Logout()
|
||||
}
|
||||
|
||||
// ApproveSignRequest instructs API to complete sending of a given transaction.
|
||||
func (cs *commandSet) ApproveSignRequest(id, password string) (string, error) {
|
||||
result := cs.statusBackend.ApproveSignRequest(id, password)
|
||||
if result.Error != nil {
|
||||
return "", result.Error
|
||||
}
|
||||
return result.Response.Hex(), nil
|
||||
}
|
||||
|
||||
// ApproveSignRequest instructs API to complete sending of a given transaction.
|
||||
// gas and gasPrice will be overrided with the given values before signing the
|
||||
// transaction.
|
||||
func (cs *commandSet) ApproveSignRequestWithArgs(id, password string, gas, gasPrice int64) (string, error) {
|
||||
result := cs.statusBackend.ApproveSignRequestWithArgs(id, password, gas, gasPrice)
|
||||
if result.Error != nil {
|
||||
return "", result.Error
|
||||
}
|
||||
return result.Response.Hex(), nil
|
||||
}
|
||||
|
|
166
lib/library.go
166
lib/library.go
|
@ -13,8 +13,9 @@ import (
|
|||
"github.com/status-im/status-go/logutils"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/profiling"
|
||||
"github.com/status-im/status-go/sign"
|
||||
"github.com/status-im/status-go/services/personal"
|
||||
"github.com/status-im/status-go/signal"
|
||||
"github.com/status-im/status-go/transactions"
|
||||
"gopkg.in/go-playground/validator.v9"
|
||||
)
|
||||
|
||||
|
@ -210,143 +211,46 @@ func Logout() *C.char {
|
|||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
//ApproveSignRequestWithArgs instructs backend to complete sending of a given transaction.
|
||||
// gas and gasPrice will be overrided with the given values before signing the
|
||||
// transaction.
|
||||
//export ApproveSignRequestWithArgs
|
||||
func ApproveSignRequestWithArgs(id, password *C.char, gas, gasPrice C.longlong) *C.char {
|
||||
result := statusBackend.ApproveSignRequestWithArgs(C.GoString(id), C.GoString(password), int64(gas), int64(gasPrice))
|
||||
|
||||
return prepareApproveSignRequestResponse(result, id)
|
||||
// SignMessage unmarshals rpc params {data, address, password} and passes
|
||||
// them onto backend.SignMessage
|
||||
//export SignMessage
|
||||
func SignMessage(rpcParams *C.char) *C.char {
|
||||
var params personal.SignParams
|
||||
err := json.Unmarshal([]byte(C.GoString(rpcParams)), ¶ms)
|
||||
if err != nil {
|
||||
return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams))
|
||||
}
|
||||
result, err := statusBackend.SignMessage(params)
|
||||
return C.CString(prepareJSONResponse(result.String(), err))
|
||||
}
|
||||
|
||||
//ApproveSignRequest instructs backend to complete sending of a given transaction.
|
||||
//export ApproveSignRequest
|
||||
func ApproveSignRequest(id, password *C.char) *C.char {
|
||||
result := statusBackend.ApproveSignRequest(C.GoString(id), C.GoString(password))
|
||||
|
||||
return prepareApproveSignRequestResponse(result, id)
|
||||
// Recover unmarshals rpc params {signDataString, signedData} and passes
|
||||
// them onto backend.
|
||||
//export Recover
|
||||
func Recover(rpcParams *C.char) *C.char {
|
||||
var params personal.RecoverParams
|
||||
err := json.Unmarshal([]byte(C.GoString(rpcParams)), ¶ms)
|
||||
if err != nil {
|
||||
return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams))
|
||||
}
|
||||
addr, err := statusBackend.Recover(params)
|
||||
return C.CString(prepareJSONResponse(addr.String(), err))
|
||||
}
|
||||
|
||||
// prepareApproveSignRequestResponse based on a sign.Result prepares the binding
|
||||
// response.
|
||||
func prepareApproveSignRequestResponse(result sign.Result, id *C.char) *C.char {
|
||||
errString := ""
|
||||
if result.Error != nil {
|
||||
fmt.Fprintln(os.Stderr, result.Error)
|
||||
errString = result.Error.Error()
|
||||
}
|
||||
|
||||
out := SignRequestResult{
|
||||
ID: C.GoString(id),
|
||||
Hash: result.Response.Hex(),
|
||||
Error: errString,
|
||||
}
|
||||
outBytes, err := json.Marshal(out)
|
||||
// SendTransaction converts RPC args and calls backend.SendTransaction
|
||||
//export SendTransaction
|
||||
func SendTransaction(txArgsJSON, password *C.char) *C.char {
|
||||
var params transactions.SendTxArgs
|
||||
err := json.Unmarshal([]byte(C.GoString(txArgsJSON)), ¶ms)
|
||||
if err != nil {
|
||||
logger.Error("failed to marshal ApproveSignRequest output", "error", err)
|
||||
return makeJSONResponse(err)
|
||||
return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams))
|
||||
}
|
||||
|
||||
return C.CString(string(outBytes))
|
||||
}
|
||||
|
||||
//ApproveSignRequests instructs backend to complete sending of multiple transactions
|
||||
//export ApproveSignRequests
|
||||
func ApproveSignRequests(ids, password *C.char) *C.char {
|
||||
out := SignRequestsResult{}
|
||||
out.Results = make(map[string]SignRequestResult)
|
||||
|
||||
parsedIDs, err := ParseJSONArray(C.GoString(ids))
|
||||
if err != nil {
|
||||
out.Results["none"] = SignRequestResult{
|
||||
Error: err.Error(),
|
||||
}
|
||||
} else {
|
||||
txIDs := make([]string, len(parsedIDs))
|
||||
for i, id := range parsedIDs {
|
||||
txIDs[i] = id
|
||||
}
|
||||
|
||||
results := statusBackend.ApproveSignRequests(txIDs, C.GoString(password))
|
||||
for txID, result := range results {
|
||||
txResult := SignRequestResult{
|
||||
ID: txID,
|
||||
Hash: result.Response.Hex(),
|
||||
}
|
||||
if result.Error != nil {
|
||||
txResult.Error = result.Error.Error()
|
||||
}
|
||||
out.Results[txID] = txResult
|
||||
}
|
||||
hash, err := statusBackend.SendTransaction(params, C.GoString(password))
|
||||
code := codeUnknown
|
||||
if c, ok := errToCodeMap[err]; ok {
|
||||
code = c
|
||||
}
|
||||
|
||||
outBytes, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
logger.Error("failed to marshal ApproveSignRequests output", "error", err)
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
return C.CString(string(outBytes))
|
||||
}
|
||||
|
||||
//DiscardSignRequest discards a given transaction from transaction queue
|
||||
//export DiscardSignRequest
|
||||
func DiscardSignRequest(id *C.char) *C.char {
|
||||
err := statusBackend.DiscardSignRequest(C.GoString(id))
|
||||
|
||||
errString := ""
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
errString = err.Error()
|
||||
}
|
||||
|
||||
out := DiscardSignRequestResult{
|
||||
ID: C.GoString(id),
|
||||
Error: errString,
|
||||
}
|
||||
outBytes, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
log.Error("failed to marshal DiscardSignRequest output", "error", err)
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
return C.CString(string(outBytes))
|
||||
}
|
||||
|
||||
//DiscardSignRequests discards given multiple transactions from transaction queue
|
||||
//export DiscardSignRequests
|
||||
func DiscardSignRequests(ids *C.char) *C.char {
|
||||
out := DiscardSignRequestsResult{}
|
||||
out.Results = make(map[string]DiscardSignRequestResult)
|
||||
|
||||
parsedIDs, err := ParseJSONArray(C.GoString(ids))
|
||||
if err != nil {
|
||||
out.Results["none"] = DiscardSignRequestResult{
|
||||
Error: err.Error(),
|
||||
}
|
||||
} else {
|
||||
txIDs := make([]string, len(parsedIDs))
|
||||
for i, id := range parsedIDs {
|
||||
txIDs[i] = id
|
||||
}
|
||||
|
||||
results := statusBackend.DiscardSignRequests(txIDs)
|
||||
for txID, err := range results {
|
||||
out.Results[txID] = DiscardSignRequestResult{
|
||||
ID: txID,
|
||||
Error: err.Error(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outBytes, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
logger.Error("failed to marshal DiscardSignRequests output", "error", err)
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
return C.CString(string(outBytes))
|
||||
return C.CString(prepareJSONResponseWithCode(hash.String(), err, code))
|
||||
}
|
||||
|
||||
//StartCPUProfile runs pprof for cpu
|
||||
|
|
|
@ -11,7 +11,6 @@ package main
|
|||
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
@ -21,19 +20,19 @@ import (
|
|||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
gethparams "github.com/ethereum/go-ethereum/params"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/status-im/status-go/account"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/sign"
|
||||
"github.com/status-im/status-go/signal"
|
||||
. "github.com/status-im/status-go/t/utils" //nolint: golint
|
||||
"github.com/status-im/status-go/transactions"
|
||||
|
@ -45,7 +44,7 @@ const initJS = `
|
|||
};`
|
||||
|
||||
var (
|
||||
zeroHash = sign.EmptyResponse.Hex()
|
||||
zeroHash = gethcommon.Hash{}
|
||||
testChainDir string
|
||||
nodeConfigJSON string
|
||||
)
|
||||
|
@ -127,20 +126,16 @@ func testExportedAPI(t *testing.T, done chan struct{}) {
|
|||
testAccountLogout,
|
||||
},
|
||||
{
|
||||
"complete single queued transaction",
|
||||
testCompleteTransaction,
|
||||
"send transaction",
|
||||
testSendTransaction,
|
||||
},
|
||||
{
|
||||
"test complete multiple queued transactions",
|
||||
testCompleteMultipleQueuedTransactions,
|
||||
"send transaction with invalid password",
|
||||
testSendTransactionInvalidPassword,
|
||||
},
|
||||
{
|
||||
"discard single queued transaction",
|
||||
testDiscardTransaction,
|
||||
},
|
||||
{
|
||||
"test discard multiple queued transactions",
|
||||
testDiscardMultipleQueuedTransactions,
|
||||
"failed single transaction",
|
||||
testFailedTransaction,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -243,7 +238,7 @@ func testResetChainData(t *testing.T) bool {
|
|||
}
|
||||
|
||||
EnsureNodeSync(statusBackend.StatusNode().EnsureSync)
|
||||
testCompleteTransaction(t)
|
||||
testSendTransaction(t)
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -350,7 +345,7 @@ func testStopResumeNode(t *testing.T) bool { //nolint: gocyclo
|
|||
}
|
||||
|
||||
// additionally, let's complete transaction (just to make sure that node lives through pause/resume w/o issues)
|
||||
testCompleteTransaction(t)
|
||||
testSendTransaction(t)
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -776,9 +771,12 @@ func testAccountLogout(t *testing.T) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func testCompleteTransaction(t *testing.T) bool {
|
||||
signRequests := statusBackend.PendingSignRequests()
|
||||
type jsonrpcAnyResponse struct {
|
||||
Result json.RawMessage `json:"result"`
|
||||
jsonrpcErrorResponse
|
||||
}
|
||||
|
||||
func testSendTransaction(t *testing.T) bool {
|
||||
EnsureNodeSync(statusBackend.StatusNode().EnsureSync)
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
|
@ -787,487 +785,109 @@ func testCompleteTransaction(t *testing.T) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// make sure you panic if transaction complete doesn't return
|
||||
queuedTxCompleted := make(chan struct{}, 1)
|
||||
abortPanic := make(chan struct{}, 1)
|
||||
PanicAfter(10*time.Second, abortPanic, "testCompleteTransaction")
|
||||
|
||||
// replace transaction notification handler
|
||||
var txHash = ""
|
||||
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var envelope signal.Envelope
|
||||
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
||||
t.Errorf("cannot unmarshal event's JSON: %s. Error %q", jsonEvent, err)
|
||||
return
|
||||
}
|
||||
if envelope.Type == signal.EventSignRequestAdded {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
t.Logf("transaction queued (will be completed shortly): {id: %s}\n", event["id"].(string))
|
||||
|
||||
completeTxResponse := SignRequestResult{}
|
||||
rawResponse := ApproveSignRequest(C.CString(event["id"].(string)), C.CString(TestConfig.Account1.Password))
|
||||
|
||||
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &completeTxResponse); err != nil {
|
||||
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
|
||||
}
|
||||
|
||||
if completeTxResponse.Error != "" {
|
||||
t.Errorf("cannot complete queued transaction[%v]: %v", event["id"], completeTxResponse.Error)
|
||||
}
|
||||
|
||||
txHash = completeTxResponse.Hash
|
||||
|
||||
t.Logf("transaction complete: https://testnet.etherscan.io/tx/%s", txHash)
|
||||
abortPanic <- struct{}{} // so that timeout is aborted
|
||||
queuedTxCompleted <- struct{}{}
|
||||
}
|
||||
})
|
||||
|
||||
// this call blocks, up until Complete Transaction is called
|
||||
txCheckHash, err := statusBackend.SendTransaction(context.TODO(), transactions.SendTxArgs{
|
||||
args, err := json.Marshal(transactions.SendTxArgs{
|
||||
From: account.FromAddress(TestConfig.Account1.Address),
|
||||
To: account.ToAddress(TestConfig.Account2.Address),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Failed to SendTransaction: %s", err)
|
||||
t.Errorf("failed to marshal errors: %v", err)
|
||||
return false
|
||||
}
|
||||
rawResult := SendTransaction(C.CString(string(args)), C.CString(TestConfig.Account1.Password))
|
||||
|
||||
<-queuedTxCompleted // make sure that complete transaction handler completes its magic, before we proceed
|
||||
|
||||
if txHash != txCheckHash.Hex() {
|
||||
t.Errorf("Transaction hash returned from SendTransaction is invalid: expected %s, got %s",
|
||||
txCheckHash.Hex(), txHash)
|
||||
var result jsonrpcAnyResponse
|
||||
if err := json.Unmarshal([]byte(C.GoString(rawResult)), &result); err != nil {
|
||||
t.Errorf("failed to unmarshal rawResult '%s': %v", C.GoString(rawResult), err)
|
||||
return false
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(txCheckHash, gethcommon.Hash{}) {
|
||||
t.Error("Test failed: transaction was never queued or completed")
|
||||
if result.Error.Message != "" {
|
||||
t.Errorf("failed to send transaction: %v", result.Error)
|
||||
return false
|
||||
}
|
||||
|
||||
if signRequests.Count() != 0 {
|
||||
t.Error("tx queue must be empty at this point")
|
||||
hash := gethcommon.BytesToHash(result.Result)
|
||||
if reflect.DeepEqual(hash, gethcommon.Hash{}) {
|
||||
t.Errorf("response hash empty: %s", hash.Hex())
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func testCompleteMultipleQueuedTransactions(t *testing.T) bool { //nolint: gocyclo
|
||||
signRequests := statusBackend.PendingSignRequests()
|
||||
func testSendTransactionInvalidPassword(t *testing.T) bool {
|
||||
EnsureNodeSync(statusBackend.StatusNode().EnsureSync)
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
if err := statusBackend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot select account: %v", TestConfig.Account1.Address)
|
||||
if err := statusBackend.SelectAccount(
|
||||
TestConfig.Account1.Address,
|
||||
TestConfig.Account1.Password,
|
||||
); err != nil {
|
||||
t.Errorf("cannot select account: %v. Error %q", TestConfig.Account1.Address, err)
|
||||
return false
|
||||
}
|
||||
|
||||
// make sure you panic if transaction complete doesn't return
|
||||
testTxCount := 3
|
||||
txIDs := make(chan string, testTxCount)
|
||||
allTestTxCompleted := make(chan struct{}, 1)
|
||||
|
||||
// replace transaction notification handler
|
||||
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var txID string
|
||||
var envelope signal.Envelope
|
||||
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
||||
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
||||
return
|
||||
}
|
||||
if envelope.Type == signal.EventSignRequestAdded {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
txID = event["id"].(string)
|
||||
t.Logf("transaction queued (will be completed in a single call, once aggregated): {id: %s}\n", txID)
|
||||
|
||||
txIDs <- txID
|
||||
}
|
||||
})
|
||||
|
||||
// this call blocks, and should return when DiscardQueuedTransaction() for a given tx id is called
|
||||
sendTx := func() {
|
||||
txHashCheck, err := statusBackend.SendTransaction(context.TODO(), transactions.SendTxArgs{
|
||||
From: account.FromAddress(TestConfig.Account1.Address),
|
||||
To: account.ToAddress(TestConfig.Account2.Address),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error thrown: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(txHashCheck, gethcommon.Hash{}) {
|
||||
t.Error("transaction returned empty hash")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// wait for transactions, and complete them in a single call
|
||||
completeTxs := func(txIDStrings string) {
|
||||
var parsedIDs []string
|
||||
if err := json.Unmarshal([]byte(txIDStrings), &parsedIDs); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
parsedIDs = append(parsedIDs, "invalid-tx-id")
|
||||
updatedTxIDStrings, _ := json.Marshal(parsedIDs)
|
||||
|
||||
// complete
|
||||
resultsString := ApproveSignRequests(C.CString(string(updatedTxIDStrings)), C.CString(TestConfig.Account1.Password))
|
||||
resultsStruct := SignRequestsResult{}
|
||||
if err := json.Unmarshal([]byte(C.GoString(resultsString)), &resultsStruct); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
results := resultsStruct.Results
|
||||
|
||||
if len(results) != (testTxCount+1) || results["invalid-tx-id"].Error != sign.ErrSignReqNotFound.Error() {
|
||||
t.Errorf("cannot complete txs: %v", results)
|
||||
return
|
||||
}
|
||||
for txID, txResult := range results {
|
||||
if txID != txResult.ID {
|
||||
t.Errorf("tx id not set in result: expected id is %s", txID)
|
||||
return
|
||||
}
|
||||
if txResult.Error != "" && txID != "invalid-tx-id" {
|
||||
t.Errorf("invalid error for %s", txID)
|
||||
return
|
||||
}
|
||||
if txResult.Hash == zeroHash && txID != "invalid-tx-id" {
|
||||
t.Errorf("invalid hash (expected non empty hash): %s", txID)
|
||||
return
|
||||
}
|
||||
|
||||
if txResult.Hash != zeroHash {
|
||||
t.Logf("transaction complete: https://testnet.etherscan.io/tx/%s", txResult.Hash)
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
|
||||
for _, txID := range parsedIDs {
|
||||
if signRequests.Has(string(txID)) {
|
||||
t.Errorf("txqueue should not have test tx at this point (it should be completed): %s", txID)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
go func() {
|
||||
var txIDStrings []string
|
||||
for i := 0; i < testTxCount; i++ {
|
||||
txIDStrings = append(txIDStrings, <-txIDs)
|
||||
}
|
||||
|
||||
txIDJSON, _ := json.Marshal(txIDStrings)
|
||||
completeTxs(string(txIDJSON))
|
||||
allTestTxCompleted <- struct{}{}
|
||||
}()
|
||||
|
||||
// send multiple transactions
|
||||
for i := 0; i < testTxCount; i++ {
|
||||
go sendTx()
|
||||
}
|
||||
|
||||
select {
|
||||
case <-allTestTxCompleted:
|
||||
// pass
|
||||
case <-time.After(20 * time.Second):
|
||||
t.Error("test timed out")
|
||||
return false
|
||||
}
|
||||
|
||||
if signRequests.Count() != 0 {
|
||||
t.Error("tx queue must be empty at this point")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func testDiscardTransaction(t *testing.T) bool { //nolint: gocyclo
|
||||
signRequests := statusBackend.PendingSignRequests()
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
if err := statusBackend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot select account: %v", TestConfig.Account1.Address)
|
||||
return false
|
||||
}
|
||||
|
||||
// make sure you panic if transaction complete doesn't return
|
||||
completeQueuedTransaction := make(chan struct{}, 1)
|
||||
PanicAfter(20*time.Second, completeQueuedTransaction, "testDiscardTransaction")
|
||||
|
||||
// replace transaction notification handler
|
||||
var txID string
|
||||
txFailedEventCalled := make(chan struct{})
|
||||
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var envelope signal.Envelope
|
||||
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
||||
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
||||
return
|
||||
}
|
||||
if envelope.Type == signal.EventSignRequestAdded {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
txID = event["id"].(string)
|
||||
t.Logf("transaction queued (will be discarded soon): {id: %s}\n", txID)
|
||||
|
||||
if !signRequests.Has(string(txID)) {
|
||||
t.Errorf("txqueue should still have test tx: %s", txID)
|
||||
return
|
||||
}
|
||||
|
||||
// discard
|
||||
discardResponse := DiscardSignRequestResult{}
|
||||
rawResponse := DiscardSignRequest(C.CString(txID))
|
||||
|
||||
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &discardResponse); err != nil {
|
||||
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
|
||||
}
|
||||
|
||||
if discardResponse.Error != "" {
|
||||
t.Errorf("cannot discard tx: %v", discardResponse.Error)
|
||||
return
|
||||
}
|
||||
|
||||
// try completing discarded transaction
|
||||
err := statusBackend.ApproveSignRequest(string(txID), TestConfig.Account1.Password).Error
|
||||
if err != sign.ErrSignReqNotFound {
|
||||
t.Error("expects tx not found, but call to CompleteTransaction succeeded")
|
||||
return
|
||||
}
|
||||
|
||||
if signRequests.Has(string(txID)) {
|
||||
t.Errorf("txqueue should not have test tx at this point (it should be discarded): %s", txID)
|
||||
return
|
||||
}
|
||||
|
||||
completeQueuedTransaction <- struct{}{} // so that timeout is aborted
|
||||
}
|
||||
|
||||
if envelope.Type == signal.EventSignRequestFailed {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
t.Logf("transaction return event received: %+v\n", event)
|
||||
|
||||
receivedErrMessage := event["error_message"].(string)
|
||||
expectedErrMessage := sign.ErrSignReqDiscarded.Error()
|
||||
if receivedErrMessage != expectedErrMessage {
|
||||
t.Errorf("unexpected error message received: got %v", receivedErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
receivedErrCode := event["error_code"].(string)
|
||||
if receivedErrCode != strconv.Itoa(sign.SignRequestDiscardedErrorCode) {
|
||||
t.Errorf("unexpected error code received: got %v", receivedErrCode)
|
||||
return
|
||||
}
|
||||
|
||||
close(txFailedEventCalled)
|
||||
}
|
||||
})
|
||||
|
||||
// this call blocks, and should return when DiscardQueuedTransaction() is called
|
||||
txHashCheck, err := statusBackend.SendTransaction(context.TODO(), transactions.SendTxArgs{
|
||||
args, err := json.Marshal(transactions.SendTxArgs{
|
||||
From: account.FromAddress(TestConfig.Account1.Address),
|
||||
To: account.ToAddress(TestConfig.Account2.Address),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
|
||||
select {
|
||||
case <-txFailedEventCalled:
|
||||
case <-time.After(time.Second * 10):
|
||||
t.Error("expected tx failure signal is not received")
|
||||
if err != nil {
|
||||
t.Errorf("failed to marshal errors: %v", err)
|
||||
return false
|
||||
}
|
||||
rawResult := SendTransaction(C.CString(string(args)), C.CString("invalid password"))
|
||||
|
||||
if err != sign.ErrSignReqDiscarded {
|
||||
t.Errorf("expected error not thrown: %v", err)
|
||||
var result jsonrpcAnyResponse
|
||||
if err := json.Unmarshal([]byte(C.GoString(rawResult)), &result); err != nil {
|
||||
t.Errorf("failed to unmarshal rawResult '%s': %v", C.GoString(rawResult), err)
|
||||
return false
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(txHashCheck, gethcommon.Hash{}) {
|
||||
t.Error("transaction returned hash, while it shouldn't")
|
||||
return false
|
||||
}
|
||||
|
||||
if signRequests.Count() != 0 {
|
||||
t.Error("tx queue must be empty at this point")
|
||||
if result.Error.Message != keystore.ErrDecrypt.Error() {
|
||||
t.Errorf("invalid result: %q", result)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func testDiscardMultipleQueuedTransactions(t *testing.T) bool { //nolint: gocyclo
|
||||
signRequests := statusBackend.PendingSignRequests()
|
||||
func testFailedTransaction(t *testing.T) bool {
|
||||
EnsureNodeSync(statusBackend.StatusNode().EnsureSync)
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
if err := statusBackend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot select account: %v", TestConfig.Account1.Address)
|
||||
// log into wrong account in order to get selectedAccount error
|
||||
if err := statusBackend.SelectAccount(TestConfig.Account2.Address, TestConfig.Account2.Password); err != nil {
|
||||
t.Errorf("cannot select account: %v. Error %q", TestConfig.Account1.Address, err)
|
||||
return false
|
||||
}
|
||||
|
||||
// make sure you panic if transaction complete doesn't return
|
||||
testTxCount := 3
|
||||
txIDs := make(chan string, testTxCount)
|
||||
|
||||
var testTxDiscarded sync.WaitGroup
|
||||
testTxDiscarded.Add(testTxCount)
|
||||
|
||||
// replace transaction notification handler
|
||||
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var txID string
|
||||
var envelope signal.Envelope
|
||||
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
||||
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
||||
return
|
||||
}
|
||||
if envelope.Type == signal.EventSignRequestAdded {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
txID = event["id"].(string)
|
||||
t.Logf("transaction queued (will be discarded soon): {id: %s}\n", txID)
|
||||
|
||||
if !signRequests.Has(string(txID)) {
|
||||
t.Errorf("txqueue should still have test tx: %s", txID)
|
||||
return
|
||||
}
|
||||
|
||||
txIDs <- txID
|
||||
}
|
||||
|
||||
if envelope.Type == signal.EventSignRequestFailed {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
t.Logf("transaction return event received: {id: %s}\n", event["id"].(string))
|
||||
|
||||
receivedErrMessage := event["error_message"].(string)
|
||||
expectedErrMessage := sign.ErrSignReqDiscarded.Error()
|
||||
if receivedErrMessage != expectedErrMessage {
|
||||
t.Errorf("unexpected error message received: got %v", receivedErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
receivedErrCode := event["error_code"].(string)
|
||||
if receivedErrCode != strconv.Itoa(sign.SignRequestDiscardedErrorCode) {
|
||||
t.Errorf("unexpected error code received: got %v", receivedErrCode)
|
||||
return
|
||||
}
|
||||
|
||||
testTxDiscarded.Done()
|
||||
}
|
||||
args, err := json.Marshal(transactions.SendTxArgs{
|
||||
From: account.FromAddress(TestConfig.Account1.Address),
|
||||
To: account.ToAddress(TestConfig.Account2.Address),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
|
||||
// this call blocks, and should return when DiscardQueuedTransaction() for a given tx id is called
|
||||
sendTx := func() {
|
||||
txHashCheck, err := statusBackend.SendTransaction(context.TODO(), transactions.SendTxArgs{
|
||||
From: account.FromAddress(TestConfig.Account1.Address),
|
||||
To: account.ToAddress(TestConfig.Account2.Address),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
if err != sign.ErrSignReqDiscarded {
|
||||
t.Errorf("expected error not thrown: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(txHashCheck, gethcommon.Hash{}) {
|
||||
t.Error("transaction returned hash, while it shouldn't")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("failed to marshal errors: %v", err)
|
||||
return false
|
||||
}
|
||||
rawResult := SendTransaction(C.CString(string(args)), C.CString(TestConfig.Account1.Password))
|
||||
|
||||
// wait for transactions, and discard immediately
|
||||
discardTxs := func(txIDStrings string) {
|
||||
var parsedIDs []string
|
||||
if err := json.Unmarshal([]byte(txIDStrings), &parsedIDs); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
parsedIDs = append(parsedIDs, "invalid-tx-id")
|
||||
updatedTxIDStrings, _ := json.Marshal(parsedIDs)
|
||||
|
||||
// discard
|
||||
discardResultsString := DiscardSignRequests(C.CString(string(updatedTxIDStrings)))
|
||||
discardResultsStruct := DiscardSignRequestsResult{}
|
||||
if err := json.Unmarshal([]byte(C.GoString(discardResultsString)), &discardResultsStruct); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
discardResults := discardResultsStruct.Results
|
||||
|
||||
if len(discardResults) != 1 || discardResults["invalid-tx-id"].Error != sign.ErrSignReqNotFound.Error() {
|
||||
t.Errorf("cannot discard txs: %v", discardResults)
|
||||
return
|
||||
}
|
||||
|
||||
// try completing discarded transaction
|
||||
completeResultsString := ApproveSignRequests(C.CString(string(updatedTxIDStrings)), C.CString(TestConfig.Account1.Password))
|
||||
completeResultsStruct := SignRequestsResult{}
|
||||
if err := json.Unmarshal([]byte(C.GoString(completeResultsString)), &completeResultsStruct); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
completeResults := completeResultsStruct.Results
|
||||
|
||||
if len(completeResults) != (testTxCount + 1) {
|
||||
t.Error("unexpected number of errors (call to ApproveSignRequest should not succeed)")
|
||||
}
|
||||
for txID, txResult := range completeResults {
|
||||
if txID != txResult.ID {
|
||||
t.Errorf("tx id not set in result: expected id is %s", txID)
|
||||
return
|
||||
}
|
||||
if txResult.Error != sign.ErrSignReqNotFound.Error() {
|
||||
t.Errorf("invalid error for %s", txResult.Hash)
|
||||
return
|
||||
}
|
||||
if txResult.Hash != zeroHash {
|
||||
t.Errorf("invalid hash (expected zero): '%s'", txResult.Hash)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
|
||||
for _, txID := range parsedIDs {
|
||||
if signRequests.Has(string(txID)) {
|
||||
t.Errorf("txqueue should not have test tx at this point (it should be discarded): %s", txID)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
go func() {
|
||||
var txIDStrings []string
|
||||
for i := 0; i < testTxCount; i++ {
|
||||
txIDStrings = append(txIDStrings, <-txIDs)
|
||||
}
|
||||
|
||||
txIDJSON, _ := json.Marshal(txIDStrings)
|
||||
discardTxs(string(txIDJSON))
|
||||
}()
|
||||
|
||||
// send multiple transactions
|
||||
for i := 0; i < testTxCount; i++ {
|
||||
go sendTx()
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() { testTxDiscarded.Wait(); close(done) }()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
// pass
|
||||
case <-time.After(20 * time.Second):
|
||||
t.Error("test timed out")
|
||||
var result jsonrpcAnyResponse
|
||||
if err := json.Unmarshal([]byte(C.GoString(rawResult)), &result); err != nil {
|
||||
t.Errorf("failed to unmarshal rawResult '%s': %v", C.GoString(rawResult), err)
|
||||
return false
|
||||
}
|
||||
|
||||
if signRequests.Count() != 0 {
|
||||
t.Error("tx queue must be empty at this point")
|
||||
if result.Error.Message != transactions.ErrInvalidTxSender.Error() {
|
||||
t.Errorf("expected error to be ErrInvalidTxSender, got %s", result.Error.Message)
|
||||
return false
|
||||
}
|
||||
|
||||
if result.Result != nil {
|
||||
t.Errorf("expected result to be nil")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
func startTestNode(t *testing.T) <-chan struct{} {
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/status-im/status-go/account"
|
||||
"github.com/status-im/status-go/transactions"
|
||||
)
|
||||
|
||||
const (
|
||||
codeUnknown int = iota
|
||||
// special codes
|
||||
codeFailedParseResponse
|
||||
codeFailedParseParams
|
||||
// account related codes
|
||||
codeErrNoAccountSelected
|
||||
codeErrInvalidTxSender
|
||||
codeErrDecrypt
|
||||
)
|
||||
|
||||
var errToCodeMap = map[error]int{
|
||||
account.ErrNoAccountSelected: codeErrNoAccountSelected,
|
||||
transactions.ErrInvalidTxSender: codeErrInvalidTxSender,
|
||||
keystore.ErrDecrypt: codeErrDecrypt,
|
||||
}
|
||||
|
||||
type jsonrpcSuccessfulResponse struct {
|
||||
Result interface{} `json:"result"`
|
||||
}
|
||||
|
||||
type jsonrpcErrorResponse struct {
|
||||
Error jsonError `json:"error"`
|
||||
}
|
||||
|
||||
type jsonError struct {
|
||||
Code int `json:"code,omitempty"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func prepareJSONResponse(result interface{}, err error) string {
|
||||
code := codeUnknown
|
||||
if c, ok := errToCodeMap[err]; ok {
|
||||
code = c
|
||||
}
|
||||
|
||||
return prepareJSONResponseWithCode(result, err, code)
|
||||
}
|
||||
|
||||
func prepareJSONResponseWithCode(result interface{}, err error, code int) string {
|
||||
if err != nil {
|
||||
errResponse := jsonrpcErrorResponse{
|
||||
Error: jsonError{Code: code, Message: err.Error()},
|
||||
}
|
||||
response, _ := json.Marshal(&errResponse)
|
||||
return string(response)
|
||||
}
|
||||
|
||||
data, err := json.Marshal(jsonrpcSuccessfulResponse{result})
|
||||
if err != nil {
|
||||
return prepareJSONResponseWithCode(nil, err, codeFailedParseResponse)
|
||||
}
|
||||
return string(data)
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type nonJSON struct{}
|
||||
|
||||
func (*nonJSON) MarshalJSON() ([]byte, error) {
|
||||
return nil, errors.New("invalid JSON")
|
||||
}
|
||||
|
||||
func TestPrepareJSONResponseErrorWithResult(t *testing.T) {
|
||||
data := prepareJSONResponse("0x123", nil)
|
||||
require.Equal(t, `{"result":"0x123"}`, data)
|
||||
|
||||
data = prepareJSONResponse(&nonJSON{}, nil)
|
||||
require.Contains(t, data, `{"error":{"code":1,"message":`)
|
||||
}
|
||||
|
||||
func TestPrepareJSONResponseErrorWithError(t *testing.T) {
|
||||
data := prepareJSONResponse("0x123", errors.New("some error"))
|
||||
require.Contains(t, data, `{"error":{"message":"some error"}}`)
|
||||
}
|
23
lib/types.go
23
lib/types.go
|
@ -75,26 +75,3 @@ type NotifyResult struct {
|
|||
Status bool `json:"status"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// SignRequestResult is a JSON returned from transaction complete function (used in exposed method)
|
||||
type SignRequestResult struct {
|
||||
ID string `json:"id"`
|
||||
Hash string `json:"hash"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// SignRequestsResult is list of results from CompleteTransactions() (used in exposed method)
|
||||
type SignRequestsResult struct {
|
||||
Results map[string]SignRequestResult `json:"results"`
|
||||
}
|
||||
|
||||
// DiscardSignRequestResult is a JSON returned from transaction discard function
|
||||
type DiscardSignRequestResult struct {
|
||||
ID string `json:"id"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// DiscardSignRequestsResult is a list of results from DiscardTransactions()
|
||||
type DiscardSignRequestsResult struct {
|
||||
Results map[string]DiscardSignRequestResult `json:"results"`
|
||||
}
|
||||
|
|
|
@ -6,53 +6,42 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/status-im/status-go/account"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/rpc"
|
||||
"github.com/status-im/status-go/sign"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidPersonalSignAccount is returned when the account passed to
|
||||
// personal_sign isn't equal to the currently selected account.
|
||||
ErrInvalidPersonalSignAccount = errors.New("invalid account as only the selected one can generate a signature")
|
||||
|
||||
// ErrSignInvalidNumberOfParameters is returned when the number of parameters for personal_sign
|
||||
// is not valid.
|
||||
ErrSignInvalidNumberOfParameters = errors.New("invalid number of parameters for personal_sign (2 or 3 expected)")
|
||||
)
|
||||
|
||||
type metadata struct {
|
||||
Data interface{} `json:"data"`
|
||||
Address string `json:"account"`
|
||||
// SignParams required to sign messages
|
||||
type SignParams struct {
|
||||
Data interface{} `json:"data"`
|
||||
Address string `json:"account"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func newMetadata(rpcParams []interface{}) (*metadata, error) {
|
||||
// personal_sign can be called with the following parameters
|
||||
// 1) data to sign
|
||||
// 2) account
|
||||
// 3) (optional) password
|
||||
// here, we always ignore (3) because we send a confirmation for the password to UI
|
||||
if len(rpcParams) < 2 || len(rpcParams) > 3 {
|
||||
return nil, ErrSignInvalidNumberOfParameters
|
||||
}
|
||||
data := rpcParams[0]
|
||||
address := rpcParams[1].(string)
|
||||
|
||||
return &metadata{data, address}, nil
|
||||
// RecoverParams are for calling `personal_ecRecover`
|
||||
type RecoverParams struct {
|
||||
Message string `json:"message"`
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
|
||||
// PublicAPI represents a set of APIs from the `web3.personal` namespace.
|
||||
type PublicAPI struct {
|
||||
pendingSignRequests *sign.PendingRequests
|
||||
rpcClient *rpc.Client
|
||||
rpcTimeout time.Duration
|
||||
rpcClient *rpc.Client
|
||||
rpcTimeout time.Duration
|
||||
}
|
||||
|
||||
// NewAPI creates an instance of the personal API.
|
||||
func NewAPI(pendingSignRequests *sign.PendingRequests) *PublicAPI {
|
||||
func NewAPI() *PublicAPI {
|
||||
return &PublicAPI{
|
||||
pendingSignRequests: pendingSignRequests,
|
||||
rpcTimeout: 300 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,59 +52,32 @@ func (api *PublicAPI) SetRPC(rpcClient *rpc.Client, timeout time.Duration) {
|
|||
}
|
||||
|
||||
// Recover is an implementation of `personal_ecRecover` or `web3.personal.ecRecover` API
|
||||
func (api *PublicAPI) Recover(context context.Context, rpcParams ...interface{}) (interface{}, error) {
|
||||
var response interface{}
|
||||
func (api *PublicAPI) Recover(rpcParams RecoverParams) (addr common.Address, err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), api.rpcTimeout)
|
||||
defer cancel()
|
||||
err = api.rpcClient.CallContextIgnoringLocalHandlers(
|
||||
ctx,
|
||||
&addr,
|
||||
params.PersonalRecoverMethodName,
|
||||
rpcParams.Message, rpcParams.Signature)
|
||||
|
||||
err := api.rpcClient.CallContextIgnoringLocalHandlers(
|
||||
context, &response, params.PersonalRecoverMethodName, rpcParams...)
|
||||
|
||||
return response, err
|
||||
return
|
||||
}
|
||||
|
||||
// Sign is an implementation of `personal_sign` or `web3.personal.sign` API
|
||||
func (api *PublicAPI) Sign(context context.Context, rpcParams ...interface{}) (interface{}, error) {
|
||||
metadata, err := newMetadata(rpcParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := api.pendingSignRequests.Add(context, params.PersonalSignMethodName, metadata, api.completeFunc(context, *metadata))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := api.pendingSignRequests.Wait(req.ID, api.rpcTimeout)
|
||||
return result.Response, result.Error
|
||||
}
|
||||
|
||||
func (api *PublicAPI) completeFunc(context context.Context, metadata metadata) sign.CompleteFunc {
|
||||
return func(acc *account.SelectedExtKey, password string, signArgs *sign.TxArgs) (response sign.Response, err error) {
|
||||
response = sign.EmptyResponse
|
||||
|
||||
err = api.validateAccount(metadata, acc)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = api.rpcClient.CallContextIgnoringLocalHandlers(
|
||||
context,
|
||||
&response,
|
||||
params.PersonalSignMethodName,
|
||||
metadata.Data, metadata.Address, password)
|
||||
|
||||
func (api *PublicAPI) Sign(rpcParams SignParams, verifiedAccount *account.SelectedExtKey) (result hexutil.Bytes, err error) {
|
||||
if !strings.EqualFold(rpcParams.Address, verifiedAccount.Address.Hex()) {
|
||||
err = ErrInvalidPersonalSignAccount
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// make sure that only account which created the tx can complete it
|
||||
func (api *PublicAPI) validateAccount(metadata metadata, selectedAccount *account.SelectedExtKey) error {
|
||||
if selectedAccount == nil {
|
||||
return account.ErrNoAccountSelected
|
||||
}
|
||||
|
||||
// case-insensitive string comparison
|
||||
if !strings.EqualFold(metadata.Address, selectedAccount.Address.Hex()) {
|
||||
return ErrInvalidPersonalSignAccount
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), api.rpcTimeout)
|
||||
defer cancel()
|
||||
err = api.rpcClient.CallContextIgnoringLocalHandlers(
|
||||
ctx,
|
||||
&result,
|
||||
params.PersonalSignMethodName,
|
||||
rpcParams.Data, rpcParams.Address, rpcParams.Password)
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
# sign
|
||||
`sign` package represents the API and signals for sending and receiving
|
||||
signature request to and from our API user.
|
||||
|
||||
When a method is called that requires an additional signature confirmation from
|
||||
a user (like, a transaction), it gets it's sign request.
|
||||
|
||||
Client of the API is then nofified of the sign request.
|
||||
|
||||
Client has a chance to approve the sign request (by providing a valid password)
|
||||
or to discard it. When the request is approved, the locked functinality is
|
||||
executed.
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
package sign
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/status-im/status-go/account"
|
||||
)
|
||||
|
||||
var (
|
||||
//ErrSignReqNotFound - error sign request hash not found
|
||||
ErrSignReqNotFound = errors.New("sign request not found")
|
||||
//ErrSignReqInProgress - error sign request is in progress
|
||||
ErrSignReqInProgress = errors.New("sign request is in progress")
|
||||
//ErrSignReqTimedOut - error sign request sending timed out
|
||||
ErrSignReqTimedOut = errors.New("sign request sending timed out")
|
||||
//ErrSignReqDiscarded - error sign request discarded
|
||||
ErrSignReqDiscarded = errors.New("sign request has been discarded")
|
||||
)
|
||||
|
||||
// TransientError means that the sign request won't be removed from the list of
|
||||
// pending if it happens. There are a few built-in transient errors, and this
|
||||
// struct can be used to wrap any error to be transient.
|
||||
type TransientError struct {
|
||||
Reason error
|
||||
}
|
||||
|
||||
// Error returns the string representation of the underlying error.
|
||||
func (e TransientError) Error() string {
|
||||
return e.Reason.Error()
|
||||
}
|
||||
|
||||
// NewTransientError wraps an error into a TransientError structure.
|
||||
func NewTransientError(reason error) TransientError {
|
||||
return TransientError{reason}
|
||||
}
|
||||
|
||||
// remove from queue on any error (except for transient ones) and propagate
|
||||
// defined as map[string]bool because errors from ethclient returned wrapped as jsonError
|
||||
var transientErrs = map[string]bool{
|
||||
keystore.ErrDecrypt.Error(): true, // wrong password
|
||||
account.ErrNoAccountSelected.Error(): true, // account not selected
|
||||
}
|
||||
|
||||
func isTransient(err error) bool {
|
||||
_, ok := err.(TransientError)
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
_, transient := transientErrs[err.Error()]
|
||||
return transient
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
package sign
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/status-im/status-go/signal"
|
||||
)
|
||||
|
||||
const (
|
||||
// MessageIDKey is a key for message ID
|
||||
// This ID is required to track from which chat a given send transaction request is coming.
|
||||
MessageIDKey = contextKey("message_id")
|
||||
)
|
||||
|
||||
type contextKey string // in order to make sure that our context key does not collide with keys from other packages
|
||||
|
||||
// messageIDFromContext returns message id from context (if exists)
|
||||
func messageIDFromContext(ctx context.Context) string {
|
||||
if ctx == nil {
|
||||
return ""
|
||||
}
|
||||
if messageID, ok := ctx.Value(MessageIDKey).(string); ok {
|
||||
return messageID
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// SendSignRequestAdded sends a signal when a sign request is added.
|
||||
func SendSignRequestAdded(request *Request) {
|
||||
signal.SendSignRequestAdded(
|
||||
signal.PendingRequestEvent{
|
||||
ID: request.ID,
|
||||
Args: request.Meta,
|
||||
Method: request.Method,
|
||||
MessageID: messageIDFromContext(request.context),
|
||||
})
|
||||
}
|
||||
|
||||
// SendSignRequestFailed sends a signal only if error had happened
|
||||
func SendSignRequestFailed(request *Request, err error) {
|
||||
signal.SendSignRequestFailed(
|
||||
signal.PendingRequestEvent{
|
||||
ID: request.ID,
|
||||
Args: request.Meta,
|
||||
Method: request.Method,
|
||||
MessageID: messageIDFromContext(request.context),
|
||||
},
|
||||
err, sendTransactionErrorCode(err))
|
||||
}
|
||||
|
||||
const (
|
||||
// SignRequestNoErrorCode is sent when no error occurred.
|
||||
SignRequestNoErrorCode = iota
|
||||
// SignRequestDefaultErrorCode is every case when there is no special tx return code.
|
||||
SignRequestDefaultErrorCode
|
||||
// SignRequestPasswordErrorCode is sent when account failed verification.
|
||||
SignRequestPasswordErrorCode
|
||||
// SignRequestTimeoutErrorCode is sent when tx is timed out.
|
||||
SignRequestTimeoutErrorCode
|
||||
// SignRequestDiscardedErrorCode is sent when tx was discarded.
|
||||
SignRequestDiscardedErrorCode
|
||||
)
|
||||
|
||||
var txReturnCodes = map[error]int{
|
||||
nil: SignRequestNoErrorCode,
|
||||
keystore.ErrDecrypt: SignRequestPasswordErrorCode,
|
||||
ErrSignReqTimedOut: SignRequestTimeoutErrorCode,
|
||||
ErrSignReqDiscarded: SignRequestDiscardedErrorCode,
|
||||
}
|
||||
|
||||
func sendTransactionErrorCode(err error) int {
|
||||
if code, ok := txReturnCodes[err]; ok {
|
||||
return code
|
||||
}
|
||||
return SignRequestDefaultErrorCode
|
||||
}
|
|
@ -1,181 +0,0 @@
|
|||
package sign
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/status-im/status-go/account"
|
||||
)
|
||||
|
||||
type verifyFunc func(string) (*account.SelectedExtKey, error)
|
||||
|
||||
// PendingRequests is a capped container that holds pending signing requests.
|
||||
type PendingRequests struct {
|
||||
mu sync.RWMutex // to guard transactions map
|
||||
requests map[string]*Request
|
||||
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
// NewPendingRequests creates a new requests list
|
||||
func NewPendingRequests() *PendingRequests {
|
||||
logger := log.New("package", "status-go/sign.PendingRequests")
|
||||
|
||||
return &PendingRequests{
|
||||
requests: make(map[string]*Request),
|
||||
log: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Add a new signing request.
|
||||
func (rs *PendingRequests) Add(ctx context.Context, method string, meta Meta, completeFunc CompleteFunc) (*Request, error) {
|
||||
rs.mu.Lock()
|
||||
defer rs.mu.Unlock()
|
||||
|
||||
request := newRequest(ctx, method, meta, completeFunc)
|
||||
rs.requests[request.ID] = request
|
||||
rs.log.Info("signing request is created", "ID", request.ID)
|
||||
|
||||
go SendSignRequestAdded(request)
|
||||
|
||||
return request, nil
|
||||
}
|
||||
|
||||
// Get returns a signing request by it's ID.
|
||||
func (rs *PendingRequests) Get(id string) (*Request, error) {
|
||||
rs.mu.RLock()
|
||||
defer rs.mu.RUnlock()
|
||||
|
||||
if request, ok := rs.requests[id]; ok {
|
||||
return request, nil
|
||||
}
|
||||
return nil, ErrSignReqNotFound
|
||||
}
|
||||
|
||||
// First returns a first signing request (if exists, nil otherwise).
|
||||
func (rs *PendingRequests) First() *Request {
|
||||
rs.mu.RLock()
|
||||
defer rs.mu.RUnlock()
|
||||
|
||||
for _, req := range rs.requests {
|
||||
return req
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Approve a signing request by it's ID. Requires a valid password and a verification function.
|
||||
func (rs *PendingRequests) Approve(id string, password string, args *TxArgs, verify verifyFunc) Result {
|
||||
rs.log.Info("complete sign request", "id", id)
|
||||
request, err := rs.tryLock(id)
|
||||
if err != nil {
|
||||
rs.log.Warn("can't process transaction", "err", err)
|
||||
return newErrResult(err)
|
||||
}
|
||||
|
||||
selectedAccount, err := verify(password)
|
||||
if err != nil {
|
||||
rs.complete(request, EmptyResponse, err)
|
||||
return newErrResult(err)
|
||||
}
|
||||
|
||||
response, err := request.completeFunc(selectedAccount, password, args)
|
||||
rs.log.Info("completed sign request ", "id", request.ID, "response", response, "err", err)
|
||||
|
||||
rs.complete(request, response, err)
|
||||
|
||||
return Result{
|
||||
Response: response,
|
||||
Error: err,
|
||||
}
|
||||
}
|
||||
|
||||
// Discard remove a signing request from the list of pending requests.
|
||||
func (rs *PendingRequests) Discard(id string) error {
|
||||
request, err := rs.Get(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rs.complete(request, EmptyResponse, ErrSignReqDiscarded)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait blocks until a request with a specified ID is completed (approved or discarded)
|
||||
func (rs *PendingRequests) Wait(id string, timeout time.Duration) Result {
|
||||
request, err := rs.Get(id)
|
||||
if err != nil {
|
||||
return newErrResult(err)
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case rst := <-request.result:
|
||||
return rst
|
||||
case <-time.After(timeout):
|
||||
_, err := rs.tryLock(request.ID)
|
||||
// if request is not already in progress, we complete it.
|
||||
if err == nil {
|
||||
rs.complete(request, EmptyResponse, ErrSignReqTimedOut)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Count returns number of currently pending requests
|
||||
func (rs *PendingRequests) Count() int {
|
||||
rs.mu.RLock()
|
||||
defer rs.mu.RUnlock()
|
||||
return len(rs.requests)
|
||||
}
|
||||
|
||||
// Has checks whether a pending request with a given identifier exists in the list
|
||||
func (rs *PendingRequests) Has(id string) bool {
|
||||
rs.mu.RLock()
|
||||
defer rs.mu.RUnlock()
|
||||
_, ok := rs.requests[id]
|
||||
return ok
|
||||
}
|
||||
|
||||
// tryLock is used to avoid double-completion of the same request.
|
||||
// it returns a request instance if it isn't processing yet, returns an error otherwise.
|
||||
func (rs *PendingRequests) tryLock(id string) (*Request, error) {
|
||||
rs.mu.Lock()
|
||||
defer rs.mu.Unlock()
|
||||
if tx, ok := rs.requests[id]; ok {
|
||||
if tx.locked {
|
||||
return nil, ErrSignReqInProgress
|
||||
}
|
||||
tx.locked = true
|
||||
return tx, nil
|
||||
}
|
||||
return nil, ErrSignReqNotFound
|
||||
}
|
||||
|
||||
// complete removes the request from the list if there is no error or an error is non-transient
|
||||
func (rs *PendingRequests) complete(request *Request, response Response, err error) {
|
||||
rs.mu.Lock()
|
||||
defer rs.mu.Unlock()
|
||||
|
||||
request.locked = false
|
||||
|
||||
if err != nil {
|
||||
// TODO(divan): do we need the goroutine here?
|
||||
go SendSignRequestFailed(request, err)
|
||||
}
|
||||
|
||||
if err != nil && isTransient(err) {
|
||||
return
|
||||
}
|
||||
|
||||
delete(rs.requests, request.ID)
|
||||
|
||||
// response is updated only if err is nil, but transaction is not removed from a queue
|
||||
result := Result{Error: err}
|
||||
if err == nil {
|
||||
result.Response = response
|
||||
}
|
||||
|
||||
request.result <- result
|
||||
}
|
|
@ -1,258 +0,0 @@
|
|||
package sign
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/big"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
|
||||
"github.com/status-im/status-go/account"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
const (
|
||||
correctPassword = "password-correct"
|
||||
wrongPassword = "password-wrong"
|
||||
)
|
||||
|
||||
var (
|
||||
overridenGas = hexutil.Uint64(90002)
|
||||
overridenGasPrice = (*hexutil.Big)(big.NewInt(20))
|
||||
)
|
||||
|
||||
func testVerifyFunc(password string) (*account.SelectedExtKey, error) {
|
||||
if password == correctPassword {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, keystore.ErrDecrypt
|
||||
}
|
||||
|
||||
func TestPendingRequestsSuite(t *testing.T) {
|
||||
suite.Run(t, new(PendingRequestsSuite))
|
||||
}
|
||||
|
||||
type PendingRequestsSuite struct {
|
||||
suite.Suite
|
||||
pendingRequests *PendingRequests
|
||||
}
|
||||
|
||||
func (s *PendingRequestsSuite) SetupTest() {
|
||||
s.pendingRequests = NewPendingRequests()
|
||||
}
|
||||
|
||||
func (s *PendingRequestsSuite) defaultSignTxArgs() *TxArgs {
|
||||
return &TxArgs{}
|
||||
}
|
||||
|
||||
func (s *PendingRequestsSuite) defaultCompleteFunc() CompleteFunc {
|
||||
hash := gethcommon.Hash{1}
|
||||
return func(acc *account.SelectedExtKey, password string, args *TxArgs) (Response, error) {
|
||||
s.Nil(acc, "account should be `nil`")
|
||||
s.Equal(correctPassword, password)
|
||||
return hash.Bytes(), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PendingRequestsSuite) delayedCompleteFunc() CompleteFunc {
|
||||
hash := gethcommon.Hash{1}
|
||||
return func(acc *account.SelectedExtKey, password string, args *TxArgs) (Response, error) {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
s.Nil(acc, "account should be `nil`")
|
||||
s.Equal(correctPassword, password)
|
||||
return hash.Bytes(), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PendingRequestsSuite) overridenCompleteFunc() CompleteFunc {
|
||||
hash := gethcommon.Hash{1}
|
||||
return func(acc *account.SelectedExtKey, password string, args *TxArgs) (Response, error) {
|
||||
s.Nil(acc, "account should be `nil`")
|
||||
s.Equal(correctPassword, password)
|
||||
s.Equal(&overridenGas, args.Gas)
|
||||
s.Equal(overridenGasPrice, args.GasPrice)
|
||||
return hash.Bytes(), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PendingRequestsSuite) errorCompleteFunc(err error) CompleteFunc {
|
||||
hash := gethcommon.Hash{1}
|
||||
return func(acc *account.SelectedExtKey, password string, args *TxArgs) (Response, error) {
|
||||
s.Nil(acc, "account should be `nil`")
|
||||
return hash.Bytes(), err
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PendingRequestsSuite) TestGet() {
|
||||
req, err := s.pendingRequests.Add(context.Background(), "", nil, s.defaultCompleteFunc())
|
||||
s.NoError(err)
|
||||
for i := 2; i > 0; i-- {
|
||||
actualRequest, err := s.pendingRequests.Get(req.ID)
|
||||
s.NoError(err)
|
||||
s.Equal(req, actualRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PendingRequestsSuite) testComplete(password string, hash gethcommon.Hash, completeFunc CompleteFunc, signArgs *TxArgs) (string, error) {
|
||||
req, err := s.pendingRequests.Add(context.Background(), "", nil, completeFunc)
|
||||
s.NoError(err)
|
||||
|
||||
s.True(s.pendingRequests.Has(req.ID), "sign request should exist")
|
||||
|
||||
result := s.pendingRequests.Approve(req.ID, password, signArgs, testVerifyFunc)
|
||||
|
||||
if s.pendingRequests.Has(req.ID) {
|
||||
// transient error
|
||||
s.Equal(EmptyResponse, result.Response, "no hash should be sent")
|
||||
} else {
|
||||
s.Equal(hash.Bytes(), result.Response.Bytes(), "hashes should match")
|
||||
}
|
||||
|
||||
return req.ID, result.Error
|
||||
}
|
||||
|
||||
func (s *PendingRequestsSuite) TestCompleteSuccess() {
|
||||
id, err := s.testComplete(correctPassword, gethcommon.Hash{1}, s.defaultCompleteFunc(), s.defaultSignTxArgs())
|
||||
s.NoError(err, "no errors should be there")
|
||||
|
||||
s.False(s.pendingRequests.Has(id), "sign request should not exist")
|
||||
}
|
||||
|
||||
func (s *PendingRequestsSuite) TestCompleteTransientError() {
|
||||
hash := gethcommon.Hash{}
|
||||
id, err := s.testComplete(wrongPassword, hash, s.errorCompleteFunc(keystore.ErrDecrypt), s.defaultSignTxArgs())
|
||||
s.Equal(keystore.ErrDecrypt, err, "error value should be preserved")
|
||||
|
||||
s.True(s.pendingRequests.Has(id))
|
||||
// verify that you are able to re-approve it after a transient error
|
||||
_, err = s.pendingRequests.tryLock(id)
|
||||
s.NoError(err)
|
||||
}
|
||||
|
||||
func (s *PendingRequestsSuite) TestCompleteError() {
|
||||
hash := gethcommon.Hash{1}
|
||||
expectedError := errors.New("test")
|
||||
|
||||
id, err := s.testComplete(correctPassword, hash, s.errorCompleteFunc(expectedError), s.defaultSignTxArgs())
|
||||
|
||||
s.Equal(expectedError, err, "error value should be preserved")
|
||||
|
||||
s.False(s.pendingRequests.Has(id))
|
||||
}
|
||||
|
||||
func (s PendingRequestsSuite) TestMultipleComplete() {
|
||||
id, err := s.testComplete(correctPassword, gethcommon.Hash{1}, s.defaultCompleteFunc(), s.defaultSignTxArgs())
|
||||
s.NoError(err, "no errors should be there")
|
||||
|
||||
result := s.pendingRequests.Approve(id, correctPassword, s.defaultSignTxArgs(), testVerifyFunc)
|
||||
|
||||
s.Equal(ErrSignReqNotFound, result.Error)
|
||||
}
|
||||
|
||||
func (s PendingRequestsSuite) TestConcurrentComplete() {
|
||||
req, err := s.pendingRequests.Add(context.Background(), "", nil, s.delayedCompleteFunc())
|
||||
s.NoError(err)
|
||||
|
||||
s.True(s.pendingRequests.Has(req.ID), "sign request should exist")
|
||||
|
||||
var approved int32
|
||||
var tried int32
|
||||
|
||||
for i := 10; i > 0; i-- {
|
||||
go func() {
|
||||
result := s.pendingRequests.Approve(req.ID, correctPassword, s.defaultSignTxArgs(), testVerifyFunc)
|
||||
if result.Error == nil {
|
||||
atomic.AddInt32(&approved, 1)
|
||||
}
|
||||
atomic.AddInt32(&tried, 1)
|
||||
}()
|
||||
}
|
||||
|
||||
rst := s.pendingRequests.Wait(req.ID, 10*time.Second)
|
||||
s.Require().NoError(rst.Error)
|
||||
|
||||
s.False(s.pendingRequests.Has(req.ID), "sign request should exist")
|
||||
|
||||
s.EqualValues(atomic.LoadInt32(&approved), 1, "request should be approved only once")
|
||||
s.EqualValues(atomic.LoadInt32(&tried), 10, "request should be tried to approve 10 times")
|
||||
}
|
||||
|
||||
func (s PendingRequestsSuite) TestWaitSuccess() {
|
||||
req, err := s.pendingRequests.Add(context.Background(), "", nil, s.defaultCompleteFunc())
|
||||
s.NoError(err)
|
||||
|
||||
s.True(s.pendingRequests.Has(req.ID), "sign request should exist")
|
||||
|
||||
go func() {
|
||||
result := s.pendingRequests.Approve(req.ID, correctPassword, s.defaultSignTxArgs(), testVerifyFunc)
|
||||
s.NoError(result.Error)
|
||||
}()
|
||||
|
||||
result := s.pendingRequests.Wait(req.ID, 1*time.Second)
|
||||
s.NoError(result.Error)
|
||||
}
|
||||
|
||||
func (s PendingRequestsSuite) TestDiscard() {
|
||||
req, err := s.pendingRequests.Add(context.Background(), "", nil, s.defaultCompleteFunc())
|
||||
s.NoError(err)
|
||||
|
||||
s.True(s.pendingRequests.Has(req.ID), "sign request should exist")
|
||||
|
||||
s.Equal(ErrSignReqNotFound, s.pendingRequests.Discard(""))
|
||||
|
||||
go func() {
|
||||
// enough to make it be called after Wait
|
||||
time.Sleep(time.Millisecond)
|
||||
s.NoError(s.pendingRequests.Discard(req.ID))
|
||||
}()
|
||||
|
||||
result := s.pendingRequests.Wait(req.ID, 1*time.Second)
|
||||
s.Equal(ErrSignReqDiscarded, result.Error)
|
||||
}
|
||||
|
||||
func (s PendingRequestsSuite) TestWaitFail() {
|
||||
expectedError := errors.New("test-wait-fail")
|
||||
req, err := s.pendingRequests.Add(context.Background(), "", nil, s.errorCompleteFunc(expectedError))
|
||||
s.NoError(err)
|
||||
|
||||
s.True(s.pendingRequests.Has(req.ID), "sign request should exist")
|
||||
|
||||
go func() {
|
||||
result := s.pendingRequests.Approve(req.ID, correctPassword, s.defaultSignTxArgs(), testVerifyFunc)
|
||||
s.Equal(expectedError, result.Error)
|
||||
}()
|
||||
|
||||
result := s.pendingRequests.Wait(req.ID, 1*time.Second)
|
||||
s.Equal(expectedError, result.Error)
|
||||
}
|
||||
|
||||
func (s PendingRequestsSuite) TestWaitTimeout() {
|
||||
req, err := s.pendingRequests.Add(context.Background(), "", nil, s.defaultCompleteFunc())
|
||||
s.NoError(err)
|
||||
|
||||
s.True(s.pendingRequests.Has(req.ID), "sign request should exist")
|
||||
|
||||
result := s.pendingRequests.Wait(req.ID, 0*time.Second)
|
||||
s.Equal(ErrSignReqTimedOut, result.Error)
|
||||
|
||||
// Try approving the timed out request, it will fail
|
||||
result = s.pendingRequests.Approve(req.ID, correctPassword, s.defaultSignTxArgs(), testVerifyFunc)
|
||||
s.NotNil(result.Error)
|
||||
}
|
||||
|
||||
func (s *PendingRequestsSuite) TestCompleteSuccessWithOverridenGas() {
|
||||
txArgs := TxArgs{
|
||||
Gas: &overridenGas,
|
||||
GasPrice: overridenGasPrice,
|
||||
}
|
||||
id, err := s.testComplete(correctPassword, gethcommon.Hash{1}, s.overridenCompleteFunc(), &txArgs)
|
||||
s.NoError(err, "no errors should be there")
|
||||
|
||||
s.False(s.pendingRequests.Has(id), "sign request should not exist")
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
package sign
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/pborman/uuid"
|
||||
"github.com/status-im/status-go/account"
|
||||
)
|
||||
|
||||
// CompleteFunc is a function that is called after the sign request is approved.
|
||||
type CompleteFunc func(account *account.SelectedExtKey, password string, completeArgs *TxArgs) (Response, error)
|
||||
|
||||
// Meta represents any metadata that could be attached to a signing request.
|
||||
// It will be JSON-serialized and used in notifications to the API consumer.
|
||||
type Meta interface{}
|
||||
|
||||
// Request is a single signing request.
|
||||
type Request struct {
|
||||
ID string
|
||||
Method string
|
||||
Meta Meta
|
||||
context context.Context
|
||||
locked bool
|
||||
completeFunc CompleteFunc
|
||||
result chan Result
|
||||
}
|
||||
|
||||
// TxArgs represents the arguments to submit when signing a transaction
|
||||
type TxArgs struct {
|
||||
Gas *hexutil.Uint64 `json:"gas"`
|
||||
GasPrice *hexutil.Big `json:"gasPrice"`
|
||||
}
|
||||
|
||||
func newRequest(ctx context.Context, method string, meta Meta, completeFunc CompleteFunc) *Request {
|
||||
return &Request{
|
||||
ID: uuid.New(),
|
||||
Method: method,
|
||||
Meta: meta,
|
||||
context: ctx,
|
||||
locked: false,
|
||||
completeFunc: completeFunc,
|
||||
result: make(chan Result, 1),
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
package sign
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
// Response is a byte payload returned by the signed function
|
||||
type Response []byte
|
||||
|
||||
// Hex returns a string representation of the response
|
||||
func (r Response) Hex() string {
|
||||
return hexutil.Encode(r[:])
|
||||
}
|
||||
|
||||
// Bytes returns a byte representation of the response
|
||||
func (r Response) Bytes() []byte {
|
||||
return []byte(r)
|
||||
}
|
||||
|
||||
// Hash converts response to a hash.
|
||||
func (r Response) Hash() common.Hash {
|
||||
return common.BytesToHash(r.Bytes())
|
||||
}
|
||||
|
||||
// EmptyResponse is returned when an error occures
|
||||
var EmptyResponse = Response([]byte{})
|
||||
|
||||
// Result is a result of a signing request, error or successful
|
||||
type Result struct {
|
||||
Response Response
|
||||
Error error
|
||||
}
|
||||
|
||||
// newErrResult creates a result based on an empty response and an error
|
||||
func newErrResult(err error) Result {
|
||||
return Result{
|
||||
Response: EmptyResponse,
|
||||
Error: err,
|
||||
}
|
||||
}
|
|
@ -5,13 +5,20 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/status-im/status-go/api"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/signal"
|
||||
"github.com/status-im/status-go/t/e2e"
|
||||
|
||||
. "github.com/status-im/status-go/t/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
// see vendor/github.com/ethereum/go-ethereum/rpc/errors.go:L27
|
||||
methodNotFoundErrorCode = -32601
|
||||
)
|
||||
|
||||
type rpcError struct {
|
||||
Code int `json:"code"`
|
||||
}
|
||||
|
||||
type BaseJSONRPCSuite struct {
|
||||
e2e.BackendTestSuite
|
||||
}
|
||||
|
@ -81,30 +88,3 @@ func (s *BaseJSONRPCSuite) SetupTest(upstreamEnabled, statusServiceEnabled, debu
|
|||
|
||||
return s.Backend.StartNode(nodeConfig)
|
||||
}
|
||||
|
||||
func (s *BaseJSONRPCSuite) notificationHandler(account string, pass string, expectedError error) func(string) {
|
||||
return func(jsonEvent string) {
|
||||
envelope := unmarshalEnvelope(jsonEvent)
|
||||
if envelope.Type == signal.EventSignRequestAdded {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
id := event["id"].(string)
|
||||
s.T().Logf("Sign request added (will be completed shortly): {id: %s}\n", id)
|
||||
|
||||
//check for the correct method name
|
||||
method := event["method"].(string)
|
||||
s.Equal(params.PersonalSignMethodName, method)
|
||||
//check the event data
|
||||
args := event["args"].(map[string]interface{})
|
||||
s.Equal(signDataString, args["data"].(string))
|
||||
s.Equal(account, args["account"].(string))
|
||||
|
||||
e := s.Backend.ApproveSignRequest(id, pass).Error
|
||||
s.T().Logf("Sign request approved. {id: %s, acc: %s, err: %v}", id, account, e)
|
||||
if expectedError == nil {
|
||||
s.NoError(e, "cannot complete sign reauest[%v]: %v", id, e)
|
||||
} else {
|
||||
s.EqualError(e, expectedError.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,47 +1,22 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
acc "github.com/status-im/status-go/account"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/services/personal"
|
||||
"github.com/status-im/status-go/signal"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
. "github.com/status-im/status-go/t/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
signDataString = "0xBAADBEEF"
|
||||
accountNotExists = "0x00164ca341326a03b547c05B343b2E21eFAe2400"
|
||||
|
||||
// see vendor/github.com/ethereum/go-ethereum/rpc/errors.go:L27
|
||||
methodNotFoundErrorCode = -32601
|
||||
signDataString = "0xBAADBEEF"
|
||||
)
|
||||
|
||||
type rpcError struct {
|
||||
Code int `json:"code"`
|
||||
}
|
||||
|
||||
type testParams struct {
|
||||
Title string
|
||||
EnableUpstream bool
|
||||
Account string
|
||||
Password string
|
||||
HandlerFactory func(string, string) func(string)
|
||||
ExpectedError error
|
||||
DontSelectAccount bool // to take advantage of the fact, that the default is `false`
|
||||
}
|
||||
|
||||
func TestPersonalSignSuite(t *testing.T) {
|
||||
s := new(PersonalSignSuite)
|
||||
s.upstream = false
|
||||
suite.Run(t, s)
|
||||
type PersonalSignSuite struct {
|
||||
upstream bool
|
||||
BaseJSONRPCSuite
|
||||
}
|
||||
|
||||
func TestPersonalSignSuiteUpstream(t *testing.T) {
|
||||
|
@ -50,18 +25,13 @@ func TestPersonalSignSuiteUpstream(t *testing.T) {
|
|||
suite.Run(t, s)
|
||||
}
|
||||
|
||||
type PersonalSignSuite struct {
|
||||
BaseJSONRPCSuite
|
||||
upstream bool
|
||||
}
|
||||
|
||||
func (s *PersonalSignSuite) TestRestrictedPersonalAPIs() {
|
||||
if s.upstream && GetNetworkID() == params.StatusChainNetworkID {
|
||||
s.T().Skip()
|
||||
return
|
||||
}
|
||||
|
||||
err := s.SetupTest(s.upstream, false, false)
|
||||
err := s.SetupTest(true, false, false)
|
||||
s.NoError(err)
|
||||
defer func() {
|
||||
err := s.Backend.StopNode()
|
||||
|
@ -79,172 +49,30 @@ func (s *PersonalSignSuite) TestRestrictedPersonalAPIs() {
|
|||
s.AssertAPIMethodUnexported("personal_importRawKey")
|
||||
}
|
||||
|
||||
func (s *PersonalSignSuite) TestPersonalSignSuccess() {
|
||||
s.testPersonalSign(testParams{
|
||||
Account: TestConfig.Account1.Address,
|
||||
Password: TestConfig.Account1.Password,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *PersonalSignSuite) TestPersonalSignWrongPassword() {
|
||||
s.testPersonalSign(testParams{
|
||||
Account: TestConfig.Account1.Address,
|
||||
Password: TestConfig.Account1.Password,
|
||||
HandlerFactory: s.notificationHandlerWrongPassword,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *PersonalSignSuite) TestPersonalSignNoSuchAccount() {
|
||||
s.testPersonalSign(testParams{
|
||||
Account: accountNotExists,
|
||||
Password: TestConfig.Account1.Password,
|
||||
ExpectedError: personal.ErrInvalidPersonalSignAccount,
|
||||
HandlerFactory: s.notificationHandlerNoAccount,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *PersonalSignSuite) TestPersonalSignWrongAccount() {
|
||||
s.testPersonalSign(testParams{
|
||||
Account: TestConfig.Account2.Address,
|
||||
Password: TestConfig.Account2.Password,
|
||||
ExpectedError: personal.ErrInvalidPersonalSignAccount,
|
||||
HandlerFactory: s.notificationHandlerInvalidAccount,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *PersonalSignSuite) TestPersonalSignNoAccountSelected() {
|
||||
s.testPersonalSign(testParams{
|
||||
Account: TestConfig.Account1.Address,
|
||||
Password: TestConfig.Account1.Password,
|
||||
HandlerFactory: s.notificationHandlerNoAccountSelected,
|
||||
DontSelectAccount: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Utility methods
|
||||
func (s *PersonalSignSuite) notificationHandlerWrongPassword(account string, pass string) func(string) {
|
||||
return func(jsonEvent string) {
|
||||
s.notificationHandler(account, pass+"wrong", keystore.ErrDecrypt)(jsonEvent)
|
||||
s.notificationHandlerSuccess(account, pass)(jsonEvent)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PersonalSignSuite) notificationHandlerNoAccount(account string, pass string) func(string) {
|
||||
return func(jsonEvent string) {
|
||||
s.notificationHandler(account, pass, personal.ErrInvalidPersonalSignAccount)(jsonEvent)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PersonalSignSuite) notificationHandlerInvalidAccount(account string, pass string) func(string) {
|
||||
return func(jsonEvent string) {
|
||||
s.notificationHandler(account, pass, personal.ErrInvalidPersonalSignAccount)(jsonEvent)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PersonalSignSuite) notificationHandlerNoAccountSelected(account string, pass string) func(string) {
|
||||
return func(jsonEvent string) {
|
||||
s.notificationHandler(account, pass, acc.ErrNoAccountSelected)(jsonEvent)
|
||||
envelope := unmarshalEnvelope(jsonEvent)
|
||||
if envelope.Type == signal.EventSignRequestAdded {
|
||||
err := s.Backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
|
||||
s.NoError(err)
|
||||
}
|
||||
s.notificationHandlerSuccess(account, pass)(jsonEvent)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PersonalSignSuite) notificationHandler(account string, pass string, expectedError error) func(string) {
|
||||
return func(jsonEvent string) {
|
||||
envelope := unmarshalEnvelope(jsonEvent)
|
||||
if envelope.Type == signal.EventSignRequestAdded {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
id := event["id"].(string)
|
||||
s.T().Logf("Sign request added (will be completed shortly): {id: %s}\n", id)
|
||||
|
||||
//check for the correct method name
|
||||
method := event["method"].(string)
|
||||
s.Equal(params.PersonalSignMethodName, method)
|
||||
//check the event data
|
||||
args := event["args"].(map[string]interface{})
|
||||
s.Equal(signDataString, args["data"].(string))
|
||||
s.Equal(account, args["account"].(string))
|
||||
|
||||
e := s.Backend.ApproveSignRequest(id, pass).Error
|
||||
s.T().Logf("Sign request approved. {id: %s, acc: %s, err: %v}", id, account, e)
|
||||
if expectedError == nil {
|
||||
s.NoError(e, "cannot complete sign reauest[%v]: %v", id, e)
|
||||
} else {
|
||||
s.EqualError(e, expectedError.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PersonalSignSuite) testPersonalSign(testParams testParams) string {
|
||||
func (s *PersonalSignSuite) TestPersonalSignUnsupportedMethod() {
|
||||
// Test upstream if that's not StatusChain
|
||||
if s.upstream && GetNetworkID() == params.StatusChainNetworkID {
|
||||
s.T().Skip()
|
||||
return ""
|
||||
}
|
||||
|
||||
if testParams.HandlerFactory == nil {
|
||||
testParams.HandlerFactory = s.notificationHandlerSuccess
|
||||
}
|
||||
|
||||
err := s.SetupTest(s.upstream, false, false)
|
||||
err := s.SetupTest(true, false, false)
|
||||
s.NoError(err)
|
||||
defer func() {
|
||||
err := s.Backend.StopNode()
|
||||
s.NoError(err)
|
||||
}()
|
||||
|
||||
signal.SetDefaultNodeNotificationHandler(testParams.HandlerFactory(testParams.Account, testParams.Password))
|
||||
|
||||
if testParams.DontSelectAccount {
|
||||
s.NoError(s.Backend.Logout())
|
||||
} else {
|
||||
s.NoError(s.Backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
|
||||
}
|
||||
|
||||
basicCall := fmt.Sprintf(
|
||||
`{"jsonrpc":"2.0","method":"personal_sign","params":["%s", "%s"],"id":67}`,
|
||||
signDataString,
|
||||
testParams.Account)
|
||||
TestConfig.Account1.Address)
|
||||
|
||||
result := s.Backend.CallRPC(basicCall)
|
||||
if testParams.ExpectedError == nil {
|
||||
s.NotContains(result, "error")
|
||||
return s.extractResultFromRPCResponse(result)
|
||||
}
|
||||
rawResult := s.Backend.CallRPC(basicCall)
|
||||
|
||||
s.Contains(result, testParams.ExpectedError.Error())
|
||||
return ""
|
||||
s.Contains(rawResult, `"error":{"code":-32700,"message":"method is unsupported by RPC interface"}`)
|
||||
}
|
||||
|
||||
func (s *PersonalSignSuite) extractResultFromRPCResponse(response string) string {
|
||||
var r struct {
|
||||
Result string `json:"result"`
|
||||
}
|
||||
s.NoError(json.Unmarshal([]byte(response), &r))
|
||||
|
||||
return r.Result
|
||||
}
|
||||
|
||||
func unmarshalEnvelope(jsonEvent string) signal.Envelope {
|
||||
var envelope signal.Envelope
|
||||
if e := json.Unmarshal([]byte(jsonEvent), &envelope); e != nil {
|
||||
panic(e)
|
||||
}
|
||||
return envelope
|
||||
}
|
||||
|
||||
func (s *PersonalSignSuite) TestPersonalRecoverSuccess() {
|
||||
|
||||
// 1. Sign
|
||||
signedData := s.testPersonalSign(testParams{
|
||||
Account: TestConfig.Account1.Address,
|
||||
Password: TestConfig.Account1.Password,
|
||||
})
|
||||
func (s *PersonalSignSuite) TestPersonalRecoverUnsupportedMethod() {
|
||||
|
||||
// Test upstream if that's not StatusChain
|
||||
if s.upstream && GetNetworkID() == params.StatusChainNetworkID {
|
||||
|
@ -252,7 +80,7 @@ func (s *PersonalSignSuite) TestPersonalRecoverSuccess() {
|
|||
return
|
||||
}
|
||||
|
||||
err := s.SetupTest(s.upstream, false, false)
|
||||
err := s.SetupTest(true, false, false)
|
||||
s.NoError(err)
|
||||
defer func() {
|
||||
err := s.Backend.StopNode()
|
||||
|
@ -263,17 +91,9 @@ func (s *PersonalSignSuite) TestPersonalRecoverSuccess() {
|
|||
basicCall := fmt.Sprintf(
|
||||
`{"jsonrpc":"2.0","method":"personal_ecRecover","params":["%s", "%s"],"id":67}`,
|
||||
signDataString,
|
||||
signedData)
|
||||
"")
|
||||
|
||||
response := s.Backend.CallRPC(basicCall)
|
||||
rawResult := s.Backend.CallRPC(basicCall)
|
||||
|
||||
result := s.extractResultFromRPCResponse(response)
|
||||
|
||||
s.True(strings.EqualFold(result, TestConfig.Account1.Address))
|
||||
}
|
||||
|
||||
func (s *BaseJSONRPCSuite) notificationHandlerSuccess(account string, pass string) func(string) {
|
||||
return func(jsonEvent string) {
|
||||
s.notificationHandler(account, pass, nil)(jsonEvent)
|
||||
}
|
||||
s.Contains(rawResult, `"error":{"code":-32700,"message":"method is unsupported by RPC interface"}`)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"github.com/status-im/status-go/account"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/services/status"
|
||||
"github.com/status-im/status-go/signal"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
. "github.com/status-im/status-go/t/utils"
|
||||
|
@ -109,10 +108,6 @@ func (s *StatusAPISuite) testStatusLogin(testParams statusTestParams) *status.Lo
|
|||
return nil
|
||||
}
|
||||
|
||||
if testParams.HandlerFactory == nil {
|
||||
testParams.HandlerFactory = s.notificationHandlerSuccess
|
||||
}
|
||||
|
||||
err := s.SetupTest(s.upstream, true, false)
|
||||
s.NoError(err)
|
||||
defer func() {
|
||||
|
@ -120,8 +115,6 @@ func (s *StatusAPISuite) testStatusLogin(testParams statusTestParams) *status.Lo
|
|||
s.NoError(err)
|
||||
}()
|
||||
|
||||
signal.SetDefaultNodeNotificationHandler(testParams.HandlerFactory(testParams.Address, testParams.Password))
|
||||
|
||||
req := status.LoginRequest{
|
||||
Addr: testParams.Address,
|
||||
Password: testParams.Password,
|
||||
|
@ -155,10 +148,6 @@ func (s *StatusAPISuite) testStatusSignup(testParams statusTestParams) *status.S
|
|||
return nil
|
||||
}
|
||||
|
||||
if testParams.HandlerFactory == nil {
|
||||
testParams.HandlerFactory = s.notificationHandlerSuccess
|
||||
}
|
||||
|
||||
err := s.SetupTest(s.upstream, true, false)
|
||||
s.NoError(err)
|
||||
defer func() {
|
||||
|
@ -166,8 +155,6 @@ func (s *StatusAPISuite) testStatusSignup(testParams statusTestParams) *status.S
|
|||
s.NoError(err)
|
||||
}()
|
||||
|
||||
signal.SetDefaultNodeNotificationHandler(testParams.HandlerFactory(testParams.Address, testParams.Password))
|
||||
|
||||
req := status.SignupRequest{
|
||||
Password: testParams.Password,
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
|
||||
"github.com/status-im/status-go/api"
|
||||
"github.com/status-im/status-go/node"
|
||||
"github.com/status-im/status-go/sign"
|
||||
"github.com/status-im/status-go/signal"
|
||||
. "github.com/status-im/status-go/t/utils" //nolint: golint
|
||||
"github.com/status-im/status-go/transactions"
|
||||
|
@ -129,11 +128,6 @@ func (s *BackendTestSuite) Transactor() *transactions.Transactor {
|
|||
return s.Backend.Transactor()
|
||||
}
|
||||
|
||||
// PendingSignRequests returns a reference to PendingSignRequests.
|
||||
func (s *BackendTestSuite) PendingSignRequests() *sign.PendingRequests {
|
||||
return s.Backend.PendingSignRequests()
|
||||
}
|
||||
|
||||
func importTestAccounts(keyStoreDir string) (err error) {
|
||||
logger.Debug("Import accounts to", "dir", keyStoreDir)
|
||||
|
||||
|
|
|
@ -1,37 +1,22 @@
|
|||
package transactions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/status-im/status-go/account"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/sign"
|
||||
"github.com/status-im/status-go/signal"
|
||||
e2e "github.com/status-im/status-go/t/e2e"
|
||||
. "github.com/status-im/status-go/t/utils"
|
||||
"github.com/status-im/status-go/transactions"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
const invalidTxID = "invalid-tx-id"
|
||||
|
||||
type initFunc func([]byte, *transactions.SendTxArgs)
|
||||
|
||||
func txURLString(result sign.Result) string {
|
||||
return fmt.Sprintf("https://ropsten.etherscan.io/tx/%s", result.Response.Hash().Hex())
|
||||
}
|
||||
|
||||
func TestTransactionsTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(TransactionsTestSuite))
|
||||
}
|
||||
|
@ -86,53 +71,17 @@ func (s *TransactionsTestSuite) sendTransactionUsingRPCClient(callRPCFn func(str
|
|||
err := s.Backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
|
||||
s.NoError(err)
|
||||
|
||||
transactionCompleted := make(chan struct{})
|
||||
|
||||
var signResult sign.Result
|
||||
signal.SetDefaultNodeNotificationHandler(func(rawSignal string) {
|
||||
var sg signal.Envelope
|
||||
err := json.Unmarshal([]byte(rawSignal), &sg)
|
||||
s.NoError(err)
|
||||
|
||||
if sg.Type == signal.EventSignRequestAdded {
|
||||
event := sg.Event.(map[string]interface{})
|
||||
// check for the correct method name
|
||||
method := event["method"].(string)
|
||||
s.Equal(params.SendTransactionMethodName, method)
|
||||
|
||||
txID := event["id"].(string)
|
||||
|
||||
// Complete with a wrong passphrase.
|
||||
signResult = s.Backend.ApproveSignRequest(txID, "some-invalid-passphrase")
|
||||
s.EqualError(signResult.Error, keystore.ErrDecrypt.Error(), "should return an error as the passphrase was invalid")
|
||||
|
||||
// Complete with a correct passphrase.
|
||||
signResult = s.Backend.ApproveSignRequest(txID, TestConfig.Account2.Password)
|
||||
s.NoError(signResult.Error, "cannot complete queued transaction %s", txID)
|
||||
|
||||
close(transactionCompleted)
|
||||
}
|
||||
})
|
||||
|
||||
result := callRPCFn(`{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "eth_sendTransaction",
|
||||
"params": [{
|
||||
"from": "` + TestConfig.Account1.Address + `",
|
||||
"to": "` + TestConfig.Account2.Address + `",
|
||||
"to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
|
||||
"value": "0x9184e72a"
|
||||
}]
|
||||
}`)
|
||||
s.NotContains(result, "error")
|
||||
|
||||
select {
|
||||
case <-transactionCompleted:
|
||||
case <-time.After(time.Minute):
|
||||
s.FailNow("sending transaction timed out")
|
||||
}
|
||||
|
||||
s.Equal(`{"jsonrpc":"2.0","id":1,"result":"`+signResult.Response.Hash().Hex()+`"}`, result)
|
||||
s.Contains(result, `"error":{"code":-32700,"message":"method is unsupported by RPC interface"}`)
|
||||
}
|
||||
|
||||
func (s *TransactionsTestSuite) TestEmptyToFieldPreserved() {
|
||||
|
@ -145,41 +94,13 @@ func (s *TransactionsTestSuite) TestEmptyToFieldPreserved() {
|
|||
err := s.Backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
|
||||
s.NoError(err)
|
||||
|
||||
transactionCompleted := make(chan struct{})
|
||||
signal.SetDefaultNodeNotificationHandler(func(rawSignal string) {
|
||||
var sg struct {
|
||||
Type string
|
||||
Event json.RawMessage
|
||||
}
|
||||
err := json.Unmarshal([]byte(rawSignal), &sg)
|
||||
s.NoError(err)
|
||||
if sg.Type == signal.EventSignRequestAdded {
|
||||
var event signal.PendingRequestEvent
|
||||
s.NoError(json.Unmarshal(sg.Event, &event))
|
||||
args := event.Args.(map[string]interface{})
|
||||
s.NotNil(args["from"])
|
||||
s.Nil(args["to"])
|
||||
signResult := s.Backend.ApproveSignRequest(event.ID, TestConfig.Account1.Password)
|
||||
s.NoError(signResult.Error)
|
||||
close(transactionCompleted)
|
||||
}
|
||||
})
|
||||
|
||||
result := s.Backend.CallRPC(`{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "eth_sendTransaction",
|
||||
"params": [{
|
||||
"from": "` + TestConfig.Account1.Address + `"
|
||||
}]
|
||||
}`)
|
||||
s.NotContains(result, "error")
|
||||
|
||||
select {
|
||||
case <-transactionCompleted:
|
||||
case <-time.After(10 * time.Second):
|
||||
s.FailNow("sending transaction timed out")
|
||||
args := transactions.SendTxArgs{
|
||||
From: account.FromAddress(TestConfig.Account1.Address),
|
||||
}
|
||||
|
||||
hash, err := s.Backend.SendTransaction(args, TestConfig.Account1.Password)
|
||||
s.NoError(err)
|
||||
s.NotNil(hash)
|
||||
}
|
||||
|
||||
// TestSendContractCompat tries to send transaction using the legacy "Data"
|
||||
|
@ -232,79 +153,15 @@ func (s *TransactionsTestSuite) TestSendContractTx() {
|
|||
s.testSendContractTx(initFunc, nil, "")
|
||||
}
|
||||
|
||||
func (s *TransactionsTestSuite) setDefaultNodeNotificationHandler(signRequestResult *[]byte, sampleAddress string, done chan struct{}, expectedError error) {
|
||||
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) { // nolint :dupl
|
||||
var envelope signal.Envelope
|
||||
err := json.Unmarshal([]byte(jsonEvent), &envelope)
|
||||
s.NoError(err, fmt.Sprintf("cannot unmarshal JSON: %s", jsonEvent))
|
||||
|
||||
if envelope.Type == signal.EventSignRequestAdded {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
log.Info("transaction queued (will be completed shortly)", "id", event["id"].(string))
|
||||
|
||||
// the first call will fail (we are not logged in, but trying to complete tx)
|
||||
log.Info("trying to complete with no user logged in")
|
||||
err = s.Backend.ApproveSignRequest(
|
||||
event["id"].(string),
|
||||
TestConfig.Account1.Password,
|
||||
).Error
|
||||
s.EqualError(
|
||||
err,
|
||||
account.ErrNoAccountSelected.Error(),
|
||||
fmt.Sprintf("expected error on queued transaction[%v] not thrown", event["id"]),
|
||||
)
|
||||
|
||||
// the second call will also fail (we are logged in as different user)
|
||||
log.Info("trying to complete with invalid user")
|
||||
err = s.Backend.SelectAccount(sampleAddress, TestConfig.Account1.Password)
|
||||
s.NoError(err)
|
||||
err = s.Backend.ApproveSignRequest(
|
||||
event["id"].(string),
|
||||
TestConfig.Account1.Password,
|
||||
).Error
|
||||
s.EqualError(
|
||||
err,
|
||||
transactions.ErrInvalidCompleteTxSender.Error(),
|
||||
fmt.Sprintf("expected error on queued transaction[%v] not thrown", event["id"]),
|
||||
)
|
||||
|
||||
// the third call will work as expected (as we are logged in with correct credentials)
|
||||
log.Info("trying to complete with correct user, this should succeed")
|
||||
s.NoError(s.Backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
|
||||
result := s.Backend.ApproveSignRequest(
|
||||
event["id"].(string),
|
||||
TestConfig.Account1.Password,
|
||||
)
|
||||
if expectedError != nil {
|
||||
s.Equal(expectedError, result.Error)
|
||||
} else {
|
||||
s.NoError(result.Error, fmt.Sprintf("cannot complete queued transaction[%v]", event["id"]))
|
||||
}
|
||||
|
||||
*signRequestResult = result.Response.Bytes()[:]
|
||||
|
||||
log.Info("contract transaction complete", "URL", txURLString(result))
|
||||
close(done)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (s *TransactionsTestSuite) testSendContractTx(setInputAndDataValue initFunc, expectedError error, expectedErrorDescription string) {
|
||||
s.StartTestBackend()
|
||||
defer s.StopTestBackend()
|
||||
|
||||
EnsureNodeSync(s.Backend.StatusNode().EnsureSync)
|
||||
|
||||
sampleAddress, _, _, err := s.Backend.AccountManager().CreateAccount(TestConfig.Account1.Password)
|
||||
err := s.Backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
|
||||
s.NoError(err)
|
||||
|
||||
completeQueuedTransaction := make(chan struct{})
|
||||
|
||||
// replace transaction notification handler
|
||||
var signRequestResult []byte
|
||||
s.setDefaultNodeNotificationHandler(&signRequestResult, sampleAddress, completeQueuedTransaction, expectedError)
|
||||
|
||||
// this call blocks, up until Complete Transaction is called
|
||||
byteCode, err := hexutil.Decode(`0x6060604052341561000c57fe5b5b60a58061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680636ffa1caa14603a575bfe5b3415604157fe5b60556004808035906020019091905050606b565b6040518082815260200191505060405180910390f35b60008160020290505b9190505600a165627a7a72305820ccdadd737e4ac7039963b54cee5e5afb25fa859a275252bdcf06f653155228210029`)
|
||||
s.NoError(err)
|
||||
|
@ -318,24 +175,13 @@ func (s *TransactionsTestSuite) testSendContractTx(setInputAndDataValue initFunc
|
|||
}
|
||||
|
||||
setInputAndDataValue(byteCode, &args)
|
||||
txHashCheck, err := s.Backend.SendTransaction(context.TODO(), args)
|
||||
|
||||
hash, err := s.Backend.SendTransaction(args, TestConfig.Account1.Password)
|
||||
if expectedError != nil {
|
||||
s.Equal(expectedError, err, expectedErrorDescription)
|
||||
return
|
||||
}
|
||||
s.NoError(err, "cannot send transaction")
|
||||
|
||||
select {
|
||||
case <-completeQueuedTransaction:
|
||||
case <-time.After(2 * time.Minute):
|
||||
s.FailNow("completing transaction timed out")
|
||||
}
|
||||
|
||||
s.Equal(txHashCheck.Bytes(), signRequestResult, "transaction hash returned from SendTransaction is invalid")
|
||||
s.False(reflect.DeepEqual(txHashCheck, gethcommon.Hash{}), "transaction was never queued or completed")
|
||||
s.Zero(s.PendingSignRequests().Count(), "tx queue must be empty at this point")
|
||||
|
||||
s.NoError(err)
|
||||
s.False(reflect.DeepEqual(hash, gethcommon.Hash{}))
|
||||
s.NoError(s.Backend.Logout())
|
||||
}
|
||||
|
||||
|
@ -347,33 +193,16 @@ func (s *TransactionsTestSuite) TestSendEther() {
|
|||
|
||||
EnsureNodeSync(s.Backend.StatusNode().EnsureSync)
|
||||
|
||||
// create an account
|
||||
sampleAddress, _, _, err := s.Backend.AccountManager().CreateAccount(TestConfig.Account1.Password)
|
||||
err := s.Backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
|
||||
s.NoError(err)
|
||||
|
||||
completeQueuedTransaction := make(chan struct{})
|
||||
|
||||
// replace transaction notification handler
|
||||
var signRequestResult []byte
|
||||
s.setDefaultNodeNotificationHandler(&signRequestResult, sampleAddress, completeQueuedTransaction, nil)
|
||||
|
||||
// this call blocks, up until Complete Transaction is called
|
||||
txHashCheck, err := s.Backend.SendTransaction(context.TODO(), transactions.SendTxArgs{
|
||||
hash, err := s.Backend.SendTransaction(transactions.SendTxArgs{
|
||||
From: account.FromAddress(TestConfig.Account1.Address),
|
||||
To: account.ToAddress(TestConfig.Account2.Address),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
s.NoError(err, "cannot send transaction")
|
||||
|
||||
select {
|
||||
case <-completeQueuedTransaction:
|
||||
case <-time.After(2 * time.Minute):
|
||||
s.FailNow("completing transaction timed out")
|
||||
}
|
||||
|
||||
s.Equal(txHashCheck.Bytes(), signRequestResult, "transaction hash returned from SendTransaction is invalid")
|
||||
s.False(reflect.DeepEqual(txHashCheck, gethcommon.Hash{}), "transaction was never queued or completed")
|
||||
s.Zero(s.Backend.PendingSignRequests().Count(), "tx queue must be empty at this point")
|
||||
}, TestConfig.Account1.Password)
|
||||
s.NoError(err)
|
||||
s.False(reflect.DeepEqual(hash, gethcommon.Hash{}))
|
||||
}
|
||||
|
||||
func (s *TransactionsTestSuite) TestSendEtherTxUpstream() {
|
||||
|
@ -387,460 +216,12 @@ func (s *TransactionsTestSuite) TestSendEtherTxUpstream() {
|
|||
err = s.Backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
|
||||
s.NoError(err)
|
||||
|
||||
completeQueuedTransaction := make(chan struct{})
|
||||
|
||||
// replace transaction notification handler
|
||||
var txHash = gethcommon.Hash{}
|
||||
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) { // nolint: dupl
|
||||
var envelope signal.Envelope
|
||||
err = json.Unmarshal([]byte(jsonEvent), &envelope)
|
||||
s.NoError(err, "cannot unmarshal JSON: %s", jsonEvent)
|
||||
|
||||
if envelope.Type == signal.EventSignRequestAdded {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
log.Info("transaction queued (will be completed shortly)", "id", event["id"].(string))
|
||||
|
||||
signResult := s.Backend.ApproveSignRequest(
|
||||
event["id"].(string),
|
||||
TestConfig.Account1.Password,
|
||||
)
|
||||
s.NoError(signResult.Error, "cannot complete queued transaction[%v]", event["id"])
|
||||
|
||||
txHash = signResult.Response.Hash()
|
||||
log.Info("contract transaction complete", "URL", txURLString(signResult))
|
||||
close(completeQueuedTransaction)
|
||||
}
|
||||
})
|
||||
|
||||
// This call blocks, up until Complete Transaction is called.
|
||||
// Explicitly not setting Gas to get it estimated.
|
||||
txHashCheck, err := s.Backend.SendTransaction(context.TODO(), transactions.SendTxArgs{
|
||||
hash, err := s.Backend.SendTransaction(transactions.SendTxArgs{
|
||||
From: account.FromAddress(TestConfig.Account1.Address),
|
||||
To: account.ToAddress(TestConfig.Account2.Address),
|
||||
GasPrice: (*hexutil.Big)(big.NewInt(28000000000)),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
s.NoError(err, "cannot send transaction")
|
||||
|
||||
select {
|
||||
case <-completeQueuedTransaction:
|
||||
case <-time.After(1 * time.Minute):
|
||||
s.FailNow("completing transaction timed out")
|
||||
}
|
||||
|
||||
s.Equal(txHash.Hex(), txHashCheck.Hex(), "transaction hash returned from SendTransaction is invalid")
|
||||
s.Zero(s.Backend.PendingSignRequests().Count(), "tx queue must be empty at this point")
|
||||
}
|
||||
|
||||
func (s *TransactionsTestSuite) TestDoubleCompleteQueuedTransactions() {
|
||||
CheckTestSkipForNetworks(s.T(), params.MainNetworkID)
|
||||
|
||||
s.StartTestBackend()
|
||||
defer s.StopTestBackend()
|
||||
|
||||
EnsureNodeSync(s.Backend.StatusNode().EnsureSync)
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
s.NoError(s.Backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
|
||||
|
||||
completeQueuedTransaction := make(chan struct{})
|
||||
|
||||
// replace transaction notification handler
|
||||
var isTxFailedEventCalled int32 // using int32 as bool to avoid data race: 0 is `false`, 1 is `true`
|
||||
signHash := gethcommon.Hash{}
|
||||
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var envelope signal.Envelope
|
||||
err := json.Unmarshal([]byte(jsonEvent), &envelope)
|
||||
s.NoError(err, fmt.Sprintf("cannot unmarshal JSON: %s", jsonEvent))
|
||||
|
||||
if envelope.Type == signal.EventSignRequestAdded {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
txID := event["id"].(string)
|
||||
log.Info("transaction queued (will be failed and completed on the second call)", "id", txID)
|
||||
|
||||
// try with wrong password
|
||||
// make sure that tx is NOT removed from the queue (by re-trying with the correct password)
|
||||
err = s.Backend.ApproveSignRequest(txID, TestConfig.Account1.Password+"wrong").Error
|
||||
s.EqualError(err, keystore.ErrDecrypt.Error())
|
||||
|
||||
s.Equal(1, s.PendingSignRequests().Count(), "txqueue cannot be empty, as tx has failed")
|
||||
|
||||
// now try to complete transaction, but with the correct password
|
||||
signResult := s.Backend.ApproveSignRequest(txID, TestConfig.Account1.Password)
|
||||
s.NoError(signResult.Error)
|
||||
|
||||
log.Info("transaction complete", "URL", txURLString(signResult))
|
||||
|
||||
signHash = signResult.Response.Hash()
|
||||
|
||||
close(completeQueuedTransaction)
|
||||
}
|
||||
|
||||
if envelope.Type == signal.EventSignRequestFailed {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
log.Info("transaction return event received", "id", event["id"].(string))
|
||||
|
||||
receivedErrMessage := event["error_message"].(string)
|
||||
expectedErrMessage := "could not decrypt key with given passphrase"
|
||||
s.Equal(expectedErrMessage, receivedErrMessage)
|
||||
|
||||
receivedErrCode := event["error_code"].(string)
|
||||
s.Equal("2", receivedErrCode)
|
||||
|
||||
atomic.AddInt32(&isTxFailedEventCalled, 1)
|
||||
}
|
||||
})
|
||||
|
||||
// this call blocks, and should return on *second* attempt to ApproveSignRequest (w/ the correct password)
|
||||
sendTxHash, err := s.Backend.SendTransaction(context.TODO(), transactions.SendTxArgs{
|
||||
From: account.FromAddress(TestConfig.Account1.Address),
|
||||
To: account.ToAddress(TestConfig.Account2.Address),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
s.NoError(err, "cannot send transaction")
|
||||
|
||||
select {
|
||||
case <-completeQueuedTransaction:
|
||||
case <-time.After(time.Minute):
|
||||
s.FailNow("test timed out")
|
||||
}
|
||||
|
||||
s.Equal(sendTxHash, signHash, "transaction hash returned from SendTransaction is invalid")
|
||||
s.False(reflect.DeepEqual(sendTxHash, gethcommon.Hash{}), "transaction was never queued or completed")
|
||||
s.Zero(s.Backend.PendingSignRequests().Count(), "tx queue must be empty at this point")
|
||||
s.True(atomic.LoadInt32(&isTxFailedEventCalled) > 0, "expected tx failure signal is not received")
|
||||
}
|
||||
|
||||
func (s *TransactionsTestSuite) TestDiscardQueuedTransaction() {
|
||||
CheckTestSkipForNetworks(s.T(), params.MainNetworkID)
|
||||
|
||||
s.StartTestBackend()
|
||||
defer s.StopTestBackend()
|
||||
|
||||
EnsureNodeSync(s.Backend.StatusNode().EnsureSync)
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
s.NoError(s.Backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
|
||||
|
||||
completeQueuedTransaction := make(chan struct{})
|
||||
|
||||
// replace transaction notification handler
|
||||
var isTxFailedEventCalled int32 // using int32 as bool to avoid data race: 0 = `false`, 1 = `true`
|
||||
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var envelope signal.Envelope
|
||||
err := json.Unmarshal([]byte(jsonEvent), &envelope)
|
||||
s.NoError(err, fmt.Sprintf("cannot unmarshal JSON: %s", jsonEvent))
|
||||
|
||||
if envelope.Type == signal.EventSignRequestAdded {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
txID := event["id"].(string)
|
||||
log.Info("transaction queued (will be discarded soon)", "id", txID)
|
||||
|
||||
s.True(s.Backend.PendingSignRequests().Has(txID), "txqueue should still have test tx")
|
||||
|
||||
// discard
|
||||
err := s.Backend.DiscardSignRequest(txID)
|
||||
s.NoError(err, "cannot discard tx")
|
||||
|
||||
// try completing discarded transaction
|
||||
err = s.Backend.ApproveSignRequest(txID, TestConfig.Account1.Password).Error
|
||||
s.EqualError(err, sign.ErrSignReqNotFound.Error(), "expects tx not found, but call to ApproveSignRequest succeeded")
|
||||
|
||||
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
|
||||
s.False(s.Backend.PendingSignRequests().Has(txID),
|
||||
fmt.Sprintf("txqueue should not have test tx at this point (it should be discarded): %s", txID))
|
||||
|
||||
close(completeQueuedTransaction)
|
||||
}
|
||||
|
||||
if envelope.Type == signal.EventSignRequestFailed {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
log.Info("transaction return event received", "id", event["id"].(string))
|
||||
|
||||
receivedErrMessage := event["error_message"].(string)
|
||||
expectedErrMessage := sign.ErrSignReqDiscarded.Error()
|
||||
s.Equal(receivedErrMessage, expectedErrMessage)
|
||||
|
||||
receivedErrCode := event["error_code"].(string)
|
||||
s.Equal("4", receivedErrCode)
|
||||
|
||||
atomic.AddInt32(&isTxFailedEventCalled, 1)
|
||||
}
|
||||
})
|
||||
|
||||
// this call blocks, and should return when DiscardQueuedTransaction() is called
|
||||
txHashCheck, err := s.Backend.SendTransaction(context.TODO(), transactions.SendTxArgs{
|
||||
From: account.FromAddress(TestConfig.Account1.Address),
|
||||
To: account.ToAddress(TestConfig.Account2.Address),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
s.EqualError(err, sign.ErrSignReqDiscarded.Error(), "transaction is expected to be discarded")
|
||||
|
||||
select {
|
||||
case <-completeQueuedTransaction:
|
||||
case <-time.After(10 * time.Second):
|
||||
s.FailNow("test timed out")
|
||||
}
|
||||
|
||||
s.True(reflect.DeepEqual(txHashCheck, gethcommon.Hash{}), "transaction returned hash, while it shouldn't")
|
||||
s.Zero(s.Backend.PendingSignRequests().Count(), "tx queue must be empty at this point")
|
||||
s.True(atomic.LoadInt32(&isTxFailedEventCalled) > 0, "expected tx failure signal is not received")
|
||||
}
|
||||
|
||||
func (s *TransactionsTestSuite) TestCompleteMultipleQueuedTransactions() {
|
||||
CheckTestSkipForNetworks(s.T(), params.MainNetworkID)
|
||||
|
||||
s.setupLocalNode()
|
||||
defer s.StopTestBackend()
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
err := s.Backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
|
||||
}, TestConfig.Account1.Password)
|
||||
s.NoError(err)
|
||||
|
||||
s.sendConcurrentTransactions(3)
|
||||
}
|
||||
|
||||
func (s *TransactionsTestSuite) TestDiscardMultipleQueuedTransactions() {
|
||||
CheckTestSkipForNetworks(s.T(), params.MainNetworkID)
|
||||
|
||||
s.StartTestBackend()
|
||||
defer s.StopTestBackend()
|
||||
|
||||
EnsureNodeSync(s.Backend.StatusNode().EnsureSync)
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
s.NoError(s.Backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
|
||||
|
||||
testTxCount := 3
|
||||
txIDs := make(chan string, testTxCount)
|
||||
allTestTxDiscarded := make(chan struct{})
|
||||
|
||||
// replace transaction notification handler
|
||||
var txFailedEventCallCount int32
|
||||
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var envelope signal.Envelope
|
||||
err := json.Unmarshal([]byte(jsonEvent), &envelope)
|
||||
s.NoError(err)
|
||||
if envelope.Type == signal.EventSignRequestAdded {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
txID := event["id"].(string)
|
||||
log.Info("transaction queued (will be discarded soon)", "id", txID)
|
||||
|
||||
s.True(s.Backend.PendingSignRequests().Has(txID),
|
||||
"txqueue should still have test tx")
|
||||
txIDs <- txID
|
||||
}
|
||||
|
||||
if envelope.Type == signal.EventSignRequestFailed {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
log.Info("transaction return event received", "id", event["id"].(string))
|
||||
|
||||
receivedErrMessage := event["error_message"].(string)
|
||||
expectedErrMessage := sign.ErrSignReqDiscarded.Error()
|
||||
s.Equal(receivedErrMessage, expectedErrMessage)
|
||||
|
||||
receivedErrCode := event["error_code"].(string)
|
||||
s.Equal("4", receivedErrCode)
|
||||
|
||||
newCount := atomic.AddInt32(&txFailedEventCallCount, 1)
|
||||
if newCount == int32(testTxCount) {
|
||||
close(allTestTxDiscarded)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
require := s.Require()
|
||||
|
||||
// this call blocks, and should return when DiscardQueuedTransaction() for a given tx id is called
|
||||
sendTx := func() {
|
||||
txHashCheck, err := s.Backend.SendTransaction(context.TODO(), transactions.SendTxArgs{
|
||||
From: account.FromAddress(TestConfig.Account1.Address),
|
||||
To: account.ToAddress(TestConfig.Account2.Address),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
require.EqualError(err, sign.ErrSignReqDiscarded.Error())
|
||||
require.Equal(gethcommon.Hash{}, txHashCheck, "transaction returned hash, while it shouldn't")
|
||||
}
|
||||
|
||||
signRequests := s.Backend.PendingSignRequests()
|
||||
|
||||
// wait for transactions, and discard immediately
|
||||
discardTxs := func(txIDs []string) {
|
||||
txIDs = append(txIDs, invalidTxID)
|
||||
|
||||
// discard
|
||||
discardResults := s.Backend.DiscardSignRequests(txIDs)
|
||||
require.Len(discardResults, 1, "cannot discard txs: %v", discardResults)
|
||||
require.Error(discardResults[invalidTxID], sign.ErrSignReqNotFound, "cannot discard txs: %v", discardResults)
|
||||
|
||||
// try completing discarded transaction
|
||||
completeResults := s.Backend.ApproveSignRequests(txIDs, TestConfig.Account1.Password)
|
||||
require.Len(completeResults, testTxCount+1, "unexpected number of errors (call to ApproveSignRequest should not succeed)")
|
||||
|
||||
for _, txResult := range completeResults {
|
||||
require.Error(txResult.Error, sign.ErrSignReqNotFound, "invalid error for %s", txResult.Response.Hex())
|
||||
require.Equal(sign.EmptyResponse, txResult.Response, "invalid hash (expected zero): %s", txResult.Response.Hex())
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
|
||||
|
||||
for _, txID := range txIDs {
|
||||
require.False(
|
||||
signRequests.Has(txID),
|
||||
"txqueue should not have test tx at this point (it should be discarded): %s",
|
||||
txID,
|
||||
)
|
||||
}
|
||||
}
|
||||
go func() {
|
||||
ids := make([]string, testTxCount)
|
||||
for i := 0; i < testTxCount; i++ {
|
||||
ids[i] = <-txIDs
|
||||
}
|
||||
|
||||
discardTxs(ids)
|
||||
}()
|
||||
|
||||
// send multiple transactions
|
||||
for i := 0; i < testTxCount; i++ {
|
||||
go sendTx()
|
||||
}
|
||||
|
||||
select {
|
||||
case <-allTestTxDiscarded:
|
||||
case <-time.After(1 * time.Minute):
|
||||
s.FailNow("test timed out")
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
s.Zero(s.Backend.PendingSignRequests().Count(), "tx queue must be empty at this point")
|
||||
}
|
||||
|
||||
func (s *TransactionsTestSuite) TestNonExistentQueuedTransactions() {
|
||||
s.StartTestBackend()
|
||||
defer s.StopTestBackend()
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
s.NoError(s.Backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
|
||||
|
||||
// replace transaction notification handler
|
||||
signal.SetDefaultNodeNotificationHandler(func(string) {})
|
||||
|
||||
// try completing non-existing transaction
|
||||
err := s.Backend.ApproveSignRequest("some-bad-transaction-id", TestConfig.Account1.Password).Error
|
||||
s.Error(err, "error expected and not received")
|
||||
s.EqualError(err, sign.ErrSignReqNotFound.Error())
|
||||
}
|
||||
|
||||
func (s *TransactionsTestSuite) TestCompleteMultipleQueuedTransactionsUpstream() {
|
||||
CheckTestSkipForNetworks(s.T(), params.MainNetworkID)
|
||||
|
||||
s.setupUpstreamNode()
|
||||
defer s.StopTestBackend()
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
err := s.Backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
|
||||
s.NoError(err)
|
||||
|
||||
s.sendConcurrentTransactions(30)
|
||||
}
|
||||
|
||||
func (s *TransactionsTestSuite) setupLocalNode() {
|
||||
s.StartTestBackend()
|
||||
|
||||
EnsureNodeSync(s.Backend.StatusNode().EnsureSync)
|
||||
}
|
||||
|
||||
func (s *TransactionsTestSuite) setupUpstreamNode() {
|
||||
if GetNetworkID() == params.StatusChainNetworkID {
|
||||
s.T().Skip()
|
||||
}
|
||||
|
||||
addr, err := GetRemoteURL()
|
||||
s.NoError(err)
|
||||
s.StartTestBackend(e2e.WithUpstream(addr))
|
||||
}
|
||||
|
||||
func (s *TransactionsTestSuite) sendConcurrentTransactions(testTxCount int) {
|
||||
txIDs := make(chan string, testTxCount)
|
||||
allTestTxCompleted := make(chan struct{})
|
||||
|
||||
require := s.Require()
|
||||
|
||||
// replace transaction notification handler
|
||||
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var envelope signal.Envelope
|
||||
err := json.Unmarshal([]byte(jsonEvent), &envelope)
|
||||
require.NoError(err, fmt.Sprintf("cannot unmarshal JSON: %s", jsonEvent))
|
||||
|
||||
if envelope.Type == signal.EventSignRequestAdded {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
txID := event["id"].(string)
|
||||
log.Info("transaction queued (will be completed in a single call, once aggregated)", "id", txID)
|
||||
|
||||
txIDs <- txID
|
||||
}
|
||||
})
|
||||
|
||||
// this call blocks, and should return when DiscardQueuedTransaction() for a given tx id is called
|
||||
sendTx := func() {
|
||||
txHashCheck, err := s.Backend.SendTransaction(context.TODO(), transactions.SendTxArgs{
|
||||
From: account.FromAddress(TestConfig.Account1.Address),
|
||||
To: account.ToAddress(TestConfig.Account2.Address),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
require.NoError(err, "cannot send transaction")
|
||||
require.NotEqual(gethcommon.Hash{}, txHashCheck, "transaction returned empty hash")
|
||||
}
|
||||
|
||||
// wait for transactions, and complete them in a single call
|
||||
completeTxs := func(txIDs []string) {
|
||||
txIDs = append(txIDs, invalidTxID)
|
||||
results := s.Backend.ApproveSignRequests(txIDs, TestConfig.Account1.Password)
|
||||
s.Len(results, testTxCount+1)
|
||||
s.EqualError(results[invalidTxID].Error, sign.ErrSignReqNotFound.Error())
|
||||
|
||||
for txID, txResult := range results {
|
||||
s.False(
|
||||
txResult.Error != nil && txID != invalidTxID,
|
||||
"invalid error for %s", txID,
|
||||
)
|
||||
s.False(
|
||||
len(txResult.Response.Bytes()) < 1 && txID != invalidTxID,
|
||||
"invalid hash (expected non empty hash): %s", txID,
|
||||
)
|
||||
log.Info("transaction complete", "URL", txURLString(txResult))
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
|
||||
|
||||
for _, txID := range txIDs {
|
||||
s.False(
|
||||
s.Backend.PendingSignRequests().Has(txID),
|
||||
"txqueue should not have test tx at this point (it should be completed)",
|
||||
)
|
||||
}
|
||||
}
|
||||
go func() {
|
||||
ids := make([]string, testTxCount)
|
||||
for i := 0; i < testTxCount; i++ {
|
||||
ids[i] = <-txIDs
|
||||
}
|
||||
|
||||
completeTxs(ids)
|
||||
close(allTestTxCompleted)
|
||||
}()
|
||||
|
||||
// send multiple transactions
|
||||
for i := 0; i < testTxCount; i++ {
|
||||
go sendTx()
|
||||
}
|
||||
|
||||
select {
|
||||
case <-allTestTxCompleted:
|
||||
case <-time.After(60 * time.Second):
|
||||
s.FailNow("test timed out")
|
||||
}
|
||||
|
||||
s.Zero(s.PendingSignRequests().Count(), "queue should be empty")
|
||||
s.False(reflect.DeepEqual(hash, gethcommon.Hash{}))
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package transactions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -14,9 +14,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/log"
|
||||
|
||||
"github.com/status-im/status-go/account"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/rpc"
|
||||
"github.com/status-im/status-go/sign"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -29,7 +27,6 @@ const (
|
|||
// Transactor validates, signs transactions.
|
||||
// It uses upstream to propagate transactions to the Ethereum network.
|
||||
type Transactor struct {
|
||||
pendingSignRequests *sign.PendingRequests
|
||||
sender ethereum.TransactionSender
|
||||
pendingNonceProvider PendingNonceProvider
|
||||
gasCalculator GasCalculator
|
||||
|
@ -43,13 +40,12 @@ type Transactor struct {
|
|||
}
|
||||
|
||||
// NewTransactor returns a new Manager.
|
||||
func NewTransactor(signRequests *sign.PendingRequests) *Transactor {
|
||||
func NewTransactor() *Transactor {
|
||||
return &Transactor{
|
||||
pendingSignRequests: signRequests,
|
||||
addrLock: &AddrLocker{},
|
||||
sendTxTimeout: sendTxTimeout,
|
||||
localNonce: sync.Map{},
|
||||
log: log.New("package", "status-go/transactions.Manager"),
|
||||
addrLock: &AddrLocker{},
|
||||
sendTxTimeout: sendTxTimeout,
|
||||
localNonce: sync.Map{},
|
||||
log: log.New("package", "status-go/transactions.Manager"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,25 +64,9 @@ func (t *Transactor) SetRPC(rpcClient *rpc.Client, timeout time.Duration) {
|
|||
}
|
||||
|
||||
// SendTransaction is an implementation of eth_sendTransaction. It queues the tx to the sign queue.
|
||||
func (t *Transactor) SendTransaction(ctx context.Context, args SendTxArgs) (gethcommon.Hash, error) {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
completeFunc := func(acc *account.SelectedExtKey, password string, signArgs *sign.TxArgs) (sign.Response, error) {
|
||||
t.mergeSignTxArgsOntoSendTxArgs(signArgs, &args)
|
||||
hash, err := t.validateAndPropagate(acc, args)
|
||||
return sign.Response(hash.Bytes()), err
|
||||
}
|
||||
|
||||
request, err := t.pendingSignRequests.Add(ctx, params.SendTransactionMethodName, args, completeFunc)
|
||||
|
||||
if err != nil {
|
||||
return gethcommon.Hash{}, err
|
||||
}
|
||||
|
||||
result := t.pendingSignRequests.Wait(request.ID, t.sendTxTimeout)
|
||||
return result.Response.Hash(), result.Error
|
||||
func (t *Transactor) SendTransaction(sendArgs SendTxArgs, verifiedAccount *account.SelectedExtKey) (hash gethcommon.Hash, err error) {
|
||||
hash, err = t.validateAndPropagate(verifiedAccount, sendArgs)
|
||||
return
|
||||
}
|
||||
|
||||
// make sure that only account which created the tx can complete it
|
||||
|
@ -95,10 +75,8 @@ func (t *Transactor) validateAccount(args SendTxArgs, selectedAccount *account.S
|
|||
return account.ErrNoAccountSelected
|
||||
}
|
||||
|
||||
if args.From.Hex() != selectedAccount.Address.Hex() {
|
||||
err := sign.NewTransientError(ErrInvalidCompleteTxSender)
|
||||
t.log.Error("queued transaction does not belong to the selected account", "err", err)
|
||||
return err
|
||||
if !bytes.Equal(args.From.Bytes(), selectedAccount.Address.Bytes()) {
|
||||
return ErrInvalidTxSender
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -108,6 +86,7 @@ func (t *Transactor) validateAndPropagate(selectedAccount *account.SelectedExtKe
|
|||
if err = t.validateAccount(args, selectedAccount); err != nil {
|
||||
return hash, err
|
||||
}
|
||||
|
||||
if !args.Valid() {
|
||||
return hash, ErrInvalidSendTxArgs
|
||||
}
|
||||
|
@ -165,7 +144,7 @@ func (t *Transactor) validateAndPropagate(selectedAccount *account.SelectedExtKe
|
|||
return hash, err
|
||||
}
|
||||
if gas < defaultGas {
|
||||
t.log.Info(fmt.Sprintf("default gas will be used. estimated gas %v is lower than %v", gas, defaultGas))
|
||||
t.log.Info("default gas will be used because estimated is lower", "estimated", gas, "default", defaultGas)
|
||||
gas = defaultGas
|
||||
}
|
||||
} else {
|
||||
|
@ -205,15 +184,3 @@ func (t *Transactor) validateAndPropagate(selectedAccount *account.SelectedExtKe
|
|||
}
|
||||
return signedTx.Hash(), nil
|
||||
}
|
||||
|
||||
func (t *Transactor) mergeSignTxArgsOntoSendTxArgs(signArgs *sign.TxArgs, args *SendTxArgs) {
|
||||
if signArgs == nil {
|
||||
return
|
||||
}
|
||||
if signArgs.Gas != nil {
|
||||
args.Gas = signArgs.Gas
|
||||
}
|
||||
if signArgs.GasPrice != nil {
|
||||
args.GasPrice = signArgs.GasPrice
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -24,23 +25,16 @@ import (
|
|||
"github.com/status-im/status-go/account"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/rpc"
|
||||
"github.com/status-im/status-go/sign"
|
||||
"github.com/status-im/status-go/transactions/fake"
|
||||
|
||||
. "github.com/status-im/status-go/t/utils"
|
||||
)
|
||||
|
||||
func simpleVerifyFunc(acc *account.SelectedExtKey) func(string) (*account.SelectedExtKey, error) {
|
||||
return func(string) (*account.SelectedExtKey, error) {
|
||||
return acc, nil
|
||||
}
|
||||
func TestTransactorSuite(t *testing.T) {
|
||||
suite.Run(t, new(TransactorSuite))
|
||||
}
|
||||
|
||||
func TestTxQueueTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(TxQueueTestSuite))
|
||||
}
|
||||
|
||||
type TxQueueTestSuite struct {
|
||||
type TransactorSuite struct {
|
||||
suite.Suite
|
||||
server *gethrpc.Server
|
||||
client *gethrpc.Client
|
||||
|
@ -51,7 +45,7 @@ type TxQueueTestSuite struct {
|
|||
manager *Transactor
|
||||
}
|
||||
|
||||
func (s *TxQueueTestSuite) SetupTest() {
|
||||
func (s *TransactorSuite) SetupTest() {
|
||||
s.txServiceMockCtrl = gomock.NewController(s.T())
|
||||
|
||||
s.server, s.txServiceMock = fake.NewTestServer(s.txServiceMockCtrl)
|
||||
|
@ -63,55 +57,49 @@ func (s *TxQueueTestSuite) SetupTest() {
|
|||
s.Require().NoError(err)
|
||||
s.nodeConfig = nodeConfig
|
||||
|
||||
s.manager = NewTransactor(sign.NewPendingRequests())
|
||||
s.manager = NewTransactor()
|
||||
s.manager.sendTxTimeout = time.Second
|
||||
s.manager.SetNetworkID(chainID)
|
||||
s.manager.SetRPC(rpcClient, time.Second)
|
||||
}
|
||||
|
||||
func (s *TxQueueTestSuite) TearDownTest() {
|
||||
func (s *TransactorSuite) TearDownTest() {
|
||||
s.txServiceMockCtrl.Finish()
|
||||
s.server.Stop()
|
||||
s.client.Close()
|
||||
}
|
||||
|
||||
var (
|
||||
testGas = hexutil.Uint64(defaultGas + 1)
|
||||
testGasPrice = (*hexutil.Big)(big.NewInt(10))
|
||||
testOverridenGas = hexutil.Uint64(defaultGas + 2)
|
||||
testOverridenGasPrice = (*hexutil.Big)(big.NewInt(20))
|
||||
testNonce = hexutil.Uint64(10)
|
||||
testGas = hexutil.Uint64(defaultGas + 1)
|
||||
testGasPrice = (*hexutil.Big)(big.NewInt(10))
|
||||
testNonce = hexutil.Uint64(10)
|
||||
)
|
||||
|
||||
func (s *TxQueueTestSuite) setupTransactionPoolAPI(args SendTxArgs, returnNonce, resultNonce hexutil.Uint64, account *account.SelectedExtKey, txErr error, signArgs *sign.TxArgs) {
|
||||
func (s *TransactorSuite) setupTransactionPoolAPI(args SendTxArgs, returnNonce, resultNonce hexutil.Uint64, account *account.SelectedExtKey, txErr error) {
|
||||
// Expect calls to gas functions only if there are no user defined values.
|
||||
// And also set the expected gas and gas price for RLP encoding the expected tx.
|
||||
var usedGas hexutil.Uint64
|
||||
var usedGasPrice *big.Int
|
||||
s.txServiceMock.EXPECT().GetTransactionCount(gomock.Any(), account.Address, gethrpc.PendingBlockNumber).Return(&returnNonce, nil)
|
||||
if signArgs != nil && signArgs.GasPrice != nil {
|
||||
usedGasPrice = (*big.Int)(signArgs.GasPrice)
|
||||
} else if args.GasPrice == nil {
|
||||
if args.GasPrice == nil {
|
||||
usedGasPrice = (*big.Int)(testGasPrice)
|
||||
s.txServiceMock.EXPECT().GasPrice(gomock.Any()).Return(testGasPrice, nil)
|
||||
} else {
|
||||
usedGasPrice = (*big.Int)(args.GasPrice)
|
||||
}
|
||||
if signArgs != nil && signArgs.Gas != nil {
|
||||
usedGas = *signArgs.Gas
|
||||
} else if args.Gas == nil {
|
||||
if args.Gas == nil {
|
||||
s.txServiceMock.EXPECT().EstimateGas(gomock.Any(), gomock.Any()).Return(testGas, nil)
|
||||
usedGas = testGas
|
||||
} else {
|
||||
usedGas = *args.Gas
|
||||
}
|
||||
// Prepare the transaction anD RLP encode it.
|
||||
// Prepare the transaction and RLP encode it.
|
||||
data := s.rlpEncodeTx(args, s.nodeConfig, account, &resultNonce, usedGas, usedGasPrice)
|
||||
// Expect the RLP encoded transaction.
|
||||
s.txServiceMock.EXPECT().SendRawTransaction(gomock.Any(), data).Return(gethcommon.Hash{}, txErr)
|
||||
}
|
||||
|
||||
func (s *TxQueueTestSuite) rlpEncodeTx(args SendTxArgs, config *params.NodeConfig, account *account.SelectedExtKey, nonce *hexutil.Uint64, gas hexutil.Uint64, gasPrice *big.Int) hexutil.Bytes {
|
||||
func (s *TransactorSuite) rlpEncodeTx(args SendTxArgs, config *params.NodeConfig, account *account.SelectedExtKey, nonce *hexutil.Uint64, gas hexutil.Uint64, gasPrice *big.Int) hexutil.Bytes {
|
||||
newTx := types.NewTransaction(
|
||||
uint64(*nonce),
|
||||
*args.To,
|
||||
|
@ -128,87 +116,36 @@ func (s *TxQueueTestSuite) rlpEncodeTx(args SendTxArgs, config *params.NodeConfi
|
|||
return hexutil.Bytes(data)
|
||||
}
|
||||
|
||||
func (s *TxQueueTestSuite) TestCompleteTransaction() {
|
||||
func (s *TransactorSuite) TestGasValues() {
|
||||
key, _ := crypto.GenerateKey()
|
||||
selectedAccount := &account.SelectedExtKey{
|
||||
Address: account.FromAddress(TestConfig.Account1.Address),
|
||||
AccountKey: &keystore.Key{PrivateKey: key},
|
||||
}
|
||||
testCases := []struct {
|
||||
name string
|
||||
gas *hexutil.Uint64
|
||||
gasPrice *hexutil.Big
|
||||
signTxArgs *sign.TxArgs
|
||||
name string
|
||||
gas *hexutil.Uint64
|
||||
gasPrice *hexutil.Big
|
||||
}{
|
||||
{
|
||||
"noGasDef",
|
||||
nil,
|
||||
nil,
|
||||
s.defaultSignTxArgs(),
|
||||
},
|
||||
{
|
||||
"gasDefined",
|
||||
&testGas,
|
||||
nil,
|
||||
s.defaultSignTxArgs(),
|
||||
},
|
||||
{
|
||||
"gasPriceDefined",
|
||||
nil,
|
||||
testGasPrice,
|
||||
s.defaultSignTxArgs(),
|
||||
},
|
||||
{
|
||||
"inputPassedInLegacyDataField",
|
||||
nil,
|
||||
testGasPrice,
|
||||
s.defaultSignTxArgs(),
|
||||
},
|
||||
{
|
||||
"overrideGas",
|
||||
nil,
|
||||
nil,
|
||||
&sign.TxArgs{
|
||||
Gas: &testGas,
|
||||
},
|
||||
},
|
||||
{
|
||||
"overridePreExistingGas",
|
||||
&testGas,
|
||||
nil,
|
||||
&sign.TxArgs{
|
||||
Gas: &testOverridenGas,
|
||||
},
|
||||
},
|
||||
{
|
||||
"overridePreExistingGasPrice",
|
||||
nil,
|
||||
testGasPrice,
|
||||
&sign.TxArgs{
|
||||
GasPrice: testOverridenGasPrice,
|
||||
},
|
||||
},
|
||||
{
|
||||
"nilSignTransactionSpecificArgs",
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"overridePreExistingGasWithNil",
|
||||
&testGas,
|
||||
nil,
|
||||
&sign.TxArgs{
|
||||
Gas: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
"overridePreExistingGasPriceWithNil",
|
||||
nil,
|
||||
testGasPrice,
|
||||
&sign.TxArgs{
|
||||
GasPrice: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -221,96 +158,48 @@ func (s *TxQueueTestSuite) TestCompleteTransaction() {
|
|||
Gas: testCase.gas,
|
||||
GasPrice: testCase.gasPrice,
|
||||
}
|
||||
s.setupTransactionPoolAPI(args, testNonce, testNonce, selectedAccount, nil, testCase.signTxArgs)
|
||||
s.setupTransactionPoolAPI(args, testNonce, testNonce, selectedAccount, nil)
|
||||
|
||||
w := make(chan struct{})
|
||||
var sendHash gethcommon.Hash
|
||||
go func() {
|
||||
var sendErr error
|
||||
sendHash, sendErr = s.manager.SendTransaction(context.Background(), args)
|
||||
s.NoError(sendErr)
|
||||
close(w)
|
||||
}()
|
||||
|
||||
for i := 10; i > 0; i-- {
|
||||
if s.manager.pendingSignRequests.Count() > 0 {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
|
||||
req := s.manager.pendingSignRequests.First()
|
||||
s.NotNil(req)
|
||||
approveResult := s.manager.pendingSignRequests.Approve(req.ID, "", testCase.signTxArgs, simpleVerifyFunc(selectedAccount))
|
||||
s.NoError(approveResult.Error)
|
||||
s.NoError(WaitClosed(w, time.Second))
|
||||
|
||||
// Transaction should be already removed from the queue.
|
||||
s.False(s.manager.pendingSignRequests.Has(req.ID))
|
||||
s.Equal(sendHash.Bytes(), approveResult.Response.Bytes())
|
||||
hash, err := s.manager.SendTransaction(args, selectedAccount)
|
||||
s.NoError(err)
|
||||
s.False(reflect.DeepEqual(hash, gethcommon.Hash{}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TxQueueTestSuite) defaultSignTxArgs() *sign.TxArgs {
|
||||
return &sign.TxArgs{}
|
||||
func (s *TransactorSuite) TestArgsValidation() {
|
||||
args := SendTxArgs{
|
||||
From: account.FromAddress(TestConfig.Account1.Address),
|
||||
To: account.ToAddress(TestConfig.Account2.Address),
|
||||
Data: hexutil.Bytes([]byte{0x01, 0x02}),
|
||||
Input: hexutil.Bytes([]byte{0x02, 0x01}),
|
||||
}
|
||||
s.False(args.Valid())
|
||||
selectedAccount := &account.SelectedExtKey{
|
||||
Address: account.FromAddress(TestConfig.Account1.Address),
|
||||
}
|
||||
_, err := s.manager.SendTransaction(args, selectedAccount)
|
||||
s.EqualError(err, ErrInvalidSendTxArgs.Error())
|
||||
}
|
||||
|
||||
func (s *TxQueueTestSuite) TestAccountMismatch() {
|
||||
func (s *TransactorSuite) TestAccountMismatch() {
|
||||
args := SendTxArgs{
|
||||
From: account.FromAddress(TestConfig.Account1.Address),
|
||||
To: account.ToAddress(TestConfig.Account2.Address),
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// missing account
|
||||
_, err = s.manager.SendTransaction(args, nil)
|
||||
s.EqualError(err, account.ErrNoAccountSelected.Error())
|
||||
|
||||
// mismatched accounts
|
||||
selectedAccount := &account.SelectedExtKey{
|
||||
Address: account.FromAddress(TestConfig.Account2.Address),
|
||||
}
|
||||
|
||||
args := SendTxArgs{
|
||||
From: account.FromAddress(TestConfig.Account1.Address),
|
||||
To: account.ToAddress(TestConfig.Account2.Address),
|
||||
}
|
||||
|
||||
go func() {
|
||||
s.manager.SendTransaction(context.Background(), args) // nolint: errcheck
|
||||
}()
|
||||
|
||||
for i := 10; i > 0; i-- {
|
||||
if s.manager.pendingSignRequests.Count() > 0 {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
|
||||
req := s.manager.pendingSignRequests.First()
|
||||
s.NotNil(req)
|
||||
result := s.manager.pendingSignRequests.Approve(req.ID, "", s.defaultSignTxArgs(), simpleVerifyFunc(selectedAccount))
|
||||
s.EqualError(result.Error, ErrInvalidCompleteTxSender.Error())
|
||||
|
||||
// Transaction should stay in the queue as mismatched accounts
|
||||
// is a recoverable error.
|
||||
s.True(s.manager.pendingSignRequests.Has(req.ID))
|
||||
}
|
||||
|
||||
func (s *TxQueueTestSuite) TestDiscardTransaction() {
|
||||
args := SendTxArgs{
|
||||
From: account.FromAddress(TestConfig.Account1.Address),
|
||||
To: account.ToAddress(TestConfig.Account2.Address),
|
||||
}
|
||||
w := make(chan struct{})
|
||||
go func() {
|
||||
_, err := s.manager.SendTransaction(context.Background(), args)
|
||||
s.Equal(sign.ErrSignReqDiscarded, err)
|
||||
close(w)
|
||||
}()
|
||||
|
||||
for i := 10; i > 0; i-- {
|
||||
if s.manager.pendingSignRequests.Count() > 0 {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
|
||||
req := s.manager.pendingSignRequests.First()
|
||||
s.NotNil(req)
|
||||
err := s.manager.pendingSignRequests.Discard(req.ID)
|
||||
s.NoError(err)
|
||||
s.NoError(WaitClosed(w, time.Second))
|
||||
_, err = s.manager.SendTransaction(args, selectedAccount)
|
||||
s.EqualError(err, ErrInvalidTxSender.Error())
|
||||
}
|
||||
|
||||
// TestLocalNonce verifies that local nonce will be used unless
|
||||
|
@ -320,7 +209,7 @@ func (s *TxQueueTestSuite) TestDiscardTransaction() {
|
|||
// then, we return higher nonce, as if another node was used to send 2 transactions
|
||||
// upstream nonce will be equal to 5, we update our local counter to 5+1
|
||||
// as the last step, we verify that if tx failed nonce is not updated
|
||||
func (s *TxQueueTestSuite) TestLocalNonce() {
|
||||
func (s *TransactorSuite) TestLocalNonce() {
|
||||
txCount := 3
|
||||
key, _ := crypto.GenerateKey()
|
||||
selectedAccount := &account.SelectedExtKey{
|
||||
|
@ -329,30 +218,14 @@ func (s *TxQueueTestSuite) TestLocalNonce() {
|
|||
}
|
||||
nonce := hexutil.Uint64(0)
|
||||
|
||||
go func() {
|
||||
approved := 0
|
||||
for {
|
||||
// 3 in a cycle, then 2
|
||||
if approved >= txCount+2 {
|
||||
return
|
||||
}
|
||||
req := s.manager.pendingSignRequests.First()
|
||||
if req == nil {
|
||||
time.Sleep(time.Millisecond)
|
||||
} else {
|
||||
s.manager.pendingSignRequests.Approve(req.ID, "", s.defaultSignTxArgs(), simpleVerifyFunc(selectedAccount)) // nolint: errcheck
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for i := 0; i < txCount; i++ {
|
||||
args := SendTxArgs{
|
||||
From: account.FromAddress(TestConfig.Account1.Address),
|
||||
To: account.ToAddress(TestConfig.Account2.Address),
|
||||
}
|
||||
s.setupTransactionPoolAPI(args, nonce, hexutil.Uint64(i), selectedAccount, nil, nil)
|
||||
s.setupTransactionPoolAPI(args, nonce, hexutil.Uint64(i), selectedAccount, nil)
|
||||
|
||||
_, err := s.manager.SendTransaction(context.Background(), args)
|
||||
_, err := s.manager.SendTransaction(args, selectedAccount)
|
||||
s.NoError(err)
|
||||
resultNonce, _ := s.manager.localNonce.Load(args.From)
|
||||
s.Equal(uint64(i)+1, resultNonce.(uint64))
|
||||
|
@ -364,9 +237,9 @@ func (s *TxQueueTestSuite) TestLocalNonce() {
|
|||
To: account.ToAddress(TestConfig.Account2.Address),
|
||||
}
|
||||
|
||||
s.setupTransactionPoolAPI(args, nonce, nonce, selectedAccount, nil, nil)
|
||||
s.setupTransactionPoolAPI(args, nonce, nonce, selectedAccount, nil)
|
||||
|
||||
_, err := s.manager.SendTransaction(context.Background(), args)
|
||||
_, err := s.manager.SendTransaction(args, selectedAccount)
|
||||
s.NoError(err)
|
||||
|
||||
resultNonce, _ := s.manager.localNonce.Load(args.From)
|
||||
|
@ -379,13 +252,13 @@ func (s *TxQueueTestSuite) TestLocalNonce() {
|
|||
To: account.ToAddress(TestConfig.Account2.Address),
|
||||
}
|
||||
|
||||
_, err = s.manager.SendTransaction(context.Background(), args)
|
||||
s.EqualError(testErr, err.Error())
|
||||
_, err = s.manager.SendTransaction(args, selectedAccount)
|
||||
s.EqualError(err, testErr.Error())
|
||||
resultNonce, _ = s.manager.localNonce.Load(args.From)
|
||||
s.Equal(uint64(nonce)+1, resultNonce.(uint64))
|
||||
}
|
||||
|
||||
func (s *TxQueueTestSuite) TestContractCreation() {
|
||||
func (s *TransactorSuite) TestContractCreation() {
|
||||
key, _ := crypto.GenerateKey()
|
||||
testaddr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
genesis := core.GenesisAlloc{
|
||||
|
@ -404,19 +277,7 @@ func (s *TxQueueTestSuite) TestContractCreation() {
|
|||
Input: hexutil.Bytes(gethcommon.FromHex(contract.ENSBin)),
|
||||
}
|
||||
|
||||
go func() {
|
||||
for i := 1000; i > 0; i-- {
|
||||
req := s.manager.pendingSignRequests.First()
|
||||
if req == nil {
|
||||
time.Sleep(time.Millisecond)
|
||||
} else {
|
||||
s.manager.pendingSignRequests.Approve(req.ID, "", s.defaultSignTxArgs(), simpleVerifyFunc(selectedAccount)) // nolint: errcheck
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
hash, err := s.manager.SendTransaction(context.Background(), tx)
|
||||
hash, err := s.manager.SendTransaction(tx, selectedAccount)
|
||||
s.NoError(err)
|
||||
backend.Commit()
|
||||
receipt, err := backend.TransactionReceipt(context.TODO(), hash)
|
||||
|
|
|
@ -3,7 +3,6 @@ package transactions
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
ethereum "github.com/ethereum/go-ethereum"
|
||||
|
@ -13,12 +12,11 @@ import (
|
|||
|
||||
var (
|
||||
// ErrInvalidSendTxArgs is returned when the structure of SendTxArgs is ambigious.
|
||||
ErrInvalidSendTxArgs = errors.New("Transaction arguments are invalid (are both 'input' and 'data' fields used?)")
|
||||
// ErrUnexpectedArgs returned when args are of unexpected length.
|
||||
ErrInvalidSendTxArgs = errors.New("transaction arguments are invalid")
|
||||
// ErrUnexpectedArgs is returned when args are of unexpected length.
|
||||
ErrUnexpectedArgs = errors.New("unexpected args")
|
||||
|
||||
//ErrInvalidCompleteTxSender - error transaction with invalid sender
|
||||
ErrInvalidCompleteTxSender = errors.New("transaction can only be completed by its creator")
|
||||
//ErrInvalidTxSender is returned when selected account is different tham From field.
|
||||
ErrInvalidTxSender = errors.New("transaction can only be send by its creator")
|
||||
)
|
||||
|
||||
// PendingNonceProvider provides information about nonces.
|
||||
|
@ -72,20 +70,3 @@ func (args SendTxArgs) GetInput() hexutil.Bytes {
|
|||
func isNilOrEmpty(bytes hexutil.Bytes) bool {
|
||||
return bytes == nil || len(bytes) == 0
|
||||
}
|
||||
|
||||
// RPCCalltoSendTxArgs creates SendTxArgs based on RPC parameters
|
||||
func RPCCalltoSendTxArgs(args ...interface{}) (SendTxArgs, error) {
|
||||
var txArgs SendTxArgs
|
||||
if len(args) != 1 {
|
||||
return txArgs, ErrUnexpectedArgs
|
||||
}
|
||||
data, err := json.Marshal(args[0])
|
||||
if err != nil {
|
||||
return txArgs, err
|
||||
}
|
||||
if err := json.Unmarshal(data, &txArgs); err != nil {
|
||||
return txArgs, err
|
||||
}
|
||||
|
||||
return txArgs, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue