diff --git a/api/cosmos/group/v1/events.pulsar.go b/api/cosmos/group/v1/events.pulsar.go index c7d71f093..9678ef871 100644 --- a/api/cosmos/group/v1/events.pulsar.go +++ b/api/cosmos/group/v1/events.pulsar.go @@ -2876,12 +2876,14 @@ func (x *fastReflection_EventVote) ProtoMethods() *protoiface.Methods { var ( md_EventExec protoreflect.MessageDescriptor fd_EventExec_proposal_id protoreflect.FieldDescriptor + fd_EventExec_result protoreflect.FieldDescriptor ) func init() { file_cosmos_group_v1_events_proto_init() md_EventExec = File_cosmos_group_v1_events_proto.Messages().ByName("EventExec") fd_EventExec_proposal_id = md_EventExec.Fields().ByName("proposal_id") + fd_EventExec_result = md_EventExec.Fields().ByName("result") } var _ protoreflect.Message = (*fastReflection_EventExec)(nil) @@ -2955,6 +2957,12 @@ func (x *fastReflection_EventExec) Range(f func(protoreflect.FieldDescriptor, pr return } } + if x.Result != 0 { + value := protoreflect.ValueOfEnum((protoreflect.EnumNumber)(x.Result)) + if !f(fd_EventExec_result, value) { + return + } + } } // Has reports whether a field is populated. @@ -2972,6 +2980,8 @@ func (x *fastReflection_EventExec) Has(fd protoreflect.FieldDescriptor) bool { switch fd.FullName() { case "cosmos.group.v1.EventExec.proposal_id": return x.ProposalId != uint64(0) + case "cosmos.group.v1.EventExec.result": + return x.Result != 0 default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec")) @@ -2990,6 +3000,8 @@ func (x *fastReflection_EventExec) Clear(fd protoreflect.FieldDescriptor) { switch fd.FullName() { case "cosmos.group.v1.EventExec.proposal_id": x.ProposalId = uint64(0) + case "cosmos.group.v1.EventExec.result": + x.Result = 0 default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec")) @@ -3009,6 +3021,9 @@ func (x *fastReflection_EventExec) Get(descriptor protoreflect.FieldDescriptor) case "cosmos.group.v1.EventExec.proposal_id": value := x.ProposalId return protoreflect.ValueOfUint64(value) + case "cosmos.group.v1.EventExec.result": + value := x.Result + return protoreflect.ValueOfEnum((protoreflect.EnumNumber)(value)) default: if descriptor.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec")) @@ -3031,6 +3046,8 @@ func (x *fastReflection_EventExec) Set(fd protoreflect.FieldDescriptor, value pr switch fd.FullName() { case "cosmos.group.v1.EventExec.proposal_id": x.ProposalId = value.Uint() + case "cosmos.group.v1.EventExec.result": + x.Result = (ProposalExecutorResult)(value.Enum()) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec")) @@ -3053,6 +3070,8 @@ func (x *fastReflection_EventExec) Mutable(fd protoreflect.FieldDescriptor) prot switch fd.FullName() { case "cosmos.group.v1.EventExec.proposal_id": panic(fmt.Errorf("field proposal_id of message cosmos.group.v1.EventExec is not mutable")) + case "cosmos.group.v1.EventExec.result": + panic(fmt.Errorf("field result of message cosmos.group.v1.EventExec is not mutable")) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec")) @@ -3068,6 +3087,8 @@ func (x *fastReflection_EventExec) NewField(fd protoreflect.FieldDescriptor) pro switch fd.FullName() { case "cosmos.group.v1.EventExec.proposal_id": return protoreflect.ValueOfUint64(uint64(0)) + case "cosmos.group.v1.EventExec.result": + return protoreflect.ValueOfEnum(0) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.group.v1.EventExec")) @@ -3140,6 +3161,9 @@ func (x *fastReflection_EventExec) ProtoMethods() *protoiface.Methods { if x.ProposalId != 0 { n += 1 + runtime.Sov(uint64(x.ProposalId)) } + if x.Result != 0 { + n += 1 + runtime.Sov(uint64(x.Result)) + } if x.unknownFields != nil { n += len(x.unknownFields) } @@ -3169,6 +3193,11 @@ func (x *fastReflection_EventExec) ProtoMethods() *protoiface.Methods { i -= len(x.unknownFields) copy(dAtA[i:], x.unknownFields) } + if x.Result != 0 { + i = runtime.EncodeVarint(dAtA, i, uint64(x.Result)) + i-- + dAtA[i] = 0x10 + } if x.ProposalId != 0 { i = runtime.EncodeVarint(dAtA, i, uint64(x.ProposalId)) i-- @@ -3242,6 +3271,25 @@ func (x *fastReflection_EventExec) ProtoMethods() *protoiface.Methods { break } } + case 2: + if wireType != 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field Result", wireType) + } + x.Result = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + x.Result |= ProposalExecutorResult(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := runtime.Skip(dAtA[iNdEx:]) @@ -4025,6 +4073,8 @@ type EventExec struct { // proposal_id is the unique ID of the proposal. ProposalId uint64 `protobuf:"varint,1,opt,name=proposal_id,json=proposalId,proto3" json:"proposal_id,omitempty"` + // result is the proposal execution result. + Result ProposalExecutorResult `protobuf:"varint,2,opt,name=result,proto3,enum=cosmos.group.v1.ProposalExecutorResult" json:"result,omitempty"` } func (x *EventExec) Reset() { @@ -4054,6 +4104,13 @@ func (x *EventExec) GetProposalId() uint64 { return 0 } +func (x *EventExec) GetResult() ProposalExecutorResult { + if x != nil { + return x.Result + } + return ProposalExecutorResult_PROPOSAL_EXECUTOR_RESULT_UNSPECIFIED +} + // EventLeaveGroup is an event emitted when group member leaves the group. type EventLeaveGroup struct { state protoimpl.MessageState @@ -4107,54 +4164,60 @@ var file_cosmos_group_v1_events_proto_rawDesc = []byte{ 0x31, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x76, 0x31, 0x1a, 0x19, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, - 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x2d, 0x0a, 0x10, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x19, - 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x22, 0x2d, 0x0a, 0x10, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x19, 0x0a, - 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x22, 0x4c, 0x0a, 0x16, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x50, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x12, 0x32, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, - 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x4c, 0x0a, 0x16, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x12, 0x32, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x22, 0x36, 0x0a, 0x13, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, - 0x6d, 0x69, 0x74, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, - 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x49, 0x64, 0x22, 0x38, 0x0a, 0x15, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x50, 0x72, 0x6f, - 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, - 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, - 0x6f, 0x73, 0x61, 0x6c, 0x49, 0x64, 0x22, 0x2c, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x56, - 0x6f, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, - 0x61, 0x6c, 0x49, 0x64, 0x22, 0x2c, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x45, 0x78, 0x65, - 0x63, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, - 0x49, 0x64, 0x22, 0x60, 0x0a, 0x0f, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4c, 0x65, 0x61, 0x76, 0x65, - 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, - 0x12, 0x32, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x42, 0xba, 0x01, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x73, - 0x6d, 0x6f, 0x73, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x76, 0x31, 0x42, 0x0b, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x38, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x63, - 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2d, 0x73, 0x64, 0x6b, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, - 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2f, 0x76, 0x31, 0x3b, 0x67, 0x72, - 0x6f, 0x75, 0x70, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x47, 0x58, 0xaa, 0x02, 0x0f, 0x43, 0x6f, - 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0f, - 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x5c, 0x56, 0x31, 0xe2, - 0x02, 0x1b, 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x5c, 0x56, - 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x11, - 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x3a, 0x3a, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x3a, 0x3a, 0x56, - 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x63, 0x6f, 0x73, 0x6d, + 0x6f, 0x73, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x2d, 0x0a, 0x10, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x67, + 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x67, + 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x22, 0x2d, 0x0a, 0x10, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, + 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x67, 0x72, + 0x6f, 0x75, 0x70, 0x49, 0x64, 0x22, 0x4c, 0x0a, 0x16, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, + 0x32, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x22, 0x4c, 0x0a, 0x16, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x32, 0x0a, + 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, + 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x22, 0x36, 0x0a, 0x13, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, + 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, + 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, + 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x49, 0x64, 0x22, 0x38, 0x0a, 0x15, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, + 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, + 0x6c, 0x49, 0x64, 0x22, 0x2c, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x56, 0x6f, 0x74, 0x65, + 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x49, + 0x64, 0x22, 0x6d, 0x0a, 0x09, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x45, 0x78, 0x65, 0x63, 0x12, 0x1f, + 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x49, 0x64, 0x12, + 0x3f, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x27, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x76, + 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, + 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x22, 0x60, 0x0a, 0x0f, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x47, 0x72, + 0x6f, 0x75, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x32, + 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x42, 0xba, 0x01, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, + 0x73, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x76, 0x31, 0x42, 0x0b, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x63, 0x6f, 0x73, + 0x6d, 0x6f, 0x73, 0x2d, 0x73, 0x64, 0x6b, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x73, 0x6d, + 0x6f, 0x73, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2f, 0x76, 0x31, 0x3b, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x47, 0x58, 0xaa, 0x02, 0x0f, 0x43, 0x6f, 0x73, 0x6d, + 0x6f, 0x73, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0f, 0x43, 0x6f, + 0x73, 0x6d, 0x6f, 0x73, 0x5c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x1b, + 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x5c, 0x56, 0x31, 0x5c, + 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x11, 0x43, 0x6f, + 0x73, 0x6d, 0x6f, 0x73, 0x3a, 0x3a, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x3a, 0x3a, 0x56, 0x31, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -4180,13 +4243,15 @@ var file_cosmos_group_v1_events_proto_goTypes = []interface{}{ (*EventVote)(nil), // 6: cosmos.group.v1.EventVote (*EventExec)(nil), // 7: cosmos.group.v1.EventExec (*EventLeaveGroup)(nil), // 8: cosmos.group.v1.EventLeaveGroup + (ProposalExecutorResult)(0), // 9: cosmos.group.v1.ProposalExecutorResult } var file_cosmos_group_v1_events_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 9, // 0: cosmos.group.v1.EventExec.result:type_name -> cosmos.group.v1.ProposalExecutorResult + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name } func init() { file_cosmos_group_v1_events_proto_init() } @@ -4194,6 +4259,7 @@ func file_cosmos_group_v1_events_proto_init() { if File_cosmos_group_v1_events_proto != nil { return } + file_cosmos_group_v1_types_proto_init() if !protoimpl.UnsafeEnabled { file_cosmos_group_v1_events_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*EventCreateGroup); i { diff --git a/proto/cosmos/group/v1/events.proto b/proto/cosmos/group/v1/events.proto index fbf096b40..e8907243a 100644 --- a/proto/cosmos/group/v1/events.proto +++ b/proto/cosmos/group/v1/events.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package cosmos.group.v1; import "cosmos_proto/cosmos.proto"; +import "cosmos/group/v1/types.proto"; option go_package = "github.com/cosmos/cosmos-sdk/x/group"; @@ -60,6 +61,9 @@ message EventExec { // proposal_id is the unique ID of the proposal. uint64 proposal_id = 1; + + // result is the proposal execution result. + ProposalExecutorResult result = 2; } // EventLeaveGroup is an event emitted when group member leaves the group. diff --git a/x/group/events.pb.go b/x/group/events.pb.go index 48c53f85f..0d13f3470 100644 --- a/x/group/events.pb.go +++ b/x/group/events.pb.go @@ -349,6 +349,8 @@ func (m *EventVote) GetProposalId() uint64 { type EventExec struct { // proposal_id is the unique ID of the proposal. ProposalId uint64 `protobuf:"varint,1,opt,name=proposal_id,json=proposalId,proto3" json:"proposal_id,omitempty"` + // result is the proposal execution result. + Result ProposalExecutorResult `protobuf:"varint,2,opt,name=result,proto3,enum=cosmos.group.v1.ProposalExecutorResult" json:"result,omitempty"` } func (m *EventExec) Reset() { *m = EventExec{} } @@ -391,6 +393,13 @@ func (m *EventExec) GetProposalId() uint64 { return 0 } +func (m *EventExec) GetResult() ProposalExecutorResult { + if m != nil { + return m.Result + } + return PROPOSAL_EXECUTOR_RESULT_UNSPECIFIED +} + // EventLeaveGroup is an event emitted when group member leaves the group. type EventLeaveGroup struct { // group_id is the unique ID of the group. @@ -461,27 +470,30 @@ func init() { func init() { proto.RegisterFile("cosmos/group/v1/events.proto", fileDescriptor_e8d753981546f032) } var fileDescriptor_e8d753981546f032 = []byte{ - // 314 bytes of a gzipped FileDescriptorProto + // 366 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x49, 0xce, 0x2f, 0xce, 0xcd, 0x2f, 0xd6, 0x4f, 0x2f, 0xca, 0x2f, 0x2d, 0xd0, 0x2f, 0x33, 0xd4, 0x4f, 0x2d, 0x4b, 0xcd, 0x2b, 0x29, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x87, 0xc8, 0xea, 0x81, 0x65, 0xf5, - 0xca, 0x0c, 0xa5, 0x24, 0x21, 0x02, 0xf1, 0x60, 0x69, 0x7d, 0xa8, 0x2c, 0x98, 0xa3, 0xa4, 0xcb, - 0x25, 0xe0, 0x0a, 0xd2, 0xeb, 0x5c, 0x94, 0x9a, 0x58, 0x92, 0xea, 0x0e, 0xd2, 0x21, 0x24, 0xc9, - 0xc5, 0x01, 0xd6, 0x1a, 0x9f, 0x99, 0x22, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x12, 0xc4, 0x0e, 0xe6, - 0x7b, 0xa6, 0xc0, 0x95, 0x87, 0x16, 0xa4, 0x10, 0xa3, 0xdc, 0x87, 0x4b, 0x0c, 0xdd, 0xf4, 0x80, - 0xfc, 0x9c, 0xcc, 0xe4, 0x4a, 0x21, 0x23, 0x2e, 0xf6, 0xc4, 0x94, 0x94, 0xa2, 0xd4, 0xe2, 0x62, - 0xb0, 0x1e, 0x4e, 0x27, 0x89, 0x4b, 0x5b, 0x74, 0x45, 0xa0, 0x4e, 0x73, 0x84, 0xc8, 0x04, 0x97, - 0x14, 0x65, 0xe6, 0xa5, 0x07, 0xc1, 0x14, 0xc2, 0x4d, 0x43, 0xb2, 0x9c, 0x02, 0xd3, 0xcc, 0xb8, - 0x84, 0xc1, 0xa6, 0x05, 0x97, 0x26, 0xe5, 0x66, 0x96, 0x04, 0x14, 0xe5, 0x17, 0xe4, 0x17, 0x27, - 0xe6, 0x08, 0xc9, 0x73, 0x71, 0x17, 0x40, 0xd9, 0x08, 0x0f, 0x71, 0xc1, 0x84, 0x3c, 0x53, 0x94, - 0x2c, 0xb8, 0x44, 0xc1, 0xfa, 0xc2, 0x33, 0x4b, 0x32, 0x52, 0x8a, 0x12, 0xcb, 0x89, 0xd7, 0xa9, - 0xc3, 0xc5, 0x09, 0xd6, 0x19, 0x96, 0x5f, 0x92, 0x4a, 0xbc, 0x6a, 0xd7, 0x8a, 0xd4, 0x64, 0xc2, - 0xaa, 0x13, 0xb8, 0xf8, 0xc1, 0xaa, 0x7d, 0x52, 0x13, 0xcb, 0x08, 0xc6, 0x0b, 0x72, 0x78, 0x31, - 0x11, 0x19, 0x5e, 0x4e, 0x76, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, - 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0xa5, - 0x92, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0x0b, 0x4d, 0x5c, 0x50, 0x4a, 0xb7, - 0x38, 0x25, 0x5b, 0xbf, 0x02, 0x92, 0x4a, 0x93, 0xd8, 0xc0, 0x09, 0xce, 0x18, 0x10, 0x00, 0x00, - 0xff, 0xff, 0xa2, 0x42, 0x09, 0x69, 0xbc, 0x02, 0x00, 0x00, + 0xca, 0x0c, 0xa5, 0x24, 0x21, 0x02, 0xf1, 0x60, 0x69, 0x7d, 0xa8, 0x2c, 0x98, 0x23, 0x25, 0x8d, + 0x6e, 0x52, 0x49, 0x65, 0x41, 0x2a, 0x54, 0x52, 0x49, 0x97, 0x4b, 0xc0, 0x15, 0x64, 0xb0, 0x73, + 0x51, 0x6a, 0x62, 0x49, 0xaa, 0x3b, 0x48, 0x89, 0x90, 0x24, 0x17, 0x07, 0x58, 0x6d, 0x7c, 0x66, + 0x8a, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x4b, 0x10, 0x3b, 0x98, 0xef, 0x99, 0x02, 0x57, 0x1e, 0x5a, + 0x90, 0x42, 0x8c, 0x72, 0x1f, 0x2e, 0x31, 0x74, 0xd3, 0x03, 0xf2, 0x73, 0x32, 0x93, 0x2b, 0x85, + 0x8c, 0xb8, 0xd8, 0x13, 0x53, 0x52, 0x8a, 0x52, 0x8b, 0x8b, 0xc1, 0x7a, 0x38, 0x9d, 0x24, 0x2e, + 0x6d, 0xd1, 0x15, 0x81, 0xba, 0xdb, 0x11, 0x22, 0x13, 0x5c, 0x52, 0x94, 0x99, 0x97, 0x1e, 0x04, + 0x53, 0x08, 0x37, 0x0d, 0xc9, 0x72, 0x0a, 0x4c, 0x33, 0xe3, 0x12, 0x06, 0x9b, 0x16, 0x5c, 0x9a, + 0x94, 0x9b, 0x59, 0x12, 0x50, 0x94, 0x5f, 0x90, 0x5f, 0x9c, 0x98, 0x23, 0x24, 0xcf, 0xc5, 0x5d, + 0x00, 0x65, 0x23, 0x3c, 0xc4, 0x05, 0x13, 0xf2, 0x4c, 0x51, 0xb2, 0xe0, 0x12, 0x05, 0xeb, 0x0b, + 0xcf, 0x2c, 0xc9, 0x48, 0x29, 0x4a, 0x2c, 0x27, 0x5e, 0xa7, 0x0e, 0x17, 0x27, 0x58, 0x67, 0x58, + 0x7e, 0x49, 0x2a, 0x61, 0xd5, 0xb9, 0x50, 0xd5, 0xae, 0x15, 0xa9, 0xc9, 0x04, 0x55, 0x0b, 0xd9, + 0x73, 0xb1, 0x15, 0xa5, 0x16, 0x97, 0xe6, 0x94, 0x48, 0x30, 0x29, 0x30, 0x6a, 0xf0, 0x19, 0xa9, + 0xeb, 0xa1, 0xa5, 0x10, 0x3d, 0x98, 0x3b, 0x41, 0xe6, 0x95, 0x96, 0xe4, 0x17, 0x05, 0x81, 0x95, + 0x07, 0x41, 0xb5, 0x29, 0x25, 0x70, 0xf1, 0x83, 0xad, 0xf3, 0x49, 0x4d, 0x2c, 0x23, 0x18, 0xb1, + 0xc8, 0x01, 0xce, 0x44, 0x64, 0x80, 0x3b, 0xd9, 0x9d, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, + 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x13, 0x1e, 0xcb, 0x31, 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, + 0x1c, 0x43, 0x94, 0x4a, 0x7a, 0x66, 0x49, 0x46, 0x69, 0x92, 0x5e, 0x72, 0x7e, 0x2e, 0x34, 0xe9, + 0x42, 0x29, 0xdd, 0xe2, 0x94, 0x6c, 0xfd, 0x0a, 0x48, 0xca, 0x4d, 0x62, 0x03, 0xa7, 0x58, 0x63, + 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe6, 0x94, 0x89, 0xaf, 0x1a, 0x03, 0x00, 0x00, } func (m *EventCreateGroup) Marshal() (dAtA []byte, err error) { @@ -704,6 +716,11 @@ func (m *EventExec) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.Result != 0 { + i = encodeVarintEvents(dAtA, i, uint64(m.Result)) + i-- + dAtA[i] = 0x10 + } if m.ProposalId != 0 { i = encodeVarintEvents(dAtA, i, uint64(m.ProposalId)) i-- @@ -853,6 +870,9 @@ func (m *EventExec) Size() (n int) { if m.ProposalId != 0 { n += 1 + sovEvents(uint64(m.ProposalId)) } + if m.Result != 0 { + n += 1 + sovEvents(uint64(m.Result)) + } return n } @@ -1435,6 +1455,25 @@ func (m *EventExec) Unmarshal(dAtA []byte) error { break } } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Result", wireType) + } + m.Result = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Result |= ProposalExecutorResult(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipEvents(dAtA[iNdEx:]) diff --git a/x/group/keeper/invariants.go b/x/group/keeper/invariants.go index 1d222e91d..15213fb3f 100644 --- a/x/group/keeper/invariants.go +++ b/x/group/keeper/invariants.go @@ -20,24 +20,10 @@ const ( // RegisterInvariants registers all group invariants func RegisterInvariants(ir sdk.InvariantRegistry, keeper Keeper) { - ir.RegisterRoute(group.ModuleName, votesInvariant, TallyVotesInvariant(keeper)) ir.RegisterRoute(group.ModuleName, weightInvariant, GroupTotalWeightInvariant(keeper)) ir.RegisterRoute(group.ModuleName, votesSumInvariant, TallyVotesSumInvariant(keeper)) } -// TallyVotesInvariant checks that vote tally sums must never have less than the block before. -func TallyVotesInvariant(keeper Keeper) sdk.Invariant { - return func(ctx sdk.Context) (string, bool) { - if ctx.BlockHeight()-1 < 0 { - return sdk.FormatInvariant(group.ModuleName, votesInvariant, "Not enough blocks to perform TallyVotesInvariant"), false - } - prevCtx, _ := ctx.CacheContext() - prevCtx = prevCtx.WithBlockHeight(ctx.BlockHeight() - 1) - msg, broken := TallyVotesInvariantHelper(ctx, prevCtx, keeper.key, keeper.proposalTable) - return sdk.FormatInvariant(group.ModuleName, votesInvariant, msg), broken - } -} - // GroupTotalWeightInvariant checks that group's TotalWeight must be equal to the sum of its members. func GroupTotalWeightInvariant(keeper Keeper) sdk.Invariant { return func(ctx sdk.Context) (string, bool) { @@ -46,105 +32,20 @@ func GroupTotalWeightInvariant(keeper Keeper) sdk.Invariant { } } -// TallyVotesSumInvariant checks that proposal FinalTallyResult must correspond to the vote option. +// TallyVotesSumInvariant checks that proposal FinalTallyResult must correspond to the vote option, +// for proposals with PROPOSAL_STATUS_CLOSED status. func TallyVotesSumInvariant(keeper Keeper) sdk.Invariant { return func(ctx sdk.Context) (string, bool) { - msg, broken := TallyVotesSumInvariantHelper(ctx, keeper.key, keeper.groupTable, keeper.proposalTable, keeper.groupMemberTable, keeper.voteByProposalIndex, keeper.groupPolicyTable) + msg, broken := TallyVotesSumInvariantHelper(ctx, keeper.key, keeper.proposalTable, keeper.groupMemberTable, keeper.voteByProposalIndex, keeper.groupPolicyTable) return sdk.FormatInvariant(group.ModuleName, votesSumInvariant, msg), broken } } -func TallyVotesInvariantHelper(ctx sdk.Context, prevCtx sdk.Context, key storetypes.StoreKey, proposalTable orm.AutoUInt64Table) (string, bool) { - - var msg string - var broken bool - - prevIt, err := proposalTable.PrefixScan(prevCtx.KVStore(key), 1, math.MaxUint64) - if err != nil { - msg += fmt.Sprintf("PrefixScan failure on proposal table at block height %d\n%v\n", prevCtx.BlockHeight(), err) - return msg, broken - } - - curIt, err := proposalTable.PrefixScan(ctx.KVStore(key), 1, math.MaxUint64) - if err != nil { - msg += fmt.Sprintf("PrefixScan failure on proposal table at block height %d\n%v\n", ctx.BlockHeight(), err) - return msg, broken - } - - var curProposals []*group.Proposal - _, err = orm.ReadAll(curIt, &curProposals) - if err != nil { - msg += fmt.Sprintf("error while getting all the proposals at block height %d\n%v\n", ctx.BlockHeight(), err) - return msg, broken - } - - var prevProposals []*group.Proposal - _, err = orm.ReadAll(prevIt, &prevProposals) - if err != nil { - msg += fmt.Sprintf("error while getting all the proposals at block height %d\n%v\n", prevCtx.BlockHeight(), err) - return msg, broken - } - - for i := 0; i < len(prevProposals); i++ { - if prevProposals[i].Id == curProposals[i].Id { - prevYesCount, err := prevProposals[i].FinalTallyResult.GetYesCount() - if err != nil { - msg += fmt.Sprintf("error while getting yes votes weight of proposal at block height %d\n%v\n", prevCtx.BlockHeight(), err) - return msg, broken - } - curYesCount, err := curProposals[i].FinalTallyResult.GetYesCount() - if err != nil { - msg += fmt.Sprintf("error while getting yes votes weight of proposal at block height %d\n%v\n", ctx.BlockHeight(), err) - return msg, broken - } - prevNoCount, err := prevProposals[i].FinalTallyResult.GetNoCount() - if err != nil { - msg += fmt.Sprintf("error while getting no votes weight of proposal at block height %d\n%v\n", prevCtx.BlockHeight(), err) - return msg, broken - } - curNoCount, err := curProposals[i].FinalTallyResult.GetNoCount() - if err != nil { - msg += fmt.Sprintf("error while getting no votes weight of proposal at block height %d\n%v\n", ctx.BlockHeight(), err) - return msg, broken - } - prevAbstainCount, err := prevProposals[i].FinalTallyResult.GetAbstainCount() - if err != nil { - msg += fmt.Sprintf("error while getting abstain votes weight of proposal at block height %d\n%v\n", prevCtx.BlockHeight(), err) - return msg, broken - } - curAbstainCount, err := curProposals[i].FinalTallyResult.GetAbstainCount() - if err != nil { - msg += fmt.Sprintf("error while getting abstain votes weight of proposal at block height %d\n%v\n", ctx.BlockHeight(), err) - return msg, broken - } - prevVetoCount, err := prevProposals[i].FinalTallyResult.GetNoWithVetoCount() - if err != nil { - msg += fmt.Sprintf("error while getting veto votes weight of proposal at block height %d\n%v\n", prevCtx.BlockHeight(), err) - return msg, broken - } - curVetoCount, err := curProposals[i].FinalTallyResult.GetNoWithVetoCount() - if err != nil { - msg += fmt.Sprintf("error while getting veto votes weight of proposal at block height %d\n%v\n", ctx.BlockHeight(), err) - return msg, broken - } - if (curYesCount.Cmp(prevYesCount) == -1) || (curNoCount.Cmp(prevNoCount) == -1) || (curAbstainCount.Cmp(prevAbstainCount) == -1) || (curVetoCount.Cmp(prevVetoCount) == -1) { - broken = true - msg += "vote tally sums must never have less than the block before\n" - return msg, broken - } - } - } - return msg, broken -} - func GroupTotalWeightInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, groupTable orm.AutoUInt64Table, groupMemberByGroupIndex orm.Index) (string, bool) { var msg string var broken bool - var groupInfo group.GroupInfo - var groupMember group.GroupMember - groupIt, err := groupTable.PrefixScan(ctx.KVStore(key), 1, math.MaxUint64) if err != nil { msg += fmt.Sprintf("PrefixScan failure on group table\n%v\n", err) @@ -158,10 +59,16 @@ func GroupTotalWeightInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, g msg += fmt.Sprintf("error while parsing positive dec zero for group member\n%v\n", err) return msg, broken } + var groupInfo group.GroupInfo _, err = groupIt.LoadNext(&groupInfo) if errors.ErrORMIteratorDone.Is(err) { break } + if err != nil { + msg += fmt.Sprintf("LoadNext failure on group table iterator\n%v\n", err) + return msg, broken + } + memIt, err := groupMemberByGroupIndex.Get(ctx.KVStore(key), groupInfo.Id) if err != nil { msg += fmt.Sprintf("error while returning group member iterator for group with ID %d\n%v\n", groupInfo.Id, err) @@ -170,10 +77,16 @@ func GroupTotalWeightInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, g defer memIt.Close() for { + var groupMember group.GroupMember _, err = memIt.LoadNext(&groupMember) if errors.ErrORMIteratorDone.Is(err) { break } + if err != nil { + msg += fmt.Sprintf("LoadNext failure on member table iterator\n%v\n", err) + return msg, broken + } + curMemWeight, err := groupmath.NewNonNegativeDecFromString(groupMember.GetMember().GetWeight()) if err != nil { msg += fmt.Sprintf("error while parsing non-nengative decimal for group member %s\n%v\n", groupMember.Member.Address, err) @@ -201,25 +114,32 @@ func GroupTotalWeightInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, g return msg, broken } -func TallyVotesSumInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, groupTable orm.AutoUInt64Table, proposalTable orm.AutoUInt64Table, groupMemberTable orm.PrimaryKeyTable, voteByProposalIndex orm.Index, groupPolicyTable orm.PrimaryKeyTable) (string, bool) { +func TallyVotesSumInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, proposalTable orm.AutoUInt64Table, groupMemberTable orm.PrimaryKeyTable, voteByProposalIndex orm.Index, groupPolicyTable orm.PrimaryKeyTable) (string, bool) { var msg string var broken bool - var groupInfo group.GroupInfo - var proposal group.Proposal - var groupPolicy group.GroupPolicyInfo - var groupMem group.GroupMember - var vote group.Vote - proposalIt, err := proposalTable.PrefixScan(ctx.KVStore(key), 1, math.MaxUint64) if err != nil { - fmt.Println(err) msg += fmt.Sprintf("PrefixScan failure on proposal table\n%v\n", err) return msg, broken } defer proposalIt.Close() for { + var proposal group.Proposal + _, err = proposalIt.LoadNext(&proposal) + if errors.ErrORMIteratorDone.Is(err) { + break + } + if err != nil { + msg += fmt.Sprintf("LoadNext failure on proposal table iterator\n%v\n", err) + return msg, broken + } + + // Only look at proposals that are closed, i.e. for which FinalTallyResult has been computed + if proposal.Status != group.PROPOSAL_STATUS_CLOSED { + continue + } totalVotingWeight, err := groupmath.NewNonNegativeDecFromString("0") if err != nil { @@ -247,11 +167,7 @@ func TallyVotesSumInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, grou return msg, broken } - _, err = proposalIt.LoadNext(&proposal) - if errors.ErrORMIteratorDone.Is(err) { - break - } - + var groupPolicy group.GroupPolicyInfo err = groupPolicyTable.GetOne(ctx.KVStore(key), orm.PrimaryKey(&group.GroupPolicyInfo{Address: proposal.Address}), &groupPolicy) if err != nil { msg += fmt.Sprintf("group policy not found for address: %s\n%v\n", proposal.Address, err) @@ -263,29 +179,25 @@ func TallyVotesSumInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, grou return msg, broken } - _, err = groupTable.GetOne(ctx.KVStore(key), groupPolicy.GroupId, &groupInfo) - if err != nil { - msg += fmt.Sprintf("group info not found for group id %d\n%v\n", groupPolicy.GroupId, err) - return msg, broken - } - - if groupInfo.Version != proposal.GroupVersion { - msg += fmt.Sprintf("group with id %d was modified\n", groupInfo.Id) - return msg, broken - } - voteIt, err := voteByProposalIndex.Get(ctx.KVStore(key), proposal.Id) if err != nil { msg += fmt.Sprintf("error while returning vote iterator for proposal with ID %d\n%v\n", proposal.Id, err) return msg, broken } + defer voteIt.Close() for { + var vote group.Vote _, err := voteIt.LoadNext(&vote) if errors.ErrORMIteratorDone.Is(err) { break } + if err != nil { + msg += fmt.Sprintf("LoadNext failure on voteByProposalIndex index iterator\n%v\n", err) + return msg, broken + } + var groupMem group.GroupMember err = groupMemberTable.GetOne(ctx.KVStore(key), orm.PrimaryKey(&group.GroupMember{GroupId: groupPolicy.GroupId, Member: &group.Member{Address: vote.Voter}}), &groupMem) if err != nil { msg += fmt.Sprintf("group member not found with group ID %d and group member %s\n%v\n", groupPolicy.GroupId, vote.Voter, err) @@ -330,7 +242,6 @@ func TallyVotesSumInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, grou } } } - voteIt.Close() totalProposalVotes, err := proposal.FinalTallyResult.TotalCounts() if err != nil { @@ -366,7 +277,7 @@ func TallyVotesSumInvariantHelper(ctx sdk.Context, key storetypes.StoreKey, grou if (yesVoteWeight.Cmp(proposalYesCount) != 0) || (noVoteWeight.Cmp(proposalNoCount) != 0) || (abstainVoteWeight.Cmp(proposalAbstainCount) != 0) || (vetoVoteWeight.Cmp(proposalVetoCount) != 0) { broken = true - msg += fmt.Sprintf("proposal FinalTallyResult must correspond to the vote option\nProposal with ID %d and voter address %s must correspond to the vote option\n", proposal.Id, vote.Voter) + msg += fmt.Sprintf("proposal FinalTallyResult must correspond to the sum of all votes\nProposal with ID %d FinalTallyResult must correspond to the sum of all votes\n", proposal.Id) break } } diff --git a/x/group/keeper/invariants_test.go b/x/group/keeper/invariants_test.go index 11c3a5f94..308d2b6a1 100644 --- a/x/group/keeper/invariants_test.go +++ b/x/group/keeper/invariants_test.go @@ -51,189 +51,6 @@ func (s *invariantTestSuite) SetupSuite() { } -func (s *invariantTestSuite) TestTallyVotesInvariant() { - sdkCtx, _ := s.ctx.CacheContext() - curCtx, cdc, key := sdkCtx, s.cdc, s.key - prevCtx, _ := curCtx.CacheContext() - prevCtx = prevCtx.WithBlockHeight(curCtx.BlockHeight() - 1) - - // Proposal Table - proposalTable, err := orm.NewAutoUInt64Table([2]byte{keeper.ProposalTablePrefix}, keeper.ProposalTableSeqPrefix, &group.Proposal{}, cdc) - s.Require().NoError(err) - - _, _, addr1 := testdata.KeyTestPubAddr() - _, _, addr2 := testdata.KeyTestPubAddr() - - specs := map[string]struct { - prevProposal *group.Proposal - curProposal *group.Proposal - expBroken bool - }{ - "invariant not broken": { - prevProposal: &group.Proposal{ - Id: 1, - Address: addr1.String(), - Proposers: []string{addr1.String()}, - SubmitTime: prevCtx.BlockTime(), - GroupVersion: 1, - GroupPolicyVersion: 1, - Status: group.PROPOSAL_STATUS_SUBMITTED, - Result: group.PROPOSAL_RESULT_UNFINALIZED, - FinalTallyResult: group.TallyResult{YesCount: "1", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"}, - VotingPeriodEnd: prevCtx.BlockTime().Add(time.Second * 600), - ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, - }, - - curProposal: &group.Proposal{ - Id: 1, - Address: addr2.String(), - Proposers: []string{addr2.String()}, - SubmitTime: curCtx.BlockTime(), - GroupVersion: 1, - GroupPolicyVersion: 1, - Status: group.PROPOSAL_STATUS_SUBMITTED, - Result: group.PROPOSAL_RESULT_UNFINALIZED, - FinalTallyResult: group.TallyResult{YesCount: "2", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"}, - VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600), - ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, - }, - }, - "current block yes vote count must be greater than previous block yes vote count": { - prevProposal: &group.Proposal{ - Id: 1, - Address: addr1.String(), - Proposers: []string{addr1.String()}, - SubmitTime: prevCtx.BlockTime(), - GroupVersion: 1, - GroupPolicyVersion: 1, - Status: group.PROPOSAL_STATUS_SUBMITTED, - Result: group.PROPOSAL_RESULT_UNFINALIZED, - FinalTallyResult: group.TallyResult{YesCount: "2", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"}, - VotingPeriodEnd: prevCtx.BlockTime().Add(time.Second * 600), - ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, - }, - curProposal: &group.Proposal{ - Id: 1, - Address: addr2.String(), - Proposers: []string{addr2.String()}, - SubmitTime: curCtx.BlockTime(), - GroupVersion: 1, - GroupPolicyVersion: 1, - Status: group.PROPOSAL_STATUS_SUBMITTED, - Result: group.PROPOSAL_RESULT_UNFINALIZED, - FinalTallyResult: group.TallyResult{YesCount: "1", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"}, - VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600), - ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, - }, - expBroken: true, - }, - "current block no vote count must be greater than previous block no vote count": { - prevProposal: &group.Proposal{ - Id: 1, - Address: addr1.String(), - Proposers: []string{addr1.String()}, - SubmitTime: prevCtx.BlockTime(), - GroupVersion: 1, - GroupPolicyVersion: 1, - Status: group.PROPOSAL_STATUS_SUBMITTED, - Result: group.PROPOSAL_RESULT_UNFINALIZED, - FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "2", AbstainCount: "0", NoWithVetoCount: "0"}, - VotingPeriodEnd: prevCtx.BlockTime().Add(time.Second * 600), - ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, - }, - curProposal: &group.Proposal{ - Id: 1, - Address: addr2.String(), - Proposers: []string{addr2.String()}, - SubmitTime: curCtx.BlockTime(), - GroupVersion: 1, - GroupPolicyVersion: 1, - Status: group.PROPOSAL_STATUS_SUBMITTED, - Result: group.PROPOSAL_RESULT_UNFINALIZED, - FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "1", AbstainCount: "0", NoWithVetoCount: "0"}, - VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600), - ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, - }, - expBroken: true, - }, - "current block abstain vote count must be greater than previous block abstain vote count": { - prevProposal: &group.Proposal{ - Id: 1, - Address: addr1.String(), - Proposers: []string{addr1.String()}, - SubmitTime: prevCtx.BlockTime(), - GroupVersion: 1, - GroupPolicyVersion: 1, - Status: group.PROPOSAL_STATUS_SUBMITTED, - Result: group.PROPOSAL_RESULT_UNFINALIZED, - FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "2", NoWithVetoCount: "0"}, - VotingPeriodEnd: prevCtx.BlockTime().Add(time.Second * 600), - ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, - }, - curProposal: &group.Proposal{ - Id: 1, - Address: addr2.String(), - Proposers: []string{addr2.String()}, - SubmitTime: curCtx.BlockTime(), - GroupVersion: 1, - GroupPolicyVersion: 1, - Status: group.PROPOSAL_STATUS_SUBMITTED, - Result: group.PROPOSAL_RESULT_UNFINALIZED, - FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "1", NoWithVetoCount: "0"}, - VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600), - ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, - }, - expBroken: true, - }, - "current block veto vote count must be greater than previous block veto vote count": { - prevProposal: &group.Proposal{ - Id: 1, - Address: addr1.String(), - Proposers: []string{addr1.String()}, - SubmitTime: prevCtx.BlockTime(), - GroupVersion: 1, - GroupPolicyVersion: 1, - Status: group.PROPOSAL_STATUS_SUBMITTED, - Result: group.PROPOSAL_RESULT_UNFINALIZED, - FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "2"}, - VotingPeriodEnd: prevCtx.BlockTime().Add(time.Second * 600), - ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, - }, - curProposal: &group.Proposal{ - Id: 1, - Address: addr2.String(), - Proposers: []string{addr2.String()}, - SubmitTime: curCtx.BlockTime(), - GroupVersion: 1, - GroupPolicyVersion: 1, - Status: group.PROPOSAL_STATUS_SUBMITTED, - Result: group.PROPOSAL_RESULT_UNFINALIZED, - FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "1"}, - VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600), - ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, - }, - expBroken: true, - }, - } - - for _, spec := range specs { - - prevProposal := spec.prevProposal - curProposal := spec.curProposal - - cachePrevCtx, _ := prevCtx.CacheContext() - cacheCurCtx, _ := curCtx.CacheContext() - - _, err = proposalTable.Create(cachePrevCtx.KVStore(key), prevProposal) - s.Require().NoError(err) - _, err = proposalTable.Create(cacheCurCtx.KVStore(key), curProposal) - s.Require().NoError(err) - - _, broken := keeper.TallyVotesInvariantHelper(cacheCurCtx, cachePrevCtx, key, *proposalTable) - s.Require().Equal(spec.expBroken, broken) - } -} - func (s *invariantTestSuite) TestGroupTotalWeightInvariant() { sdkCtx, _ := s.ctx.CacheContext() curCtx, cdc, key := sdkCtx, s.cdc, s.key @@ -365,6 +182,8 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() { _, _, addr1 := testdata.KeyTestPubAddr() _, _, addr2 := testdata.KeyTestPubAddr() + votingPeriodEnd := curCtx.BlockTime().Add(time.Second * 600) + specs := map[string]struct { groupsInfo *group.GroupInfo groupPolicy *group.GroupPolicyInfo @@ -409,10 +228,10 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() { SubmitTime: curCtx.BlockTime(), GroupVersion: 1, GroupPolicyVersion: 1, - Status: group.PROPOSAL_STATUS_SUBMITTED, - Result: group.PROPOSAL_RESULT_UNFINALIZED, + Status: group.PROPOSAL_STATUS_CLOSED, + Result: group.PROPOSAL_RESULT_ACCEPTED, FinalTallyResult: group.TallyResult{YesCount: "4", NoCount: "3", AbstainCount: "0", NoWithVetoCount: "0"}, - VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600), + VotingPeriodEnd: votingPeriodEnd, ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, }, votes: []*group.Vote{ @@ -431,6 +250,58 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() { }, expBroken: false, }, + "proposal not closed ignored": { + groupsInfo: &group.GroupInfo{ + Id: 1, + Admin: adminAddr.String(), + Version: 1, + TotalWeight: "7", + }, + groupPolicy: &group.GroupPolicyInfo{ + Address: addr1.String(), + GroupId: 1, + Admin: adminAddr.String(), + Version: 1, + }, + groupMembers: []*group.GroupMember{ + { + GroupId: 1, + Member: &group.Member{ + Address: addr1.String(), + Weight: "4", + }, + }, + { + GroupId: 1, + Member: &group.Member{ + Address: addr2.String(), + Weight: "3", + }, + }, + }, + proposal: &group.Proposal{ + Id: 1, + Address: addr1.String(), + Proposers: []string{addr1.String()}, + SubmitTime: curCtx.BlockTime(), + GroupVersion: 1, + GroupPolicyVersion: 1, + Status: group.PROPOSAL_STATUS_SUBMITTED, + Result: group.PROPOSAL_RESULT_UNFINALIZED, + FinalTallyResult: group.TallyResult{YesCount: "0", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"}, + VotingPeriodEnd: votingPeriodEnd, + ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, + }, + votes: []*group.Vote{ + { + ProposalId: 1, + Voter: addr1.String(), + Option: group.VOTE_OPTION_YES, + SubmitTime: curCtx.BlockTime(), + }, + }, + expBroken: false, + }, "proposal tally must correspond to the sum of vote weights": { groupsInfo: &group.GroupInfo{ Id: 1, @@ -467,10 +338,10 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() { SubmitTime: curCtx.BlockTime(), GroupVersion: 1, GroupPolicyVersion: 1, - Status: group.PROPOSAL_STATUS_SUBMITTED, - Result: group.PROPOSAL_RESULT_UNFINALIZED, + Status: group.PROPOSAL_STATUS_CLOSED, + Result: group.PROPOSAL_RESULT_ACCEPTED, FinalTallyResult: group.TallyResult{YesCount: "6", NoCount: "0", AbstainCount: "0", NoWithVetoCount: "0"}, - VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600), + VotingPeriodEnd: votingPeriodEnd, ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, }, votes: []*group.Vote{ @@ -525,10 +396,10 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() { SubmitTime: curCtx.BlockTime(), GroupVersion: 1, GroupPolicyVersion: 1, - Status: group.PROPOSAL_STATUS_SUBMITTED, - Result: group.PROPOSAL_RESULT_UNFINALIZED, + Status: group.PROPOSAL_STATUS_CLOSED, + Result: group.PROPOSAL_RESULT_ACCEPTED, FinalTallyResult: group.TallyResult{YesCount: "4", NoCount: "3", AbstainCount: "0", NoWithVetoCount: "0"}, - VotingPeriodEnd: curCtx.BlockTime().Add(time.Second * 600), + VotingPeriodEnd: votingPeriodEnd, ExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, }, votes: []*group.Vote{ @@ -578,7 +449,7 @@ func (s *invariantTestSuite) TestTallyVotesSumInvariant() { s.Require().NoError(err) } - _, broken := keeper.TallyVotesSumInvariantHelper(cacheCurCtx, key, *groupTable, *proposalTable, *groupMemberTable, voteByProposalIndex, *groupPolicyTable) + _, broken := keeper.TallyVotesSumInvariantHelper(cacheCurCtx, key, *proposalTable, *groupMemberTable, voteByProposalIndex, *groupPolicyTable) s.Require().Equal(spec.expBroken, broken) } } diff --git a/x/group/keeper/keeper.go b/x/group/keeper/keeper.go index 103a9b86a..7177eefba 100644 --- a/x/group/keeper/keeper.go +++ b/x/group/keeper/keeper.go @@ -2,12 +2,14 @@ package keeper import ( "fmt" + "time" "github.com/tendermint/tendermint/libs/log" "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" authmiddleware "github.com/cosmos/cosmos-sdk/x/auth/middleware" "github.com/cosmos/cosmos-sdk/x/group" "github.com/cosmos/cosmos-sdk/x/group/errors" @@ -35,8 +37,7 @@ const ( ProposalTablePrefix byte = 0x30 ProposalTableSeqPrefix byte = 0x31 ProposalByGroupPolicyIndexPrefix byte = 0x32 - ProposalByProposerIndexPrefix byte = 0x33 - ProposalsByVotingPeriodEndPrefix byte = 0x34 + ProposalsByVotingPeriodEndPrefix byte = 0x33 // Vote Table VoteTablePrefix byte = 0x40 @@ -67,7 +68,6 @@ type Keeper struct { // Proposal Table proposalTable orm.AutoUInt64Table proposalByGroupPolicyIndex orm.Index - proposalByProposerIndex orm.Index proposalsByVotingPeriodEnd orm.Index // Vote Table @@ -169,21 +169,6 @@ func NewKeeper(storeKey storetypes.StoreKey, cdc codec.Codec, router *authmiddle if err != nil { panic(err.Error()) } - k.proposalByProposerIndex, err = orm.NewIndex(proposalTable, ProposalByProposerIndexPrefix, func(value interface{}) ([]interface{}, error) { - proposers := value.(*group.Proposal).Proposers - r := make([]interface{}, len(proposers)) - for i := range proposers { - addr, err := sdk.AccAddressFromBech32(proposers[i]) - if err != nil { - return nil, err - } - r[i] = addr.Bytes() - } - return r, nil - }, []byte{}) - if err != nil { - panic(err.Error()) - } k.proposalsByVotingPeriodEnd, err = orm.NewIndex(proposalTable, ProposalsByVotingPeriodEndPrefix, func(value interface{}) ([]interface{}, error) { votingPeriodEnd := value.(*group.Proposal).VotingPeriodEnd return []interface{}{sdk.FormatTimeBytes(votingPeriodEnd)}, nil @@ -233,16 +218,14 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", fmt.Sprintf("x/%s", group.ModuleName)) } -// MaxMetadataLength returns the max length of the metadata bytes field for various entities within the group module. -func (k Keeper) MaxMetadataLength() uint64 { return k.config.MaxMetadataLen } - // GetGroupSequence returns the current value of the group table sequence func (k Keeper) GetGroupSequence(ctx sdk.Context) uint64 { return k.groupTable.Sequence().CurVal(ctx.KVStore(k.key)) } -func (k Keeper) iterateProposalsByVPEnd(ctx sdk.Context, cb func(proposal group.Proposal) (bool, error)) error { - timeBytes := sdk.FormatTimeBytes(ctx.BlockTime()) +// iterateProposalsByVPEnd iterates over all proposals whose voting_period_end is after the `endTime` time argument. +func (k Keeper) iterateProposalsByVPEnd(ctx sdk.Context, endTime time.Time, cb func(proposal group.Proposal) (bool, error)) error { + timeBytes := sdk.FormatTimeBytes(endTime) it, err := k.proposalsByVotingPeriodEnd.PrefixScan(ctx.KVStore(k.key), nil, timeBytes) if err != nil { @@ -250,8 +233,17 @@ func (k Keeper) iterateProposalsByVPEnd(ctx sdk.Context, cb func(proposal group. } defer it.Close() - var proposal group.Proposal for { + // Important: this following line cannot outside the for loop. + // It seems that when one unmarshals into the same `group.Proposal` + // reference, then gogoproto somehow "adds" the new bytes to the old + // object for some fields. When running simulations, for proposals with + // each 1-2 proposers, after a couple of loop iterations we got to a + // proposal with 60k+ proposers. + // So we're declaring a local variable that gets GCed. + // + // Also see `x/group/types/proposal_test.go`, TestGogoUnmarshalProposal(). + var proposal group.Proposal _, err := it.LoadNext(&proposal) if errors.ErrORMIteratorDone.Is(err) { break @@ -272,30 +264,87 @@ func (k Keeper) iterateProposalsByVPEnd(ctx sdk.Context, cb func(proposal group. return nil } -func (k Keeper) UpdateTallyOfVPEndProposals(ctx sdk.Context) error { - k.iterateProposalsByVPEnd(ctx, func(proposal group.Proposal) (bool, error) { +// pruneProposal deletes a proposal from state. +func (k Keeper) pruneProposal(ctx sdk.Context, proposalID uint64) error { + store := ctx.KVStore(k.key) - policyInfo, err := k.getGroupPolicyInfo(ctx, proposal.Address) + err := k.proposalTable.Delete(store, proposalID) + if err != nil { + return err + } + + k.Logger(ctx).Debug(fmt.Sprintf("Pruned proposal %d", proposalID)) + return nil +} + +// pruneVotes prunes all votes for a proposal from state. +func (k Keeper) pruneVotes(ctx sdk.Context, proposalID uint64) error { + store := ctx.KVStore(k.key) + it, err := k.voteByProposalIndex.Get(store, proposalID) + if err != nil { + return err + } + defer it.Close() + + for { + var vote group.Vote + _, err = it.LoadNext(&vote) + if errors.ErrORMIteratorDone.Is(err) { + break + } if err != nil { - return true, err + return err } - electorate, err := k.getGroupInfo(ctx, policyInfo.GroupId) + err = k.voteTable.Delete(store, &vote) if err != nil { - return true, err + return err } + } - err = k.doTallyAndUpdate(ctx, &proposal, electorate, policyInfo) + return nil +} + +// PruneProposals prunes all proposals that are expired, i.e. whose +// `voting_period + max_execution_period` is greater than the current block +// time. +func (k Keeper) PruneProposals(ctx sdk.Context) error { + err := k.iterateProposalsByVPEnd(ctx, ctx.BlockTime().Add(-k.config.MaxExecutionPeriod), func(proposal group.Proposal) (bool, error) { + err := k.pruneProposal(ctx, proposal.Id) if err != nil { return true, err } - if err := k.proposalTable.Update(ctx.KVStore(k.key), proposal.Id, &proposal); err != nil { - return true, err - } - return false, nil }) + if err != nil { + return err + } return nil } + +func (k Keeper) UpdateTallyOfVPEndProposals(ctx sdk.Context) error { + return k.iterateProposalsByVPEnd(ctx, ctx.BlockTime(), func(proposal group.Proposal) (bool, error) { + policyInfo, err := k.getGroupPolicyInfo(ctx, proposal.Address) + if err != nil { + return true, sdkerrors.Wrap(err, "group policy") + } + + electorate, err := k.getGroupInfo(ctx, policyInfo.GroupId) + if err != nil { + return true, sdkerrors.Wrap(err, "group") + } + + err = k.doTallyAndUpdate(ctx, &proposal, electorate, policyInfo) + if err != nil { + return true, sdkerrors.Wrap(err, "doTallyAndUpdate") + } + + if err := k.proposalTable.Update(ctx.KVStore(k.key), proposal.Id, &proposal); err != nil { + return true, sdkerrors.Wrap(err, "proposal update") + } + + return false, nil + }) +} diff --git a/x/group/keeper/keeper_test.go b/x/group/keeper/keeper_test.go index 06f0c5c2e..ba1a7d533 100644 --- a/x/group/keeper/keeper_test.go +++ b/x/group/keeper/keeper_test.go @@ -1598,27 +1598,29 @@ func (s *TestSuite) TestSubmitProposal() { s.Require().NoError(err) id := res.ProposalId - // then all data persisted - proposalRes, err := s.keeper.Proposal(s.ctx, &group.QueryProposalRequest{ProposalId: id}) - s.Require().NoError(err) - proposal := proposalRes.Proposal + if !(spec.expProposal.ExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS) { + // then all data persisted + proposalRes, err := s.keeper.Proposal(s.ctx, &group.QueryProposalRequest{ProposalId: id}) + s.Require().NoError(err) + proposal := proposalRes.Proposal - s.Assert().Equal(spec.expProposal.Address, proposal.Address) - s.Assert().Equal(spec.req.Metadata, proposal.Metadata) - s.Assert().Equal(spec.req.Proposers, proposal.Proposers) - s.Assert().Equal(s.blockTime, proposal.SubmitTime) - s.Assert().Equal(uint64(1), proposal.GroupVersion) - s.Assert().Equal(uint64(1), proposal.GroupPolicyVersion) - s.Assert().Equal(spec.expProposal.Status, proposal.Status) - s.Assert().Equal(spec.expProposal.Result, proposal.Result) - s.Assert().Equal(spec.expProposal.FinalTallyResult, proposal.FinalTallyResult) - s.Assert().Equal(spec.expProposal.ExecutorResult, proposal.ExecutorResult) - s.Assert().Equal(s.blockTime.Add(time.Second), proposal.VotingPeriodEnd) + s.Assert().Equal(spec.expProposal.Address, proposal.Address) + s.Assert().Equal(spec.req.Metadata, proposal.Metadata) + s.Assert().Equal(spec.req.Proposers, proposal.Proposers) + s.Assert().Equal(s.blockTime, proposal.SubmitTime) + s.Assert().Equal(uint64(1), proposal.GroupVersion) + s.Assert().Equal(uint64(1), proposal.GroupPolicyVersion) + s.Assert().Equal(spec.expProposal.Status, proposal.Status) + s.Assert().Equal(spec.expProposal.Result, proposal.Result) + s.Assert().Equal(spec.expProposal.FinalTallyResult, proposal.FinalTallyResult) + s.Assert().Equal(spec.expProposal.ExecutorResult, proposal.ExecutorResult) + s.Assert().Equal(s.blockTime.Add(time.Second), proposal.VotingPeriodEnd) - if spec.msgs == nil { // then empty list is ok - s.Assert().Len(proposal.GetMsgs(), 0) - } else { - s.Assert().Equal(spec.msgs, proposal.GetMsgs()) + if spec.msgs == nil { // then empty list is ok + s.Assert().Len(proposal.GetMsgs(), 0) + } else { + s.Assert().Equal(spec.msgs, proposal.GetMsgs()) + } } spec.postRun(s.sdkCtx) @@ -2042,47 +2044,6 @@ func (s *TestSuite) TestVote() { expErr: true, postRun: func(sdkCtx sdk.Context) {}, }, - "with group modified": { - req: &group.MsgVote{ - ProposalId: myProposalID, - Voter: addr4.String(), - Option: group.VOTE_OPTION_NO, - }, - doBefore: func(ctx context.Context) { - _, err = s.keeper.UpdateGroupMetadata(ctx, &group.MsgUpdateGroupMetadata{ - GroupId: myGroupID, - Admin: addr1.String(), - }) - s.Require().NoError(err) - }, - expErr: true, - postRun: func(sdkCtx sdk.Context) {}, - }, - "with policy modified": { - req: &group.MsgVote{ - ProposalId: myProposalID, - Voter: addr4.String(), - Option: group.VOTE_OPTION_NO, - }, - doBefore: func(ctx context.Context) { - m, err := group.NewMsgUpdateGroupPolicyDecisionPolicyRequest( - addr1, - groupPolicy, - &group.ThresholdDecisionPolicy{ - Threshold: "1", - Windows: &group.DecisionPolicyWindows{ - VotingPeriod: time.Second, - }, - }, - ) - s.Require().NoError(err) - - _, err = s.keeper.UpdateGroupPolicyDecisionPolicy(ctx, m) - s.Require().NoError(err) - }, - expErr: true, - postRun: func(sdkCtx sdk.Context) {}, - }, } for msg, spec := range specs { spec := spec @@ -2105,66 +2066,69 @@ func (s *TestSuite) TestVote() { s.Require().NoError(err) s.Require().NoError(err) - // vote is stored and all data persisted - res, err := s.keeper.VoteByProposalVoter(ctx, &group.QueryVoteByProposalVoterRequest{ - ProposalId: spec.req.ProposalId, - Voter: spec.req.Voter, - }) - s.Require().NoError(err) - loaded := res.Vote - s.Assert().Equal(spec.req.ProposalId, loaded.ProposalId) - s.Assert().Equal(spec.req.Voter, loaded.Voter) - s.Assert().Equal(spec.req.Option, loaded.Option) - s.Assert().Equal(spec.req.Metadata, loaded.Metadata) - s.Assert().Equal(s.blockTime, loaded.SubmitTime) - // query votes by proposal - votesByProposalRes, err := s.keeper.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{ - ProposalId: spec.req.ProposalId, - }) - s.Require().NoError(err) - votesByProposal := votesByProposalRes.Votes - s.Require().Equal(1, len(votesByProposal)) - vote := votesByProposal[0] - s.Assert().Equal(spec.req.ProposalId, vote.ProposalId) - s.Assert().Equal(spec.req.Voter, vote.Voter) - s.Assert().Equal(spec.req.Option, vote.Option) - s.Assert().Equal(spec.req.Metadata, vote.Metadata) - s.Assert().Equal(s.blockTime, vote.SubmitTime) + if !(spec.expExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS) { + // vote is stored and all data persisted + res, err := s.keeper.VoteByProposalVoter(ctx, &group.QueryVoteByProposalVoterRequest{ + ProposalId: spec.req.ProposalId, + Voter: spec.req.Voter, + }) + s.Require().NoError(err) + loaded := res.Vote + s.Assert().Equal(spec.req.ProposalId, loaded.ProposalId) + s.Assert().Equal(spec.req.Voter, loaded.Voter) + s.Assert().Equal(spec.req.Option, loaded.Option) + s.Assert().Equal(spec.req.Metadata, loaded.Metadata) + s.Assert().Equal(s.blockTime, loaded.SubmitTime) - // query votes by voter - voter := spec.req.Voter - votesByVoterRes, err := s.keeper.VotesByVoter(ctx, &group.QueryVotesByVoterRequest{ - Voter: voter, - }) - s.Require().NoError(err) - votesByVoter := votesByVoterRes.Votes - s.Require().Equal(1, len(votesByVoter)) - s.Assert().Equal(spec.req.ProposalId, votesByVoter[0].ProposalId) - s.Assert().Equal(voter, votesByVoter[0].Voter) - s.Assert().Equal(spec.req.Option, votesByVoter[0].Option) - s.Assert().Equal(spec.req.Metadata, votesByVoter[0].Metadata) - s.Assert().Equal(s.blockTime, votesByVoter[0].SubmitTime) + // query votes by proposal + votesByProposalRes, err := s.keeper.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{ + ProposalId: spec.req.ProposalId, + }) + s.Require().NoError(err) + votesByProposal := votesByProposalRes.Votes + s.Require().Equal(1, len(votesByProposal)) + vote := votesByProposal[0] + s.Assert().Equal(spec.req.ProposalId, vote.ProposalId) + s.Assert().Equal(spec.req.Voter, vote.Voter) + s.Assert().Equal(spec.req.Option, vote.Option) + s.Assert().Equal(spec.req.Metadata, vote.Metadata) + s.Assert().Equal(s.blockTime, vote.SubmitTime) - proposalRes, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{ - ProposalId: spec.req.ProposalId, - }) - s.Require().NoError(err) + // query votes by voter + voter := spec.req.Voter + votesByVoterRes, err := s.keeper.VotesByVoter(ctx, &group.QueryVotesByVoterRequest{ + Voter: voter, + }) + s.Require().NoError(err) + votesByVoter := votesByVoterRes.Votes + s.Require().Equal(1, len(votesByVoter)) + s.Assert().Equal(spec.req.ProposalId, votesByVoter[0].ProposalId) + s.Assert().Equal(voter, votesByVoter[0].Voter) + s.Assert().Equal(spec.req.Option, votesByVoter[0].Option) + s.Assert().Equal(spec.req.Metadata, votesByVoter[0].Metadata) + s.Assert().Equal(s.blockTime, votesByVoter[0].SubmitTime) - proposal := proposalRes.Proposal - if spec.isFinal { - s.Assert().Equal(spec.expTallyResult, proposal.FinalTallyResult) - s.Assert().Equal(spec.expResult, proposal.Result) - s.Assert().Equal(spec.expProposalStatus, proposal.Status) - s.Assert().Equal(spec.expExecutorResult, proposal.ExecutorResult) - } else { - s.Assert().Equal(group.DefaultTallyResult(), proposal.FinalTallyResult) // Make sure proposal isn't mutated. - - // do a round of tallying - tallyResult, err := s.keeper.Tally(sdkCtx, *proposal, myGroupID) + proposalRes, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{ + ProposalId: spec.req.ProposalId, + }) s.Require().NoError(err) - s.Assert().Equal(spec.expTallyResult, tallyResult) + proposal := proposalRes.Proposal + if spec.isFinal { + s.Assert().Equal(spec.expTallyResult, proposal.FinalTallyResult) + s.Assert().Equal(spec.expResult, proposal.Result) + s.Assert().Equal(spec.expProposalStatus, proposal.Status) + s.Assert().Equal(spec.expExecutorResult, proposal.ExecutorResult) + } else { + s.Assert().Equal(group.DefaultTallyResult(), proposal.FinalTallyResult) // Make sure proposal isn't mutated. + + // do a round of tallying + tallyResult, err := s.keeper.Tally(sdkCtx, *proposal, myGroupID) + s.Require().NoError(err) + + s.Assert().Equal(spec.expTallyResult, tallyResult) + } } spec.postRun(sdkCtx) @@ -2329,36 +2293,6 @@ func (s *TestSuite) TestExecProposal() { expProposalResult: group.PROPOSAL_RESULT_REJECTED, expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, }, - "with group modified before tally": { - setupProposal: func(ctx context.Context) uint64 { - myProposalID := submitProposal(ctx, s, []sdk.Msg{msgSend1}, proposers) - - // then modify group - _, err := s.keeper.UpdateGroupMetadata(ctx, &group.MsgUpdateGroupMetadata{ - Admin: addr1.String(), - GroupId: s.groupID, - }) - s.Require().NoError(err) - return myProposalID - }, - expProposalStatus: group.PROPOSAL_STATUS_ABORTED, - expProposalResult: group.PROPOSAL_RESULT_UNFINALIZED, - expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, - }, - "with group policy modified before tally": { - setupProposal: func(ctx context.Context) uint64 { - myProposalID := submitProposal(ctx, s, []sdk.Msg{msgSend1}, proposers) - _, err := s.keeper.UpdateGroupPolicyMetadata(ctx, &group.MsgUpdateGroupPolicyMetadata{ - Admin: addr1.String(), - Address: s.groupPolicyAddr.String(), - }) - s.Require().NoError(err) - return myProposalID - }, - expProposalStatus: group.PROPOSAL_STATUS_ABORTED, - expProposalResult: group.PROPOSAL_RESULT_UNFINALIZED, - expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, - }, "prevent double execution when successful": { setupProposal: func(ctx context.Context) uint64 { myProposalID := submitProposalAndVote(ctx, s, []sdk.Msg{msgSend1}, proposers, group.VOTE_OPTION_YES) @@ -2367,6 +2301,7 @@ func (s *TestSuite) TestExecProposal() { s.Require().NoError(err) return myProposalID }, + expErr: true, // since proposal is pruned after a successful MsgExec expProposalStatus: group.PROPOSAL_STATUS_CLOSED, expProposalResult: group.PROPOSAL_RESULT_ACCEPTED, expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS, @@ -2419,22 +2354,25 @@ func (s *TestSuite) TestExecProposal() { } s.Require().NoError(err) - // and proposal is updated - res, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{ProposalId: proposalID}) - s.Require().NoError(err) - proposal := res.Proposal + if !(spec.expExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS) { - exp := group.ProposalResult_name[int32(spec.expProposalResult)] - got := group.ProposalResult_name[int32(proposal.Result)] - s.Assert().Equal(exp, got) + // and proposal is updated + res, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{ProposalId: proposalID}) + s.Require().NoError(err) + proposal := res.Proposal - exp = group.ProposalStatus_name[int32(spec.expProposalStatus)] - got = group.ProposalStatus_name[int32(proposal.Status)] - s.Assert().Equal(exp, got) + exp := group.ProposalResult_name[int32(spec.expProposalResult)] + got := group.ProposalResult_name[int32(proposal.Result)] + s.Assert().Equal(exp, got) - exp = group.ProposalExecutorResult_name[int32(spec.expExecutorResult)] - got = group.ProposalExecutorResult_name[int32(proposal.ExecutorResult)] - s.Assert().Equal(exp, got) + exp = group.ProposalStatus_name[int32(spec.expProposalStatus)] + got = group.ProposalStatus_name[int32(proposal.Status)] + s.Assert().Equal(exp, got) + + exp = group.ProposalExecutorResult_name[int32(spec.expExecutorResult)] + got = group.ProposalExecutorResult_name[int32(proposal.ExecutorResult)] + s.Assert().Equal(exp, got) + } if spec.expBalance { fromBalances := s.app.BankKeeper.GetAllBalances(sdkCtx, s.groupPolicyAddr) @@ -2446,6 +2384,150 @@ func (s *TestSuite) TestExecProposal() { } } +func (s *TestSuite) TestExecPrunedProposalsAndVotes() { + addrs := s.addrs + addr1 := addrs[0] + addr2 := addrs[1] + + msgSend1 := &banktypes.MsgSend{ + FromAddress: s.groupPolicyAddr.String(), + ToAddress: addr2.String(), + Amount: sdk.Coins{sdk.NewInt64Coin("test", 100)}, + } + msgSend2 := &banktypes.MsgSend{ + FromAddress: s.groupPolicyAddr.String(), + ToAddress: addr2.String(), + Amount: sdk.Coins{sdk.NewInt64Coin("test", 10001)}, + } + proposers := []string{addr2.String()} + specs := map[string]struct { + srcBlockTime time.Time + setupProposal func(ctx context.Context) uint64 + expErr bool + expErrMsg string + expExecutorResult group.ProposalExecutorResult + }{ + "proposal pruned after executor result success": { + setupProposal: func(ctx context.Context) uint64 { + msgs := []sdk.Msg{msgSend1} + return submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_YES) + }, + expErrMsg: "load proposal: not found", + expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS, + }, + "proposal with multiple messages pruned when executed with result success": { + setupProposal: func(ctx context.Context) uint64 { + msgs := []sdk.Msg{msgSend1, msgSend1} + return submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_YES) + }, + expErrMsg: "load proposal: not found", + expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS, + }, + "proposal not pruned when not executed and rejected": { + setupProposal: func(ctx context.Context) uint64 { + msgs := []sdk.Msg{msgSend1} + return submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_NO) + }, + expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, + }, + "open proposal is not pruned which must not fail ": { + setupProposal: func(ctx context.Context) uint64 { + return submitProposal(ctx, s, []sdk.Msg{msgSend1}, proposers) + }, + expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, + }, + "proposal not pruned with group modified before tally": { + setupProposal: func(ctx context.Context) uint64 { + myProposalID := submitProposal(ctx, s, []sdk.Msg{msgSend1}, proposers) + + // then modify group + _, err := s.keeper.UpdateGroupMetadata(ctx, &group.MsgUpdateGroupMetadata{ + Admin: addr1.String(), + GroupId: s.groupID, + }) + s.Require().NoError(err) + return myProposalID + }, + expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, + }, + "proposal not pruned with group policy modified before tally": { + setupProposal: func(ctx context.Context) uint64 { + myProposalID := submitProposal(ctx, s, []sdk.Msg{msgSend1}, proposers) + _, err := s.keeper.UpdateGroupPolicyMetadata(ctx, &group.MsgUpdateGroupPolicyMetadata{ + Admin: addr1.String(), + Address: s.groupPolicyAddr.String(), + }) + s.Require().NoError(err) + return myProposalID + }, + expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, + }, + "proposal exists when rollback all msg updates on failure": { + setupProposal: func(ctx context.Context) uint64 { + msgs := []sdk.Msg{msgSend1, msgSend2} + return submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_YES) + }, + expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_FAILURE, + }, + "pruned when proposal is executable when failed before": { + setupProposal: func(ctx context.Context) uint64 { + msgs := []sdk.Msg{msgSend2} + myProposalID := submitProposalAndVote(ctx, s, msgs, proposers, group.VOTE_OPTION_YES) + + _, err := s.keeper.Exec(ctx, &group.MsgExec{Signer: addr1.String(), ProposalId: myProposalID}) + s.Require().NoError(err) + sdkCtx := sdk.UnwrapSDKContext(ctx) + s.Require().NoError(testutil.FundAccount(s.app.BankKeeper, sdkCtx, s.groupPolicyAddr, sdk.Coins{sdk.NewInt64Coin("test", 10002)})) + + return myProposalID + }, + expErrMsg: "load proposal: not found", + expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS, + }, + } + for msg, spec := range specs { + spec := spec + s.Run(msg, func() { + sdkCtx, _ := s.sdkCtx.CacheContext() + ctx := sdk.WrapSDKContext(sdkCtx) + proposalID := spec.setupProposal(ctx) + + if !spec.srcBlockTime.IsZero() { + sdkCtx = sdkCtx.WithBlockTime(spec.srcBlockTime) + } + + ctx = sdk.WrapSDKContext(sdkCtx) + _, err := s.keeper.Exec(ctx, &group.MsgExec{Signer: addr1.String(), ProposalId: proposalID}) + if spec.expErr { + s.Require().Error(err) + return + } + s.Require().NoError(err) + + if spec.expExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS { + // Make sure proposal is deleted from state + _, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{ProposalId: proposalID}) + s.Require().Contains(err.Error(), spec.expErrMsg) + res, err := s.keeper.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{ProposalId: proposalID}) + s.Require().NoError(err) + s.Require().Empty(res.GetVotes()) + + } else { + // Check that proposal and votes exists + res, err := s.keeper.Proposal(ctx, &group.QueryProposalRequest{ProposalId: proposalID}) + s.Require().NoError(err) + _, err = s.keeper.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{ProposalId: res.Proposal.Id}) + s.Require().NoError(err) + s.Require().Equal("", spec.expErrMsg) + + exp := group.ProposalExecutorResult_name[int32(spec.expExecutorResult)] + got := group.ProposalExecutorResult_name[int32(res.Proposal.ExecutorResult)] + s.Assert().Equal(exp, got) + } + }) + } +} + func (s *TestSuite) TestProposalsByVPEnd() { addrs := s.addrs addr2 := addrs[1] diff --git a/x/group/keeper/msg_server.go b/x/group/keeper/msg_server.go index d615a105d..d4f84a3e4 100644 --- a/x/group/keeper/msg_server.go +++ b/x/group/keeper/msg_server.go @@ -637,22 +637,14 @@ func (k Keeper) Vote(goCtx context.Context, req *group.MsgVote) (*group.MsgVoteR var policyInfo group.GroupPolicyInfo - // Ensure that group policy hasn't been modified since the proposal submission. if policyInfo, err = k.getGroupPolicyInfo(ctx, proposal.Address); err != nil { return nil, sdkerrors.Wrap(err, "load group policy") } - if proposal.GroupPolicyVersion != policyInfo.Version { - return nil, sdkerrors.Wrap(errors.ErrModified, "group policy was modified") - } - // Ensure that group hasn't been modified since the proposal submission. electorate, err := k.getGroupInfo(ctx, policyInfo.GroupId) if err != nil { return nil, err } - if electorate.Version != proposal.GroupVersion { - return nil, sdkerrors.Wrap(errors.ErrModified, "group was modified") - } // Count and store votes. voterAddr := req.Voter @@ -711,17 +703,22 @@ func (k Keeper) doTallyAndUpdate(ctx sdk.Context, p *group.Proposal, electorate return err } - switch result, err := policy.Allow(tallyResult, electorate.TotalWeight, ctx.BlockTime().Sub(submittedAt)); { + result, err := policy.Allow(tallyResult, electorate.TotalWeight, ctx.BlockTime().Sub(submittedAt)) + switch { case err != nil: - return sdkerrors.Wrap(err, "policy execution") - case result.Allow && result.Final: + return sdkerrors.Wrap(err, "policy allow") + case result.Final: + if err := k.pruneVotes(ctx, p.Id); err != nil { + return err + } p.FinalTallyResult = tallyResult - p.Result = group.PROPOSAL_RESULT_ACCEPTED - p.Status = group.PROPOSAL_STATUS_CLOSED - case !result.Allow && result.Final: - p.FinalTallyResult = tallyResult - p.Result = group.PROPOSAL_RESULT_REJECTED - p.Status = group.PROPOSAL_STATUS_CLOSED + if result.Allow { + p.Result = group.PROPOSAL_RESULT_ACCEPTED + p.Status = group.PROPOSAL_STATUS_CLOSED + } else { + p.Result = group.PROPOSAL_RESULT_REJECTED + p.Status = group.PROPOSAL_STATUS_CLOSED + } } return nil @@ -747,32 +744,28 @@ func (k Keeper) Exec(goCtx context.Context, req *group.MsgExec) (*group.MsgExecR } storeUpdates := func() (*group.MsgExecResponse, error) { - if err := k.proposalTable.Update(ctx.KVStore(k.key), id, &proposal); err != nil { - return nil, err + store := ctx.KVStore(k.key) + + // If proposal has successfully run, delete it from state. + if proposal.ExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS { + if err := k.pruneProposal(ctx, proposal.Id); err != nil { + return nil, err + } + } else { + if err := k.proposalTable.Update(store, id, &proposal); err != nil { + return nil, err + } } + return &group.MsgExecResponse{}, nil } if proposal.Status == group.PROPOSAL_STATUS_SUBMITTED { - // Ensure that group policy hasn't been modified before tally. - if proposal.GroupPolicyVersion != policyInfo.Version { - proposal.Result = group.PROPOSAL_RESULT_UNFINALIZED - proposal.Status = group.PROPOSAL_STATUS_ABORTED - return storeUpdates() - } - electorate, err := k.getGroupInfo(ctx, policyInfo.GroupId) if err != nil { return nil, sdkerrors.Wrap(err, "load group") } - // Ensure that group hasn't been modified before tally. - if electorate.Version != proposal.GroupVersion { - proposal.Result = group.PROPOSAL_RESULT_UNFINALIZED - proposal.Status = group.PROPOSAL_STATUS_ABORTED - return storeUpdates() - } - if err := k.doTallyAndUpdate(ctx, &proposal, electorate, policyInfo); err != nil { return nil, err } @@ -805,7 +798,10 @@ func (k Keeper) Exec(goCtx context.Context, req *group.MsgExec) (*group.MsgExecR return nil, err } - err = ctx.EventManager().EmitTypedEvent(&group.EventExec{ProposalId: id}) + err = ctx.EventManager().EmitTypedEvent(&group.EventExec{ + ProposalId: id, + Result: proposal.ExecutorResult, + }) if err != nil { return nil, err } @@ -983,8 +979,8 @@ func (k Keeper) validateDecisionPolicies(ctx sdk.Context, g group.GroupInfo) err } defer it.Close() - var groupPolicy group.GroupPolicyInfo for { + var groupPolicy group.GroupPolicyInfo _, err = it.LoadNext(&groupPolicy) if errors.ErrORMIteratorDone.Is(err) { break diff --git a/x/group/keeper/tally.go b/x/group/keeper/tally.go index 1a3bb94c3..2f1a21629 100644 --- a/x/group/keeper/tally.go +++ b/x/group/keeper/tally.go @@ -25,8 +25,8 @@ func (q Keeper) Tally(ctx sdk.Context, p group.Proposal, groupId uint64) (group. tallyResult := group.DefaultTallyResult() - var vote group.Vote for { + var vote group.Vote _, err = it.LoadNext(&vote) if errors.ErrORMIteratorDone.Is(err) { break diff --git a/x/group/module/abci.go b/x/group/module/abci.go index 90e900beb..36354d1e8 100644 --- a/x/group/module/abci.go +++ b/x/group/module/abci.go @@ -9,4 +9,12 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) { if err := k.UpdateTallyOfVPEndProposals(ctx); err != nil { panic(err) } + pruneProposals(ctx, k) +} + +func pruneProposals(ctx sdk.Context, k keeper.Keeper) { + err := k.PruneProposals(ctx) + if err != nil { + panic(err) + } } diff --git a/x/group/module/abci_test.go b/x/group/module/abci_test.go index f303a38d0..4393a7258 100644 --- a/x/group/module/abci_test.go +++ b/x/group/module/abci_test.go @@ -6,18 +6,196 @@ import ( "time" "github.com/cosmos/cosmos-sdk/simapp" - "github.com/cosmos/cosmos-sdk/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/bank/testutil" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/cosmos/cosmos-sdk/x/group" "github.com/cosmos/cosmos-sdk/x/group/module" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) +func TestEndBlockerPruning(t *testing.T) { + app := simapp.Setup(t, false) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + addrs := simapp.AddTestAddrsIncremental(app, ctx, 3, sdk.NewInt(30000000)) + addr1 := addrs[0] + addr2 := addrs[1] + addr3 := addrs[2] + + // Initial group, group policy and balance setup + members := []group.Member{ + {Address: addr1.String(), Weight: "1"}, {Address: addr2.String(), Weight: "2"}, + } + + groupRes, err := app.GroupKeeper.CreateGroup(ctx, &group.MsgCreateGroup{ + Admin: addr1.String(), + Members: members, + }) + + require.NoError(t, err) + groupID := groupRes.GroupId + + policy := group.NewThresholdDecisionPolicy( + "2", + time.Second, + 0, + ) + + policyReq := &group.MsgCreateGroupPolicy{ + Admin: addr1.String(), + GroupId: groupID, + } + + err = policyReq.SetDecisionPolicy(policy) + require.NoError(t, err) + policyRes, err := app.GroupKeeper.CreateGroupPolicy(ctx, policyReq) + require.NoError(t, err) + + groupPolicyAddr, err := sdk.AccAddressFromBech32(policyRes.Address) + require.NoError(t, err) + require.NoError(t, testutil.FundAccount(app.BankKeeper, ctx, groupPolicyAddr, sdk.Coins{sdk.NewInt64Coin("test", 10000)})) + + msgSend1 := &banktypes.MsgSend{ + FromAddress: groupPolicyAddr.String(), + ToAddress: addr2.String(), + Amount: sdk.Coins{sdk.NewInt64Coin("test", 100)}, + } + proposers := []string{addr2.String()} + + specs := map[string]struct { + srcBlockTime time.Time + setupProposal func(ctx context.Context) uint64 + expErr bool + expErrMsg string + expExecutorResult group.ProposalExecutorResult + }{ + "proposal pruned after executor result success": { + setupProposal: func(ctx context.Context) uint64 { + msgs := []sdk.Msg{msgSend1} + pID, err := submitProposalAndVote(app, ctx, msgs, proposers, groupPolicyAddr, group.VOTE_OPTION_YES) + require.NoError(t, err) + _, err = app.GroupKeeper.Exec(ctx, &group.MsgExec{Signer: addr3.String(), ProposalId: pID}) + require.NoError(t, err) + sdkCtx := sdk.UnwrapSDKContext(ctx) + require.NoError(t, testutil.FundAccount(app.BankKeeper, sdkCtx, groupPolicyAddr, sdk.Coins{sdk.NewInt64Coin("test", 10002)})) + + return pID + }, + expErrMsg: "load proposal: not found", + expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS, + }, + "proposal with multiple messages pruned when executed with result success": { + setupProposal: func(ctx context.Context) uint64 { + msgs := []sdk.Msg{msgSend1, msgSend1} + pID, err := submitProposalAndVote(app, ctx, msgs, proposers, groupPolicyAddr, group.VOTE_OPTION_YES) + require.NoError(t, err) + _, err = app.GroupKeeper.Exec(ctx, &group.MsgExec{Signer: addr3.String(), ProposalId: pID}) + require.NoError(t, err) + sdkCtx := sdk.UnwrapSDKContext(ctx) + require.NoError(t, testutil.FundAccount(app.BankKeeper, sdkCtx, groupPolicyAddr, sdk.Coins{sdk.NewInt64Coin("test", 10002)})) + + return pID + }, + expErrMsg: "load proposal: not found", + expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS, + }, + "proposal not pruned when not executed and rejected": { + setupProposal: func(ctx context.Context) uint64 { + msgs := []sdk.Msg{msgSend1} + pID, err := submitProposalAndVote(app, ctx, msgs, proposers, groupPolicyAddr, group.VOTE_OPTION_NO) + require.NoError(t, err) + _, err = app.GroupKeeper.Exec(ctx, &group.MsgExec{Signer: addr3.String(), ProposalId: pID}) + require.NoError(t, err) + sdkCtx := sdk.UnwrapSDKContext(ctx) + require.NoError(t, testutil.FundAccount(app.BankKeeper, sdkCtx, groupPolicyAddr, sdk.Coins{sdk.NewInt64Coin("test", 10002)})) + + return pID + }, + expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, + }, + "open proposal is not pruned which must not fail ": { + setupProposal: func(ctx context.Context) uint64 { + pID, err := submitProposal(app, ctx, []sdk.Msg{msgSend1}, proposers, groupPolicyAddr) + require.NoError(t, err) + _, err = app.GroupKeeper.Exec(ctx, &group.MsgExec{Signer: addr3.String(), ProposalId: pID}) + require.NoError(t, err) + sdkCtx := sdk.UnwrapSDKContext(ctx) + require.NoError(t, testutil.FundAccount(app.BankKeeper, sdkCtx, groupPolicyAddr, sdk.Coins{sdk.NewInt64Coin("test", 10002)})) + + return pID + }, + expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, + }, + "proposal not pruned with group policy modified before tally": { + setupProposal: func(ctx context.Context) uint64 { + pID, err := submitProposal(app, ctx, []sdk.Msg{msgSend1}, proposers, groupPolicyAddr) + require.NoError(t, err) + _, err = app.GroupKeeper.UpdateGroupPolicyMetadata(ctx, &group.MsgUpdateGroupPolicyMetadata{ + Admin: addr1.String(), + Address: groupPolicyAddr.String(), + }) + require.NoError(t, err) + _, err = app.GroupKeeper.Exec(ctx, &group.MsgExec{Signer: addr3.String(), ProposalId: pID}) + require.NoError(t, err) + sdkCtx := sdk.UnwrapSDKContext(ctx) + require.NoError(t, testutil.FundAccount(app.BankKeeper, sdkCtx, groupPolicyAddr, sdk.Coins{sdk.NewInt64Coin("test", 10002)})) + + return pID + }, + expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, + }, + "pruned when proposal is executable when failed before": { + setupProposal: func(ctx context.Context) uint64 { + msgs := []sdk.Msg{msgSend1} + pID, err := submitProposalAndVote(app, ctx, msgs, proposers, groupPolicyAddr, group.VOTE_OPTION_YES) + require.NoError(t, err) + _, err = app.GroupKeeper.Exec(ctx, &group.MsgExec{Signer: addrs[2].String(), ProposalId: pID}) + require.NoError(t, err) + return pID + }, + expErrMsg: "load proposal: not found", + expExecutorResult: group.PROPOSAL_EXECUTOR_RESULT_SUCCESS, + }, + } + for msg, spec := range specs { + spec := spec + t.Run(msg, func(t *testing.T) { + proposalID := spec.setupProposal(ctx) + + module.EndBlocker(ctx, app.GroupKeeper) + + if spec.expExecutorResult == group.PROPOSAL_EXECUTOR_RESULT_SUCCESS { + // Make sure proposal is deleted from state + _, err = app.GroupKeeper.Proposal(ctx, &group.QueryProposalRequest{ProposalId: proposalID}) + require.Contains(t, err.Error(), spec.expErrMsg) + res, err := app.GroupKeeper.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{ProposalId: proposalID}) + require.NoError(t, err) + require.Empty(t, res.GetVotes()) + } else { + // Check that proposal and votes exists + res, err := app.GroupKeeper.Proposal(ctx, &group.QueryProposalRequest{ProposalId: proposalID}) + require.NoError(t, err) + _, err = app.GroupKeeper.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{ProposalId: res.Proposal.Id}) + require.NoError(t, err) + require.Equal(t, "", spec.expErrMsg) + + exp := group.ProposalExecutorResult_name[int32(spec.expExecutorResult)] + got := group.ProposalExecutorResult_name[int32(res.Proposal.ExecutorResult)] + assert.Equal(t, exp, got) + } + }) + } + +} + func TestEndBlocker(t *testing.T) { app := simapp.Setup(t, false) ctx := app.BaseApp.NewContext(false, tmproto.Header{}) - addrs := simapp.AddTestAddrsIncremental(app, ctx, 4, types.NewInt(30000000)) + + addrs := simapp.AddTestAddrsIncremental(app, ctx, 4, sdk.NewInt(30000000)) // Initial group, group policy and balance setup members := []group.Member{ @@ -48,45 +226,44 @@ func TestEndBlocker(t *testing.T) { policyRes, err := app.GroupKeeper.CreateGroupPolicy(ctx, policyReq) require.NoError(t, err) - groupPolicyAddr, err := types.AccAddressFromBech32(policyRes.Address) + groupPolicyAddr, err := sdk.AccAddressFromBech32(policyRes.Address) require.NoError(t, err) votingPeriod := policy.GetVotingPeriod() - now := time.Now() msgSend := &banktypes.MsgSend{ FromAddress: groupPolicyAddr.String(), ToAddress: addrs[3].String(), - Amount: types.Coins{types.NewInt64Coin("test", 100)}, + Amount: sdk.Coins{sdk.NewInt64Coin("test", 100)}, } proposers := []string{addrs[2].String()} specs := map[string]struct { - preRun func(sdkCtx types.Context) uint64 + preRun func(sdkCtx sdk.Context) uint64 proposalId uint64 admin string expErrMsg string - newCtx types.Context + newCtx sdk.Context tallyRes group.TallyResult expStatus group.ProposalStatus expExecutorResult group.ProposalResult }{ "tally updated after voting power end": { - preRun: func(sdkCtx types.Context) uint64 { - pId, err := submitProposal(app, sdkCtx, []types.Msg{msgSend}, proposers, groupPolicyAddr) + preRun: func(sdkCtx sdk.Context) uint64 { + pId, err := submitProposal(app, sdkCtx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr) require.NoError(t, err) return pId }, admin: proposers[0], - newCtx: ctx.WithBlockTime(now.Add(votingPeriod).Add(time.Hour)), + newCtx: ctx.WithBlockTime(ctx.BlockTime().Add(votingPeriod).Add(time.Hour)), tallyRes: group.DefaultTallyResult(), expStatus: group.PROPOSAL_STATUS_SUBMITTED, expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED, }, "tally within voting period": { - preRun: func(sdkCtx types.Context) uint64 { - pId, err := submitProposal(app, sdkCtx, []types.Msg{msgSend}, proposers, groupPolicyAddr) + preRun: func(sdkCtx sdk.Context) uint64 { + pId, err := submitProposal(app, sdkCtx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr) require.NoError(t, err) return pId @@ -98,8 +275,8 @@ func TestEndBlocker(t *testing.T) { expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED, }, "tally within voting period(with votes)": { - preRun: func(sdkCtx types.Context) uint64 { - pId, err := submitProposalAndVote(app, ctx, []types.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES) + preRun: func(sdkCtx sdk.Context) uint64 { + pId, err := submitProposalAndVote(app, ctx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES) require.NoError(t, err) return pId @@ -111,14 +288,14 @@ func TestEndBlocker(t *testing.T) { expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED, }, "tally after voting period(with votes)": { - preRun: func(sdkCtx types.Context) uint64 { - pId, err := submitProposalAndVote(app, ctx, []types.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES) + preRun: func(sdkCtx sdk.Context) uint64 { + pId, err := submitProposalAndVote(app, ctx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES) require.NoError(t, err) return pId }, admin: proposers[0], - newCtx: ctx.WithBlockTime(now.Add(votingPeriod).Add(time.Hour)), + newCtx: ctx.WithBlockTime(ctx.BlockTime().Add(votingPeriod).Add(time.Hour)), tallyRes: group.TallyResult{ YesCount: "2", NoCount: "0", @@ -129,8 +306,8 @@ func TestEndBlocker(t *testing.T) { expExecutorResult: group.PROPOSAL_RESULT_ACCEPTED, }, "tally of closed proposal": { - preRun: func(sdkCtx types.Context) uint64 { - pId, err := submitProposal(app, sdkCtx, []types.Msg{msgSend}, proposers, groupPolicyAddr) + preRun: func(sdkCtx sdk.Context) uint64 { + pId, err := submitProposal(app, sdkCtx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr) require.NoError(t, err) _, err = app.GroupKeeper.WithdrawProposal(ctx, &group.MsgWithdrawProposal{ @@ -148,8 +325,8 @@ func TestEndBlocker(t *testing.T) { expExecutorResult: group.PROPOSAL_RESULT_UNFINALIZED, }, "tally of closed proposal (with votes)": { - preRun: func(sdkCtx types.Context) uint64 { - pId, err := submitProposalAndVote(app, ctx, []types.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES) + preRun: func(sdkCtx sdk.Context) uint64 { + pId, err := submitProposalAndVote(app, ctx, []sdk.Msg{msgSend}, proposers, groupPolicyAddr, group.VOTE_OPTION_YES) require.NoError(t, err) _, err = app.GroupKeeper.WithdrawProposal(ctx, &group.MsgWithdrawProposal{ @@ -193,8 +370,8 @@ func TestEndBlocker(t *testing.T) { } func submitProposal( - app *simapp.SimApp, ctx context.Context, msgs []types.Msg, - proposers []string, groupPolicyAddr types.AccAddress) (uint64, error) { + app *simapp.SimApp, ctx context.Context, msgs []sdk.Msg, + proposers []string, groupPolicyAddr sdk.AccAddress) (uint64, error) { proposalReq := &group.MsgSubmitProposal{ Address: groupPolicyAddr.String(), Proposers: proposers, @@ -213,13 +390,12 @@ func submitProposal( } func submitProposalAndVote( - app *simapp.SimApp, ctx context.Context, msgs []types.Msg, - proposers []string, groupPolicyAddr types.AccAddress, voteOption group.VoteOption) (uint64, error) { + app *simapp.SimApp, ctx context.Context, msgs []sdk.Msg, + proposers []string, groupPolicyAddr sdk.AccAddress, voteOption group.VoteOption) (uint64, error) { myProposalID, err := submitProposal(app, ctx, msgs, proposers, groupPolicyAddr) if err != nil { return 0, err } - _, err = app.GroupKeeper.Vote(ctx, &group.MsgVote{ ProposalId: myProposalID, Voter: proposers[0], @@ -228,6 +404,5 @@ func submitProposalAndVote( if err != nil { return 0, err } - return myProposalID, nil } diff --git a/x/group/proposal_test.go b/x/group/proposal_test.go new file mode 100644 index 000000000..c2f3128b9 --- /dev/null +++ b/x/group/proposal_test.go @@ -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) +} diff --git a/x/group/simulation/genesis.go b/x/group/simulation/genesis.go index 1f5df3a2d..afd283a06 100644 --- a/x/group/simulation/genesis.go +++ b/x/group/simulation/genesis.go @@ -72,13 +72,13 @@ func getGroupPolicies(r *rand.Rand, simState *module.SimulationState) []*group.G return groupPolicies } -func getProposals(r *rand.Rand, simState *module.SimulationState) []*group.Proposal { +func getProposals(r *rand.Rand, simState *module.SimulationState, groupPolicies []*group.GroupPolicyInfo) []*group.Proposal { proposals := make([]*group.Proposal, 3) proposers := []string{simState.Accounts[0].Address.String(), simState.Accounts[1].Address.String()} for i := 0; i < 3; i++ { - from, _ := simtypes.RandomAcc(r, simState.Accounts) + idx := r.Intn(len(groupPolicies)) + groupPolicyAddress := groupPolicies[idx].Address to, _ := simtypes.RandomAcc(r, simState.Accounts) - fromAddr := from.Address.String() submittedAt := time.Unix(0, 0) timeout := submittedAt.Add(time.Second * 1000).UTC() @@ -86,7 +86,7 @@ func getProposals(r *rand.Rand, simState *module.SimulationState) []*group.Propo proposal := &group.Proposal{ Id: uint64(i + 1), Proposers: proposers, - Address: fromAddr, + Address: groupPolicyAddress, GroupVersion: uint64(i + 1), GroupPolicyVersion: uint64(i + 1), Status: group.PROPOSAL_STATUS_SUBMITTED, @@ -103,7 +103,7 @@ func getProposals(r *rand.Rand, simState *module.SimulationState) []*group.Propo VotingPeriodEnd: timeout, } err := proposal.SetMsgs([]sdk.Msg{&banktypes.MsgSend{ - FromAddress: fromAddr, + FromAddress: groupPolicyAddress, ToAddress: to.Address.String(), Amount: sdk.NewCoins(sdk.NewInt64Coin("test", 10)), }}) @@ -163,7 +163,7 @@ func RandomizedGenState(simState *module.SimulationState) { func(r *rand.Rand) { members = getGroupMembers(r, simState.Accounts) }, ) - // group accounts + // group policies var groupPolicies []*group.GroupPolicyInfo simState.AppParams.GetOrGenerate( simState.Cdc, GroupPolicyInfo, &groupPolicies, simState.Rand, @@ -174,7 +174,7 @@ func RandomizedGenState(simState *module.SimulationState) { var proposals []*group.Proposal simState.AppParams.GetOrGenerate( simState.Cdc, GroupProposals, &proposals, simState.Rand, - func(r *rand.Rand) { proposals = getProposals(r, simState) }, + func(r *rand.Rand) { proposals = getProposals(r, simState, groupPolicies) }, ) // votes diff --git a/x/group/simulation/operations.go b/x/group/simulation/operations.go index 22ac6138c..4bcdcc67a 100644 --- a/x/group/simulation/operations.go +++ b/x/group/simulation/operations.go @@ -873,16 +873,6 @@ func SimulateMsgWithdrawProposal(ak group.AccountKeeper, return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, "no proposals found"), nil, nil } - // Ensure that group and group policy haven't been modified since the proposal submission. - if proposal.GroupPolicyVersion != groupPolicy.Version { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, "group policy has been modified"), nil, nil - } - - // Ensure the group hasn't been modified. - if proposal.GroupVersion != g.Version { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, "group has been modified"), nil, nil - } - // select a random proposer proposers := proposal.Proposers n := randIntInRange(r, len(proposers)) @@ -972,13 +962,11 @@ func SimulateMsgVote(ak group.AccountKeeper, return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "no proposals found"), nil, nil } - var proposal *group.Proposal proposalID := -1 for _, p := range proposals { if p.Status == group.PROPOSAL_STATUS_SUBMITTED { timeout := p.VotingPeriodEnd - proposal = p proposalID = int(p.Id) if timeout.Before(sdkCtx.BlockTime()) || timeout.Equal(sdkCtx.BlockTime()) { return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "voting period ended: skipping"), nil, nil @@ -992,14 +980,6 @@ func SimulateMsgVote(ak group.AccountKeeper, return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "no proposals found"), nil, nil } - // Ensure that group and group policy haven't been modified since the proposal submission. - if proposal.GroupPolicyVersion != groupPolicy.Version { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "group policy has been modified"), nil, nil - } - if proposal.GroupVersion != g.Version { - return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, "group has been modified"), nil, nil - } - // Ensure member hasn't already voted res, _ := k.VoteByProposalVoter(ctx, &group.QueryVoteByProposalVoterRequest{ Voter: acc.Address.String(), diff --git a/x/group/spec/01_concepts.md b/x/group/spec/01_concepts.md index b8e7af52d..0194f7184 100644 --- a/x/group/spec/01_concepts.md +++ b/x/group/spec/01_concepts.md @@ -26,14 +26,19 @@ those "sub-accounts" using the `x/authz` module. ## Decision Policy -A decision policy is the mechanism by which members of a group can vote on -proposals. +A decision policy is the mechanism by which members of a group can vote on +proposals, as well as the rules that dictate whether a proposal should pass +or not based on its tally outcome. -All decision policies generally would have a minimum and maximum voting window. -The minimum voting window is the minimum amount of time that must pass in order -for a proposal to potentially pass, and it may be set to 0. The maximum voting -window is the maximum time that a proposal may be voted on before it is closed. -Both of these values must be less than a chain-wide max voting window parameter. +All decision policies generally would have a mininum execution perdio and a +maximum voting window. The minimum execution period is the minimum amount of time +that must pass in order for a proposal to potentially be executed, and it may +be set to 0. The maximum voting window is the maximum time that a proposal may +be voted on before it is closed. + +The chain developer also defines an app-wide maximum execution period, which is +the maximum amount of time after a proposal's voting period end where users are +allowed to execute a proposal. ### Threshold decision policy @@ -41,6 +46,14 @@ A threshold decision policy defines a threshold of yes votes (based on a tally of voter weights) that must be achieved in order for a proposal to pass. For this decision policy, abstain and veto are simply treated as no's. +### Percentage decision policy + +A percentage decision policy is similar to a threshold decision policy, except +that the threshold is not defined as a constant weight, but as a percentage. +It's more suited for groups where the group members' weights can be updated, as +the percentage threshold stays the same, and doesn't depend on how those member +weights get updated. + ## Proposal Any member of a group can submit a proposal for a group policy account to decide upon. @@ -51,24 +64,56 @@ passes as well as any metadata associated with the proposal. There are four choices to choose while voting - yes, no, abstain and veto. Not all decision policies will support them. Votes can contain some optional metadata. -During the voting window, accounts that have already voted may change their vote. In the current implementation, the voting window begins as soon as a proposal is submitted. +## Tallying + +Tallying is the counting of all votes on a proposal. It happens only once in +the lifecycle of a proposal, but can be triggered by two factors, whichever +happens first: + +- either someone tries to execute the proposal (see next section), which can + happen on a `Msg/Exec` transaction, or a `Msg/{SubmitProposal,Vote}` + transaction with the `Exec` field set. When a proposal execution is attempted, + a tally is done first to make sure the proposal passes. +- or on `EndBlock` when the proposal's voting period end just passed. + +If the tally result passes the decision policy's rules, then the proposal is +marked as `STATUS_CLOSED`, so no more voting is allowed anymore, and the tally +result is persisted to state. + ## Executing Proposals +Proposals are executed only when the tallying is done, and the group account's +decision policy allows the proposal to pass based on the tally outcome. + Proposals will not be automatically executed by the chain in this current design, but rather a user must submit a `Msg/Exec` transaction to attempt to execute the proposal based on the current votes and decision policy. It's also possible to try to execute a proposal immediately on creation or on -new votes using the `Exec` field of `Msg/CreateProposal` and `Msg/Vote` requests. +new votes using the `Exec` field of `Msg/SubmitProposal` and `Msg/Vote` requests. In the former case, proposers signatures are considered as yes votes. For now, if the proposal can't be executed, it'll still be opened for new votes and could be executed later on. -### Changing Group Membership +## Pruning -In the current implementation, changing a group's membership (adding or removing members or changing their weight) -will cause all existing proposals for group policy accounts linked to this group -to be invalidated. They will simply fail if someone calls `Msg/Exec` and will -eventually be garbage collected. +Proposals and votes are automatically pruned to avoid state bloat. + +Votes are pruned: + +- either after a successful tally, i.e. a tally whose result passes the decision + policy's rules, which can be trigged by a `Msg/Exec` or a + `Msg/{SubmitProposal,Vote}` with the `Exec` field, +- or on `EndBlock` right after the proposal's voting period end, + +whichever happens first. + +Proposals are pruned: + +- either after a successful proposal execution, +- or on `EndBlock` right after the proposal's `voting_period_end` + + `max_execution_period` (defined as an app-wide configuration) is passed, + +whichever happens first. diff --git a/x/group/spec/02_state.md b/x/group/spec/02_state.md index 55be76d87..a26d16313 100644 --- a/x/group/spec/02_state.md +++ b/x/group/spec/02_state.md @@ -80,10 +80,12 @@ The second `0x1` corresponds to the ORM `sequenceStorageKey`. `proposalByGroupPolicyIndex` allows to retrieve proposals by group policy account address: `0x32 | len([]byte(account.Address)) | []byte(account.Address) | BigEndian(ProposalId) -> []byte()`. -### proposalByProposerIndex +### ProposalsByVotingPeriodEndIndex -`proposalByProposerIndex` allows to retrieve proposals by proposer address: -`0x33 | len([]byte(proposer.Address)) | []byte(proposer.Address) | BigEndian(ProposalId) -> []byte()`. +`proposalsByVotingPeriodEndIndex` allows to retrieve proposals sorted by chronological `voting_period_end`: +`0x33 | sdk.FormatTimeBytes(proposal.VotingPeriodEnd) | BigEndian(ProposalId) -> []byte()`. + +This index is used when tallying the proposal votes at the end of the voting period, and for pruning proposals at `VotingPeriodEnd + MaxExecutionPeriod`. ## Vote Table diff --git a/x/group/spec/03_messages.md b/x/group/spec/03_messages.md index 20ba40cfb..dc5267250 100644 --- a/x/group/spec/03_messages.md +++ b/x/group/spec/03_messages.md @@ -124,8 +124,6 @@ A proposal can be executed with the `MsgExec`. The messages that are part of this proposal won't be executed if: -* the group has been modified before tally. -* the group policy has been modified before tally. * the proposal has not been accepted. * the proposal status is not closed. * the proposal has already been successfully executed.