diff --git a/cmd/signer/api.go b/cmd/signer/api.go index 8606eb1e38..628b7a1590 100644 --- a/cmd/signer/api.go +++ b/cmd/signer/api.go @@ -45,70 +45,73 @@ type SignerAPI struct { // Metadata about the request type Metadata struct { - remote string - local string - scheme string + Remote string `json:"remote"` + Local string `json:"local"` + Scheme string `json:"scheme"` } // types for the requests/response types type ( // SignTxRequest contains info about a Transaction to sign SignTxRequest struct { - Transaction TransactionArg - From common.Address - Callinfo fmt.Stringer - Meta Metadata + Transaction TransactionArg `json:"transaction"` + From common.Address `json:"fromaccount"` + Callinfo fmt.Stringer `json:"call_info"` + Meta Metadata `json:"meta"` } // SignTxResponse result from SignTxRequest SignTxResponse struct { //The UI may make changes to the TX Transaction TransactionArg From common.Address - Approved bool - Password string + Approved bool `json:"approved"` + Password string `json:"password"` } // ExportRequest info about query to export accounts ExportRequest struct { - Address common.Address - Meta Metadata + Address common.Address `json:"address"` + Meta Metadata `json:"meta"` } // ExportResponse response to export-request ExportResponse struct { - Approved bool + Approved bool `json:"approved"` } // ImportRequest info about request to import an Account ImportRequest struct { - Meta Metadata + Meta Metadata `json:"meta"` } ImportResponse struct { - Approved bool - OldPassword string - NewPassword string + Approved bool `json:"approved"` + OldPassword string `json:"old_password"` + NewPassword string `json:"new_password"` } SignDataRequest struct { - Address common.Address - Rawdata hexutil.Bytes - Message string - Hash hexutil.Bytes - Meta Metadata + Address common.Address `json:"address"` + Rawdata hexutil.Bytes `json:"raw_data"` + Message string `json:"message"` + Hash hexutil.Bytes `json:"hash"` + Meta Metadata `json:"meta"` } SignDataResponse struct { - Approved bool + Approved bool `json:"approved"` Password string } NewAccountRequest struct { - Meta Metadata + Meta Metadata `json:"meta"` } NewAccountResponse struct { - Approved bool - Password string + Approved bool `json:"approved"` + Password string `json:"password"` } ListRequest struct { - Accounts []Account - Meta Metadata + Accounts []Account `json:"accounts"` + Meta Metadata `json:"meta"` } ListResponse struct { - Accounts []Account + Accounts []Account `json:"accounts"` + } + Message struct { + Message string `json:"message"` } ) @@ -186,13 +189,13 @@ func metaData(ctx context.Context) Metadata { m := Metadata{"NA", "NA", "NA"} if v := ctx.Value("remote"); v != nil { - m.remote = v.(string) + m.Remote = v.(string) } if v := ctx.Value("scheme"); v != nil { - m.scheme = v.(string) + m.Scheme = v.(string) } if v := ctx.Value("local"); v != nil { - m.local = v.(string) + m.Local = v.(string) } return m } @@ -463,7 +466,7 @@ func (api *SignerAPI) Export(ctx context.Context, addr common.Address) (json.Raw // Imports tries to import the given keyJSON in the local keystore. The keyJSON data is expected to be // in web3 keystore format. It will decrypt the keyJSON with the given passphrase and on successful // decryption it will encrypt the key with the given newPassphrase and store it in the keystore. -func (api *SignerAPI) Import(ctx context.Context, string, keyJSON json.RawMessage) (Account, error) { +func (api *SignerAPI) Import(ctx context.Context, keyJSON json.RawMessage) (Account, error) { be := api.am.Backends(keystore.KeyStoreType) diff --git a/cmd/signer/cliui.go b/cmd/signer/cliui.go index f2b1d6f9df..881349ee40 100644 --- a/cmd/signer/cliui.go +++ b/cmd/signer/cliui.go @@ -90,7 +90,7 @@ func (ui *CommandlineUI) confirm() bool { } func showMetadata(metadata Metadata) { - fmt.Printf("Request info:\n\t%v -> %v -> %v\n", metadata.remote, metadata.scheme, metadata.local) + fmt.Printf("Request info:\n\t%v -> %v -> %v\n", metadata.Remote, metadata.Scheme, metadata.Local) } // ApproveTx prompt the user for confirmation to request to sign Transaction @@ -100,7 +100,7 @@ func (ui *CommandlineUI) ApproveTx(request *SignTxRequest) (SignTxResponse, erro weival := request.Transaction.Value fmt.Printf("--------- Transaction request-------------\n") - fmt.Printf("to: %v\n", request.Transaction.To.Hex()) + fmt.Printf("to: %v\n", request.Transaction.To) fmt.Printf("from: %v\n", request.From.Hex()) fmt.Printf("value: %v wei\n", weival) if len(request.Transaction.Data) > 0 { @@ -125,7 +125,7 @@ func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResp fmt.Printf("-------- Sign data request--------------\n") fmt.Printf("Account: %x\n", request.Address) - fmt.Printf("message: \n%v\n", request.Message) + fmt.Printf("message: \n%q\n", request.Message) fmt.Printf("raw data: \n%v\n", request.Rawdata) fmt.Printf("message hash: %v\n", request.Hash) fmt.Printf("-------------------------------------------\n") @@ -154,7 +154,7 @@ func (ui *CommandlineUI) ApproveImport(request *ImportRequest) (ImportResponse, ui.mu.Lock() defer ui.mu.Unlock() - fmt.Printf("-------- Export Account request--------------\n") + fmt.Printf("-------- Import Account request--------------\n") fmt.Printf("A request has been made to import an encrypted keyfile\n") fmt.Printf("-------------------------------------------\n") showMetadata(request.Meta) diff --git a/cmd/signer/main.go b/cmd/signer/main.go index de9a864837..3fcf97e4a9 100644 --- a/cmd/signer/main.go +++ b/cmd/signer/main.go @@ -24,7 +24,10 @@ import ( "os" "path/filepath" + "context" + "encoding/json" "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" @@ -84,6 +87,10 @@ func main() { "This means that an STDIN/STDOUT is used for RPC-communication with a e.g. a graphical user " + "interface, and can be used when the signer is started by an external process.", }, + cli.BoolFlag{ + Name: "stdio-ui-test", + Usage: "Mechanism to test interface between signer and UI. Requires 'stdio-ui'.", + }, } app.Action = func(c *cli.Context) error { @@ -136,6 +143,7 @@ func main() { if err = server.RegisterName("account", api); err != nil { utils.Fatalf("Could not register signer API: %v", err) } + //server.ListServices() // Import from file if rfile := c.String("requestfile"); rfile != "" { @@ -151,13 +159,63 @@ func main() { log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%s", endpoint)) cors := []string{"*"} + if c.Bool("stdio-ui-test") { + log.Info("Performing UI test") + go testExternalUI(api) + } + rpc.NewHTTPServer(cors, server).Serve(listener) + return nil } app.Run(os.Args) } +func testExternalUI(api *SignerAPI) { + + ctx := context.WithValue(context.Background(), "remote", "signer binary") + ctx = context.WithValue(ctx, "scheme", "in-proc") + ctx = context.WithValue(ctx, "local", "main") + + errs := make([]string, 0) + + api.ui.ShowInfo("Testing 'ShowInfo'") + api.ui.ShowError("Testing 'ShowError'") + + checkErr:= func(method string, err error){ + if err != nil && err != ErrRequestDenied{ + errs = append(errs, fmt.Sprintf("%v: %v", method, err.Error())) + } + } + var err error + + _, err = api.SignTransaction(ctx, common.Address{}, TransactionArg{}, nil); + checkErr("SignTransaction", err) + _, err = api.Sign(ctx, common.Address{}, common.Hex2Bytes("01020304")) + checkErr("Sign", err) + _, err =api.List(ctx) + checkErr("List", err) + _, err = api.New(ctx) + checkErr("New", err) + _, err = api.Export(ctx, common.Address{}) + checkErr("Export", err) + _, err = api.Import(ctx, json.RawMessage{}) + checkErr("Import", err) + + api.ui.ShowInfo("Tests completed") + + if len(errs) > 0 { + log.Error("Got errors") + for _, e := range errs { + log.Error(e) + } + }else{ + log.Info("No errors") + } + +} + // Create Account // curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_new","params":["test"],"id":67}' localhost:8550 diff --git a/cmd/signer/pythonsigner.py b/cmd/signer/pythonsigner.py index fc469a96e1..b58ff77997 100644 --- a/cmd/signer/pythonsigner.py +++ b/cmd/signer/pythonsigner.py @@ -34,60 +34,117 @@ class PipeTransport(ServerTransport): def receive_message(self): data = self.input.readline() - print("IN ->\n{}".format( data)) + #print(">> {}".format( data)) return None, urlparse.unquote(data) def send_reply(self, context, reply): - print("OUT <-\n{}".format( reply)) + #print("<< {}".format( reply)) self.output.write(reply) self.output.write("\n") dispatcher = RPCDispatcher() @dispatcher.public -def ApproveTx(Transaction = None, From = None, Callinfo = None, Meta = None): +def ApproveTx(transaction = None, fromaccount = None, call_info = None, meta = None): + """ + Example request: + + {"jsonrpc":"2.0","method":"ApproveTx","params":{"transaction":{"to":null,"gas":null,"gasPrice":null,"value":null,"data":"0x","nonce":null},"from":"0x0000000000000000000000000000000000000000","call_info":null,"meta":{"remote":"signer binary","local":"main","scheme":"in-proc"}},"id":2} + + :param transaction: transaction info + :param call_info: info abou the call, e.g. if ABI info could not be + :param meta: metadata about the request, e.g. where the call comes from + :return: + """ return { - "Approved" : True, - "Transaction" : Transaction, - "From" : From, - "Password" : None, + "approved" : False, + "transaction" : None, + "fromaccount" : fromaccount, + "password" : None, } @dispatcher.public -def ApproveSignData(): - return {"Approved": False, - "Password" : None} +def ApproveSignData(address=None, raw_data = None, message = None, hash = None, meta = None): + """ Example request + + {"jsonrpc":"2.0","method":"ApproveSignData","params":{"address":"0x0000000000000000000000000000000000000000","raw_data":"0x01020304","message":"\u0019Ethereum Signed Message:\n4\u0001\u0002\u0003\u0004","hash":"0x7e3a4e7a9d1744bc5c675c25e1234ca8ed9162bd17f78b9085e48047c15ac310","meta":{"remote":"signer binary","local":"main","scheme":"in-proc"}},"id":3} + + + """ + return {"approved": False, + "password" : None} @dispatcher.public -def ApproveExport(): - return {"Approved" : False} +def ApproveExport(address = None, meta = None): + """ Example request + + {"jsonrpc":"2.0","method":"ApproveExport","params":{"address":"0x0000000000000000000000000000000000000000","meta":{"remote":"signer binary","local":"main","scheme":"in-proc"}},"id":5} + + """ + return {"approved" : False} @dispatcher.public -def ApproveImport(): - return {"Approved" : False, "OldPassword": "", "NewPassword": ""} +def ApproveImport(meta = None): + """ Example request + + {"jsonrpc":"2.0","method":"ApproveImport","params":{"Meta":{}},"id":4} + + """ + return {"approved" : False, "old_password": "", "new_password": ""} @dispatcher.public -def ApproveListing(): - return [] +def ApproveListing(accounts=None, meta = None): + """ Example request + + {"jsonrpc":"2.0","method":"ApproveListing","params":{"accounts":[{"type":"Account","url":"keystore:///home/user/ethereum/keystore/file","address":"0x010101010101010010101010101abcdef0001337"}],"Meta":{}},"id":2} + """ + return {'accounts': []} @dispatcher.public -def ApproveNewAccount(): - return {"Approved": False, "Password": ""} +def ApproveNewAccount(meta = None): + """ + Example request + + {"jsonrpc":"2.0","method":"ApproveNewAccount","params":{"meta":{"remote":"signer binary","local":"main","scheme":"in-proc"}},"id":5} + + :return: + """ + return {"approved": False, "password": ""} @dispatcher.public -def ShowError(text = ""): - sys.err.println("Error: %s", text) +def ShowError(message = ""): + """ + Example request: + + {"jsonrpc":"2.0","method":"ShowInfo","params":{"message":"Testing 'ShowError'"},"id":1} + + :param text: to show + :return: nothing + """ + sys.stderr.write("Error: {}\n".format( message)) return @dispatcher.public -def ShowInfo(text = ""): - sys.err.println("Info: %s", text) +def ShowInfo(message = ""): + """ + Example request + {"jsonrpc":"2.0","method":"ShowInfo","params":{"message":"Testing 'ShowInfo'"},"id":0} + + :param text: to display + :return:nothing + """ + sys.stdout.write("Info: {}\n".format( message)) return -def main(): +def main(args): + + cmd = ["./signer", "--stdio-ui"] + if len(args) > 0 and args[0] == "test": + cmd.extend(["--stdio-ui-test"]) + print("cmd: {}".format(" ".join(cmd))) # line buffered - p = subprocess.Popen(["./signer", "--stdio-ui"], bufsize=1, universal_newlines=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE) + p = subprocess.Popen(cmd, bufsize=1, universal_newlines=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE) transport = PipeTransport(p.stdout, p.stdin) rpc_server = RPCServer( transport, @@ -97,4 +154,4 @@ def main(): rpc_server.serve_forever() if __name__ == '__main__': - main() \ No newline at end of file + main(sys.argv[1:]) \ No newline at end of file diff --git a/cmd/signer/stdioui.go b/cmd/signer/stdioui.go index d5472956a1..876342ff82 100644 --- a/cmd/signer/stdioui.go +++ b/cmd/signer/stdioui.go @@ -109,9 +109,17 @@ func (ui StdIOUI) ApproveNewAccount(request *NewAccountRequest) (NewAccountRespo } func (ui StdIOUI) ShowError(message string) { + err := ui.dispatch("ShowError", &Message{message}, nil) + if err != nil { + log.Info("Error calling 'ShowError'", "exc", err.Error(),"msg", message) + } } func (ui StdIOUI) ShowInfo(message string) { + err := ui.dispatch("ShowInfo", Message{message}, nil) + if err != nil { + log.Info("Error calling 'ShowInfo'", "exc", err.Error(), "msg", message) + } } type rwc struct {