# ADR 042: Group Module
# Changelog
- 2020/04/09: Initial Draft
# Status
Draft
# Abstract
This ADR defines the x/group
module which allows the creation and management of on-chain multi-signature accounts and enables voting for message execution based on configurable decision policies.
# Context
The legacy amino multi-signature mechanism of the Cosmos SDK has certain limitations:
- Key rotation is not possible, although this can be solved with account rekeying.
- Thresholds can't be changed.
- UX is cumbersome for non-technical users (#5661 (opens new window)).
- It requires
legacy_amino
sign mode (#8141 (opens new window)).
While the group module is not meant to be a total replacement for the current multi-signature accounts, it provides a solution to the limitations described above, with a more flexible key management system where keys can be added, updated or removed, as well as configurable thresholds.
It's meant to be used with other access control modules such as x/feegrant
ans x/authz
to simplify key management for individuals and organizations.
The proof of concept of the group module can be found in https://github.com/regen-network/regen-ledger/tree/master/proto/regen/group/v1alpha1 and https://github.com/regen-network/regen-ledger/tree/master/x/group.
# Decision
We propose merging the x/group
module with its supporting ORM/Table Store package (opens new window) (#7098 (opens new window)) into the Cosmos SDK and continuing development here. There will be a dedicated ADR for the ORM package.
# Group
A group is a composition of accounts with associated weights. It is not an account and doesn't have a balance. It doesn't in and of itself have any sort of voting or decision weight. Group members can create proposals and vote on them through group accounts using different decision policies.
It has an admin
account which can manage members in the group, update the group
metadata and set a new admin.
# Group Account
A group account is an account associated with a group and a decision policy. A group account does have a balance.
Group accounts are abstracted from groups because a single group may have
multiple decision policies for different types of actions. Managing group
membership separately from decision policies results in the least overhead
and keeps membership consistent across different policies. The pattern that
is recommended is to have a single master group account for a given group,
and then to create separate group accounts with different decision policies
and delegate the desired permissions from the master account to
those "sub-accounts" using the x/authz
module.
Similarly to a group admin, a group account admin can update its metadata, decision policy or set a new group account admin.
A group account can also be an admin or a member of a group. For instance, a group admin could be another group account which could "elects" the members or it could be the same group that elects itself.
# Decision Policy
A decision policy is the mechanism by which members of a group can vote on proposals.
All decision policies should have a minimum and maximum voting window. The minimum voting window is the minimum duration that must pass in order for a proposal to potentially pass, and it may be set to 0. The maximum voting window is the maximum time that a proposal may be voted on and executed if it reached enough support before it is closed. Both of these values must be less than a chain-wide max voting window parameter.
We define the DecisionPolicy
interface that all decision policies must implement:
# Threshold decision policy
A threshold decision policy defines a minimum support votes (yes), based on a tally of voter weights, for a proposal to pass. For this decision policy, abstain and veto are treated as no support (no).
# Proposal
Any member of a group can submit a proposal for a group account to decide upon.
A proposal consists of a set of sdk.Msg
s that will be executed if the proposal
passes as well as any metadata associated with the proposal. These sdk.Msg
s get validated as part of the Msg/CreateProposal
request validation. They should also have their signer set as the group account.
Internally, a proposal also tracks:
- its current
Status
: submitted, closed or aborted - its
Result
: unfinalized, accepted or rejected - its
VoteState
in the form of aTally
, which is calculated on new votes and when executing the proposal.
# Voting
Members of a group can vote on proposals. There are four choices to choose while voting - yes, no, abstain and veto. Not all decision policies will support them. Votes can contain some optional metadata. In the current implementation, the voting window begins as soon as a proposal is submitted.
Voting internally updates the proposal VoteState
as well as Status
and Result
if needed.
# Executing Proposals
Proposals will not be automatically executed by the chain in this current design,
but rather a user must submit a Msg/Exec
transaction to attempt to execute the
proposal based on the current votes and decision policy. A future upgrade could
automate this and have the group account (or a fee granter) pay.
# Changing Group Membership
In the current implementation, updating a group or a group account after submitting a proposal will make it invalid. It will simply fail if someone calls Msg/Exec
and will eventually be garbage collected.
# Notes on current implementation
This section outlines the current implementation used in the proof of concept of the group module but this could be subject to changes and iterated on.
# ORM
The ORM package (opens new window) defines tables, sequences and secondary indexes which are used in the group module.
Groups are stored in state as part of a groupTable
, the group_id
being an auto-increment integer. Group members are stored in a groupMemberTable
.
Group accounts are stored in a groupAccountTable
. The group account address is generated based on an auto-increment integer which is used to derive the group module RootModuleKey
into a DerivedModuleKey
, as stated in ADR-033. The group account is added as a new ModuleAccount
through x/auth
.
Proposals are stored as part of the proposalTable
using the Proposal
type. The proposal_id
is an auto-increment integer.
Votes are stored in the voteTable
. The primary key is based on the vote's proposal_id
and voter
account address.
# ADR-033 to route proposal messages
Inter-module communication introduced by ADR-033 can be used to route a proposal's messages using the DerivedModuleKey
corresponding to the proposal's group account.
# Consequences
# Positive
- Improved UX for multi-signature accounts allowing key rotation and custom decision policies.
# Negative
# Neutral
- It uses ADR 033 so it will need to be implemented within the Cosmos SDK, but this doesn't imply necessarily any large refactoring of existing Cosmos SDK modules.
- The current implementation of the group module uses the ORM package.
# Further Discussions
- Convergence of
/group
andx/gov
as both support proposals and voting: https://github.com/cosmos/cosmos-sdk/discussions/9066 x/group
possible future improvements:- Execute proposals on submission (https://github.com/regen-network/regen-ledger/issues/288)
- Withdraw a proposal (https://github.com/regen-network/cosmos-modules/issues/41)
- Make
Tally
more flexible and support non-binary choices
# References
- Initial specification:
- https://gist.github.com/aaronc/b60628017352df5983791cad30babe56#group-module
- #5236 (opens new window)
- Proposal to add
x/group
into the SDK: #7633 (opens new window)