Implement `personal_sign`.

This commit implements `personal_sign` RPC or web3 personal.sign
methods.

NB! Contains breaking API changes.
This commit is contained in:
Igor Mandrigin 2018-04-10 12:02:54 +02:00 committed by Igor Mandrigin
parent 364f88efd9
commit 4cc6028d59
37 changed files with 986 additions and 412 deletions

2
Gopkg.lock generated
View File

@ -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

View File

@ -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`)

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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.

View File

@ -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
}

View File

@ -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 {

View File

@ -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"

View File

@ -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...)
}

View File

@ -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
}

View File

@ -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.

View File

@ -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.

View File

@ -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)
}

View File

@ -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")

View File

@ -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"`
}

View File

@ -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.

111
services/personal/api.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -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,

View File

@ -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,
}
}

View File

@ -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{}{}
},
}

View File

@ -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())
}

View File

@ -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
}

View File

@ -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

View File

@ -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"
)

View File

@ -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)
}

View File

@ -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
}