owned this note
owned this note
Published
Linked with GitHub
The purpose of this documentation is to explain in plain english the contents and interactions of each module in the runtime module library to avoid incorrect assumptions or interpretations of the code by new users. It will be used to create tutorials.
The target audiences are users wishing to implement a governance runtime module, and other documentation authors.
## Substrate Runtime Module Library (SRML)
### Council Module
---
### Introduction
TODO
> Note: There are different voting systems. The `vote` function in srml/council/src/motions.rs is for Council Motions that allow an arbitrary number of councillors (other than the councillor that proposed the motion) to vote and initiate a call and where the `origin` is the Council (not Root). Whereas the `vote` function in srml/council/src/voting.rs is for the Council Voting as a body regarding tabling referenda.
#### Council Proposals
##### Council Proposal (Motion and Origin)
> Source: Gav (rephrased by Luke)
A council motion is a mechanism that is used enact an outer `Call` dispatch (of type `Proposal`) from the council origin.
The council origin is defined in srml/council/src/motion.rs and has different mechanics to other origins. It contains the council motion mechanism.
##### Council Proposal (Validity and Creation)
> Source: srml/council/src/voting.rs
A council proposal is valid if:
* It is submitted by a councillor
* It is not a duplicate proposal
* It is not vetoed
* The term of the council associated with the councillor proposing it does not expire before the configured voting period.
If the council proposal is valid then:
* The proposal's hash and the expiry block number of its voting period are stored as a tuple in the `Proposals` storage vector
* The proposal's hash is mapped to the value of the proposal in storage using `ProposalsOf`
* The proposal's hash is mapped to a vector of account ids that have voted for it in storage using `ProposalVoters`. Initially the account id of the councillor that created the proposal is added to the vector.
* A tuple including the proposal's hash and the councillor's account id is mapped to their corresponding vote in storage using `CouncilVoteOf`. Initially the councillor that created the proposal is added with a vote of yay.
##### Council Proposal (Threshold, Voting, Approval, Execution)
> Source: srml/council/src/voting.rs
> Source: srml/council/src/motions.rs
A councillor is a ['countable'](https://youtu.be/eO3fG_1YrE4?t=2282) stakeholder who may vote on a council proposal during the voting period if they did not create the proposal. Their vote is:
* Pushed onto a storage vector `ProposalVoters` of account ids that have voted for the proposal's hash
* Added to a storage mapping `CouncilVoteOf`, comprising of a tuple (the proposal's hash and the councillor's account id) that's mapped to its corresponding vote.
* Added to the storage mapping `Voting` that maps a proposal's hash to a tuple containing the corresponding proposal's index, its vote threshold, and a vectors of both yay and nay voter account id's.
When a council proposal is created with a threshold level of one, then it is voted upon and approved by the councillor that created it, and then executed.
Whereas a council proposal with a threshold level of more than one is added to a storage mapping `Voting` that was described earlier.
During council voting:
* Duplicate votes from the same councillor are ignored.
* Councillors may swap their vote from yay to nay and vice versa.
When the tally of yay votes for a council proposal reach the council proposal's threshold level during the council voting period then the council approval is approved and elevated to the table of active referendum on the next block.
Councillors that abstain from voting may postpone a council proposal from being approved or disapproved.
> Note: Postponing is equivalent to vetoing since the veto only lasts for the cooldown period.
Once the tally of votes result in the council proposal being approved or disapproved, we remove the motion from the `Voting` storage mapping, and remove the proposal hash from the list of active proposals `Proposals`.
If the tally of votes for the council proposal was unanimous (i.e. only yay votes) and was executed, then it is elevated to the table of active referendum on the next block, and a vote threshold of 'super majority against' is applied to the referendum.
Whereas if the tally of votes for the council proposal had a simple majority (i.e. more yay than nay votes) and was executed, then it is elevated to the table of active referendums on the next block, and a vote threshold of 'simple majority' is applied to the referendum.
##### Council Proposal (Veto)
> Source: srml/council/src/voting.rs
A councillor may veto a proposal if its stored in the `ProposalVoters` mapping on condition that they have not vetoed it previously. If their veto is valid then:
* Their veto is stored in `VetoedProposal` amongst other councillors that have vetoed the proposal, which maps the proposal's hash to a tuple comprising of the block number when the veto expires, and the account id of the vetoer.
* The vetoed proposal is removed from various storage locations, including the `Proposals` storage vector, the `ProposalsOf` storage mapping, the `ProposalVoters` storage mapping, and the `CouncilVoteOf` mapping.
A veto cancels a proposal, but the veto is not considered a vote.
Once a councillor vetoes a council proposal they cannot propose the proposal again until after a cooling off period measured in blocks.
##### Council Proposal (Majority Agreement)
A majority council agreement occurs when a council proposal is elevated to the table of referenda after the tally from majority voting results in a simple majority (more explicit yay than nay votes) from the councillors. This signals a sensible and uncontroversial proposal.
##### Council Proposal (Unanimous Agreement)
A unanimous council agreement occurs when a council proposal is elevated to the table of referenda after a unanimous voting tally occurs. It uses a negative AQB to encourage councillors not to [abstain](https://youtu.be/eO3fG_1YrE4?t=2525). A single veto from a councillor cancels the proposal and prevents the agreement. Council proposals submitted this way must have over 50% approval since abstention votes will be biased in favour of the proposal (alongside any nay votes).
##### Council Proposal (Elevation or Cancellation)
> Source: srml/council/src/voting.rs
In modules of the SRML, the `on_finalise` signature is used in the module declaration to run anything that needs to be done at the end of the block.
In council voting it calls a private function `end_block` with the current block as an argument, which loops through each proposal in storage whose voting period ends at the given block. For each proposal it calls `is_aux_sub_type` and destructures the return value. The return value is a call to a function `cancel_referendum` with a `ref_index` (referendum index) argument that removes the referendum with that referendum index.
When the call to the return value `cancel_referendum` returns a value, then we know that this council proposal was elevated to the table of referenda and therefore has a referendum index. We also already know that it expires on this block, so we may proceed to run the associated block of code that actually cancels the referendum by:
* Dispatching event `TallyCancelation` to indicate that a voting tally has happened associated with a cancellation vote for the referendum associated with the given proposal.
* The council directly calling `internal_cancel_referendum` to remove the specific referendum with given referendum index, but doing so is only permissed if the voting tally for the proposal was a unanimous vote (i.e. no nays, no abstainers).
Otherwise when the call to `is_aux_sub_type` with the current proposal does not destructure to a function call that is able to cancel a referendum index associated with the proposal, then we know this council proposal has not yet been elevated to the table of referenda, so we may proceed to run the associated block of code that checks the vote tally to determine whether to start the referendum.
In the associated block of code it dispatches an event `TallyReferendum` to indicate the start of a voting tally for a referendum vote on the current proposal, and if the voting tally of the current proposal has more yay votes than the combination of all nay votes and abstainers, then it:
* Removes any veto imposed upon the council proposal (since the proposal voting period is expiring)
If the council voting tally was unanimous then it:
* Starts a referendum (elevating the proposal to the table of referendum) with a vote threshold of `SuperMajorityAgainst`.
Otherwise if there were any nay voters or abstainers at the end of council voting on the proposal then it:
* Starts a referendum (elevating the proposal to the table of referendum) with a vote threshold of `SimplyMajority`.
##### Referenda
A council or public proposal that is elevated to the table of referenda. Each proposal is instantly executed autonomously once their votes reach a threshold level of approval.
---
#### Council Seats
> Source: srml/council/src/seats.rs
##### Candidate Approval Voting Call
Express candidate approval voting is a public call that anyone may execute by signing and submitting an extrinsic. We ensure that information about the `origin` where the dispatch initiated is a signed account using `ensure_signed`. It performs an `O(1)` operation that involves one extra DB entry and one DB change.
##### Candidate Registration and Vote Index
Candidate approval votes are only considered before the presentation period and when the candidates have a registered list slot with an approved candidate index `VoteIndex` that exists.
##### Candidate Voter's Security Bond for First Vote
If it is the voter's first vote and its valid, then:
* Before enacting any operation and changing the storage a "voter" security bond is deducted from the voter using the `reserve` function of the `Balances` module, as it may result in a major alteration of storage. The bond amount should be sufficient enough to cover any costs of the substantial execution in case the operation cannot proceed. The bond is a mitigation measure against the classical blockchain attack scenario since we cannot be certain that the operation will not require substantial computation.
* The voter's account id is pushed onto the `Voters` vector that contains the list of present voters.
##### Candidate Voter's Subsequent Votes
If it is a subsequent vote from the voter
When a vote is valid:
* Their vote is recorded in `LastActiveOf`, which maps their account id to the last cleared vote index that they were active at.
* Their votes (i.e. yay or nay) for each candidate of a vote index is added to `ApprovalsOf` that maps
##### Candidate Voter Inactivity Reaping
Removing an inactive voter is a public call. The reporter is removed if the target account is actually active and the reporter's account is removed and the reporter receivers the target's "voter" bond. It performs an `O(1)` operation that involves one fewer DB entry and one DB change.
Reaping Inactive Voter's are only considered when valid satisfying the following:
* Before the presentation period
* When the both the reporter and the target have already voted and are recorded in `LastActiveOf`
* When the assumed vote index is after an inactivity grace period (vote indexes remaining after the target voter's last active vote and when their associated approval votes are uncertain)
* When both the given reporter's vote index and the given target's vote index exist
* If the candidates approved index `VoteIndex` exists and matches the vote index that the reporter assumed was correct.
We then determine whether the reporter has made a "valid" claim that target account was inactive, and if true remove the target inactive account, or if false remove the account that falsely reported their inactivity.
We use `.zip` to enumerate over two list iterators in parallel:
* `approvals_of` (maps the account id of the target voter to their list of votes for last vote index when their vote was active)
* `candidates` (present list of candidate account id's)
Then we deem the report's claim "invalid" if none of the following are true:
* If any of the candidate account id's has a list of votes to the last vote index, and;
If any of the candidate account ids isn't the default, and;
If using `RegisterInfoOf` any of the candidate account ids maps to a corresponding vote index when they registered that is less than or equal to the target voter's last active vote index.
This would indicate that the candidate registered before the last vote index `LastActiveOf` when the target voter was last active, and means it is a "valid" claim that the target voter was an inactive voter between the registration and the last activate vote.
We then call `remove_voter` and:
* Delete the inactive voter with associated vote index and list of approval votes if "valid', OR
* Delete the reporter if "invalid"
Lastly we perform the reaping. If the reporter's claim that the target account was inactive was:
* "valid" then we call `repatriate_reserved` to slash the target account and move a `value` from their reserved account balance to the free balance of the reporter (`beneficiary`), and emit a `VoterReaped` event.
* "invalid" then we call `slash_reserved` to slash the reporter for their bad behaviour making a false claim. We slash the report's account by deducting up to a `value` from their reserved balance, and decrease the total amount of stake in the system by the amount that was slashed, and emit a `BadReaperSlashed` event.
---
### Democracy Module
---
#### Public Proposals and Referendums
##### Public Proposal (Proposing)
Source: srml/democracy/src/lib.rs
Public proposal is a public call that anyone may execute by signing and submitting an extrinsic. We ensure that information about the origin where the dispatch initiated is a signed account using `ensure_signed`.
It is considered valid when the provided deposit is above the minimum amount `minimum_deposit` that is required for a public proposal and when the proposer's has a sufficient account balance to transfer that deposit into a `reserve`.
We then increment the amount of public proposals `PublicPropCount` that have been made and store in `DepositOf` the proposal's index mapped to a tuple including the proposer's account id and their locked deposit.
Lastly we update the unsorted list of public proposals `PublicProps` with a tuple containing the proposal's index, the proposal itself, and their proposer's account id.
##### Public Proposal (Sponsorship/Seconding)
Source: srml/democracy/src/lib.rs
Sponsoring a public proposal is a public call that anyone may execute by signing and submitting an extrinsic. We ensure that information about the origin where the dispatch initiated is a signed account using `ensure_signed`.
It is considered valid sponsorship when:
* The proposal that is to be sponsored exists and a deposit from it's proposer has been locked away in the first element of the tuple returned when we lookup the proposal index in the mapping `DepositOf`
* The sponsor has a sufficient account balance to transfer a deposit into a `reserve`, where the amount matches the deposit that the proposer made.
##### Public Referendum (Voting)
Source: srml/democracy/src/lib.rs
Voting on a public referendum index is a public call that anyone may execute by signing and submitting an extrinsic. We ensure that information about the origin where the dispatch initiated is a signed account using `ensure_signed`.
It is considered a valid vote when:
* The referendum index that the voter wishes to vote is an index in the mapping `ReferendumInfoOf` (an active referendum)
* The voter (transactor) has a balance above zero to signal approval
If the voter has not voted for this referendum index yet (no record in `VoteOf`) then we update the `VotersFor` mapping of referendum index to associated voters, by adding the voter to to list of voters for this referendum index.
Lastly we add the vote of the voter associated with the referendum index to `VoteOf` that maps a tuple of the referendum index and voter's account id to their vote (yay or nay)
##### Public Referendum (Start)
Source: srml/democracy/src/lib.rs
Starting a public referendum is a public call that anyone may execute by signing and submitting an extrinsic. We ensure that information about the origin where the dispatch initiated is a signed account using `ensure_signed`.
It is not considered a valid public referendum when:
* There are existing referendum and the new referendum has a voting period that ends before the existing referendum with the preceeding referendum index.
The public referendum is allocated referendum index and the `ReferendumCount` is incremented.
We map the public referendum index to a tuple containing its corresponding voting period expiry block, the proposal it relates to, and the given voting threshold before emitting a `Started` event.
##### Public Referendum (Cancel)
Source: srml/democracy/src/lib.rs
We may remove all information about a referendum id.
##### Public Referendum (Elevating Public Proposals, Passing and Executing Referenda)
Source: srml/democracy/src/lib.rs
In modules of the SRML, the `on_finalise` signature is used in the module declaration to run anything that needs to be done at the end of the block.
In democracy it calls a private function `end_block` with the current block as an argument.
###### Winning Public Proposal elevated to the Table of Referenda as a Public Referendum
We first check the configured `LaunchPeriod` in blocks to see if a new public referendum should be launched. If so, we find and remove as the winning index from `PublicProps` the public proposal with the largest locked deposit amount.
We then use `DepositOf` to determine the account id's that locked a deposit
into the winning proposal and we refund them the deposit that was reserved.
We then emit a `Tabled` event to indicate that the winning proposal is being elevated to the Table of Referenda, and start a public referendum with the winning proposal using a voting threshold of 'super majority approve'.
###### Passing and Executing maturing Public Referenda
Lastly we search for any public referenda that are expiring at the current block to tally their votes by calling `maturing_referendums_at`.
We remove them from the Table of Referenda, and then either:
* Pass and execute the public referendum if the tally met the vote threshold
* Not pass the public referendum if the tally did not meet the vote threshold
We then increment the next referendum index `NextTally` that should be tallied.
---
### Assets Module
---
#### Asset Management (Issue, Transfer, Destroy)
##### Introduction
The assets module is for asset management, including the issuance, transfer, and destruction of fungible asset classes with a fixed supply.
> Note: Fungible assets may be easily interchanged by an identical equivalent, whereas non-fungible assets are digitally scarce since they offer unique characteristics.
##### Asset Issuance (Fungible)
> Source: srml/assets/src/lib.rs
An `origin` account may issue to themselves a `total` fixed supply in units of a new class of fungible assets that is allocated the next `AssetId` identifier. This triggers the `Issued` event.
##### Asset Transfer (Fungible)
> Source: srml/assets/src/lib.rs
An account may transfer an amount of units from their holding of an asset id to a recipient account `target`.
A transfer is valid when:
* The public call `transfer` is executed by an signed account (`origin`) when submitting an extrinsic, which we check using `ensure_signed`.
* The sender's `origin` account balance is greater than the amount to be transferred
It triggers the `Transferred` event.
##### Asset Destruction (Fungible)
> Source: srml/assets/src/lib.rs
An account may destroy their entire holding of an asset id if they have a non-zero balance. We ensure the public call `destroy` may only be executed by an signed account (`origin`) by using `ensure_signed` when submitting an extrinsic. It triggers the `Destroyed` event.
---
### Session Module
---
#### Session Management
##### Introduction
TODO
##### Session Keys of Validator Set for the Next Session
> Source: srml/session/src/lib.rs
* Session Key of a Validator that was set in the previous session with `set_key` (a public call since it uses `ensure_signed` that checks that the origin is a signed account) is used as their Validator Session Key in the next session, which uses `NextKeyFor` to store a mapping from their AccountID to the Session Key that each of them provide.
> Note: Instead of only allowing inherent calls by using `ensure_inherent`, it instead uses `ensure_signed` so that it is a public call since we want to allow users to set their Session Key prior to becoming a Validator, hence the Account ID of the origin that we are storing in `NextKeyFor` may not necessarily be associated with a block author or a validator, and so we must ensure that the Session Key of reaped (deleted) accounts is removed. See https://github.com/paritytech/substrate/issues/1201.
* Validator Set is set with `set_validators`, which calls `set_authority` of the Consensus SRML passing a set of Session Keys (which it generates from the provided list of Account IDs corresponding to each of the new validators that are to be set).
* Session Length (amount of block numbers) is set with `set_length` during a session and is used for subsequent sessions
##### Changing Session (Rewardable or Slashable)
> Source: srml/session/src/lib.rs
* Change (Rotate) Session at the end of the final block of the current Session Length using the `on_finalise` method (that is called by either an origin or internally from another SRML at the end of each block). It may be configured to be a normal rotation (rewardable, with rewards applied) or an exceptional (slashable) session. At the start of the next session we allocated a Session Index and record the timestamp when it started.
If a Next Session Length was recorded in the previous session we record it as the new Session Length, and if it is not the last block of the session we record it as the Last Length Change for the New Session.
#### Session Key for each Authority maps to an Index of the Validator Accound IDs
Lastly we iterate through each of current set of Validator Account IDs by index and check if a Session Key was created for it with `set_key` in the previous session, and if so we call `set_authority` of the Consensus SRML where we check if the Session Key of this current authority matches the Session Key (if any) stored under their index in the `AuthorityStorageVec` mapping, and if not we store their index to Session Key and update the saved list of original authorities if necessary (see https://github.com/paritytech/substrate/issues/1290).
> Note: Authorities are stored in the Consensus SRML. They are represented by the Validator index from the Session SRML and allocated with a Session Key for the length of the Session.
---
### Staking Module
---
#### Staking Management
##### Introduction
TODO
##### Validator Configuration and Preferences
> Source: srml/staking/src/lib.rs
* Validator Minimum Count is set (i.e. 4)
* Validator Preferences include an Unstake Threshold (i.e. 3) of slashes that are required before unstaking occurs (TODO: unstaking of whom?)
* Validator Preferences include the Validator Payment (Reward) that is received up-front. Further rewards are split with the Nominators (TODO: so the more nominators that are associated with a validator, the smaller the reward for each nominator)
In the Staking SRML we store accounts that have an intention to stake at the end of the `Intentions` vector when they call `stake`.
Then when a user wants to unstake they call `unstake` and provide as an argument the index where their intention was stored in the `Intentions` vector.
So they need to first iterate through the vector to retrieve their intention index before making this call.
We may want to benchmark lookup performance of retrieving their intention index and consider using a different data structure for when we have a very large number of intentions.
##### Staking, Staking Intentions, and Unstaking
> Source: srml/staking/src/lib.rs
* Transactors may call `stake` to be added to the list of `Intentions` (accounts with an intention to stake) and added to `Bondage` a mapping between their Account ID and the block number when their funds will become entirely liquid again. The effects of these changes are felt at the start of the Next Era. (TODO: why is this set to T::BlockNumber::max_value() instead of say `NextSessionsPerEra`? is it because `NextSessionsPerEra` may not yet be defined so we just set it to the maximum possible block number and then set the value again at the start of the Next Era when `NextSessionsPerEra` should be defined?)
* Transactors may call `unstake` and provide the Index of their Intention as an argument. So the Transactor should find the Index where their Account ID is stored in the `Intentions` vector prior to making the call. Verification occurs and prevents unstaking when there are: less staked participants than the Minimum Validator Count; or if the Account ID stored in the provided Index does not match that of the caller.
After verification: the Account ID is removed from the `Intentions` vector, `ValidatorPreferences` mapping, and the `SlashCount` mapping; the block number corresponding to when their bondage duration `BondingDuration` expires is mapped to their Account ID in `Bondage` (where a default `BondingDuration` such as 1000 eras is used unless it has been set with `set_bonding_duration`).
The effects of these changes are felt at the start of the Next Era.
##### Nominating, and Unnominating
> Source: srml/staking/src/lib.rs
* Transactors may call `nominate` and provide the `Address` of the target account that they wish to nominate. Verification occurs and prevents accounts nominating when they are already nominating or staked.
After verification: we add the calling Account ID (nominator) to the list of accounts that have nominated the target account (nominee) using `NominatorsFor`; add a mapping to `Nominating` with the nominator to their nominee; add a mapping to `Bondage` from their Account ID and the block number when their funds will become entirely liquid again.
The effects of these changes are felt at the start of the Next Era.
> Note: `stake`, `unstake`, and `nominate` are a public calls since they use `ensure_signed` that checks that the origin account calling the function is a signed account
* Transactions may call `unnominate` and provide the Index of the target account that they wish to unnominate.
So the Transactor should find the Index where the target Account ID is stored in the `NominatorsFor` mapping prior to making the call.
IN PROGRESS
* TODO - see if add a call that calculates the "total amount staked" at the start of an era adds any value (after all Bondage values have been updated from the max possible block number to the block number when their stake becomes liquid)
* TODO - see how implementers are using it to potentially improve the boilerplate (i.e. AdEx)
* TODO - Update to integrate Staking SRML based on these comments:
* https://github.com/paritytech/substrate/blob/master/srml/session/src/lib.rs#L180
* https://github.com/paritytech/substrate/blob/master/srml/session/src/lib.rs#L171
* explain normal rotation (rewardable, with rewards applied) session versus an exceptional (slashable) session