From a043ed2ff68318d34341e10b1722743280885c9d Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Sun, 11 Nov 2018 22:03:03 +0100 Subject: [PATCH] signer,clef: implement timeout for external api --- cmd/clef/main.go | 22 ++++--- signer/core/api.go | 10 ++- signer/core/timeout.go | 135 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 10 deletions(-) create mode 100644 signer/core/timeout.go diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 519d63b3c1..88e3755ca2 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -49,12 +49,6 @@ import ( "gopkg.in/urfave/cli.v1" ) -// ExternalAPIVersion -- see extapi_changelog.md -const ExternalAPIVersion = "4.0.0" - -// InternalAPIVersion -- see intapi_changelog.md -const InternalAPIVersion = "3.0.0" - const legalWarning = ` WARNING! @@ -110,6 +104,11 @@ var ( Usage: "File used to emit audit logs. Set to \"\" to disable", Value: "audit.log", } + timeoutFlag = cli.DurationFlag{ + Name: "timeout", + Usage: "Specify a timeout, so external callers get a response within a certain time (default 0 = no timeout)", + Value: 0, + } ruleFlag = cli.StringFlag{ Name: "rules", Usage: "Enable rule-engine", @@ -194,6 +193,7 @@ func init() { dBFlag, customDBFlag, auditLogFlag, + timeoutFlag, ruleFlag, stdiouiFlag, testFlag, @@ -414,6 +414,12 @@ func signer(c *cli.Context) error { c.GlobalBool(utils.LightKDFFlag.Name), c.GlobalBool(advancedMode.Name)) api = apiImpl + + // Timeout + if timeout := c.GlobalDuration(timeoutFlag.Name); timeout > 0 { + log.Info("extapi timeout set", "time", timeout) + api = core.NewTimedExternalAPI(api, timeout) + } // Audit logging if logfile := c.GlobalString(auditLogFlag.Name); logfile != "" { api, err = core.NewAuditLogger(logfile, api) @@ -479,8 +485,8 @@ func signer(c *cli.Context) error { } ui.OnSignerStartup(core.StartupInfo{ Info: map[string]interface{}{ - "extapi_version": ExternalAPIVersion, - "intapi_version": InternalAPIVersion, + "extapi_version": core.ExternalAPIVersion, + "intapi_version": core.InternalAPIVersion, "extapi_http": extapiURL, "extapi_ipc": ipcapiURL, }, diff --git a/signer/core/api.go b/signer/core/api.go index 2b96cdb5f3..b282170f64 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -36,8 +36,14 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) -// numberOfAccountsToDerive For hardware wallets, the number of accounts to derive -const numberOfAccountsToDerive = 10 +const( + // numberOfAccountsToDerive For hardware wallets, the number of accounts to derive + numberOfAccountsToDerive = 10 + // ExternalAPIVersion -- see extapi_changelog.md + ExternalAPIVersion = "4.0.0" + // InternalAPIVersion -- see intapi_changelog.md + InternalAPIVersion = "3.0.0" +) // ExternalAPI defines the external API through which signing requests are made. type ExternalAPI interface { diff --git a/signer/core/timeout.go b/signer/core/timeout.go new file mode 100644 index 0000000000..0e6786e800 --- /dev/null +++ b/signer/core/timeout.go @@ -0,0 +1,135 @@ +package core + +import ( + "context" + "encoding/json" + "fmt" + "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" + "github.com/ethereum/go-ethereum/log" + "time" +) + +// The TimedExternalAPI implements ExternalAPI, but can be configured to time out after a specified interval. +// This can be used to ensure that callers get a response within reasonable time, +// even if the user is unresponsive. +type TimedExternalAPI struct { + timeout time.Duration + next ExternalAPI +} + +func NewTimedExternalAPI(next ExternalAPI, timeout time.Duration) ExternalAPI { + return &TimedExternalAPI{timeout, next} +} + +var ( + ErrTimeout = fmt.Errorf("timeout occurred") +) + +func (t *TimedExternalAPI) List(ctx context.Context) ([]common.Address, error) { + type response struct { + addr []common.Address + err error + } + ch := make(chan response) + go func() { + addr, err := t.next.List(ctx) + ch <- response{addr, err} + }() + + select { + case r := <-ch: + return r.addr, r.err + case <-time.After(t.timeout): + log.Info("timeout", "op", "list") + go func() { <-ch }() + return []common.Address{}, ErrTimeout + } +} + +func (t *TimedExternalAPI) New(ctx context.Context) (accounts.Account, error) { + type response struct { + acc accounts.Account + err error + } + ch := make(chan response) + go func() { + acc, err := t.next.New(ctx) + ch <- response{acc, err} + }() + + select { + case r := <-ch: + return r.acc, r.err + case <-time.After(t.timeout): + log.Info("timeout", "op", "list") + go func() { <-ch }() + return accounts.Account{}, ErrTimeout + } +} + +func (t *TimedExternalAPI) SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) { + type response struct { + res *ethapi.SignTransactionResult + err error + } + ch := make(chan response) + go func() { + res, err := t.next.SignTransaction(ctx, args, methodSelector) + ch <- response{res, err} + }() + select { + case r := <-ch: + return r.res, r.err + case <-time.After(t.timeout): + log.Info("timeout", "op", "signTransaction") + go func() { <-ch }() + return nil, ErrTimeout + } +} + +func (t *TimedExternalAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { + type response struct { + res hexutil.Bytes + err error + } + ch := make(chan response) + go func() { + res, err := t.next.Sign(ctx, addr, data) + ch <- response{res, err} + }() + select { + case r := <-ch: + return r.res, r.err + case <-time.After(t.timeout): + log.Info("timeout", "op", "sign") + go func() { <-ch }() + return nil, ErrTimeout + } +} + +func (t *TimedExternalAPI) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) { + type response struct { + res json.RawMessage + err error + } + ch := make(chan response) + go func() { + res, err := t.next.Export(ctx, addr) + ch <- response{res, err} + }() + select { + case r := <-ch: + return r.res, r.err + case <-time.After(t.timeout): + log.Info("timeout", "op", "sign") + go func() { <-ch }() + return json.RawMessage{}, ErrTimeout + } +} + +func (t *TimedExternalAPI) Version(ctx context.Context) (string, error) { + return ExternalAPIVersion, nil +}