From e0a871892238495b02a7935533e963c9aac0fa8f Mon Sep 17 00:00:00 2001 From: Jun FURUSE Date: Sun, 2 Dec 2018 01:34:03 +0000 Subject: [PATCH] Client: add voting commands tezos-client show votes to show the current voting status tezos-client submit proposals for PKH PROP1 .. to submit proposals tezos-client submit ballot for PKH PROP yay|nay|pass to submit a ballot --- scripts/protocol_parameters.json | 1 + src/bin_client/test/dune | 18 +- src/bin_client/test/protocol_parameters.json | 1 + src/bin_client/test/test_voting.sh | 171 ++++++++++++++++++ .../lib_client/client_proto_context.ml | 121 +++++++++++++ .../lib_client/client_proto_context.mli | 31 ++++ .../lib_client/operation_result.ml | 1 + .../client_proto_context_commands.ml | 69 ++++++- 8 files changed, 411 insertions(+), 2 deletions(-) create mode 100755 src/bin_client/test/test_voting.sh diff --git a/scripts/protocol_parameters.json b/scripts/protocol_parameters.json index 25910b6de..92922ca2c 100644 --- a/scripts/protocol_parameters.json +++ b/scripts/protocol_parameters.json @@ -42,6 +42,7 @@ "time_between_blocks" : [ "1", "0" ], "blocks_per_roll_snapshot" : 4, "blocks_per_cycle" : 8, + "blocks_per_voting_period" : 64, "preserved_cycles" : 2, "proof_of_work_threshold": "-1" } diff --git a/src/bin_client/test/dune b/src/bin_client/test/dune index b22b5b459..fcdf327db 100644 --- a/src/bin_client/test/dune +++ b/src/bin_client/test/dune @@ -140,6 +140,21 @@ %{bin:tezos-client} %{bin:tezos-admin-client}))) +(alias + (name runtest_voting.sh) + (locks /tcp-port/18731 + /tcp-port/19731) + (deps sandbox.json + test_lib.inc.sh + (glob_files demo/*)) + (action + (run bash %{dep:test_voting.sh} + %{bin:tezos-sandboxed-node.sh} + %{bin:tezos-node} + %{bin:tezos-init-sandboxed-client.sh} + %{bin:tezos-client} + %{bin:tezos-admin-client}))) + (alias (name runtest) (deps (alias runtest_basic.sh) @@ -150,4 +165,5 @@ (alias runtest_multinode.sh) (alias runtest_injection.sh) (alias runtest_tls.sh) - (alias runtest_cors.sh))) + (alias runtest_cors.sh) + (alias runtest_voting.sh))) diff --git a/src/bin_client/test/protocol_parameters.json b/src/bin_client/test/protocol_parameters.json index d6ed21c15..7a76b3539 100644 --- a/src/bin_client/test/protocol_parameters.json +++ b/src/bin_client/test/protocol_parameters.json @@ -21,6 +21,7 @@ "time_between_blocks" : [ "1", "0" ], "blocks_per_cycle" : 128, "blocks_per_roll_snapshot" : 32, + "blocks_per_voting_period" : 64, "preserved_cycles" : 1, "proof_of_work_threshold": "-1" } diff --git a/src/bin_client/test/test_voting.sh b/src/bin_client/test/test_voting.sh new file mode 100755 index 000000000..1306a965e --- /dev/null +++ b/src/bin_client/test/test_voting.sh @@ -0,0 +1,171 @@ +#!/bin/bash + +# Requires jq command + +set -e +set -o pipefail + +test_dir="$(cd "$(dirname "$0")" && echo "$(pwd -P)")" +source $test_dir/test_lib.inc.sh "$@" + +# Prepare a config with shorter blocks_per_voting_period +temp=`mktemp` +sed -e 's/"blocks_per_voting_period" : [0-9]*/"blocks_per_voting_period" : 4/' $parameters_file > $temp +parameters_file=$temp +echo params=${parameters_file} + +start_node 1 +activate_alpha + +echo Alpha activated + +res=`$client show votes` +echo $res + +[ `echo $res | jq .voting_period_position` = '1' ] \ + || { echo "strange voting_period_position" ; exit 1 ; } +[ `echo $res | jq .voting_period_remaining` = '3' ] \ + || { echo "strange voting_period_remaining" ; exit 1 ; } +[ `echo $res | jq .listings` = '[]' ] \ + || { echo "empty listings bug was fixed?!" ; exit 1 ; } + +bake # pos=2 + +res=`$client show votes` +[ `echo $res | jq .voting_period_position` = '2' ] \ + || { echo "strange voting_period_position" ; exit 1 ; } +[ `echo $res | jq .voting_period_remaining` = '2' ] \ + || { echo "strange voting_period_remaining" ; exit 1 ; } + +bake # pos=3 +bake # new period, pos=0 + +res=`$client show votes` +[ `echo $res | jq .voting_period_position` = '0' ] \ + || { echo "strange voting_period_position" ; exit 1 ; } +[ `echo $res | jq .voting_period_remaining` = '4' ] \ + || { echo "strange voting_period_remaining" ; exit 1 ; } +[ "`echo $res | jq .listings`" != '[]' ] \ + || { echo "strange listings" ; exit 1 ; } + +proto1='ProtoBetaBetaBetaBetaBetaBetaBetaBetaBet11111a5ug96' +proto2='Proto222222222222222222222222222222222225b7e3dV844j' +proto3='Proto33333333333333333333333333333333333c6379eVysnU' + +$client submit proposals for bootstrap1 $proto1 +$client submit proposals for bootstrap2 $proto1 $proto2 +$client submit proposals for bootstrap3 $proto2 +$client submit proposals for bootstrap4 $proto3 + +bake + +res=`$client show votes` +[ "`echo $res | jq .proposals`" != '[]' ] \ + || { echo "strange proposals" ; exit 1 ; } + +bake # pos=2 + +echo Breaking the tie + +$client submit proposals for bootstrap3 $proto1 # To make $proto1 win +$client show votes + +bake # pos=3 +bake # new period! pos=0 + +echo Proposal should be done +res=`$client show votes` +echo $res +[ `echo $res | jq .voting_period_position` = '0' ] \ + || { echo "strange voting_period_position" ; exit 1 ; } +[ `echo $res | jq .voting_period_remaining` = '4' ] \ + || { echo "strange voting_period_remaining" ; exit 1 ; } +[ `echo $res | jq .current_period_kind` = '"testing_vote"' ] \ + || { echo "strange current_period_kind" ; exit 1 ; } +[ "`echo $res | jq .listings`" != '[]' ] \ + || { echo "strange listings" ; exit 1 ; } +[ `echo $res | jq .current_proposal` = '"'$proto1'"' ] \ + || { echo "strange current_proposal" ; exit 1 ; } + +echo Ballots 1 +$client submit ballot for bootstrap1 $proto1 yay +$client submit ballot for bootstrap2 $proto1 yay +$client submit ballot for bootstrap3 $proto1 yay +$client submit ballot for bootstrap4 $proto1 yay + +bake # pos=1 + +# They cannot change their mind. +echo "Ballots 2 (should fail)" +$client submit ballot for bootstrap1 $proto1 yay \ + && { echo "submit ballot cannot be called twice" ; exit 1 ; } + +bake # pos=2 +bake # pos=3 + +$client show votes + +bake # new period pos=0 + +echo Testing vote should be done +res=`$client show votes` +[ `echo $res | jq .voting_period_position` = '0' ] \ + || { echo "strange voting_period_position" ; exit 1 ; } +[ `echo $res | jq .voting_period_remaining` = '4' ] \ + || { echo "strange voting_period_remaining" ; exit 1 ; } +[ `echo $res | jq .current_period_kind` = '"testing"' ] \ + || { echo "strange current_period_kind" ; exit 1 ; } +[ "`echo $res | jq .listings`" = '[]' ] \ + || { echo "strange listings" ; exit 1 ; } +[ `echo $res | jq .current_proposal` = '"'$proto1'"' ] \ + || { echo "strange current_proposal" ; exit 1 ; } +[ `echo $res | jq .ballot_list` = '[]' ] \ + || { echo "strange ballot_list" ; exit 1 ; } + +bake # pos=1 +bake # pos=2 +bake # pos=3 +bake # new period pos=0 + +echo Testing should be done +res=`$client show votes` +[ `echo $res | jq .voting_period_position` = '0' ] \ + || { echo "strange voting_period_position" ; exit 1 ; } +[ `echo $res | jq .voting_period_remaining` = '4' ] \ + || { echo "strange voting_period_remaining" ; exit 1 ; } +[ `echo $res | jq .current_period_kind` = '"promotion_vote"' ] \ + || { echo "strange current_period_kind" ; exit 1 ; } +[ "`echo $res | jq .listings`" != '[]' ] \ + || { echo "strange listings" ; exit 1 ; } +[ `echo $res | jq .current_proposal` = '"'$proto1'"' ] \ + || { echo "strange current_proposal" ; exit 1 ; } +[ `echo $res | jq .ballot_list` = '[]' ] \ + || { echo "strange ballot_list" ; exit 1 ; } + +$client submit ballot for bootstrap1 $proto1 yay +$client submit ballot for bootstrap2 $proto1 yay +$client submit ballot for bootstrap3 $proto1 yay +$client submit ballot for bootstrap4 $proto1 nay # not to promote + +bake # pos=1 +bake # pos=2 +bake # pos=3 + +$client show votes + +bake # new period pos=0 + +echo 'Promotion vote should be done (negatively)' +res=`$client show votes` +[ `echo $res | jq .voting_period_position` = '0' ] \ + || { echo "strange voting_period_position" ; exit 1 ; } +[ `echo $res | jq .voting_period_remaining` = '4' ] \ + || { echo "strange voting_period_remaining" ; exit 1 ; } +[ `echo $res | jq .current_period_kind` = '"proposal"' ] \ + || { echo "strange current_period_kind" ; exit 1 ; } +[ "`echo $res | jq .listings`" != '[]' ] \ + || { echo "strange listings" ; exit 1 ; } +[ `echo $res | jq .current_proposal` = 'null' ] \ + || { echo "strange current_proposal" ; exit 1 ; } +[ `echo $res | jq .ballot_list` = '[]' ] \ + || { echo "strange ballot_list" ; exit 1 ; } diff --git a/src/proto_alpha/lib_client/client_proto_context.ml b/src/proto_alpha/lib_client/client_proto_context.ml index a0168dc8f..9a5dc3455 100644 --- a/src/proto_alpha/lib_client/client_proto_context.ml +++ b/src/proto_alpha/lib_client/client_proto_context.ml @@ -420,6 +420,127 @@ let activate_existing_account | Some _ -> failwith "Only Ed25519 accounts can be activated" | None -> failwith "Unknown account" +type vote_info = { + current_period_kind : Voting_period.kind ; + voting_period_position : int32 ; + voting_period_remaining : int32 ; + current_quorum : Int32.t ; + listings : (public_key_hash * int32) list ; + (* The equality between Alpha_environment.Protocol_hash.t + and Protocol_hash.t is dropped at Tezos_protocol_environment.Make(_).V1 *) + proposals : Int32.t Alpha_environment.Protocol_hash.Map.t ; + current_proposal : Protocol_hash.t option ; + ballots : Vote.ballots ; + ballot_list : (public_key_hash * Vote.ballot) list ; +} + +(* Should be moved to src/proto_alpha/lib_protocol/src/vote_storage.ml *) +let ballot_list_encoding = + Data_encoding.(list (obj2 + (req "pkh" Signature.Public_key_hash.encoding) + (req "balllot" Vote.ballot_encoding))) + +let vote_info_encoding = + let open Data_encoding in + conv + (fun { current_period_kind ; + voting_period_position ; + voting_period_remaining ; + current_quorum ; + listings ; + proposals ; + current_proposal ; + ballots ; + ballot_list } -> + ( current_period_kind , + voting_period_position , + voting_period_remaining , + current_quorum , + listings , + proposals , + current_proposal , + ballots , + ballot_list )) + (fun ( current_period_kind , + voting_period_position , + voting_period_remaining , + current_quorum , + listings , + proposals , + current_proposal , + ballots , + ballot_list ) -> + { current_period_kind ; + voting_period_position ; + voting_period_remaining ; + current_quorum ; + listings ; + proposals ; + current_proposal ; + ballots ; + ballot_list }) + @@ obj9 + (req "current_period_kind" Voting_period.kind_encoding) + (req "voting_period_position" Data_encoding.int32) + (req "voting_period_remaining" Data_encoding.int32) + (req "current_quorum" Data_encoding.int32) + (req "listings" Vote.listings_encoding) + (req "proposals" (Alpha_environment.Protocol_hash.Map.encoding Data_encoding.int32)) + (req "current_proposal" (Data_encoding.option Protocol_hash.encoding)) + (req "ballots" Vote.ballots_encoding) + (req "ballot_list" ballot_list_encoding) + +let get_vote_info + (cctxt : #Proto_alpha.full) + ~chain ~block = + (* Get the next level, not the current *) + let cb = (chain, block) in + Alpha_services.Helpers.current_level cctxt ~offset:1l cb >>=? fun level -> + Alpha_services.Constants.all cctxt cb >>=? fun constants -> + let voting_period_position = level.voting_period_position in + let voting_period_remaining = + Int32.(sub constants.parametric.blocks_per_voting_period voting_period_position) in + Alpha_services.Voting.ballots cctxt cb >>=? fun ballots -> + Alpha_services.Voting.ballot_list cctxt cb >>=? fun ballot_list -> + Alpha_services.Voting.current_period_kind cctxt cb >>=? fun current_period_kind -> + Alpha_services.Voting.current_quorum cctxt cb >>=? fun current_quorum -> + Alpha_services.Voting.listings cctxt cb >>=? fun listings -> + Alpha_services.Voting.proposals cctxt cb >>=? fun proposals -> + Alpha_services.Voting.current_proposal cctxt cb >>=? fun current_proposal -> + return { voting_period_position ; + voting_period_remaining ; + ballots ; + ballot_list ; + current_period_kind ; + current_quorum ; + listings ; + proposals ; + current_proposal } + +let submit_proposals + (cctxt : #Proto_alpha.full) + ~chain ~block ?confirmations ~src_sk source proposals = + (* We need the next level, not the current *) + Alpha_services.Helpers.current_level cctxt ~offset:1l (chain, block) >>=? fun (level : Level.t) -> + let period = level.voting_period in + let contents = Single ( Proposals { source ; period ; proposals } ) in + Injection.inject_operation cctxt ~chain ~block ?confirmations + ~fee_parameter:Injection.dummy_fee_parameter + ~src_sk contents + +let submit_ballot + (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. + *) + Alpha_services.Helpers.current_level cctxt ~offset:1l (chain, block) >>=? fun (level : Level.t) -> + let period = level.voting_period in + 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 + let pp_operation formatter (a : Alpha_block_services.operation) = match a.receipt, a.protocol_data with | Apply_results.Operation_metadata omd, Operation_data od -> ( diff --git a/src/proto_alpha/lib_client/client_proto_context.mli b/src/proto_alpha/lib_client/client_proto_context.mli index f17e7ddb1..0987a23cc 100644 --- a/src/proto_alpha/lib_client/client_proto_context.mli +++ b/src/proto_alpha/lib_client/client_proto_context.mli @@ -218,6 +218,37 @@ val activate_existing_account: Blinded_public_key_hash.activation_code -> Kind.activate_account Injection.result tzresult Lwt.t +type vote_info + +val vote_info_encoding : vote_info Data_encoding.t + +val get_vote_info : + #Proto_alpha.full -> + chain:Shell_services.chain -> + block:Shell_services.block -> + vote_info tzresult Lwt.t + +val submit_proposals: + #Proto_alpha.full -> + chain:Shell_services.chain -> + block:Shell_services.block -> + ?confirmations:int -> + src_sk:Client_keys.sk_uri -> + public_key_hash -> + Protocol_hash.t list -> + Kind.proposals Injection.result_list tzresult Lwt.t + +val submit_ballot: + #Proto_alpha.full -> + chain:Shell_services.chain -> + block:Shell_services.block -> + ?confirmations:int -> + src_sk:Client_keys.sk_uri -> + public_key_hash -> + Protocol_hash.t -> + Proto_alpha.Alpha_context.Vote.ballot -> + Kind.ballot Injection.result_list tzresult Lwt.t + (** lookup an operation in [predecessors] previous blocks, and print the receipt if found *) val display_receipt_for_operation: diff --git a/src/proto_alpha/lib_client/operation_result.ml b/src/proto_alpha/lib_client/operation_result.ml index 29b733106..1b8380bc8 100644 --- a/src/proto_alpha/lib_client/operation_result.ml +++ b/src/proto_alpha/lib_client/operation_result.ml @@ -400,6 +400,7 @@ let rec pp_contents_and_result_list : Signature.Public_key_hash.pp source Voting_period.pp period Protocol_hash.pp proposal + (* FIXME We should use ballot_encoding? *) (match ballot with Yay -> "YAY" | Pass -> "PASS" | Nay -> "NAY") | Single_and_result (Manager_operation _ as op, (Manager_operation_result _ as res))-> 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 9149fcccb..0a2336b39 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 @@ -641,6 +641,73 @@ let commands version () = (Data_encoding.Binary.describe Alpha_context.Operation.unsigned_encoding) >>= fun () -> return_unit - end + end ; + command ~group ~desc: "Submit protocol proposals." + no_options + (prefixes [ "submit" ; "proposals" ] + @@ prefix "for" + @@ ContractAlias.destination_param + ~name: "src" ~desc: "name of the source contract" + @@ seq_of_param (param + ~name:"proposal" + ~desc:"Proposal to be submitted" + (parameter + (fun _ x -> + match Protocol_hash.of_b58check_opt x with + | None -> Error_monad.failwith "Invalid proposal hash: '%s'" x + | Some hash -> return hash))) + ) + (fun () (_name, source) proposals cctxt -> + Client_proto_context.get_manager + cctxt ~chain:`Main ~block:cctxt#block + source >>=? fun (_src_name, src_pkh, _src_pk, src_sk) -> + submit_proposals cctxt ~chain:`Main ~block:cctxt#block ~src_sk src_pkh proposals >>=? fun _res -> + return_unit + ); + + command ~group ~desc: "Submit a ballot." + no_options + (prefixes [ "submit" ; "ballot" ] + @@ prefix "for" + @@ ContractAlias.destination_param + ~name: "src" ~desc: "name of the source contract" + @@ param + ~name:"proposal" + ~desc:"Proposal" + (parameter + (fun _ x -> + match Protocol_hash.of_b58check_opt x with + | None -> Error_monad.failwith "Invalid proposal hash: '%s'" x + | Some hash -> return hash)) + @@ param + ~name:"ballot" + ~desc:"Ballot(yay/nay/pass)" + (parameter + (fun _ s -> + let fail () = Error_monad.failwith "Invalid ballot: '%s'" s in + match Data_encoding.Json.from_string ("\"" ^ s ^ "\"") with + | Error _ -> fail () + | Ok j -> + match Data_encoding.Json.destruct Vote.ballot_encoding j with + | exception _ -> fail () + | b -> return b)) + @@ stop + ) + (fun () (_name, source) proposal ballot cctxt -> + Client_proto_context.get_manager + cctxt ~chain:`Main ~block:cctxt#block + source >>=? fun (_src_name, src_pkh, _src_pk, src_sk) -> + submit_ballot cctxt ~chain:`Main ~block:cctxt#block ~src_sk src_pkh proposal ballot >>=? fun _res -> + return_unit + ); + + command ~group ~desc: "Summarize the current voting information." + no_options + (fixed [ "show" ; "votes" ]) + (fun () cctxt -> + get_vote_info ~chain:`Main ~block:cctxt#block cctxt >>=? fun vote_info -> + cctxt#message "%a" (Json_repr.pp_any ()) (Json_repr.(to_any (Data_encoding.Json.construct vote_info_encoding vote_info))) >>= fun () -> + return_unit + ) ]