Skip to content
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

Merged
merged 11 commits into from
Nov 7, 2022
Merged

cmd/clef: list accounts at startup #26082

merged 11 commits into from
Nov 7, 2022

Conversation

jmcook1186
Copy link
Contributor

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):

...
## Master Password

Please enter the password to decrypt the master seed
> 
-----------------------
INFO [11-01|10:17:23.779] Starting signer                          chainid=1 keystore=testdata/keystore light-kdf=false advanced=false
DEBUG[11-01|10:17:23.779] FS scan times                            list="48.716µs" set="12.935µs" diff="2.75µs"
DEBUG[11-01|10:17:23.781] Ledger support enabled 
DEBUG[11-01|10:17:23.781] Trezor support enabled via HID 
DEBUG[11-01|10:17:23.781] Trezor support enabled via WebUSB 
INFO [11-01|10:17:23.781] Smartcard socket file missing, disabling err="stat /run/pcscd/pcscd.comm: no such file or directory"
INFO [11-01|10:17:23.781] Audit logs configured                    file=audit.log
DEBUG[11-01|10:17:23.781] IPCs registered                          namespaces=account
INFO [11-01|10:17:23.782] IPC endpoint opened                      url=/home/joe/.clef/clef.ipc

Accounts known to Clef:

0xEcF899FCb2C48340e92d746Ee8F37958A0330678
0x1Aec041B2BdDAF395D474C50283436c0FDD2CC4d

------- Signer info -------
* intapi_version : 7.0.1
* extapi_version : 6.1.0

cmd/clef/main.go Outdated
Comment on lines 639 to 643
ui_server := core.NewUIServerAPI(apiImpl)
accs, err := ui_server.ListAccounts(c.Context)
if err != nil {
return err
}
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor

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)
	}
}

@holiman
Copy link
Contributor

holiman commented Nov 4, 2022

Still this: #26082 (comment)

@jmcook1186
Copy link
Contributor Author

jmcook1186 commented Nov 6, 2022

Still this: #26082 (comment)

ok I think this is addressed - thanks for the hints! The UI registers an API and the call to listAccounts() now originates inside ui.OnSignerStartup.

Terminal looks like this on startup:

Accounts known to Clef:

Account 0: 0x4f4094BaBd1A8c433e0f52A6ee3B6ff32dEe6a9c at keystore:///home/user/Code/go-ethereum/testdata/keystore/UTC--2022-11-01T17-05-01.517877299Z--4f4094babd1a8c433e0f52a6ee3b6ff32dee6a9c
Account 1: 0x8Ef15919F852A8034688a71d8b57Ab0187364009 at keystore:///home/user/Code/go-ethereum/testdata/keystore/UTC--2022-11-01T17-05-11.100536003Z--8ef15919f852a8034688a71d8b57ab0187364009
Account 2: 0x99A94ac1168B301449970310904fc38c7Bf55037 at keystore:///home/user/Code/go-ethereum/testdata/keystore/UTC--2022-11-02T11-40-55.527987625Z--99a94ac1168b301449970310904fc38c7bf55037

------- Signer info -------
* extapi_version : 6.1.0
* extapi_http : n/a
* extapi_ipc : /home/joe/.clef/clef.ipc
* intapi_version : 7.0.1

fix linting

fix linting

fix typo: accoutns -> accounts
Copy link
Contributor

@holiman holiman left a 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!

cmd/clef/main.go Outdated Show resolved Hide resolved
cmd/clef/main.go Outdated Show resolved Hide resolved
cmd/clef/main.go Outdated Show resolved Hide resolved
Comment on lines 247 to 264
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()
}
Copy link
Contributor

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.

  1. 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(){ ... }()

  1. When printing to os.Stdout, there are other things that can happen. There may be Log 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, 56e46c3 breaks the account listing into a separate func and cuts out whitespace as suggested. adc44ff minimizes print statements by bundling all the account info and titling into a single string.

Copy link
Contributor

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 Show resolved Hide resolved
signer/core/cliui.go Outdated Show resolved Hide resolved
@@ -243,25 +243,30 @@ func (ui *CommandlineUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
}
}

func (ui *CommandlineUI) OnSignerStartup(info StartupInfo) {
func (ui *CommandlineUI) ShowAccounts() string {
Copy link
Contributor

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)
	}()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, yes ok: c1dbc02

Copy link
Contributor

@holiman holiman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost there now!

Comment on lines 267 to 270
go func() {
addresses := ui.showAccounts()
fmt.Printf("%s", addresses)
}()
Copy link
Contributor

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

Suggested change
go func() {
addresses := ui.showAccounts()
fmt.Printf("%s", addresses)
}()
go ui.showAccounts()

Copy link
Contributor Author

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

cmd/clef/main.go Outdated Show resolved Hide resolved
Copy link
Contributor

@holiman holiman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@holiman holiman added this to the 1.11.0 milestone Nov 7, 2022
signer/core/cliui.go Outdated Show resolved Hide resolved
signer/core/cliui.go Outdated Show resolved Hide resolved
@holiman holiman merged commit 55a92fa into ethereum:master Nov 7, 2022
@fjl
Copy link
Contributor

fjl commented Nov 7, 2022

How does this behave when a user has lots of accounts?

@jmcook1186
Copy link
Contributor Author

fair point - it will list them all. Might be good idea to restrict listing to < N accounts.

@holiman
Copy link
Contributor

holiman commented Nov 7, 2022

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...

@holiman
Copy link
Contributor

holiman commented Nov 7, 2022

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.

holiman added a commit that referenced this pull request Nov 8, 2022
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>
shekhirin pushed a commit to shekhirin/go-ethereum that referenced this pull request Jun 6, 2023
Reports accounts known to Clef during startup, after master seed is provided by the user.
shekhirin pushed a commit to shekhirin/go-ethereum that referenced this pull request Jun 6, 2023
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants