491 lines
17 KiB
Go
491 lines
17 KiB
Go
package simulation
|
|
|
|
import (
|
|
"context"
|
|
"slices"
|
|
"strconv"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/cosmos/cosmos-sdk/testutil/simsx"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
"github.com/cosmos/cosmos-sdk/x/group"
|
|
"github.com/cosmos/cosmos-sdk/x/group/keeper"
|
|
)
|
|
|
|
func MsgCreateGroupFactory() simsx.SimMsgFactoryFn[*group.MsgCreateGroup] {
|
|
return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgCreateGroup) {
|
|
admin := testData.AnyAccount(reporter, simsx.WithSpendableBalance())
|
|
members := genGroupMembersX(testData, reporter)
|
|
msg := &group.MsgCreateGroup{Admin: admin.AddressBech32, Members: members, Metadata: testData.Rand().StringN(10)}
|
|
return []simsx.SimAccount{admin}, msg
|
|
}
|
|
}
|
|
|
|
func MsgCreateGroupPolicyFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgCreateGroupPolicy] {
|
|
return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgCreateGroupPolicy) {
|
|
groupInfo := randomGroupX(ctx, k, testData, reporter, s)
|
|
groupAdmin := testData.GetAccount(reporter, groupInfo.Admin)
|
|
if reporter.IsSkipped() {
|
|
return nil, nil
|
|
}
|
|
groupID := groupInfo.Id
|
|
|
|
r := testData.Rand()
|
|
msg, err := group.NewMsgCreateGroupPolicy(
|
|
groupAdmin.Address,
|
|
groupID,
|
|
r.StringN(10),
|
|
&group.ThresholdDecisionPolicy{
|
|
Threshold: strconv.Itoa(r.IntInRange(1, 10)),
|
|
Windows: &group.DecisionPolicyWindows{
|
|
VotingPeriod: time.Second * time.Duration(30*24*60*60),
|
|
},
|
|
},
|
|
)
|
|
if err != nil {
|
|
reporter.Skip(err.Error())
|
|
return nil, nil
|
|
}
|
|
return []simsx.SimAccount{groupAdmin}, msg
|
|
}
|
|
}
|
|
|
|
func MsgCreateGroupWithPolicyFactory() simsx.SimMsgFactoryFn[*group.MsgCreateGroupWithPolicy] {
|
|
return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgCreateGroupWithPolicy) {
|
|
admin := testData.AnyAccount(reporter, simsx.WithSpendableBalance())
|
|
members := genGroupMembersX(testData, reporter)
|
|
r := testData.Rand()
|
|
msg := &group.MsgCreateGroupWithPolicy{
|
|
Admin: admin.AddressBech32,
|
|
Members: members,
|
|
GroupMetadata: r.StringN(10),
|
|
GroupPolicyMetadata: r.StringN(10),
|
|
GroupPolicyAsAdmin: r.Float32() < 0.5,
|
|
}
|
|
decisionPolicy := &group.ThresholdDecisionPolicy{
|
|
Threshold: strconv.Itoa(r.IntInRange(1, 10)),
|
|
Windows: &group.DecisionPolicyWindows{
|
|
VotingPeriod: time.Second * time.Duration(30*24*60*60),
|
|
},
|
|
}
|
|
if err := msg.SetDecisionPolicy(decisionPolicy); err != nil {
|
|
reporter.Skip(err.Error())
|
|
return nil, nil
|
|
}
|
|
return []simsx.SimAccount{admin}, msg
|
|
}
|
|
}
|
|
|
|
func MsgWithdrawProposalFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgWithdrawProposal] {
|
|
return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgWithdrawProposal) {
|
|
groupInfo, groupPolicy := randomGroupPolicyX(ctx, testData, reporter, k, s)
|
|
if reporter.IsSkipped() {
|
|
return nil, nil
|
|
}
|
|
policy, err := groupPolicy.GetDecisionPolicy()
|
|
if err != nil {
|
|
reporter.Skip(err.Error())
|
|
return nil, nil
|
|
}
|
|
err = policy.Validate(*groupInfo, group.DefaultConfig())
|
|
if err != nil {
|
|
reporter.Skip(err.Error())
|
|
return nil, nil
|
|
}
|
|
proposalsResult, err := k.ProposalsByGroupPolicy(ctx,
|
|
&group.QueryProposalsByGroupPolicyRequest{Address: groupPolicy.Address},
|
|
)
|
|
if err != nil {
|
|
reporter.Skip(err.Error())
|
|
return nil, nil
|
|
}
|
|
|
|
now := simsx.BlockTime(ctx)
|
|
proposal := simsx.First(proposalsResult.GetProposals(), func(p *group.Proposal) bool {
|
|
return p.Status == group.PROPOSAL_STATUS_SUBMITTED && p.VotingPeriodEnd.After(now)
|
|
})
|
|
if proposal == nil {
|
|
reporter.Skip("no proposal found")
|
|
return nil, nil
|
|
}
|
|
// select a random proposer
|
|
r := testData.Rand()
|
|
proposer := testData.GetAccount(reporter, simsx.OneOf(r, (*proposal).Proposers))
|
|
|
|
msg := &group.MsgWithdrawProposal{
|
|
ProposalId: (*proposal).Id,
|
|
Address: proposer.AddressBech32,
|
|
}
|
|
return []simsx.SimAccount{proposer}, msg
|
|
}
|
|
}
|
|
|
|
func MsgVoteFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgVote] {
|
|
return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgVote) {
|
|
groupInfo, groupPolicy := randomGroupPolicyX(ctx, testData, reporter, k, s)
|
|
if reporter.IsSkipped() {
|
|
return nil, nil
|
|
}
|
|
proposalsResult, err := k.ProposalsByGroupPolicy(ctx,
|
|
&group.QueryProposalsByGroupPolicyRequest{Address: groupPolicy.Address},
|
|
)
|
|
if err != nil {
|
|
reporter.Skip(err.Error())
|
|
return nil, nil
|
|
}
|
|
|
|
now := simsx.BlockTime(ctx)
|
|
proposal := simsx.First(proposalsResult.GetProposals(), func(p *group.Proposal) bool {
|
|
return p.Status == group.PROPOSAL_STATUS_SUBMITTED && p.VotingPeriodEnd.After(now)
|
|
})
|
|
if proposal == nil {
|
|
reporter.Skip("no proposal found")
|
|
return nil, nil
|
|
}
|
|
// select a random member
|
|
r := testData.Rand()
|
|
res, err := k.GroupMembers(ctx, &group.QueryGroupMembersRequest{GroupId: groupInfo.Id})
|
|
if err != nil {
|
|
reporter.Skip(err.Error())
|
|
return nil, nil
|
|
}
|
|
if len(res.Members) == 0 {
|
|
reporter.Skip("group has no members")
|
|
return nil, nil
|
|
}
|
|
voter := testData.GetAccount(reporter, simsx.OneOf(r, res.Members).Member.Address)
|
|
vRes, err := k.VotesByProposal(ctx, &group.QueryVotesByProposalRequest{
|
|
ProposalId: (*proposal).Id,
|
|
})
|
|
if err != nil {
|
|
reporter.Skip(err.Error())
|
|
return nil, nil
|
|
}
|
|
if slices.ContainsFunc(vRes.Votes, func(v *group.Vote) bool { return v.Voter == voter.AddressBech32 }) {
|
|
reporter.Skip("voted already on proposal")
|
|
return nil, nil
|
|
}
|
|
|
|
msg := &group.MsgVote{
|
|
ProposalId: (*proposal).Id,
|
|
Voter: voter.AddressBech32,
|
|
Option: group.VOTE_OPTION_YES,
|
|
Metadata: r.StringN(10),
|
|
}
|
|
return []simsx.SimAccount{voter}, msg
|
|
}
|
|
}
|
|
|
|
func MsgSubmitProposalFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgSubmitProposal] {
|
|
return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgSubmitProposal) {
|
|
groupInfo, groupPolicy := randomGroupPolicyX(ctx, testData, reporter, k, s)
|
|
if reporter.IsSkipped() {
|
|
return nil, nil
|
|
}
|
|
// Return a no-op if we know the proposal cannot be created
|
|
policy, err := groupPolicy.GetDecisionPolicy()
|
|
if err != nil {
|
|
reporter.Skip(err.Error())
|
|
return nil, nil
|
|
}
|
|
|
|
if err = policy.Validate(*groupInfo, group.DefaultConfig()); err != nil {
|
|
reporter.Skip(err.Error())
|
|
return nil, nil
|
|
}
|
|
|
|
// Pick a random member from the group
|
|
r := testData.Rand()
|
|
res, err := k.GroupMembers(ctx, &group.QueryGroupMembersRequest{GroupId: groupInfo.Id})
|
|
if err != nil {
|
|
reporter.Skip(err.Error())
|
|
return nil, nil
|
|
}
|
|
if len(res.Members) == 0 {
|
|
reporter.Skip("group has no members")
|
|
return nil, nil
|
|
}
|
|
proposer := testData.GetAccount(reporter, simsx.OneOf(r, res.Members).Member.Address)
|
|
|
|
msg := &group.MsgSubmitProposal{
|
|
GroupPolicyAddress: groupPolicy.Address,
|
|
Proposers: []string{proposer.AddressBech32},
|
|
Metadata: r.StringN(10),
|
|
Title: "Test Proposal",
|
|
Summary: "Summary of the proposal",
|
|
}
|
|
return []simsx.SimAccount{proposer}, msg
|
|
}
|
|
}
|
|
|
|
func MsgExecFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgExec] {
|
|
return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgExec) {
|
|
groupPolicy, policyAdmin := randomGroupPolicyWithAdmin(ctx, testData, reporter, k, s)
|
|
if reporter.IsSkipped() {
|
|
return nil, nil
|
|
}
|
|
proposalsResult, err := k.ProposalsByGroupPolicy(ctx,
|
|
&group.QueryProposalsByGroupPolicyRequest{Address: groupPolicy.Address},
|
|
)
|
|
if err != nil {
|
|
reporter.Skip(err.Error())
|
|
return nil, nil
|
|
}
|
|
|
|
proposal := simsx.First(proposalsResult.GetProposals(), func(p *group.Proposal) bool {
|
|
return p.Status == group.PROPOSAL_STATUS_ACCEPTED
|
|
})
|
|
if proposal == nil {
|
|
reporter.Skip("no proposal found")
|
|
return nil, nil
|
|
}
|
|
|
|
msg := &group.MsgExec{
|
|
ProposalId: (*proposal).Id,
|
|
Executor: policyAdmin.AddressBech32,
|
|
}
|
|
return []simsx.SimAccount{policyAdmin}, msg
|
|
}
|
|
}
|
|
|
|
func randomGroupPolicyWithAdmin(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter, k keeper.Keeper, s *SharedState) (*group.GroupPolicyInfo, simsx.SimAccount) {
|
|
for range 5 {
|
|
_, groupPolicy := randomGroupPolicyX(ctx, testData, reporter, k, s)
|
|
if groupPolicy != nil && testData.HasAccount(groupPolicy.Admin) {
|
|
return groupPolicy, testData.GetAccount(reporter, groupPolicy.Admin)
|
|
}
|
|
}
|
|
reporter.Skip("no group policy found with a sims account")
|
|
return nil, simsx.SimAccount{}
|
|
}
|
|
|
|
func MsgUpdateGroupMetadataFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgUpdateGroupMetadata] {
|
|
return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgUpdateGroupMetadata) {
|
|
groupInfo := randomGroupX(ctx, k, testData, reporter, s)
|
|
groupAdmin := testData.GetAccount(reporter, groupInfo.Admin)
|
|
if reporter.IsSkipped() {
|
|
return nil, nil
|
|
}
|
|
msg := &group.MsgUpdateGroupMetadata{
|
|
GroupId: groupInfo.Id,
|
|
Admin: groupAdmin.AddressBech32,
|
|
Metadata: testData.Rand().StringN(10),
|
|
}
|
|
return []simsx.SimAccount{groupAdmin}, msg
|
|
}
|
|
}
|
|
|
|
func MsgUpdateGroupAdminFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgUpdateGroupAdmin] {
|
|
return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgUpdateGroupAdmin) {
|
|
groupInfo := randomGroupX(ctx, k, testData, reporter, s)
|
|
groupAdmin := testData.GetAccount(reporter, groupInfo.Admin)
|
|
if reporter.IsSkipped() {
|
|
return nil, nil
|
|
}
|
|
newAdmin := testData.AnyAccount(reporter, simsx.ExcludeAccounts(groupAdmin))
|
|
msg := &group.MsgUpdateGroupAdmin{
|
|
GroupId: groupInfo.Id,
|
|
Admin: groupAdmin.AddressBech32,
|
|
NewAdmin: newAdmin.AddressBech32,
|
|
}
|
|
return []simsx.SimAccount{groupAdmin}, msg
|
|
}
|
|
}
|
|
|
|
func MsgUpdateGroupMembersFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgUpdateGroupMembers] {
|
|
return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgUpdateGroupMembers) {
|
|
groupInfo := randomGroupX(ctx, k, testData, reporter, s)
|
|
groupAdmin := testData.GetAccount(reporter, groupInfo.Admin)
|
|
if reporter.IsSkipped() {
|
|
return nil, nil
|
|
}
|
|
res, err := k.GroupMembers(ctx, &group.QueryGroupMembersRequest{GroupId: groupInfo.Id})
|
|
if err != nil {
|
|
reporter.Skip("group members not found")
|
|
return nil, nil
|
|
}
|
|
oldMemberAddrs := simsx.Collect(res.Members, func(a *group.GroupMember) string { return a.Member.Address })
|
|
members := genGroupMembersX(testData, reporter, simsx.ExcludeAddresses(oldMemberAddrs...))
|
|
if len(res.Members) > 1 {
|
|
// set existing random group member weight to zero to remove from the group
|
|
obsoleteMember := simsx.OneOf(testData.Rand(), res.Members)
|
|
obsoleteMember.Member.Weight = "0"
|
|
members = append(members, group.MemberToMemberRequest(obsoleteMember.Member))
|
|
}
|
|
msg := &group.MsgUpdateGroupMembers{
|
|
GroupId: groupInfo.Id,
|
|
Admin: groupAdmin.AddressBech32,
|
|
MemberUpdates: members,
|
|
}
|
|
return []simsx.SimAccount{groupAdmin}, msg
|
|
}
|
|
}
|
|
|
|
func MsgUpdateGroupPolicyAdminFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgUpdateGroupPolicyAdmin] {
|
|
return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgUpdateGroupPolicyAdmin) {
|
|
groupPolicy, policyAdmin := randomGroupPolicyWithAdmin(ctx, testData, reporter, k, s)
|
|
if reporter.IsSkipped() {
|
|
return nil, nil
|
|
}
|
|
newAdmin := testData.AnyAccount(reporter, simsx.ExcludeAccounts(policyAdmin))
|
|
msg := &group.MsgUpdateGroupPolicyAdmin{
|
|
Admin: policyAdmin.AddressBech32,
|
|
GroupPolicyAddress: groupPolicy.Address,
|
|
NewAdmin: newAdmin.AddressBech32,
|
|
}
|
|
return []simsx.SimAccount{policyAdmin}, msg
|
|
}
|
|
}
|
|
|
|
func MsgUpdateGroupPolicyDecisionPolicyFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgUpdateGroupPolicyDecisionPolicy] {
|
|
return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgUpdateGroupPolicyDecisionPolicy) {
|
|
groupPolicy, policyAdmin := randomGroupPolicyWithAdmin(ctx, testData, reporter, k, s)
|
|
if reporter.IsSkipped() {
|
|
return nil, nil
|
|
}
|
|
r := testData.Rand()
|
|
policyAddr, err := k.AddressCodec().StringToBytes(groupPolicy.Address)
|
|
if err != nil {
|
|
reporter.Skip(err.Error())
|
|
return nil, nil
|
|
}
|
|
msg, err := group.NewMsgUpdateGroupPolicyDecisionPolicy(policyAdmin.Address, policyAddr, &group.ThresholdDecisionPolicy{
|
|
Threshold: strconv.Itoa(r.IntInRange(1, 10)),
|
|
Windows: &group.DecisionPolicyWindows{
|
|
VotingPeriod: time.Second * time.Duration(r.IntInRange(100, 1000)),
|
|
},
|
|
})
|
|
if err != nil {
|
|
reporter.Skip(err.Error())
|
|
return nil, nil
|
|
}
|
|
return []simsx.SimAccount{policyAdmin}, msg
|
|
}
|
|
}
|
|
|
|
func MsgUpdateGroupPolicyMetadataFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgUpdateGroupPolicyMetadata] {
|
|
return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgUpdateGroupPolicyMetadata) {
|
|
groupPolicy, policyAdmin := randomGroupPolicyWithAdmin(ctx, testData, reporter, k, s)
|
|
if reporter.IsSkipped() {
|
|
return nil, nil
|
|
}
|
|
msg := &group.MsgUpdateGroupPolicyMetadata{
|
|
Admin: policyAdmin.AddressBech32,
|
|
GroupPolicyAddress: groupPolicy.Address,
|
|
Metadata: testData.Rand().StringN(10),
|
|
}
|
|
return []simsx.SimAccount{policyAdmin}, msg
|
|
}
|
|
}
|
|
|
|
func MsgLeaveGroupFactory(k keeper.Keeper, s *SharedState) simsx.SimMsgFactoryFn[*group.MsgLeaveGroup] {
|
|
return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *group.MsgLeaveGroup) {
|
|
groupInfo := randomGroupX(ctx, k, testData, reporter, s)
|
|
if reporter.IsSkipped() {
|
|
return nil, nil
|
|
}
|
|
res, err := k.GroupMembers(ctx, &group.QueryGroupMembersRequest{GroupId: groupInfo.Id})
|
|
if err != nil {
|
|
reporter.Skip("group members not found")
|
|
return nil, nil
|
|
}
|
|
if len(res.Members) == 0 {
|
|
reporter.Skip("group has no members")
|
|
return nil, nil
|
|
}
|
|
anyMember := simsx.OneOf(testData.Rand(), res.Members)
|
|
leaver := testData.GetAccount(reporter, anyMember.Member.Address)
|
|
msg := &group.MsgLeaveGroup{
|
|
GroupId: groupInfo.Id,
|
|
Address: leaver.AddressBech32,
|
|
}
|
|
return []simsx.SimAccount{leaver}, msg
|
|
}
|
|
}
|
|
|
|
func genGroupMembersX(testData *simsx.ChainDataSource, reporter simsx.SimulationReporter, filters ...simsx.SimAccountFilter) []group.MemberRequest {
|
|
r := testData.Rand()
|
|
membersCount := r.Intn(5) + 1
|
|
members := make([]group.MemberRequest, membersCount)
|
|
uniqueAccountsFilter := simsx.UniqueAccounts()
|
|
for i := 0; i < membersCount && !reporter.IsSkipped(); i++ {
|
|
m := testData.AnyAccount(reporter, append(filters, uniqueAccountsFilter)...)
|
|
members[i] = group.MemberRequest{
|
|
Address: m.AddressBech32,
|
|
Weight: strconv.Itoa(r.IntInRange(1, 10)),
|
|
Metadata: r.StringN(10),
|
|
}
|
|
}
|
|
return members
|
|
}
|
|
|
|
func randomGroupX(ctx context.Context, k keeper.Keeper, testdata *simsx.ChainDataSource, reporter simsx.SimulationReporter, s *SharedState) *group.GroupInfo {
|
|
r := testdata.Rand()
|
|
groupID := k.GetGroupSequence(sdk.UnwrapSDKContext(ctx))
|
|
if initialGroupID := s.getMinGroupID(); initialGroupID == unsetGroupID {
|
|
s.setMinGroupID(groupID)
|
|
} else if initialGroupID < groupID {
|
|
groupID = r.Uint64InRange(initialGroupID+1, groupID+1)
|
|
}
|
|
|
|
// when groupID is 0, it proves that SimulateMsgCreateGroup has never been called. that is, no group exists in the chain
|
|
if groupID == 0 {
|
|
reporter.Skip("no group exists")
|
|
return nil
|
|
}
|
|
|
|
res, err := k.GroupInfo(ctx, &group.QueryGroupInfoRequest{GroupId: groupID})
|
|
if err != nil {
|
|
reporter.Skip(err.Error())
|
|
return nil
|
|
}
|
|
return res.Info
|
|
}
|
|
|
|
func randomGroupPolicyX(
|
|
ctx context.Context,
|
|
testdata *simsx.ChainDataSource,
|
|
reporter simsx.SimulationReporter,
|
|
k keeper.Keeper,
|
|
s *SharedState,
|
|
) (*group.GroupInfo, *group.GroupPolicyInfo) {
|
|
for range 5 {
|
|
groupInfo := randomGroupX(ctx, k, testdata, reporter, s)
|
|
if reporter.IsSkipped() {
|
|
return nil, nil
|
|
}
|
|
groupID := groupInfo.Id
|
|
result, err := k.GroupPoliciesByGroup(ctx, &group.QueryGroupPoliciesByGroupRequest{GroupId: groupID})
|
|
if err != nil {
|
|
reporter.Skip(err.Error())
|
|
return nil, nil
|
|
}
|
|
if len(result.GroupPolicies) != 0 {
|
|
return groupInfo, simsx.OneOf(testdata.Rand(), result.GroupPolicies)
|
|
}
|
|
}
|
|
reporter.Skip("no group policies")
|
|
return nil, nil
|
|
}
|
|
|
|
// SharedState shared state between message invocations
|
|
type SharedState struct {
|
|
minGroupID atomic.Uint64
|
|
}
|
|
|
|
// NewSharedState constructor
|
|
func NewSharedState() *SharedState {
|
|
r := &SharedState{}
|
|
r.setMinGroupID(unsetGroupID)
|
|
return r
|
|
}
|
|
|
|
func (s *SharedState) getMinGroupID() uint64 {
|
|
return s.minGroupID.Load()
|
|
}
|
|
|
|
func (s *SharedState) setMinGroupID(id uint64) {
|
|
s.minGroupID.Store(id)
|
|
}
|