cmd/signer: Made lowercase json-definitions, added UI-signer test functionality

This commit is contained in:
Martin Holst Swende 2017-12-06 14:40:46 +01:00
parent d0c0b9bacd
commit cd84631194
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
5 changed files with 187 additions and 61 deletions

View file

@ -45,70 +45,73 @@ type SignerAPI struct {
// Metadata about the request // Metadata about the request
type Metadata struct { type Metadata struct {
remote string Remote string `json:"remote"`
local string Local string `json:"local"`
scheme string Scheme string `json:"scheme"`
} }
// types for the requests/response types // types for the requests/response types
type ( type (
// SignTxRequest contains info about a Transaction to sign // SignTxRequest contains info about a Transaction to sign
SignTxRequest struct { SignTxRequest struct {
Transaction TransactionArg Transaction TransactionArg `json:"transaction"`
From common.Address From common.Address `json:"fromaccount"`
Callinfo fmt.Stringer Callinfo fmt.Stringer `json:"call_info"`
Meta Metadata Meta Metadata `json:"meta"`
} }
// SignTxResponse result from SignTxRequest // SignTxResponse result from SignTxRequest
SignTxResponse struct { SignTxResponse struct {
//The UI may make changes to the TX //The UI may make changes to the TX
Transaction TransactionArg Transaction TransactionArg
From common.Address From common.Address
Approved bool Approved bool `json:"approved"`
Password string Password string `json:"password"`
} }
// ExportRequest info about query to export accounts // ExportRequest info about query to export accounts
ExportRequest struct { ExportRequest struct {
Address common.Address Address common.Address `json:"address"`
Meta Metadata Meta Metadata `json:"meta"`
} }
// ExportResponse response to export-request // ExportResponse response to export-request
ExportResponse struct { ExportResponse struct {
Approved bool Approved bool `json:"approved"`
} }
// ImportRequest info about request to import an Account // ImportRequest info about request to import an Account
ImportRequest struct { ImportRequest struct {
Meta Metadata Meta Metadata `json:"meta"`
} }
ImportResponse struct { ImportResponse struct {
Approved bool Approved bool `json:"approved"`
OldPassword string OldPassword string `json:"old_password"`
NewPassword string NewPassword string `json:"new_password"`
} }
SignDataRequest struct { SignDataRequest struct {
Address common.Address Address common.Address `json:"address"`
Rawdata hexutil.Bytes Rawdata hexutil.Bytes `json:"raw_data"`
Message string Message string `json:"message"`
Hash hexutil.Bytes Hash hexutil.Bytes `json:"hash"`
Meta Metadata Meta Metadata `json:"meta"`
} }
SignDataResponse struct { SignDataResponse struct {
Approved bool Approved bool `json:"approved"`
Password string Password string
} }
NewAccountRequest struct { NewAccountRequest struct {
Meta Metadata Meta Metadata `json:"meta"`
} }
NewAccountResponse struct { NewAccountResponse struct {
Approved bool Approved bool `json:"approved"`
Password string Password string `json:"password"`
} }
ListRequest struct { ListRequest struct {
Accounts []Account Accounts []Account `json:"accounts"`
Meta Metadata Meta Metadata `json:"meta"`
} }
ListResponse struct { 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"} m := Metadata{"NA", "NA", "NA"}
if v := ctx.Value("remote"); v != nil { if v := ctx.Value("remote"); v != nil {
m.remote = v.(string) m.Remote = v.(string)
} }
if v := ctx.Value("scheme"); v != nil { if v := ctx.Value("scheme"); v != nil {
m.scheme = v.(string) m.Scheme = v.(string)
} }
if v := ctx.Value("local"); v != nil { if v := ctx.Value("local"); v != nil {
m.local = v.(string) m.Local = v.(string)
} }
return m 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 // 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 // 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. // 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) be := api.am.Backends(keystore.KeyStoreType)

View file

@ -90,7 +90,7 @@ func (ui *CommandlineUI) confirm() bool {
} }
func showMetadata(metadata Metadata) { 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 // 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 weival := request.Transaction.Value
fmt.Printf("--------- Transaction request-------------\n") 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("from: %v\n", request.From.Hex())
fmt.Printf("value: %v wei\n", weival) fmt.Printf("value: %v wei\n", weival)
if len(request.Transaction.Data) > 0 { 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("-------- Sign data request--------------\n")
fmt.Printf("Account: %x\n", request.Address) 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("raw data: \n%v\n", request.Rawdata)
fmt.Printf("message hash: %v\n", request.Hash) fmt.Printf("message hash: %v\n", request.Hash)
fmt.Printf("-------------------------------------------\n") fmt.Printf("-------------------------------------------\n")
@ -154,7 +154,7 @@ func (ui *CommandlineUI) ApproveImport(request *ImportRequest) (ImportResponse,
ui.mu.Lock() ui.mu.Lock()
defer ui.mu.Unlock() 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("A request has been made to import an encrypted keyfile\n")
fmt.Printf("-------------------------------------------\n") fmt.Printf("-------------------------------------------\n")
showMetadata(request.Meta) showMetadata(request.Meta)

View file

@ -24,7 +24,10 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"context"
"encoding/json"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params" "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 " + "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.", "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 { app.Action = func(c *cli.Context) error {
@ -136,6 +143,7 @@ func main() {
if err = server.RegisterName("account", api); err != nil { if err = server.RegisterName("account", api); err != nil {
utils.Fatalf("Could not register signer API: %v", err) utils.Fatalf("Could not register signer API: %v", err)
} }
//server.ListServices()
// Import from file // Import from file
if rfile := c.String("requestfile"); rfile != "" { if rfile := c.String("requestfile"); rfile != "" {
@ -151,13 +159,63 @@ func main() {
log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%s", endpoint)) log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%s", endpoint))
cors := []string{"*"} cors := []string{"*"}
if c.Bool("stdio-ui-test") {
log.Info("Performing UI test")
go testExternalUI(api)
}
rpc.NewHTTPServer(cors, server).Serve(listener) rpc.NewHTTPServer(cors, server).Serve(listener)
return nil return nil
} }
app.Run(os.Args) 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 // Create Account
// curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_new","params":["test"],"id":67}' localhost:8550 // curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_new","params":["test"],"id":67}' localhost:8550

View file

@ -34,60 +34,117 @@ class PipeTransport(ServerTransport):
def receive_message(self): def receive_message(self):
data = self.input.readline() data = self.input.readline()
print("IN ->\n{}".format( data)) #print(">> {}".format( data))
return None, urlparse.unquote(data) return None, urlparse.unquote(data)
def send_reply(self, context, reply): def send_reply(self, context, reply):
print("OUT <-\n{}".format( reply)) #print("<< {}".format( reply))
self.output.write(reply) self.output.write(reply)
self.output.write("\n") self.output.write("\n")
dispatcher = RPCDispatcher() dispatcher = RPCDispatcher()
@dispatcher.public @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 { return {
"Approved" : True, "approved" : False,
"Transaction" : Transaction, "transaction" : None,
"From" : From, "fromaccount" : fromaccount,
"Password" : None, "password" : None,
} }
@dispatcher.public @dispatcher.public
def ApproveSignData(): def ApproveSignData(address=None, raw_data = None, message = None, hash = None, meta = None):
return {"Approved": False, """ Example request
"Password" : None}
{"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 @dispatcher.public
def ApproveExport(): def ApproveExport(address = None, meta = None):
return {"Approved" : False} """ 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 @dispatcher.public
def ApproveImport(): def ApproveImport(meta = None):
return {"Approved" : False, "OldPassword": "", "NewPassword": ""} """ Example request
{"jsonrpc":"2.0","method":"ApproveImport","params":{"Meta":{}},"id":4}
"""
return {"approved" : False, "old_password": "", "new_password": ""}
@dispatcher.public @dispatcher.public
def ApproveListing(): def ApproveListing(accounts=None, meta = None):
return [] """ 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 @dispatcher.public
def ApproveNewAccount(): def ApproveNewAccount(meta = None):
return {"Approved": False, "Password": ""} """
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 @dispatcher.public
def ShowError(text = ""): def ShowError(message = ""):
sys.err.println("Error: %s", text) """
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 return
@dispatcher.public @dispatcher.public
def ShowInfo(text = ""): def ShowInfo(message = ""):
sys.err.println("Info: %s", text) """
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 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 # 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) transport = PipeTransport(p.stdout, p.stdin)
rpc_server = RPCServer( rpc_server = RPCServer(
transport, transport,
@ -97,4 +154,4 @@ def main():
rpc_server.serve_forever() rpc_server.serve_forever()
if __name__ == '__main__': if __name__ == '__main__':
main() main(sys.argv[1:])

View file

@ -109,9 +109,17 @@ func (ui StdIOUI) ApproveNewAccount(request *NewAccountRequest) (NewAccountRespo
} }
func (ui StdIOUI) ShowError(message string) { 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) { 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 { type rwc struct {