-
Notifications
You must be signed in to change notification settings - Fork 122
/
Copy pathante.go
418 lines (380 loc) · 17.3 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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
package app
import (
"sync"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/store/cachemulti"
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
customante "github.com/dydxprotocol/v4-chain/protocol/app/ante"
"github.com/dydxprotocol/v4-chain/protocol/lib"
libante "github.com/dydxprotocol/v4-chain/protocol/lib/ante"
"github.com/dydxprotocol/v4-chain/protocol/lib/log"
accountpluskeeper "github.com/dydxprotocol/v4-chain/protocol/x/accountplus/keeper"
clobante "github.com/dydxprotocol/v4-chain/protocol/x/clob/ante"
clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types"
perpetualstypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types"
pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types"
)
// HandlerOptions are the options required for constructing an SDK AnteHandler.
// Note: This struct is defined here in order to add `ClobKeeper`. We use
// struct embedding to include the normal cosmos-sdk `HandlerOptions`.
type HandlerOptions struct {
ante.HandlerOptions
Codec codec.Codec
AuthStoreKey storetypes.StoreKey
AccountplusKeeper *accountpluskeeper.Keeper
ClobKeeper clobtypes.ClobKeeper
PerpetualsKeeper perpetualstypes.PerpetualsKeeper
PricesKeeper pricestypes.PricesKeeper
}
// NewAnteHandler returns an AnteHandler that checks and increments sequence
// numbers, checks signatures & account numbers, deducts fees from the first
// signer, and handles in-memory clob messages.
//
// Note that the contract for the forked version of Cosmos SDK is that during `checkTx` the ante handler
// is responsible for branching and writing the state store. During this time the forked Cosmos SDK has
// a read lock allowing for parallel state reads but no writes. The `AnteHandler` is responsible for ensuring
// the linearization of reads and writes by having locks cover each other. This requires any ante decorators
// that read state that can be mutated during `checkTx` to acquire an appropriate lock. Today that is:
// - account keeper params / consensus params (and all other state) are only read during `checkTx` and only
// mutated during `deliverTx` thus no additional locking is needed to linearize reads and writes.
// - accounts require the per account lock to be acquired since accounts have have pub keys set or the
// sequence number incremented to linearize reads and writes.
// - banks / fee state (and all other state) that can be mutated during `checkTx` requires the global
// lock to be acquired before it is read or written to linearize reads and writes.
//
// During `deliverTx` and simulation the Cosmos SDK is responsible for branching and writing the state store
// so no additional locking is necessary to linearize state reads and writes. Note that simulation only ever occurs
// on a past block and not the current `checkState` so there is no opportunity for it to collide with concurrent
// `checkTx` invocations.
//
// Also note that all the ante decorators that are used return immediately the results of invoking `next` allowing
// us to significantly reduce the stack by saving and passing forward the context to the next ante decorator.
// This allows us to have a method that contains the order in which all the ante decorators are invoked including
// a single place to reason about the locking semantics without needing to look at several ante decorators.
//
// Link to default `AnteHandler` used by cosmos sdk:
// https://github.com/cosmos/cosmos-sdk/blob/3bb27795742dab2451b232bab02b82566d1a0192/x/auth/ante/ante.go#L25
func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
if options.AccountKeeper == nil {
return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "account keeper is required for ante builder")
}
if options.AccountplusKeeper == nil {
return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "accountplus keeper is required for ante builder")
}
if options.BankKeeper == nil {
return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "bank keeper is required for ante builder")
}
if options.ClobKeeper == nil {
return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "clob keeper is required for ante builder")
}
if options.SignModeHandler == nil {
return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder")
}
if options.Codec == nil {
return nil, errorsmod.Wrapf(sdkerrors.ErrLogic, "codec is required for ante builder")
}
if options.AuthStoreKey == nil {
return nil, errorsmod.Wrapf(sdkerrors.ErrLogic, "auth store key is required for ante builder")
}
if options.PerpetualsKeeper == nil {
return nil, errorsmod.Wrapf(sdkerrors.ErrLogic, "perpetuals keeper is required for ante builder")
}
if options.PricesKeeper == nil {
return nil, errorsmod.Wrapf(sdkerrors.ErrLogic, "prices keeper is required for ante builder")
}
h := &lockingAnteHandler{
authStoreKey: options.AuthStoreKey,
setupContextDecorator: ante.NewSetUpContextDecorator(),
freeInfiniteGasDecorator: customante.NewFreeInfiniteGasDecorator(),
extensionOptionsChecker: ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker),
validateMsgType: customante.NewValidateMsgTypeDecorator(),
txTimeoutHeight: ante.NewTxTimeoutHeightDecorator(),
validateMemo: ante.NewValidateMemoDecorator(options.AccountKeeper),
validateBasic: ante.NewValidateBasicDecorator(),
validateSigCount: ante.NewValidateSigCountDecorator(options.AccountKeeper),
incrementSequence: customante.NewIncrementSequenceDecorator(options.AccountKeeper),
sigVerification: customante.NewSigVerificationDecorator(
options.AccountKeeper,
*options.AccountplusKeeper,
options.SignModeHandler,
),
consumeTxSizeGas: ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
deductFee: ante.NewDeductFeeDecorator(
options.AccountKeeper,
options.BankKeeper,
options.FeegrantKeeper,
options.TxFeeChecker,
),
setPubKey: ante.NewSetPubKeyDecorator(options.AccountKeeper),
sigGasConsume: ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer),
clobRateLimit: clobante.NewRateLimitDecorator(options.ClobKeeper),
clob: clobante.NewClobDecorator(options.ClobKeeper),
marketUpdates: customante.NewValidateMarketUpdateDecorator(options.PerpetualsKeeper, options.PricesKeeper),
}
return h.AnteHandle, nil
}
// An ante handler that returns the context.
func noOpAnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) {
return ctx, nil
}
type lockingAnteHandler struct {
globalLock sync.Mutex
authStoreKey storetypes.StoreKey
setupContextDecorator ante.SetUpContextDecorator
freeInfiniteGasDecorator customante.FreeInfiniteGasDecorator
extensionOptionsChecker sdk.AnteDecorator
validateMsgType customante.ValidateMsgTypeDecorator
txTimeoutHeight ante.TxTimeoutHeightDecorator
validateMemo ante.ValidateMemoDecorator
validateBasic ante.ValidateBasicDecorator
validateSigCount ante.ValidateSigCountDecorator
incrementSequence customante.IncrementSequenceDecorator
sigVerification customante.SigVerificationDecorator
consumeTxSizeGas ante.ConsumeTxSizeGasDecorator
deductFee ante.DeductFeeDecorator
setPubKey ante.SetPubKeyDecorator
sigGasConsume ante.SigGasConsumeDecorator
clobRateLimit clobante.ClobRateLimitDecorator
clob clobante.ClobDecorator
marketUpdates customante.ValidateMarketUpdateDecorator
}
func (h *lockingAnteHandler) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) {
ctx = log.AddPersistentTagsToLogger(ctx,
log.Callback, lib.TxMode(ctx),
log.BlockHeight, ctx.BlockHeight()+1,
)
isClob, err := clobante.IsSingleClobMsgTx(tx)
if err != nil {
return ctx, err
} else if isClob {
return h.clobAnteHandle(ctx, tx, simulate)
}
if libante.IsSingleAppInjectedMsg(tx.GetMsgs()) {
return h.appInjectedMsgAnteHandle(ctx, tx, simulate)
}
return h.otherMsgAnteHandle(ctx, tx, simulate)
}
func (h *lockingAnteHandler) clobAnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool) (
newCtx sdk.Context,
err error,
) {
// These ante decorators access state but only state that is mutated during `deliverTx`. The Cosmos SDK
// is responsible for linearizing the reads and writes during `deliverTx`.
if ctx, err = h.freeInfiniteGasDecorator.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.extensionOptionsChecker.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.validateMsgType.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.validateBasic.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.txTimeoutHeight.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.validateMemo.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
// During `deliverTx` and simulation the Cosmos SDK is responsible for branching and writing the state store.
// During `checkTx` we acquire a per account lock to prevent stale reads of state that can be mutated during
// `checkTx`. Note that these messages are common so we use a row level like lock for each account and branch
// the state store to support writes in the ante decorators that follow.
var cacheMs storetypes.CacheMultiStore
if !simulate && (ctx.IsCheckTx() || ctx.IsReCheckTx()) {
sigTx, ok := tx.(authsigning.SigVerifiableTx)
if !ok {
return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a sigTx")
}
var signers [][]byte
signers, err = sigTx.GetSigners()
if err != nil {
return ctx, err
}
cacheMs = ctx.MultiStore().(cachemulti.Store).CacheMultiStoreWithLocking(map[storetypes.StoreKey][][]byte{
h.authStoreKey: signers,
})
defer cacheMs.(storetypes.LockingStore).Unlock()
ctx = ctx.WithMultiStore(cacheMs)
}
if ctx, err = h.consumeTxSizeGas.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.setPubKey.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.validateSigCount.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.sigGasConsume.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.sigVerification.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
var isShortTerm bool
if isShortTerm, err = clobante.IsShortTermClobMsgTx(ctx, tx); err != nil {
return ctx, err
}
if !isShortTerm {
if ctx, err = h.incrementSequence.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
}
// We now acquire the global ante handler since the clob decorator is not thread safe and performs
// several reads and writes across many stores.
if !simulate && (ctx.IsCheckTx() || ctx.IsReCheckTx()) {
h.globalLock.Lock()
defer h.globalLock.Unlock()
}
if ctx, err = h.clobRateLimit.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.clob.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
// During non-simulated `checkTx` we must write the store since we own branching and writing.
// During `deliverTx` and simulation the Cosmos SDK is responsible for branching and writing.
if err == nil && !simulate && (ctx.IsCheckTx() || ctx.IsReCheckTx()) {
cacheMs.Write()
}
return ctx, err
}
// appInjectedMsgAnteHandle processes app injected messages through the necessary and sufficient set
// of ante decorators.
//
// Note that app injected messages do not require gas and are unsigned thus we do not need to:
// - setup context to install a gas meter.
// - validate basic for the signature check.
// - set the pub key on the account.
// - verify the signature.
// - consume gas.
// - deduct fees.
// - increment the sequence number.
// - rate limit or handle through the clob decorator since this isn't a clob message.
func (h *lockingAnteHandler) appInjectedMsgAnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool) (
newCtx sdk.Context,
err error,
) {
// Note that app injected messages are only sent during `deliverTx` (checked by validateMsgType)
// and hence we do not require any additional locking beyond what the Cosmos SDK already provides.
if ctx, err = h.freeInfiniteGasDecorator.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.extensionOptionsChecker.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.validateMsgType.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.txTimeoutHeight.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.validateMemo.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.consumeTxSizeGas.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.validateSigCount.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
return ctx, err
}
// otherMsgAnteHandle processes all non-clob and non-app injected messages through the necessary and sufficient
// set of ante decorators.
//
// Note that these messages will never need to use the clob ante decorators so they are omitted.
func (h *lockingAnteHandler) otherMsgAnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool) (
newCtx sdk.Context,
err error,
) {
// During `deliverTx` we hold an exclusive lock on `app.mtx` and have a context with a branched state store
// allowing us to not have to perform any further locking or state store branching.
//
// For `checkTx`, these ante decorators access state but only state that is mutated during `deliverTx`
// and hence since we already hold a read lock on `app.mtx` we can be certain that no state writes will occur.
if ctx, err = h.setupContextDecorator.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.freeInfiniteGasDecorator.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.extensionOptionsChecker.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.validateMsgType.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.validateBasic.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.txTimeoutHeight.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.validateMemo.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.marketUpdates.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
// During `deliverTx` and simulation the Cosmos SDK is responsible for branching and writing the state store.
// During `checkTx` we acquire a per account lock to prevent stale reads of state that can be mutated during
// `checkTx`. Note that we acquire row level like locks per account and also acquire the global lock to ensure
// that we linearize reads and writes for accounts with the clobAnteHandle and the global lock ensures that
// we linearize reads and writes to other stores since the deduct fees decorator mutates state outside of the
// account keeper and those stores are currently not safe for concurrent use.
var cacheMs storetypes.CacheMultiStore
if !simulate && (ctx.IsCheckTx() || ctx.IsReCheckTx()) {
sigTx, ok := tx.(authsigning.SigVerifiableTx)
if !ok {
return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a sigTx")
}
var signers [][]byte
signers, err = sigTx.GetSigners()
if err != nil {
return ctx, err
}
cacheMs = ctx.MultiStore().(cachemulti.Store).CacheMultiStoreWithLocking(map[storetypes.StoreKey][][]byte{
h.authStoreKey: signers,
})
defer cacheMs.(storetypes.LockingStore).Unlock()
ctx = ctx.WithMultiStore(cacheMs)
h.globalLock.Lock()
defer h.globalLock.Unlock()
}
if ctx, err = h.consumeTxSizeGas.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.deductFee.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.setPubKey.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.validateSigCount.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.sigGasConsume.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.sigVerification.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
if ctx, err = h.incrementSequence.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil {
return ctx, err
}
// During non-simulated `checkTx` we must write the store since we own branching and writing.
// During `deliverTx` and simulation the Cosmos SDK is responsible for branching and writing.
if err == nil && !simulate && (ctx.IsCheckTx() || ctx.IsReCheckTx()) {
cacheMs.Write()
}
return ctx, err
}