diff --git a/app/app.go b/app/app.go index 46282b2f7..f3581f2b8 100644 --- a/app/app.go +++ b/app/app.go @@ -541,11 +541,13 @@ func NewBandApp( app.MintKeeper, app.DistrKeeper, app.GovKeeper, + app.GroupKeeper, app.OracleKeeper, app.ICAHostKeeper, app.IBCKeeper.ClientKeeper, app.IBCKeeper.ConnectionKeeper, app.IBCKeeper.ChannelKeeper, + keys[group.StoreKey], emitterFlag, false, )) diff --git a/flusher/flusher/db.py b/flusher/flusher/db.py index bf1b6e090..5a084c081 100644 --- a/flusher/flusher/db.py +++ b/flusher/flusher/db.py @@ -485,6 +485,78 @@ def Column(*args, **kwargs): Column("last_update", CustomDateTime, index=True), ) + +""" +This section is mainly for tables of Group module +""" + +groups = sa.Table( + "groups", + metadata, + Column("id", sa.Integer, primary_key=True), + Column("version", sa.Integer, index=True), + Column("admin", sa.String, sa.ForeignKey("accounts.address")), + Column("metadata", sa.String), + Column("total_weight", sa.BigInteger), + Column("created_at", CustomDateTime), +) + +group_members = sa.Table( + "group_members", + metadata, + Column("group_id", sa.Integer, sa.ForeignKey("groups.id"), primary_key=True), + Column("account_id", sa.Integer, sa.ForeignKey("accounts.id"), primary_key=True), + Column("weight", sa.BigInteger), + Column("metadata", sa.String), + Column("added_at", CustomDateTime), +) + +group_policies = sa.Table( + "group_policies", + metadata, + Column("address", sa.String, primary_key=True), + Column("type", sa.String), + Column("group_id", sa.Integer, sa.ForeignKey("groups.id")), + Column("admin", sa.String, sa.ForeignKey("accounts.address")), + Column("metadata", sa.String), + Column("version", sa.Integer), + Column("decision_policy", sa.JSON), + Column("created_at", CustomDateTime), +) + +group_proposals = sa.Table( + "group_proposals", + metadata, + Column("id", sa.Integer, primary_key=True), + Column("group_policy_address", sa.String, sa.ForeignKey("group_policies.address")), + Column("metadata", sa.String), + Column("proposers", sa.String), + Column("submit_time", CustomDateTime), + Column("group_id", sa.Integer, sa.ForeignKey("groups.id")), + Column("group_version", sa.Integer, index=True), + Column("group_policy_version", sa.Integer, index=True), + Column("status", sa.String), + Column("yes_vote", sa.BigInteger, nullable=True), + Column("no_vote", sa.BigInteger, nullable=True), + Column("no_with_veto_vote", sa.BigInteger, nullable=True), + Column("abstain_vote", sa.BigInteger, nullable=True), + Column("voting_period_end", CustomDateTime), + Column("executor_result", sa.String), + Column("messages", sa.JSON), + Column("title", sa.String), + Column("summary", sa.String), +) + +group_votes = sa.Table( + "group_votes", + metadata, + Column("group_proposal_id", sa.Integer, sa.ForeignKey("group_proposals.id"), primary_key=True), + Column("voter_id", sa.Integer, sa.ForeignKey("accounts.id"), primary_key=True), + Column("option", sa.String), + Column("metadata", sa.String), + Column("submit_time", CustomDateTime), +) + relayer_tx_stat_days = sa.Table( "relayer_tx_stat_days", metadata, diff --git a/flusher/flusher/handler.py b/flusher/flusher/handler.py index 6fe84ba57..fce669cbe 100644 --- a/flusher/flusher/handler.py +++ b/flusher/flusher/handler.py @@ -38,6 +38,11 @@ counterparty_chains, connections, channels, + groups, + group_members, + group_policies, + group_proposals, + group_votes, relayer_tx_stat_days, ) @@ -85,6 +90,9 @@ def get_data_source_id(self, id): def get_oracle_script_id(self, id): return self.conn.execute(select([oracle_scripts.c.id]).where(oracle_scripts.c.id == id)).scalar() + def get_group_id_from_policy_address(self, address): + return self.conn.execute(select([group_policies.c.group_id]).where(group_policies.c.address == address)).scalar() + def get_ibc_received_txs(self, date, port, channel, address): msg = {"date": date, "port": port, "channel": channel, "address": address} condition = True @@ -140,6 +148,47 @@ def handle_new_data_source(self, msg): self.conn.execute(data_sources.insert(), msg) self.init_data_source_request_count(msg["id"]) + def handle_new_group(self, msg): + self.conn.execute(groups.insert(), msg) + + def handle_new_group_member(self, msg): + msg["account_id"] = self.get_account_id(msg["address"]) + del msg["address"] + self.conn.execute(group_members.insert(), msg) + + def handle_new_group_policy(self, msg): + self.get_account_id(msg["address"]) + self.conn.execute(group_policies.insert(), msg) + + def handle_new_group_proposal(self, msg): + msg["group_id"] = self.get_group_id_from_policy_address(msg["group_policy_address"]) + self.conn.execute(group_proposals.insert(), msg) + + def handle_new_group_vote(self, msg): + msg["voter_id"] = self.get_account_id(msg["voter_address"]) + del msg["voter_address"] + self.conn.execute(group_votes.insert(), msg) + + def handle_update_group(self, msg): + self.conn.execute(groups.update().where(groups.c.id == msg["id"]).values(**msg)) + + def handle_remove_group_member(self, msg): + account_id = self.get_account_id(msg["address"]) + self.conn.execute(group_members.delete().where((group_members.c.group_id == msg["group_id"]) & (group_members.c.account_id == account_id))) + + def handle_remove_group_members_by_group_id(self, msg): + self.conn.execute(group_members.delete().where(group_members.c.group_id == msg["group_id"])) + + def handle_update_group_policy(self, msg): + self.conn.execute(group_policies.update().where(group_policies.c.address == msg["address"]).values(**msg)) + + def handle_update_group_proposal(self, msg): + msg["group_id"] = self.get_group_id_from_policy_address(msg["group_policy_address"]) + self.conn.execute(group_proposals.update().where(group_proposals.c.id == msg["id"]).values(**msg)) + + def handle_update_group_proposal_by_id(self, msg): + self.conn.execute(group_proposals.update().where(group_proposals.c.id == msg["id"]).values(**msg)) + def handle_set_data_source(self, msg): msg["transaction_id"] = self.get_transaction_id(msg["tx_hash"]) del msg["tx_hash"] diff --git a/hasura/hasura-metadata/tables.yaml b/hasura/hasura-metadata/tables.yaml index 1f2e87d04..51491dcca 100644 --- a/hasura/hasura-metadata/tables.yaml +++ b/hasura/hasura-metadata/tables.yaml @@ -48,6 +48,34 @@ table: name: deposits schema: public + - name: group_members + using: + foreign_key_constraint_on: + column: account_id + table: + name: group_members + schema: public + - name: group_policies + using: + foreign_key_constraint_on: + column: admin + table: + name: group_policies + schema: public + - name: group_votes + using: + foreign_key_constraint_on: + column: voter_id + table: + name: group_votes + schema: public + - name: groups + using: + foreign_key_constraint_on: + column: admin + table: + name: groups + schema: public - name: proposals using: foreign_key_constraint_on: @@ -290,6 +318,98 @@ - name: transaction using: foreign_key_constraint_on: tx_id +- table: + name: group_members + schema: public + object_relationships: + - name: account + using: + foreign_key_constraint_on: account_id + - name: group + using: + foreign_key_constraint_on: group_id +- table: + name: group_policies + schema: public + object_relationships: + - name: account + using: + foreign_key_constraint_on: admin + - name: group + using: + foreign_key_constraint_on: group_id + array_relationships: + - name: group_proposals + using: + foreign_key_constraint_on: + column: group_policy_address + table: + name: group_proposals + schema: public +- table: + name: group_proposals + schema: public + object_relationships: + - name: group + using: + foreign_key_constraint_on: group_id + - name: group_policy + using: + foreign_key_constraint_on: group_policy_address + - name: group_vote + using: + foreign_key_constraint_on: + column: group_proposal_id + table: + name: group_votes + schema: public + array_relationships: + - name: group_votes + using: + foreign_key_constraint_on: + column: group_proposal_id + table: + name: group_votes + schema: public +- table: + name: group_votes + schema: public + object_relationships: + - name: account + using: + foreign_key_constraint_on: voter_id + - name: group_proposal + using: + foreign_key_constraint_on: group_proposal_id +- table: + name: groups + schema: public + object_relationships: + - name: account + using: + foreign_key_constraint_on: admin + array_relationships: + - name: group_members + using: + foreign_key_constraint_on: + column: group_id + table: + name: group_members + schema: public + - name: group_policies + using: + foreign_key_constraint_on: + column: group_id + table: + name: group_policies + schema: public + - name: group_proposals + using: + foreign_key_constraint_on: + column: group_id + table: + name: group_proposals + schema: public - table: name: historical_bonded_token_on_validators schema: public diff --git a/hooks/emitter/decoder.go b/hooks/emitter/decoder.go index acd90e918..ba3cb6e44 100644 --- a/hooks/emitter/decoder.go +++ b/hooks/emitter/decoder.go @@ -11,6 +11,7 @@ import ( feegranttypes "github.com/cosmos/cosmos-sdk/x/feegrant" govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + "github.com/cosmos/cosmos-sdk/x/group" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" @@ -124,6 +125,34 @@ func DecodeMsg(msg sdk.Msg, detail common.JsDict) { DecodeMsgGrantAllowance(msg, detail) case *feegranttypes.MsgRevokeAllowance: DecodeMsgRevokeAllowance(msg, detail) + case *group.MsgCreateGroup: + DecodeGroupMsgCreateGroup(msg, detail) + case *group.MsgCreateGroupPolicy: + DecodeGroupMsgCreateGroupPolicy(msg, detail) + case *group.MsgCreateGroupWithPolicy: + DecodeGroupMsgCreateGroupWithPolicy(msg, detail) + case *group.MsgExec: + DecodeGroupMsgExec(msg, detail) + case *group.MsgLeaveGroup: + DecodeGroupMsgLeaveGroup(msg, detail) + case *group.MsgSubmitProposal: + DecodeGroupMsgSubmitProposal(msg, detail) + case *group.MsgUpdateGroupAdmin: + DecodeGroupMsgUpdateGroupAdmin(msg, detail) + case *group.MsgUpdateGroupMembers: + DecodeGroupMsgUpdateGroupMembers(msg, detail) + case *group.MsgUpdateGroupMetadata: + DecodeGroupMsgUpdateGroupMetadata(msg, detail) + case *group.MsgUpdateGroupPolicyAdmin: + DecodeGroupMsgUpdateGroupPolicyAdmin(msg, detail) + case *group.MsgUpdateGroupPolicyDecisionPolicy: + DecodeGroupMsgUpdateGroupPolicyDecisionPolicy(msg, detail) + case *group.MsgUpdateGroupPolicyMetadata: + DecodeGroupMsgUpdateGroupPolicyMetadata(msg, detail) + case *group.MsgVote: + DecodeGroupMsgVote(msg, detail) + case *group.MsgWithdrawProposal: + DecodeGroupMsgWithdrawProposal(msg, detail) default: break } @@ -618,3 +647,108 @@ func DecodeDescription(des stakingtypes.Description) common.JsDict { "website": des.GetWebsite(), } } + +func DecodeGroupMsgCreateGroup(msg *group.MsgCreateGroup, detail common.JsDict) { + detail["admin"] = msg.Admin + detail["members"] = msg.Members + detail["metadata"] = msg.Metadata +} + +func DecodeGroupMsgCreateGroupPolicy(msg *group.MsgCreateGroupPolicy, detail common.JsDict) { + detail["admin"] = msg.Admin + detail["group_id"] = msg.GroupId + detail["metadata"] = msg.Metadata + detail["decision_policy"] = msg.DecisionPolicy.GetCachedValue() +} + +func DecodeGroupMsgCreateGroupWithPolicy(msg *group.MsgCreateGroupWithPolicy, detail common.JsDict) { + detail["admin"] = msg.Admin + detail["members"] = msg.Members + detail["group_metadata"] = msg.GroupMetadata + detail["group_policy_metadata"] = msg.GroupPolicyMetadata + detail["group_policy_as_admin"] = msg.GroupPolicyAsAdmin + detail["decision_policy"] = msg.DecisionPolicy.GetCachedValue() +} + +func DecodeGroupMsgExec(msg *group.MsgExec, detail common.JsDict) { + detail["proposal_id"] = msg.ProposalId + detail["executor"] = msg.Executor +} + +func DecodeGroupMsgLeaveGroup(msg *group.MsgLeaveGroup, detail common.JsDict) { + detail["address"] = msg.Address + detail["group_id"] = msg.GroupId +} + +func DecodeGroupMsgSubmitProposal(msg *group.MsgSubmitProposal, detail common.JsDict) { + detail["group_policy_address"] = msg.GroupPolicyAddress + detail["proposers"] = msg.Proposers + detail["metadata"] = msg.Metadata + + msgs, _ := msg.GetMsgs() + messages := make([]common.JsDict, len(msgs)) + for i, m := range msgs { + detail := make(common.JsDict) + DecodeMsg(m, detail) + messages[i] = common.JsDict{ + "msg": detail, + "type": sdk.MsgTypeURL(m), + } + } + detail["msgs"] = messages + + detail["exec"] = msg.Exec.String() + detail["summary"] = msg.Summary +} + +func DecodeGroupMsgUpdateGroupAdmin(msg *group.MsgUpdateGroupAdmin, detail common.JsDict) { + detail["admin"] = msg.Admin + detail["group_id"] = msg.GroupId + detail["new_admin"] = msg.NewAdmin +} + +func DecodeGroupMsgUpdateGroupMembers(msg *group.MsgUpdateGroupMembers, detail common.JsDict) { + detail["admin"] = msg.Admin + detail["group_id"] = msg.GroupId + detail["members"] = msg.MemberUpdates +} + +func DecodeGroupMsgUpdateGroupMetadata(msg *group.MsgUpdateGroupMetadata, detail common.JsDict) { + detail["admin"] = msg.Admin + detail["group_id"] = msg.GroupId + detail["metadata"] = msg.Metadata +} + +func DecodeGroupMsgUpdateGroupPolicyAdmin(msg *group.MsgUpdateGroupPolicyAdmin, detail common.JsDict) { + detail["admin"] = msg.Admin + detail["group_policy_address"] = msg.GroupPolicyAddress + detail["new_admin"] = msg.NewAdmin +} + +func DecodeGroupMsgUpdateGroupPolicyDecisionPolicy( + msg *group.MsgUpdateGroupPolicyDecisionPolicy, + detail common.JsDict, +) { + detail["admin"] = msg.Admin + detail["group_policy_address"] = msg.GroupPolicyAddress + detail["decision_policy"] = msg.DecisionPolicy.GetCachedValue() +} + +func DecodeGroupMsgUpdateGroupPolicyMetadata(msg *group.MsgUpdateGroupPolicyMetadata, detail common.JsDict) { + detail["admin"] = msg.Admin + detail["group_policy_address"] = msg.GroupPolicyAddress + detail["metadata"] = msg.Metadata +} + +func DecodeGroupMsgVote(msg *group.MsgVote, detail common.JsDict) { + detail["proposal_id"] = msg.ProposalId + detail["voter"] = msg.Voter + detail["option"] = msg.Option + detail["metadata"] = msg.Metadata + detail["exec"] = msg.Exec.String() +} + +func DecodeGroupMsgWithdrawProposal(msg *group.MsgWithdrawProposal, detail common.JsDict) { + detail["proposal_id"] = msg.ProposalId + detail["address"] = msg.Address +} diff --git a/hooks/emitter/emitter.go b/hooks/emitter/emitter.go index dd6c3e887..1399d74e6 100644 --- a/hooks/emitter/emitter.go +++ b/hooks/emitter/emitter.go @@ -10,6 +10,7 @@ import ( "github.com/cometbft/cometbft/crypto/tmhash" tmjson "github.com/cometbft/cometbft/libs/json" "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" "github.com/cosmos/cosmos-sdk/x/authz" @@ -21,6 +22,7 @@ import ( govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + groupkeeper "github.com/cosmos/cosmos-sdk/x/group/keeper" mintkeeper "github.com/cosmos/cosmos-sdk/x/mint/keeper" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -55,6 +57,7 @@ type Hook struct { mintKeeper mintkeeper.Keeper distrKeeper distrkeeper.Keeper govKeeper govkeeper.Keeper + groupKeeper groupkeeper.Keeper oracleKeeper oraclekeeper.Keeper icahostKeeper icahostkeeper.Keeper @@ -62,6 +65,8 @@ type Hook struct { clientkeeper clientkeeper.Keeper connectionkeeper connectionkeeper.Keeper channelkeeper channelkeeper.Keeper + + groupStoreKey storetypes.StoreKey } // NewHook creates an emitter hook instance that will be added in Band App. @@ -75,11 +80,13 @@ func NewHook( mintKeeper mintkeeper.Keeper, distrKeeper distrkeeper.Keeper, govKeeper govkeeper.Keeper, + groupKeeper groupkeeper.Keeper, oracleKeeper oraclekeeper.Keeper, icahostKeeper icahostkeeper.Keeper, clientkeeper clientkeeper.Keeper, connectionkeeper connectionkeeper.Keeper, channelkeeper channelkeeper.Keeper, + groupstorekey storetypes.StoreKey, kafkaURI string, emitStartState bool, ) *Hook { @@ -101,11 +108,13 @@ func NewHook( mintKeeper: mintKeeper, distrKeeper: distrKeeper, govKeeper: govKeeper, + groupKeeper: groupKeeper, oracleKeeper: oracleKeeper, icahostKeeper: icahostKeeper, clientkeeper: clientkeeper, connectionkeeper: connectionkeeper, channelkeeper: channelkeeper, + groupStoreKey: groupstorekey, emitStartState: emitStartState, } } @@ -421,6 +430,19 @@ func (h *Hook) AfterDeliverTx(ctx sdk.Context, req abci.RequestDeliverTx, res ab // AfterEndBlock specify actions need to do after end block period (app.Hook interface). func (h *Hook) AfterEndBlock(ctx sdk.Context, req abci.RequestEndBlock, res abci.ResponseEndBlock) { + // update group proposals when voting period is end + timeBytes := sdk.FormatTimeBytes(ctx.BlockTime().UTC()) + lenTimeByte := byte(len(timeBytes)) + prefix := []byte{groupkeeper.ProposalsByVotingPeriodEndPrefix} + + iterator := ctx.KVStore(h.groupStoreKey). + Iterator(prefix, sdk.PrefixEndBytes(append(append(prefix, lenTimeByte), timeBytes...))) + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + proposalID, _ := splitKeyWithTime(iterator.Key()) + h.doUpdateGroupProposal(ctx, proposalID) + } + for _, event := range res.Events { h.handleBeginBlockEndBlockEvent(ctx, event) } diff --git a/hooks/emitter/group.go b/hooks/emitter/group.go new file mode 100644 index 000000000..bb1f79cc9 --- /dev/null +++ b/hooks/emitter/group.go @@ -0,0 +1,412 @@ +package emitter + +import ( + "encoding/json" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + "github.com/cosmos/cosmos-sdk/x/group" + proto "github.com/cosmos/gogoproto/proto" + + "github.com/bandprotocol/chain/v2/hooks/common" +) + +func extractStringFromEventMap(evMap common.EvMap, event string, topic string) string { + return strings.Trim(evMap[event+"."+topic][0], `"`) +} + +// handleGroupMsgCreateGroup implements emitter handler for Group's MsgCreateGroup. +func (h *Hook) handleGroupMsgCreateGroup( + ctx sdk.Context, evMap common.EvMap, +) { + groupId := uint64( + common.Atoi(extractStringFromEventMap(evMap, proto.MessageName(&group.EventCreateGroup{}), "group_id")), + ) + groupInfoResponse, _ := h.groupKeeper.GroupInfo( + sdk.WrapSDKContext(ctx), + &group.QueryGroupInfoRequest{GroupId: groupId}, + ) + groupInfo := groupInfoResponse.Info + h.Write("NEW_GROUP", common.JsDict{ + "id": groupId, + "version": groupInfo.Version, + "admin": groupInfo.Admin, + "metadata": groupInfo.Metadata, + "total_weight": groupInfo.TotalWeight, + "created_at": common.TimeToNano(&groupInfo.CreatedAt), + }) + h.doAddGroupMembers(ctx, groupId) +} + +// handleGroupMsgCreateGroup implements emitter handler for Group's MsgCreateGroupPolicy. +func (h *Hook) handleGroupMsgCreateGroupPolicy( + ctx sdk.Context, evMap common.EvMap, +) { + policyAddress := extractStringFromEventMap(evMap, proto.MessageName(&group.EventCreateGroupPolicy{}), "address") + groupPolicyResponse, _ := h.groupKeeper.GroupPolicyInfo( + sdk.WrapSDKContext(ctx), + &group.QueryGroupPolicyInfoRequest{ + Address: policyAddress, + }, + ) + groupPolicyInfo := groupPolicyResponse.Info + decisionPolicy, _ := groupPolicyInfo.GetDecisionPolicy() + h.Write("NEW_GROUP_POLICY", common.JsDict{ + "address": policyAddress, + "type": proto.MessageName(decisionPolicy), + "group_id": groupPolicyInfo.GroupId, + "admin": groupPolicyInfo.Admin, + "metadata": groupPolicyInfo.Metadata, + "version": groupPolicyInfo.Version, + "decision_policy": decisionPolicy, + "created_at": common.TimeToNano(&groupPolicyInfo.CreatedAt), + }) +} + +// handleGroupMsgCreateGroupWithPolicy implements emitter handler for Group's MsgCreateGroupWithPolicy. +func (h *Hook) handleGroupMsgCreateGroupWithPolicy( + ctx sdk.Context, evMap common.EvMap, +) { + h.handleGroupMsgCreateGroup(ctx, evMap) + h.handleGroupMsgCreateGroupPolicy(ctx, evMap) +} + +// handleGroupMsgSubmitProposal implements emitter handler for Group's MsgSubmitProposal. +func (h *Hook) handleGroupMsgSubmitProposal( + ctx sdk.Context, evMap common.EvMap, +) { + proposalId := uint64( + common.Atoi(extractStringFromEventMap(evMap, proto.MessageName(&group.EventSubmitProposal{}), "proposal_id")), + ) + proposalResponse, _ := h.groupKeeper.Proposal( + sdk.WrapSDKContext(ctx), + &group.QueryProposalRequest{ProposalId: proposalId}, + ) + proposal := proposalResponse.Proposal + msgs, _ := proposal.GetMsgs() + messages := make([]common.JsDict, len(msgs)) + for i, m := range msgs { + messages[i] = common.JsDict{ + "msg": m, + "type": sdk.MsgTypeURL(m), + } + } + + h.Write("NEW_GROUP_PROPOSAL", common.JsDict{ + "id": proposal.Id, + "group_policy_address": proposal.GroupPolicyAddress, + "metadata": proposal.Metadata, + "proposers": strings.Join(proposal.Proposers, ","), + "submit_time": common.TimeToNano(&proposal.SubmitTime), + "group_version": proposal.GroupVersion, + "group_policy_version": proposal.GroupPolicyVersion, + "status": proposal.Status.String(), + "yes_vote": proposal.FinalTallyResult.YesCount, + "no_vote": proposal.FinalTallyResult.NoCount, + "no_with_veto_vote": proposal.FinalTallyResult.NoWithVetoCount, + "abstain_vote": proposal.FinalTallyResult.AbstainCount, + "voting_period_end": common.TimeToNano(&proposal.VotingPeriodEnd), + "executor_result": proposal.ExecutorResult.String(), + "messages": messages, + "title": proposal.Title, + "summary": proposal.Summary, + }) +} + +// handleGroupMsgVote implements emitter handler for Group's MsgVote. +func (h *Hook) handleGroupMsgVote( + ctx sdk.Context, msg *group.MsgVote, evMap common.EvMap, +) { + proposalId := uint64( + common.Atoi(extractStringFromEventMap(evMap, proto.MessageName(&group.EventVote{}), "proposal_id")), + ) + voteResponse, err := h.groupKeeper.VoteByProposalVoter( + sdk.WrapSDKContext(ctx), + &group.QueryVoteByProposalVoterRequest{ + ProposalId: proposalId, + Voter: msg.Voter, + }, + ) + if err != nil { + return + } + vote := voteResponse.Vote + h.Write("NEW_GROUP_VOTE", common.JsDict{ + "group_proposal_id": proposalId, + "voter_address": vote.Voter, + "option": vote.Option.String(), + "metadata": vote.Metadata, + "submit_time": common.TimeToNano(&vote.SubmitTime), + }) +} + +// handleGroupMsgLeaveGroup implements emitter handler for Group's MsgLeaveGroup. +func (h *Hook) handleGroupMsgLeaveGroup( + ctx sdk.Context, evMap common.EvMap, +) { + groupId := uint64( + common.Atoi(extractStringFromEventMap(evMap, proto.MessageName(&group.EventLeaveGroup{}), "group_id")), + ) + address := extractStringFromEventMap(evMap, proto.MessageName(&group.EventLeaveGroup{}), "address") + h.doUpdateGroup(ctx, groupId) + h.Write("REMOVE_GROUP_MEMBER", common.JsDict{ + "group_id": groupId, + "address": address, + }) +} + +// handleGroupMsgUpdateGroupAdmin implements emitter handler for Group's MsgUpdateGroupAdmin. +func (h *Hook) handleGroupMsgUpdateGroupAdmin( + ctx sdk.Context, evMap common.EvMap, +) { + groupId := uint64( + common.Atoi(extractStringFromEventMap(evMap, proto.MessageName(&group.EventUpdateGroup{}), "group_id")), + ) + h.doUpdateGroup(ctx, groupId) +} + +// handleGroupMsgUpdateGroupMembers implements emitter handler for Group's MsgUpdateGroupMembers. +func (h *Hook) handleGroupMsgUpdateGroupMembers( + ctx sdk.Context, msg *group.MsgUpdateGroupMembers, evMap common.EvMap, +) { + h.Write("REMOVE_GROUP_MEMBERS_BY_GROUP_ID", common.JsDict{ + "group_id": msg.GroupId, + }) + h.doAddGroupMembers(ctx, msg.GroupId) + h.doUpdateGroup(ctx, msg.GroupId) +} + +// handleGroupMsgUpdateGroupMetadata implements emitter handler for Group's MsgUpdateGroupMetadata. +func (h *Hook) handleGroupMsgUpdateGroupMetadata( + ctx sdk.Context, evMap common.EvMap, +) { + groupId := uint64( + common.Atoi(extractStringFromEventMap(evMap, proto.MessageName(&group.EventUpdateGroup{}), "group_id")), + ) + h.doUpdateGroup(ctx, groupId) +} + +// handleGroupMsgUpdateGroupPolicyAdmin implements emitter handler for Group's MsgUpdateGroupPolicyAdmin. +func (h *Hook) handleGroupMsgUpdateGroupPolicyAdmin( + ctx sdk.Context, evMap common.EvMap, +) { + groupPolicyAddress := extractStringFromEventMap( + evMap, + proto.MessageName(&group.EventUpdateGroupPolicy{}), + "address", + ) + h.doUpdateGroupPolicy(ctx, groupPolicyAddress) +} + +// handleGroupMsgUpdateGroupPolicyDecisionPolicy implements emitter handler for Group's MsgUpdateGroupPolicyDecisionPolicy. +func (h *Hook) handleGroupMsgUpdateGroupPolicyDecisionPolicy( + ctx sdk.Context, evMap common.EvMap, +) { + groupPolicyAddress := extractStringFromEventMap( + evMap, + proto.MessageName(&group.EventUpdateGroupPolicy{}), + "address", + ) + h.doUpdateGroupPolicy(ctx, groupPolicyAddress) +} + +// handleGroupMsgUpdateGroupPolicyMetadata implements emitter handler for Group's MsgUpdateGroupPolicyMetadata. +func (h *Hook) handleGroupMsgUpdateGroupPolicyMetadata( + ctx sdk.Context, evMap common.EvMap, +) { + groupPolicyAddress := extractStringFromEventMap( + evMap, + proto.MessageName(&group.EventUpdateGroupPolicy{}), + "address", + ) + h.doUpdateGroupPolicy(ctx, groupPolicyAddress) +} + +// handleGroupMsgWithdrawProposal implements emitter handler for Group's MsgWithdrawProposal. +func (h *Hook) handleGroupMsgWithdrawProposal( + ctx sdk.Context, evMap common.EvMap, +) { + proposalId := uint64( + common.Atoi( + extractStringFromEventMap(evMap, proto.MessageName(&group.EventWithdrawProposal{}), "proposal_id"), + ), + ) + h.doUpdateGroupProposal(ctx, proposalId) +} + +// handleGroupEventExec implements emitter handler for Group's EventExec. +func (h *Hook) handleGroupEventExec( + ctx sdk.Context, evMap common.EvMap, +) { + if len(evMap[proto.MessageName(&group.EventExec{})+".proposal_id"]) == 0 { + return + } + proposalId := uint64( + common.Atoi(extractStringFromEventMap(evMap, proto.MessageName(&group.EventExec{}), "proposal_id")), + ) + executorResult := extractStringFromEventMap(evMap, proto.MessageName(&group.EventExec{}), "result") + h.Write("UPDATE_GROUP_PROPOSAL_BY_ID", common.JsDict{ + "id": proposalId, + "executor_result": executorResult, + }) + + h.handleGroupEventProposalPruned(ctx, evMap) +} + +// handleGroupEventProposalPruned implements emitter handler for Group's EventProposalPruned. +func (h *Hook) handleGroupEventProposalPruned( + ctx sdk.Context, evMap common.EvMap, +) { + if len(evMap[proto.MessageName(&group.EventProposalPruned{})+".proposal_id"]) == 0 { + return + } + proposalId := uint64( + common.Atoi(extractStringFromEventMap(evMap, proto.MessageName(&group.EventProposalPruned{}), "proposal_id")), + ) + proposalStatus := extractStringFromEventMap(evMap, proto.MessageName(&group.EventProposalPruned{}), "status") + tallyResult := group.DefaultTallyResult() + _ = json.Unmarshal([]byte(evMap[proto.MessageName(&group.EventProposalPruned{})+".tally_result"][0]), &tallyResult) + h.Write("UPDATE_GROUP_PROPOSAL_BY_ID", common.JsDict{ + "id": proposalId, + "status": proposalStatus, + "yes_vote": tallyResult.YesCount, + "no_vote": tallyResult.NoCount, + "no_with_veto_vote": tallyResult.NoWithVetoCount, + "abstain_vote": tallyResult.AbstainCount, + }) +} + +func (h *Hook) doUpdateGroup(ctx sdk.Context, groupId uint64) { + groupInfoResponse, _ := h.groupKeeper.GroupInfo( + sdk.WrapSDKContext(ctx), + &group.QueryGroupInfoRequest{GroupId: groupId}, + ) + groupInfo := groupInfoResponse.Info + h.Write("UPDATE_GROUP", common.JsDict{ + "id": groupId, + "version": groupInfo.Version, + "admin": groupInfo.Admin, + "metadata": groupInfo.Metadata, + "total_weight": groupInfo.TotalWeight, + "created_at": common.TimeToNano(&groupInfo.CreatedAt), + }) +} + +func (h *Hook) doUpdateGroupPolicy(ctx sdk.Context, policyAddress string) { + groupPolicyResponse, _ := h.groupKeeper.GroupPolicyInfo( + sdk.WrapSDKContext(ctx), + &group.QueryGroupPolicyInfoRequest{ + Address: policyAddress, + }, + ) + groupPolicyInfo := groupPolicyResponse.Info + decisionPolicy, _ := groupPolicyInfo.GetDecisionPolicy() + h.Write("UPDATE_GROUP_POLICY", common.JsDict{ + "address": policyAddress, + "group_id": groupPolicyInfo.GroupId, + "admin": groupPolicyInfo.Admin, + "metadata": groupPolicyInfo.Metadata, + "version": groupPolicyInfo.Version, + "decision_policy": decisionPolicy, + "created_at": common.TimeToNano(&groupPolicyInfo.CreatedAt), + }) + + h.doAbortProposals(ctx, policyAddress) +} + +func (h *Hook) doAbortProposals(ctx sdk.Context, policyAddress string) { + groupProposalsResponse, _ := h.groupKeeper.ProposalsByGroupPolicy( + sdk.WrapSDKContext(ctx), + &group.QueryProposalsByGroupPolicyRequest{ + Address: policyAddress, + }, + ) + for { + groupProposals := groupProposalsResponse.Proposals + for _, groupProposal := range groupProposals { + if groupProposal.Status == group.PROPOSAL_STATUS_ABORTED { + h.doUpdateGroupProposal(ctx, groupProposal.Id) + } + } + if len(groupProposalsResponse.Pagination.NextKey) == 0 { + break + } + groupProposalsResponse, _ = h.groupKeeper.ProposalsByGroupPolicy( + sdk.WrapSDKContext(ctx), + &group.QueryProposalsByGroupPolicyRequest{ + Address: policyAddress, + Pagination: &query.PageRequest{ + Key: groupProposalsResponse.Pagination.NextKey, + }, + }, + ) + } +} + +func (h *Hook) doUpdateGroupProposal(ctx sdk.Context, proposalId uint64) { + proposalResponse, _ := h.groupKeeper.Proposal( + sdk.WrapSDKContext(ctx), + &group.QueryProposalRequest{ProposalId: proposalId}, + ) + proposal := proposalResponse.Proposal + msgs, _ := proposal.GetMsgs() + messages := make([]common.JsDict, len(msgs)) + for i, m := range msgs { + messages[i] = common.JsDict{ + "msg": m, + "type": sdk.MsgTypeURL(m), + } + } + + h.Write("UPDATE_GROUP_PROPOSAL", common.JsDict{ + "id": proposal.Id, + "group_policy_address": proposal.GroupPolicyAddress, + "metadata": proposal.Metadata, + "proposers": strings.Join(proposal.Proposers, ","), + "submit_time": common.TimeToNano(&proposal.SubmitTime), + "group_version": proposal.GroupVersion, + "group_policy_version": proposal.GroupPolicyVersion, + "status": proposal.Status.String(), + "yes_vote": proposal.FinalTallyResult.YesCount, + "no_vote": proposal.FinalTallyResult.NoCount, + "no_with_veto_vote": proposal.FinalTallyResult.NoWithVetoCount, + "abstain_vote": proposal.FinalTallyResult.AbstainCount, + "voting_period_end": common.TimeToNano(&proposal.VotingPeriodEnd), + "executor_result": proposal.ExecutorResult.String(), + "messages": messages, + "title": proposal.Title, + "summary": proposal.Summary, + }) +} + +func (h *Hook) doAddGroupMembers(ctx sdk.Context, groupId uint64) { + groupMembersResponse, _ := h.groupKeeper.GroupMembers( + sdk.WrapSDKContext(ctx), + &group.QueryGroupMembersRequest{GroupId: groupId}, + ) + for { + groupMembers := groupMembersResponse.Members + for _, groupMember := range groupMembers { + h.Write("NEW_GROUP_MEMBER", common.JsDict{ + "group_id": groupId, + "address": groupMember.Member.Address, + "weight": groupMember.Member.Weight, + "metadata": groupMember.Member.Metadata, + "added_at": common.TimeToNano(&groupMember.Member.AddedAt), + }) + } + if len(groupMembersResponse.Pagination.NextKey) == 0 { + break + } + groupMembersResponse, _ = h.groupKeeper.GroupMembers( + sdk.WrapSDKContext(ctx), + &group.QueryGroupMembersRequest{ + GroupId: groupId, + Pagination: &query.PageRequest{ + Key: groupMembersResponse.Pagination.NextKey, + }, + }, + ) + } +} diff --git a/hooks/emitter/handler.go b/hooks/emitter/handler.go index 28cf31f1a..bccac2446 100644 --- a/hooks/emitter/handler.go +++ b/hooks/emitter/handler.go @@ -1,16 +1,21 @@ package emitter import ( + "time" + abci "github.com/cometbft/cometbft/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/kv" "github.com/cosmos/cosmos-sdk/x/authz" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + "github.com/cosmos/cosmos-sdk/x/group" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + proto "github.com/cosmos/gogoproto/proto" transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" connectiontypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" @@ -118,6 +123,35 @@ func (h *Hook) handleMsg(ctx sdk.Context, txHash []byte, msg sdk.Msg, log sdk.AB h.handleMsgRevoke(msg, detail) case *authz.MsgExec: h.handleMsgExec(ctx, txHash, msg, log, detail) + case *group.MsgCreateGroup: + h.handleGroupMsgCreateGroup(ctx, evMap) + case *group.MsgCreateGroupPolicy: + h.handleGroupMsgCreateGroupPolicy(ctx, evMap) + case *group.MsgCreateGroupWithPolicy: + h.handleGroupMsgCreateGroupWithPolicy(ctx, evMap) + case *group.MsgExec: + h.handleGroupEventExec(ctx, evMap) + case *group.MsgLeaveGroup: + h.handleGroupMsgLeaveGroup(ctx, evMap) + case *group.MsgSubmitProposal: + h.handleGroupMsgSubmitProposal(ctx, evMap) + case *group.MsgUpdateGroupAdmin: + h.handleGroupMsgUpdateGroupAdmin(ctx, evMap) + case *group.MsgUpdateGroupMembers: + h.handleGroupMsgUpdateGroupMembers(ctx, msg, evMap) + case *group.MsgUpdateGroupMetadata: + h.handleGroupMsgUpdateGroupMetadata(ctx, evMap) + case *group.MsgUpdateGroupPolicyAdmin: + h.handleGroupMsgUpdateGroupPolicyAdmin(ctx, evMap) + case *group.MsgUpdateGroupPolicyDecisionPolicy: + h.handleGroupMsgUpdateGroupPolicyDecisionPolicy(ctx, evMap) + case *group.MsgUpdateGroupPolicyMetadata: + h.handleGroupMsgUpdateGroupPolicyMetadata(ctx, evMap) + case *group.MsgVote: + h.handleGroupMsgVote(ctx, msg, evMap) + h.handleGroupEventExec(ctx, evMap) + case *group.MsgWithdrawProposal: + h.handleGroupMsgWithdrawProposal(ctx, evMap) default: break } @@ -145,7 +179,22 @@ func (h *Hook) handleBeginBlockEndBlockEvent(ctx sdk.Context, event abci.Event) h.handleEventTypeTransfer(evMap) case channeltypes.EventTypeSendPacket: h.handleEventSendPacket(ctx, evMap) + case proto.MessageName(&group.EventProposalPruned{}): + h.handleGroupEventProposalPruned(ctx, evMap) default: break } } + +func splitKeyWithTime(key []byte) (proposalID uint64, endTime time.Time) { + lenTime := len(sdk.FormatTimeBytes(time.Now())) + kv.AssertKeyLength(key[2:], 8+lenTime) + + endTime, err := sdk.ParseTimeBytes(key[2 : 2+lenTime]) + if err != nil { + panic(err) + } + + proposalID = sdk.BigEndianToUint64(key[2+lenTime:]) + return +}