diff --git a/x/gov/abci.go b/x/gov/abci.go index 8fc06069f886..bc8764aec0dc 100644 --- a/x/gov/abci.go +++ b/x/gov/abci.go @@ -31,7 +31,13 @@ func EndBlocker(ctx sdk.Context, keeper *keeper.Keeper) { } // called when proposal become inactive - keeper.Hooks().AfterProposalFailedMinDeposit(ctx, proposal.Id) + cacheCtx, writeCache := ctx.CacheContext() + err := keeper.Hooks().AfterProposalFailedMinDeposit(cacheCtx, proposal.Id) + if err == nil { // purposely ignoring the error here not to halt the chain if the hook fails + writeCache() + } else { + keeper.Logger(ctx).Error("failed to execute AfterProposalFailedMinDeposit hook", "error", err) + } ctx.EventManager().EmitEvent( sdk.NewEvent( @@ -118,7 +124,13 @@ func EndBlocker(ctx sdk.Context, keeper *keeper.Keeper) { keeper.RemoveFromActiveProposalQueue(ctx, proposal.Id, *proposal.VotingEndTime) // when proposal become active - keeper.Hooks().AfterProposalVotingPeriodEnded(ctx, proposal.Id) + cacheCtx, writeCache := ctx.CacheContext() + err := keeper.Hooks().AfterProposalVotingPeriodEnded(cacheCtx, proposal.Id) + if err == nil { // purposely ignoring the error here not to halt the chain if the hook fails + writeCache() + } else { + keeper.Logger(ctx).Error("failed to execute AfterProposalVotingPeriodEnded hook", "error", err) + } logger.Info( "proposal tallied", diff --git a/x/gov/keeper/deposit.go b/x/gov/keeper/deposit.go index 62b90bd46d5f..f99be8b8bf3d 100644 --- a/x/gov/keeper/deposit.go +++ b/x/gov/keeper/deposit.go @@ -147,7 +147,10 @@ func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID uint64, depositorAdd } // called when deposit has been added to a proposal, however the proposal may not be active - keeper.Hooks().AfterProposalDeposit(ctx, proposalID, depositorAddr) + err = keeper.Hooks().AfterProposalDeposit(ctx, proposalID, depositorAddr) + if err != nil { + return false, err + } ctx.EventManager().EmitEvent( sdk.NewEvent( diff --git a/x/gov/keeper/hooks_test.go b/x/gov/keeper/hooks_test.go index cf07cd121329..f24c37f2b29a 100644 --- a/x/gov/keeper/hooks_test.go +++ b/x/gov/keeper/hooks_test.go @@ -25,24 +25,29 @@ type MockGovHooksReceiver struct { AfterProposalVotingPeriodEndedValid bool } -func (h *MockGovHooksReceiver) AfterProposalSubmission(ctx sdk.Context, proposalID uint64) { +func (h *MockGovHooksReceiver) AfterProposalSubmission(ctx sdk.Context, proposalID uint64) error { h.AfterProposalSubmissionValid = true + return nil } -func (h *MockGovHooksReceiver) AfterProposalDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress) { +func (h *MockGovHooksReceiver) AfterProposalDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress) error { h.AfterProposalDepositValid = true + return nil } -func (h *MockGovHooksReceiver) AfterProposalVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) { +func (h *MockGovHooksReceiver) AfterProposalVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) error { h.AfterProposalVoteValid = true + return nil } -func (h *MockGovHooksReceiver) AfterProposalFailedMinDeposit(ctx sdk.Context, proposalID uint64) { +func (h *MockGovHooksReceiver) AfterProposalFailedMinDeposit(ctx sdk.Context, proposalID uint64) error { h.AfterProposalFailedMinDepositValid = true + return nil } -func (h *MockGovHooksReceiver) AfterProposalVotingPeriodEnded(ctx sdk.Context, proposalID uint64) { +func (h *MockGovHooksReceiver) AfterProposalVotingPeriodEnded(ctx sdk.Context, proposalID uint64) error { h.AfterProposalVotingPeriodEndedValid = true + return nil } func TestHooks(t *testing.T) { diff --git a/x/gov/keeper/proposal.go b/x/gov/keeper/proposal.go index b050b51fcf10..ee1ac5e0c431 100644 --- a/x/gov/keeper/proposal.go +++ b/x/gov/keeper/proposal.go @@ -94,7 +94,10 @@ func (keeper Keeper) SubmitProposal(ctx sdk.Context, messages []sdk.Msg, metadat keeper.SetProposalID(ctx, proposalID+1) // called right after a proposal is submitted - keeper.Hooks().AfterProposalSubmission(ctx, proposalID) + err = keeper.Hooks().AfterProposalSubmission(ctx, proposalID) + if err != nil { + return v1.Proposal{}, err + } ctx.EventManager().EmitEvent( sdk.NewEvent( diff --git a/x/gov/keeper/vote.go b/x/gov/keeper/vote.go index 2f24c37d2e93..ec20dd4155a3 100644 --- a/x/gov/keeper/vote.go +++ b/x/gov/keeper/vote.go @@ -32,7 +32,10 @@ func (keeper Keeper) AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.A keeper.SetVote(ctx, vote) // called after a vote on a proposal is cast - keeper.Hooks().AfterProposalVote(ctx, proposalID, voterAddr) + err = keeper.Hooks().AfterProposalVote(ctx, proposalID, voterAddr) + if err != nil { + return err + } ctx.EventManager().EmitEvent( sdk.NewEvent( diff --git a/x/gov/types/expected_keepers.go b/x/gov/types/expected_keepers.go index 70f0e5fbe994..0a0da4e9e1c0 100644 --- a/x/gov/types/expected_keepers.go +++ b/x/gov/types/expected_keepers.go @@ -56,11 +56,11 @@ type BankKeeper interface { // GovHooks event hooks for governance proposal object (noalias) type GovHooks interface { - AfterProposalSubmission(ctx sdk.Context, proposalID uint64) // Must be called after proposal is submitted - AfterProposalDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress) // Must be called after a deposit is made - AfterProposalVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) // Must be called after a vote on a proposal is cast - AfterProposalFailedMinDeposit(ctx sdk.Context, proposalID uint64) // Must be called when proposal fails to reach min deposit - AfterProposalVotingPeriodEnded(ctx sdk.Context, proposalID uint64) // Must be called when proposal's finishes it's voting period + AfterProposalSubmission(ctx sdk.Context, proposalID uint64) error // Must be called after proposal is submitted + AfterProposalDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress) error // Must be called after a deposit is made + AfterProposalVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) error // Must be called after a vote on a proposal is cast + AfterProposalFailedMinDeposit(ctx sdk.Context, proposalID uint64) error // Must be called when proposal fails to reach min deposit + AfterProposalVotingPeriodEnded(ctx sdk.Context, proposalID uint64) error // Must be called when proposal's finishes it's voting period } type GovHooksWrapper struct{ GovHooks } diff --git a/x/gov/types/hooks.go b/x/gov/types/hooks.go index 0a361687d9d2..f4c3d0ff4ae3 100644 --- a/x/gov/types/hooks.go +++ b/x/gov/types/hooks.go @@ -13,32 +13,79 @@ func NewMultiGovHooks(hooks ...GovHooks) MultiGovHooks { return hooks } -func (h MultiGovHooks) AfterProposalSubmission(ctx sdk.Context, proposalID uint64) { +func (h MultiGovHooks) AfterProposalSubmission(ctx sdk.Context, proposalID uint64) error { + var errs error for i := range h { - h[i].AfterProposalSubmission(ctx, proposalID) + errs = JoinErrors(errs, h[i].AfterProposalSubmission(ctx, proposalID)) } + return errs } -func (h MultiGovHooks) AfterProposalDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress) { +func (h MultiGovHooks) AfterProposalDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress) error { + var errs error for i := range h { - h[i].AfterProposalDeposit(ctx, proposalID, depositorAddr) + errs = JoinErrors(errs, h[i].AfterProposalDeposit(ctx, proposalID, depositorAddr)) } + return errs } -func (h MultiGovHooks) AfterProposalVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) { +func (h MultiGovHooks) AfterProposalVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) error { + var errs error for i := range h { - h[i].AfterProposalVote(ctx, proposalID, voterAddr) + errs = JoinErrors(errs, h[i].AfterProposalVote(ctx, proposalID, voterAddr)) } + return errs } -func (h MultiGovHooks) AfterProposalFailedMinDeposit(ctx sdk.Context, proposalID uint64) { +func (h MultiGovHooks) AfterProposalFailedMinDeposit(ctx sdk.Context, proposalID uint64) error { + var errs error for i := range h { - h[i].AfterProposalFailedMinDeposit(ctx, proposalID) + errs = JoinErrors(errs, h[i].AfterProposalFailedMinDeposit(ctx, proposalID)) } + return errs } -func (h MultiGovHooks) AfterProposalVotingPeriodEnded(ctx sdk.Context, proposalID uint64) { +func (h MultiGovHooks) AfterProposalVotingPeriodEnded(ctx sdk.Context, proposalID uint64) error { + var errs error for i := range h { - h[i].AfterProposalVotingPeriodEnded(ctx, proposalID) + errs = JoinErrors(errs, h[i].AfterProposalVotingPeriodEnded(ctx, proposalID)) } + return errs +} + +// implementation of errors.Join() in Go 1.20, until we upgrade to that version +func JoinErrors(errs ...error) error { + n := 0 + for _, err := range errs { + if err != nil { + n++ + } + } + if n == 0 { + return nil + } + e := &joinError{ + errs: make([]error, 0, n), + } + for _, err := range errs { + if err != nil { + e.errs = append(e.errs, err) + } + } + return e +} + +type joinError struct { + errs []error +} + +func (e *joinError) Error() string { + var b []byte + for i, err := range e.errs { + if i > 0 { + b = append(b, '\n') + } + b = append(b, err.Error()...) + } + return string(b) }