github.com/cosmos/cosmos-sdk@v0.50.10/docs/architecture/adr-042-group-module.md (about) 1 # ADR 042: Group Module 2 3 ## Changelog 4 5 * 2020/04/09: Initial Draft 6 7 ## Status 8 9 Draft 10 11 ## Abstract 12 13 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. 14 15 ## Context 16 17 The legacy amino multi-signature mechanism of the Cosmos SDK has certain limitations: 18 19 * Key rotation is not possible, although this can be solved with [account rekeying](adr-034-account-rekeying.md). 20 * Thresholds can't be changed. 21 * UX is cumbersome for non-technical users ([#5661](https://github.com/cosmos/cosmos-sdk/issues/5661)). 22 * It requires `legacy_amino` sign mode ([#8141](https://github.com/cosmos/cosmos-sdk/issues/8141)). 23 24 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. 25 It's meant to be used with other access control modules such as [`x/feegrant`](./adr-029-fee-grant-module.md) ans [`x/authz`](adr-030-authz-module.md) to simplify key management for individuals and organizations. 26 27 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. 28 29 ## Decision 30 31 We propose merging the `x/group` module with its supporting [ORM/Table Store package](https://github.com/regen-network/regen-ledger/tree/master/orm) ([#7098](https://github.com/cosmos/cosmos-sdk/issues/7098)) into the Cosmos SDK and continuing development here. There will be a dedicated ADR for the ORM package. 32 33 ### Group 34 35 A group is a composition of accounts with associated weights. It is not 36 an account and doesn't have a balance. It doesn't in and of itself have any 37 sort of voting or decision weight. 38 Group members can create proposals and vote on them through group accounts using different decision policies. 39 40 It has an `admin` account which can manage members in the group, update the group 41 metadata and set a new admin. 42 43 ```protobuf 44 message GroupInfo { 45 46 // group_id is the unique ID of this group. 47 uint64 group_id = 1; 48 49 // admin is the account address of the group's admin. 50 string admin = 2; 51 52 // metadata is any arbitrary metadata to attached to the group. 53 bytes metadata = 3; 54 55 // version is used to track changes to a group's membership structure that 56 // would break existing proposals. Whenever a member weight has changed, 57 // or any member is added or removed, the version is incremented and will 58 // invalidate all proposals from older versions. 59 uint64 version = 4; 60 61 // total_weight is the sum of the group members' weights. 62 string total_weight = 5; 63 } 64 ``` 65 66 ```protobuf 67 message GroupMember { 68 69 // group_id is the unique ID of the group. 70 uint64 group_id = 1; 71 72 // member is the member data. 73 Member member = 2; 74 } 75 76 // Member represents a group member with an account address, 77 // non-zero weight and metadata. 78 message Member { 79 80 // address is the member's account address. 81 string address = 1; 82 83 // weight is the member's voting weight that should be greater than 0. 84 string weight = 2; 85 86 // metadata is any arbitrary metadata to attached to the member. 87 bytes metadata = 3; 88 } 89 ``` 90 91 ### Group Account 92 93 A group account is an account associated with a group and a decision policy. 94 A group account does have a balance. 95 96 Group accounts are abstracted from groups because a single group may have 97 multiple decision policies for different types of actions. Managing group 98 membership separately from decision policies results in the least overhead 99 and keeps membership consistent across different policies. The pattern that 100 is recommended is to have a single master group account for a given group, 101 and then to create separate group accounts with different decision policies 102 and delegate the desired permissions from the master account to 103 those "sub-accounts" using the [`x/authz` module](adr-030-authz-module.md). 104 105 ```protobuf 106 message GroupAccountInfo { 107 108 // address is the group account address. 109 string address = 1; 110 111 // group_id is the ID of the Group the GroupAccount belongs to. 112 uint64 group_id = 2; 113 114 // admin is the account address of the group admin. 115 string admin = 3; 116 117 // metadata is any arbitrary metadata of this group account. 118 bytes metadata = 4; 119 120 // version is used to track changes to a group's GroupAccountInfo structure that 121 // invalidates active proposal from old versions. 122 uint64 version = 5; 123 124 // decision_policy specifies the group account's decision policy. 125 google.protobuf.Any decision_policy = 6 [(cosmos_proto.accepts_interface) = "cosmos.group.v1.DecisionPolicy"]; 126 } 127 ``` 128 129 Similarly to a group admin, a group account admin can update its metadata, decision policy or set a new group account admin. 130 131 A group account can also be an admin or a member of a group. 132 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. 133 134 ### Decision Policy 135 136 A decision policy is the mechanism by which members of a group can vote on 137 proposals. 138 139 All decision policies should have a minimum and maximum voting window. 140 The minimum voting window is the minimum duration that must pass in order 141 for a proposal to potentially pass, and it may be set to 0. The maximum voting 142 window is the maximum time that a proposal may be voted on and executed if 143 it reached enough support before it is closed. 144 Both of these values must be less than a chain-wide max voting window parameter. 145 146 We define the `DecisionPolicy` interface that all decision policies must implement: 147 148 ```go 149 type DecisionPolicy interface { 150 codec.ProtoMarshaler 151 152 ValidateBasic() error 153 GetTimeout() types.Duration 154 Allow(tally Tally, totalPower string, votingDuration time.Duration) (DecisionPolicyResult, error) 155 Validate(g GroupInfo) error 156 } 157 158 type DecisionPolicyResult struct { 159 Allow bool 160 Final bool 161 } 162 ``` 163 164 #### Threshold decision policy 165 166 A threshold decision policy defines a minimum support votes (_yes_), based on a tally 167 of voter weights, for a proposal to pass. For 168 this decision policy, abstain and veto are treated as no support (_no_). 169 170 ```protobuf 171 message ThresholdDecisionPolicy { 172 173 // threshold is the minimum weighted sum of support votes for a proposal to succeed. 174 string threshold = 1; 175 176 // voting_period is the duration from submission of a proposal to the end of voting period 177 // Within this period, votes and exec messages can be submitted. 178 google.protobuf.Duration voting_period = 2 [(gogoproto.nullable) = false]; 179 } 180 ``` 181 182 ### Proposal 183 184 Any member of a group can submit a proposal for a group account to decide upon. 185 A proposal consists of a set of `sdk.Msg`s that will be executed if the proposal 186 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. 187 188 Internally, a proposal also tracks: 189 190 * its current `Status`: submitted, closed or aborted 191 * its `Result`: unfinalized, accepted or rejected 192 * its `VoteState` in the form of a `Tally`, which is calculated on new votes and when executing the proposal. 193 194 ```protobuf 195 // Tally represents the sum of weighted votes. 196 message Tally { 197 option (gogoproto.goproto_getters) = false; 198 199 // yes_count is the weighted sum of yes votes. 200 string yes_count = 1; 201 202 // no_count is the weighted sum of no votes. 203 string no_count = 2; 204 205 // abstain_count is the weighted sum of abstainers. 206 string abstain_count = 3; 207 208 // veto_count is the weighted sum of vetoes. 209 string veto_count = 4; 210 } 211 ``` 212 213 ### Voting 214 215 Members of a group can vote on proposals. There are four choices to choose while voting - yes, no, abstain and veto. Not 216 all decision policies will support them. Votes can contain some optional metadata. 217 In the current implementation, the voting window begins as soon as a proposal 218 is submitted. 219 220 Voting internally updates the proposal `VoteState` as well as `Status` and `Result` if needed. 221 222 ### Executing Proposals 223 224 Proposals will not be automatically executed by the chain in this current design, 225 but rather a user must submit a `Msg/Exec` transaction to attempt to execute the 226 proposal based on the current votes and decision policy. A future upgrade could 227 automate this and have the group account (or a fee granter) pay. 228 229 #### Changing Group Membership 230 231 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. 232 233 ### Notes on current implementation 234 235 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. 236 237 #### ORM 238 239 The [ORM package](https://github.com/cosmos/cosmos-sdk/discussions/9156) defines tables, sequences and secondary indexes which are used in the group module. 240 241 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`. 242 243 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](adr-033-protobuf-inter-module-comm.md#modulekeys-and-moduleids). The group account is added as a new `ModuleAccount` through `x/auth`. 244 245 Proposals are stored as part of the `proposalTable` using the `Proposal` type. The `proposal_id` is an auto-increment integer. 246 247 Votes are stored in the `voteTable`. The primary key is based on the vote's `proposal_id` and `voter` account address. 248 249 #### ADR-033 to route proposal messages 250 251 Inter-module communication introduced by [ADR-033](adr-033-protobuf-inter-module-comm.md) can be used to route a proposal's messages using the `DerivedModuleKey` corresponding to the proposal's group account. 252 253 ## Consequences 254 255 ### Positive 256 257 * Improved UX for multi-signature accounts allowing key rotation and custom decision policies. 258 259 ### Negative 260 261 ### Neutral 262 263 * 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. 264 * The current implementation of the group module uses the ORM package. 265 266 ## Further Discussions 267 268 * Convergence of `/group` and `x/gov` as both support proposals and voting: https://github.com/cosmos/cosmos-sdk/discussions/9066 269 * `x/group` possible future improvements: 270 * Execute proposals on submission (https://github.com/regen-network/regen-ledger/issues/288) 271 * Withdraw a proposal (https://github.com/regen-network/cosmos-modules/issues/41) 272 * Make `Tally` more flexible and support non-binary choices 273 274 ## References 275 276 * Initial specification: 277 * https://gist.github.com/aaronc/b60628017352df5983791cad30babe56#group-module 278 * [#5236](https://github.com/cosmos/cosmos-sdk/pull/5236) 279 * Proposal to add `x/group` into the Cosmos SDK: [#7633](https://github.com/cosmos/cosmos-sdk/issues/7633)