Skip to content

Commit

Permalink
refactor: add subscriber; rename to RegistrarFacet, docs, diagrams
Browse files Browse the repository at this point in the history
added a subscription to the ParamManager
Rename BallotCounter's voting facet to RegistrarFacet
The registrar makes a handle for the ballotCounter to distinguish voters

Cleaned up some leftover Handle<Ballot> that should have been Handle<BallotTemplate>

more updates to the docs and diagrams
   Registrar's responsibiilty for safeguarding the ability to vote
   ContractGovernor is an ElectionManager
   The outcome of a param change is a Promise
   ElectionManager give their creator the ability to add a question

Fixed a typo in documentation in Subscriber
  • Loading branch information
Chris-Hibbert committed Sep 2, 2021
1 parent f5d4d51 commit 0117ede
Show file tree
Hide file tree
Showing 14 changed files with 103 additions and 64 deletions.
16 changes: 12 additions & 4 deletions packages/governance/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This package provides Registrars and BallotsCounters to create a general
framework for governance. It has implementations for particular kinds of
electorates and different ways of tallying votes.

The registras and BallotCounters are self-describing and reveal what they are
The registrars and BallotCounters are self-describing and reveal what they are
connected to so that voters can verify that their votes mean what they say and
will be tabulated as expected.

Expand Down Expand Up @@ -52,6 +52,14 @@ visible. ContractGovernor is a particular example of that that makes it possible
for a contract to publish details of how its parameters will be subject to
governance.

## Registrar

A Registrar represents a set of voters. Each voter receives an invitation
for a voterFacet, which works across elections. The Registrar starts a new
BallotCounter instance for each separate question, and gets back a
`registrarFacet`. The Registrar is responsible for ensuring that each voter
calls the ballotCounter's `submitVote()` method with a unique voterHandle.

## ContractGovernor

We want some contracts to be able to make it visible that their internal
Expand Down Expand Up @@ -94,7 +102,7 @@ done.

There isn't currently a way to verify the process of creating new questions.
We'll eventually need to spin a story that will make that more legible.
Currently, the abilty to create new governance questions is provided as a
Currently, the ability to create new governance questions is provided as a
private facet that contains only the method voteOnParamChange().

When a prospective user of a contract receives a link to an instance of a
Expand All @@ -120,7 +128,7 @@ resolved.
Each question describes its subject. One field of the ballotDetails is
`ElectionType`, which can be `PARAM_CHANGE`, `ELECTION`, or `SURVEY`. (I'm sure
we'll come up with more types.) When it is `PARAM_CHANGE`, the ballotDetails
will also identify the contract instance, the partcular parameter to be changed,
will also identify the contract instance, the particular parameter to be changed,
and the proposed new value. At present, all parameter change elections are by
majority vote, and if a majority doesn't vote in favor, then no change is made.

Expand Down Expand Up @@ -157,7 +165,7 @@ ContractGovernance uses this to make 'no change' be the default when voting on
parameter changes.

We should have ballotCounters for multiple candidate questions. I hope we'll
eventually have IRV (instant runnoff) and various forms of proportional
eventually have IRV (instant runoff) and various forms of proportional
representation.

### ElectionManager
Expand Down
Binary file modified packages/governance/docs/contractGovernance.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions packages/governance/docs/contractGovernance.puml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class GovernedContract {
}
note left : calls buildParamManager(paramDesc);\nmakes paramMgr state public\nreturns paramMgr in creatorFacet

class ContractGovernor {
class "ContractGovernor\n(an ElectionManager)" as ContractGovernor {
<i>verifiable</i>: governedInstance, registrarInstance
--
+getRegistrar()
Expand All @@ -28,7 +28,7 @@ class ContractGovernor {
validateBallotRegistrar()
validateBallotTimer()
}
note left : ContractGovernor starts GovernedContract\nholds paramManager tightly for issue creation
note left : ContractGovernor starts GovernedContract\nstartGovernedInstance() returns a tightly held facet\n with voteOnParamChange() for the creator.

class Registrar {
Questions
Expand Down
Binary file modified packages/governance/docs/coreArchitecture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 7 additions & 9 deletions packages/governance/docs/coreArchitecture.puml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,11 @@ package Legend <<Rectangle>> #EEEEEE {
note "Contracts have a 'C' marker.\nInvitations have an 'I'.\nblue arrows show verifiable connections.\ncreator-created links are labelled" as NC
}


class Registrar {
terms: committeeSize, committeeName
--
Questions[]
+questionNotifier()
+getQuestionSubscription()
+getOpenQuestions()
+getBallotTemplate(handle)
#getVoterInvitation()
Expand All @@ -40,12 +39,12 @@ class BallotCounter {
terms\n {ballotSpec, quorum, closingRule, tieOutcome}
--
+getDetails()
+getOutcome()
+getOutcome(): Promise
+getStats()
+getBallotTemplate()
#countVotes()
-submitVote()
-getVoterFacet()
-getRegistrarFacet()
}

note "unaware of voter registration.\n Only Registrar hands out voterFacets" as N2
Expand All @@ -62,20 +61,19 @@ object BallotDetails {
counterInstance
}

note right: "BallotDetails is a widely accessible record.\nverifiable copies are obtained from a Registrar" as BD
note right: BallotDetails is a widely accessible record.\nverifiable copies are obtained from a Registrar

class ElectionManager {
Registrar
addQuestion()
}
note left : ElectionManager is responsible\n for letting an appropriate\n party call addQuestion()

object VoterFacet {
---
submitVote(...positions)
}

note "VoterFacets are tightly held by voters." as VF
VF .. VoterFacet
note right: VoterFacets are\ntightly held by voters.

interface VoterInvitation {
Registrar
Expand All @@ -86,7 +84,7 @@ interface VoterInvitation {
interface QuestionPoserInvitation {
Registrar
--
getVoterFacet()
addQuestion()
}


Expand Down
6 changes: 3 additions & 3 deletions packages/governance/src/binaryBallotCounter.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,8 @@ const makeBinaryBallotCounter = (ballotSpec, threshold, instance) => {
isOpen: () => isOpen,
};

/** @type {VoterFacet} */
const voterFacet = Far('voterFacet', {
/** @type {RegistrarFacet} */
const registrarFacet = Far('registrarFacet', {
...sharedFacet,
submitVote,
});
Expand All @@ -166,7 +166,7 @@ const makeBinaryBallotCounter = (ballotSpec, threshold, instance) => {
/** @type {BallotCounterCreatorFacet} */
const creatorFacet = Far('adminFacet', {
...sharedFacet,
getVoterFacet: () => voterFacet,
getRegistrarFacet: () => registrarFacet,
});

const publicFacet = Far('preliminaryPublicFacet', {
Expand Down
17 changes: 10 additions & 7 deletions packages/governance/src/committeeRegistrar.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { allComparable } from '@agoric/same-structure';
import { makeStore } from '@agoric/store';
import { natSafeMath } from '@agoric/zoe/src/contractSupport/index.js';

import { makeHandle } from '@agoric/zoe/src/makeHandle';
import { QuorumRule } from './ballotBuilder.js';

const { ceilDivide } = natSafeMath;
Expand All @@ -23,11 +24,11 @@ const { ceilDivide } = natSafeMath;
const start = zcf => {
/**
* @typedef {Object} QuestionRecord
* @property {ERef<VoterFacet>} voter
* @property {ERef<RegistrarFacet>} vote
* @property {BallotCounterPublicFacet} publicFacet
*/

/** @type {Store<Handle<'Ballot'>, QuestionRecord>} */
/** @type {Store<Handle<'BallotTemplate'>, QuestionRecord>} */
const allQuestions = makeStore('Question');
const { subscription, publication } = makeSubscriptionKit();
const invitations = [];
Expand All @@ -38,21 +39,23 @@ const start = zcf => {
return [E(publicFacet).isOpen(), key];
});

/** @type {[boolean, Handle<'Ballot'>][]} */
/** @type {[boolean, Handle<'BallotTemplate'>][]} */
const isOpenQuestions = await allComparable(harden(isOpenPQuestions));
return isOpenQuestions
.filter(([open, _key]) => open)
.map(([_open, key]) => key);
};

const makeCommitteeVoterInvitation = index => {
const handler = Far('handler', voterSeat => {
/** @type {OfferHandler} */
const handler = Far('handler', () => {
const voterHandle = makeHandle('Voter');
return Far(`voter${index}`, {
castBallotFor: (handle, positions) => {
const { publicFacet: counter, voter } = allQuestions.get(handle);
const { publicFacet: counter, vote } = allQuestions.get(handle);
const ballotTemplate = E(counter).getBallotTemplate();
const ballot = E(ballotTemplate).choose(positions);
return E(voter).submitVote(voterSeat, ballot);
return E(vote).submitVote(voterHandle, ballot);
},
});
});
Expand Down Expand Up @@ -92,7 +95,7 @@ const start = zcf => {
zcf.getZoeService(),
).startInstance(voteCounter, {}, ballotCounterTerms);
const details = await E(publicFacet).getDetails();
const facets = { voter: E(creatorFacet).getVoterFacet(), publicFacet };
const facets = { vote: E(creatorFacet).getRegistrarFacet(), publicFacet };
allQuestions.init(details.handle, facets);

publication.updateState(details);
Expand Down
6 changes: 5 additions & 1 deletion packages/governance/src/paramManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { AmountMath, looksLikeBrand } from '@agoric/ertp';
import { Far } from '@agoric/marshal';
import { assertKeywordName } from '@agoric/zoe/src/cleanProposal.js';
import { Nat } from '@agoric/nat';
import { makeSubscriptionKit } from '@agoric/notifier';

/**
* @type {{
Expand Down Expand Up @@ -86,7 +87,7 @@ const buildParamManager = paramDescriptions => {
const typesAndValues = {};
// updateFns will have updateFoo() for each Foo param.
const updateFns = {};

const { publication, subscription } = makeSubscriptionKit();
paramDescriptions.forEach(({ name, value, type }) => {
// we want to create function names like updateFeeRatio(), so we insist that
// the name has Keyword-nature.
Expand All @@ -102,6 +103,8 @@ const buildParamManager = paramDescriptions => {
updateFns[`update${name}`] = newValue => {
assertType(type, newValue, name);
typesAndValues[name].value = newValue;

publication.updateState({ name, type, value });
return newValue;
};
});
Expand All @@ -126,6 +129,7 @@ const buildParamManager = paramDescriptions => {
// contractGovernor. The getParams method can be shared widely.
return Far('param manager', {
getParams,
getSubscription: () => subscription,
getParam,
...updateFns,
});
Expand Down
22 changes: 12 additions & 10 deletions packages/governance/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
* @typedef {BallotSpec} BallotDetails
* complete ballot details: ballotSpec plus counter and handle
* @property {Instance} counterInstance - instance of the BallotCounter
* @property {Handle<'Ballot'>} handle
* @property {Handle<'BallotTemplate'>} handle
*/

/**
Expand All @@ -120,15 +120,15 @@
// not yet in use
/**
* @typedef {Object} CompleteWeightedBallot
* @property {Handle<'Ballot'>} handle
* @property {Handle<'BallotTemplate'>} handle
* @property {[Position,bigint][]} weighted - list of positions with
* weights. BallotCounter may limit weights to a range or require uniqueness.
*/

// not yet in use
/**
* @typedef {Object} CompleteOrderedBallot
* @property {Handle<'Ballot'>} handle
* @property {Handle<'BallotTemplate'>} handle
* @property {Position[]} ordered - ordered list of position from most preferred
* to least preferred
*/
Expand Down Expand Up @@ -162,7 +162,7 @@
* @typedef {Object} BallotCounterCreatorFacet
* @property {() => boolean} isOpen
* @property {() => BallotTemplate} getBallotTemplate
* @property {() => VoterFacet} getVoterFacet
* @property {() => RegistrarFacet} getRegistrarFacet
*/

/**
Expand Down Expand Up @@ -218,10 +218,10 @@
/**
* @typedef {Object} RegistrarPublic
* @property {() => Subscription<BallotDetails>} getQuestionSubscription
* @property {() => ERef<Handle<'Ballot'>[]>} getOpenQuestions,
* @property {() => ERef<Handle<'BallotTemplate'>[]>} getOpenQuestions,
* @property {() => string} getName
* @property {() => Instance} getInstance
* @property {(h: Handle<'Ballot'>) => ERef<BallotTemplate>} getBallotTemplate
* @property {(h: Handle<'BallotTemplate'>) => ERef<BallotTemplate>} getBallotTemplate
*/

/**
Expand All @@ -242,9 +242,10 @@
*/

/**
* @typedef {Object} VoterFacet - a facet that the Registrar should hold
* tightly. It allows specification of the vote's weight, so the Registrar
* should distribute an attenuated wrapper that doesn't make that available!
* @typedef {Object} RegistrarFacet - a facet that the Registrar should hold
* tightly. It allows specification of the vote's voterHandle and weight, so
* the Registrar should distribute an attenuated wrapper that doesn't make
* that available!
* @property {SubmitVote} submitVote
*/

Expand Down Expand Up @@ -331,13 +332,14 @@
* @property {QuorumRule} quorumRule
* @property {NoChangeParamPosition} tieOutcome
* @property {Instance} counterInstance - instance of the BallotCounter
* @property {Handle<'Ballot'>} handle
* @property {Handle<'BallotTemplate'>} handle
*/

/**
* @typedef {Object} ParamManagerBase
* @property {() => Record<Keyword,ParamDescription>} getParams
* @property {(name: string) => ParamDescription} getParam
* @property {() => Subscription<ParamDescription>} getSubscription
*/

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { E } from '@agoric/eventual-send';
import { Far } from '@agoric/marshal';
import buildManualTimer from '@agoric/zoe/tools/manualTimer.js';
import { q } from '@agoric/assert';
import { observeIteration } from '@agoric/notifier';
import { governedParameterTerms } from './governedContract.js';

/**
Expand Down Expand Up @@ -124,6 +125,13 @@ const checkContractState = async (zoe, timer, contractInstanceP, log) => {
const contractInstance = await contractInstanceP;
const contractPublic = E(zoe).getPublicFacet(contractInstance);
let state = await E(contractPublic).getState();
const subscription = await E(contractPublic).getSubscription();
const paramChangeObserver = Far('param observer', {
updateState: update => {
log(`${update.name} was changed to ${q(update.value)}`);
},
});
observeIteration(subscription, paramChangeObserver);

// it takes a while for the update to propagate. The second time it seems good
state = await E(contractPublic).getState();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const start = async zcf => {

const publicFacet = Far('public face of governed contract', {
getState: () => paramManager.getParams(),
getSubscription: () => paramManager.getSubscription(),
getContractGovernor: () => electionManager,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ const expectedcontractGovernorStartLog = [
'&& running a task scheduled for 3. &&',
'vote outcome: {"changeParam":{"key":"contractParams","parameterName":"MalleableNumber"},"proposedValue":"[299792458n]"}',
'updated to "[299792458n]"',
'MalleableNumber was changed to "[602214090000000000000000n]"',
'current value of MalleableNumber is 299792458',
];

Expand Down
Loading

0 comments on commit 0117ede

Please sign in to comment.