ligo/vendors/ligo-utils/tezos-protocol-alpha/amendment.ml
2019-09-05 15:21:01 +02:00

276 lines
11 KiB
OCaml

(*****************************************************************************)
(* *)
(* Open Source License *)
(* Copyright (c) 2018 Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
(* *)
(* Permission is hereby granted, free of charge, to any person obtaining a *)
(* copy of this software and associated documentation files (the "Software"),*)
(* to deal in the Software without restriction, including without limitation *)
(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)
(* and/or sell copies of the Software, and to permit persons to whom the *)
(* Software is furnished to do so, subject to the following conditions: *)
(* *)
(* The above copyright notice and this permission notice shall be included *)
(* in all copies or substantial portions of the Software. *)
(* *)
(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)
(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)
(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)
(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)
(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)
(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)
(* DEALINGS IN THE SOFTWARE. *)
(* *)
(*****************************************************************************)
open Alpha_context
(** Returns the proposal submitted by the most delegates.
Returns None in case of a tie or if there are no proposals. *)
let select_winning_proposal proposals =
let merge proposal vote winners =
match winners with
| None -> Some ([proposal], vote)
| Some (winners, winners_vote) as previous ->
if Compare.Int32.(vote = winners_vote) then
Some (proposal :: winners, winners_vote)
else if Compare.Int32.(vote >= winners_vote) then
Some ([proposal], vote)
else
previous in
match Protocol_hash.Map.fold merge proposals None with
| None -> None
| Some ([proposal], _) -> Some proposal
| Some _ -> None (* in case of a tie, lets do nothing. *)
(** A proposal is approved if it has supermajority and the participation reaches
the current quorum.
Supermajority means the yays are more 8/10 of casted votes.
The participation is the ratio of all received votes, including passes, with
respect to the number of possible votes. The quorum starts at 80% and at
each vote is updated using the last expected quorum and the current
participation with the following weights:
newQ = oldQ * 8/10 + participation * 2/10 *)
let check_approval_and_update_quorum ctxt =
Vote.get_ballots ctxt >>=? fun ballots ->
Vote.listing_size ctxt >>=? fun maximum_vote ->
Vote.get_current_quorum ctxt >>=? fun expected_quorum ->
(* Note overflows: considering a maximum of 8e8 tokens, with roll size as
small as 1e3, there is a maximum of 8e5 rolls and thus votes.
In 'participation' an Int64 is used because in the worst case 'all_votes is
8e5 and after the multiplication is 8e9, making it potentially overflow a
signed Int32 which is 2e9. *)
let casted_votes = Int32.add ballots.yay ballots.nay in
let all_votes = Int32.add casted_votes ballots.pass in
let supermajority = Int32.div (Int32.mul 8l casted_votes) 10l in
let participation = (* in centile of percentage *)
Int64.to_int32
(Int64.div
(Int64.mul (Int64.of_int32 all_votes) 100_00L)
(Int64.of_int32 maximum_vote)) in
let outcome = Compare.Int32.(participation >= expected_quorum &&
ballots.yay >= supermajority) in
let updated_quorum =
Int32.div (Int32.add (Int32.mul 8l expected_quorum) (Int32.mul 2l participation)) 10l in
Vote.set_current_quorum ctxt updated_quorum >>=? fun ctxt ->
return (ctxt, outcome)
(** Implements the state machine of the amendment procedure.
Note that [freeze_listings], that computes the vote weight of each delegate,
is run at the beginning of each voting period.
*)
let start_new_voting_period ctxt =
Vote.get_current_period_kind ctxt >>=? function
| Proposal -> begin
Vote.get_proposals ctxt >>=? fun proposals ->
Vote.clear_proposals ctxt >>= fun ctxt ->
Vote.clear_listings ctxt >>=? fun ctxt ->
match select_winning_proposal proposals with
| None ->
Vote.freeze_listings ctxt >>=? fun ctxt ->
return ctxt
| Some proposal ->
Vote.init_current_proposal ctxt proposal >>=? fun ctxt ->
Vote.freeze_listings ctxt >>=? fun ctxt ->
Vote.set_current_period_kind ctxt Testing_vote >>=? fun ctxt ->
return ctxt
end
| Testing_vote ->
check_approval_and_update_quorum ctxt >>=? fun (ctxt, approved) ->
Vote.clear_ballots ctxt >>= fun ctxt ->
Vote.clear_listings ctxt >>=? fun ctxt ->
if approved then
let expiration = (* in two days maximum... *)
Time.add (Timestamp.current ctxt) (Constants.test_chain_duration ctxt) in
Vote.get_current_proposal ctxt >>=? fun proposal ->
fork_test_chain ctxt proposal expiration >>= fun ctxt ->
Vote.set_current_period_kind ctxt Testing >>=? fun ctxt ->
return ctxt
else
Vote.clear_current_proposal ctxt >>=? fun ctxt ->
Vote.freeze_listings ctxt >>=? fun ctxt ->
Vote.set_current_period_kind ctxt Proposal >>=? fun ctxt ->
return ctxt
| Testing ->
Vote.freeze_listings ctxt >>=? fun ctxt ->
Vote.set_current_period_kind ctxt Promotion_vote >>=? fun ctxt ->
return ctxt
| Promotion_vote ->
check_approval_and_update_quorum ctxt >>=? fun (ctxt, approved) ->
begin
if approved then
Vote.get_current_proposal ctxt >>=? fun proposal ->
activate ctxt proposal >>= fun ctxt ->
return ctxt
else
return ctxt
end >>=? fun ctxt ->
Vote.clear_ballots ctxt >>= fun ctxt ->
Vote.clear_listings ctxt >>=? fun ctxt ->
Vote.clear_current_proposal ctxt >>=? fun ctxt ->
Vote.freeze_listings ctxt >>=? fun ctxt ->
Vote.set_current_period_kind ctxt Proposal >>=? fun ctxt ->
return ctxt
type error += (* `Branch *)
| Invalid_proposal
| Unexpected_proposal
| Unauthorized_proposal
| Too_many_proposals
| Empty_proposal
| Unexpected_ballot
| Unauthorized_ballot
let () =
let open Data_encoding in
(* Invalid proposal *)
register_error_kind
`Branch
~id:"invalid_proposal"
~title:"Invalid proposal"
~description:"Ballot provided for a proposal that is not the current one."
~pp:(fun ppf () -> Format.fprintf ppf "Invalid proposal")
empty
(function Invalid_proposal -> Some () | _ -> None)
(fun () -> Invalid_proposal) ;
(* Unexpected proposal *)
register_error_kind
`Branch
~id:"unexpected_proposal"
~title:"Unexpected proposal"
~description:"Proposal recorded outside of a proposal period."
~pp:(fun ppf () -> Format.fprintf ppf "Unexpected proposal")
empty
(function Unexpected_proposal -> Some () | _ -> None)
(fun () -> Unexpected_proposal) ;
(* Unauthorized proposal *)
register_error_kind
`Branch
~id:"unauthorized_proposal"
~title:"Unauthorized proposal"
~description:"The delegate provided for the proposal is not in the voting listings."
~pp:(fun ppf () -> Format.fprintf ppf "Unauthorized proposal")
empty
(function Unauthorized_proposal -> Some () | _ -> None)
(fun () -> Unauthorized_proposal) ;
(* Unexpected ballot *)
register_error_kind
`Branch
~id:"unexpected_ballot"
~title:"Unexpected ballot"
~description:"Ballot recorded outside of a voting period."
~pp:(fun ppf () -> Format.fprintf ppf "Unexpected ballot")
empty
(function Unexpected_ballot -> Some () | _ -> None)
(fun () -> Unexpected_ballot) ;
(* Unauthorized ballot *)
register_error_kind
`Branch
~id:"unauthorized_ballot"
~title:"Unauthorized ballot"
~description:"The delegate provided for the ballot is not in the voting listings."
~pp:(fun ppf () -> Format.fprintf ppf "Unauthorized ballot")
empty
(function Unauthorized_ballot -> Some () | _ -> None)
(fun () -> Unauthorized_ballot) ;
(* Too many proposals *)
register_error_kind
`Branch
~id:"too_many_proposals"
~title:"Too many proposals"
~description:"The delegate reached the maximum number of allowed proposals."
~pp:(fun ppf () -> Format.fprintf ppf "Too many proposals")
empty
(function Too_many_proposals -> Some () | _ -> None)
(fun () -> Too_many_proposals) ;
(* Empty proposal *)
register_error_kind
`Branch
~id:"empty_proposal"
~title:"Empty proposal"
~description:"Proposal lists cannot be empty."
~pp:(fun ppf () -> Format.fprintf ppf "Empty proposal")
empty
(function Empty_proposal -> Some () | _ -> None)
(fun () -> Empty_proposal)
(* @return [true] if [List.length l] > [n] w/o computing length *)
let rec longer_than l n =
if Compare.Int.(n < 0) then assert false else
match l with
| [] -> false
| _ :: rest ->
if Compare.Int.(n = 0) then true
else (* n > 0 *)
longer_than rest (n-1)
let record_proposals ctxt delegate proposals =
begin match proposals with
| [] -> fail Empty_proposal
| _ :: _ -> return_unit
end >>=? fun () ->
Vote.get_current_period_kind ctxt >>=? function
| Proposal ->
Vote.in_listings ctxt delegate >>= fun in_listings ->
if in_listings then
Vote.recorded_proposal_count_for_delegate ctxt delegate >>=? fun count ->
fail_when
(longer_than proposals (Constants.max_proposals_per_delegate - count))
Too_many_proposals >>=? fun () ->
fold_left_s
(fun ctxt proposal ->
Vote.record_proposal ctxt proposal delegate)
ctxt proposals >>=? fun ctxt ->
return ctxt
else
fail Unauthorized_proposal
| Testing_vote | Testing | Promotion_vote ->
fail Unexpected_proposal
let record_ballot ctxt delegate proposal ballot =
Vote.get_current_period_kind ctxt >>=? function
| Testing_vote | Promotion_vote ->
Vote.get_current_proposal ctxt >>=? fun current_proposal ->
fail_unless (Protocol_hash.equal proposal current_proposal)
Invalid_proposal >>=? fun () ->
Vote.has_recorded_ballot ctxt delegate >>= fun has_ballot ->
fail_when has_ballot Unauthorized_ballot >>=? fun () ->
Vote.in_listings ctxt delegate >>= fun in_listings ->
if in_listings then
Vote.record_ballot ctxt delegate ballot
else
fail Unauthorized_ballot
| Testing | Proposal ->
fail Unexpected_ballot
let last_of_a_voting_period ctxt l =
Compare.Int32.(Int32.succ l.Level.voting_period_position =
Constants.blocks_per_voting_period ctxt )
let may_start_new_voting_period ctxt =
let level = Level.current ctxt in
if last_of_a_voting_period ctxt level then
start_new_voting_period ctxt
else
return ctxt