-
Notifications
You must be signed in to change notification settings - Fork 20.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
cmd/clef: list accounts at startup #26082
Conversation
cmd/clef/main.go
Outdated
ui_server := core.NewUIServerAPI(apiImpl) | ||
accs, err := ui_server.ListAccounts(c.Context) | ||
if err != nil { | ||
return err | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, this is roughly what I had in mind!
signer/core/cliui.go
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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,
OnSignerStartup
is 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 beLog
events 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
signer/core/cliui.go
Outdated
@@ -243,25 +243,30 @@ func (ui *CommandlineUI) OnApprovedTx(tx ethapi.SignTransactionResult) { | |||
} | |||
} | |||
|
|||
func (ui *CommandlineUI) OnSignerStartup(info StartupInfo) { | |||
func (ui *CommandlineUI) ShowAccounts() string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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)
}()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, yes ok: c1dbc02
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost there now!
signer/core/cliui.go
Outdated
go func() { | ||
addresses := ui.showAccounts() | ||
fmt.Printf("%s", addresses) | ||
}() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, yes that is much nicer. Done in 4131ddd
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
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 <[email protected]>
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 <[email protected]>
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):