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

Support local filesystem and rest-server modes for accounts import, accounts list #7282

Merged
merged 5 commits into from
Apr 21, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions graphql_schema.json
Original file line number Diff line number Diff line change
@@ -1711,6 +1711,65 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "ImportAccountPayload",
"description": null,
"fields": [
{
"name": "publicKey",
"description": "The public key of the imported account",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "PublicKey",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "alreadyImported",
"description": "True if the account had already been imported",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "success",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "ReloadAccountsPayload",
@@ -2434,6 +2493,51 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "importAccount",
"description": "Reload tracked account information from disk",
"args": [
{
"name": "password",
"description": "Password for the account to import",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "path",
"description": "Path to the wallet file, relative to the daemon's current working directory.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "ImportAccountPayload",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "reloadWallets",
"description": "Reload tracked account information from disk",
218 changes: 174 additions & 44 deletions src/app/cli/src/init/client.ml
Original file line number Diff line number Diff line change
@@ -1356,27 +1356,75 @@ let set_snark_work_fee =
(Unsigned.UInt64.to_int (response#setSnarkWorkFee)#lastFee) ) )

let import_key =
let privkey_path = Cli_lib.Flag.privkey_read_path in
let conf_dir = Cli_lib.Flag.conf_dir in
let flags = Args.zip2 privkey_path conf_dir in
Command.async
~summary:
"Import a password protected private key to be tracked by the daemon.\n\
Set CODA_PRIVKEY_PASS environment variable to use non-interactively \
(key will be imported using the same password)."
(Cli_lib.Background_daemon.graphql_init flags
~f:(fun graphql_endpoint (privkey_path, conf_dir) ->
let open Deferred.Let_syntax in
let%bind home = Sys.home_directory () in
let conf_dir =
Option.value
~default:(home ^/ Cli_lib.Default.conf_dir_name)
conf_dir
(let%map_open.Command.Let_syntax access_method =
choose_one
~if_nothing_chosen:(`Default_to `None)
[ Cli_lib.Flag.Uri.Client.rest_graphql_opt
|> map ~f:(Option.map ~f:(fun port -> `GraphQL port))
; Cli_lib.Flag.conf_dir
|> map ~f:(Option.map ~f:(fun conf_dir -> `Conf_dir conf_dir)) ]
and privkey_path = Cli_lib.Flag.privkey_read_path in
fun () ->
let open Deferred.Let_syntax in
let initial_password = ref None in
let do_graphql graphql_endpoint =
let%bind password =
match Sys.getenv Secrets.Keypair.env with
| Some password ->
Deferred.return (Bytes.of_string password)
| None ->
let password =
Secrets.Password.read_hidden_line ~error_help_message:""
"Secret key password: "
in
initial_password := Some password ;
password
in
let graphql =
Graphql_queries.Import_account.make ~path:privkey_path
~password:(Bytes.to_string password) ()
in
match%map Graphql_client.query graphql graphql_endpoint with
| Ok res ->
let res = res#importAccount in
if res#already_imported then Ok (`Already_imported res#public_key)
else Ok (`Imported res#public_key)
| Error (`Failed_request _ as err) ->
Error err
| Error (`Graphql_error _ as err) ->
Ok err
in
let do_local conf_dir =
let wallets_disk_location = conf_dir ^/ "wallets" in
let%bind ({Keypair.public_key; _} as keypair) =
Secrets.Keypair.Terminal_stdin.read_exn ~which:"coda keypair"
privkey_path
let rec go () =
match !initial_password with
| None ->
Secrets.Keypair.Terminal_stdin.read_exn ~which:"mina keypair"
privkey_path
| Some password -> (
(* We've already asked for the password once for a failed
GraphQL query, try that one instead of asking again.
*)
match%bind
Secrets.Keypair.read ~privkey_path
~password:(Lazy.return password)
with
| Ok res ->
return res
| Error `Incorrect_password_or_corrupted_privkey ->
printf "Wrong password! Please try again\n" ;
initial_password := None ;
go ()
| Error err ->
Secrets.Privkey_error.raise ~which:"mina keypair" err )
in
go ()
in
let pk = Public_key.compress public_key in
let%bind wallets =
@@ -1386,26 +1434,52 @@ let import_key =
(* Either we already are tracking it *)
match Secrets.Wallets.check_locked wallets ~needle:pk with
| Some _ ->
printf
!"Key already present, no need to import : %s\n"
(Public_key.Compressed.to_base58_check
(Public_key.compress public_key)) ;
Deferred.unit
Deferred.return (`Already_imported pk)
| None ->
(* Or we import it *)
let%bind _ =
let%map pk =
Secrets.Wallets.import_keypair_terminal_stdin wallets keypair
in
(* Attempt to reload, but if you can't connect to daemon, it's ok *)
let%map _response =
Graphql_client.query
(Graphql_queries.Reload_accounts.make ())
graphql_endpoint
in
`Imported pk
in
let print_result = function
| `Already_imported public_key ->
printf
!"Key already present, no need to import : %s\n"
(Public_key.Compressed.to_base58_check public_key)
| `Imported public_key ->
printf
!"\n😄 Imported account!\nPublic key: %s\n"
(Public_key.Compressed.to_base58_check
(Public_key.compress public_key)) ))
(Public_key.Compressed.to_base58_check public_key)
| `Graphql_error _ as e ->
don't_wait_for (Graphql_lib.Client.Connection_error.ok_exn e)
in
match access_method with
| `GraphQL graphql_endpoint -> (
match%map do_graphql graphql_endpoint with
| Ok res ->
print_result res
| Error err ->
don't_wait_for (Graphql_lib.Client.Connection_error.ok_exn err)
)
| `Conf_dir conf_dir ->
let%map res = do_local conf_dir in
print_result res
| `None -> (
let default_graphql_endpoint =
Cli_lib.Flag.(Uri.Client.{Types.name; value= default})
in
match%bind do_graphql default_graphql_endpoint with
| Ok res ->
Deferred.return (print_result res)
| Error _res ->
let conf_dir = Mina_lib.Conf_dir.compute_conf_dir None in
eprintf
"%sWarning: Could not connect to a running daemon.\n\
Importing to local directory %s%s\n"
Bash_colors.orange conf_dir Bash_colors.none ;
let%map res = do_local conf_dir in
print_result res ))

let export_key =
let privkey_path = Cli_lib.Flag.privkey_write_path in
@@ -1488,31 +1562,87 @@ let export_key =
Deferred.unit ))

let list_accounts =
let open Command.Param in
Command.async ~summary:"List all owned accounts"
(Cli_lib.Background_daemon.graphql_init (return ())
~f:(fun graphql_endpoint () ->
let%map response =
Graphql_client.query_exn
(let%map_open.Command.Let_syntax access_method =
choose_one
~if_nothing_chosen:(`Default_to `None)
[ Cli_lib.Flag.Uri.Client.rest_graphql_opt
|> map ~f:(Option.map ~f:(fun port -> `GraphQL port))
; Cli_lib.Flag.conf_dir
|> map ~f:(Option.map ~f:(fun conf_dir -> `Conf_dir conf_dir)) ]
in
fun () ->
let do_graphql graphql_endpoint =
match%map
Graphql_client.query
(Graphql_queries.Get_tracked_accounts.make ())
graphql_endpoint
with
| Ok response -> (
match response#trackedAccounts with
| [||] ->
printf
"😢 You have no tracked accounts!\n\
You can make a new one using `mina accounts create`\n" ;
Ok ()
| accounts ->
Array.iteri accounts ~f:(fun i w ->
printf
"Account #%d:\n\
\ Public key: %s\n\
\ Balance: %s\n\
\ Locked: %b\n"
(i + 1)
(Public_key.Compressed.to_base58_check w#public_key)
(Currency.Balance.to_formatted_string (w#balance)#total)
(Option.value ~default:true w#locked) ) ;
Ok () )
| Error (`Failed_request _ as err) ->
Error err
| Error (`Graphql_error _ as err) ->
don't_wait_for (Graphql_lib.Client.Connection_error.ok_exn err) ;
Ok ()
in
let do_local conf_dir =
let wallets_disk_location = conf_dir ^/ "wallets" in
let%map wallets =
Secrets.Wallets.load ~logger:(Logger.create ())
~disk_location:wallets_disk_location
in
match response#trackedAccounts with
| [||] ->
match wallets |> Secrets.Wallets.pks with
| [] ->
printf
"😢 You have no tracked accounts!\n\
You can make a new one using `mina accounts create`\n"
| accounts ->
Array.iteri accounts ~f:(fun i w ->
printf
"Account #%d:\n\
\ Public key: %s\n\
\ Balance: %s\n\
\ Locked: %b\n"
(i + 1)
(Public_key.Compressed.to_base58_check w#public_key)
(Currency.Balance.to_formatted_string (w#balance)#total)
(Option.value ~default:true w#locked) ) ))
List.iteri accounts ~f:(fun i public_key ->
printf "Account #%d:\n Public key: %s\n" (i + 1)
(Public_key.Compressed.to_base58_check public_key) )
in
match access_method with
| `GraphQL graphql_endpoint -> (
match%map do_graphql graphql_endpoint with
| Ok () ->
()
| Error err ->
don't_wait_for (Graphql_lib.Client.Connection_error.ok_exn err)
)
| `Conf_dir conf_dir ->
do_local conf_dir
| `None -> (
let default_graphql_endpoint =
Cli_lib.Flag.(Uri.Client.{Types.name; value= default})
in
match%bind do_graphql default_graphql_endpoint with
| Ok () ->
Deferred.unit
| Error _res ->
let conf_dir = Mina_lib.Conf_dir.compute_conf_dir None in
eprintf
"%sWarning: Could not connect to a running daemon.\n\
Listing from local directory %s%s\n"
Bash_colors.orange conf_dir Bash_colors.none ;
do_local conf_dir ))

let create_account =
let open Command.Param in
2 changes: 1 addition & 1 deletion src/app/cli/src/init/dune
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@
consensus mina_transition mina_version
mina_user_error
o1trace protocol_version node_status transition_frontier web_client_pipe
web_request graphql_lib genesis_ledger_helper)
web_request graphql_lib genesis_ledger_helper bash_colors)
(instrumentation (backend bisect_ppx))
(preprocessor_deps ../../../../config.mlh)
(preprocess (pps ppx_coda graphql_ppx ppx_version ppx_jane ppx_deriving_yojson)))
Loading