From 2ccae4ffecb0dae7c0a4ca276935fc9d82f4f64c Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Sat, 16 Dec 2017 10:38:21 +0100 Subject: [PATCH] cmd/signer: rework json communication --- cmd/signer/README.md | 17 ++++++++------- cmd/signer/pythonsigner.py | 6 +++--- cmd/signer/stdioui.go | 28 ++++++++++++++++--------- rpc/client.go | 43 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 20 deletions(-) diff --git a/cmd/signer/README.md b/cmd/signer/README.md index f4ec1fc249..f1d68dc7a7 100644 --- a/cmd/signer/README.md +++ b/cmd/signer/README.md @@ -404,17 +404,11 @@ Some snags and todos checksum, since the addresses are common.Address, and not String. This should be changed upstream, so that they are some more complex form with both common.Address and the original string (?) -* The JSON communication relies on an external library, which has not been vendored in. We could probably -reuse the jsoncodec by adding some fields for clientcodec, but it's a bit messy. * The audit-log perhaps leave some things to be desired. I have not found a perfect way to save an audit log of events. * Some more fields should be added to calldata, e.g http-header `Origin`. -* The signer should also generate the identicon for the addresses, and pass to the UI as a base64 string. - * based on https://github.com/ethereum/blockies - * Maybe actually need to be in the UI, at least in UI:s allowing the user to change the address - * The signer should take a startup param "--no-change", for UI:s that do not contain the capability to perform changes to things, only approve/deny. Such a UI should be able to start the signer in a more secure mode by telling it that it only wants approve/deny capabilities. @@ -438,4 +432,13 @@ invoking methods with the following info: * The signer should pass the `Origin` header as call-info to the UI. As of right now, the way that info about the request is put together is a bit of a hack into the http server. This could probably be greatly improved -* The signer \ No newline at end of file +* Geth relay + - Geth should be started in `geth --external_signer localhost:8550`. + +* Wallets / accounts. Add API methods for wallets. + +* Rules. In the future, it should be possible to specify rules, e.g. "Allow sending up to 1 eth per day to contract Y". Two problems: + * There needs to be a very good structure around rules. Either a full language (lua/js) or a limited but flexible syntax based on e.g. json/yaml. Kind of like a firewall ruleset. + * This implies that the signer would remember passwords, which is very problematic. However, a good UI implementation will want these things, + and it would be better to implement it once in the signer, than having UI:s develop their own remember-password logic. + diff --git a/cmd/signer/pythonsigner.py b/cmd/signer/pythonsigner.py index b58ff77997..309b801460 100644 --- a/cmd/signer/pythonsigner.py +++ b/cmd/signer/pythonsigner.py @@ -49,7 +49,7 @@ def ApproveTx(transaction = None, fromaccount = None, call_info = None, meta = N """ 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} + {"jsonrpc":"2.0","method":"ApproveTx","params":{"transaction":{"to":null,"gas":null,"gasPrice":null,"value":null,"data":"0x","nonce":null},"fromaccount":"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 @@ -59,7 +59,7 @@ def ApproveTx(transaction = None, fromaccount = None, call_info = None, meta = N return { "approved" : False, "transaction" : None, - "fromaccount" : fromaccount, +# "fromaccount" : fromaccount, "password" : None, } @@ -154,4 +154,4 @@ def main(args): rpc_server.serve_forever() if __name__ == '__main__': - main(sys.argv[1:]) \ No newline at end of file + main(sys.argv[1:]) diff --git a/cmd/signer/stdioui.go b/cmd/signer/stdioui.go index 28aa7a6e35..a14202d3ad 100644 --- a/cmd/signer/stdioui.go +++ b/cmd/signer/stdioui.go @@ -18,26 +18,32 @@ package main import ( - "bufio" "github.com/ethereum/go-ethereum/log" - "github.com/powerman/rpc-codec/jsonrpc2" + + "github.com/ethereum/go-ethereum/rpc" "io" "os" "sync" + + "context" ) type StdIOUI struct { - client *jsonrpc2.Client + //client *jsonrpc2.Client + client rpc.Client // codec rpc.ClientCodec mu sync.Mutex } func NewStdIOUI() *StdIOUI { - in, out := bufio.NewReader(os.Stdin), os.Stdout + // in, out := bufio.NewReader(os.Stdin), os.Stdout + client, err := rpc.DialContext(context.Background(), "stdio://") + if err != nil { + log.Crit("Could not create stdio client", "err", err) + } + return &StdIOUI{client: *client} + //return &StdIOUI{client: jsonrpc2.NewClient(&rwc{in, out})} - //codec := rpc2.NewJSONCodec() - return &StdIOUI{client: jsonrpc2.NewClient(&rwc{in, out})} - //return &StdIOUI{} } func (ui StdIOUI) dispatch(serviceMethod string, args interface{}, reply interface{}) error { @@ -50,10 +56,12 @@ func (ui StdIOUI) dispatch(serviceMethod string, args interface{}, reply interfa // in, out := bufio.NewReader(os.Stdin), os.Stdout // codec := jsonrpc.NewClientCodec(&rwc{in, out}) - // c := rpc.NewClientWithCodec(codec) + // c := rpc.NewClientWithCodec(codec) // return c.Call(serviceMethod, args, &reply) - - err := ui.client.Call(serviceMethod, args, &reply) + log.Info("Writing to client") + err := ui.client.Call(&reply, serviceMethod, args) + log.Info("Writing to client done") + // err := ui.client.Call(serviceMethod, args, &reply) if err != nil { log.Info("Error", "exc", err.Error()) } diff --git a/rpc/client.go b/rpc/client.go index 8aa84ec982..77e803e2cd 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -33,6 +33,7 @@ import ( "time" "github.com/ethereum/go-ethereum/log" + "os" ) var ( @@ -171,6 +172,8 @@ func DialContext(ctx context.Context, rawurl string) (*Client, error) { return DialHTTP(rawurl) case "ws", "wss": return DialWebsocket(ctx, rawurl, "") + case "stdio": + return DialStdIO(ctx) case "": return DialIPC(ctx, rawurl) default: @@ -178,6 +181,46 @@ func DialContext(ctx context.Context, rawurl string) (*Client, error) { } } +type StdIOConn struct{} + +func (io StdIOConn) Read(b []byte) (n int, err error) { + return os.Stdin.Read(b) +} + +func (io StdIOConn) Write(b []byte) (n int, err error) { + return os.Stdout.Write(b) +} + +func (io StdIOConn) Close() error { + return nil +} + +func (io StdIOConn) LocalAddr() net.Addr { + return &net.UnixAddr{Name: "stdio", Net: "stdio"} +} + +func (io StdIOConn) RemoteAddr() net.Addr { + return &net.UnixAddr{Name: "stdio", Net: "stdio"} +} + +func (io StdIOConn) SetDeadline(t time.Time) error { + return &net.OpError{Op: "set", Net: "stdio", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} +} + +func (io StdIOConn) SetReadDeadline(t time.Time) error { + return &net.OpError{Op: "set", Net: "stdio", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} +} + +func (io StdIOConn) SetWriteDeadline(t time.Time) error { + return &net.OpError{Op: "set", Net: "stdio", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} +} +func DialStdIO(ctx context.Context) (*Client, error) { + + return newClient(ctx, func(_ context.Context) (net.Conn, error) { + return StdIOConn{}, nil + }) +} + func newClient(initctx context.Context, connectFunc func(context.Context) (net.Conn, error)) (*Client, error) { conn, err := connectFunc(initctx) if err != nil {