-
Notifications
You must be signed in to change notification settings - Fork 597
/
ante.go
177 lines (151 loc) · 6.43 KB
/
ante.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package ante
import (
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
clienttypes "github.com/cosmos/ibc-go/v9/modules/core/02-client/types"
channeltypes "github.com/cosmos/ibc-go/v9/modules/core/04-channel/types"
"github.com/cosmos/ibc-go/v9/modules/core/exported"
"github.com/cosmos/ibc-go/v9/modules/core/keeper"
)
type RedundantRelayDecorator struct {
k *keeper.Keeper
}
func NewRedundantRelayDecorator(k *keeper.Keeper) RedundantRelayDecorator {
return RedundantRelayDecorator{k: k}
}
// AnteHandle returns an error if a multiMsg tx only contains packet messages (Recv, Ack, Timeout) and additional update messages
// and all packet messages are redundant. If the transaction is just a single UpdateClient message, or the multimsg transaction
// contains some other message type, then the antedecorator returns no error and continues processing to ensure these transactions
// are included. This will ensure that relayers do not waste fees on multiMsg transactions when another relayer has already submitted
// all packets, by rejecting the tx at the mempool layer.
func (rrd RedundantRelayDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
// do not run redundancy check on DeliverTx or simulate
if (ctx.IsCheckTx() || ctx.IsReCheckTx()) && !simulate {
// keep track of total packet messages and number of redundancies across `RecvPacket`, `AcknowledgePacket`, and `TimeoutPacket/OnClose`
redundancies := 0
packetMsgs := 0
for _, m := range tx.GetMsgs() {
switch msg := m.(type) {
case *channeltypes.MsgRecvPacket:
var (
response *channeltypes.MsgRecvPacketResponse
err error
)
// when we are in ReCheckTx mode, ctx.IsCheckTx() will also return true
// therefore we must start the if statement on ctx.IsReCheckTx() to correctly
// determine which mode we are in
if ctx.IsReCheckTx() {
response, err = rrd.recvPacketReCheckTx(ctx, msg)
} else {
response, err = rrd.recvPacketCheckTx(ctx, msg)
}
if err != nil {
return ctx, err
}
if response.Result == channeltypes.NOOP {
redundancies++
}
packetMsgs++
case *channeltypes.MsgAcknowledgement:
response, err := rrd.k.Acknowledgement(ctx, msg)
if err != nil {
return ctx, err
}
if response.Result == channeltypes.NOOP {
redundancies++
}
packetMsgs++
case *channeltypes.MsgTimeout:
response, err := rrd.k.Timeout(ctx, msg)
if err != nil {
return ctx, err
}
if response.Result == channeltypes.NOOP {
redundancies++
}
packetMsgs++
case *channeltypes.MsgTimeoutOnClose:
response, err := rrd.k.TimeoutOnClose(ctx, msg)
if err != nil {
return ctx, err
}
if response.Result == channeltypes.NOOP {
redundancies++
}
packetMsgs++
case *clienttypes.MsgUpdateClient:
if err := rrd.updateClientCheckTx(ctx, msg); err != nil {
return ctx, err
}
default:
// if the multiMsg tx has a msg that is not a packet msg or update msg, then we will not return error
// regardless of if all packet messages are redundant. This ensures that non-packet messages get processed
// even if they get batched with redundant packet messages.
return next(ctx, tx, simulate)
}
}
// only return error if all packet messages are redundant
if redundancies == packetMsgs && packetMsgs > 0 {
return ctx, channeltypes.ErrRedundantTx
}
}
return next(ctx, tx, simulate)
}
// recvPacketCheckTx runs a subset of ibc recv packet logic to be used specifically within the RedundantRelayDecorator AnteHandler.
// It only performs core IBC receiving logic and skips any application logic.
func (rrd RedundantRelayDecorator) recvPacketCheckTx(ctx sdk.Context, msg *channeltypes.MsgRecvPacket) (*channeltypes.MsgRecvPacketResponse, error) {
// If the packet was already received, perform a no-op
// Use a cached context to prevent accidental state changes
cacheCtx, writeFn := ctx.CacheContext()
_, err := rrd.k.ChannelKeeper.RecvPacket(cacheCtx, msg.Packet, msg.ProofCommitment, msg.ProofHeight)
switch err {
case nil:
writeFn()
case channeltypes.ErrNoOpMsg:
return &channeltypes.MsgRecvPacketResponse{Result: channeltypes.NOOP}, nil
default:
return nil, errorsmod.Wrap(err, "receive packet verification failed")
}
return &channeltypes.MsgRecvPacketResponse{Result: channeltypes.SUCCESS}, nil
}
// recvPacketReCheckTx runs a subset of ibc recv packet logic to be used specifically within the RedundantRelayDecorator AnteHandler.
// It only performs core IBC receiving logic and skips any application logic.
func (rrd RedundantRelayDecorator) recvPacketReCheckTx(ctx sdk.Context, msg *channeltypes.MsgRecvPacket) (*channeltypes.MsgRecvPacketResponse, error) {
// If the packet was already received, perform a no-op
// Use a cached context to prevent accidental state changes
cacheCtx, writeFn := ctx.CacheContext()
err := rrd.k.ChannelKeeper.RecvPacketReCheckTx(cacheCtx, msg.Packet)
switch err {
case nil:
writeFn()
case channeltypes.ErrNoOpMsg:
return &channeltypes.MsgRecvPacketResponse{Result: channeltypes.NOOP}, nil
default:
return nil, errorsmod.Wrap(err, "receive packet verification failed")
}
return &channeltypes.MsgRecvPacketResponse{Result: channeltypes.SUCCESS}, nil
}
// updateClientCheckTx runs a subset of ibc client update logic to be used specifically within the RedundantRelayDecorator AnteHandler.
// The following function performs ibc client message verification for CheckTx only and state updates in both CheckTx and ReCheckTx.
// Note that misbehaviour checks are omitted.
func (rrd RedundantRelayDecorator) updateClientCheckTx(ctx sdk.Context, msg *clienttypes.MsgUpdateClient) error {
clientMsg, err := clienttypes.UnpackClientMessage(msg.ClientMessage)
if err != nil {
return err
}
if status := rrd.k.ClientKeeper.GetClientStatus(ctx, msg.ClientId); status != exported.Active {
return errorsmod.Wrapf(clienttypes.ErrClientNotActive, "cannot update client (%s) with status %s", msg.ClientId, status)
}
clientModule, err := rrd.k.ClientKeeper.Route(ctx, msg.ClientId)
if err != nil {
return err
}
if !ctx.IsReCheckTx() {
if err := clientModule.VerifyClientMessage(ctx, msg.ClientId, clientMsg); err != nil {
return err
}
}
heights := clientModule.UpdateState(ctx, msg.ClientId, clientMsg)
ctx.Logger().With("module", "x/"+exported.ModuleName).Debug("ante ibc client update", "consensusHeights", heights)
return nil
}