Implement `personal_sign`.
This commit implements `personal_sign` RPC or web3 personal.sign methods. NB! Contains breaking API changes.
This commit is contained in:
parent
364f88efd9
commit
4cc6028d59
|
@ -489,6 +489,6 @@
|
|||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "eff125b3ad51a2875bc0ee5f9d190ce65ee9fb774e79d8df0de7a7e4bf4e32f8"
|
||||
inputs-digest = "aee11795bbc6edceede45c682bef1deaf1c8166377c53632fc1fd23003e2a784"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# These packages are introduced by patching go-ethereum, so there isn't any point in forcing dep to find them.
|
||||
ignored = [ "github.com/ethereum/go-ethereum/ethapi" ]
|
||||
|
||||
[prune]
|
||||
unused-packages = true
|
||||
go-tests = true
|
||||
|
@ -13,6 +16,7 @@
|
|||
unused-packages = false
|
||||
non-go = false
|
||||
|
||||
|
||||
# * * * * * constrained `status-go` dependencies * * * * *
|
||||
# (for the full dependency list see `Gopkg.lock`)
|
||||
|
||||
|
|
1
Makefile
1
Makefile
|
@ -141,6 +141,7 @@ test-e2e: ##@tests Run e2e tests
|
|||
go test -timeout 20m ./t/e2e/rpc/... -network=$(networkid) $(gotest_extraflags)
|
||||
go test -timeout 20m ./t/e2e/whisper/... -network=$(networkid) $(gotest_extraflags)
|
||||
go test -timeout 10m ./t/e2e/transactions/... -network=$(networkid) $(gotest_extraflags)
|
||||
go test -timeout 10m ./t/e2e/services/... -network=$(networkid) $(gotest_extraflags)
|
||||
# e2e_test tag is required to include some files from ./lib without _test suffix
|
||||
go test -timeout 40m -tags e2e_test ./lib -network=$(networkid) $(gotest_extraflags)
|
||||
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
diff --git a/ethapi/private_account.go b/ethapi/private_account.go
|
||||
new file mode 100644
|
||||
index 00000000..8d51fd31
|
||||
--- /dev/null
|
||||
+++ b/ethapi/private_account.go
|
||||
@@ -0,0 +1,26 @@
|
||||
+package ethapi
|
||||
+
|
||||
+import (
|
||||
+ "context"
|
||||
+
|
||||
+ "github.com/ethereum/go-ethereum/accounts"
|
||||
+ "github.com/ethereum/go-ethereum/common"
|
||||
+ "github.com/ethereum/go-ethereum/common/hexutil"
|
||||
+ "github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
+)
|
||||
+
|
||||
+type LimitedPersonalAPI struct {
|
||||
+ privateAPI *ethapi.PrivateAccountAPI
|
||||
+}
|
||||
+
|
||||
+func NewLimitedPersonalAPI(am *accounts.Manager) *LimitedPersonalAPI {
|
||||
+ return &LimitedPersonalAPI{ethapi.NewSubsetOfPrivateAccountAPI(am)}
|
||||
+}
|
||||
+
|
||||
+func (s *LimitedPersonalAPI) Sign(ctx context.Context, data hexutil.Bytes, addr common.Address, passwd string) (hexutil.Bytes, error) {
|
||||
+ return s.privateAPI.Sign(ctx, data, addr, passwd)
|
||||
+}
|
||||
+
|
||||
+func (s *LimitedPersonalAPI) EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error) {
|
||||
+ return s.privateAPI.EcRecover(ctx, data, sig)
|
||||
+}
|
||||
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
|
||||
index 31408633..3cee8753 100644
|
||||
--- a/internal/ethapi/api.go
|
||||
+++ b/internal/ethapi/api.go
|
||||
@@ -214,6 +214,14 @@ func NewPrivateAccountAPI(b Backend, nonceLock *AddrLocker) *PrivateAccountAPI {
|
||||
}
|
||||
}
|
||||
|
||||
+func NewSubsetOfPrivateAccountAPI(am *accounts.Manager) *PrivateAccountAPI {
|
||||
+ return &PrivateAccountAPI{
|
||||
+ am: am,
|
||||
+ nonceLock: nil,
|
||||
+ b: nil,
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
// ListAccounts will return a list of addresses for accounts this node manages.
|
||||
func (s *PrivateAccountAPI) ListAccounts() []common.Address {
|
||||
addresses := make([]common.Address, 0) // return [] instead of nil if empty
|
||||
@@ -426,7 +434,7 @@ func (s *PrivateAccountAPI) Sign(ctx context.Context, data hexutil.Bytes, addr c
|
||||
// Look up the wallet containing the requested signer
|
||||
account := accounts.Account{Address: addr}
|
||||
|
||||
- wallet, err := s.b.AccountManager().Find(account)
|
||||
+ wallet, err := s.am.Find(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -167,11 +167,11 @@ func (cs *commandSet) Logout() error {
|
|||
return cs.statusAPI.Logout()
|
||||
}
|
||||
|
||||
// CompleteTransaction instructs API to complete sending of a given transaction.
|
||||
func (cs *commandSet) CompleteTransaction(id, password string) (string, error) {
|
||||
txHash, err := cs.statusAPI.CompleteTransaction(id, password)
|
||||
if err != nil {
|
||||
return "", err
|
||||
// ApproveSignRequest instructs API to complete sending of a given transaction.
|
||||
func (cs *commandSet) ApproveSignRequest(id, password string) (string, error) {
|
||||
result := cs.statusAPI.ApproveSignRequest(id, password)
|
||||
if result.Error != nil {
|
||||
return "", result.Error
|
||||
}
|
||||
return txHash.String(), nil
|
||||
return result.Response.Hex(), nil
|
||||
}
|
||||
|
|
|
@ -161,24 +161,24 @@ func (api *StatusAPI) SendTransaction(ctx context.Context, args transactions.Sen
|
|||
return api.b.SendTransaction(ctx, args)
|
||||
}
|
||||
|
||||
// CompleteTransaction instructs backend to complete sending of a given transaction
|
||||
func (api *StatusAPI) CompleteTransaction(id string, password string) (gethcommon.Hash, error) {
|
||||
return api.b.CompleteTransaction(id, password)
|
||||
// ApproveSignRequest instructs backend to complete sending of a given transaction
|
||||
func (api *StatusAPI) ApproveSignRequest(id string, password string) sign.Result {
|
||||
return api.b.ApproveSignRequest(id, password)
|
||||
}
|
||||
|
||||
// CompleteTransactions instructs backend to complete sending of multiple transactions
|
||||
func (api *StatusAPI) CompleteTransactions(ids []string, password string) map[string]sign.Result {
|
||||
return api.b.CompleteTransactions(ids, password)
|
||||
// ApproveSignRequests instructs backend to complete sending of multiple transactions
|
||||
func (api *StatusAPI) ApproveSignRequests(ids []string, password string) map[string]sign.Result {
|
||||
return api.b.ApproveSignRequests(ids, password)
|
||||
}
|
||||
|
||||
// DiscardTransaction discards a given transaction from transaction queue
|
||||
func (api *StatusAPI) DiscardTransaction(id string) error {
|
||||
return api.b.DiscardTransaction(id)
|
||||
// DiscardSignRequest discards a given transaction from transaction queue
|
||||
func (api *StatusAPI) DiscardSignRequest(id string) error {
|
||||
return api.b.DiscardSignRequest(id)
|
||||
}
|
||||
|
||||
// DiscardTransactions discards given multiple transactions from transaction queue
|
||||
func (api *StatusAPI) DiscardTransactions(ids []string) map[string]error {
|
||||
return api.b.DiscardTransactions(ids)
|
||||
// DiscardSignRequests discards given multiple transactions from transaction queue
|
||||
func (api *StatusAPI) DiscardSignRequests(ids []string) map[string]error {
|
||||
return api.b.DiscardSignRequests(ids)
|
||||
}
|
||||
|
||||
// JailParse creates a new jail cell context, with the given chatID as identifier.
|
||||
|
|
|
@ -14,8 +14,10 @@ import (
|
|||
"github.com/status-im/status-go/geth/node"
|
||||
"github.com/status-im/status-go/geth/notifications/push/fcm"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
"github.com/status-im/status-go/geth/rpc"
|
||||
"github.com/status-im/status-go/geth/signal"
|
||||
"github.com/status-im/status-go/geth/transactions"
|
||||
"github.com/status-im/status-go/services/personal"
|
||||
"github.com/status-im/status-go/sign"
|
||||
)
|
||||
|
||||
|
@ -36,6 +38,7 @@ type StatusBackend struct {
|
|||
mu sync.Mutex
|
||||
statusNode *node.StatusNode
|
||||
pendingSignRequests *sign.PendingRequests
|
||||
personalAPI *personal.PublicAPI
|
||||
accountManager *account.Manager
|
||||
transactor *transactions.Transactor
|
||||
jailManager jail.Manager
|
||||
|
@ -52,6 +55,7 @@ func NewStatusBackend() *StatusBackend {
|
|||
pendingSignRequests := sign.NewPendingRequests()
|
||||
accountManager := account.NewManager(statusNode)
|
||||
transactor := transactions.NewTransactor(pendingSignRequests)
|
||||
personalAPI := personal.NewAPI(pendingSignRequests)
|
||||
jailManager := jail.New(statusNode)
|
||||
notificationManager := fcm.NewNotification(fcmServerKey)
|
||||
|
||||
|
@ -61,6 +65,7 @@ func NewStatusBackend() *StatusBackend {
|
|||
accountManager: accountManager,
|
||||
jailManager: jailManager,
|
||||
transactor: transactor,
|
||||
personalAPI: personalAPI,
|
||||
newNotification: notificationManager,
|
||||
log: log.New("package", "status-go/geth/api.StatusBackend"),
|
||||
}
|
||||
|
@ -81,6 +86,11 @@ func (b *StatusBackend) JailManager() jail.Manager {
|
|||
return b.jailManager
|
||||
}
|
||||
|
||||
// PersonalAPI returns reference to personal APIs
|
||||
func (b *StatusBackend) PersonalAPI() *personal.PublicAPI {
|
||||
return b.personalAPI
|
||||
}
|
||||
|
||||
// Transactor returns reference to a status transactor
|
||||
func (b *StatusBackend) Transactor() *transactions.Transactor {
|
||||
return b.transactor
|
||||
|
@ -123,7 +133,8 @@ func (b *StatusBackend) startNode(config *params.NodeConfig) (err error) {
|
|||
signal.Send(signal.Envelope{Type: signal.EventNodeStarted})
|
||||
|
||||
b.transactor.SetNetworkID(config.NetworkID)
|
||||
b.transactor.SetRPCClient(b.statusNode.RPCClient())
|
||||
b.transactor.SetRPC(b.statusNode.RPCClient(), rpc.DefaultCallTimeout)
|
||||
b.personalAPI.SetRPC(b.statusNode.RPCClient(), rpc.DefaultCallTimeout)
|
||||
if err := b.registerHandlers(); err != nil {
|
||||
b.log.Error("Handler registration failed", "err", err)
|
||||
}
|
||||
|
@ -212,34 +223,30 @@ func (b *StatusBackend) getVerifiedAccount(password string) (*account.SelectedEx
|
|||
return selectedAccount, nil
|
||||
}
|
||||
|
||||
// CompleteTransaction instructs backend to complete sending of a given transaction
|
||||
func (b *StatusBackend) CompleteTransaction(id string, password string) (hash gethcommon.Hash, err error) {
|
||||
// ApproveSignRequest instructs backend to complete sending of a given transaction
|
||||
func (b *StatusBackend) ApproveSignRequest(id string, password string) sign.Result {
|
||||
return b.pendingSignRequests.Approve(id, password, b.getVerifiedAccount)
|
||||
}
|
||||
|
||||
// CompleteTransactions instructs backend to complete sending of multiple transactions
|
||||
func (b *StatusBackend) CompleteTransactions(ids []string, password string) map[string]sign.Result {
|
||||
// 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 {
|
||||
txHash, txErr := b.CompleteTransaction(txID, password)
|
||||
results[txID] = sign.Result{
|
||||
Hash: txHash,
|
||||
Error: txErr,
|
||||
}
|
||||
results[txID] = b.ApproveSignRequest(txID, password)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// DiscardTransaction discards a given transaction from transaction queue
|
||||
func (b *StatusBackend) DiscardTransaction(id string) error {
|
||||
// DiscardSignRequest discards a given transaction from transaction queue
|
||||
func (b *StatusBackend) DiscardSignRequest(id string) error {
|
||||
return b.pendingSignRequests.Discard(id)
|
||||
}
|
||||
|
||||
// DiscardTransactions discards given multiple transactions from transaction queue
|
||||
func (b *StatusBackend) DiscardTransactions(ids []string) map[string]error {
|
||||
// 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.DiscardTransaction(txID)
|
||||
err := b.DiscardSignRequest(txID)
|
||||
if err != nil {
|
||||
results[txID] = err
|
||||
}
|
||||
|
@ -273,6 +280,8 @@ func (b *StatusBackend) registerHandlers() error {
|
|||
return hash.Hex(), err
|
||||
})
|
||||
|
||||
rpcClient.RegisterHandler(params.PersonalSignMethodName, b.PersonalAPI().Sign)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -24,14 +24,16 @@ import (
|
|||
"github.com/status-im/status-go/geth/mailservice"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
shhmetrics "github.com/status-im/status-go/metrics/whisper"
|
||||
"github.com/status-im/status-go/shhext"
|
||||
"github.com/status-im/status-go/services/personal"
|
||||
"github.com/status-im/status-go/services/shhext"
|
||||
)
|
||||
|
||||
// Errors related to node and services creation.
|
||||
var (
|
||||
ErrNodeMakeFailure = errors.New("error creating p2p node")
|
||||
ErrWhisperServiceRegistrationFailure = errors.New("failed to register the Whisper service")
|
||||
ErrLightEthRegistrationFailure = errors.New("failed to register the LES service")
|
||||
ErrNodeMakeFailure = errors.New("error creating p2p node")
|
||||
ErrWhisperServiceRegistrationFailure = errors.New("failed to register the Whisper service")
|
||||
ErrLightEthRegistrationFailure = errors.New("failed to register the LES service")
|
||||
ErrPersonalServiceRegistrationFailure = errors.New("failed to register the personal api service")
|
||||
)
|
||||
|
||||
// All general log messages in this package should be routed through this logger.
|
||||
|
@ -76,6 +78,15 @@ func MakeNode(config *params.NodeConfig) (*node.Node, error) {
|
|||
if err := activateLightEthService(stack, config); err != nil {
|
||||
return nil, fmt.Errorf("%v: %v", ErrLightEthRegistrationFailure, err)
|
||||
}
|
||||
} else {
|
||||
// `personal_sign` and `personal_recover` methods are important to
|
||||
// keep DApps working.
|
||||
// Usually, they are provided by an ETH or a LES service, but when using
|
||||
// upstream, we don't start any of these, so we need to start our own
|
||||
// implementation.
|
||||
if err := activatePersonalService(stack, config); err != nil {
|
||||
return nil, fmt.Errorf("%v: %v", ErrPersonalServiceRegistrationFailure, err)
|
||||
}
|
||||
}
|
||||
|
||||
// start Whisper service.
|
||||
|
@ -148,6 +159,13 @@ func activateLightEthService(stack *node.Node, config *params.NodeConfig) error
|
|||
})
|
||||
}
|
||||
|
||||
func activatePersonalService(stack *node.Node, config *params.NodeConfig) error {
|
||||
return stack.Register(func(*node.ServiceContext) (node.Service, error) {
|
||||
svc := personal.New(stack.AccountManager())
|
||||
return svc, nil
|
||||
})
|
||||
}
|
||||
|
||||
// activateShhService configures Whisper and adds it to the given node.
|
||||
func activateShhService(stack *node.Node, config *params.NodeConfig) (err error) {
|
||||
if config.WhisperConfig == nil || !config.WhisperConfig.Enabled {
|
||||
|
|
|
@ -30,7 +30,7 @@ const (
|
|||
ListenAddr = ":0"
|
||||
|
||||
// APIModules is a list of modules to expose via any type of RPC (HTTP, IPC, in-proc)
|
||||
APIModules = "eth,net,web3,shh,shhext"
|
||||
APIModules = "eth,net,web3,shh,shhext,personal"
|
||||
|
||||
// SendTransactionMethodName defines the name for a giving transaction.
|
||||
SendTransactionMethodName = "eth_sendTransaction"
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
|
@ -14,6 +15,11 @@ import (
|
|||
gethrpc "github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultCallTimeout is a default timeout for an RPC call
|
||||
DefaultCallTimeout = time.Minute
|
||||
)
|
||||
|
||||
// Handler defines handler for RPC methods.
|
||||
type Handler func(context.Context, ...interface{}) (interface{}, error)
|
||||
|
||||
|
@ -81,15 +87,28 @@ func (c *Client) Call(result interface{}, method string, args ...interface{}) er
|
|||
// can also pass nil, in which case the result is ignored.
|
||||
//
|
||||
// It uses custom routing scheme for calls.
|
||||
// If there are any local handlers registered for this call, they will handle it.
|
||||
func (c *Client) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error {
|
||||
// check locally registered handlers first
|
||||
if handler, ok := c.handler(method); ok {
|
||||
return c.callMethod(ctx, result, handler, args...)
|
||||
}
|
||||
|
||||
return c.CallContextIgnoringLocalHandlers(ctx, result, method, args...)
|
||||
}
|
||||
|
||||
// CallContextIgnoringLocalHandlers performs a JSON-RPC call with the given
|
||||
// arguments.
|
||||
//
|
||||
// If there are local handlers registered for this call, they would
|
||||
// be ignored. It is useful if the call is happening from within a local
|
||||
// handler itself.
|
||||
// Upstream calls routing will be used anyway.
|
||||
func (c *Client) CallContextIgnoringLocalHandlers(ctx context.Context, result interface{}, method string, args ...interface{}) error {
|
||||
if c.router.routeRemote(method) {
|
||||
return c.upstream.CallContext(ctx, result, method, args...)
|
||||
}
|
||||
|
||||
return c.local.CallContext(ctx, result, method, args...)
|
||||
}
|
||||
|
||||
|
|
|
@ -13,14 +13,14 @@ import (
|
|||
"github.com/ethereum/go-ethereum/log"
|
||||
|
||||
"github.com/status-im/status-go/geth/account"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
"github.com/status-im/status-go/geth/rpc"
|
||||
"github.com/status-im/status-go/sign"
|
||||
)
|
||||
|
||||
const (
|
||||
// sendTxTimeout defines how many seconds to wait before returning result in sentTransaction().
|
||||
sendTxTimeout = 300 * time.Second
|
||||
rpcCallTimeout = time.Minute
|
||||
sendTxTimeout = 300 * time.Second
|
||||
|
||||
defaultGas = 90000
|
||||
)
|
||||
|
@ -47,7 +47,6 @@ func NewTransactor(signRequests *sign.PendingRequests) *Transactor {
|
|||
pendingSignRequests: signRequests,
|
||||
addrLock: &AddrLocker{},
|
||||
sendTxTimeout: sendTxTimeout,
|
||||
rpcCallTimeout: rpcCallTimeout,
|
||||
localNonce: sync.Map{},
|
||||
log: log.New("package", "status-go/geth/transactions.Manager"),
|
||||
}
|
||||
|
@ -58,12 +57,13 @@ func (t *Transactor) SetNetworkID(networkID uint64) {
|
|||
t.networkID = networkID
|
||||
}
|
||||
|
||||
// SetRPCClient an RPC client.
|
||||
func (t *Transactor) SetRPCClient(rpcClient *rpc.Client) {
|
||||
// SetRPC sets RPC params, a client and a timeout
|
||||
func (t *Transactor) SetRPC(rpcClient *rpc.Client, timeout time.Duration) {
|
||||
rpcWrapper := newRPCWrapper(rpcClient)
|
||||
t.sender = rpcWrapper
|
||||
t.pendingNonceProvider = rpcWrapper
|
||||
t.gasCalculator = rpcWrapper
|
||||
t.rpcCallTimeout = timeout
|
||||
}
|
||||
|
||||
// SendTransaction is an implementation of eth_sendTransaction. It queues the tx to the sign queue.
|
||||
|
@ -72,18 +72,19 @@ func (t *Transactor) SendTransaction(ctx context.Context, args SendTxArgs) (geth
|
|||
ctx = context.Background()
|
||||
}
|
||||
|
||||
completeFunc := func(acc *account.SelectedExtKey) (gethcommon.Hash, error) {
|
||||
return t.validateAndPropagate(acc, args)
|
||||
completeFunc := func(acc *account.SelectedExtKey, password string) (sign.Response, error) {
|
||||
hash, err := t.validateAndPropagate(acc, args)
|
||||
return sign.Response(hash.Bytes()), err
|
||||
}
|
||||
|
||||
request, err := t.pendingSignRequests.Add(ctx, args, completeFunc)
|
||||
request, err := t.pendingSignRequests.Add(ctx, params.SendTransactionMethodName, args, completeFunc)
|
||||
|
||||
if err != nil {
|
||||
return gethcommon.Hash{}, err
|
||||
}
|
||||
|
||||
rst := t.pendingSignRequests.Wait(request.ID, t.sendTxTimeout)
|
||||
return rst.Hash, rst.Error
|
||||
result := t.pendingSignRequests.Wait(request.ID, t.sendTxTimeout)
|
||||
return result.Response.Hash(), result.Error
|
||||
}
|
||||
|
||||
// make sure that only account which created the tx can complete it
|
||||
|
@ -93,7 +94,7 @@ func (t *Transactor) validateAccount(args SendTxArgs, selectedAccount *account.S
|
|||
}
|
||||
|
||||
if args.From.Hex() != selectedAccount.Address.Hex() {
|
||||
err := sign.ErrInvalidCompleteTxSender
|
||||
err := sign.NewTransientError(ErrInvalidCompleteTxSender)
|
||||
t.log.Error("queued transaction does not belong to the selected account", "err", err)
|
||||
return err
|
||||
}
|
||||
|
@ -102,8 +103,6 @@ func (t *Transactor) validateAccount(args SendTxArgs, selectedAccount *account.S
|
|||
}
|
||||
|
||||
func (t *Transactor) validateAndPropagate(selectedAccount *account.SelectedExtKey, args SendTxArgs) (hash gethcommon.Hash, err error) {
|
||||
// TODO (mandrigin): Send sign request ID as a parameter to this function and uncoment the log message
|
||||
// m.log.Info("complete transaction", "id", queuedTx.ID)
|
||||
if err := t.validateAccount(args, selectedAccount); err != nil {
|
||||
return hash, err
|
||||
}
|
||||
|
|
|
@ -65,9 +65,8 @@ func (s *TxQueueTestSuite) SetupTest() {
|
|||
|
||||
s.manager = NewTransactor(sign.NewPendingRequests())
|
||||
s.manager.sendTxTimeout = time.Second
|
||||
s.manager.rpcCallTimeout = time.Second
|
||||
s.manager.SetNetworkID(chainID)
|
||||
s.manager.SetRPCClient(rpcClient)
|
||||
s.manager.SetRPC(rpcClient, time.Second)
|
||||
}
|
||||
|
||||
func (s *TxQueueTestSuite) TearDownTest() {
|
||||
|
@ -168,10 +167,7 @@ func (s *TxQueueTestSuite) TestCompleteTransaction() {
|
|||
s.setupTransactionPoolAPI(args, testNonce, testNonce, selectedAccount, nil)
|
||||
|
||||
w := make(chan struct{})
|
||||
var (
|
||||
sendHash gethcommon.Hash
|
||||
err error
|
||||
)
|
||||
var sendHash gethcommon.Hash
|
||||
go func() {
|
||||
var sendErr error
|
||||
sendHash, sendErr = s.manager.SendTransaction(context.Background(), args)
|
||||
|
@ -188,13 +184,13 @@ func (s *TxQueueTestSuite) TestCompleteTransaction() {
|
|||
|
||||
req := s.manager.pendingSignRequests.First()
|
||||
s.NotNil(req)
|
||||
approveHash, err := s.manager.pendingSignRequests.Approve(req.ID, "", simpleVerifyFunc(selectedAccount))
|
||||
s.NoError(err)
|
||||
approveResult := s.manager.pendingSignRequests.Approve(req.ID, "", 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, approveHash)
|
||||
s.Equal(sendHash.Bytes(), approveResult.Response.Bytes())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -222,8 +218,8 @@ func (s *TxQueueTestSuite) TestAccountMismatch() {
|
|||
|
||||
req := s.manager.pendingSignRequests.First()
|
||||
s.NotNil(req)
|
||||
_, err := s.manager.pendingSignRequests.Approve(req.ID, "", simpleVerifyFunc(selectedAccount))
|
||||
s.Equal(err, sign.ErrInvalidCompleteTxSender)
|
||||
result := s.manager.pendingSignRequests.Approve(req.ID, "", simpleVerifyFunc(selectedAccount))
|
||||
s.EqualError(result.Error, ErrInvalidCompleteTxSender.Error())
|
||||
|
||||
// Transaction should stay in the queue as mismatched accounts
|
||||
// is a recoverable error.
|
||||
|
|
|
@ -16,6 +16,9 @@ var (
|
|||
ErrInvalidSendTxArgs = errors.New("Transaction arguments are invalid (are both 'input' and 'data' fields used?)")
|
||||
// ErrUnexpectedArgs 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")
|
||||
)
|
||||
|
||||
// PendingNonceProvider provides information about nonces.
|
||||
|
|
|
@ -200,40 +200,40 @@ func Logout() *C.char {
|
|||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
//CompleteTransaction instructs backend to complete sending of a given transaction
|
||||
//export CompleteTransaction
|
||||
func CompleteTransaction(id, password *C.char) *C.char {
|
||||
txHash, err := statusAPI.CompleteTransaction(C.GoString(id), C.GoString(password))
|
||||
//ApproveSignRequest instructs backend to complete sending of a given transaction
|
||||
//export ApproveSignRequest
|
||||
func ApproveSignRequest(id, password *C.char) *C.char {
|
||||
result := statusAPI.ApproveSignRequest(C.GoString(id), C.GoString(password))
|
||||
|
||||
errString := ""
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
errString = err.Error()
|
||||
if result.Error != nil {
|
||||
fmt.Fprintln(os.Stderr, result.Error)
|
||||
errString = result.Error.Error()
|
||||
}
|
||||
|
||||
out := CompleteTransactionResult{
|
||||
out := SignRequestResult{
|
||||
ID: C.GoString(id),
|
||||
Hash: txHash.Hex(),
|
||||
Hash: result.Response.Hex(),
|
||||
Error: errString,
|
||||
}
|
||||
outBytes, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
logger.Error("failed to marshal CompleteTransaction output", "error", err)
|
||||
logger.Error("failed to marshal ApproveSignRequest output", "error", err)
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
return C.CString(string(outBytes))
|
||||
}
|
||||
|
||||
//CompleteTransactions instructs backend to complete sending of multiple transactions
|
||||
//export CompleteTransactions
|
||||
func CompleteTransactions(ids, password *C.char) *C.char {
|
||||
out := CompleteTransactionsResult{}
|
||||
out.Results = make(map[string]CompleteTransactionResult)
|
||||
//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"] = CompleteTransactionResult{
|
||||
out.Results["none"] = SignRequestResult{
|
||||
Error: err.Error(),
|
||||
}
|
||||
} else {
|
||||
|
@ -242,11 +242,11 @@ func CompleteTransactions(ids, password *C.char) *C.char {
|
|||
txIDs[i] = id
|
||||
}
|
||||
|
||||
results := statusAPI.CompleteTransactions(txIDs, C.GoString(password))
|
||||
results := statusAPI.ApproveSignRequests(txIDs, C.GoString(password))
|
||||
for txID, result := range results {
|
||||
txResult := CompleteTransactionResult{
|
||||
txResult := SignRequestResult{
|
||||
ID: txID,
|
||||
Hash: result.Hash.Hex(),
|
||||
Hash: result.Response.Hex(),
|
||||
}
|
||||
if result.Error != nil {
|
||||
txResult.Error = result.Error.Error()
|
||||
|
@ -257,17 +257,17 @@ func CompleteTransactions(ids, password *C.char) *C.char {
|
|||
|
||||
outBytes, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
logger.Error("failed to marshal CompleteTransactions output", "error", err)
|
||||
logger.Error("failed to marshal ApproveSignRequests output", "error", err)
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
return C.CString(string(outBytes))
|
||||
}
|
||||
|
||||
//DiscardTransaction discards a given transaction from transaction queue
|
||||
//export DiscardTransaction
|
||||
func DiscardTransaction(id *C.char) *C.char {
|
||||
err := statusAPI.DiscardTransaction(C.GoString(id))
|
||||
//DiscardSignRequest discards a given transaction from transaction queue
|
||||
//export DiscardSignRequest
|
||||
func DiscardSignRequest(id *C.char) *C.char {
|
||||
err := statusAPI.DiscardSignRequest(C.GoString(id))
|
||||
|
||||
errString := ""
|
||||
if err != nil {
|
||||
|
@ -275,28 +275,28 @@ func DiscardTransaction(id *C.char) *C.char {
|
|||
errString = err.Error()
|
||||
}
|
||||
|
||||
out := DiscardTransactionResult{
|
||||
out := DiscardSignRequestResult{
|
||||
ID: C.GoString(id),
|
||||
Error: errString,
|
||||
}
|
||||
outBytes, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
log.Error("failed to marshal DiscardTransaction output", "error", err)
|
||||
log.Error("failed to marshal DiscardSignRequest output", "error", err)
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
return C.CString(string(outBytes))
|
||||
}
|
||||
|
||||
//DiscardTransactions discards given multiple transactions from transaction queue
|
||||
//export DiscardTransactions
|
||||
func DiscardTransactions(ids *C.char) *C.char {
|
||||
out := DiscardTransactionsResult{}
|
||||
out.Results = make(map[string]DiscardTransactionResult)
|
||||
//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"] = DiscardTransactionResult{
|
||||
out.Results["none"] = DiscardSignRequestResult{
|
||||
Error: err.Error(),
|
||||
}
|
||||
} else {
|
||||
|
@ -305,9 +305,9 @@ func DiscardTransactions(ids *C.char) *C.char {
|
|||
txIDs[i] = id
|
||||
}
|
||||
|
||||
results := statusAPI.DiscardTransactions(txIDs)
|
||||
results := statusAPI.DiscardSignRequests(txIDs)
|
||||
for txID, err := range results {
|
||||
out.Results[txID] = DiscardTransactionResult{
|
||||
out.Results[txID] = DiscardSignRequestResult{
|
||||
ID: txID,
|
||||
Error: err.Error(),
|
||||
}
|
||||
|
@ -316,7 +316,7 @@ func DiscardTransactions(ids *C.char) *C.char {
|
|||
|
||||
outBytes, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
logger.Error("failed to marshal DiscardTransactions output", "error", err)
|
||||
logger.Error("failed to marshal DiscardSignRequests output", "error", err)
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
|
|
|
@ -39,14 +39,16 @@ import (
|
|||
. "github.com/status-im/status-go/t/utils" //nolint: golint
|
||||
)
|
||||
|
||||
const zeroHash = "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
const initJS = `
|
||||
var _status_catalog = {
|
||||
foo: 'bar'
|
||||
};`
|
||||
|
||||
var testChainDir string
|
||||
var nodeConfigJSON string
|
||||
var (
|
||||
zeroHash = sign.EmptyResponse.Hex()
|
||||
testChainDir string
|
||||
nodeConfigJSON string
|
||||
)
|
||||
|
||||
func init() {
|
||||
testChainDir = filepath.Join(TestDataDir, TestNetworkNames[GetNetworkID()])
|
||||
|
@ -822,12 +824,12 @@ func testCompleteTransaction(t *testing.T) bool {
|
|||
t.Errorf("cannot unmarshal event's JSON: %s. Error %q", jsonEvent, err)
|
||||
return
|
||||
}
|
||||
if envelope.Type == sign.EventTransactionQueued {
|
||||
if envelope.Type == sign.EventSignRequestAdded {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
t.Logf("transaction queued (will be completed shortly): {id: %s}\n", event["id"].(string))
|
||||
|
||||
completeTxResponse := CompleteTransactionResult{}
|
||||
rawResponse := CompleteTransaction(C.CString(event["id"].(string)), C.CString(TestConfig.Account1.Password))
|
||||
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)
|
||||
|
@ -899,7 +901,7 @@ func testCompleteMultipleQueuedTransactions(t *testing.T) bool { //nolint: gocyc
|
|||
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
||||
return
|
||||
}
|
||||
if envelope.Type == sign.EventTransactionQueued {
|
||||
if envelope.Type == sign.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)
|
||||
|
@ -938,8 +940,8 @@ func testCompleteMultipleQueuedTransactions(t *testing.T) bool { //nolint: gocyc
|
|||
updatedTxIDStrings, _ := json.Marshal(parsedIDs)
|
||||
|
||||
// complete
|
||||
resultsString := CompleteTransactions(C.CString(string(updatedTxIDStrings)), C.CString(TestConfig.Account1.Password))
|
||||
resultsStruct := CompleteTransactionsResult{}
|
||||
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
|
||||
|
@ -1031,7 +1033,7 @@ func testDiscardTransaction(t *testing.T) bool { //nolint: gocyclo
|
|||
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
||||
return
|
||||
}
|
||||
if envelope.Type == sign.EventTransactionQueued {
|
||||
if envelope.Type == sign.EventSignRequestAdded {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
txID = event["id"].(string)
|
||||
t.Logf("transaction queued (will be discarded soon): {id: %s}\n", txID)
|
||||
|
@ -1042,8 +1044,8 @@ func testDiscardTransaction(t *testing.T) bool { //nolint: gocyclo
|
|||
}
|
||||
|
||||
// discard
|
||||
discardResponse := DiscardTransactionResult{}
|
||||
rawResponse := DiscardTransaction(C.CString(txID))
|
||||
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)
|
||||
|
@ -1055,7 +1057,7 @@ func testDiscardTransaction(t *testing.T) bool { //nolint: gocyclo
|
|||
}
|
||||
|
||||
// try completing discarded transaction
|
||||
_, err := statusAPI.CompleteTransaction(string(txID), TestConfig.Account1.Password)
|
||||
err := statusAPI.ApproveSignRequest(string(txID), TestConfig.Account1.Password).Error
|
||||
if err != sign.ErrSignReqNotFound {
|
||||
t.Error("expects tx not found, but call to CompleteTransaction succeeded")
|
||||
return
|
||||
|
@ -1069,7 +1071,7 @@ func testDiscardTransaction(t *testing.T) bool { //nolint: gocyclo
|
|||
completeQueuedTransaction <- struct{}{} // so that timeout is aborted
|
||||
}
|
||||
|
||||
if envelope.Type == sign.EventTransactionFailed {
|
||||
if envelope.Type == sign.EventSignRequestFailed {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
t.Logf("transaction return event received: {id: %s}\n", event["id"].(string))
|
||||
|
||||
|
@ -1081,7 +1083,7 @@ func testDiscardTransaction(t *testing.T) bool { //nolint: gocyclo
|
|||
}
|
||||
|
||||
receivedErrCode := event["error_code"].(string)
|
||||
if receivedErrCode != strconv.Itoa(sign.SendTransactionDiscardedErrorCode) {
|
||||
if receivedErrCode != strconv.Itoa(sign.SignRequestDiscardedErrorCode) {
|
||||
t.Errorf("unexpected error code received: got %v", receivedErrCode)
|
||||
return
|
||||
}
|
||||
|
@ -1143,7 +1145,7 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool { //nolint: gocycl
|
|||
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
||||
return
|
||||
}
|
||||
if envelope.Type == sign.EventTransactionQueued {
|
||||
if envelope.Type == sign.EventSignRequestAdded {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
txID = event["id"].(string)
|
||||
t.Logf("transaction queued (will be discarded soon): {id: %s}\n", txID)
|
||||
|
@ -1156,7 +1158,7 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool { //nolint: gocycl
|
|||
txIDs <- txID
|
||||
}
|
||||
|
||||
if envelope.Type == sign.EventTransactionFailed {
|
||||
if envelope.Type == sign.EventSignRequestFailed {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
t.Logf("transaction return event received: {id: %s}\n", event["id"].(string))
|
||||
|
||||
|
@ -1168,7 +1170,7 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool { //nolint: gocycl
|
|||
}
|
||||
|
||||
receivedErrCode := event["error_code"].(string)
|
||||
if receivedErrCode != strconv.Itoa(sign.SendTransactionDiscardedErrorCode) {
|
||||
if receivedErrCode != strconv.Itoa(sign.SignRequestDiscardedErrorCode) {
|
||||
t.Errorf("unexpected error code received: got %v", receivedErrCode)
|
||||
return
|
||||
}
|
||||
|
@ -1210,8 +1212,8 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool { //nolint: gocycl
|
|||
updatedTxIDStrings, _ := json.Marshal(parsedIDs)
|
||||
|
||||
// discard
|
||||
discardResultsString := DiscardTransactions(C.CString(string(updatedTxIDStrings)))
|
||||
discardResultsStruct := DiscardTransactionsResult{}
|
||||
discardResultsString := DiscardSignRequests(C.CString(string(updatedTxIDStrings)))
|
||||
discardResultsStruct := DiscardSignRequestsResult{}
|
||||
if err := json.Unmarshal([]byte(C.GoString(discardResultsString)), &discardResultsStruct); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
|
@ -1224,8 +1226,8 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool { //nolint: gocycl
|
|||
}
|
||||
|
||||
// try completing discarded transaction
|
||||
completeResultsString := CompleteTransactions(C.CString(string(updatedTxIDStrings)), C.CString(TestConfig.Account1.Password))
|
||||
completeResultsStruct := CompleteTransactionsResult{}
|
||||
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
|
||||
|
@ -1233,7 +1235,7 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool { //nolint: gocycl
|
|||
completeResults := completeResultsStruct.Results
|
||||
|
||||
if len(completeResults) != (testTxCount + 1) {
|
||||
t.Error("unexpected number of errors (call to CompleteTransaction should not succeed)")
|
||||
t.Error("unexpected number of errors (call to ApproveSignRequest should not succeed)")
|
||||
}
|
||||
for txID, txResult := range completeResults {
|
||||
if txID != txResult.ID {
|
||||
|
@ -1245,7 +1247,7 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool { //nolint: gocycl
|
|||
return
|
||||
}
|
||||
if txResult.Hash != zeroHash {
|
||||
t.Errorf("invalid hash (expected zero): %s", txResult.Hash)
|
||||
t.Errorf("invalid hash (expected zero): '%s'", txResult.Hash)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -1457,7 +1459,7 @@ func startTestNode(t *testing.T) <-chan struct{} {
|
|||
return
|
||||
}
|
||||
|
||||
if envelope.Type == sign.EventTransactionQueued {
|
||||
if envelope.Type == sign.EventSignRequestAdded {
|
||||
}
|
||||
if envelope.Type == signal.EventNodeStarted {
|
||||
t.Log("Node started, but we wait till it be ready")
|
||||
|
|
20
lib/types.go
20
lib/types.go
|
@ -76,25 +76,25 @@ type NotifyResult struct {
|
|||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// CompleteTransactionResult is a JSON returned from transaction complete function (used in exposed method)
|
||||
type CompleteTransactionResult struct {
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// CompleteTransactionsResult is list of results from CompleteTransactions() (used in exposed method)
|
||||
type CompleteTransactionsResult struct {
|
||||
Results map[string]CompleteTransactionResult `json:"results"`
|
||||
// SignRequestsResult is list of results from CompleteTransactions() (used in exposed method)
|
||||
type SignRequestsResult struct {
|
||||
Results map[string]SignRequestResult `json:"results"`
|
||||
}
|
||||
|
||||
// DiscardTransactionResult is a JSON returned from transaction discard function
|
||||
type DiscardTransactionResult struct {
|
||||
// DiscardSignRequestResult is a JSON returned from transaction discard function
|
||||
type DiscardSignRequestResult struct {
|
||||
ID string `json:"id"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// DiscardTransactionsResult is a list of results from DiscardTransactions()
|
||||
type DiscardTransactionsResult struct {
|
||||
Results map[string]DiscardTransactionResult `json:"results"`
|
||||
// DiscardSignRequestsResult is a list of results from DiscardTransactions()
|
||||
type DiscardSignRequestsResult struct {
|
||||
Results map[string]DiscardSignRequestResult `json:"results"`
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# personal
|
||||
|
||||
This package contains Status integraton with `personal_*` RPC APIs more
|
||||
information on these APIs can be found on the Ethereum Wiki:
|
||||
https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal
|
||||
|
||||
In `web3.js` these methods are located in `web3.personal` namespace.
|
|
@ -0,0 +1,111 @@
|
|||
package personal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/status-im/status-go/geth/account"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
"github.com/status-im/status-go/geth/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"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// PublicAPI represents a set of APIs from the `web3.personal` namespace.
|
||||
type PublicAPI struct {
|
||||
pendingSignRequests *sign.PendingRequests
|
||||
rpcClient *rpc.Client
|
||||
rpcTimeout time.Duration
|
||||
}
|
||||
|
||||
// NewAPI creates an instance of the personal API.
|
||||
func NewAPI(pendingSignRequests *sign.PendingRequests) *PublicAPI {
|
||||
return &PublicAPI{
|
||||
pendingSignRequests: pendingSignRequests,
|
||||
}
|
||||
}
|
||||
|
||||
// SetRPC sets RPC params (client and timeout) for the API calls.
|
||||
func (api *PublicAPI) SetRPC(rpcClient *rpc.Client, timeout time.Duration) {
|
||||
api.rpcClient = rpcClient
|
||||
api.rpcTimeout = timeout
|
||||
}
|
||||
|
||||
// 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) (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)
|
||||
|
||||
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
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package personal
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/ethapi"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// Make sure that Service implements node.Service interface.
|
||||
var _ node.Service = (*Service)(nil)
|
||||
|
||||
// Service represents out own implementation of personal sign operations.
|
||||
type Service struct {
|
||||
am *accounts.Manager
|
||||
}
|
||||
|
||||
// New returns a new Service.
|
||||
func New(am *accounts.Manager) *Service {
|
||||
return &Service{am}
|
||||
}
|
||||
|
||||
// Protocols returns a new protocols list. In this case, there are none.
|
||||
func (s *Service) Protocols() []p2p.Protocol {
|
||||
return []p2p.Protocol{}
|
||||
}
|
||||
|
||||
// APIs returns a list of new APIs.
|
||||
func (s *Service) APIs() []rpc.API {
|
||||
return []rpc.API{
|
||||
{
|
||||
Namespace: "personal",
|
||||
Version: "1.0",
|
||||
Service: ethapi.NewLimitedPersonalAPI(s.am),
|
||||
Public: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Start is run when a service is started.
|
||||
// It does nothing in this case but is required by `node.Service` interface.
|
||||
func (s *Service) Start(server *p2p.Server) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop is run when a service is stopped.
|
||||
// It does nothing in this case but is required by `node.Service` interface.
|
||||
func (s *Service) Stop() error {
|
||||
return nil
|
||||
}
|
|
@ -7,30 +7,46 @@ import (
|
|||
"github.com/status-im/status-go/geth/account"
|
||||
)
|
||||
|
||||
// TODO (mandrigin): Change values of these errors when API change is made.
|
||||
var (
|
||||
//ErrSignReqNotFound - error transaction hash not found
|
||||
ErrSignReqNotFound = errors.New("transaction hash not found")
|
||||
//ErrSignReqInProgress - error transaction is in progress
|
||||
ErrSignReqInProgress = errors.New("transaction is in progress")
|
||||
// TODO (mandrigin): to be moved to `transactions` package
|
||||
//ErrInvalidCompleteTxSender - error transaction with invalid sender
|
||||
ErrInvalidCompleteTxSender = errors.New("transaction can only be completed by the same account which created it")
|
||||
//ErrSignReqTimedOut - error transaction sending timed out
|
||||
ErrSignReqTimedOut = errors.New("transaction sending timed out")
|
||||
//ErrSignReqDiscarded - error transaction discarded
|
||||
ErrSignReqDiscarded = errors.New("transaction has been discarded")
|
||||
//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
|
||||
ErrInvalidCompleteTxSender.Error(): true, // completing tx create from another account
|
||||
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
|
||||
}
|
||||
|
|
|
@ -8,23 +8,23 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// EventTransactionQueued is triggered when send transaction request is queued
|
||||
EventTransactionQueued = "transaction.queued"
|
||||
// EventTransactionFailed is triggered when send transaction request fails
|
||||
EventTransactionFailed = "transaction.failed"
|
||||
// EventSignRequestAdded is triggered when send transaction request is queued
|
||||
EventSignRequestAdded = "sign-request.queued"
|
||||
// EventSignRequestFailed is triggered when send transaction request fails
|
||||
EventSignRequestFailed = "sign-request.failed"
|
||||
)
|
||||
|
||||
const (
|
||||
// SendTransactionNoErrorCode is sent when no error occurred.
|
||||
SendTransactionNoErrorCode = iota
|
||||
// SendTransactionDefaultErrorCode is every case when there is no special tx return code.
|
||||
SendTransactionDefaultErrorCode
|
||||
// SendTransactionPasswordErrorCode is sent when account failed verification.
|
||||
SendTransactionPasswordErrorCode
|
||||
// SendTransactionTimeoutErrorCode is sent when tx is timed out.
|
||||
SendTransactionTimeoutErrorCode
|
||||
// SendTransactionDiscardedErrorCode is sent when tx was discarded.
|
||||
SendTransactionDiscardedErrorCode
|
||||
// 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
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -48,52 +48,55 @@ func messageIDFromContext(ctx context.Context) string {
|
|||
}
|
||||
|
||||
var txReturnCodes = map[error]int{
|
||||
nil: SendTransactionNoErrorCode,
|
||||
keystore.ErrDecrypt: SendTransactionPasswordErrorCode,
|
||||
ErrSignReqTimedOut: SendTransactionTimeoutErrorCode,
|
||||
ErrSignReqDiscarded: SendTransactionDiscardedErrorCode,
|
||||
nil: SignRequestNoErrorCode,
|
||||
keystore.ErrDecrypt: SignRequestPasswordErrorCode,
|
||||
ErrSignReqTimedOut: SignRequestTimeoutErrorCode,
|
||||
ErrSignReqDiscarded: SignRequestDiscardedErrorCode,
|
||||
}
|
||||
|
||||
// SendTransactionEvent is a signal sent on a send transaction request
|
||||
type SendTransactionEvent struct {
|
||||
// PendingRequestEvent is a signal sent when a sign request is added
|
||||
type PendingRequestEvent struct {
|
||||
ID string `json:"id"`
|
||||
Method string `json:"method"`
|
||||
Args interface{} `json:"args"`
|
||||
MessageID string `json:"message_id"`
|
||||
}
|
||||
|
||||
// NotifyOnEnqueue returns handler that processes incoming tx queue requests
|
||||
// NotifyOnEnqueue sends a signal when a sign request is added
|
||||
func NotifyOnEnqueue(request *Request) {
|
||||
signal.Send(signal.Envelope{
|
||||
Type: EventTransactionQueued,
|
||||
Event: SendTransactionEvent{
|
||||
Type: EventSignRequestAdded,
|
||||
Event: PendingRequestEvent{
|
||||
ID: request.ID,
|
||||
Args: request.Meta,
|
||||
Method: request.Method,
|
||||
MessageID: messageIDFromContext(request.context),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ReturnSendTransactionEvent is a JSON returned whenever transaction send is returned
|
||||
type ReturnSendTransactionEvent struct {
|
||||
ID string `json:"id"`
|
||||
Args interface{} `json:"args"`
|
||||
MessageID string `json:"message_id"`
|
||||
ErrorMessage string `json:"error_message"`
|
||||
ErrorCode int `json:"error_code,string"`
|
||||
// PendingRequestErrorEvent is a signal sent when sign request has failed
|
||||
type PendingRequestErrorEvent struct {
|
||||
PendingRequestEvent
|
||||
ErrorMessage string `json:"error_message"`
|
||||
ErrorCode int `json:"error_code,string"`
|
||||
}
|
||||
|
||||
// NotifyOnReturn returns handler that processes responses from internal tx manager
|
||||
func NotifyOnReturn(request *Request, err error) {
|
||||
// NotifyIfError sends a signal only if error had happened
|
||||
func NotifyIfError(request *Request, err error) {
|
||||
// we don't want to notify a user if tx was sent successfully
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
signal.Send(signal.Envelope{
|
||||
Type: EventTransactionFailed,
|
||||
Event: ReturnSendTransactionEvent{
|
||||
ID: request.ID,
|
||||
Args: request.Meta,
|
||||
MessageID: messageIDFromContext(request.context),
|
||||
Type: EventSignRequestFailed,
|
||||
Event: PendingRequestErrorEvent{
|
||||
PendingRequestEvent: PendingRequestEvent{
|
||||
ID: request.ID,
|
||||
Args: request.Meta,
|
||||
Method: request.Method,
|
||||
MessageID: messageIDFromContext(request.context),
|
||||
},
|
||||
ErrorMessage: err.Error(),
|
||||
ErrorCode: sendTransactionErrorCode(err),
|
||||
},
|
||||
|
@ -104,5 +107,5 @@ func sendTransactionErrorCode(err error) int {
|
|||
if code, ok := txReturnCodes[err]; ok {
|
||||
return code
|
||||
}
|
||||
return SendTransactionDefaultErrorCode
|
||||
return SignRequestDefaultErrorCode
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/status-im/status-go/geth/account"
|
||||
)
|
||||
|
@ -31,11 +30,11 @@ func NewPendingRequests() *PendingRequests {
|
|||
}
|
||||
|
||||
// Add a new signing request.
|
||||
func (rs *PendingRequests) Add(ctx context.Context, meta Meta, completeFunc completeFunc) (*Request, error) {
|
||||
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, meta, completeFunc)
|
||||
request := newRequest(ctx, method, meta, completeFunc)
|
||||
rs.requests[request.ID] = request
|
||||
rs.log.Info("signing request is created", "ID", request.ID)
|
||||
|
||||
|
@ -68,26 +67,29 @@ func (rs *PendingRequests) First() *Request {
|
|||
}
|
||||
|
||||
// Approve a signing request by it's ID. Requires a valid password and a verification function.
|
||||
func (rs *PendingRequests) Approve(id string, password string, verify verifyFunc) (hash gethcommon.Hash, err error) {
|
||||
rs.log.Info("complete transaction", "id", id)
|
||||
func (rs *PendingRequests) Approve(id string, password string, 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 hash, err
|
||||
return newErrResult(err)
|
||||
}
|
||||
|
||||
selectedAccount, err := verify(password)
|
||||
if err != nil {
|
||||
rs.complete(request, hash, err)
|
||||
return hash, err
|
||||
rs.complete(request, EmptyResponse, err)
|
||||
return newErrResult(err)
|
||||
}
|
||||
|
||||
hash, err = request.completeFunc(selectedAccount)
|
||||
rs.log.Info("finally completed transaction", "id", request.ID, "hash", hash, "err", err)
|
||||
response, err := request.completeFunc(selectedAccount, password)
|
||||
rs.log.Info("completed sign request ", "id", request.ID, "response", response, "err", err)
|
||||
|
||||
rs.complete(request, hash, err)
|
||||
rs.complete(request, response, err)
|
||||
|
||||
return hash, err
|
||||
return Result{
|
||||
Response: response,
|
||||
Error: err,
|
||||
}
|
||||
}
|
||||
|
||||
// Discard remove a signing request from the list of pending requests.
|
||||
|
@ -97,7 +99,7 @@ func (rs *PendingRequests) Discard(id string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
rs.complete(request, gethcommon.Hash{}, ErrSignReqDiscarded)
|
||||
rs.complete(request, EmptyResponse, ErrSignReqDiscarded)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -105,14 +107,14 @@ func (rs *PendingRequests) Discard(id string) error {
|
|||
func (rs *PendingRequests) Wait(id string, timeout time.Duration) Result {
|
||||
request, err := rs.Get(id)
|
||||
if err != nil {
|
||||
return Result{Error: err}
|
||||
return newErrResult(err)
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case rst := <-request.result:
|
||||
return rst
|
||||
case <-time.After(timeout):
|
||||
rs.complete(request, gethcommon.Hash{}, ErrSignReqTimedOut)
|
||||
rs.complete(request, EmptyResponse, ErrSignReqTimedOut)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -148,13 +150,13 @@ func (rs *PendingRequests) tryLock(id string) (*Request, error) {
|
|||
}
|
||||
|
||||
// complete removes the request from the list if there is no error or an error is non-transient
|
||||
func (rs *PendingRequests) complete(request *Request, hash gethcommon.Hash, err error) {
|
||||
func (rs *PendingRequests) complete(request *Request, response Response, err error) {
|
||||
rs.mu.Lock()
|
||||
defer rs.mu.Unlock()
|
||||
|
||||
request.locked = false
|
||||
|
||||
go NotifyOnReturn(request, err)
|
||||
go NotifyIfError(request, err)
|
||||
|
||||
if err != nil && isTransient(err) {
|
||||
return
|
||||
|
@ -162,10 +164,10 @@ func (rs *PendingRequests) complete(request *Request, hash gethcommon.Hash, err
|
|||
|
||||
delete(rs.requests, request.ID)
|
||||
|
||||
// hash is updated only if err is nil, but transaction is not removed from a queue
|
||||
// response is updated only if err is nil, but transaction is not removed from a queue
|
||||
result := Result{Error: err}
|
||||
if err == nil {
|
||||
result.Hash = hash
|
||||
result.Response = response
|
||||
}
|
||||
|
||||
request.result <- result
|
||||
|
|
|
@ -39,33 +39,35 @@ func (s *PendingRequestsSuite) SetupTest() {
|
|||
s.pendingRequests = NewPendingRequests()
|
||||
}
|
||||
|
||||
func (s *PendingRequestsSuite) defaultCompleteFunc() completeFunc {
|
||||
func (s *PendingRequestsSuite) defaultCompleteFunc() CompleteFunc {
|
||||
hash := gethcommon.Hash{1}
|
||||
return func(acc *account.SelectedExtKey) (gethcommon.Hash, error) {
|
||||
return func(acc *account.SelectedExtKey, password string) (Response, error) {
|
||||
s.Nil(acc, "account should be `nil`")
|
||||
return hash, nil
|
||||
s.Equal(correctPassword, password)
|
||||
return hash.Bytes(), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PendingRequestsSuite) delayedCompleteFunc() completeFunc {
|
||||
func (s *PendingRequestsSuite) delayedCompleteFunc() CompleteFunc {
|
||||
hash := gethcommon.Hash{1}
|
||||
return func(acc *account.SelectedExtKey) (gethcommon.Hash, error) {
|
||||
return func(acc *account.SelectedExtKey, password string) (Response, error) {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
s.Nil(acc, "account should be `nil`")
|
||||
return hash, nil
|
||||
s.Equal(correctPassword, password)
|
||||
return hash.Bytes(), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PendingRequestsSuite) errorCompleteFunc(err error) completeFunc {
|
||||
func (s *PendingRequestsSuite) errorCompleteFunc(err error) CompleteFunc {
|
||||
hash := gethcommon.Hash{1}
|
||||
return func(acc *account.SelectedExtKey) (gethcommon.Hash, error) {
|
||||
return func(acc *account.SelectedExtKey, password string) (Response, error) {
|
||||
s.Nil(acc, "account should be `nil`")
|
||||
return hash, err
|
||||
return hash.Bytes(), err
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PendingRequestsSuite) TestGet() {
|
||||
req, err := s.pendingRequests.Add(context.Background(), nil, s.defaultCompleteFunc())
|
||||
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)
|
||||
|
@ -74,16 +76,22 @@ func (s *PendingRequestsSuite) TestGet() {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *PendingRequestsSuite) testComplete(password string, hash gethcommon.Hash, completeFunc completeFunc) (string, error) {
|
||||
req, err := s.pendingRequests.Add(context.Background(), nil, completeFunc)
|
||||
func (s *PendingRequestsSuite) testComplete(password string, hash gethcommon.Hash, completeFunc CompleteFunc) (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")
|
||||
|
||||
hash2, err := s.pendingRequests.Approve(req.ID, password, testVerifyFunc)
|
||||
s.Equal(hash, hash2, "hashes should match")
|
||||
result := s.pendingRequests.Approve(req.ID, password, testVerifyFunc)
|
||||
|
||||
return req.ID, err
|
||||
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() {
|
||||
|
@ -119,13 +127,13 @@ func (s PendingRequestsSuite) TestMultipleComplete() {
|
|||
id, err := s.testComplete(correctPassword, gethcommon.Hash{1}, s.defaultCompleteFunc())
|
||||
s.NoError(err, "no errors should be there")
|
||||
|
||||
_, err = s.pendingRequests.Approve(id, correctPassword, testVerifyFunc)
|
||||
result := s.pendingRequests.Approve(id, correctPassword, testVerifyFunc)
|
||||
|
||||
s.Equal(ErrSignReqNotFound, err)
|
||||
s.Equal(ErrSignReqNotFound, result.Error)
|
||||
}
|
||||
|
||||
func (s PendingRequestsSuite) TestConcurrentComplete() {
|
||||
req, err := s.pendingRequests.Add(context.Background(), nil, s.delayedCompleteFunc())
|
||||
req, err := s.pendingRequests.Add(context.Background(), "", nil, s.delayedCompleteFunc())
|
||||
s.NoError(err)
|
||||
|
||||
s.True(s.pendingRequests.Has(req.ID), "sign request should exist")
|
||||
|
@ -135,8 +143,8 @@ func (s PendingRequestsSuite) TestConcurrentComplete() {
|
|||
|
||||
for i := 10; i > 0; i-- {
|
||||
go func() {
|
||||
_, err = s.pendingRequests.Approve(req.ID, correctPassword, testVerifyFunc)
|
||||
if err == nil {
|
||||
result := s.pendingRequests.Approve(req.ID, correctPassword, testVerifyFunc)
|
||||
if result.Error == nil {
|
||||
approved++
|
||||
}
|
||||
tried++
|
||||
|
@ -152,14 +160,14 @@ func (s PendingRequestsSuite) TestConcurrentComplete() {
|
|||
}
|
||||
|
||||
func (s PendingRequestsSuite) TestWaitSuccess() {
|
||||
req, err := s.pendingRequests.Add(context.Background(), nil, s.defaultCompleteFunc())
|
||||
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() {
|
||||
_, err := s.pendingRequests.Approve(req.ID, correctPassword, testVerifyFunc)
|
||||
s.NoError(err)
|
||||
result := s.pendingRequests.Approve(req.ID, correctPassword, testVerifyFunc)
|
||||
s.NoError(result.Error)
|
||||
}()
|
||||
|
||||
result := s.pendingRequests.Wait(req.ID, 1*time.Second)
|
||||
|
@ -167,7 +175,7 @@ func (s PendingRequestsSuite) TestWaitSuccess() {
|
|||
}
|
||||
|
||||
func (s PendingRequestsSuite) TestDiscard() {
|
||||
req, err := s.pendingRequests.Add(context.Background(), nil, s.defaultCompleteFunc())
|
||||
req, err := s.pendingRequests.Add(context.Background(), "", nil, s.defaultCompleteFunc())
|
||||
s.NoError(err)
|
||||
|
||||
s.True(s.pendingRequests.Has(req.ID), "sign request should exist")
|
||||
|
@ -186,14 +194,14 @@ func (s PendingRequestsSuite) TestDiscard() {
|
|||
|
||||
func (s PendingRequestsSuite) TestWaitFail() {
|
||||
expectedError := errors.New("test-wait-fail")
|
||||
req, err := s.pendingRequests.Add(context.Background(), nil, s.errorCompleteFunc(expectedError))
|
||||
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() {
|
||||
_, err := s.pendingRequests.Approve(req.ID, correctPassword, testVerifyFunc)
|
||||
s.Equal(expectedError, err)
|
||||
result := s.pendingRequests.Approve(req.ID, correctPassword, testVerifyFunc)
|
||||
s.Equal(expectedError, result.Error)
|
||||
}()
|
||||
|
||||
result := s.pendingRequests.Wait(req.ID, 1*time.Second)
|
||||
|
@ -201,14 +209,14 @@ func (s PendingRequestsSuite) TestWaitFail() {
|
|||
}
|
||||
|
||||
func (s PendingRequestsSuite) TestWaitTimeout() {
|
||||
req, err := s.pendingRequests.Add(context.Background(), nil, s.delayedCompleteFunc())
|
||||
req, err := s.pendingRequests.Add(context.Background(), "", nil, s.delayedCompleteFunc())
|
||||
s.NoError(err)
|
||||
|
||||
s.True(s.pendingRequests.Has(req.ID), "sign request should exist")
|
||||
|
||||
go func() {
|
||||
_, err := s.pendingRequests.Approve(req.ID, correctPassword, testVerifyFunc)
|
||||
s.NoError(err)
|
||||
result := s.pendingRequests.Approve(req.ID, correctPassword, testVerifyFunc)
|
||||
s.NoError(result.Error)
|
||||
}()
|
||||
|
||||
result := s.pendingRequests.Wait(req.ID, 0*time.Second)
|
||||
|
|
|
@ -3,12 +3,12 @@ package sign
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/pborman/uuid"
|
||||
"github.com/status-im/status-go/geth/account"
|
||||
)
|
||||
|
||||
type completeFunc func(*account.SelectedExtKey) (common.Hash, error)
|
||||
// CompleteFunc is a function that is called after the sign request is approved.
|
||||
type CompleteFunc func(account *account.SelectedExtKey, password string) (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.
|
||||
|
@ -17,16 +17,18 @@ 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
|
||||
completeFunc CompleteFunc
|
||||
result chan Result
|
||||
}
|
||||
|
||||
func newRequest(ctx context.Context, meta Meta, completeFunc completeFunc) *Request {
|
||||
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,
|
||||
|
|
|
@ -1,9 +1,41 @@
|
|||
package sign
|
||||
|
||||
import "github.com/ethereum/go-ethereum/common"
|
||||
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 {
|
||||
Hash common.Hash
|
||||
Error error
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -135,26 +135,26 @@ func (s *APIBackendTestSuite) TestRaceConditions() {
|
|||
progress <- struct{}{}
|
||||
},
|
||||
func(config *params.NodeConfig) {
|
||||
log.Info("CompleteTransaction()")
|
||||
_, err := s.Backend.CompleteTransaction("id", "password")
|
||||
s.T().Logf("CompleteTransaction(), error: %v", err)
|
||||
log.Info("ApproveSignRequest()")
|
||||
result := s.Backend.ApproveSignRequest("id", "password")
|
||||
s.T().Logf("ApproveSignRequest(), error: %v", result.Error)
|
||||
progress <- struct{}{}
|
||||
},
|
||||
func(config *params.NodeConfig) {
|
||||
log.Info("DiscardTransaction()")
|
||||
s.T().Logf("DiscardTransaction(), error: %v", s.Backend.DiscardTransaction("id"))
|
||||
log.Info("DiscardSignRequest()")
|
||||
s.T().Logf("DiscardSignRequest(), error: %v", s.Backend.DiscardSignRequest("id"))
|
||||
progress <- struct{}{}
|
||||
},
|
||||
func(config *params.NodeConfig) {
|
||||
log.Info("CompleteTransactions()")
|
||||
log.Info("ApproveSignRequests()")
|
||||
ids := []string{"id1", "id2"}
|
||||
s.T().Logf("CompleteTransactions(), result: %v", s.Backend.CompleteTransactions(ids, "password"))
|
||||
s.T().Logf("ApproveSignRequests(), result: %v", s.Backend.ApproveSignRequests(ids, "password"))
|
||||
progress <- struct{}{}
|
||||
},
|
||||
func(config *params.NodeConfig) {
|
||||
log.Info("DiscardTransactions()")
|
||||
log.Info("DiscardSignRequests()")
|
||||
ids := []string{"id1", "id2"}
|
||||
s.T().Logf("DiscardTransactions(), result: %v", s.Backend.DiscardTransactions(ids))
|
||||
s.T().Logf("DiscardSignRequests(), result: %v", s.Backend.DiscardSignRequests(ids))
|
||||
progress <- struct{}{}
|
||||
},
|
||||
}
|
||||
|
|
|
@ -126,16 +126,16 @@ func (s *JailRPCTestSuite) TestContractDeployment() {
|
|||
unmarshalErr := json.Unmarshal([]byte(jsonEvent), &envelope)
|
||||
s.NoError(unmarshalErr, "cannot unmarshal JSON: %s", jsonEvent)
|
||||
|
||||
if envelope.Type == sign.EventTransactionQueued {
|
||||
if envelope.Type == sign.EventSignRequestAdded {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
s.T().Logf("transaction queued and will be completed shortly, id: %v", event["id"])
|
||||
|
||||
s.NoError(s.Backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
|
||||
|
||||
txID := event["id"].(string)
|
||||
var txErr error
|
||||
txHash, txErr = s.Backend.CompleteTransaction(txID, TestConfig.Account1.Password)
|
||||
if s.NoError(txErr, event["id"]) {
|
||||
result := s.Backend.ApproveSignRequest(txID, TestConfig.Account1.Password)
|
||||
txHash.SetBytes(result.Response.Bytes())
|
||||
if s.NoError(result.Error, event["id"]) {
|
||||
s.T().Logf("contract transaction complete, URL: %s", "https://ropsten.etherscan.io/tx/"+txHash.Hex())
|
||||
}
|
||||
|
||||
|
@ -284,14 +284,16 @@ func (s *JailRPCTestSuite) TestJailVMPersistence() {
|
|||
s.T().Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
||||
return
|
||||
}
|
||||
if envelope.Type == sign.EventTransactionQueued {
|
||||
if envelope.Type == sign.EventSignRequestAdded {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
s.T().Logf("Transaction queued (will be completed shortly): {id: %s}\n", event["id"].(string))
|
||||
|
||||
//var txHash common.Hash
|
||||
var txHash gethcommon.Hash
|
||||
txID := event["id"].(string)
|
||||
txHash, e := s.Backend.CompleteTransaction(txID, TestConfig.Account1.Password)
|
||||
s.NoError(e, "cannot complete queued transaction[%v]: %v", event["id"], e)
|
||||
result := s.Backend.ApproveSignRequest(txID, TestConfig.Account1.Password)
|
||||
s.NoError(result.Error, "cannot complete queued transaction[%v]: %v", event["id"], result.Error)
|
||||
|
||||
txHash.SetBytes(result.Response.Bytes())
|
||||
|
||||
s.T().Logf("Transaction complete: https://ropsten.etherscan.io/tx/%s", txHash.Hex())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
acc "github.com/status-im/status-go/geth/account"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
"github.com/status-im/status-go/geth/signal"
|
||||
"github.com/status-im/status-go/services/personal"
|
||||
"github.com/status-im/status-go/sign"
|
||||
e2e "github.com/status-im/status-go/t/e2e"
|
||||
. "github.com/status-im/status-go/t/utils"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
const (
|
||||
signDataString = "0xBAADBEEF"
|
||||
accountNotExists = "0x00164ca341326a03b547c05B343b2E21eFAe2400"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func TestPersonalSignSuiteUpstream(t *testing.T) {
|
||||
s := new(PersonalSignSuite)
|
||||
s.upstream = true
|
||||
suite.Run(t, s)
|
||||
}
|
||||
|
||||
type PersonalSignSuite struct {
|
||||
e2e.BackendTestSuite
|
||||
upstream bool
|
||||
}
|
||||
|
||||
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) notificationHandlerSuccess(account string, pass string) func(string) {
|
||||
return func(jsonEvent string) {
|
||||
s.notificationHandler(account, pass, nil)(jsonEvent)
|
||||
}
|
||||
}
|
||||
|
||||
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 == sign.EventSignRequestAdded {
|
||||
err := s.Backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
|
||||
s.NoError(err)
|
||||
}
|
||||
s.notificationHandlerSuccess(account, pass)(jsonEvent)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PersonalSignSuite) initTest(upstreamEnabled bool) error {
|
||||
nodeConfig, err := MakeTestNodeConfig(GetNetworkID())
|
||||
s.NoError(err)
|
||||
|
||||
nodeConfig.IPCEnabled = false
|
||||
nodeConfig.HTTPHost = "" // to make sure that no HTTP interface is started
|
||||
|
||||
if upstreamEnabled {
|
||||
networkURL, err := GetRemoteURL()
|
||||
s.NoError(err)
|
||||
|
||||
nodeConfig.UpstreamConfig.Enabled = true
|
||||
nodeConfig.UpstreamConfig.URL = networkURL
|
||||
}
|
||||
|
||||
return s.Backend.StartNode(nodeConfig)
|
||||
}
|
||||
|
||||
func (s *PersonalSignSuite) notificationHandler(account string, pass string, expectedError error) func(string) {
|
||||
return func(jsonEvent string) {
|
||||
envelope := unmarshalEnvelope(jsonEvent)
|
||||
if envelope.Type == sign.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) {
|
||||
// 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.initTest(s.upstream)
|
||||
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)
|
||||
|
||||
result := s.Backend.CallRPC(basicCall)
|
||||
if testParams.ExpectedError == nil {
|
||||
s.NotContains(result, "error")
|
||||
} else {
|
||||
s.Contains(result, testParams.ExpectedError.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalEnvelope(jsonEvent string) signal.Envelope {
|
||||
var envelope signal.Envelope
|
||||
if e := json.Unmarshal([]byte(jsonEvent), &envelope); e != nil {
|
||||
panic(e)
|
||||
}
|
||||
return envelope
|
||||
}
|
|
@ -25,6 +25,10 @@ import (
|
|||
|
||||
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))
|
||||
}
|
||||
|
@ -44,17 +48,21 @@ func (s *TransactionsTestSuite) TestCallRPCSendTransaction() {
|
|||
|
||||
transactionCompleted := make(chan struct{})
|
||||
|
||||
var txHash gethcommon.Hash
|
||||
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 == sign.EventTransactionQueued {
|
||||
if sg.Type == sign.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)
|
||||
txHash, err = s.Backend.CompleteTransaction(string(txID), TestConfig.Account1.Password)
|
||||
s.NoError(err, "cannot complete queued transaction %s", txID)
|
||||
signResult = s.Backend.ApproveSignRequest(string(txID), TestConfig.Account1.Password)
|
||||
s.NoError(signResult.Error, "cannot complete queued transaction %s", txID)
|
||||
close(transactionCompleted)
|
||||
}
|
||||
})
|
||||
|
@ -77,7 +85,7 @@ func (s *TransactionsTestSuite) TestCallRPCSendTransaction() {
|
|||
s.FailNow("sending transaction timed out")
|
||||
}
|
||||
|
||||
s.Equal(`{"jsonrpc":"2.0","id":1,"result":"`+txHash.String()+`"}`, result)
|
||||
s.Equal(`{"jsonrpc":"2.0","id":1,"result":"`+signResult.Response.Hash().Hex()+`"}`, result)
|
||||
}
|
||||
|
||||
func (s *TransactionsTestSuite) TestCallRPCSendTransactionUpstream() {
|
||||
|
@ -95,23 +103,23 @@ func (s *TransactionsTestSuite) TestCallRPCSendTransactionUpstream() {
|
|||
|
||||
transactionCompleted := make(chan struct{})
|
||||
|
||||
var txHash gethcommon.Hash
|
||||
var signResult sign.Result
|
||||
signal.SetDefaultNodeNotificationHandler(func(rawSignal string) {
|
||||
var signalEnvelope signal.Envelope
|
||||
err := json.Unmarshal([]byte(rawSignal), &signalEnvelope)
|
||||
s.NoError(err)
|
||||
|
||||
if signalEnvelope.Type == sign.EventTransactionQueued {
|
||||
if signalEnvelope.Type == sign.EventSignRequestAdded {
|
||||
event := signalEnvelope.Event.(map[string]interface{})
|
||||
txID := event["id"].(string)
|
||||
|
||||
// Complete with a wrong passphrase.
|
||||
txHash, err = s.Backend.CompleteTransaction(string(txID), "some-invalid-passphrase")
|
||||
s.EqualError(err, keystore.ErrDecrypt.Error(), "should return an error as the passphrase was invalid")
|
||||
signResult = s.Backend.ApproveSignRequest(string(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.
|
||||
txHash, err = s.Backend.CompleteTransaction(string(txID), TestConfig.Account2.Password)
|
||||
s.NoError(err, "cannot complete queued transaction %s", txID)
|
||||
signResult = s.Backend.ApproveSignRequest(string(txID), TestConfig.Account2.Password)
|
||||
s.NoError(signResult.Error, "cannot complete queued transaction %s", txID)
|
||||
|
||||
close(transactionCompleted)
|
||||
}
|
||||
|
@ -135,7 +143,7 @@ func (s *TransactionsTestSuite) TestCallRPCSendTransactionUpstream() {
|
|||
s.FailNow("sending transaction timed out")
|
||||
}
|
||||
|
||||
s.Equal(`{"jsonrpc":"2.0","id":1,"result":"`+txHash.String()+`"}`, result)
|
||||
s.Equal(`{"jsonrpc":"2.0","id":1,"result":"`+signResult.Response.Hash().Hex()+`"}`, result)
|
||||
}
|
||||
|
||||
func (s *TransactionsTestSuite) TestEmptyToFieldPreserved() {
|
||||
|
@ -154,14 +162,14 @@ func (s *TransactionsTestSuite) TestEmptyToFieldPreserved() {
|
|||
}
|
||||
err := json.Unmarshal([]byte(rawSignal), &sg)
|
||||
s.NoError(err)
|
||||
if sg.Type == sign.EventTransactionQueued {
|
||||
var event sign.SendTransactionEvent
|
||||
if sg.Type == sign.EventSignRequestAdded {
|
||||
var event sign.PendingRequestEvent
|
||||
s.NoError(json.Unmarshal(sg.Event, &event))
|
||||
args := event.Args.(map[string]interface{})
|
||||
s.NotNil(args["from"])
|
||||
s.Nil(args["to"])
|
||||
_, err := s.Backend.CompleteTransaction(event.ID, TestConfig.Account1.Password)
|
||||
s.NoError(err)
|
||||
signResult := s.Backend.ApproveSignRequest(event.ID, TestConfig.Account1.Password)
|
||||
s.NoError(signResult.Error)
|
||||
close(transactionCompleted)
|
||||
}
|
||||
})
|
||||
|
@ -229,6 +237,64 @@ 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 == sign.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(
|
||||
string(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(
|
||||
string(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(
|
||||
string(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()
|
||||
|
@ -241,60 +307,8 @@ func (s *TransactionsTestSuite) testSendContractTx(setInputAndDataValue initFunc
|
|||
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, fmt.Sprintf("cannot unmarshal JSON: %s", jsonEvent))
|
||||
|
||||
if envelope.Type == sign.EventTransactionQueued {
|
||||
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")
|
||||
txHash, err = s.Backend.CompleteTransaction(
|
||||
string(event["id"].(string)),
|
||||
TestConfig.Account1.Password,
|
||||
)
|
||||
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)
|
||||
txHash, err = s.Backend.CompleteTransaction(
|
||||
string(event["id"].(string)),
|
||||
TestConfig.Account1.Password,
|
||||
)
|
||||
s.EqualError(
|
||||
err,
|
||||
sign.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))
|
||||
txHash, err = s.Backend.CompleteTransaction(
|
||||
string(event["id"].(string)),
|
||||
TestConfig.Account1.Password,
|
||||
)
|
||||
if expectedError != nil {
|
||||
s.Equal(expectedError, err)
|
||||
} else {
|
||||
s.NoError(err, fmt.Sprintf("cannot complete queued transaction[%v]", event["id"]))
|
||||
}
|
||||
|
||||
log.Info("contract transaction complete", "URL", "https://ropsten.etherscan.io/tx/"+txHash.Hex())
|
||||
close(completeQueuedTransaction)
|
||||
return
|
||||
}
|
||||
})
|
||||
var signRequestResult []byte
|
||||
s.setDefaultNodeNotificationHandler(&signRequestResult, sampleAddress, completeQueuedTransaction, expectedError)
|
||||
|
||||
// this call blocks, up until Complete Transaction is called
|
||||
byteCode, err := hexutil.Decode(`0x6060604052341561000c57fe5b5b60a58061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680636ffa1caa14603a575bfe5b3415604157fe5b60556004808035906020019091905050606b565b6040518082815260200191505060405180910390f35b60008160020290505b9190505600a165627a7a72305820ccdadd737e4ac7039963b54cee5e5afb25fa859a275252bdcf06f653155228210029`)
|
||||
|
@ -323,7 +337,7 @@ func (s *TransactionsTestSuite) testSendContractTx(setInputAndDataValue initFunc
|
|||
s.FailNow("completing transaction timed out")
|
||||
}
|
||||
|
||||
s.Equal(txHashCheck.Hex(), txHash.Hex(), "transaction hash returned from SendTransaction is invalid")
|
||||
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")
|
||||
}
|
||||
|
@ -341,53 +355,8 @@ func (s *TransactionsTestSuite) TestSendEther() {
|
|||
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, fmt.Sprintf("cannot unmarshal JSON: %s", jsonEvent))
|
||||
|
||||
if envelope.Type == sign.EventTransactionQueued {
|
||||
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")
|
||||
txHash, err = s.Backend.CompleteTransaction(
|
||||
string(event["id"].(string)),
|
||||
TestConfig.Account1.Password,
|
||||
)
|
||||
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)
|
||||
txHash, err = s.Backend.CompleteTransaction(
|
||||
string(event["id"].(string)), TestConfig.Account1.Password)
|
||||
s.EqualError(
|
||||
err,
|
||||
sign.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))
|
||||
txHash, err = s.Backend.CompleteTransaction(
|
||||
string(event["id"].(string)),
|
||||
TestConfig.Account1.Password,
|
||||
)
|
||||
s.NoError(err, fmt.Sprintf("cannot complete queued transaction[%v]", event["id"]))
|
||||
|
||||
close(completeQueuedTransaction)
|
||||
return
|
||||
}
|
||||
})
|
||||
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{
|
||||
|
@ -403,7 +372,7 @@ func (s *TransactionsTestSuite) TestSendEther() {
|
|||
s.FailNow("completing transaction timed out")
|
||||
}
|
||||
|
||||
s.Equal(txHashCheck.Hex(), txHash.Hex(), "transaction hash returned from SendTransaction is invalid")
|
||||
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")
|
||||
}
|
||||
|
@ -430,17 +399,18 @@ func (s *TransactionsTestSuite) TestSendEtherTxUpstream() {
|
|||
err = json.Unmarshal([]byte(jsonEvent), &envelope)
|
||||
s.NoError(err, "cannot unmarshal JSON: %s", jsonEvent)
|
||||
|
||||
if envelope.Type == sign.EventTransactionQueued {
|
||||
if envelope.Type == sign.EventSignRequestAdded {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
log.Info("transaction queued (will be completed shortly)", "id", event["id"].(string))
|
||||
|
||||
txHash, err = s.Backend.CompleteTransaction(
|
||||
signResult := s.Backend.ApproveSignRequest(
|
||||
string(event["id"].(string)),
|
||||
TestConfig.Account1.Password,
|
||||
)
|
||||
s.NoError(err, "cannot complete queued transaction[%v]", event["id"])
|
||||
s.NoError(signResult.Error, "cannot complete queued transaction[%v]", event["id"])
|
||||
|
||||
log.Info("contract transaction complete", "URL", "https://ropsten.etherscan.io/tx/"+txHash.Hex())
|
||||
txHash = signResult.Response.Hash()
|
||||
log.Info("contract transaction complete", "URL", txURLString(signResult))
|
||||
close(completeQueuedTransaction)
|
||||
}
|
||||
})
|
||||
|
@ -478,33 +448,36 @@ func (s *TransactionsTestSuite) TestDoubleCompleteQueuedTransactions() {
|
|||
|
||||
// replace transaction notification handler
|
||||
txFailedEventCalled := false
|
||||
txHash := gethcommon.Hash{}
|
||||
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 == sign.EventTransactionQueued {
|
||||
if envelope.Type == sign.EventSignRequestAdded {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
txID := string(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.CompleteTransaction(txID, TestConfig.Account1.Password+"wrong")
|
||||
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
|
||||
txHash, err = s.Backend.CompleteTransaction(txID, TestConfig.Account1.Password)
|
||||
s.NoError(err)
|
||||
signResult := s.Backend.ApproveSignRequest(txID, TestConfig.Account1.Password)
|
||||
s.NoError(signResult.Error)
|
||||
|
||||
log.Info("transaction complete", "URL", txURLString(signResult))
|
||||
|
||||
signHash = signResult.Response.Hash()
|
||||
|
||||
log.Info("transaction complete", "URL", "https://rinkeby.etherscan.io/tx/"+txHash.Hex())
|
||||
close(completeQueuedTransaction)
|
||||
}
|
||||
|
||||
if envelope.Type == sign.EventTransactionFailed {
|
||||
if envelope.Type == sign.EventSignRequestFailed {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
log.Info("transaction return event received", "id", event["id"].(string))
|
||||
|
||||
|
@ -519,8 +492,8 @@ func (s *TransactionsTestSuite) TestDoubleCompleteQueuedTransactions() {
|
|||
}
|
||||
})
|
||||
|
||||
// this call blocks, and should return on *second* attempt to CompleteTransaction (w/ the correct password)
|
||||
txHashCheck, err := s.Backend.SendTransaction(context.TODO(), transactions.SendTxArgs{
|
||||
// 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)),
|
||||
|
@ -533,8 +506,8 @@ func (s *TransactionsTestSuite) TestDoubleCompleteQueuedTransactions() {
|
|||
s.FailNow("test timed out")
|
||||
}
|
||||
|
||||
s.Equal(txHashCheck.Hex(), txHash.Hex(), "transaction hash returned from SendTransaction is invalid")
|
||||
s.False(reflect.DeepEqual(txHashCheck, gethcommon.Hash{}), "transaction was never queued or completed")
|
||||
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(txFailedEventCalled, "expected tx failure signal is not received")
|
||||
}
|
||||
|
@ -557,7 +530,7 @@ func (s *TransactionsTestSuite) TestDiscardQueuedTransaction() {
|
|||
err := json.Unmarshal([]byte(jsonEvent), &envelope)
|
||||
s.NoError(err, fmt.Sprintf("cannot unmarshal JSON: %s", jsonEvent))
|
||||
|
||||
if envelope.Type == sign.EventTransactionQueued {
|
||||
if envelope.Type == sign.EventSignRequestAdded {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
txID := string(event["id"].(string))
|
||||
log.Info("transaction queued (will be discarded soon)", "id", txID)
|
||||
|
@ -565,12 +538,12 @@ func (s *TransactionsTestSuite) TestDiscardQueuedTransaction() {
|
|||
s.True(s.Backend.PendingSignRequests().Has(txID), "txqueue should still have test tx")
|
||||
|
||||
// discard
|
||||
err := s.Backend.DiscardTransaction(txID)
|
||||
err := s.Backend.DiscardSignRequest(txID)
|
||||
s.NoError(err, "cannot discard tx")
|
||||
|
||||
// try completing discarded transaction
|
||||
_, err = s.Backend.CompleteTransaction(txID, TestConfig.Account1.Password)
|
||||
s.EqualError(err, "transaction hash not found", "expects tx not found, but call to CompleteTransaction succeeded")
|
||||
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),
|
||||
|
@ -579,7 +552,7 @@ func (s *TransactionsTestSuite) TestDiscardQueuedTransaction() {
|
|||
close(completeQueuedTransaction)
|
||||
}
|
||||
|
||||
if envelope.Type == sign.EventTransactionFailed {
|
||||
if envelope.Type == sign.EventSignRequestFailed {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
log.Info("transaction return event received", "id", event["id"].(string))
|
||||
|
||||
|
@ -643,7 +616,7 @@ func (s *TransactionsTestSuite) TestDiscardMultipleQueuedTransactions() {
|
|||
var envelope signal.Envelope
|
||||
err := json.Unmarshal([]byte(jsonEvent), &envelope)
|
||||
s.NoError(err)
|
||||
if envelope.Type == sign.EventTransactionQueued {
|
||||
if envelope.Type == sign.EventSignRequestAdded {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
txID := string(event["id"].(string))
|
||||
log.Info("transaction queued (will be discarded soon)", "id", txID)
|
||||
|
@ -653,7 +626,7 @@ func (s *TransactionsTestSuite) TestDiscardMultipleQueuedTransactions() {
|
|||
txIDs <- txID
|
||||
}
|
||||
|
||||
if envelope.Type == sign.EventTransactionFailed {
|
||||
if envelope.Type == sign.EventSignRequestFailed {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
log.Info("transaction return event received", "id", event["id"].(string))
|
||||
|
||||
|
@ -691,17 +664,17 @@ func (s *TransactionsTestSuite) TestDiscardMultipleQueuedTransactions() {
|
|||
txIDs = append(txIDs, "invalid-tx-id")
|
||||
|
||||
// discard
|
||||
discardResults := s.Backend.DiscardTransactions(txIDs)
|
||||
discardResults := s.Backend.DiscardSignRequests(txIDs)
|
||||
require.Len(discardResults, 1, "cannot discard txs: %v", discardResults)
|
||||
require.Error(discardResults["invalid-tx-id"], "transaction hash not found", "cannot discard txs: %v", discardResults)
|
||||
require.Error(discardResults["invalid-tx-id"], sign.ErrSignReqNotFound, "cannot discard txs: %v", discardResults)
|
||||
|
||||
// try completing discarded transaction
|
||||
completeResults := s.Backend.CompleteTransactions(txIDs, TestConfig.Account1.Password)
|
||||
require.Len(completeResults, testTxCount+1, "unexpected number of errors (call to CompleteTransaction should not succeed)")
|
||||
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, "transaction hash not found", "invalid error for %s", txResult.Hash.Hex())
|
||||
require.Equal(gethcommon.Hash{}, txResult.Hash, "invalid hash (expected zero): %s", txResult.Hash.Hex())
|
||||
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
|
||||
|
@ -749,7 +722,7 @@ func (s *TransactionsTestSuite) TestNonExistentQueuedTransactions() {
|
|||
signal.SetDefaultNodeNotificationHandler(func(string) {})
|
||||
|
||||
// try completing non-existing transaction
|
||||
_, err := s.Backend.CompleteTransaction("some-bad-transaction-id", TestConfig.Account1.Password)
|
||||
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())
|
||||
}
|
||||
|
@ -793,7 +766,7 @@ func (s *TransactionsTestSuite) sendConcurrentTransactions(testTxCount int) {
|
|||
err := json.Unmarshal([]byte(jsonEvent), &envelope)
|
||||
require.NoError(err, fmt.Sprintf("cannot unmarshal JSON: %s", jsonEvent))
|
||||
|
||||
if envelope.Type == sign.EventTransactionQueued {
|
||||
if envelope.Type == sign.EventSignRequestAdded {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
txID := string(event["id"].(string))
|
||||
log.Info("transaction queued (will be completed in a single call, once aggregated)", "id", txID)
|
||||
|
@ -816,9 +789,9 @@ func (s *TransactionsTestSuite) sendConcurrentTransactions(testTxCount int) {
|
|||
// wait for transactions, and complete them in a single call
|
||||
completeTxs := func(txIDs []string) {
|
||||
txIDs = append(txIDs, "invalid-tx-id")
|
||||
results := s.Backend.CompleteTransactions(txIDs, TestConfig.Account1.Password)
|
||||
results := s.Backend.ApproveSignRequests(txIDs, TestConfig.Account1.Password)
|
||||
s.Len(results, testTxCount+1)
|
||||
s.EqualError(results["invalid-tx-id"].Error, "transaction hash not found")
|
||||
s.EqualError(results["invalid-tx-id"].Error, sign.ErrSignReqNotFound.Error())
|
||||
|
||||
for txID, txResult := range results {
|
||||
s.False(
|
||||
|
@ -826,10 +799,10 @@ func (s *TransactionsTestSuite) sendConcurrentTransactions(testTxCount int) {
|
|||
"invalid error for %s", txID,
|
||||
)
|
||||
s.False(
|
||||
txResult.Hash == (gethcommon.Hash{}) && txID != "invalid-tx-id",
|
||||
len(txResult.Response.Bytes()) < 1 && txID != "invalid-tx-id",
|
||||
"invalid hash (expected non empty hash): %s", txID,
|
||||
)
|
||||
log.Info("transaction complete", "URL", "https://ropsten.etherscan.io/tx/"+txResult.Hash.Hex())
|
||||
log.Info("transaction complete", "URL", txURLString(txResult))
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
"github.com/status-im/status-go/geth/node"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
"github.com/status-im/status-go/geth/signal"
|
||||
"github.com/status-im/status-go/shhext"
|
||||
"github.com/status-im/status-go/services/shhext"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package ethapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
)
|
||||
|
||||
type LimitedPersonalAPI struct {
|
||||
privateAPI *ethapi.PrivateAccountAPI
|
||||
}
|
||||
|
||||
func NewLimitedPersonalAPI(am *accounts.Manager) *LimitedPersonalAPI {
|
||||
return &LimitedPersonalAPI{ethapi.NewSubsetOfPrivateAccountAPI(am)}
|
||||
}
|
||||
|
||||
func (s *LimitedPersonalAPI) Sign(ctx context.Context, data hexutil.Bytes, addr common.Address, passwd string) (hexutil.Bytes, error) {
|
||||
return s.privateAPI.Sign(ctx, data, addr, passwd)
|
||||
}
|
||||
|
||||
func (s *LimitedPersonalAPI) EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error) {
|
||||
return s.privateAPI.EcRecover(ctx, data, sig)
|
||||
}
|
|
@ -214,6 +214,14 @@ func NewPrivateAccountAPI(b Backend, nonceLock *AddrLocker) *PrivateAccountAPI {
|
|||
}
|
||||
}
|
||||
|
||||
func NewSubsetOfPrivateAccountAPI(am *accounts.Manager) *PrivateAccountAPI {
|
||||
return &PrivateAccountAPI{
|
||||
am: am,
|
||||
nonceLock: nil,
|
||||
b: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// ListAccounts will return a list of addresses for accounts this node manages.
|
||||
func (s *PrivateAccountAPI) ListAccounts() []common.Address {
|
||||
addresses := make([]common.Address, 0) // return [] instead of nil if empty
|
||||
|
@ -426,7 +434,7 @@ func (s *PrivateAccountAPI) Sign(ctx context.Context, data hexutil.Bytes, addr c
|
|||
// Look up the wallet containing the requested signer
|
||||
account := accounts.Account{Address: addr}
|
||||
|
||||
wallet, err := s.b.AccountManager().Find(account)
|
||||
wallet, err := s.am.Find(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue