diff --git a/src/proto_alpha/lib_client/client_proto_context.ml b/src/proto_alpha/lib_client/client_proto_context.ml index fc6b6d4bfdb9ba292200b2b9cc95d25a5bdc191a..f609c091e659ad75a248a4a77a5fe2174df29d1c 100644 --- a/src/proto_alpha/lib_client/client_proto_context.ml +++ b/src/proto_alpha/lib_client/client_proto_context.ml @@ -482,6 +482,7 @@ let get_proposals Alpha_services.Voting.proposals cctxt cb let submit_proposals + ?dry_run (cctxt : #Proto_alpha.full) ~chain ~block ?confirmations ~src_sk source proposals = (* We need the next level, not the current *) @@ -490,10 +491,10 @@ let submit_proposals let contents = Single ( Proposals { source ; period ; proposals } ) in Injection.inject_operation cctxt ~chain ~block ?confirmations ~fee_parameter:Injection.dummy_fee_parameter - ~src_sk contents + ?dry_run ~src_sk contents let submit_ballot - (cctxt : #Proto_alpha.full) + ?dry_run (cctxt : #Proto_alpha.full) ~chain ~block ?confirmations ~src_sk source proposal ballot = (* The user must provide the proposal explicitly to make himself sure for what he is voting. *) @@ -502,7 +503,7 @@ let submit_ballot let contents = Single ( Ballot { source ; period ; proposal ; ballot } ) in Injection.inject_operation cctxt ~chain ~block ?confirmations ~fee_parameter:Injection.dummy_fee_parameter - ~src_sk contents + ?dry_run ~src_sk contents let pp_operation formatter (a : Alpha_block_services.operation) = match a.receipt, a.protocol_data with diff --git a/src/proto_alpha/lib_client/client_proto_context.mli b/src/proto_alpha/lib_client/client_proto_context.mli index a5cc310d90532138d9636bf2e1b79291a1a70529..7badc296e885852b1a11d06aba0c7f98d0e9a464 100644 --- a/src/proto_alpha/lib_client/client_proto_context.mli +++ b/src/proto_alpha/lib_client/client_proto_context.mli @@ -251,6 +251,7 @@ val get_proposals : Int32.t Alpha_environment.Protocol_hash.Map.t tzresult Lwt.t val submit_proposals: + ?dry_run:bool -> #Proto_alpha.full -> chain:Shell_services.chain -> block:Shell_services.block -> @@ -261,6 +262,7 @@ val submit_proposals: Kind.proposals Injection.result_list tzresult Lwt.t val submit_ballot: + ?dry_run:bool -> #Proto_alpha.full -> chain:Shell_services.chain -> block:Shell_services.block -> diff --git a/src/proto_alpha/lib_client_commands/client_proto_context_commands.ml b/src/proto_alpha/lib_client_commands/client_proto_context_commands.ml index e0149dedd5a39996a3ceb93dbe32ce6c55859291..ff98e7be7417bfa32cbdd2c9c16afa58ce3dd66c 100644 --- a/src/proto_alpha/lib_client_commands/client_proto_context_commands.ml +++ b/src/proto_alpha/lib_client_commands/client_proto_context_commands.ml @@ -656,7 +656,12 @@ let commands version () = end ; command ~group ~desc: "Submit protocol proposals" - no_options + (args2 + dry_run_switch + (switch + ~doc:"Do not fail when the checks that try to prevent the user \ + from shooting themselves in the foot do." + ~long:"force" ())) (prefixes [ "submit" ; "proposals" ; "for" ] @@ ContractAlias.destination_param ~name: "delegate" @@ -670,7 +675,7 @@ let commands version () = match Protocol_hash.of_b58check_opt x with | None -> Error_monad.failwith "Invalid proposal hash: '%s'" x | Some hash -> return hash)))) - begin fun () (_name, source) proposals (cctxt : Proto_alpha.full) -> + begin fun (dry_run, force) (_name, source) proposals (cctxt : Proto_alpha.full) -> get_period_info ~chain:cctxt#chain ~block:cctxt#block cctxt >>=? fun info -> begin match info.current_period_kind with | Proposal -> return_unit @@ -678,41 +683,106 @@ let commands version () = end >>=? fun () -> Shell_services.Protocol.list cctxt >>=? fun known_protos -> get_proposals ~chain:cctxt#chain ~block:cctxt#block cctxt >>=? fun known_proposals -> + Alpha_services.Voting.listings cctxt (`Main, cctxt#block) >>=? fun listings -> + Client_proto_context.get_manager + cctxt ~chain:`Main ~block:cctxt#block + source >>=? fun (src_name, src_pkh, _src_pk, src_sk) -> (* for a proposal to be valid it must either a protocol that was already proposed by somebody else or a protocol known by the node, because the user is the first proposer and just injected it with tezos-admin-client *) let check_proposals proposals : bool tzresult Lwt.t = let n = List.length proposals in - if n = 0 then cctxt#error "Empty proposal" - else if n > Constants.fixed.max_proposals_per_delegate then - cctxt#error "Too many proposals" + let errors = ref [] in + let error ppf = + Format.kasprintf (fun s -> errors := s :: !errors) ppf in + if n = 0 then error "Empty proposal list." ; + if n > Constants.fixed.max_proposals_per_delegate then + error "Too many proposals: %d > %d." + n Constants.fixed.max_proposals_per_delegate ; + begin match + Base.List.find_all_dups ~compare:Protocol_hash.compare proposals with + | [] -> () + | dups -> + error "There %s: %a." + (if List.length dups = 1 + then "is a duplicate proposal" + else "are duplicate proposals") + Format.( + pp_print_list + ~pp_sep:(fun ppf () -> pp_print_string ppf ", ") + Protocol_hash.pp) + dups + end ; + List.iter (fun (p : Protocol_hash.t) -> + if (List.mem p known_protos) || + (Alpha_environment.Protocol_hash.Map.mem p known_proposals) + then () + else + error "Protocol %a is not a known proposal." Protocol_hash.pp p + ) proposals ; + if not ( + List.exists + (fun (pkh, _) -> Signature.Public_key_hash.equal pkh src_pkh) + listings) + then + error "Public-key-hash `%a` from account `%s` does not appear to \ + have voting rights." + Signature.Public_key_hash.pp src_pkh + src_name ; + if !errors <> [] + then + cctxt#message "There %s with the submission:%t" + (if List.length !errors = 1 then "is an issue" else "are issues") + Format.(fun ppf -> + pp_print_cut ppf () ; + pp_open_vbox ppf 0 ; + List.iter (fun msg -> + pp_open_hovbox ppf 2 ; + pp_print_string ppf "* "; + pp_print_text ppf msg ; + pp_close_box ppf () ; + pp_print_cut ppf ()) + !errors ; + pp_close_box ppf ()) + >>= fun () -> + return_false else - fold_left_s (fun acc (p : Protocol_hash.t) -> - if (List.mem p known_protos) || - (Alpha_environment.Protocol_hash.Map.mem p known_proposals) - then return acc - else cctxt#message "Protocol %a is not a known proposal" - Protocol_hash.pp p >>= fun () -> - return false) - true proposals + return_true in check_proposals proposals >>=? fun all_valid -> begin if all_valid then - cctxt#message "All proposals are valid" + cctxt#message "All proposals are valid." + else if force then + cctxt#message + "Some proposals are not valid, but `--force` was used." else - cctxt#error "Submission failed because of invalid proposals" + cctxt#error "Submission failed because of invalid proposals." end >>= fun () -> - Client_proto_context.get_manager - cctxt ~chain:cctxt#chain ~block:cctxt#block - source >>=? fun (_src_name, src_pkh, _src_pk, src_sk) -> - submit_proposals cctxt ~chain:cctxt#chain ~block:cctxt#block ~src_sk src_pkh - proposals >>=? fun _res -> - return_unit + submit_proposals ~dry_run + cctxt ~chain:cctxt#chain ~block:cctxt#block ~src_sk src_pkh + proposals >>= function + | Ok _res -> return_unit + | Error errs -> + begin match errs with + | Unregistred_error + (`O [ "kind", `String "generic" ; + "error", `String msg ]) :: [] -> + cctxt#message "Error:@[@.%a@]" + Format.pp_print_text + (String.split_on_char ' ' msg + |> List.filter (function "" | "\n" -> false | _ -> true) + |> String.concat " " + |> String.map (function '\n' | '\t' -> ' ' | c -> c)) + | el -> + cctxt#message "Error:@ %a" pp_print_error el + end + >>= fun () -> + failwith "Failed to submit proposals" end ; command ~group ~desc: "Submit a ballot" - no_options + (args1 dry_run_switch) (prefixes [ "submit" ; "ballot" ; "for" ] @@ ContractAlias.destination_param ~name: "delegate" @@ -727,7 +797,7 @@ let commands version () = | Some hash -> return hash)) @@ param ~name:"ballot" - ~desc:"the ballot value (yay, nay or pass)" + ~desc:"the ballot value (yea/yay, nay, or pass)" (parameter ~autocomplete: (fun _ -> return [ "yea" ; "nay" ; "pass" ]) (fun _ s -> (* We should have [Vote.of_string]. *) @@ -737,7 +807,7 @@ let commands version () = | "pass" -> return Vote.Pass | s -> failwith "Invalid ballot: '%s'" s)) @@ stop) - begin fun () (_name, source) proposal ballot (cctxt : Proto_alpha.full) -> + begin fun dry_run (_name, source) proposal ballot (cctxt : Proto_alpha.full) -> get_period_info ~chain:cctxt#chain ~block:cctxt#block cctxt >>=? fun info -> begin match info.current_period_kind with | Testing_vote | Promotion_vote -> return_unit @@ -747,7 +817,7 @@ let commands version () = cctxt ~chain:cctxt#chain ~block:cctxt#block source >>=? fun (_src_name, src_pkh, _src_pk, src_sk) -> submit_ballot cctxt ~chain:cctxt#chain ~block:cctxt#block ~src_sk src_pkh - proposal ballot >>=? fun _res -> + ~dry_run proposal ballot >>=? fun _res -> return_unit end ; @@ -763,6 +833,7 @@ let commands version () = Proto_alpha.Alpha_context.Voting_period.kind_encoding info.current_period_kind) info.remaining >>= fun () -> + Shell_services.Protocol.list cctxt >>=? fun known_protos -> get_proposals ~chain:cctxt#chain ~block:cctxt#block cctxt >>=? fun props -> let ranks = Alpha_environment.Protocol_hash.Map.bindings props |> List.sort (fun (_,v1) (_,v2) -> Int32.(compare v2 v1)) in @@ -773,13 +844,17 @@ let commands version () = in match info.current_period_kind with | Proposal -> - (* TODO improve printing of proposals *) - let proposals_string = - if List.length ranks = 0 then " none" else - List.fold_left (fun acc (p,w) -> - Format.asprintf "%s\n%a %ld" acc Protocol_hash.pp p w) "" ranks - in - cctxt#answer "Current proposals:%s" proposals_string + cctxt#answer "Current proposals:%t" + Format.(fun ppf -> + pp_print_cut ppf () ; + pp_open_vbox ppf 0 ; + List.iter + (fun (p, w) -> + fprintf ppf "* %a %ld (%sknown by the node)@." + Protocol_hash.pp p w + (if (List.mem p known_protos) then "" else "not ")) + ranks ; + pp_close_box ppf () ) >>= fun () -> return_unit | Testing_vote | Promotion_vote -> print_proposal info.current_proposal >>= fun () ->