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

View File

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

View File

@ -20,24 +20,10 @@ const (
// RegisterInvariants registers all group invariants // RegisterInvariants registers all group invariants
func RegisterInvariants(ir sdk.InvariantRegistry, keeper Keeper) { 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, weightInvariant, GroupTotalWeightInvariant(keeper))
ir.RegisterRoute(group.ModuleName, votesSumInvariant, TallyVotesSumInvariant(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. // GroupTotalWeightInvariant checks that group's TotalWeight must be equal to the sum of its members.
func GroupTotalWeightInvariant(keeper Keeper) sdk.Invariant { func GroupTotalWeightInvariant(keeper Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) { 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 { func TallyVotesSumInvariant(keeper Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) { 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 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) { func GroupTotalWeightInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, groupTable orm.AutoUInt64Table, groupMemberByGroupIndex orm.Index) (string, bool) {
var msg string var msg string
var broken bool var broken bool
var groupInfo group.GroupInfo
var groupMember group.GroupMember
groupIt, err := groupTable.PrefixScan(ctx.KVStore(key), 1, math.MaxUint64) groupIt, err := groupTable.PrefixScan(ctx.KVStore(key), 1, math.MaxUint64)
if err != nil { if err != nil {
msg += fmt.Sprintf("PrefixScan failure on group table\n%v\n", err) 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) msg += fmt.Sprintf("error while parsing positive dec zero for group member\n%v\n", err)
return msg, broken return msg, broken
} }
var groupInfo group.GroupInfo
_, err = groupIt.LoadNext(&groupInfo) _, err = groupIt.LoadNext(&groupInfo)
if errors.ErrORMIteratorDone.Is(err) { if errors.ErrORMIteratorDone.Is(err) {
break 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) memIt, err := groupMemberByGroupIndex.Get(ctx.KVStore(key), groupInfo.Id)
if err != nil { if err != nil {
msg += fmt.Sprintf("error while returning group member iterator for group with ID %d\n%v\n", groupInfo.Id, err) 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() defer memIt.Close()
for { for {
var groupMember group.GroupMember
_, err = memIt.LoadNext(&groupMember) _, err = memIt.LoadNext(&groupMember)
if errors.ErrORMIteratorDone.Is(err) { if errors.ErrORMIteratorDone.Is(err) {
break 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()) curMemWeight, err := groupmath.NewNonNegativeDecFromString(groupMember.GetMember().GetWeight())
if err != nil { if err != nil {
msg += fmt.Sprintf("error while parsing non-nengative decimal for group member %s\n%v\n", groupMember.Member.Address, err) 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 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 msg string
var broken bool 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) proposalIt, err := proposalTable.PrefixScan(ctx.KVStore(key), 1, math.MaxUint64)
if err != nil { if err != nil {
fmt.Println(err)
msg += fmt.Sprintf("PrefixScan failure on proposal table\n%v\n", err) msg += fmt.Sprintf("PrefixScan failure on proposal table\n%v\n", err)
return msg, broken return msg, broken
} }
defer proposalIt.Close() defer proposalIt.Close()
for { 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") totalVotingWeight, err := groupmath.NewNonNegativeDecFromString("0")
if err != nil { if err != nil {
@ -247,11 +167,7 @@ func TallyVotesSumInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, grou
return msg, broken return msg, broken
} }
_, err = proposalIt.LoadNext(&proposal) var groupPolicy group.GroupPolicyInfo
if errors.ErrORMIteratorDone.Is(err) {
break
}
err = groupPolicyTable.GetOne(ctx.KVStore(key), orm.PrimaryKey(&group.GroupPolicyInfo{Address: proposal.Address}), &groupPolicy) err = groupPolicyTable.GetOne(ctx.KVStore(key), orm.PrimaryKey(&group.GroupPolicyInfo{Address: proposal.Address}), &groupPolicy)
if err != nil { if err != nil {
msg += fmt.Sprintf("group policy not found for address: %s\n%v\n", proposal.Address, err) 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 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) voteIt, err := voteByProposalIndex.Get(ctx.KVStore(key), proposal.Id)
if err != nil { if err != nil {
msg += fmt.Sprintf("error while returning vote iterator for proposal with ID %d\n%v\n", proposal.Id, err) msg += fmt.Sprintf("error while returning vote iterator for proposal with ID %d\n%v\n", proposal.Id, err)
return msg, broken return msg, broken
} }
defer voteIt.Close()
for { for {
var vote group.Vote
_, err := voteIt.LoadNext(&vote) _, err := voteIt.LoadNext(&vote)
if errors.ErrORMIteratorDone.Is(err) { if errors.ErrORMIteratorDone.Is(err) {
break 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) err = groupMemberTable.GetOne(ctx.KVStore(key), orm.PrimaryKey(&group.GroupMember{GroupId: groupPolicy.GroupId, Member: &group.Member{Address: vote.Voter}}), &groupMem)
if err != nil { 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) 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() totalProposalVotes, err := proposal.FinalTallyResult.TotalCounts()
if err != nil { 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) { if (yesVoteWeight.Cmp(proposalYesCount) != 0) || (noVoteWeight.Cmp(proposalNoCount) != 0) || (abstainVoteWeight.Cmp(proposalAbstainCount) != 0) || (vetoVoteWeight.Cmp(proposalVetoCount) != 0) {
broken = true 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 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() { func (s *invariantTestSuite) TestGroupTotalWeightInvariant() {
sdkCtx, _ := s.ctx.CacheContext() sdkCtx, _ := s.ctx.CacheContext()
curCtx, cdc, key := sdkCtx, s.cdc, s.key curCtx, cdc, key := sdkCtx, s.cdc, s.key
@ -365,6 +182,8 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
_, _, addr1 := testdata.KeyTestPubAddr() _, _, addr1 := testdata.KeyTestPubAddr()
_, _, addr2 := testdata.KeyTestPubAddr() _, _, addr2 := testdata.KeyTestPubAddr()
votingPeriodEnd := curCtx.BlockTime().Add(time.Second * 600)
specs := map[string]struct { specs := map[string]struct {
groupsInfo *group.GroupInfo groupsInfo *group.GroupInfo
groupPolicy *group.GroupPolicyInfo groupPolicy *group.GroupPolicyInfo
@ -409,10 +228,10 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
SubmitTime: curCtx.BlockTime(), SubmitTime: curCtx.BlockTime(),
GroupVersion: 1, GroupVersion: 1,
GroupPolicyVersion: 1, GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED, Status: group.PROPOSAL_STATUS_CLOSED,
Result: group.PROPOSAL_RESULT_UNFINALIZED, Result: group.PROPOSAL_RESULT_ACCEPTED,
FinalTallyResult: group.TallyResult{YesCount: "4", NoCount: "3", AbstainCount: "0", NoWithVetoCount: "0"}, 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, ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
}, },
votes: []*group.Vote{ votes: []*group.Vote{
@ -431,6 +250,58 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
}, },
expBroken: false, 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": { "proposal tally must correspond to the sum of vote weights": {
groupsInfo: &group.GroupInfo{ groupsInfo: &group.GroupInfo{
Id: 1, Id: 1,
@ -467,10 +338,10 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
SubmitTime: curCtx.BlockTime(), SubmitTime: curCtx.BlockTime(),
GroupVersion: 1, GroupVersion: 1,
GroupPolicyVersion: 1, GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED, Status: group.PROPOSAL_STATUS_CLOSED,
Result: group.PROPOSAL_RESULT_UNFINALIZED, Result: group.PROPOSAL_RESULT_ACCEPTED,
FinalTallyResult: group.TallyResult{YesCount: "6", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"}, 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, ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
}, },
votes: []*group.Vote{ votes: []*group.Vote{
@ -525,10 +396,10 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
SubmitTime: curCtx.BlockTime(), SubmitTime: curCtx.BlockTime(),
GroupVersion: 1, GroupVersion: 1,
GroupPolicyVersion: 1, GroupPolicyVersion: 1,
Status: group.PROPOSAL_STATUS_SUBMITTED, Status: group.PROPOSAL_STATUS_CLOSED,
Result: group.PROPOSAL_RESULT_UNFINALIZED, Result: group.PROPOSAL_RESULT_ACCEPTED,
FinalTallyResult: group.TallyResult{YesCount: "4", NoCount: "3", AbstainCount: "0", NoWithVetoCount: "0"}, 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, ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN,
}, },
votes: []*group.Vote{ votes: []*group.Vote{
@ -578,7 +449,7 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() {
s.Require().NoError(err) 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) s.Require().Equal(spec.expBroken, broken)
} }
} }

View File

@ -2,12 +2,14 @@ package keeper
import ( import (
"fmt" "fmt"
"time"
"github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/log"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
storetypes "github.com/cosmos/cosmos-sdk/store/types" storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/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" authmiddleware "github.com/cosmos/cosmos-sdk/x/auth/middleware"
"github.com/cosmos/cosmos-sdk/x/group" "github.com/cosmos/cosmos-sdk/x/group"
"github.com/cosmos/cosmos-sdk/x/group/errors" "github.com/cosmos/cosmos-sdk/x/group/errors"
@ -35,8 +37,7 @@ const (
ProposalTablePrefix byte = 0x30 ProposalTablePrefix byte = 0x30
ProposalTableSeqPrefix byte = 0x31 ProposalTableSeqPrefix byte = 0x31
ProposalByGroupPolicyIndexPrefix byte = 0x32 ProposalByGroupPolicyIndexPrefix byte = 0x32
ProposalByProposerIndexPrefix byte = 0x33 ProposalsByVotingPeriodEndPrefix byte = 0x33
ProposalsByVotingPeriodEndPrefix byte = 0x34
// Vote Table // Vote Table
VoteTablePrefix byte = 0x40 VoteTablePrefix byte = 0x40
@ -67,7 +68,6 @@ type Keeper struct {
// Proposal Table // Proposal Table
proposalTable orm.AutoUInt64Table proposalTable orm.AutoUInt64Table
proposalByGroupPolicyIndex orm.Index proposalByGroupPolicyIndex orm.Index
proposalByProposerIndex orm.Index
proposalsByVotingPeriodEnd orm.Index proposalsByVotingPeriodEnd orm.Index
// Vote Table // Vote Table
@ -169,21 +169,6 @@ func NewKeeper(storeKey storetypes.StoreKey, cdc codec.Codec, router *authmiddle
if err != nil { if err != nil {
panic(err.Error()) 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) { k.proposalsByVotingPeriodEnd, err = orm.NewIndex(proposalTable, ProposalsByVotingPeriodEndPrefix, func(value interface{}) ([]interface{}, error) {
votingPeriodEnd := value.(*group.Proposal).VotingPeriodEnd votingPeriodEnd := value.(*group.Proposal).VotingPeriodEnd
return []interface{}{sdk.FormatTimeBytes(votingPeriodEnd)}, nil 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)) 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 // GetGroupSequence returns the current value of the group table sequence
func (k Keeper) GetGroupSequence(ctx sdk.Context) uint64 { func (k Keeper) GetGroupSequence(ctx sdk.Context) uint64 {
return k.groupTable.Sequence().CurVal(ctx.KVStore(k.key)) return k.groupTable.Sequence().CurVal(ctx.KVStore(k.key))
} }
func (k Keeper) iterateProposalsByVPEnd(ctx sdk.Context, cb func(proposal group.Proposal) (bool, error)) error { // iterateProposalsByVPEnd iterates over all proposals whose voting_period_end is after the `endTime` time argument.
timeBytes := sdk.FormatTimeBytes(ctx.BlockTime()) 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) it, err := k.proposalsByVotingPeriodEnd.PrefixScan(ctx.KVStore(k.key), nil, timeBytes)
if err != nil { if err != nil {
@ -250,8 +233,17 @@ func (k Keeper) iterateProposalsByVPEnd(ctx sdk.Context, cb func(proposal group.
} }
defer it.Close() defer it.Close()
var proposal group.Proposal
for { 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) _, err := it.LoadNext(&proposal)
if errors.ErrORMIteratorDone.Is(err) { if errors.ErrORMIteratorDone.Is(err) {
break break
@ -272,30 +264,87 @@ func (k Keeper) iterateProposalsByVPEnd(ctx sdk.Context, cb func(proposal group.
return nil return nil
} }
func (k Keeper) UpdateTallyOfVPEndProposals(ctx sdk.Context) error { // pruneProposal deletes a proposal from state.
k.iterateProposalsByVPEnd(ctx, func(proposal group.Proposal) (bool, error) { 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 { if err != nil {
return true, err return err
} }
electorate, err := k.getGroupInfo(ctx, policyInfo.GroupId) err = k.voteTable.Delete(store, &vote)
if err != nil { 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 { if err != nil {
return true, err return true, err
} }
if err := k.proposalTable.Update(ctx.KVStore(k.key), proposal.Id, &proposal); err != nil {
return true, err
}
return false, nil return false, nil
}) })
if err != nil {
return err
}
return nil 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) s.Require().NoError(err)
id := res.ProposalId id := res.ProposalId
// then all data persisted if !(spec.expProposal.ExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS) {
proposalRes, err := s.keeper.Proposal(s.ctx, &group.QueryProposalRequest{ProposalId: id}) // then all data persisted
s.Require().NoError(err) proposalRes, err := s.keeper.Proposal(s.ctx, &group.QueryProposalRequest{ProposalId: id})
proposal := proposalRes.Proposal s.Require().NoError(err)
proposal := proposalRes.Proposal
s.Assert().Equal(spec.expProposal.Address, proposal.Address) s.Assert().Equal(spec.expProposal.Address, proposal.Address)
s.Assert().Equal(spec.req.Metadata, proposal.Metadata) s.Assert().Equal(spec.req.Metadata, proposal.Metadata)
s.Assert().Equal(spec.req.Proposers, proposal.Proposers) s.Assert().Equal(spec.req.Proposers, proposal.Proposers)
s.Assert().Equal(s.blockTime, proposal.SubmitTime) s.Assert().Equal(s.blockTime, proposal.SubmitTime)
s.Assert().Equal(uint64(1), proposal.GroupVersion) s.Assert().Equal(uint64(1), proposal.GroupVersion)
s.Assert().Equal(uint64(1), proposal.GroupPolicyVersion) s.Assert().Equal(uint64(1), proposal.GroupPolicyVersion)
s.Assert().Equal(spec.expProposal.Status, proposal.Status) s.Assert().Equal(spec.expProposal.Status, proposal.Status)
s.Assert().Equal(spec.expProposal.Result, proposal.Result) s.Assert().Equal(spec.expProposal.Result, proposal.Result)
s.Assert().Equal(spec.expProposal.FinalTallyResult, proposal.FinalTallyResult) s.Assert().Equal(spec.expProposal.FinalTallyResult, proposal.FinalTallyResult)
s.Assert().Equal(spec.expProposal.ExecutorResult, proposal.ExecutorResult) s.Assert().Equal(spec.expProposal.ExecutorResult, proposal.ExecutorResult)
s.Assert().Equal(s.blockTime.Add(time.Second), proposal.VotingPeriodEnd) s.Assert().Equal(s.blockTime.Add(time.Second), proposal.VotingPeriodEnd)
if spec.msgs == nil { // then empty list is ok if spec.msgs == nil { // then empty list is ok
s.Assert().Len(proposal.GetMsgs(), 0) s.Assert().Len(proposal.GetMsgs(), 0)
} else { } else {
s.Assert().Equal(spec.msgs, proposal.GetMsgs()) s.Assert().Equal(spec.msgs, proposal.GetMsgs())
}
} }
spec.postRun(s.sdkCtx) spec.postRun(s.sdkCtx)
@ -2042,47 +2044,6 @@ func (s *TestSuite) TestVote() {
expErr: true, expErr: true,
postRun: func(sdkCtx sdk.Context) {}, 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 { for msg, spec := range specs {
spec := spec spec := spec
@ -2105,66 +2066,69 @@ func (s *TestSuite) TestVote() {
s.Require().NoError(err) s.Require().NoError(err)
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 if !(spec.expExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS) {
votesByProposalRes, err := s.keeper.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{ // vote is stored and all data persisted
ProposalId: spec.req.ProposalId, res, err := s.keeper.VoteByProposalVoter(ctx, &group.QueryVoteByProposalVoterRequest{
}) ProposalId: spec.req.ProposalId,
s.Require().NoError(err) Voter: spec.req.Voter,
votesByProposal := votesByProposalRes.Votes })
s.Require().Equal(1, len(votesByProposal)) s.Require().NoError(err)
vote := votesByProposal[0] loaded := res.Vote
s.Assert().Equal(spec.req.ProposalId, vote.ProposalId) s.Assert().Equal(spec.req.ProposalId, loaded.ProposalId)
s.Assert().Equal(spec.req.Voter, vote.Voter) s.Assert().Equal(spec.req.Voter, loaded.Voter)
s.Assert().Equal(spec.req.Option, vote.Option) s.Assert().Equal(spec.req.Option, loaded.Option)
s.Assert().Equal(spec.req.Metadata, vote.Metadata) s.Assert().Equal(spec.req.Metadata, loaded.Metadata)
s.Assert().Equal(s.blockTime, vote.SubmitTime) s.Assert().Equal(s.blockTime, loaded.SubmitTime)
// query votes by voter // query votes by proposal
voter := spec.req.Voter votesByProposalRes, err := s.keeper.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{
votesByVoterRes, err := s.keeper.VotesByVoter(ctx, &group.QueryVotesByVoterRequest{ ProposalId: spec.req.ProposalId,
Voter: voter, })
}) s.Require().NoError(err)
s.Require().NoError(err) votesByProposal := votesByProposalRes.Votes
votesByVoter := votesByVoterRes.Votes s.Require().Equal(1, len(votesByProposal))
s.Require().Equal(1, len(votesByVoter)) vote := votesByProposal[0]
s.Assert().Equal(spec.req.ProposalId, votesByVoter[0].ProposalId) s.Assert().Equal(spec.req.ProposalId, vote.ProposalId)
s.Assert().Equal(voter, votesByVoter[0].Voter) s.Assert().Equal(spec.req.Voter, vote.Voter)
s.Assert().Equal(spec.req.Option, votesByVoter[0].Option) s.Assert().Equal(spec.req.Option, vote.Option)
s.Assert().Equal(spec.req.Metadata, votesByVoter[0].Metadata) s.Assert().Equal(spec.req.Metadata, vote.Metadata)
s.Assert().Equal(s.blockTime, votesByVoter[0].SubmitTime) s.Assert().Equal(s.blockTime, vote.SubmitTime)
proposalRes, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{ // query votes by voter
ProposalId: spec.req.ProposalId, voter := spec.req.Voter
}) votesByVoterRes, err := s.keeper.VotesByVoter(ctx, &group.QueryVotesByVoterRequest{
s.Require().NoError(err) 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 proposalRes, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{
if spec.isFinal { ProposalId: spec.req.ProposalId,
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.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) spec.postRun(sdkCtx)
@ -2329,36 +2293,6 @@ func (s *TestSuite) TestExecProposal() {
expProposalResult: group.PROPOSAL_RESULT_REJECTED, expProposalResult: group.PROPOSAL_RESULT_REJECTED,
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, 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": { "prevent double execution when successful": {
setupProposal: func(ctx context.Context) uint64 { setupProposal: func(ctx context.Context) uint64 {
myProposalID := submitProposalAndVote(ctx, s, []sdk.Msg{msgSend1}, proposers, group.VOTE_OPTION_YES) myProposalID := submitProposalAndVote(ctx, s, []sdk.Msg{msgSend1}, proposers, group.VOTE_OPTION_YES)
@ -2367,6 +2301,7 @@ func (s *TestSuite) TestExecProposal() {
s.Require().NoError(err) s.Require().NoError(err)
return myProposalID return myProposalID
}, },
expErr: true, // since proposal is pruned after a successful MsgExec
expProposalStatus: group.PROPOSAL_STATUS_CLOSED, expProposalStatus: group.PROPOSAL_STATUS_CLOSED,
expProposalResult: group.PROPOSAL_RESULT_ACCEPTED, expProposalResult: group.PROPOSAL_RESULT_ACCEPTED,
expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS, expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS,
@ -2419,22 +2354,25 @@ func (s *TestSuite) TestExecProposal() {
} }
s.Require().NoError(err) s.Require().NoError(err)
// and proposal is updated if !(spec.expExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS) {
res, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{ProposalId: proposalID})
s.Require().NoError(err)
proposal := res.Proposal
exp := group.ProposalResult_name[int32(spec.expProposalResult)] // and proposal is updated
got := group.ProposalResult_name[int32(proposal.Result)] res, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{ProposalId: proposalID})
s.Assert().Equal(exp, got) s.Require().NoError(err)
proposal := res.Proposal
exp = group.ProposalStatus_name[int32(spec.expProposalStatus)] exp := group.ProposalResult_name[int32(spec.expProposalResult)]
got = group.ProposalStatus_name[int32(proposal.Status)] got := group.ProposalResult_name[int32(proposal.Result)]
s.Assert().Equal(exp, got) s.Assert().Equal(exp, got)
exp = group.ProposalExecutorResult_name[int32(spec.expExecutorResult)] exp = group.ProposalStatus_name[int32(spec.expProposalStatus)]
got = group.ProposalExecutorResult_name[int32(proposal.ExecutorResult)] got = group.ProposalStatus_name[int32(proposal.Status)]
s.Assert().Equal(exp, got) 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 { if spec.expBalance {
fromBalances := s.app.BankKeeper.GetAllBalances(sdkCtx, s.groupPolicyAddr) 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() { func (s *TestSuite) TestProposalsByVPEnd() {
addrs := s.addrs addrs := s.addrs
addr2 := addrs[1] 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 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 { if policyInfo, err = k.getGroupPolicyInfo(ctx, proposal.Address); err != nil {
return nil, sdkerrors.Wrap(err, "load group policy") 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) electorate, err := k.getGroupInfo(ctx, policyInfo.GroupId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if electorate.Version != proposal.GroupVersion {
return nil, sdkerrors.Wrap(errors.ErrModified, "group was modified")
}
// Count and store votes. // Count and store votes.
voterAddr := req.Voter voterAddr := req.Voter
@ -711,17 +703,22 @@ func (k Keeper) doTallyAndUpdate(ctx sdk.Context, p *group.Proposal, electorate
return err 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: case err != nil:
return sdkerrors.Wrap(err, "policy execution") return sdkerrors.Wrap(err, "policy allow")
case result.Allow && result.Final: case result.Final:
if err := k.pruneVotes(ctx, p.Id); err != nil {
return err
}
p.FinalTallyResult = tallyResult p.FinalTallyResult = tallyResult
p.Result = group.PROPOSAL_RESULT_ACCEPTED if result.Allow {
p.Status = group.PROPOSAL_STATUS_CLOSED p.Result = group.PROPOSAL_RESULT_ACCEPTED
case !result.Allow && result.Final: p.Status = group.PROPOSAL_STATUS_CLOSED
p.FinalTallyResult = tallyResult } else {
p.Result = group.PROPOSAL_RESULT_REJECTED p.Result = group.PROPOSAL_RESULT_REJECTED
p.Status = group.PROPOSAL_STATUS_CLOSED p.Status = group.PROPOSAL_STATUS_CLOSED
}
} }
return nil return nil
@ -747,32 +744,28 @@ func (k Keeper) Exec(goCtx context.Context, req *group.MsgExec) (*group.MsgExecR
} }
storeUpdates := func() (*group.MsgExecResponse, error) { storeUpdates := func() (*group.MsgExecResponse, error) {
if err := k.proposalTable.Update(ctx.KVStore(k.key), id, &proposal); err != nil { store := ctx.KVStore(k.key)
return nil, err
// 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 return &group.MsgExecResponse{}, nil
} }
if proposal.Status == group.PROPOSAL_STATUS_SUBMITTED { 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) electorate, err := k.getGroupInfo(ctx, policyInfo.GroupId)
if err != nil { if err != nil {
return nil, sdkerrors.Wrap(err, "load group") 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 { if err := k.doTallyAndUpdate(ctx, &proposal, electorate, policyInfo); err != nil {
return nil, err return nil, err
} }
@ -805,7 +798,10 @@ func (k Keeper) Exec(goCtx context.Context, req *group.MsgExec) (*group.MsgExecR
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }
@ -983,8 +979,8 @@ func (k Keeper) validateDecisionPolicies(ctx sdk.Context, g group.GroupInfo) err
} }
defer it.Close() defer it.Close()
var groupPolicy group.GroupPolicyInfo
for { for {
var groupPolicy group.GroupPolicyInfo
_, err = it.LoadNext(&groupPolicy) _, err = it.LoadNext(&groupPolicy)
if errors.ErrORMIteratorDone.Is(err) { if errors.ErrORMIteratorDone.Is(err) {
break break

View File

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

View File

@ -9,4 +9,12 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) {
if err := k.UpdateTallyOfVPEndProposals(ctx); err != nil { if err := k.UpdateTallyOfVPEndProposals(ctx); err != nil {
panic(err) 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" "time"
"github.com/cosmos/cosmos-sdk/simapp" "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" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/cosmos/cosmos-sdk/x/group" "github.com/cosmos/cosmos-sdk/x/group"
"github.com/cosmos/cosmos-sdk/x/group/module" "github.com/cosmos/cosmos-sdk/x/group/module"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types" 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) { func TestEndBlocker(t *testing.T) {
app := simapp.Setup(t, false) app := simapp.Setup(t, false)
ctx := app.BaseApp.NewContext(false, tmproto.Header{}) 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 // Initial group, group policy and balance setup
members := []group.Member{ members := []group.Member{
@ -48,45 +226,44 @@ func TestEndBlocker(t *testing.T) {
policyRes, err := app.GroupKeeper.CreateGroupPolicy(ctx, policyReq) policyRes, err := app.GroupKeeper.CreateGroupPolicy(ctx, policyReq)
require.NoError(t, err) require.NoError(t, err)
groupPolicyAddr, err := types.AccAddressFromBech32(policyRes.Address) groupPolicyAddr, err := sdk.AccAddressFromBech32(policyRes.Address)
require.NoError(t, err) require.NoError(t, err)
votingPeriod := policy.GetVotingPeriod() votingPeriod := policy.GetVotingPeriod()
now := time.Now()
msgSend := &banktypes.MsgSend{ msgSend := &banktypes.MsgSend{
FromAddress: groupPolicyAddr.String(), FromAddress: groupPolicyAddr.String(),
ToAddress: addrs[3].String(), ToAddress: addrs[3].String(),
Amount: types.Coins{types.NewInt64Coin("test", 100)}, Amount: sdk.Coins{sdk.NewInt64Coin("test", 100)},
} }
proposers := []string{addrs[2].String()} proposers := []string{addrs[2].String()}
specs := map[string]struct { specs := map[string]struct {
preRun func(sdkCtx types.Context) uint64 preRun func(sdkCtx sdk.Context) uint64
proposalId uint64 proposalId uint64
admin string admin string
expErrMsg string expErrMsg string
newCtx types.Context newCtx sdk.Context
tallyRes group.TallyResult tallyRes group.TallyResult
expStatus group.ProposalStatus expStatus group.ProposalStatus
expExecutorResult group.ProposalResult expExecutorResult group.ProposalResult
}{ }{
"tally updated after voting power end": { "tally updated after voting power end": {
preRun: func(sdkCtx types.Context) uint64 { preRun: func(sdkCtx sdk.Context) uint64 {
pId, err := submitProposal(app, sdkCtx, []types.Msg{msgSend}, proposers, groupPolicyAddr) pId, err := submitProposal(app, sdkCtx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr)
require.NoError(t, err) require.NoError(t, err)
return pId return pId
}, },
admin: proposers[0], 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(), tallyRes: group.DefaultTallyResult(),
expStatus: group.PROPOSAL_STATUS_SUBMITTED, expStatus: group.PROPOSAL_STATUS_SUBMITTED,
expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED, expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED,
}, },
"tally within voting period": { "tally within voting period": {
preRun: func(sdkCtx types.Context) uint64 { preRun: func(sdkCtx sdk.Context) uint64 {
pId, err := submitProposal(app, sdkCtx, []types.Msg{msgSend}, proposers, groupPolicyAddr) pId, err := submitProposal(app, sdkCtx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr)
require.NoError(t, err) require.NoError(t, err)
return pId return pId
@ -98,8 +275,8 @@ func TestEndBlocker(t *testing.T) {
expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED, expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED,
}, },
"tally within voting period(with votes)": { "tally within voting period(with votes)": {
preRun: func(sdkCtx types.Context) uint64 { preRun: func(sdkCtx sdk.Context) uint64 {
pId, err := submitProposalAndVote(app, ctx, []types.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES) pId, err := submitProposalAndVote(app, ctx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
require.NoError(t, err) require.NoError(t, err)
return pId return pId
@ -111,14 +288,14 @@ func TestEndBlocker(t *testing.T) {
expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED, expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED,
}, },
"tally after voting period(with votes)": { "tally after voting period(with votes)": {
preRun: func(sdkCtx types.Context) uint64 { preRun: func(sdkCtx sdk.Context) uint64 {
pId, err := submitProposalAndVote(app, ctx, []types.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES) pId, err := submitProposalAndVote(app, ctx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
require.NoError(t, err) require.NoError(t, err)
return pId return pId
}, },
admin: proposers[0], 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{ tallyRes: group.TallyResult{
YesCount: "2", YesCount: "2",
NoCount: "0", NoCount: "0",
@ -129,8 +306,8 @@ func TestEndBlocker(t *testing.T) {
expExecutorResult: group.PROPOSAL_RESULT_ACCEPTED, expExecutorResult: group.PROPOSAL_RESULT_ACCEPTED,
}, },
"tally of closed proposal": { "tally of closed proposal": {
preRun: func(sdkCtx types.Context) uint64 { preRun: func(sdkCtx sdk.Context) uint64 {
pId, err := submitProposal(app, sdkCtx, []types.Msg{msgSend}, proposers, groupPolicyAddr) pId, err := submitProposal(app, sdkCtx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr)
require.NoError(t, err) require.NoError(t, err)
_, err = app.GroupKeeper.WithdrawProposal(ctx, &group.MsgWithdrawProposal{ _, err = app.GroupKeeper.WithdrawProposal(ctx, &group.MsgWithdrawProposal{
@ -148,8 +325,8 @@ func TestEndBlocker(t *testing.T) {
expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED, expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED,
}, },
"tally of closed proposal (with votes)": { "tally of closed proposal (with votes)": {
preRun: func(sdkCtx types.Context) uint64 { preRun: func(sdkCtx sdk.Context) uint64 {
pId, err := submitProposalAndVote(app, ctx, []types.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES) pId, err := submitProposalAndVote(app, ctx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES)
require.NoError(t, err) require.NoError(t, err)
_, err = app.GroupKeeper.WithdrawProposal(ctx, &group.MsgWithdrawProposal{ _, err = app.GroupKeeper.WithdrawProposal(ctx, &group.MsgWithdrawProposal{
@ -193,8 +370,8 @@ func TestEndBlocker(t *testing.T) {
} }
func submitProposal( func submitProposal(
app *simapp.SimApp, ctx context.Context, msgs []types.Msg, app *simapp.SimApp, ctx context.Context, msgs []sdk.Msg,
proposers []string, groupPolicyAddr types.AccAddress) (uint64, error) { proposers []string, groupPolicyAddr sdk.AccAddress) (uint64, error) {
proposalReq := &group.MsgSubmitProposal{ proposalReq := &group.MsgSubmitProposal{
Address: groupPolicyAddr.String(), Address: groupPolicyAddr.String(),
Proposers: proposers, Proposers: proposers,
@ -213,13 +390,12 @@ func submitProposal(
} }
func submitProposalAndVote( func submitProposalAndVote(
app *simapp.SimApp, ctx context.Context, msgs []types.Msg, app *simapp.SimApp, ctx context.Context, msgs []sdk.Msg,
proposers []string, groupPolicyAddr types.AccAddress, voteOption group.VoteOption) (uint64, error) { proposers []string, groupPolicyAddr sdk.AccAddress, voteOption group.VoteOption) (uint64, error) {
myProposalID, err := submitProposal(app, ctx, msgs, proposers, groupPolicyAddr) myProposalID, err := submitProposal(app, ctx, msgs, proposers, groupPolicyAddr)
if err != nil { if err != nil {
return 0, err return 0, err
} }
_, err = app.GroupKeeper.Vote(ctx, &group.MsgVote{ _, err = app.GroupKeeper.Vote(ctx, &group.MsgVote{
ProposalId: myProposalID, ProposalId: myProposalID,
Voter: proposers[0], Voter: proposers[0],
@ -228,6 +404,5 @@ func submitProposalAndVote(
if err != nil { if err != nil {
return 0, err return 0, err
} }
return myProposalID, nil 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 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) proposals := make([]*group.Proposal, 3)
proposers := []string{simState.Accounts[0].Address.String(), simState.Accounts[1].Address.String()} proposers := []string{simState.Accounts[0].Address.String(), simState.Accounts[1].Address.String()}
for i := 0; i < 3; i++ { 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) to, _ := simtypes.RandomAcc(r, simState.Accounts)
fromAddr := from.Address.String()
submittedAt := time.Unix(0, 0) submittedAt := time.Unix(0, 0)
timeout := submittedAt.Add(time.Second * 1000).UTC() timeout := submittedAt.Add(time.Second * 1000).UTC()
@ -86,7 +86,7 @@ func getProposals(r *rand.Rand, simState *module.SimulationState) []*group.Propo
proposal := &group.Proposal{ proposal := &group.Proposal{
Id: uint64(i + 1), Id: uint64(i + 1),
Proposers: proposers, Proposers: proposers,
Address: fromAddr, Address: groupPolicyAddress,
GroupVersion: uint64(i + 1), GroupVersion: uint64(i + 1),
GroupPolicyVersion: uint64(i + 1), GroupPolicyVersion: uint64(i + 1),
Status: group.PROPOSAL_STATUS_SUBMITTED, Status: group.PROPOSAL_STATUS_SUBMITTED,
@ -103,7 +103,7 @@ func getProposals(r *rand.Rand, simState *module.SimulationState) []*group.Propo
VotingPeriodEnd: timeout, VotingPeriodEnd: timeout,
} }
err := proposal.SetMsgs([]sdk.Msg{&banktypes.MsgSend{ err := proposal.SetMsgs([]sdk.Msg{&banktypes.MsgSend{
FromAddress: fromAddr, FromAddress: groupPolicyAddress,
ToAddress: to.Address.String(), ToAddress: to.Address.String(),
Amount: sdk.NewCoins(sdk.NewInt64Coin("test", 10)), 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) }, func(r *rand.Rand) { members = getGroupMembers(r, simState.Accounts) },
) )
// group accounts // group policies
var groupPolicies []*group.GroupPolicyInfo var groupPolicies []*group.GroupPolicyInfo
simState.AppParams.GetOrGenerate( simState.AppParams.GetOrGenerate(
simState.Cdc, GroupPolicyInfo, &groupPolicies, simState.Rand, simState.Cdc, GroupPolicyInfo, &groupPolicies, simState.Rand,
@ -174,7 +174,7 @@ func RandomizedGenState(simState *module.SimulationState) {
var proposals []*group.Proposal var proposals []*group.Proposal
simState.AppParams.GetOrGenerate( simState.AppParams.GetOrGenerate(
simState.Cdc, GroupProposals, &proposals, simState.Rand, simState.Cdc, GroupProposals, &proposals, simState.Rand,
func(r *rand.Rand) { proposals = getProposals(r, simState) }, func(r *rand.Rand) { proposals = getProposals(r, simState, groupPolicies) },
) )
// votes // votes

View File

@ -873,16 +873,6 @@ func SimulateMsgWithdrawProposal(ak group.AccountKeeper,
return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, "no proposals found"), nil, nil 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 // select a random proposer
proposers := proposal.Proposers proposers := proposal.Proposers
n := randIntInRange(r, len(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 return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "no proposals found"), nil, nil
} }
var proposal *group.Proposal
proposalID := -1 proposalID := -1
for _, p := range proposals { for _, p := range proposals {
if p.Status == group.PROPOSAL_STATUS_SUBMITTED { if p.Status == group.PROPOSAL_STATUS_SUBMITTED {
timeout := p.VotingPeriodEnd timeout := p.VotingPeriodEnd
proposal = p
proposalID = int(p.Id) proposalID = int(p.Id)
if timeout.Before(sdkCtx.BlockTime()) || timeout.Equal(sdkCtx.BlockTime()) { if timeout.Before(sdkCtx.BlockTime()) || timeout.Equal(sdkCtx.BlockTime()) {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "voting period ended: skipping"), nil, nil 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 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 // Ensure member hasn't already voted
res, _ := k.VoteByProposalVoter(ctx, &group.QueryVoteByProposalVoterRequest{ res, _ := k.VoteByProposalVoter(ctx, &group.QueryVoteByProposalVoterRequest{
Voter: acc.Address.String(), Voter: acc.Address.String(),

View File

@ -26,14 +26,19 @@ those "sub-accounts" using the `x/authz` module.
## Decision Policy ## Decision Policy
A decision policy is the mechanism by which members of a group can vote on A decision policy is the mechanism by which members of a group can vote on
proposals. 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. All decision policies generally would have a mininum execution perdio and a
The minimum voting window is the minimum amount of time that must pass in order maximum voting window. The minimum execution period is the minimum amount of time
for a proposal to potentially pass, and it may be set to 0. The maximum voting that must pass in order for a proposal to potentially be executed, and it may
window is the maximum time that a proposal may be voted on before it is closed. be set to 0. The maximum voting window is the maximum time that a proposal may
Both of these values must be less than a chain-wide max voting window parameter. 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 ### 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 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. 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 ## Proposal
Any member of a group can submit a proposal for a group policy account to decide upon. 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 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. 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 In the current implementation, the voting window begins as soon as a proposal
is submitted. 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 ## 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, 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 but rather a user must submit a `Msg/Exec` transaction to attempt to execute the
proposal based on the current votes and decision policy. proposal based on the current votes and decision policy.
It's also possible to try to execute a proposal immediately on creation or on 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. 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 For now, if the proposal can't be executed, it'll still be opened for new votes and
could be executed later on. 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) Proposals and votes are automatically pruned to avoid state bloat.
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 Votes are pruned:
eventually be garbage collected.
- 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: `proposalByGroupPolicyIndex` allows to retrieve proposals by group policy account address:
`0x32 | len([]byte(account.Address)) | []byte(account.Address) | BigEndian(ProposalId) -> []byte()`. `0x32 | len([]byte(account.Address)) | []byte(account.Address) | BigEndian(ProposalId) -> []byte()`.
### proposalByProposerIndex ### ProposalsByVotingPeriodEndIndex
`proposalByProposerIndex` allows to retrieve proposals by proposer address: `proposalsByVotingPeriodEndIndex` allows to retrieve proposals sorted by chronological `voting_period_end`:
`0x33 | len([]byte(proposer.Address)) | []byte(proposer.Address) | BigEndian(ProposalId) -> []byte()`. `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 ## 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 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 has not been accepted.
* the proposal status is not closed. * the proposal status is not closed.
* the proposal has already been successfully executed. * the proposal has already been successfully executed.