@@ -3,6 +3,7 @@ package keeper
33import (
44 "bytes"
55 "context"
6+ "errors"
67 "fmt"
78
89 errorsmod "cosmossdk.io/errors"
@@ -11,6 +12,7 @@ import (
1112
1213 "github.com/cosmos/ibc-go/v9/modules/apps/29-fee/types"
1314 channeltypes "github.com/cosmos/ibc-go/v9/modules/core/04-channel/types"
15+ ibcerrors "github.com/cosmos/ibc-go/v9/modules/core/errors"
1416)
1517
1618// escrowPacketFee sends the packet fee to the 29-fee module account to hold in escrow
@@ -46,38 +48,39 @@ func (k Keeper) escrowPacketFee(ctx context.Context, packetID channeltypes.Packe
4648
4749// DistributePacketFeesOnAcknowledgement pays all the acknowledgement & receive fees for a given packetID while refunding the timeout fees to the refund account.
4850func (k Keeper ) DistributePacketFeesOnAcknowledgement (ctx context.Context , forwardRelayer string , reverseRelayer sdk.AccAddress , packetFees []types.PacketFee , packetID channeltypes.PacketId ) {
49- // cache context before trying to distribute fees
51+ // use branched multistore for distribution of fees.
5052 // if the escrow account has insufficient balance then we want to avoid partially distributing fees
51- sdkCtx := sdk .UnwrapSDKContext (ctx ) // TODO: https://github.com/cosmos/ibc-go/issues/5917
52- cacheCtx , writeFn := sdkCtx .CacheContext ()
53+ if err := k .BranchService .Execute (ctx , func (ctx context.Context ) error {
54+ // forward relayer address will be empty if conversion fails
55+ forwardAddr , _ := sdk .AccAddressFromBech32 (forwardRelayer )
56+
57+ for _ , packetFee := range packetFees {
58+ if ! k .EscrowAccountHasBalance (ctx , packetFee .Fee .Total ()) {
59+ // NOTE: we lock the fee module on error return so that the state changes are persisted
60+ return ibcerrors .ErrInsufficientFunds
61+ }
62+
63+ // check if refundAcc address works
64+ refundAddr , err := sdk .AccAddressFromBech32 (packetFee .RefundAddress )
65+ if err != nil {
66+ panic (fmt .Errorf ("could not parse refundAcc %s to sdk.AccAddress" , packetFee .RefundAddress ))
67+ }
5368
54- // forward relayer address will be empty if conversion fails
55- forwardAddr , _ := sdk . AccAddressFromBech32 ( forwardRelayer )
69+ k . distributePacketFeeOnAcknowledgement ( ctx , refundAddr , forwardAddr , reverseRelayer , packetFee )
70+ }
5671
57- for _ , packetFee := range packetFees {
58- if ! k .EscrowAccountHasBalance (cacheCtx , packetFee .Fee .Total ()) {
72+ return nil
73+ }); err != nil {
74+ if errors .Is (err , ibcerrors .ErrInsufficientFunds ) {
5975 // if the escrow account does not have sufficient funds then there must exist a severe bug
6076 // the fee module should be locked until manual intervention fixes the issue
6177 // a locked fee module will simply skip fee logic, all channels will temporarily function as
6278 // fee disabled channels
63- // NOTE: we use the uncached context to lock the fee module so that the state changes from
64- // locking the fee module are persisted
6579 k .lockFeeModule (ctx )
6680 return
6781 }
68-
69- // check if refundAcc address works
70- refundAddr , err := sdk .AccAddressFromBech32 (packetFee .RefundAddress )
71- if err != nil {
72- panic (fmt .Errorf ("could not parse refundAcc %s to sdk.AccAddress" , packetFee .RefundAddress ))
73- }
74-
75- k .distributePacketFeeOnAcknowledgement (cacheCtx , refundAddr , forwardAddr , reverseRelayer , packetFee )
7682 }
7783
78- // write the cache
79- writeFn ()
80-
8184 // removes the fees from the store as fees are now paid
8285 k .DeleteFeesInEscrow (ctx , packetID )
8386}
@@ -104,35 +107,36 @@ func (k Keeper) distributePacketFeeOnAcknowledgement(ctx context.Context, refund
104107
105108// DistributePacketFeesOnTimeout pays all the timeout fees for a given packetID while refunding the acknowledgement & receive fees to the refund account.
106109func (k Keeper ) DistributePacketFeesOnTimeout (ctx context.Context , timeoutRelayer sdk.AccAddress , packetFees []types.PacketFee , packetID channeltypes.PacketId ) {
107- // cache context before trying to distribute fees
110+ // use branched multistore for distribution of fees.
108111 // if the escrow account has insufficient balance then we want to avoid partially distributing fees
109- sdkCtx := sdk .UnwrapSDKContext (ctx ) // TODO: https://github.com/cosmos/ibc-go/issues/5917
110- cacheCtx , writeFn := sdkCtx .CacheContext ()
112+ if err := k .BranchService .Execute (ctx , func (ctx context.Context ) error {
113+ for _ , packetFee := range packetFees {
114+ if ! k .EscrowAccountHasBalance (ctx , packetFee .Fee .Total ()) {
115+ // NOTE: we lock the fee module on error return so that the state changes are persisted
116+ return ibcerrors .ErrInsufficientFunds
117+ }
111118
112- for _ , packetFee := range packetFees {
113- if ! k .EscrowAccountHasBalance (cacheCtx , packetFee .Fee .Total ()) {
119+ // check if refundAcc address works
120+ refundAddr , err := sdk .AccAddressFromBech32 (packetFee .RefundAddress )
121+ if err != nil {
122+ panic (fmt .Errorf ("could not parse refundAcc %s to sdk.AccAddress" , packetFee .RefundAddress ))
123+ }
124+
125+ k .distributePacketFeeOnTimeout (ctx , refundAddr , timeoutRelayer , packetFee )
126+ }
127+
128+ return nil
129+ }); err != nil {
130+ if errors .Is (err , ibcerrors .ErrInsufficientFunds ) {
114131 // if the escrow account does not have sufficient funds then there must exist a severe bug
115132 // the fee module should be locked until manual intervention fixes the issue
116133 // a locked fee module will simply skip fee logic, all channels will temporarily function as
117134 // fee disabled channels
118- // NOTE: we use the uncached context to lock the fee module so that the state changes from
119- // locking the fee module are persisted
120135 k .lockFeeModule (ctx )
121136 return
122137 }
123-
124- // check if refundAcc address works
125- refundAddr , err := sdk .AccAddressFromBech32 (packetFee .RefundAddress )
126- if err != nil {
127- panic (fmt .Errorf ("could not parse refundAcc %s to sdk.AccAddress" , packetFee .RefundAddress ))
128- }
129-
130- k .distributePacketFeeOnTimeout (cacheCtx , refundAddr , timeoutRelayer , packetFee )
131138 }
132139
133- // write the cache
134- writeFn ()
135-
136140 // removing the fee from the store as the fee is now paid
137141 k .DeleteFeesInEscrow (ctx , packetID )
138142}
@@ -151,36 +155,36 @@ func (k Keeper) distributePacketFeeOnTimeout(ctx context.Context, refundAddr, ti
151155// If the distribution fails for any reason (such as the receiving address being blocked),
152156// the state changes will be discarded.
153157func (k Keeper ) distributeFee (ctx context.Context , receiver , refundAccAddress sdk.AccAddress , fee sdk.Coins ) {
154- // cache context before trying to distribute fees
155- sdkCtx := sdk .UnwrapSDKContext (ctx ) // TODO: https://github.com/cosmos/ibc-go/issues/7223
156- cacheCtx , writeFn := sdkCtx .CacheContext ()
158+ // use branched multistore before trying to distribute fees
159+ if err := k .BranchService .Execute (ctx , func (ctx context.Context ) error {
160+ err := k .bankKeeper .SendCoinsFromModuleToAccount (ctx , types .ModuleName , receiver , fee )
161+ if err != nil {
162+ if bytes .Equal (receiver , refundAccAddress ) {
163+ // if sending to the refund address already failed, then return (no-op)
164+ return errorsmod .Wrapf (types .ErrRefundDistributionFailed , "receiver address: %s" , receiver )
165+ }
157166
158- err := k .bankKeeper .SendCoinsFromModuleToAccount (cacheCtx , types .ModuleName , receiver , fee )
159- if err != nil {
160- if bytes .Equal (receiver , refundAccAddress ) {
161- k .Logger .Error ("error distributing fee" , "receiver address" , receiver , "fee" , fee )
162- return // if sending to the refund address already failed, then return (no-op)
163- }
167+ // if an error is returned from x/bank and the receiver is not the refundAccAddress
168+ // then attempt to refund the fee to the original sender
169+ err := k .bankKeeper .SendCoinsFromModuleToAccount (ctx , types .ModuleName , refundAccAddress , fee )
170+ if err != nil {
171+ // if sending to the refund address fails, no-op
172+ return errorsmod .Wrapf (types .ErrRefundDistributionFailed , "receiver address: %s" , refundAccAddress )
173+ }
164174
165- // if an error is returned from x/bank and the receiver is not the refundAccAddress
166- // then attempt to refund the fee to the original sender
167- err := k .bankKeeper .SendCoinsFromModuleToAccount (cacheCtx , types .ModuleName , refundAccAddress , fee )
168- if err != nil {
169- k .Logger .Error ("error refunding fee to the original sender" , "refund address" , refundAccAddress , "fee" , fee )
170- return // if sending to the refund address fails, no-op
175+ if err := k .emitDistributeFeeEvent (ctx , refundAccAddress .String (), fee ); err != nil {
176+ panic (err )
177+ }
171178 }
172179
173- if err := k .emitDistributeFeeEvent (ctx , refundAccAddress .String (), fee ); err != nil {
174- panic (err )
175- }
176- } else {
177- if err := k .emitDistributeFeeEvent (ctx , receiver .String (), fee ); err != nil {
178- panic (err )
179- }
180+ return nil
181+ }); err != nil {
182+ k .Logger .Error ("error distributing fee" , "error" , err .Error ())
180183 }
181184
182- // write the cache
183- writeFn ()
185+ if err := k .emitDistributeFeeEvent (ctx , receiver .String (), fee ); err != nil {
186+ panic (err )
187+ }
184188}
185189
186190// RefundFeesOnChannelClosure will refund all fees associated with the given port and channel identifiers.
@@ -190,52 +194,52 @@ func (k Keeper) distributeFee(ctx context.Context, receiver, refundAccAddress sd
190194func (k Keeper ) RefundFeesOnChannelClosure (ctx context.Context , portID , channelID string ) error {
191195 identifiedPacketFees := k .GetIdentifiedPacketFeesForChannel (ctx , portID , channelID )
192196
193- // cache context before trying to distribute fees
197+ // use branched multistore for distribution of fees.
194198 // if the escrow account has insufficient balance then we want to avoid partially distributing fees
195- sdkCtx := sdk .UnwrapSDKContext (ctx ) // TODO: https://github.com/cosmos/ibc-go/issues/5917
196- cacheCtx , writeFn := sdkCtx .CacheContext ()
197-
198- for _ , identifiedPacketFee := range identifiedPacketFees {
199- var unRefundedFees []types.PacketFee
200- for _ , packetFee := range identifiedPacketFee .PacketFees {
201-
202- if ! k .EscrowAccountHasBalance (cacheCtx , packetFee .Fee .Total ()) {
203- // if the escrow account does not have sufficient funds then there must exist a severe bug
204- // the fee module should be locked until manual intervention fixes the issue
205- // a locked fee module will simply skip fee logic, all channels will temporarily function as
206- // fee disabled channels
207- // NOTE: we use the uncached context to lock the fee module so that the state changes from
208- // locking the fee module are persisted
209- k .lockFeeModule (ctx )
210-
211- // return a nil error so state changes are committed but distribution stops
212- return nil
199+ if err := k .BranchService .Execute (ctx , func (ctx context.Context ) error {
200+ for _ , identifiedPacketFee := range identifiedPacketFees {
201+ var unRefundedFees []types.PacketFee
202+ for _ , packetFee := range identifiedPacketFee .PacketFees {
203+
204+ if ! k .EscrowAccountHasBalance (ctx , packetFee .Fee .Total ()) {
205+ // NOTE: we lock the fee module on error return so that the state changes are persisted
206+ return ibcerrors .ErrInsufficientFunds
207+ }
208+
209+ refundAddr , err := sdk .AccAddressFromBech32 (packetFee .RefundAddress )
210+ if err != nil {
211+ unRefundedFees = append (unRefundedFees , packetFee )
212+ continue
213+ }
214+
215+ // refund all fees to refund address
216+ if err = k .bankKeeper .SendCoinsFromModuleToAccount (ctx , types .ModuleName , refundAddr , packetFee .Fee .Total ()); err != nil {
217+ unRefundedFees = append (unRefundedFees , packetFee )
218+ continue
219+ }
213220 }
214221
215- refundAddr , err := sdk .AccAddressFromBech32 (packetFee .RefundAddress )
216- if err != nil {
217- unRefundedFees = append (unRefundedFees , packetFee )
218- continue
219- }
220-
221- // refund all fees to refund address
222- if err = k .bankKeeper .SendCoinsFromModuleToAccount (cacheCtx , types .ModuleName , refundAddr , packetFee .Fee .Total ()); err != nil {
223- unRefundedFees = append (unRefundedFees , packetFee )
224- continue
222+ if len (unRefundedFees ) > 0 {
223+ // update packet fees to keep only the unrefunded fees
224+ packetFees := types .NewPacketFees (unRefundedFees )
225+ k .SetFeesInEscrow (ctx , identifiedPacketFee .PacketId , packetFees )
226+ } else {
227+ k .DeleteFeesInEscrow (ctx , identifiedPacketFee .PacketId )
225228 }
226229 }
227230
228- if len (unRefundedFees ) > 0 {
229- // update packet fees to keep only the unrefunded fees
230- packetFees := types .NewPacketFees (unRefundedFees )
231- k .SetFeesInEscrow (cacheCtx , identifiedPacketFee .PacketId , packetFees )
232- } else {
233- k .DeleteFeesInEscrow (cacheCtx , identifiedPacketFee .PacketId )
231+ return nil
232+ }); err != nil {
233+ if errors .Is (err , ibcerrors .ErrInsufficientFunds ) {
234+ // if the escrow account does not have sufficient funds then there must exist a severe bug
235+ // the fee module should be locked until manual intervention fixes the issue
236+ // a locked fee module will simply skip fee logic, all channels will temporarily function as
237+ // fee disabled channels
238+ k .lockFeeModule (ctx )
239+
240+ return nil // commit state changes to lock module and stop fee distribution
234241 }
235242 }
236243
237- // write the cache
238- writeFn ()
239-
240244 return nil
241245}
0 commit comments