feat(group): Prune expired proposals and votes (#11315)

## Description

Closes: #11245

### 1. Pruning proposal.

Proposals are pruned:
- after a successful MsgExec (or try_exec)
- on voting_period_end + max_execution_period

whichever happens first.

### 2. Pruning votes.

Votes are pruned:
  - on MsgExec (or try_exec) if tally is final
  - on voting_period_end

whichever happens first. 

### 3. Various group fixes

See #11404 for details

TODO:
- [x] Add vote pruning after Tally, once #11310  is merged
- [x] Add tests



---

### Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*

I have...

- [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] added `!` to the type prefix if API or client breaking change
- [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting))
- [ ] provided a link to the relevant issue or specification
- [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules)
- [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing)
- [ ] added a changelog entry to `CHANGELOG.md`
- [ ] included comments for [documenting Go code](https://blog.golang.org/godoc)
- [ ] updated the relevant documentation or specification
- [ ] reviewed "Files changed" and left comments if necessary
- [ ] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

- [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] confirmed `!` in the type prefix if API or client breaking change
- [ ] confirmed all author checklist items have been addressed 
- [ ] reviewed state machine logic
- [ ] reviewed API design and naming
- [ ] reviewed documentation is accurate
- [ ] reviewed tests and test coverage
- [ ] manually tested (if applicable)
This commit is contained in:
Amaury 2022-03-28 06:44:01 +02:00 committed by GitHub
parent 73c9f42fc0
commit 5491be27d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 956 additions and 693 deletions

View File

@ -2876,12 +2876,14 @@ func (x *fastReflection_EventVote) ProtoMethods() *protoiface.Methods {
var (
md_EventExec protoreflect.MessageDescriptor
fd_EventExec_proposal_id protoreflect.FieldDescriptor
fd_EventExec_result protoreflect.FieldDescriptor
)
func init() {
file_cosmos_group_v1_events_proto_init()
md_EventExec = File_cosmos_group_v1_events_proto.Messages().ByName("EventExec")
fd_EventExec_proposal_id = md_EventExec.Fields().ByName("proposal_id")
fd_EventExec_result = md_EventExec.Fields().ByName("result")
}
var _ protoreflect.Message = (*fastReflection_EventExec)(nil)
@ -2955,6 +2957,12 @@ func (x *fastReflection_EventExec) Range(f func(protoreflect.FieldDescriptor, pr
return
}
}
if x.Result != 0 {
value := protoreflect.ValueOfEnum((protoreflect.EnumNumber)(x.Result))
if !f(fd_EventExec_result, value) {
return
}
}
}
// Has reports whether a field is populated.
@ -2972,6 +2980,8 @@ func (x *fastReflection_EventExec) Has(fd protoreflect.FieldDescriptor) bool {
switch fd.FullName() {
case "cosmos.group.v1.EventExec.proposal_id":
return x.ProposalId != uint64(0)
case "cosmos.group.v1.EventExec.result":
return x.Result != 0
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec"))
@ -2990,6 +3000,8 @@ func (x *fastReflection_EventExec) Clear(fd protoreflect.FieldDescriptor) {
switch fd.FullName() {
case "cosmos.group.v1.EventExec.proposal_id":
x.ProposalId = uint64(0)
case "cosmos.group.v1.EventExec.result":
x.Result = 0
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec"))
@ -3009,6 +3021,9 @@ func (x *fastReflection_EventExec) Get(descriptor protoreflect.FieldDescriptor)
case "cosmos.group.v1.EventExec.proposal_id":
value := x.ProposalId
return protoreflect.ValueOfUint64(value)
case "cosmos.group.v1.EventExec.result":
value := x.Result
return protoreflect.ValueOfEnum((protoreflect.EnumNumber)(value))
default:
if descriptor.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec"))
@ -3031,6 +3046,8 @@ func (x *fastReflection_EventExec) Set(fd protoreflect.FieldDescriptor, value pr
switch fd.FullName() {
case "cosmos.group.v1.EventExec.proposal_id":
x.ProposalId = value.Uint()
case "cosmos.group.v1.EventExec.result":
x.Result = (ProposalExecutorResult)(value.Enum())
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec"))
@ -3053,6 +3070,8 @@ func (x *fastReflection_EventExec) Mutable(fd protoreflect.FieldDescriptor) prot
switch fd.FullName() {
case "cosmos.group.v1.EventExec.proposal_id":
panic(fmt.Errorf("field proposal_id of message cosmos.group.v1.EventExec is not mutable"))
case "cosmos.group.v1.EventExec.result":
panic(fmt.Errorf("field result of message cosmos.group.v1.EventExec is not mutable"))
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec"))
@ -3068,6 +3087,8 @@ func (x *fastReflection_EventExec) NewField(fd protoreflect.FieldDescriptor) pro
switch fd.FullName() {
case "cosmos.group.v1.EventExec.proposal_id":
return protoreflect.ValueOfUint64(uint64(0))
case "cosmos.group.v1.EventExec.result":
return protoreflect.ValueOfEnum(0)
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec"))
@ -3140,6 +3161,9 @@ func (x *fastReflection_EventExec) ProtoMethods() *protoiface.Methods {
if x.ProposalId != 0 {
n += 1 + runtime.Sov(uint64(x.ProposalId))
}
if x.Result != 0 {
n += 1 + runtime.Sov(uint64(x.Result))
}
if x.unknownFields != nil {
n += len(x.unknownFields)
}
@ -3169,6 +3193,11 @@ func (x *fastReflection_EventExec) ProtoMethods() *protoiface.Methods {
i -= len(x.unknownFields)
copy(dAtA[i:], x.unknownFields)
}
if x.Result != 0 {
i = runtime.EncodeVarint(dAtA, i, uint64(x.Result))
i--
dAtA[i] = 0x10
}
if x.ProposalId != 0 {
i = runtime.EncodeVarint(dAtA, i, uint64(x.ProposalId))
i--
@ -3242,6 +3271,25 @@ func (x *fastReflection_EventExec) ProtoMethods() *protoiface.Methods {
break
}
}
case 2:
if wireType != 0 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field Result", wireType)
}
x.Result = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow
}
if iNdEx >= l {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
x.Result |= ProposalExecutorResult(b&0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := runtime.Skip(dAtA[iNdEx:])
@ -4025,6 +4073,8 @@ type EventExec struct {
// proposal_id is the unique ID of the proposal.
ProposalId uint64 `protobuf:"varint,1,opt,name=proposal_id,json=proposalId,proto3" json:"proposal_id,omitempty"`
// result is the proposal execution result.
Result ProposalExecutorResult `protobuf:"varint,2,opt,name=result,proto3,enum=cosmos.group.v1.ProposalExecutorResult" json:"result,omitempty"`
}
func (x *EventExec) Reset() {
@ -4054,6 +4104,13 @@ func (x *EventExec) GetProposalId() uint64 {
return 0
}
func (x *EventExec) GetResult() ProposalExecutorResult {
if x != nil {
return x.Result
}
return ProposalExecutorResult_PROPOSAL_EXECUTOR_RESULT_UNSPECIFIED
}
// EventLeaveGroup is an event emitted when group member leaves the group.
type EventLeaveGroup struct {
state protoimpl.MessageState
@ -4107,54 +4164,60 @@ var file_cosmos_group_v1_events_proto_rawDesc = []byte{
0x31, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f,
0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x76, 0x31, 0x1a,
0x19, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f,
0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x2d, 0x0a, 0x10, 0x45, 0x76,
0x65, 0x6e, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x19,
0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x22, 0x2d, 0x0a, 0x10, 0x45, 0x76, 0x65,
0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x19, 0x0a,
0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52,
0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x22, 0x4c, 0x0a, 0x16, 0x45, 0x76, 0x65, 0x6e,
0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x50, 0x6f, 0x6c, 0x69,
0x63, 0x79, 0x12, 0x32, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e,
0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x4c, 0x0a, 0x16, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55,
0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79,
0x12, 0x32, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x22, 0x36, 0x0a, 0x13, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62,
0x6d, 0x69, 0x74, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70,
0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x49, 0x64, 0x22, 0x38, 0x0a, 0x15,
0x45, 0x76, 0x65, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x50, 0x72, 0x6f,
0x70, 0x6f, 0x73, 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61,
0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70,
0x6f, 0x73, 0x61, 0x6c, 0x49, 0x64, 0x22, 0x2c, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x56,
0x6f, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73,
0x61, 0x6c, 0x49, 0x64, 0x22, 0x2c, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x45, 0x78, 0x65,
0x63, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c,
0x49, 0x64, 0x22, 0x60, 0x0a, 0x0f, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4c, 0x65, 0x61, 0x76, 0x65,
0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64,
0x12, 0x32, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x42, 0xba, 0x01, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x73,
0x6d, 0x6f, 0x73, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x76, 0x31, 0x42, 0x0b, 0x45, 0x76,
0x65, 0x6e, 0x74, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x38, 0x67, 0x69, 0x74,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x63,
0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2d, 0x73, 0x64, 0x6b, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f,
0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2f, 0x76, 0x31, 0x3b, 0x67, 0x72,
0x6f, 0x75, 0x70, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x47, 0x58, 0xaa, 0x02, 0x0f, 0x43, 0x6f,
0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0f,
0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x5c, 0x56, 0x31, 0xe2,
0x02, 0x1b, 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x5c, 0x56,
0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x11,
0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x3a, 0x3a, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x3a, 0x3a, 0x56,
0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x63, 0x6f, 0x73, 0x6d,
0x6f, 0x73, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x79, 0x70, 0x65,
0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x2d, 0x0a, 0x10, 0x45, 0x76, 0x65, 0x6e, 0x74,
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x67,
0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x67,
0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x22, 0x2d, 0x0a, 0x10, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55,
0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72,
0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x67, 0x72,
0x6f, 0x75, 0x70, 0x49, 0x64, 0x22, 0x4c, 0x0a, 0x16, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x43, 0x72,
0x65, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12,
0x32, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x22, 0x4c, 0x0a, 0x16, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61,
0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x32, 0x0a,
0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18,
0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x22, 0x36, 0x0a, 0x13, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74,
0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70,
0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70,
0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x49, 0x64, 0x22, 0x38, 0x0a, 0x15, 0x45, 0x76, 0x65,
0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73,
0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61,
0x6c, 0x49, 0x64, 0x22, 0x2c, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x56, 0x6f, 0x74, 0x65,
0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x49,
0x64, 0x22, 0x6d, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x45, 0x78, 0x65, 0x63, 0x12, 0x1f,
0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x49, 0x64, 0x12,
0x3f, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x27, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x76,
0x31, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74,
0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74,
0x22, 0x60, 0x0a, 0x0f, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x47, 0x72,
0x6f, 0x75, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x32,
0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42,
0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x42, 0xba, 0x01, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f,
0x73, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x76, 0x31, 0x42, 0x0b, 0x45, 0x76, 0x65, 0x6e,
0x74, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75,
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x63, 0x6f, 0x73,
0x6d, 0x6f, 0x73, 0x2d, 0x73, 0x64, 0x6b, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x73, 0x6d,
0x6f, 0x73, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2f, 0x76, 0x31, 0x3b, 0x67, 0x72, 0x6f, 0x75,
0x70, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x47, 0x58, 0xaa, 0x02, 0x0f, 0x43, 0x6f, 0x73, 0x6d,
0x6f, 0x73, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0f, 0x43, 0x6f,
0x73, 0x6d, 0x6f, 0x73, 0x5c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x1b,
0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x5c, 0x56, 0x31, 0x5c,
0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x11, 0x43, 0x6f,
0x73, 0x6d, 0x6f, 0x73, 0x3a, 0x3a, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x3a, 0x3a, 0x56, 0x31, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -4180,13 +4243,15 @@ var file_cosmos_group_v1_events_proto_goTypes = []interface{}{
(*EventVote)(nil), // 6: cosmos.group.v1.EventVote
(*EventExec)(nil), // 7: cosmos.group.v1.EventExec
(*EventLeaveGroup)(nil), // 8: cosmos.group.v1.EventLeaveGroup
(ProposalExecutorResult)(0), // 9: cosmos.group.v1.ProposalExecutorResult
}
var file_cosmos_group_v1_events_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
9, // 0: cosmos.group.v1.EventExec.result:type_name -> cosmos.group.v1.ProposalExecutorResult
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_cosmos_group_v1_events_proto_init() }
@ -4194,6 +4259,7 @@ func file_cosmos_group_v1_events_proto_init() {
if File_cosmos_group_v1_events_proto != nil {
return
}
file_cosmos_group_v1_types_proto_init()
if !protoimpl.UnsafeEnabled {
file_cosmos_group_v1_events_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*EventCreateGroup); i {

View File

@ -3,6 +3,7 @@ syntax = "proto3";
package cosmos.group.v1;
import "cosmos_proto/cosmos.proto";
import "cosmos/group/v1/types.proto";
option go_package = "github.com/cosmos/cosmos-sdk/x/group";
@ -60,6 +61,9 @@ message EventExec {
// proposal_id is the unique ID of the proposal.
uint64 proposal_id = 1;
// result is the proposal execution result.
ProposalExecutorResult result = 2;
}
// EventLeaveGroup is an event emitted when group member leaves the group.

View File

@ -349,6 +349,8 @@ func (m *EventVote) GetProposalId() uint64 {
type EventExec struct {
// proposal_id is the unique ID of the proposal.
ProposalId uint64 `protobuf:"varint,1,opt,name=proposal_id,json=proposalId,proto3" json:"proposal_id,omitempty"`
// result is the proposal execution result.
Result ProposalExecutorResult `protobuf:"varint,2,opt,name=result,proto3,enum=cosmos.group.v1.ProposalExecutorResult" json:"result,omitempty"`
}
func (m *EventExec) Reset() { *m = EventExec{} }
@ -391,6 +393,13 @@ func (m *EventExec) GetProposalId() uint64 {
return 0
}
func (m *EventExec) GetResult() ProposalExecutorResult {
if m != nil {
return m.Result
}
return PROPOSAL_EXECUTOR_RESULT_UNSPECIFIED
}
// EventLeaveGroup is an event emitted when group member leaves the group.
type EventLeaveGroup struct {
// group_id is the unique ID of the group.
@ -461,27 +470,30 @@ func init() {
func init() { proto.RegisterFile("cosmos/group/v1/events.proto", fileDescriptor_e8d753981546f032) }
var fileDescriptor_e8d753981546f032 = []byte{
// 314 bytes of a gzipped FileDescriptorProto
// 366 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x49, 0xce, 0x2f, 0xce,
0xcd, 0x2f, 0xd6, 0x4f, 0x2f, 0xca, 0x2f, 0x2d, 0xd0, 0x2f, 0x33, 0xd4, 0x4f, 0x2d, 0x4b, 0xcd,
0x2b, 0x29, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x87, 0xc8, 0xea, 0x81, 0x65, 0xf5,
0xca, 0x0c, 0xa5, 0x24, 0x21, 0x02, 0xf1, 0x60, 0x69, 0x7d, 0xa8, 0x2c, 0x98, 0xa3, 0xa4, 0xcb,
0x25, 0xe0, 0x0a, 0xd2, 0xeb, 0x5c, 0x94, 0x9a, 0x58, 0x92, 0xea, 0x0e, 0xd2, 0x21, 0x24, 0xc9,
0xc5, 0x01, 0xd6, 0x1a, 0x9f, 0x99, 0x22, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x12, 0xc4, 0x0e, 0xe6,
0x7b, 0xa6, 0xc0, 0x95, 0x87, 0x16, 0xa4, 0x10, 0xa3, 0xdc, 0x87, 0x4b, 0x0c, 0xdd, 0xf4, 0x80,
0xfc, 0x9c, 0xcc, 0xe4, 0x4a, 0x21, 0x23, 0x2e, 0xf6, 0xc4, 0x94, 0x94, 0xa2, 0xd4, 0xe2, 0x62,
0xb0, 0x1e, 0x4e, 0x27, 0x89, 0x4b, 0x5b, 0x74, 0x45, 0xa0, 0x4e, 0x73, 0x84, 0xc8, 0x04, 0x97,
0x14, 0x65, 0xe6, 0xa5, 0x07, 0xc1, 0x14, 0xc2, 0x4d, 0x43, 0xb2, 0x9c, 0x02, 0xd3, 0xcc, 0xb8,
0x84, 0xc1, 0xa6, 0x05, 0x97, 0x26, 0xe5, 0x66, 0x96, 0x04, 0x14, 0xe5, 0x17, 0xe4, 0x17, 0x27,
0xe6, 0x08, 0xc9, 0x73, 0x71, 0x17, 0x40, 0xd9, 0x08, 0x0f, 0x71, 0xc1, 0x84, 0x3c, 0x53, 0x94,
0x2c, 0xb8, 0x44, 0xc1, 0xfa, 0xc2, 0x33, 0x4b, 0x32, 0x52, 0x8a, 0x12, 0xcb, 0x89, 0xd7, 0xa9,
0xc3, 0xc5, 0x09, 0xd6, 0x19, 0x96, 0x5f, 0x92, 0x4a, 0xbc, 0x6a, 0xd7, 0x8a, 0xd4, 0x64, 0xc2,
0xaa, 0x13, 0xb8, 0xf8, 0xc1, 0xaa, 0x7d, 0x52, 0x13, 0xcb, 0x08, 0xc6, 0x0b, 0x72, 0x78, 0x31,
0x11, 0x19, 0x5e, 0x4e, 0x76, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91,
0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0xa5,
0x92, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0x0b, 0x4d, 0x5c, 0x50, 0x4a, 0xb7,
0x38, 0x25, 0x5b, 0xbf, 0x02, 0x92, 0x4a, 0x93, 0xd8, 0xc0, 0x09, 0xce, 0x18, 0x10, 0x00, 0x00,
0xff, 0xff, 0xa2, 0x42, 0x09, 0x69, 0xbc, 0x02, 0x00, 0x00,
0xca, 0x0c, 0xa5, 0x24, 0x21, 0x02, 0xf1, 0x60, 0x69, 0x7d, 0xa8, 0x2c, 0x98, 0x23, 0x25, 0x8d,
0x6e, 0x52, 0x49, 0x65, 0x41, 0x2a, 0x54, 0x52, 0x49, 0x97, 0x4b, 0xc0, 0x15, 0x64, 0xb0, 0x73,
0x51, 0x6a, 0x62, 0x49, 0xaa, 0x3b, 0x48, 0x89, 0x90, 0x24, 0x17, 0x07, 0x58, 0x6d, 0x7c, 0x66,
0x8a, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x4b, 0x10, 0x3b, 0x98, 0xef, 0x99, 0x02, 0x57, 0x1e, 0x5a,
0x90, 0x42, 0x8c, 0x72, 0x1f, 0x2e, 0x31, 0x74, 0xd3, 0x03, 0xf2, 0x73, 0x32, 0x93, 0x2b, 0x85,
0x8c, 0xb8, 0xd8, 0x13, 0x53, 0x52, 0x8a, 0x52, 0x8b, 0x8b, 0xc1, 0x7a, 0x38, 0x9d, 0x24, 0x2e,
0x6d, 0xd1, 0x15, 0x81, 0xba, 0xdb, 0x11, 0x22, 0x13, 0x5c, 0x52, 0x94, 0x99, 0x97, 0x1e, 0x04,
0x53, 0x08, 0x37, 0x0d, 0xc9, 0x72, 0x0a, 0x4c, 0x33, 0xe3, 0x12, 0x06, 0x9b, 0x16, 0x5c, 0x9a,
0x94, 0x9b, 0x59, 0x12, 0x50, 0x94, 0x5f, 0x90, 0x5f, 0x9c, 0x98, 0x23, 0x24, 0xcf, 0xc5, 0x5d,
0x00, 0x65, 0x23, 0x3c, 0xc4, 0x05, 0x13, 0xf2, 0x4c, 0x51, 0xb2, 0xe0, 0x12, 0x05, 0xeb, 0x0b,
0xcf, 0x2c, 0xc9, 0x48, 0x29, 0x4a, 0x2c, 0x27, 0x5e, 0xa7, 0x0e, 0x17, 0x27, 0x58, 0x67, 0x58,
0x7e, 0x49, 0x2a, 0x61, 0xd5, 0xb9, 0x50, 0xd5, 0xae, 0x15, 0xa9, 0xc9, 0x04, 0x55, 0x0b, 0xd9,
0x73, 0xb1, 0x15, 0xa5, 0x16, 0x97, 0xe6, 0x94, 0x48, 0x30, 0x29, 0x30, 0x6a, 0xf0, 0x19, 0xa9,
0xeb, 0xa1, 0xa5, 0x10, 0x3d, 0x98, 0x3b, 0x41, 0xe6, 0x95, 0x96, 0xe4, 0x17, 0x05, 0x81, 0x95,
0x07, 0x41, 0xb5, 0x29, 0x25, 0x70, 0xf1, 0x83, 0xad, 0xf3, 0x49, 0x4d, 0x2c, 0x23, 0x18, 0xb1,
0xc8, 0x01, 0xce, 0x44, 0x64, 0x80, 0x3b, 0xd9, 0x9d, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c,
0xe3, 0x83, 0x47, 0x72, 0x8c, 0x13, 0x1e, 0xcb, 0x31, 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1,
0x1c, 0x43, 0x94, 0x4a, 0x7a, 0x66, 0x49, 0x46, 0x69, 0x92, 0x5e, 0x72, 0x7e, 0x2e, 0x34, 0xe9,
0x42, 0x29, 0xdd, 0xe2, 0x94, 0x6c, 0xfd, 0x0a, 0x48, 0xca, 0x4d, 0x62, 0x03, 0xa7, 0x58, 0x63,
0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe6, 0x94, 0x89, 0xaf, 0x1a, 0x03, 0x00, 0x00,
}
func (m *EventCreateGroup) Marshal() (dAtA []byte, err error) {
@ -704,6 +716,11 @@ func (m *EventExec) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
if m.Result != 0 {
i = encodeVarintEvents(dAtA, i, uint64(m.Result))
i--
dAtA[i] = 0x10
}
if m.ProposalId != 0 {
i = encodeVarintEvents(dAtA, i, uint64(m.ProposalId))
i--
@ -853,6 +870,9 @@ func (m *EventExec) Size() (n int) {
if m.ProposalId != 0 {
n += 1 + sovEvents(uint64(m.ProposalId))
}
if m.Result != 0 {
n += 1 + sovEvents(uint64(m.Result))
}
return n
}
@ -1435,6 +1455,25 @@ func (m *EventExec) Unmarshal(dAtA []byte) error {
break
}
}
case 2:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Result", wireType)
}
m.Result = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowEvents
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Result |= ProposalExecutorResult(b&0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := skipEvents(dAtA[iNdEx:])

View File

@ -20,24 +20,10 @@ const (
// RegisterInvariants registers all group invariants
func RegisterInvariants(ir sdk.InvariantRegistry, keeper Keeper) {
ir.RegisterRoute(group.ModuleName, votesInvariant, TallyVotesInvariant(keeper))
ir.RegisterRoute(group.ModuleName, weightInvariant, GroupTotalWeightInvariant(keeper))
ir.RegisterRoute(group.ModuleName, votesSumInvariant, TallyVotesSumInvariant(keeper))
}
// TallyVotesInvariant checks that vote tally sums must never have less than the block before.
func TallyVotesInvariant(keeper Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
if ctx.BlockHeight()-1 < 0 {
return sdk.FormatInvariant(group.ModuleName, votesInvariant, "Not enough blocks to perform TallyVotesInvariant"), false
}
prevCtx, _ := ctx.CacheContext()
prevCtx = prevCtx.WithBlockHeight(ctx.BlockHeight() - 1)
msg, broken := TallyVotesInvariantHelper(ctx, prevCtx, keeper.key, keeper.proposalTable)
return sdk.FormatInvariant(group.ModuleName, votesInvariant, msg), broken
}
}
// GroupTotalWeightInvariant checks that group's TotalWeight must be equal to the sum of its members.
func GroupTotalWeightInvariant(keeper Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
@ -46,105 +32,20 @@ func GroupTotalWeightInvariant(keeper Keeper) sdk.Invariant {
}
}
// TallyVotesSumInvariant checks that proposal FinalTallyResult must correspond to the vote option.
// TallyVotesSumInvariant checks that proposal FinalTallyResult must correspond to the vote option,
// for proposals with PROPOSAL_STATUS_CLOSED status.
func TallyVotesSumInvariant(keeper Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
msg, broken := TallyVotesSumInvariantHelper(ctx, keeper.key, keeper.groupTable, keeper.proposalTable, keeper.groupMemberTable, keeper.voteByProposalIndex, keeper.groupPolicyTable)
msg, broken := TallyVotesSumInvariantHelper(ctx, keeper.key, keeper.proposalTable, keeper.groupMemberTable, keeper.voteByProposalIndex, keeper.groupPolicyTable)
return sdk.FormatInvariant(group.ModuleName, votesSumInvariant, msg), broken
}
}
func TallyVotesInvariantHelper(ctx sdk.Context, prevCtx sdk.Context, key storetypes.StoreKey, proposalTable orm.AutoUInt64Table) (string, bool) {
var msg string
var broken bool
prevIt, err := proposalTable.PrefixScan(prevCtx.KVStore(key), 1, math.MaxUint64)
if err != nil {
msg += fmt.Sprintf("PrefixScan failure on proposal table at block height %d\n%v\n", prevCtx.BlockHeight(), err)
return msg, broken
}
curIt, err := proposalTable.PrefixScan(ctx.KVStore(key), 1, math.MaxUint64)
if err != nil {
msg += fmt.Sprintf("PrefixScan failure on proposal table at block height %d\n%v\n", ctx.BlockHeight(), err)
return msg, broken
}
var curProposals []*group.Proposal
_, err = orm.ReadAll(curIt, &curProposals)
if err != nil {
msg += fmt.Sprintf("error while getting all the proposals at block height %d\n%v\n", ctx.BlockHeight(), err)
return msg, broken
}
var prevProposals []*group.Proposal
_, err = orm.ReadAll(prevIt, &prevProposals)
if err != nil {
msg += fmt.Sprintf("error while getting all the proposals at block height %d\n%v\n", prevCtx.BlockHeight(), err)
return msg, broken
}
for i := 0; i < len(prevProposals); i++ {
if prevProposals[i].Id == curProposals[i].Id {
prevYesCount, err := prevProposals[i].FinalTallyResult.GetYesCount()
if err != nil {
msg += fmt.Sprintf("error while getting yes votes weight of proposal at block height %d\n%v\n", prevCtx.BlockHeight(), err)
return msg, broken
}
curYesCount, err := curProposals[i].FinalTallyResult.GetYesCount()
if err != nil {
msg += fmt.Sprintf("error while getting yes votes weight of proposal at block height %d\n%v\n", ctx.BlockHeight(), err)
return msg, broken
}
prevNoCount, err := prevProposals[i].FinalTallyResult.GetNoCount()
if err != nil {
msg += fmt.Sprintf("error while getting no votes weight of proposal at block height %d\n%v\n", prevCtx.BlockHeight(), err)
return msg, broken
}
curNoCount, err := curProposals[i].FinalTallyResult.GetNoCount()
if err != nil {
msg += fmt.Sprintf("error while getting no votes weight of proposal at block height %d\n%v\n", ctx.BlockHeight(), err)
return msg, broken
}
prevAbstainCount, err := prevProposals[i].FinalTallyResult.GetAbstainCount()
if err != nil {
msg += fmt.Sprintf("error while getting abstain votes weight of proposal at block height %d\n%v\n", prevCtx.BlockHeight(), err)
return msg, broken
}
curAbstainCount, err := curProposals[i].FinalTallyResult.GetAbstainCount()
if err != nil {
msg += fmt.Sprintf("error while getting abstain votes weight of proposal at block height %d\n%v\n", ctx.BlockHeight(), err)
return msg, broken
}
prevVetoCount, err := prevProposals[i].FinalTallyResult.GetNoWithVetoCount()
if err != nil {
msg += fmt.Sprintf("error while getting veto votes weight of proposal at block height %d\n%v\n", prevCtx.BlockHeight(), err)
return msg, broken
}
curVetoCount, err := curProposals[i].FinalTallyResult.GetNoWithVetoCount()
if err != nil {
msg += fmt.Sprintf("error while getting veto votes weight of proposal at block height %d\n%v\n", ctx.BlockHeight(), err)
return msg, broken
}
if (curYesCount.Cmp(prevYesCount) == -1) || (curNoCount.Cmp(prevNoCount) == -1) || (curAbstainCount.Cmp(prevAbstainCount) == -1) || (curVetoCount.Cmp(prevVetoCount) == -1) {
broken = true
msg += "vote tally sums must never have less than the block before\n"
return msg, broken
}
}
}
return msg, broken
}
func GroupTotalWeightInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, groupTable orm.AutoUInt64Table, groupMemberByGroupIndex orm.Index) (string, bool) {
var msg string
var broken bool
var groupInfo group.GroupInfo
var groupMember group.GroupMember
groupIt, err := groupTable.PrefixScan(ctx.KVStore(key), 1, math.MaxUint64)
if err != nil {
msg += fmt.Sprintf("PrefixScan failure on group table\n%v\n", err)
@ -158,10 +59,16 @@ func GroupTotalWeightInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, g
msg += fmt.Sprintf("error while parsing positive dec zero for group member\n%v\n", err)
return msg, broken
}
var groupInfo group.GroupInfo
_, err = groupIt.LoadNext(&groupInfo)
if errors.ErrORMIteratorDone.Is(err) {
break
}
if err != nil {
msg += fmt.Sprintf("LoadNext failure on group table iterator\n%v\n", err)
return msg, broken
}
memIt, err := groupMemberByGroupIndex.Get(ctx.KVStore(key), groupInfo.Id)
if err != nil {
msg += fmt.Sprintf("error while returning group member iterator for group with ID %d\n%v\n", groupInfo.Id, err)
@ -170,10 +77,16 @@ func GroupTotalWeightInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, g
defer memIt.Close()
for {
var groupMember group.GroupMember
_, err = memIt.LoadNext(&groupMember)
if errors.ErrORMIteratorDone.Is(err) {
break
}
if err != nil {
msg += fmt.Sprintf("LoadNext failure on member table iterator\n%v\n", err)
return msg, broken
}
curMemWeight, err := groupmath.NewNonNegativeDecFromString(groupMember.GetMember().GetWeight())
if err != nil {
msg += fmt.Sprintf("error while parsing non-nengative decimal for group member %s\n%v\n", groupMember.Member.Address, err)
@ -201,25 +114,32 @@ func GroupTotalWeightInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, g
return msg, broken
}
func TallyVotesSumInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, groupTable orm.AutoUInt64Table, proposalTable orm.AutoUInt64Table, groupMemberTable orm.PrimaryKeyTable, voteByProposalIndex orm.Index, groupPolicyTable orm.PrimaryKeyTable) (string, bool) {
func TallyVotesSumInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, proposalTable orm.AutoUInt64Table, groupMemberTable orm.PrimaryKeyTable, voteByProposalIndex orm.Index, groupPolicyTable orm.PrimaryKeyTable) (string, bool) {
var msg string
var broken bool
var groupInfo group.GroupInfo
var proposal group.Proposal
var groupPolicy group.GroupPolicyInfo
var groupMem group.GroupMember
var vote group.Vote
proposalIt, err := proposalTable.PrefixScan(ctx.KVStore(key), 1, math.MaxUint64)
if err != nil {
fmt.Println(err)
msg += fmt.Sprintf("PrefixScan failure on proposal table\n%v\n", err)
return msg, broken
}
defer proposalIt.Close()
for {
var proposal group.Proposal
_, err = proposalIt.LoadNext(&proposal)
if errors.ErrORMIteratorDone.Is(err) {
break
}
if err != nil {
msg += fmt.Sprintf("LoadNext failure on proposal table iterator\n%v\n", err)
return msg, broken
}
// Only look at proposals that are closed, i.e. for which FinalTallyResult has been computed
if proposal.Status != group.PROPOSAL_STATUS_CLOSED {
continue
}
totalVotingWeight, err := groupmath.NewNonNegativeDecFromString("0")
if err != nil {
@ -247,11 +167,7 @@ func TallyVotesSumInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, grou
return msg, broken
}
_, err = proposalIt.LoadNext(&proposal)
if errors.ErrORMIteratorDone.Is(err) {
break
}
var groupPolicy group.GroupPolicyInfo
err = groupPolicyTable.GetOne(ctx.KVStore(key), orm.PrimaryKey(&group.GroupPolicyInfo{Address: proposal.Address}), &groupPolicy)
if err != nil {
msg += fmt.Sprintf("group policy not found for address: %s\n%v\n", proposal.Address, err)
@ -263,29 +179,25 @@ func TallyVotesSumInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, grou
return msg, broken
}
_, err = groupTable.GetOne(ctx.KVStore(key), groupPolicy.GroupId, &groupInfo)
if err != nil {
msg += fmt.Sprintf("group info not found for group id %d\n%v\n", groupPolicy.GroupId, err)
return msg, broken
}
if groupInfo.Version != proposal.GroupVersion {
msg += fmt.Sprintf("group with id %d was modified\n", groupInfo.Id)
return msg, broken
}
voteIt, err := voteByProposalIndex.Get(ctx.KVStore(key), proposal.Id)
if err != nil {
msg += fmt.Sprintf("error while returning vote iterator for proposal with ID %d\n%v\n", proposal.Id, err)
return msg, broken
}
defer voteIt.Close()
for {
var vote group.Vote
_, err := voteIt.LoadNext(&vote)
if errors.ErrORMIteratorDone.Is(err) {
break
}
if err != nil {
msg += fmt.Sprintf("LoadNext failure on voteByProposalIndex index iterator\n%v\n", err)
return msg, broken
}
var groupMem group.GroupMember
err = groupMemberTable.GetOne(ctx.KVStore(key), orm.PrimaryKey(&group.GroupMember{GroupId: groupPolicy.GroupId, Member: &group.Member{Address: vote.Voter}}), &groupMem)
if err != nil {
msg += fmt.Sprintf("group member not found with group ID %d and group member %s\n%v\n", groupPolicy.GroupId, vote.Voter, err)
@ -330,7 +242,6 @@ func TallyVotesSumInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, grou
}
}
}
voteIt.Close()
totalProposalVotes, err := proposal.FinalTallyResult.TotalCounts()
if err != nil {
@ -366,7 +277,7 @@ func TallyVotesSumInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, grou
if (yesVoteWeight.Cmp(proposalYesCount) != 0) || (noVoteWeight.Cmp(proposalNoCount) != 0) || (abstainVoteWeight.Cmp(proposalAbstainCount) != 0) || (vetoVoteWeight.Cmp(proposalVetoCount) != 0) {
broken = true
msg += fmt.Sprintf("proposal FinalTallyResult must correspond to the vote option\nProposal with ID %d and voter address %s must correspond to the vote option\n", proposal.Id, vote.Voter)
msg += fmt.Sprintf("proposal FinalTallyResult must correspond to the sum of all votes\nProposal with ID %d FinalTallyResult must correspond to the sum of all votes\n", proposal.Id)
break
}
}

View File

@ -51,189 +51,6 @@ func (s *invariantTestSuite) SetupSuite() {
}
func (s *invariantTestSuite) TestTallyVotesInvariant() {
sdkCtx, _ := s.ctx.CacheContext()
curCtx, cdc, key := sdkCtx, s.cdc, s.key
prevCtx, _ := curCtx.CacheContext()
prevCtx = prevCtx.WithBlockHeight(curCtx.BlockHeight() - 1)
// Proposal Table
proposalTable, err := orm.NewAutoUInt64Table([2]byte{keeper.ProposalTablePrefix}, keeper.ProposalTableSeqPrefix, &group.Proposal{}, cdc)
s.Require().NoError(err)
_, _, addr1 := testdata.KeyTestPubAddr()
_, _, addr2 := testdata.KeyTestPubAddr()
specs := map[string]struct {
prevProposal *group.Proposal
curProposal *group.Proposal
expBroken bool
}{
"invariant not broken": {
prevProposal: &group.Proposal{
Id: 1,
Address: addr1.String(),
Proposers: []string{addr1.String()},
SubmitTime: prevCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "1", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"},
VotingPeriodEnd: prevCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
curProposal: &group.Proposal{
Id: 1,
Address: addr2.String(),
Proposers: []string{addr2.String()},
SubmitTime: curCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "2", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"},
VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
},
"current block yes vote count must be greater than previous block yes vote count": {
prevProposal: &group.Proposal{
Id: 1,
Address: addr1.String(),
Proposers: []string{addr1.String()},
SubmitTime: prevCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "2", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"},
VotingPeriodEnd: prevCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
curProposal: &group.Proposal{
Id: 1,
Address: addr2.String(),
Proposers: []string{addr2.String()},
SubmitTime: curCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "1", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"},
VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
expBroken: true,
},
"current block no vote count must be greater than previous block no vote count": {
prevProposal: &group.Proposal{
Id: 1,
Address: addr1.String(),
Proposers: []string{addr1.String()},
SubmitTime: prevCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "2", AbstainCount: "0", NoWithVetoCount: "0"},
VotingPeriodEnd: prevCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
curProposal: &group.Proposal{
Id: 1,
Address: addr2.String(),
Proposers: []string{addr2.String()},
SubmitTime: curCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "1", AbstainCount: "0", NoWithVetoCount: "0"},
VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
expBroken: true,
},
"current block abstain vote count must be greater than previous block abstain vote count": {
prevProposal: &group.Proposal{
Id: 1,
Address: addr1.String(),
Proposers: []string{addr1.String()},
SubmitTime: prevCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "2", NoWithVetoCount: "0"},
VotingPeriodEnd: prevCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
curProposal: &group.Proposal{
Id: 1,
Address: addr2.String(),
Proposers: []string{addr2.String()},
SubmitTime: curCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "1", NoWithVetoCount: "0"},
VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
expBroken: true,
},
"current block veto vote count must be greater than previous block veto vote count": {
prevProposal: &group.Proposal{
Id: 1,
Address: addr1.String(),
Proposers: []string{addr1.String()},
SubmitTime: prevCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "2"},
VotingPeriodEnd: prevCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
curProposal: &group.Proposal{
Id: 1,
Address: addr2.String(),
Proposers: []string{addr2.String()},
SubmitTime: curCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "1"},
VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
expBroken: true,
},
}
for _, spec := range specs {
prevProposal := spec.prevProposal
curProposal := spec.curProposal
cachePrevCtx, _ := prevCtx.CacheContext()
cacheCurCtx, _ := curCtx.CacheContext()
_, err = proposalTable.Create(cachePrevCtx.KVStore(key), prevProposal)
s.Require().NoError(err)
_, err = proposalTable.Create(cacheCurCtx.KVStore(key), curProposal)
s.Require().NoError(err)
_, broken := keeper.TallyVotesInvariantHelper(cacheCurCtx, cachePrevCtx, key, *proposalTable)
s.Require().Equal(spec.expBroken, broken)
}
}
func (s *invariantTestSuite) TestGroupTotalWeightInvariant() {
sdkCtx, _ := s.ctx.CacheContext()
curCtx, cdc, key := sdkCtx, s.cdc, s.key
@ -365,6 +182,8 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
_, _, addr1 := testdata.KeyTestPubAddr()
_, _, addr2 := testdata.KeyTestPubAddr()
votingPeriodEnd := curCtx.BlockTime().Add(time.Second * 600)
specs := map[string]struct {
groupsInfo *group.GroupInfo
groupPolicy *group.GroupPolicyInfo
@ -409,10 +228,10 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
SubmitTime: curCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
Status: group.PROPOSAL_STATUS_CLOSED,
Result: group.PROPOSAL_RESULT_ACCEPTED,
FinalTallyResult: group.TallyResult{YesCount: "4", NoCount: "3", AbstainCount: "0", NoWithVetoCount: "0"},
VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
VotingPeriodEnd: votingPeriodEnd,
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
votes: []*group.Vote{
@ -431,6 +250,58 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
},
expBroken: false,
},
"proposal not closed ignored": {
groupsInfo: &group.GroupInfo{
Id: 1,
Admin: adminAddr.String(),
Version: 1,
TotalWeight: "7",
},
groupPolicy: &group.GroupPolicyInfo{
Address: addr1.String(),
GroupId: 1,
Admin: adminAddr.String(),
Version: 1,
},
groupMembers: []*group.GroupMember{
{
GroupId: 1,
Member: &group.Member{
Address: addr1.String(),
Weight: "4",
},
},
{
GroupId: 1,
Member: &group.Member{
Address: addr2.String(),
Weight: "3",
},
},
},
proposal: &group.Proposal{
Id: 1,
Address: addr1.String(),
Proposers: []string{addr1.String()},
SubmitTime: curCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"},
VotingPeriodEnd: votingPeriodEnd,
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
votes: []*group.Vote{
{
ProposalId: 1,
Voter: addr1.String(),
Option: group.VOTE_OPTION_YES,
SubmitTime: curCtx.BlockTime(),
},
},
expBroken: false,
},
"proposal tally must correspond to the sum of vote weights": {
groupsInfo: &group.GroupInfo{
Id: 1,
@ -467,10 +338,10 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
SubmitTime: curCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
Status: group.PROPOSAL_STATUS_CLOSED,
Result: group.PROPOSAL_RESULT_ACCEPTED,
FinalTallyResult: group.TallyResult{YesCount: "6", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"},
VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
VotingPeriodEnd: votingPeriodEnd,
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
votes: []*group.Vote{
@ -525,10 +396,10 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
SubmitTime: curCtx.BlockTime(),
GroupVersion: 1,
GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED,
Result: group.PROPOSAL_RESULT_UNFINALIZED,
Status: group.PROPOSAL_STATUS_CLOSED,
Result: group.PROPOSAL_RESULT_ACCEPTED,
FinalTallyResult: group.TallyResult{YesCount: "4", NoCount: "3", AbstainCount: "0", NoWithVetoCount: "0"},
VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600),
VotingPeriodEnd: votingPeriodEnd,
ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
votes: []*group.Vote{
@ -578,7 +449,7 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
s.Require().NoError(err)
}
_, broken := keeper.TallyVotesSumInvariantHelper(cacheCurCtx, key, *groupTable, *proposalTable, *groupMemberTable, voteByProposalIndex, *groupPolicyTable)
_, broken := keeper.TallyVotesSumInvariantHelper(cacheCurCtx, key, *proposalTable, *groupMemberTable, voteByProposalIndex, *groupPolicyTable)
s.Require().Equal(spec.expBroken, broken)
}
}

View File

@ -2,12 +2,14 @@ package keeper
import (
"fmt"
"time"
"github.com/tendermint/tendermint/libs/log"
"github.com/cosmos/cosmos-sdk/codec"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authmiddleware "github.com/cosmos/cosmos-sdk/x/auth/middleware"
"github.com/cosmos/cosmos-sdk/x/group"
"github.com/cosmos/cosmos-sdk/x/group/errors"
@ -35,8 +37,7 @@ const (
ProposalTablePrefix byte = 0x30
ProposalTableSeqPrefix byte = 0x31
ProposalByGroupPolicyIndexPrefix byte = 0x32
ProposalByProposerIndexPrefix byte = 0x33
ProposalsByVotingPeriodEndPrefix byte = 0x34
ProposalsByVotingPeriodEndPrefix byte = 0x33
// Vote Table
VoteTablePrefix byte = 0x40
@ -67,7 +68,6 @@ type Keeper struct {
// Proposal Table
proposalTable orm.AutoUInt64Table
proposalByGroupPolicyIndex orm.Index
proposalByProposerIndex orm.Index
proposalsByVotingPeriodEnd orm.Index
// Vote Table
@ -169,21 +169,6 @@ func NewKeeper(storeKey storetypes.StoreKey, cdc codec.Codec, router *authmiddle
if err != nil {
panic(err.Error())
}
k.proposalByProposerIndex, err = orm.NewIndex(proposalTable, ProposalByProposerIndexPrefix, func(value interface{}) ([]interface{}, error) {
proposers := value.(*group.Proposal).Proposers
r := make([]interface{}, len(proposers))
for i := range proposers {
addr, err := sdk.AccAddressFromBech32(proposers[i])
if err != nil {
return nil, err
}
r[i] = addr.Bytes()
}
return r, nil
}, []byte{})
if err != nil {
panic(err.Error())
}
k.proposalsByVotingPeriodEnd, err = orm.NewIndex(proposalTable, ProposalsByVotingPeriodEndPrefix, func(value interface{}) ([]interface{}, error) {
votingPeriodEnd := value.(*group.Proposal).VotingPeriodEnd
return []interface{}{sdk.FormatTimeBytes(votingPeriodEnd)}, nil
@ -233,16 +218,14 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", fmt.Sprintf("x/%s", group.ModuleName))
}
// MaxMetadataLength returns the max length of the metadata bytes field for various entities within the group module.
func (k Keeper) MaxMetadataLength() uint64 { return k.config.MaxMetadataLen }
// GetGroupSequence returns the current value of the group table sequence
func (k Keeper) GetGroupSequence(ctx sdk.Context) uint64 {
return k.groupTable.Sequence().CurVal(ctx.KVStore(k.key))
}
func (k Keeper) iterateProposalsByVPEnd(ctx sdk.Context, cb func(proposal group.Proposal) (bool, error)) error {
timeBytes := sdk.FormatTimeBytes(ctx.BlockTime())
// iterateProposalsByVPEnd iterates over all proposals whose voting_period_end is after the `endTime` time argument.
func (k Keeper) iterateProposalsByVPEnd(ctx sdk.Context, endTime time.Time, cb func(proposal group.Proposal) (bool, error)) error {
timeBytes := sdk.FormatTimeBytes(endTime)
it, err := k.proposalsByVotingPeriodEnd.PrefixScan(ctx.KVStore(k.key), nil, timeBytes)
if err != nil {
@ -250,8 +233,17 @@ func (k Keeper) iterateProposalsByVPEnd(ctx sdk.Context, cb func(proposal group.
}
defer it.Close()
var proposal group.Proposal
for {
// Important: this following line cannot outside the for loop.
// It seems that when one unmarshals into the same `group.Proposal`
// reference, then gogoproto somehow "adds" the new bytes to the old
// object for some fields. When running simulations, for proposals with
// each 1-2 proposers, after a couple of loop iterations we got to a
// proposal with 60k+ proposers.
// So we're declaring a local variable that gets GCed.
//
// Also see `x/group/types/proposal_test.go`, TestGogoUnmarshalProposal().
var proposal group.Proposal
_, err := it.LoadNext(&proposal)
if errors.ErrORMIteratorDone.Is(err) {
break
@ -272,30 +264,87 @@ func (k Keeper) iterateProposalsByVPEnd(ctx sdk.Context, cb func(proposal group.
return nil
}
func (k Keeper) UpdateTallyOfVPEndProposals(ctx sdk.Context) error {
k.iterateProposalsByVPEnd(ctx, func(proposal group.Proposal) (bool, error) {
// pruneProposal deletes a proposal from state.
func (k Keeper) pruneProposal(ctx sdk.Context, proposalID uint64) error {
store := ctx.KVStore(k.key)
policyInfo, err := k.getGroupPolicyInfo(ctx, proposal.Address)
err := k.proposalTable.Delete(store, proposalID)
if err != nil {
return err
}
k.Logger(ctx).Debug(fmt.Sprintf("Pruned proposal %d", proposalID))
return nil
}
// pruneVotes prunes all votes for a proposal from state.
func (k Keeper) pruneVotes(ctx sdk.Context, proposalID uint64) error {
store := ctx.KVStore(k.key)
it, err := k.voteByProposalIndex.Get(store, proposalID)
if err != nil {
return err
}
defer it.Close()
for {
var vote group.Vote
_, err = it.LoadNext(&vote)
if errors.ErrORMIteratorDone.Is(err) {
break
}
if err != nil {
return true, err
return err
}
electorate, err := k.getGroupInfo(ctx, policyInfo.GroupId)
err = k.voteTable.Delete(store, &vote)
if err != nil {
return true, err
return err
}
}
err = k.doTallyAndUpdate(ctx, &proposal, electorate, policyInfo)
return nil
}
// PruneProposals prunes all proposals that are expired, i.e. whose
// `voting_period + max_execution_period` is greater than the current block
// time.
func (k Keeper) PruneProposals(ctx sdk.Context) error {
err := k.iterateProposalsByVPEnd(ctx, ctx.BlockTime().Add(-k.config.MaxExecutionPeriod), func(proposal group.Proposal) (bool, error) {
err := k.pruneProposal(ctx, proposal.Id)
if err != nil {
return true, err
}
if err := k.proposalTable.Update(ctx.KVStore(k.key), proposal.Id, &proposal); err != nil {
return true, err
}
return false, nil
})
if err != nil {
return err
}
return nil
}
func (k Keeper) UpdateTallyOfVPEndProposals(ctx sdk.Context) error {
return k.iterateProposalsByVPEnd(ctx, ctx.BlockTime(), func(proposal group.Proposal) (bool, error) {
policyInfo, err := k.getGroupPolicyInfo(ctx, proposal.Address)
if err != nil {
return true, sdkerrors.Wrap(err, "group policy")
}
electorate, err := k.getGroupInfo(ctx, policyInfo.GroupId)
if err != nil {
return true, sdkerrors.Wrap(err, "group")
}
err = k.doTallyAndUpdate(ctx, &proposal, electorate, policyInfo)
if err != nil {
return true, sdkerrors.Wrap(err, "doTallyAndUpdate")
}
if err := k.proposalTable.Update(ctx.KVStore(k.key), proposal.Id, &proposal); err != nil {
return true, sdkerrors.Wrap(err, "proposal update")
}
return false, nil
})
}

View File

@ -1598,27 +1598,29 @@ func (s *TestSuite) TestSubmitProposal() {
s.Require().NoError(err)
id := res.ProposalId
// then all data persisted
proposalRes, err := s.keeper.Proposal(s.ctx, &group.QueryProposalRequest{ProposalId: id})
s.Require().NoError(err)
proposal := proposalRes.Proposal
if !(spec.expProposal.ExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS) {
// then all data persisted
proposalRes, err := s.keeper.Proposal(s.ctx, &group.QueryProposalRequest{ProposalId: id})
s.Require().NoError(err)
proposal := proposalRes.Proposal
s.Assert().Equal(spec.expProposal.Address, proposal.Address)
s.Assert().Equal(spec.req.Metadata, proposal.Metadata)
s.Assert().Equal(spec.req.Proposers, proposal.Proposers)
s.Assert().Equal(s.blockTime, proposal.SubmitTime)
s.Assert().Equal(uint64(1), proposal.GroupVersion)
s.Assert().Equal(uint64(1), proposal.GroupPolicyVersion)
s.Assert().Equal(spec.expProposal.Status, proposal.Status)
s.Assert().Equal(spec.expProposal.Result, proposal.Result)
s.Assert().Equal(spec.expProposal.FinalTallyResult, proposal.FinalTallyResult)
s.Assert().Equal(spec.expProposal.ExecutorResult, proposal.ExecutorResult)
s.Assert().Equal(s.blockTime.Add(time.Second), proposal.VotingPeriodEnd)
s.Assert().Equal(spec.expProposal.Address, proposal.Address)
s.Assert().Equal(spec.req.Metadata, proposal.Metadata)
s.Assert().Equal(spec.req.Proposers, proposal.Proposers)
s.Assert().Equal(s.blockTime, proposal.SubmitTime)
s.Assert().Equal(uint64(1), proposal.GroupVersion)
s.Assert().Equal(uint64(1), proposal.GroupPolicyVersion)
s.Assert().Equal(spec.expProposal.Status, proposal.Status)
s.Assert().Equal(spec.expProposal.Result, proposal.Result)
s.Assert().Equal(spec.expProposal.FinalTallyResult, proposal.FinalTallyResult)
s.Assert().Equal(spec.expProposal.ExecutorResult, proposal.ExecutorResult)
s.Assert().Equal(s.blockTime.Add(time.Second), proposal.VotingPeriodEnd)
if spec.msgs == nil { // then empty list is ok
s.Assert().Len(proposal.GetMsgs(), 0)
} else {
s.Assert().Equal(spec.msgs, proposal.GetMsgs())
if spec.msgs == nil { // then empty list is ok
s.Assert().Len(proposal.GetMsgs(), 0)
} else {
s.Assert().Equal(spec.msgs, proposal.GetMsgs())
}
}
spec.postRun(s.sdkCtx)
@ -2042,47 +2044,6 @@ func (s *TestSuite) TestVote() {
expErr: true,
postRun: func(sdkCtx sdk.Context) {},
},
"with group modified": {
req: &group.MsgVote{
ProposalId: myProposalID,
Voter: addr4.String(),
Option: group.VOTE_OPTION_NO,
},
doBefore: func(ctx context.Context) {
_, err = s.keeper.UpdateGroupMetadata(ctx, &group.MsgUpdateGroupMetadata{
GroupId: myGroupID,
Admin: addr1.String(),
})
s.Require().NoError(err)
},
expErr: true,
postRun: func(sdkCtx sdk.Context) {},
},
"with policy modified": {
req: &group.MsgVote{
ProposalId: myProposalID,
Voter: addr4.String(),
Option: group.VOTE_OPTION_NO,
},
doBefore: func(ctx context.Context) {
m, err := group.NewMsgUpdateGroupPolicyDecisionPolicyRequest(
addr1,
groupPolicy,
&group.ThresholdDecisionPolicy{
Threshold: "1",
Windows: &group.DecisionPolicyWindows{
VotingPeriod: time.Second,
},
},
)
s.Require().NoError(err)
_, err = s.keeper.UpdateGroupPolicyDecisionPolicy(ctx, m)
s.Require().NoError(err)
},
expErr: true,
postRun: func(sdkCtx sdk.Context) {},
},
}
for msg, spec := range specs {
spec := spec
@ -2105,66 +2066,69 @@ func (s *TestSuite) TestVote() {
s.Require().NoError(err)
s.Require().NoError(err)
// vote is stored and all data persisted
res, err := s.keeper.VoteByProposalVoter(ctx, &group.QueryVoteByProposalVoterRequest{
ProposalId: spec.req.ProposalId,
Voter: spec.req.Voter,
})
s.Require().NoError(err)
loaded := res.Vote
s.Assert().Equal(spec.req.ProposalId, loaded.ProposalId)
s.Assert().Equal(spec.req.Voter, loaded.Voter)
s.Assert().Equal(spec.req.Option, loaded.Option)
s.Assert().Equal(spec.req.Metadata, loaded.Metadata)
s.Assert().Equal(s.blockTime, loaded.SubmitTime)
// query votes by proposal
votesByProposalRes, err := s.keeper.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{
ProposalId: spec.req.ProposalId,
})
s.Require().NoError(err)
votesByProposal := votesByProposalRes.Votes
s.Require().Equal(1, len(votesByProposal))
vote := votesByProposal[0]
s.Assert().Equal(spec.req.ProposalId, vote.ProposalId)
s.Assert().Equal(spec.req.Voter, vote.Voter)
s.Assert().Equal(spec.req.Option, vote.Option)
s.Assert().Equal(spec.req.Metadata, vote.Metadata)
s.Assert().Equal(s.blockTime, vote.SubmitTime)
if !(spec.expExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS) {
// vote is stored and all data persisted
res, err := s.keeper.VoteByProposalVoter(ctx, &group.QueryVoteByProposalVoterRequest{
ProposalId: spec.req.ProposalId,
Voter: spec.req.Voter,
})
s.Require().NoError(err)
loaded := res.Vote
s.Assert().Equal(spec.req.ProposalId, loaded.ProposalId)
s.Assert().Equal(spec.req.Voter, loaded.Voter)
s.Assert().Equal(spec.req.Option, loaded.Option)
s.Assert().Equal(spec.req.Metadata, loaded.Metadata)
s.Assert().Equal(s.blockTime, loaded.SubmitTime)
// query votes by voter
voter := spec.req.Voter
votesByVoterRes, err := s.keeper.VotesByVoter(ctx, &group.QueryVotesByVoterRequest{
Voter: voter,
})
s.Require().NoError(err)
votesByVoter := votesByVoterRes.Votes
s.Require().Equal(1, len(votesByVoter))
s.Assert().Equal(spec.req.ProposalId, votesByVoter[0].ProposalId)
s.Assert().Equal(voter, votesByVoter[0].Voter)
s.Assert().Equal(spec.req.Option, votesByVoter[0].Option)
s.Assert().Equal(spec.req.Metadata, votesByVoter[0].Metadata)
s.Assert().Equal(s.blockTime, votesByVoter[0].SubmitTime)
// query votes by proposal
votesByProposalRes, err := s.keeper.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{
ProposalId: spec.req.ProposalId,
})
s.Require().NoError(err)
votesByProposal := votesByProposalRes.Votes
s.Require().Equal(1, len(votesByProposal))
vote := votesByProposal[0]
s.Assert().Equal(spec.req.ProposalId, vote.ProposalId)
s.Assert().Equal(spec.req.Voter, vote.Voter)
s.Assert().Equal(spec.req.Option, vote.Option)
s.Assert().Equal(spec.req.Metadata, vote.Metadata)
s.Assert().Equal(s.blockTime, vote.SubmitTime)
proposalRes, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{
ProposalId: spec.req.ProposalId,
})
s.Require().NoError(err)
// query votes by voter
voter := spec.req.Voter
votesByVoterRes, err := s.keeper.VotesByVoter(ctx, &group.QueryVotesByVoterRequest{
Voter: voter,
})
s.Require().NoError(err)
votesByVoter := votesByVoterRes.Votes
s.Require().Equal(1, len(votesByVoter))
s.Assert().Equal(spec.req.ProposalId, votesByVoter[0].ProposalId)
s.Assert().Equal(voter, votesByVoter[0].Voter)
s.Assert().Equal(spec.req.Option, votesByVoter[0].Option)
s.Assert().Equal(spec.req.Metadata, votesByVoter[0].Metadata)
s.Assert().Equal(s.blockTime, votesByVoter[0].SubmitTime)
proposal := proposalRes.Proposal
if spec.isFinal {
s.Assert().Equal(spec.expTallyResult, proposal.FinalTallyResult)
s.Assert().Equal(spec.expResult, proposal.Result)
s.Assert().Equal(spec.expProposalStatus, proposal.Status)
s.Assert().Equal(spec.expExecutorResult, proposal.ExecutorResult)
} else {
s.Assert().Equal(group.DefaultTallyResult(), proposal.FinalTallyResult) // Make sure proposal isn't mutated.
// do a round of tallying
tallyResult, err := s.keeper.Tally(sdkCtx, *proposal, myGroupID)
proposalRes, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{
ProposalId: spec.req.ProposalId,
})
s.Require().NoError(err)
s.Assert().Equal(spec.expTallyResult, tallyResult)
proposal := proposalRes.Proposal
if spec.isFinal {
s.Assert().Equal(spec.expTallyResult, proposal.FinalTallyResult)
s.Assert().Equal(spec.expResult, proposal.Result)
s.Assert().Equal(spec.expProposalStatus, proposal.Status)
s.Assert().Equal(spec.expExecutorResult, proposal.ExecutorResult)
} else {
s.Assert().Equal(group.DefaultTallyResult(), proposal.FinalTallyResult) // Make sure proposal isn't mutated.
// do a round of tallying
tallyResult, err := s.keeper.Tally(sdkCtx, *proposal, myGroupID)
s.Require().NoError(err)
s.Assert().Equal(spec.expTallyResult, tallyResult)
}
}
spec.postRun(sdkCtx)
@ -2329,36 +2293,6 @@ func (s *TestSuite) TestExecProposal() {
expProposalResult: group.PROPOSAL_RESULT_REJECTED,
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
"with group modified before tally": {
setupProposal: func(ctx context.Context) uint64 {
myProposalID := submitProposal(ctx, s, []sdk.Msg{msgSend1}, proposers)
// then modify group
_, err := s.keeper.UpdateGroupMetadata(ctx, &group.MsgUpdateGroupMetadata{
Admin: addr1.String(),
GroupId: s.groupID,
})
s.Require().NoError(err)
return myProposalID
},
expProposalStatus: group.PROPOSAL_STATUS_ABORTED,
expProposalResult: group.PROPOSAL_RESULT_UNFINALIZED,
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
"with group policy modified before tally": {
setupProposal: func(ctx context.Context) uint64 {
myProposalID := submitProposal(ctx, s, []sdk.Msg{msgSend1}, proposers)
_, err := s.keeper.UpdateGroupPolicyMetadata(ctx, &group.MsgUpdateGroupPolicyMetadata{
Admin: addr1.String(),
Address: s.groupPolicyAddr.String(),
})
s.Require().NoError(err)
return myProposalID
},
expProposalStatus: group.PROPOSAL_STATUS_ABORTED,
expProposalResult: group.PROPOSAL_RESULT_UNFINALIZED,
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
"prevent double execution when successful": {
setupProposal: func(ctx context.Context) uint64 {
myProposalID := submitProposalAndVote(ctx, s, []sdk.Msg{msgSend1}, proposers, group.VOTE_OPTION_YES)
@ -2367,6 +2301,7 @@ func (s *TestSuite) TestExecProposal() {
s.Require().NoError(err)
return myProposalID
},
expErr: true, // since proposal is pruned after a successful MsgExec
expProposalStatus: group.PROPOSAL_STATUS_CLOSED,
expProposalResult: group.PROPOSAL_RESULT_ACCEPTED,
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
@ -2419,22 +2354,25 @@ func (s *TestSuite) TestExecProposal() {
}
s.Require().NoError(err)
// and proposal is updated
res, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{ProposalId: proposalID})
s.Require().NoError(err)
proposal := res.Proposal
if !(spec.expExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS) {
exp := group.ProposalResult_name[int32(spec.expProposalResult)]
got := group.ProposalResult_name[int32(proposal.Result)]
s.Assert().Equal(exp, got)
// and proposal is updated
res, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{ProposalId: proposalID})
s.Require().NoError(err)
proposal := res.Proposal
exp = group.ProposalStatus_name[int32(spec.expProposalStatus)]
got = group.ProposalStatus_name[int32(proposal.Status)]
s.Assert().Equal(exp, got)
exp := group.ProposalResult_name[int32(spec.expProposalResult)]
got := group.ProposalResult_name[int32(proposal.Result)]
s.Assert().Equal(exp, got)
exp = group.ProposalExecutorResult_name[int32(spec.expExecutorResult)]
got = group.ProposalExecutorResult_name[int32(proposal.ExecutorResult)]
s.Assert().Equal(exp, got)
exp = group.ProposalStatus_name[int32(spec.expProposalStatus)]
got = group.ProposalStatus_name[int32(proposal.Status)]
s.Assert().Equal(exp, got)
exp = group.ProposalExecutorResult_name[int32(spec.expExecutorResult)]
got = group.ProposalExecutorResult_name[int32(proposal.ExecutorResult)]
s.Assert().Equal(exp, got)
}
if spec.expBalance {
fromBalances := s.app.BankKeeper.GetAllBalances(sdkCtx, s.groupPolicyAddr)
@ -2446,6 +2384,150 @@ func (s *TestSuite) TestExecProposal() {
}
}
func (s *TestSuite) TestExecPrunedProposalsAndVotes() {
addrs := s.addrs
addr1 := addrs[0]
addr2 := addrs[1]
msgSend1 := &banktypes.MsgSend{
FromAddress: s.groupPolicyAddr.String(),
ToAddress: addr2.String(),
Amount: sdk.Coins{sdk.NewInt64Coin("test", 100)},
}
msgSend2 := &banktypes.MsgSend{
FromAddress: s.groupPolicyAddr.String(),
ToAddress: addr2.String(),
Amount: sdk.Coins{sdk.NewInt64Coin("test", 10001)},
}
proposers := []string{addr2.String()}
specs := map[string]struct {
srcBlockTime time.Time
setupProposal func(ctx context.Context) uint64
expErr bool
expErrMsg string
expExecutorResult group.ProposalExecutorResult
}{
"proposal pruned after executor result success": {
setupProposal: func(ctx context.Context) uint64 {
msgs := []sdk.Msg{msgSend1}
return submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_YES)
},
expErrMsg: "load proposal: not found",
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
},
"proposal with multiple messages pruned when executed with result success": {
setupProposal: func(ctx context.Context) uint64 {
msgs := []sdk.Msg{msgSend1, msgSend1}
return submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_YES)
},
expErrMsg: "load proposal: not found",
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
},
"proposal not pruned when not executed and rejected": {
setupProposal: func(ctx context.Context) uint64 {
msgs := []sdk.Msg{msgSend1}
return submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_NO)
},
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
"open proposal is not pruned which must not fail ": {
setupProposal: func(ctx context.Context) uint64 {
return submitProposal(ctx, s, []sdk.Msg{msgSend1}, proposers)
},
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
"proposal not pruned with group modified before tally": {
setupProposal: func(ctx context.Context) uint64 {
myProposalID := submitProposal(ctx, s, []sdk.Msg{msgSend1}, proposers)
// then modify group
_, err := s.keeper.UpdateGroupMetadata(ctx, &group.MsgUpdateGroupMetadata{
Admin: addr1.String(),
GroupId: s.groupID,
})
s.Require().NoError(err)
return myProposalID
},
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
"proposal not pruned with group policy modified before tally": {
setupProposal: func(ctx context.Context) uint64 {
myProposalID := submitProposal(ctx, s, []sdk.Msg{msgSend1}, proposers)
_, err := s.keeper.UpdateGroupPolicyMetadata(ctx, &group.MsgUpdateGroupPolicyMetadata{
Admin: addr1.String(),
Address: s.groupPolicyAddr.String(),
})
s.Require().NoError(err)
return myProposalID
},
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
"proposal exists when rollback all msg updates on failure": {
setupProposal: func(ctx context.Context) uint64 {
msgs := []sdk.Msg{msgSend1, msgSend2}
return submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_YES)
},
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_FAILURE,
},
"pruned when proposal is executable when failed before": {
setupProposal: func(ctx context.Context) uint64 {
msgs := []sdk.Msg{msgSend2}
myProposalID := submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_YES)
_, err := s.keeper.Exec(ctx, &group.MsgExec{Signer: addr1.String(), ProposalId: myProposalID})
s.Require().NoError(err)
sdkCtx := sdk.UnwrapSDKContext(ctx)
s.Require().NoError(testutil.FundAccount(s.app.BankKeeper, sdkCtx, s.groupPolicyAddr, sdk.Coins{sdk.NewInt64Coin("test", 10002)}))
return myProposalID
},
expErrMsg: "load proposal: not found",
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
},
}
for msg, spec := range specs {
spec := spec
s.Run(msg, func() {
sdkCtx, _ := s.sdkCtx.CacheContext()
ctx := sdk.WrapSDKContext(sdkCtx)
proposalID := spec.setupProposal(ctx)
if !spec.srcBlockTime.IsZero() {
sdkCtx = sdkCtx.WithBlockTime(spec.srcBlockTime)
}
ctx = sdk.WrapSDKContext(sdkCtx)
_, err := s.keeper.Exec(ctx, &group.MsgExec{Signer: addr1.String(), ProposalId: proposalID})
if spec.expErr {
s.Require().Error(err)
return
}
s.Require().NoError(err)
if spec.expExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS {
// Make sure proposal is deleted from state
_, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{ProposalId: proposalID})
s.Require().Contains(err.Error(), spec.expErrMsg)
res, err := s.keeper.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{ProposalId: proposalID})
s.Require().NoError(err)
s.Require().Empty(res.GetVotes())
} else {
// Check that proposal and votes exists
res, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{ProposalId: proposalID})
s.Require().NoError(err)
_, err = s.keeper.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{ProposalId: res.Proposal.Id})
s.Require().NoError(err)
s.Require().Equal("", spec.expErrMsg)
exp := group.ProposalExecutorResult_name[int32(spec.expExecutorResult)]
got := group.ProposalExecutorResult_name[int32(res.Proposal.ExecutorResult)]
s.Assert().Equal(exp, got)
}
})
}
}
func (s *TestSuite) TestProposalsByVPEnd() {
addrs := s.addrs
addr2 := addrs[1]

View File

@ -637,22 +637,14 @@ func (k Keeper) Vote(goCtx context.Context, req *group.MsgVote) (*group.MsgVoteR
var policyInfo group.GroupPolicyInfo
// Ensure that group policy hasn't been modified since the proposal submission.
if policyInfo, err = k.getGroupPolicyInfo(ctx, proposal.Address); err != nil {
return nil, sdkerrors.Wrap(err, "load group policy")
}
if proposal.GroupPolicyVersion != policyInfo.Version {
return nil, sdkerrors.Wrap(errors.ErrModified, "group policy was modified")
}
// Ensure that group hasn't been modified since the proposal submission.
electorate, err := k.getGroupInfo(ctx, policyInfo.GroupId)
if err != nil {
return nil, err
}
if electorate.Version != proposal.GroupVersion {
return nil, sdkerrors.Wrap(errors.ErrModified, "group was modified")
}
// Count and store votes.
voterAddr := req.Voter
@ -711,17 +703,22 @@ func (k Keeper) doTallyAndUpdate(ctx sdk.Context, p *group.Proposal, electorate
return err
}
switch result, err := policy.Allow(tallyResult, electorate.TotalWeight, ctx.BlockTime().Sub(submittedAt)); {
result, err := policy.Allow(tallyResult, electorate.TotalWeight, ctx.BlockTime().Sub(submittedAt))
switch {
case err != nil:
return sdkerrors.Wrap(err, "policy execution")
case result.Allow && result.Final:
return sdkerrors.Wrap(err, "policy allow")
case result.Final:
if err := k.pruneVotes(ctx, p.Id); err != nil {
return err
}
p.FinalTallyResult = tallyResult
p.Result = group.PROPOSAL_RESULT_ACCEPTED
p.Status = group.PROPOSAL_STATUS_CLOSED
case !result.Allow && result.Final:
p.FinalTallyResult = tallyResult
p.Result = group.PROPOSAL_RESULT_REJECTED
p.Status = group.PROPOSAL_STATUS_CLOSED
if result.Allow {
p.Result = group.PROPOSAL_RESULT_ACCEPTED
p.Status = group.PROPOSAL_STATUS_CLOSED
} else {
p.Result = group.PROPOSAL_RESULT_REJECTED
p.Status = group.PROPOSAL_STATUS_CLOSED
}
}
return nil
@ -747,32 +744,28 @@ func (k Keeper) Exec(goCtx context.Context, req *group.MsgExec) (*group.MsgExecR
}
storeUpdates := func() (*group.MsgExecResponse, error) {
if err := k.proposalTable.Update(ctx.KVStore(k.key), id, &proposal); err != nil {
return nil, err
store := ctx.KVStore(k.key)
// If proposal has successfully run, delete it from state.
if proposal.ExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS {
if err := k.pruneProposal(ctx, proposal.Id); err != nil {
return nil, err
}
} else {
if err := k.proposalTable.Update(store, id, &proposal); err != nil {
return nil, err
}
}
return &group.MsgExecResponse{}, nil
}
if proposal.Status == group.PROPOSAL_STATUS_SUBMITTED {
// Ensure that group policy hasn't been modified before tally.
if proposal.GroupPolicyVersion != policyInfo.Version {
proposal.Result = group.PROPOSAL_RESULT_UNFINALIZED
proposal.Status = group.PROPOSAL_STATUS_ABORTED
return storeUpdates()
}
electorate, err := k.getGroupInfo(ctx, policyInfo.GroupId)
if err != nil {
return nil, sdkerrors.Wrap(err, "load group")
}
// Ensure that group hasn't been modified before tally.
if electorate.Version != proposal.GroupVersion {
proposal.Result = group.PROPOSAL_RESULT_UNFINALIZED
proposal.Status = group.PROPOSAL_STATUS_ABORTED
return storeUpdates()
}
if err := k.doTallyAndUpdate(ctx, &proposal, electorate, policyInfo); err != nil {
return nil, err
}
@ -805,7 +798,10 @@ func (k Keeper) Exec(goCtx context.Context, req *group.MsgExec) (*group.MsgExecR
return nil, err
}
err = ctx.EventManager().EmitTypedEvent(&group.EventExec{ProposalId: id})
err = ctx.EventManager().EmitTypedEvent(&group.EventExec{
ProposalId: id,
Result: proposal.ExecutorResult,
})
if err != nil {
return nil, err
}
@ -983,8 +979,8 @@ func (k Keeper) validateDecisionPolicies(ctx sdk.Context, g group.GroupInfo) err
}
defer it.Close()
var groupPolicy group.GroupPolicyInfo
for {
var groupPolicy group.GroupPolicyInfo
_, err = it.LoadNext(&groupPolicy)
if errors.ErrORMIteratorDone.Is(err) {
break

View File

@ -25,8 +25,8 @@ func (q Keeper) Tally(ctx sdk.Context, p group.Proposal, groupId uint64) (group.
tallyResult := group.DefaultTallyResult()
var vote group.Vote
for {
var vote group.Vote
_, err = it.LoadNext(&vote)
if errors.ErrORMIteratorDone.Is(err) {
break

View File

@ -9,4 +9,12 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) {
if err := k.UpdateTallyOfVPEndProposals(ctx); err != nil {
panic(err)
}
pruneProposals(ctx, k)
}
func pruneProposals(ctx sdk.Context, k keeper.Keeper) {
err := k.PruneProposals(ctx)
if err != nil {
panic(err)
}
}

View File

@ -6,18 +6,196 @@ import (
"time"
"github.com/cosmos/cosmos-sdk/simapp"
"github.com/cosmos/cosmos-sdk/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/bank/testutil"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/cosmos/cosmos-sdk/x/group"
"github.com/cosmos/cosmos-sdk/x/group/module"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
)
func TestEndBlockerPruning(t *testing.T) {
app := simapp.Setup(t, false)
ctx := app.BaseApp.NewContext(false, tmproto.Header{})
addrs := simapp.AddTestAddrsIncremental(app, ctx, 3, sdk.NewInt(30000000))
addr1 := addrs[0]
addr2 := addrs[1]
addr3 := addrs[2]
// Initial group, group policy and balance setup
members := []group.Member{
{Address: addr1.String(), Weight: "1"}, {Address: addr2.String(), Weight: "2"},
}
groupRes, err := app.GroupKeeper.CreateGroup(ctx, &group.MsgCreateGroup{
Admin: addr1.String(),
Members: members,
})
require.NoError(t, err)
groupID := groupRes.GroupId
policy := group.NewThresholdDecisionPolicy(
"2",
time.Second,
0,
)
policyReq := &group.MsgCreateGroupPolicy{
Admin: addr1.String(),
GroupId: groupID,
}
err = policyReq.SetDecisionPolicy(policy)
require.NoError(t, err)
policyRes, err := app.GroupKeeper.CreateGroupPolicy(ctx, policyReq)
require.NoError(t, err)
groupPolicyAddr, err := sdk.AccAddressFromBech32(policyRes.Address)
require.NoError(t, err)
require.NoError(t, testutil.FundAccount(app.BankKeeper, ctx, groupPolicyAddr, sdk.Coins{sdk.NewInt64Coin("test", 10000)}))
msgSend1 := &banktypes.MsgSend{
FromAddress: groupPolicyAddr.String(),
ToAddress: addr2.String(),
Amount: sdk.Coins{sdk.NewInt64Coin("test", 100)},
}
proposers := []string{addr2.String()}
specs := map[string]struct {
srcBlockTime time.Time
setupProposal func(ctx context.Context) uint64
expErr bool
expErrMsg string
expExecutorResult group.ProposalExecutorResult
}{
"proposal pruned after executor result success": {
setupProposal: func(ctx context.Context) uint64 {
msgs := []sdk.Msg{msgSend1}
pID, err := submitProposalAndVote(app, ctx, msgs, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
require.NoError(t, err)
_, err = app.GroupKeeper.Exec(ctx, &group.MsgExec{Signer: addr3.String(), ProposalId: pID})
require.NoError(t, err)
sdkCtx := sdk.UnwrapSDKContext(ctx)
require.NoError(t, testutil.FundAccount(app.BankKeeper, sdkCtx, groupPolicyAddr, sdk.Coins{sdk.NewInt64Coin("test", 10002)}))
return pID
},
expErrMsg: "load proposal: not found",
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
},
"proposal with multiple messages pruned when executed with result success": {
setupProposal: func(ctx context.Context) uint64 {
msgs := []sdk.Msg{msgSend1, msgSend1}
pID, err := submitProposalAndVote(app, ctx, msgs, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
require.NoError(t, err)
_, err = app.GroupKeeper.Exec(ctx, &group.MsgExec{Signer: addr3.String(), ProposalId: pID})
require.NoError(t, err)
sdkCtx := sdk.UnwrapSDKContext(ctx)
require.NoError(t, testutil.FundAccount(app.BankKeeper, sdkCtx, groupPolicyAddr, sdk.Coins{sdk.NewInt64Coin("test", 10002)}))
return pID
},
expErrMsg: "load proposal: not found",
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
},
"proposal not pruned when not executed and rejected": {
setupProposal: func(ctx context.Context) uint64 {
msgs := []sdk.Msg{msgSend1}
pID, err := submitProposalAndVote(app, ctx, msgs, proposers, groupPolicyAddr, group.VOTE_OPTION_NO)
require.NoError(t, err)
_, err = app.GroupKeeper.Exec(ctx, &group.MsgExec{Signer: addr3.String(), ProposalId: pID})
require.NoError(t, err)
sdkCtx := sdk.UnwrapSDKContext(ctx)
require.NoError(t, testutil.FundAccount(app.BankKeeper, sdkCtx, groupPolicyAddr, sdk.Coins{sdk.NewInt64Coin("test", 10002)}))
return pID
},
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
"open proposal is not pruned which must not fail ": {
setupProposal: func(ctx context.Context) uint64 {
pID, err := submitProposal(app, ctx, []sdk.Msg{msgSend1}, proposers, groupPolicyAddr)
require.NoError(t, err)
_, err = app.GroupKeeper.Exec(ctx, &group.MsgExec{Signer: addr3.String(), ProposalId: pID})
require.NoError(t, err)
sdkCtx := sdk.UnwrapSDKContext(ctx)
require.NoError(t, testutil.FundAccount(app.BankKeeper, sdkCtx, groupPolicyAddr, sdk.Coins{sdk.NewInt64Coin("test", 10002)}))
return pID
},
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
"proposal not pruned with group policy modified before tally": {
setupProposal: func(ctx context.Context) uint64 {
pID, err := submitProposal(app, ctx, []sdk.Msg{msgSend1}, proposers, groupPolicyAddr)
require.NoError(t, err)
_, err = app.GroupKeeper.UpdateGroupPolicyMetadata(ctx, &group.MsgUpdateGroupPolicyMetadata{
Admin: addr1.String(),
Address: groupPolicyAddr.String(),
})
require.NoError(t, err)
_, err = app.GroupKeeper.Exec(ctx, &group.MsgExec{Signer: addr3.String(), ProposalId: pID})
require.NoError(t, err)
sdkCtx := sdk.UnwrapSDKContext(ctx)
require.NoError(t, testutil.FundAccount(app.BankKeeper, sdkCtx, groupPolicyAddr, sdk.Coins{sdk.NewInt64Coin("test", 10002)}))
return pID
},
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
},
"pruned when proposal is executable when failed before": {
setupProposal: func(ctx context.Context) uint64 {
msgs := []sdk.Msg{msgSend1}
pID, err := submitProposalAndVote(app, ctx, msgs, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
require.NoError(t, err)
_, err = app.GroupKeeper.Exec(ctx, &group.MsgExec{Signer: addrs[2].String(), ProposalId: pID})
require.NoError(t, err)
return pID
},
expErrMsg: "load proposal: not found",
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
},
}
for msg, spec := range specs {
spec := spec
t.Run(msg, func(t *testing.T) {
proposalID := spec.setupProposal(ctx)
module.EndBlocker(ctx, app.GroupKeeper)
if spec.expExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS {
// Make sure proposal is deleted from state
_, err = app.GroupKeeper.Proposal(ctx, &group.QueryProposalRequest{ProposalId: proposalID})
require.Contains(t, err.Error(), spec.expErrMsg)
res, err := app.GroupKeeper.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{ProposalId: proposalID})
require.NoError(t, err)
require.Empty(t, res.GetVotes())
} else {
// Check that proposal and votes exists
res, err := app.GroupKeeper.Proposal(ctx, &group.QueryProposalRequest{ProposalId: proposalID})
require.NoError(t, err)
_, err = app.GroupKeeper.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{ProposalId: res.Proposal.Id})
require.NoError(t, err)
require.Equal(t, "", spec.expErrMsg)
exp := group.ProposalExecutorResult_name[int32(spec.expExecutorResult)]
got := group.ProposalExecutorResult_name[int32(res.Proposal.ExecutorResult)]
assert.Equal(t, exp, got)
}
})
}
}
func TestEndBlocker(t *testing.T) {
app := simapp.Setup(t, false)
ctx := app.BaseApp.NewContext(false, tmproto.Header{})
addrs := simapp.AddTestAddrsIncremental(app, ctx, 4, types.NewInt(30000000))
addrs := simapp.AddTestAddrsIncremental(app, ctx, 4, sdk.NewInt(30000000))
// Initial group, group policy and balance setup
members := []group.Member{
@ -48,45 +226,44 @@ func TestEndBlocker(t *testing.T) {
policyRes, err := app.GroupKeeper.CreateGroupPolicy(ctx, policyReq)
require.NoError(t, err)
groupPolicyAddr, err := types.AccAddressFromBech32(policyRes.Address)
groupPolicyAddr, err := sdk.AccAddressFromBech32(policyRes.Address)
require.NoError(t, err)
votingPeriod := policy.GetVotingPeriod()
now := time.Now()
msgSend := &banktypes.MsgSend{
FromAddress: groupPolicyAddr.String(),
ToAddress: addrs[3].String(),
Amount: types.Coins{types.NewInt64Coin("test", 100)},
Amount: sdk.Coins{sdk.NewInt64Coin("test", 100)},
}
proposers := []string{addrs[2].String()}
specs := map[string]struct {
preRun func(sdkCtx types.Context) uint64
preRun func(sdkCtx sdk.Context) uint64
proposalId uint64
admin string
expErrMsg string
newCtx types.Context
newCtx sdk.Context
tallyRes group.TallyResult
expStatus group.ProposalStatus
expExecutorResult group.ProposalResult
}{
"tally updated after voting power end": {
preRun: func(sdkCtx types.Context) uint64 {
pId, err := submitProposal(app, sdkCtx, []types.Msg{msgSend}, proposers, groupPolicyAddr)
preRun: func(sdkCtx sdk.Context) uint64 {
pId, err := submitProposal(app, sdkCtx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr)
require.NoError(t, err)
return pId
},
admin: proposers[0],
newCtx: ctx.WithBlockTime(now.Add(votingPeriod).Add(time.Hour)),
newCtx: ctx.WithBlockTime(ctx.BlockTime().Add(votingPeriod).Add(time.Hour)),
tallyRes: group.DefaultTallyResult(),
expStatus: group.PROPOSAL_STATUS_SUBMITTED,
expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED,
},
"tally within voting period": {
preRun: func(sdkCtx types.Context) uint64 {
pId, err := submitProposal(app, sdkCtx, []types.Msg{msgSend}, proposers, groupPolicyAddr)
preRun: func(sdkCtx sdk.Context) uint64 {
pId, err := submitProposal(app, sdkCtx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr)
require.NoError(t, err)
return pId
@ -98,8 +275,8 @@ func TestEndBlocker(t *testing.T) {
expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED,
},
"tally within voting period(with votes)": {
preRun: func(sdkCtx types.Context) uint64 {
pId, err := submitProposalAndVote(app, ctx, []types.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
preRun: func(sdkCtx sdk.Context) uint64 {
pId, err := submitProposalAndVote(app, ctx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
require.NoError(t, err)
return pId
@ -111,14 +288,14 @@ func TestEndBlocker(t *testing.T) {
expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED,
},
"tally after voting period(with votes)": {
preRun: func(sdkCtx types.Context) uint64 {
pId, err := submitProposalAndVote(app, ctx, []types.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
preRun: func(sdkCtx sdk.Context) uint64 {
pId, err := submitProposalAndVote(app, ctx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
require.NoError(t, err)
return pId
},
admin: proposers[0],
newCtx: ctx.WithBlockTime(now.Add(votingPeriod).Add(time.Hour)),
newCtx: ctx.WithBlockTime(ctx.BlockTime().Add(votingPeriod).Add(time.Hour)),
tallyRes: group.TallyResult{
YesCount: "2",
NoCount: "0",
@ -129,8 +306,8 @@ func TestEndBlocker(t *testing.T) {
expExecutorResult: group.PROPOSAL_RESULT_ACCEPTED,
},
"tally of closed proposal": {
preRun: func(sdkCtx types.Context) uint64 {
pId, err := submitProposal(app, sdkCtx, []types.Msg{msgSend}, proposers, groupPolicyAddr)
preRun: func(sdkCtx sdk.Context) uint64 {
pId, err := submitProposal(app, sdkCtx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr)
require.NoError(t, err)
_, err = app.GroupKeeper.WithdrawProposal(ctx, &group.MsgWithdrawProposal{
@ -148,8 +325,8 @@ func TestEndBlocker(t *testing.T) {
expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED,
},
"tally of closed proposal (with votes)": {
preRun: func(sdkCtx types.Context) uint64 {
pId, err := submitProposalAndVote(app, ctx, []types.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
preRun: func(sdkCtx sdk.Context) uint64 {
pId, err := submitProposalAndVote(app, ctx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
require.NoError(t, err)
_, err = app.GroupKeeper.WithdrawProposal(ctx, &group.MsgWithdrawProposal{
@ -193,8 +370,8 @@ func TestEndBlocker(t *testing.T) {
}
func submitProposal(
app *simapp.SimApp, ctx context.Context, msgs []types.Msg,
proposers []string, groupPolicyAddr types.AccAddress) (uint64, error) {
app *simapp.SimApp, ctx context.Context, msgs []sdk.Msg,
proposers []string, groupPolicyAddr sdk.AccAddress) (uint64, error) {
proposalReq := &group.MsgSubmitProposal{
Address: groupPolicyAddr.String(),
Proposers: proposers,
@ -213,13 +390,12 @@ func submitProposal(
}
func submitProposalAndVote(
app *simapp.SimApp, ctx context.Context, msgs []types.Msg,
proposers []string, groupPolicyAddr types.AccAddress, voteOption group.VoteOption) (uint64, error) {
app *simapp.SimApp, ctx context.Context, msgs []sdk.Msg,
proposers []string, groupPolicyAddr sdk.AccAddress, voteOption group.VoteOption) (uint64, error) {
myProposalID, err := submitProposal(app, ctx, msgs, proposers, groupPolicyAddr)
if err != nil {
return 0, err
}
_, err = app.GroupKeeper.Vote(ctx, &group.MsgVote{
ProposalId: myProposalID,
Voter: proposers[0],
@ -228,6 +404,5 @@ func submitProposalAndVote(
if err != nil {
return 0, err
}
return myProposalID, nil
}

37
x/group/proposal_test.go Normal file
View File

@ -0,0 +1,37 @@
package group_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/simapp"
"github.com/cosmos/cosmos-sdk/x/group"
)
// TestGogoUnmarshalProposal tests some weird behavior in gogoproto
// unmarshalling.
// This test serves as a showcase that we need to be careful when unmarshalling
// multiple times into the same reference.
func TestGogoUnmarshalProposal(t *testing.T) {
cdc := simapp.MakeTestEncodingConfig().Codec
p1 := group.Proposal{Proposers: []string{"foo"}}
p2 := group.Proposal{Proposers: []string{"bar"}}
p1Bz, err := cdc.Marshal(&p1)
require.NoError(t, err)
p2Bz, err := cdc.Marshal(&p2)
require.NoError(t, err)
var p group.Proposal
err = cdc.Unmarshal(p1Bz, &p)
require.NoError(t, err)
err = cdc.Unmarshal(p2Bz, &p)
require.NoError(t, err)
// One would expect that unmarshalling into the same `&p` reference would
// clear the previous `p` value. But it seems that (at least for array
// fields), the values are not replaced, but concatenated, which
// is not an intuitive behavior.
require.Len(t, p.Proposers, 2)
}

View File

@ -72,13 +72,13 @@ func getGroupPolicies(r *rand.Rand, simState *module.SimulationState) []*group.G
return groupPolicies
}
func getProposals(r *rand.Rand, simState *module.SimulationState) []*group.Proposal {
func getProposals(r *rand.Rand, simState *module.SimulationState, groupPolicies []*group.GroupPolicyInfo) []*group.Proposal {
proposals := make([]*group.Proposal, 3)
proposers := []string{simState.Accounts[0].Address.String(), simState.Accounts[1].Address.String()}
for i := 0; i < 3; i++ {
from, _ := simtypes.RandomAcc(r, simState.Accounts)
idx := r.Intn(len(groupPolicies))
groupPolicyAddress := groupPolicies[idx].Address
to, _ := simtypes.RandomAcc(r, simState.Accounts)
fromAddr := from.Address.String()
submittedAt := time.Unix(0, 0)
timeout := submittedAt.Add(time.Second * 1000).UTC()
@ -86,7 +86,7 @@ func getProposals(r *rand.Rand, simState *module.SimulationState) []*group.Propo
proposal := &group.Proposal{
Id: uint64(i + 1),
Proposers: proposers,
Address: fromAddr,
Address: groupPolicyAddress,
GroupVersion: uint64(i + 1),
GroupPolicyVersion: uint64(i + 1),
Status: group.PROPOSAL_STATUS_SUBMITTED,
@ -103,7 +103,7 @@ func getProposals(r *rand.Rand, simState *module.SimulationState) []*group.Propo
VotingPeriodEnd: timeout,
}
err := proposal.SetMsgs([]sdk.Msg{&banktypes.MsgSend{
FromAddress: fromAddr,
FromAddress: groupPolicyAddress,
ToAddress: to.Address.String(),
Amount: sdk.NewCoins(sdk.NewInt64Coin("test", 10)),
}})
@ -163,7 +163,7 @@ func RandomizedGenState(simState *module.SimulationState) {
func(r *rand.Rand) { members = getGroupMembers(r, simState.Accounts) },
)
// group accounts
// group policies
var groupPolicies []*group.GroupPolicyInfo
simState.AppParams.GetOrGenerate(
simState.Cdc, GroupPolicyInfo, &groupPolicies, simState.Rand,
@ -174,7 +174,7 @@ func RandomizedGenState(simState *module.SimulationState) {
var proposals []*group.Proposal
simState.AppParams.GetOrGenerate(
simState.Cdc, GroupProposals, &proposals, simState.Rand,
func(r *rand.Rand) { proposals = getProposals(r, simState) },
func(r *rand.Rand) { proposals = getProposals(r, simState, groupPolicies) },
)
// votes

View File

@ -873,16 +873,6 @@ func SimulateMsgWithdrawProposal(ak group.AccountKeeper,
return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, "no proposals found"), nil, nil
}
// Ensure that group and group policy haven't been modified since the proposal submission.
if proposal.GroupPolicyVersion != groupPolicy.Version {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, "group policy has been modified"), nil, nil
}
// Ensure the group hasn't been modified.
if proposal.GroupVersion != g.Version {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, "group has been modified"), nil, nil
}
// select a random proposer
proposers := proposal.Proposers
n := randIntInRange(r, len(proposers))
@ -972,13 +962,11 @@ func SimulateMsgVote(ak group.AccountKeeper,
return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "no proposals found"), nil, nil
}
var proposal *group.Proposal
proposalID := -1
for _, p := range proposals {
if p.Status == group.PROPOSAL_STATUS_SUBMITTED {
timeout := p.VotingPeriodEnd
proposal = p
proposalID = int(p.Id)
if timeout.Before(sdkCtx.BlockTime()) || timeout.Equal(sdkCtx.BlockTime()) {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "voting period ended: skipping"), nil, nil
@ -992,14 +980,6 @@ func SimulateMsgVote(ak group.AccountKeeper,
return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "no proposals found"), nil, nil
}
// Ensure that group and group policy haven't been modified since the proposal submission.
if proposal.GroupPolicyVersion != groupPolicy.Version {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "group policy has been modified"), nil, nil
}
if proposal.GroupVersion != g.Version {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "group has been modified"), nil, nil
}
// Ensure member hasn't already voted
res, _ := k.VoteByProposalVoter(ctx, &group.QueryVoteByProposalVoterRequest{
Voter: acc.Address.String(),

View File

@ -26,14 +26,19 @@ those "sub-accounts" using the `x/authz` module.
## Decision Policy
A decision policy is the mechanism by which members of a group can vote on
proposals.
A decision policy is the mechanism by which members of a group can vote on
proposals, as well as the rules that dictate whether a proposal should pass
or not based on its tally outcome.
All decision policies generally would have a minimum and maximum voting window.
The minimum voting window is the minimum amount of time 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 before it is closed.
Both of these values must be less than a chain-wide max voting window parameter.
All decision policies generally would have a mininum execution perdio and a
maximum voting window. The minimum execution period is the minimum amount of time
that must pass in order for a proposal to potentially be executed, and it may
be set to 0. The maximum voting window is the maximum time that a proposal may
be voted on before it is closed.
The chain developer also defines an app-wide maximum execution period, which is
the maximum amount of time after a proposal's voting period end where users are
allowed to execute a proposal.
### Threshold decision policy
@ -41,6 +46,14 @@ A threshold decision policy defines a threshold of yes votes (based on a tally
of voter weights) that must be achieved in order for a proposal to pass. For
this decision policy, abstain and veto are simply treated as no's.
### Percentage decision policy
A percentage decision policy is similar to a threshold decision policy, except
that the threshold is not defined as a constant weight, but as a percentage.
It's more suited for groups where the group members' weights can be updated, as
the percentage threshold stays the same, and doesn't depend on how those member
weights get updated.
## Proposal
Any member of a group can submit a proposal for a group policy account to decide upon.
@ -51,24 +64,56 @@ passes as well as any metadata associated with the proposal.
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.
During the voting window, accounts that have already voted may change their vote.
In the current implementation, the voting window begins as soon as a proposal
is submitted.
## Tallying
Tallying is the counting of all votes on a proposal. It happens only once in
the lifecycle of a proposal, but can be triggered by two factors, whichever
happens first:
- either someone tries to execute the proposal (see next section), which can
happen on a `Msg/Exec` transaction, or a `Msg/{SubmitProposal,Vote}`
transaction with the `Exec` field set. When a proposal execution is attempted,
a tally is done first to make sure the proposal passes.
- or on `EndBlock` when the proposal's voting period end just passed.
If the tally result passes the decision policy's rules, then the proposal is
marked as `STATUS_CLOSED`, so no more voting is allowed anymore, and the tally
result is persisted to state.
## Executing Proposals
Proposals are executed only when the tallying is done, and the group account's
decision policy allows the proposal to pass based on the tally outcome.
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.
It's also possible to try to execute a proposal immediately on creation or on
new votes using the `Exec` field of `Msg/CreateProposal` and `Msg/Vote` requests.
new votes using the `Exec` field of `Msg/SubmitProposal` and `Msg/Vote` requests.
In the former case, proposers signatures are considered as yes votes.
For now, if the proposal can't be executed, it'll still be opened for new votes and
could be executed later on.
### Changing Group Membership
## Pruning
In the current implementation, changing a group's membership (adding or removing members or changing their weight)
will cause all existing proposals for group policy accounts linked to this group
to be invalidated. They will simply fail if someone calls `Msg/Exec` and will
eventually be garbage collected.
Proposals and votes are automatically pruned to avoid state bloat.
Votes are pruned:
- either after a successful tally, i.e. a tally whose result passes the decision
policy's rules, which can be trigged by a `Msg/Exec` or a
`Msg/{SubmitProposal,Vote}` with the `Exec` field,
- or on `EndBlock` right after the proposal's voting period end,
whichever happens first.
Proposals are pruned:
- either after a successful proposal execution,
- or on `EndBlock` right after the proposal's `voting_period_end` +
`max_execution_period` (defined as an app-wide configuration) is passed,
whichever happens first.

View File

@ -80,10 +80,12 @@ The second `0x1` corresponds to the ORM `sequenceStorageKey`.
`proposalByGroupPolicyIndex` allows to retrieve proposals by group policy account address:
`0x32 | len([]byte(account.Address)) | []byte(account.Address) | BigEndian(ProposalId) -> []byte()`.
### proposalByProposerIndex
### ProposalsByVotingPeriodEndIndex
`proposalByProposerIndex` allows to retrieve proposals by proposer address:
`0x33 | len([]byte(proposer.Address)) | []byte(proposer.Address) | BigEndian(ProposalId) -> []byte()`.
`proposalsByVotingPeriodEndIndex` allows to retrieve proposals sorted by chronological `voting_period_end`:
`0x33 | sdk.FormatTimeBytes(proposal.VotingPeriodEnd) | BigEndian(ProposalId) -> []byte()`.
This index is used when tallying the proposal votes at the end of the voting period, and for pruning proposals at `VotingPeriodEnd + MaxExecutionPeriod`.
## Vote Table

View File

@ -124,8 +124,6 @@ A proposal can be executed with the `MsgExec`.
The messages that are part of this proposal won't be executed if:
* the group has been modified before tally.
* the group policy has been modified before tally.
* the proposal has not been accepted.
* the proposal status is not closed.
* the proposal has already been successfully executed.