cmd/clef: list accounts at startup#26082
cmd/clef: list accounts at startup#26082holiman merged 11 commits intoethereum:masterfrom jmcook1186:jc-clef3
Conversation
| ui_server := core.NewUIServerAPI(apiImpl) | ||
| accs, err := ui_server.ListAccounts(c.Context) | ||
| if err != nil { | ||
| return err | ||
| } |
There was a problem hiding this comment.
This is not correct. We already instantiate core.NewUIServerAPI(apiImpl) at line 569. If we want to maintain access to it, we should just keep it around instead of instantiating a second one here.
Now ,beside that... At this point, we already have a ui. It migth be a console UI, but it might also be a stdioui-ui, i.e.: a UI which uses json-rpc communication over standard-input and standard-output. A proper external UI would use that approach.
That means that we cannot "just print" stuff on stdout. If we need to show information to the user, the idiomatic way of doing it is to tell our ui to do so. The main way of doing that is ui.ShowInfo() -- that will work both for console users and remote-ui users.
If we want to display it only in the console, then we can either ensure to print on stderr (fmt.Fprintf(os.Stderr, "...), or show the info using logging - which is already configured to not disrupt the stdout.
Anyway, I'm not sure I agree with that readme-note any more. The UI can just ask the server about the accounts. If the ui-server is used, as opposed to the external api, then it should not necessitate explicit user approval, so be a very quick operation.
There was a problem hiding this comment.
ah ok i see what you mean. Sorry - I think I've fixed this now, but if it is not a desired feature then no worries.
There was a problem hiding this comment.
I think that what would be more idiomatic, would be if the cliui itself does this. So instead of clef spitting out the accounts on the console, the ui would ask for the accounts, and then show them to the user.
That's the job of the ui, not the job of the backend. You could hook into the OnSignerStartup, and make that call trigger the UI into doing an account listing.
There was a problem hiding this comment.
In order to do that, you will need to 'capture' the backend that we currently ignore
func (ui *CommandlineUI) RegisterUIServer(api *UIServerAPI) {
// noop
}and then extend the method here
func (ui *CommandlineUI) OnSignerStartup(info StartupInfo) {
fmt.Printf("------- Signer info -------\n")
for k, v := range info.Info {
fmt.Printf("* %v : %v\n", k, v)
}
}|
Still this: #26082 (comment) |
ok I think this is addressed - thanks for the hints! The UI registers an API and the call to Terminal looks like this on startup: |
fix linting fix linting fix typo: accoutns -> accounts
holiman
left a comment
There was a problem hiding this comment.
Yep, this is roughly what I had in mind!
| accounts, err := ui.api.ListAccounts(context.Background()) | ||
| if err != nil { | ||
| fmt.Print("error listing accounts", err) | ||
| } | ||
| if len(accounts) == 0 { | ||
| fmt.Println("No accounts known to Clef") | ||
| } else { | ||
| // account info to string for nicer printing | ||
| var addresses string = "\n" | ||
| fmt.Println("Accounts known to Clef:") | ||
| for i, account := range accounts { | ||
| // concat string to avoid repeating "INFO" on terminal | ||
| addresses += fmt.Sprintf("Account %v: %s at %s", i, account.Address, account.URL) | ||
| addresses += "\n" | ||
| } | ||
| fmt.Print(addresses) | ||
| fmt.Println() | ||
| } |
There was a problem hiding this comment.
Generally correct, but two nitpicks.
- We are in an rpc callback, a remote process has issued a remote procedure call. Generally, it might be waiting for a response (but in this particular case,
OnSignerStartupis an answer-less request. Therefore, I think it's a good idea to end this call as soon as possible, and return to the caller and let the caller continue -- instead of inside the handler do a call back for listing accounts.
So, the TLDR; use this method for bootstrapping only, do the account-listing asynchronously by placing this clause inside a go func(){ ... }()
- When printing to
os.Stdout, there are other things that can happen. There may beLogevents happening, and there may be incoming requests prompting the user for action, in different threads/routines.
So it is better to do do the printing only once, to ensure that the chunk of text that we want to display is displayed in one piece, not broken up by other messages/prompts. That can be done either by souping up the lines to write into a []string and then using strings.Join, or by using a strings.Builder.
This is something that I have not done a very good job of myself, in the rest of this file.
There was a problem hiding this comment.
Actually, ignore the second point. Let's just leave it be, and maybe we can fix all of those at the same time, in a separate PR
| } | ||
|
|
||
| func (ui *CommandlineUI) OnSignerStartup(info StartupInfo) { | ||
| func (ui *CommandlineUI) ShowAccounts() string { |
There was a problem hiding this comment.
Myeah, sure, that's one way of doing it. If you do this, then you should make it lowercase - so it's a non-public method showAccounts.
But the primary thing I wanted was to make it async. So instead of just calling
addresses := ui.ShowAccounts()
fmt.Print("Accounts known to Clef:\n\n")
fmt.Printf("%s", addresses)
You would do
go fun(){
addresses := ui.ShowAccounts()
fmt.Print("Accounts known to Clef:\n\n")
fmt.Printf("%s", addresses)
}()
| go func() { | ||
| addresses := ui.showAccounts() | ||
| fmt.Printf("%s", addresses) | ||
| }() |
There was a problem hiding this comment.
If you change the semantics of showAccounts so it actually displays the info by itself (and not return anything), then this can be minimized into
| go func() { | |
| addresses := ui.showAccounts() | |
| fmt.Printf("%s", addresses) | |
| }() | |
| go ui.showAccounts() |
There was a problem hiding this comment.
oh, yes that is much nicer. Done in 4131ddd
|
How does this behave when a user has lots of accounts? |
|
fair point - it will list them all. Might be good idea to restrict listing to < N accounts. |
|
I just commented on #26128, but now I saw the discussion here. When a user has lots of accounts, it will show lots of info. Is that a problem? Because if it is, the same problem will happen basically any time that the user is requested to sign anything... |
|
I mean -- the CLI user-interface is not The Ultimate User Experience. It is meant to provide the basics, with as little fuss as possible. |
PR #26082 added account listing to OnSignerStartup but did not consider the case where a user has a large number of accounts which would be annoying to display. This PR updates showAccounts() so that if there are more than 20 accounts available the user sees the first 20 displayed in the console followed by: First 20 accounts listed (N more available). Co-authored-by: Martin Holst Swende <martin@swende.se>
Reports accounts known to Clef during startup, after master seed is provided by the user.
PR ethereum#26082 added account listing to OnSignerStartup but did not consider the case where a user has a large number of accounts which would be annoying to display. This PR updates showAccounts() so that if there are more than 20 accounts available the user sees the first 20 displayed in the console followed by: First 20 accounts listed (N more available). Co-authored-by: Martin Holst Swende <martin@swende.se>
Reports accounts known to Clef during startup, after master seed is provided by the user.
Reports accounts known to Clef during startup, after master seed is provided by the user.
PR ethereum#26082 added account listing to OnSignerStartup but did not consider the case where a user has a large number of accounts which would be annoying to display. This PR updates showAccounts() so that if there are more than 20 accounts available the user sees the first 20 displayed in the console followed by: First 20 accounts listed (N more available). Co-authored-by: Martin Holst Swende <martin@swende.se>
Reports accounts known to Clef during startup, after master seed is provided by the user.
This addresses a TODO in the Clef README here.
Example output for
clef --keystore testdata/keystore(omitting logs prior to user entering master password):