diff --git a/CHANGES.rst b/CHANGES.rst index de05765e65a514b99b8796274cddc33075121e4a..439c3366a176dfd14761730bc46c3799f39558c7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -109,6 +109,11 @@ Client Also like with script, the ``file:`` prefix can be added for disambiguation. +- Add option ``--force`` to the command ``submit ballots``. This is + mostly for testing purposes: it disables all checks and allows to + cast invalid ballots (unexpected voting period, missing voting + rights, ...) + Baker / Endorser / Accuser -------------------------- diff --git a/docs/protocols/alpha.rst b/docs/protocols/alpha.rst index c79d7c9b0984f67723e84ca71da76f43fb97ac66..fbe325759fc3586098791518e4e0f48a2f99f5c9 100644 --- a/docs/protocols/alpha.rst +++ b/docs/protocols/alpha.rst @@ -23,6 +23,12 @@ Breaking Changes RPC Changes ----------- +- Add a new RPC for querying data found on the voting listings for a + delegate, i.e. voting power, casted ballots and proposals in the + current voting period. (MR :gl:`!4577`) + + ``/chains//blocks//context/delegates//voting_info`` + Bug Fixes --------- diff --git a/src/proto_012_Psithaca/lib_client_commands/client_proto_context_commands.ml b/src/proto_012_Psithaca/lib_client_commands/client_proto_context_commands.ml index 9160af25f32433cda64259b49a62d8e9f7790cf8..3f0d66fb73cbfd1cc1a1804233ca241c8a314d74 100644 --- a/src/proto_012_Psithaca/lib_client_commands/client_proto_context_commands.ml +++ b/src/proto_012_Psithaca/lib_client_commands/client_proto_context_commands.ml @@ -1642,7 +1642,7 @@ let commands_rw () = (switch ~doc: "Do not fail when the checks that try to prevent the user from \ - shooting themselves in the foot do." + shooting themselves in the foot do fail." ~long:"force" ())) (prefixes ["submit"; "proposals"; "for"] @@ -1676,9 +1676,11 @@ let commands_rw () = cctxt >>=? fun info -> (match info.current_period_kind with - | Proposal -> return_unit - | _ -> cctxt#error "Not in a proposal period") - >>=? fun () -> + | Proposal -> Lwt.return_unit + | _ -> + (if force then cctxt#warning else cctxt#error) + "Not in a proposal period") + >>= fun () -> Shell_services.Protocol.list cctxt >>=? fun known_protos -> get_proposals ~chain:cctxt#chain ~block:cctxt#block cctxt >>=? fun known_proposals -> @@ -1801,7 +1803,15 @@ let commands_rw () = command ~group ~desc:"Submit a ballot" - (args2 verbose_signing_switch dry_run_switch) + (args3 + verbose_signing_switch + dry_run_switch + (switch + ~doc: + "Do not fail when the checks that try to prevent the user from \ + shooting themselves in the foot do fail." + ~long:"force" + ())) (prefixes ["submit"; "ballot"; "for"] @@ ContractAlias.destination_param ~name:"delegate" @@ -1826,7 +1836,7 @@ let commands_rw () = | "pass" -> return Vote.Pass | s -> failwith "Invalid ballot: '%s'" s)) @@ stop) - (fun (verbose_signing, dry_run) + (fun (verbose_signing, dry_run, force) (_name, source) proposal ballot @@ -1844,9 +1854,24 @@ let commands_rw () = ~block:cctxt#block cctxt >>=? fun info -> - (match info.current_period_kind with - | Exploration | Promotion -> return_unit - | _ -> cctxt#error "Not in Exploration or Promotion period") + Alpha_services.Voting.current_proposal + cctxt + (cctxt#chain, cctxt#block) + >>=? fun current_proposal -> + (match (info.current_period_kind, current_proposal) with + | ((Exploration | Promotion), Some current_proposal) -> + if Protocol_hash.equal proposal current_proposal then + return_unit + else + (if force then cctxt#warning else cctxt#error) + "Unexpected proposal, expected: %a" + Protocol_hash.pp + current_proposal + >>= fun () -> return_unit + | _ -> + (if force then cctxt#warning else cctxt#error) + "Not in Exploration or Promotion period" + >>= fun () -> return_unit) >>=? fun () -> submit_ballot cctxt diff --git a/src/proto_013_PtJakart/lib_client_commands/client_proto_context_commands.ml b/src/proto_013_PtJakart/lib_client_commands/client_proto_context_commands.ml index fb6daefc6b76000fa3882c3df7b2a136cc40226f..6316d4c73bcff27ac6d4caaac43ef7ff6f2a86c7 100644 --- a/src/proto_013_PtJakart/lib_client_commands/client_proto_context_commands.ml +++ b/src/proto_013_PtJakart/lib_client_commands/client_proto_context_commands.ml @@ -1749,7 +1749,7 @@ let commands_rw () = (switch ~doc: "Do not fail when the checks that try to prevent the user from \ - shooting themselves in the foot do." + shooting themselves in the foot do fail." ~long:"force" ())) (prefixes ["submit"; "proposals"; "for"] @@ -1783,9 +1783,11 @@ let commands_rw () = cctxt >>=? fun info -> (match info.current_period_kind with - | Proposal -> return_unit - | _ -> cctxt#error "Not in a proposal period") - >>=? fun () -> + | Proposal -> Lwt.return_unit + | _ -> + (if force then cctxt#warning else cctxt#error) + "Not in a proposal period") + >>= fun () -> Shell_services.Protocol.list cctxt >>=? fun known_protos -> get_proposals ~chain:cctxt#chain ~block:cctxt#block cctxt >>=? fun known_proposals -> @@ -1908,7 +1910,15 @@ let commands_rw () = command ~group ~desc:"Submit a ballot" - (args2 verbose_signing_switch dry_run_switch) + (args3 + verbose_signing_switch + dry_run_switch + (switch + ~doc: + "Do not fail when the checks that try to prevent the user from \ + shooting themselves in the foot do fail." + ~long:"force" + ())) (prefixes ["submit"; "ballot"; "for"] @@ ContractAlias.destination_param ~name:"delegate" @@ -1933,7 +1943,7 @@ let commands_rw () = | "pass" -> return Vote.Pass | s -> failwith "Invalid ballot: '%s'" s)) @@ stop) - (fun (verbose_signing, dry_run) + (fun (verbose_signing, dry_run, force) (_name, source) proposal ballot @@ -1951,9 +1961,24 @@ let commands_rw () = ~block:cctxt#block cctxt >>=? fun info -> - (match info.current_period_kind with - | Exploration | Promotion -> return_unit - | _ -> cctxt#error "Not in Exploration or Promotion period") + Alpha_services.Voting.current_proposal + cctxt + (cctxt#chain, cctxt#block) + >>=? fun current_proposal -> + (match (info.current_period_kind, current_proposal) with + | ((Exploration | Promotion), Some current_proposal) -> + if Protocol_hash.equal proposal current_proposal then + return_unit + else + (if force then cctxt#warning else cctxt#error) + "Unexpected proposal, expected: %a" + Protocol_hash.pp + current_proposal + >>= fun () -> return_unit + | _ -> + (if force then cctxt#warning else cctxt#error) + "Not in Exploration or Promotion period" + >>= fun () -> return_unit) >>=? fun () -> submit_ballot cctxt 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 9ba3ef0691b6ea6d6db66a41ad0af1d12b7a5d01..91088fe4d098511636580c7b01523b0fd4c915b1 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 @@ -1749,7 +1749,7 @@ let commands_rw () = (switch ~doc: "Do not fail when the checks that try to prevent the user from \ - shooting themselves in the foot do." + shooting themselves in the foot do fail." ~long:"force" ())) (prefixes ["submit"; "proposals"; "for"] @@ -1783,14 +1783,26 @@ let commands_rw () = cctxt >>=? fun info -> (match info.current_period_kind with - | Proposal -> return_unit - | _ -> cctxt#error "Not in a proposal period") - >>=? fun () -> + | Proposal -> Lwt.return_unit + | _ -> + (if force then cctxt#warning else cctxt#error) + "Not in a proposal period") + >>= 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 (cctxt#chain, cctxt#block) - >>=? fun listings -> + (Alpha_services.Delegate.voting_power + cctxt + (cctxt#chain, cctxt#block) + src_pkh + >>= function + | Ok voting_power -> return (voting_power <> 0L) + | Error + (Environment.Ecoproto_error (Delegate_storage.Not_registered _) + :: _) -> + return false + | Error _ as err -> Lwt.return err) + >>=? fun has_voting_power -> (* 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 @@ -1838,13 +1850,7 @@ let commands_rw () = Protocol_hash.pp p) proposals ; - if - not - (List.exists - (fun (pkh, _) -> - Signature.Public_key_hash.equal pkh src_pkh) - listings) - then + if not has_voting_power then error "Public-key-hash `%a` from account `%s` does not appear to \ have voting rights." @@ -1908,7 +1914,15 @@ let commands_rw () = command ~group ~desc:"Submit a ballot" - (args2 verbose_signing_switch dry_run_switch) + (args3 + verbose_signing_switch + dry_run_switch + (switch + ~doc: + "Do not fail when the checks that try to prevent the user from \ + shooting themselves in the foot do fail." + ~long:"force" + ())) (prefixes ["submit"; "ballot"; "for"] @@ ContractAlias.destination_param ~name:"delegate" @@ -1933,7 +1947,7 @@ let commands_rw () = | "pass" -> return Vote.Pass | s -> failwith "Invalid ballot: '%s'" s)) @@ stop) - (fun (verbose_signing, dry_run) + (fun (verbose_signing, dry_run, force) (_name, source) proposal ballot @@ -1942,7 +1956,7 @@ let commands_rw () = | None -> failwith "only implicit accounts can submit ballot" | Some src_pkh -> Client_keys.get_key cctxt src_pkh - >>=? fun (_src_name, _src_pk, src_sk) -> + >>=? fun (src_name, _src_pk, src_sk) -> get_period_info (* Find period info of the successor, because the operation will be injected on the next block at the earliest *) @@ -1951,10 +1965,46 @@ let commands_rw () = ~block:cctxt#block cctxt >>=? fun info -> - (match info.current_period_kind with - | Exploration | Promotion -> return_unit - | _ -> cctxt#error "Not in Exploration or Promotion period") + Alpha_services.Voting.current_proposal + cctxt + (cctxt#chain, cctxt#block) + >>=? fun current_proposal -> + (match (info.current_period_kind, current_proposal) with + | ((Exploration | Promotion), Some current_proposal) -> + if Protocol_hash.equal proposal current_proposal then + return_unit + else + (if force then cctxt#warning else cctxt#error) + "Unexpected proposal, expected: %a" + Protocol_hash.pp + current_proposal + >>= fun () -> return_unit + | _ -> + (if force then cctxt#warning else cctxt#error) + "Not in Exploration or Promotion period" + >>= fun () -> return_unit) >>=? fun () -> + (Alpha_services.Delegate.voting_power + cctxt + (cctxt#chain, cctxt#block) + src_pkh + >>= function + | Ok voting_power -> return (voting_power <> 0L) + | Error + (Environment.Ecoproto_error (Delegate_storage.Not_registered _) + :: _) -> + return false + | Error _ as err -> Lwt.return err) + >>=? fun has_voting_power -> + (if has_voting_power then Lwt.return_unit + else + (if force then cctxt#warning else cctxt#error) + "Public-key-hash `%a` from account `%s` does not appear to \ + have voting rights." + Signature.Public_key_hash.pp + src_pkh + src_name) + >>= fun () -> submit_ballot cctxt ~chain:cctxt#chain diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index 20c78d441b24cb105ffc53f8026fcd88a9fb7a59..b806e5f5429ff1a49b816daf3a4320f37be780b9 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -2381,6 +2381,22 @@ module Vote : sig type ballot = Yay | Nay | Pass + val equal_ballot : ballot -> ballot -> bool + + type delegate_info = { + voting_power : Int64.t option; + current_ballot : ballot option; + current_proposals : Protocol_hash.t list; + remaining_proposals : int; + } + + val pp_delegate_info : Format.formatter -> delegate_info -> unit + + val delegate_info_encoding : delegate_info Data_encoding.t + + val get_delegate_info : + context -> Signature.Public_key_hash.t -> delegate_info tzresult Lwt.t + val get_voting_power_free : context -> Signature.Public_key_hash.t -> int64 tzresult Lwt.t diff --git a/src/proto_alpha/lib_protocol/delegate_services.ml b/src/proto_alpha/lib_protocol/delegate_services.ml index a8adc4da357e841574cf36cd21379f6b21a16b39..b3daf7ff85bc01f77f5ef93159f7f41a1d6861be 100644 --- a/src/proto_alpha/lib_protocol/delegate_services.ml +++ b/src/proto_alpha/lib_protocol/delegate_services.ml @@ -59,7 +59,7 @@ type info = { delegated_balance : Tez.t; deactivated : bool; grace_period : Cycle.t; - voting_power : int64; + voting_info : Vote.delegate_info; } let info_encoding = @@ -75,28 +75,28 @@ let info_encoding = delegated_balance; deactivated; grace_period; - voting_power; + voting_info; } -> - ( full_balance, - current_frozen_deposits, - frozen_deposits, - staking_balance, - frozen_deposits_limit, - delegated_contracts, - delegated_balance, - deactivated, - grace_period, - voting_power )) - (fun ( full_balance, - current_frozen_deposits, - frozen_deposits, - staking_balance, - frozen_deposits_limit, - delegated_contracts, - delegated_balance, - deactivated, - grace_period, - voting_power ) -> + ( ( full_balance, + current_frozen_deposits, + frozen_deposits, + staking_balance, + frozen_deposits_limit, + delegated_contracts, + delegated_balance, + deactivated, + grace_period ), + voting_info )) + (fun ( ( full_balance, + current_frozen_deposits, + frozen_deposits, + staking_balance, + frozen_deposits_limit, + delegated_contracts, + delegated_balance, + deactivated, + grace_period ), + voting_info ) -> { full_balance; current_frozen_deposits; @@ -107,19 +107,20 @@ let info_encoding = delegated_balance; deactivated; grace_period; - voting_power; + voting_info; }) - (obj10 - (req "full_balance" Tez.encoding) - (req "current_frozen_deposits" Tez.encoding) - (req "frozen_deposits" Tez.encoding) - (req "staking_balance" Tez.encoding) - (opt "frozen_deposits_limit" Tez.encoding) - (req "delegated_contracts" (list Contract.encoding)) - (req "delegated_balance" Tez.encoding) - (req "deactivated" bool) - (req "grace_period" Cycle.encoding) - (req "voting_power" int64)) + (merge_objs + (obj9 + (req "full_balance" Tez.encoding) + (req "current_frozen_deposits" Tez.encoding) + (req "frozen_deposits" Tez.encoding) + (req "staking_balance" Tez.encoding) + (opt "frozen_deposits_limit" Tez.encoding) + (req "delegated_contracts" (list Contract.encoding)) + (req "delegated_balance" Tez.encoding) + (req "deactivated" bool) + (req "grace_period" Cycle.encoding)) + Vote.delegate_info_encoding) let participation_info_encoding = let open Data_encoding in @@ -297,6 +298,15 @@ module S = struct ~output:Data_encoding.int64 RPC_path.(path / "voting_power") + let voting_info = + RPC_service.get_service + ~description: + "Returns the delegate info (e.g. voting power) found in the listings \ + of the current voting period." + ~query:RPC_query.empty + ~output:Vote.delegate_info_encoding + RPC_path.(path / "voting_info") + let participation = RPC_service.get_service ~description: @@ -364,7 +374,7 @@ let register () = Delegate.delegated_balance ctxt pkh >>=? fun delegated_balance -> Delegate.deactivated ctxt pkh >>=? fun deactivated -> Delegate.last_cycle_before_deactivation ctxt pkh >>=? fun grace_period -> - Vote.get_voting_power_free ctxt pkh >|=? fun voting_power -> + Vote.get_delegate_info ctxt pkh >|=? fun voting_info -> { full_balance; current_frozen_deposits = frozen_deposits.current_amount; @@ -375,7 +385,7 @@ let register () = delegated_balance; deactivated; grace_period; - voting_power; + voting_info; }) ; register1 ~chunked:false S.full_balance (fun ctxt pkh () () -> trace (Balance_rpc_non_delegate pkh) (Delegate.check_delegate ctxt pkh) @@ -409,6 +419,9 @@ let register () = register1 ~chunked:false S.voting_power (fun ctxt pkh () () -> Delegate.check_delegate ctxt pkh >>=? fun () -> Vote.get_voting_power_free ctxt pkh) ; + register1 ~chunked:false S.voting_info (fun ctxt pkh () () -> + Delegate.check_delegate ctxt pkh >>=? fun () -> + Vote.get_delegate_info ctxt pkh) ; register1 ~chunked:false S.participation (fun ctxt pkh () () -> Delegate.check_delegate ctxt pkh >>=? fun () -> Delegate.delegate_participation_info ctxt pkh) @@ -454,5 +467,8 @@ let grace_period ctxt block pkh = let voting_power ctxt block pkh = RPC_context.make_call1 S.voting_power ctxt block pkh () () +let voting_info ctxt block pkh = + RPC_context.make_call1 S.voting_info ctxt block pkh () () + let participation ctxt block pkh = RPC_context.make_call1 S.participation ctxt block pkh () () diff --git a/src/proto_alpha/lib_protocol/delegate_services.mli b/src/proto_alpha/lib_protocol/delegate_services.mli index e01ffa725aef88f34b48f4a770a54e15f6f54282..8092f2c368cd0652ae1c5bbb7076c0ba60b163e6 100644 --- a/src/proto_alpha/lib_protocol/delegate_services.mli +++ b/src/proto_alpha/lib_protocol/delegate_services.mli @@ -51,7 +51,7 @@ type info = { delegated_balance : Tez.t; deactivated : bool; grace_period : Cycle.t; - voting_power : int64; + voting_info : Vote.delegate_info; } val info_encoding : info Data_encoding.t @@ -119,6 +119,12 @@ val grace_period : val voting_power : 'a #RPC_context.simple -> 'a -> public_key_hash -> int64 shell_tzresult Lwt.t +val voting_info : + 'a #RPC_context.simple -> + 'a -> + public_key_hash -> + Vote.delegate_info shell_tzresult Lwt.t + val participation : 'a #RPC_context.simple -> 'a -> diff --git a/src/proto_alpha/lib_protocol/test/helpers/context.ml b/src/proto_alpha/lib_protocol/test/helpers/context.ml index 07c5f3953a5b537a6e5d02e1ba170c4fc7e05867..1092404a993f002b61248c5aae6faa1a9152a18b 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/context.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/context.ml @@ -250,6 +250,13 @@ module Vote = struct TzEndian.set_int32 bytes 0 ema ; Environment.Context.add b.context ["votes"; "participation_ema"] bytes >|= fun context -> {b with context} + + type delegate_info = Alpha_context.Vote.delegate_info = { + voting_power : Int64.t option; + current_ballot : Alpha_context.Vote.ballot option; + current_proposals : Protocol_hash.t list; + remaining_proposals : int; + } end module Contract = struct @@ -321,7 +328,7 @@ module Delegate = struct delegated_balance : Tez.t; deactivated : bool; grace_period : Cycle.t; - voting_power : int64; + voting_info : Alpha_context.Vote.delegate_info; } let info ctxt pkh = Delegate_services.info rpc_ctxt ctxt pkh @@ -342,6 +349,8 @@ module Delegate = struct let deactivated ctxt pkh = Delegate_services.deactivated rpc_ctxt ctxt pkh + let voting_info ctxt d = Alpha_services.Delegate.voting_info rpc_ctxt ctxt d + let participation ctxt pkh = Delegate_services.participation rpc_ctxt ctxt pkh end diff --git a/src/proto_alpha/lib_protocol/test/helpers/context.mli b/src/proto_alpha/lib_protocol/test/helpers/context.mli index 6e267f7243cf737c1b922334e56860af681cb9df..138d32c4a063bf0d2a6faea20b2dddb7dc9538ca 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/context.mli +++ b/src/proto_alpha/lib_protocol/test/helpers/context.mli @@ -110,6 +110,13 @@ module Vote : sig val get_protocol : Block.t -> Protocol_hash.t Lwt.t val set_participation_ema : Block.t -> int32 -> Block.t Lwt.t + + type delegate_info = Alpha_context.Vote.delegate_info = { + voting_power : Int64.t option; + current_ballot : Alpha_context.Vote.ballot option; + current_proposals : Protocol_hash.t list; + remaining_proposals : int; + } end module Contract : sig @@ -156,7 +163,7 @@ module Delegate : sig delegated_balance : Tez.t; deactivated : bool; grace_period : Cycle.t; - voting_power : int64; + voting_info : Vote.delegate_info; } val info : t -> public_key_hash -> Delegate_services.info tzresult Lwt.t @@ -176,6 +183,8 @@ module Delegate : sig val deactivated : t -> public_key_hash -> bool tzresult Lwt.t + val voting_info : t -> public_key_hash -> Vote.delegate_info tzresult Lwt.t + val participation : t -> public_key_hash -> Delegate.participation_info tzresult Lwt.t end diff --git a/src/proto_alpha/lib_protocol/test/integration/operations/test_voting.ml b/src/proto_alpha/lib_protocol/test/integration/operations/test_voting.ml index 255af2a744bf3cab95ce6f09d2eec4f59766ae4d..9fbb4c0c12280de4ffe81789ee6daecb2122aef6 100644 --- a/src/proto_alpha/lib_protocol/test/integration/operations/test_voting.ml +++ b/src/proto_alpha/lib_protocol/test/integration/operations/test_voting.ml @@ -209,13 +209,13 @@ let get_delegates_and_power_from_listings b = (* compute the voting power of each delegate *) let get_power b delegates loc = - Context.Vote.get_listings (B b) >>=? fun l -> List.map_es (fun delegate -> let pkh = Context.Contract.pkh delegate in - match List.find_opt (fun (del, _) -> del = pkh) l with + Context.Delegate.voting_info (B b) pkh >>=? fun info -> + match info.voting_power with | None -> failwith "%s - Missing delegate" loc - | Some (_, power) -> return power) + | Some power -> return power) delegates (* Checks that the listings are populated *) @@ -224,6 +224,24 @@ let assert_listings_not_empty b ~loc = | [] -> failwith "Unexpected empty listings (%s)" loc | _ -> return_unit +let equal_delegate_info a b = + Option.equal Int64.equal a.Vote.voting_power b.Vote.voting_power + && Option.equal Vote.equal_ballot a.current_ballot b.current_ballot + && List.equal + Protocol_hash.equal + (List.sort Protocol_hash.compare a.current_proposals) + (List.sort Protocol_hash.compare b.current_proposals) + && Int.equal a.remaining_proposals b.remaining_proposals + +let assert_equal_info ~loc a b = + Assert.equal + ~loc + equal_delegate_info + "delegate_info" + Vote.pp_delegate_info + a + b + let bake_until_first_block_of_next_period ?policy b = Context.Vote.get_current_period (B b) >>=? fun {remaining; _} -> Block.bake_n ?policy Int32.(add remaining one |> to_int) b @@ -284,12 +302,38 @@ let test_successful_vote num_delegates () = let del2 = WithExceptions.Option.get ~loc:__LOC__ @@ List.nth delegates_p1 1 in + let pkh1 = Context.Contract.pkh del1 in + let pkh2 = Context.Contract.pkh del2 in + let pow1 = WithExceptions.Option.get ~loc:__LOC__ @@ List.nth power_p1 0 in + let pow2 = WithExceptions.Option.get ~loc:__LOC__ @@ List.nth power_p1 1 in let props = List.map (fun i -> protos.(i)) (2 -- Constants.max_proposals_per_delegate) in Op.proposals (B b) del1 (Protocol_hash.zero :: props) >>=? fun ops1 -> Op.proposals (B b) del2 [Protocol_hash.zero] >>=? fun ops2 -> Block.bake ~operations:[ops1; ops2] b >>=? fun b -> + Context.Delegate.voting_info (B b) pkh1 >>=? fun info1 -> + Context.Delegate.voting_info (B b) pkh2 >>=? fun info2 -> + assert_equal_info + ~loc:__LOC__ + info1 + { + voting_power = Some pow1; + current_ballot = None; + current_proposals = Protocol_hash.zero :: props; + remaining_proposals = 0; + } + >>=? fun () -> + assert_equal_info + ~loc:__LOC__ + info2 + { + voting_power = Some pow2; + current_ballot = None; + current_proposals = [Protocol_hash.zero]; + remaining_proposals = Constants.max_proposals_per_delegate - 1; + } + >>=? fun () -> (* proposals are now populated *) Context.Vote.get_proposals (B b) >>=? fun ps -> (* correctly count the double proposal for zero *) @@ -344,6 +388,17 @@ let test_successful_vote num_delegates () = Block.bake ~operations b >>=? fun b -> Op.ballot (B b) del1 Protocol_hash.zero Vote.Nay >>=? fun op -> Block.bake ~operations:[op] b >>= fun res -> + Context.Delegate.voting_info (B b) pkh1 >>=? fun info1 -> + assert_equal_info + ~loc:__LOC__ + info1 + { + voting_power = Some pow1; + current_ballot = Some Yay; + current_proposals = []; + remaining_proposals = 0; + } + >>=? fun () -> Assert.proto_error_with_info ~loc:__LOC__ res "Duplicate ballot" >>=? fun () -> (* Allocate votes from weight of active delegates *) diff --git a/src/proto_alpha/lib_protocol/vote_repr.ml b/src/proto_alpha/lib_protocol/vote_repr.ml index e94cc33ddf5263ca18f4b8440bde36f21e2ec207..fd4543942af0cb8d311e6a0c9de2ade1f408d606 100644 --- a/src/proto_alpha/lib_protocol/vote_repr.ml +++ b/src/proto_alpha/lib_protocol/vote_repr.ml @@ -40,3 +40,13 @@ let ballot_encoding = splitted ~binary:(conv_with_guard to_int8 of_int8 int8) ~json:(string_enum [("yay", Yay); ("nay", Nay); ("pass", Pass)]) + +let equal_ballot a b = + match (a, b) with + | (Yay, Yay) | (Nay, Nay) | (Pass, Pass) -> true + | _ -> false + +let pp_ballot ppf = function + | Yay -> Format.fprintf ppf "yay" + | Nay -> Format.fprintf ppf "nay" + | Pass -> Format.fprintf ppf "pass" diff --git a/src/proto_alpha/lib_protocol/vote_repr.mli b/src/proto_alpha/lib_protocol/vote_repr.mli index 8a7d4a59b68574ae6bdf136ff45ca954e95bab2b..adfd1a167d09ea0d0bdf372fa4254105d7737b69 100644 --- a/src/proto_alpha/lib_protocol/vote_repr.mli +++ b/src/proto_alpha/lib_protocol/vote_repr.mli @@ -31,3 +31,7 @@ type proposal = Protocol_hash.t type ballot = Yay | Nay | Pass val ballot_encoding : ballot Data_encoding.t + +val equal_ballot : ballot -> ballot -> bool + +val pp_ballot : Format.formatter -> ballot -> unit diff --git a/src/proto_alpha/lib_protocol/vote_storage.ml b/src/proto_alpha/lib_protocol/vote_storage.ml index abe4ff822e3c44806511c768f096007ad72c0b5b..2892ba2cef7b7c7acc210aa2a3a2aefc6005869b 100644 --- a/src/proto_alpha/lib_protocol/vote_storage.ml +++ b/src/proto_alpha/lib_protocol/vote_storage.ml @@ -106,10 +106,100 @@ let update_listings ctxt = Storage.Vote.Voting_power_in_listings.add ctxt total >>= fun ctxt -> return ctxt +type delegate_info = { + voting_power : Int64.t option; + current_ballot : Vote_repr.ballot option; + current_proposals : Protocol_hash.t list; + remaining_proposals : int; +} + +let pp_delegate_info ppf info = + match info.voting_power with + | None -> Format.fprintf ppf "Voting power: none" + | Some p -> ( + Format.fprintf + ppf + "Voting power: %a" + Tez_repr.pp + (Tez_repr.of_mutez_exn p) ; + (match info.current_ballot with + | None -> () + | Some ballot -> + Format.fprintf ppf "@,Current ballot: %a" Vote_repr.pp_ballot ballot) ; + match info.current_proposals with + | [] -> + if Compare.Int.(info.remaining_proposals <> 0) then + Format.fprintf + ppf + "@,Remaining proposals: %d" + info.remaining_proposals + | proposals -> + Format.fprintf ppf "@,@[Current proposals:" ; + List.iter + (fun p -> Format.fprintf ppf "@,- %a" Protocol_hash.pp p) + proposals ; + Format.fprintf ppf "@]" ; + Format.fprintf + ppf + "@,Remaining proposals: %d" + info.remaining_proposals) + +let delegate_info_encoding = + let open Data_encoding in + conv + (fun {voting_power; current_ballot; current_proposals; remaining_proposals} -> + (voting_power, current_ballot, current_proposals, remaining_proposals)) + (fun (voting_power, current_ballot, current_proposals, remaining_proposals) -> + {voting_power; current_ballot; current_proposals; remaining_proposals}) + (obj4 + (opt "voting_power" int64) + (opt "current_ballot" Vote_repr.ballot_encoding) + (dft "current_proposals" (list Protocol_hash.encoding) []) + (dft "remaining_proposals" int31 0)) + let in_listings = Storage.Vote.Listings.mem let get_listings = Storage.Vote.Listings.bindings +let get_delegate_info ctxt delegate = + Storage.Vote.Listings.find ctxt delegate >>=? fun voting_power -> + match voting_power with + | None -> + return + { + voting_power; + current_proposals = []; + current_ballot = None; + remaining_proposals = 0; + } + | Some _ -> + Voting_period_storage.get_current_kind ctxt >>=? fun period -> + (match period with + | Exploration | Promotion -> Storage.Vote.Ballots.find ctxt delegate + | Proposal | Cooldown | Adoption -> return None) + >>=? fun current_ballot -> + (match period with + | Exploration | Promotion | Cooldown | Adoption -> Lwt.return [] + | Proposal -> + Storage.Vote.Proposals.fold + ctxt + ~order:`Undefined + ~init:[] + ~f:(fun (h, d) acc -> + if Signature.Public_key_hash.equal d delegate then + Lwt.return (h :: acc) + else Lwt.return acc)) + >>= fun current_proposals -> + let remaining_proposals = + match period with + | Proposal -> + Constants_repr.max_proposals_per_delegate + - List.length current_proposals + | _ -> 0 + in + return + {voting_power; current_ballot; current_proposals; remaining_proposals} + let get_voting_power_free ctxt owner = Storage.Vote.Listings.find ctxt owner >|=? Option.value ~default:0L diff --git a/src/proto_alpha/lib_protocol/vote_storage.mli b/src/proto_alpha/lib_protocol/vote_storage.mli index 27bb7eefbb6ef1411a7e16b5b5e7fafaeb1ea596..7e901d7d44c4fd2823244904b6cb3d6b499ef517 100644 --- a/src/proto_alpha/lib_protocol/vote_storage.mli +++ b/src/proto_alpha/lib_protocol/vote_storage.mli @@ -80,6 +80,20 @@ val in_listings : Raw_context.t -> Signature.Public_key_hash.t -> bool Lwt.t val get_listings : Raw_context.t -> (Signature.Public_key_hash.t * int64) list Lwt.t +type delegate_info = { + voting_power : Int64.t option; + current_ballot : Vote_repr.ballot option; + current_proposals : Protocol_hash.t list; + remaining_proposals : int; +} + +val pp_delegate_info : Format.formatter -> delegate_info -> unit + +val delegate_info_encoding : delegate_info Data_encoding.t + +val get_delegate_info : + Raw_context.t -> Signature.public_key_hash -> delegate_info tzresult Lwt.t + val get_voting_power_free : Raw_context.t -> Signature.public_key_hash -> int64 tzresult Lwt.t diff --git a/tezt/_regressions/rpc/alpha.client.delegates.out b/tezt/_regressions/rpc/alpha.client.delegates.out index 578db39278184c0d9718b158fdd7622397bfe37e..5b87aa0650a66b326124a82c9b324244a5e67ee6 100644 --- a/tezt/_regressions/rpc/alpha.client.delegates.out +++ b/tezt/_regressions/rpc/alpha.client.delegates.out @@ -22,7 +22,7 @@ rpc/alpha.client.delegates.out "frozen_deposits": "200000000000", "staking_balance": "4000000000000", "delegated_contracts": [ "[PUBLIC_KEY_HASH]" ], "delegated_balance": "0", "deactivated": false, "grace_period": 5, - "voting_power": "4000000000000" } + "voting_power": "4000000000000", "remaining_proposals": 20 } ./tezos-client rpc get '/chains/main/blocks/head/context/delegates/[PUBLIC_KEY_HASH]/full_balance' "4000000000000" diff --git a/tezt/_regressions/rpc/alpha.light.delegates.out b/tezt/_regressions/rpc/alpha.light.delegates.out index 5caaa289f2e76ec15d3cc5641c15527e872a8a44..86f0c849ccff7933a9476d928051ff402cd00030 100644 --- a/tezt/_regressions/rpc/alpha.light.delegates.out +++ b/tezt/_regressions/rpc/alpha.light.delegates.out @@ -24,7 +24,7 @@ protocol of light mode unspecified, using the node's protocol: ProtoGenesisGenes "frozen_deposits": "200000000000", "staking_balance": "4000000000000", "delegated_contracts": [ "[PUBLIC_KEY_HASH]" ], "delegated_balance": "0", "deactivated": false, "grace_period": 5, - "voting_power": "4000000000000" } + "voting_power": "4000000000000", "remaining_proposals": 20 } protocol of light mode unspecified, using the node's protocol: ProtoGenesisGenesisGenesisGenesisGenesisGenesk612im ./tezos-client --mode light rpc get '/chains/main/blocks/head/context/delegates/[PUBLIC_KEY_HASH]/full_balance' diff --git a/tezt/_regressions/rpc/alpha.proxy.delegates.out b/tezt/_regressions/rpc/alpha.proxy.delegates.out index bea1b9c552c43c13cdf2182d4e7b4939e5023de9..172a65ed7b8fbf59684c0cc2874abea13a78c9bf 100644 --- a/tezt/_regressions/rpc/alpha.proxy.delegates.out +++ b/tezt/_regressions/rpc/alpha.proxy.delegates.out @@ -24,7 +24,7 @@ protocol of proxy unspecified, using the node's protocol: ProtoGenesisGenesisGen "frozen_deposits": "200000000000", "staking_balance": "4000000000000", "delegated_contracts": [ "[PUBLIC_KEY_HASH]" ], "delegated_balance": "0", "deactivated": false, "grace_period": 5, - "voting_power": "4000000000000" } + "voting_power": "4000000000000", "remaining_proposals": 20 } protocol of proxy unspecified, using the node's protocol: ProtoGenesisGenesisGenesisGenesisGenesisGenesk612im ./tezos-client --mode proxy rpc get '/chains/main/blocks/head/context/delegates/[PUBLIC_KEY_HASH]/full_balance' diff --git a/tezt/_regressions/rpc/alpha.proxy_server_data_dir.delegates.out b/tezt/_regressions/rpc/alpha.proxy_server_data_dir.delegates.out index 8b3f4059aa82c9e3d1d7ab5b5ea90b8670da21db..82f40f30c09c4c867cd84afcd9cd6474eaf1de89 100644 --- a/tezt/_regressions/rpc/alpha.proxy_server_data_dir.delegates.out +++ b/tezt/_regressions/rpc/alpha.proxy_server_data_dir.delegates.out @@ -22,7 +22,7 @@ rpc/alpha.proxy_server_data_dir.delegates.out "frozen_deposits": "200000000000", "staking_balance": "4000000000000", "delegated_contracts": [ "[PUBLIC_KEY_HASH]" ], "delegated_balance": "0", "deactivated": false, "grace_period": 5, - "voting_power": "4000000000000" } + "voting_power": "4000000000000", "remaining_proposals": 20 } ./tezos-client rpc get '/chains/main/blocks/head/context/delegates/[PUBLIC_KEY_HASH]/full_balance' "4000000000000" diff --git a/tezt/_regressions/rpc/alpha.proxy_server_rpc.delegates.out b/tezt/_regressions/rpc/alpha.proxy_server_rpc.delegates.out index bcb265c312fe11c1dec517ad859b0481fa3d138a..bf6ef936d9ad787f9f99a30b160626badc6175e4 100644 --- a/tezt/_regressions/rpc/alpha.proxy_server_rpc.delegates.out +++ b/tezt/_regressions/rpc/alpha.proxy_server_rpc.delegates.out @@ -22,7 +22,7 @@ rpc/alpha.proxy_server_rpc.delegates.out "frozen_deposits": "200000000000", "staking_balance": "4000000000000", "delegated_contracts": [ "[PUBLIC_KEY_HASH]" ], "delegated_balance": "0", "deactivated": false, "grace_period": 5, - "voting_power": "4000000000000" } + "voting_power": "4000000000000", "remaining_proposals": 20 } ./tezos-client rpc get '/chains/main/blocks/head/context/delegates/[PUBLIC_KEY_HASH]/full_balance' "4000000000000"