/
ante.go
344 lines (281 loc) · 10.8 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
package auth
import (
"bytes"
"encoding/hex"
"fmt"
"math/big"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/secp256k1"
authTypes "github.com/maticnetwork/heimdall/auth/types"
"github.com/maticnetwork/heimdall/chainmanager"
checkpointTypes "github.com/maticnetwork/heimdall/checkpoint/types"
"github.com/maticnetwork/heimdall/helper"
"github.com/maticnetwork/heimdall/types"
)
var (
// simulation signature values used to estimate gas consumption
simSecp256k1Pubkey secp256k1.PubKeySecp256k1
// DefaultFeeInMatic represents default fee in matic
DefaultFeeInMatic = big.NewInt(10).Exp(big.NewInt(10), big.NewInt(15), nil)
// DefaultFeeWantedPerTx fee wanted per tx
DefaultFeeWantedPerTx = sdk.Coins{sdk.Coin{Denom: authTypes.FeeToken, Amount: sdk.NewIntFromBigInt(DefaultFeeInMatic)}}
)
func init() {
// This decodes a valid hex string into a sepc256k1Pubkey for use in transaction simulation
bz, _ := hex.DecodeString("035AD6810A47F073553FF30D2FCC7E0D3B1C0B74B61A1AAA2582344037151E143A")
copy(simSecp256k1Pubkey[:], bz)
}
// SignatureVerificationGasConsumer is the type of function that is used to both consume gas when verifying signatures
// and also to accept or reject different types of PubKey's. This is where apps can define their own PubKey
type SignatureVerificationGasConsumer = func(meter sdk.GasMeter, sig authTypes.StdSignature, params authTypes.Params) sdk.Result
//
// Collect fees interface
//
// FeeCollector interface for fees collector
type FeeCollector interface {
GetModuleAddress(string) types.HeimdallAddress
SendCoinsFromAccountToModule(
sdk.Context,
types.HeimdallAddress,
string,
sdk.Coins,
) sdk.Error
}
// MainTxMsg tx hash
type MainTxMsg interface {
GetTxHash() types.HeimdallHash
GetLogIndex() uint64
}
// NewAnteHandler returns an AnteHandler that checks and increments sequence
// numbers, checks signatures & account numbers, and deducts fees from the first
// signer.
func NewAnteHandler(
ak AccountKeeper,
chainKeeper chainmanager.Keeper,
feeCollector FeeCollector,
contractCaller helper.IContractCaller,
sigGasConsumer SignatureVerificationGasConsumer,
) sdk.AnteHandler {
return func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
// get module address
if addr := feeCollector.GetModuleAddress(authTypes.FeeCollectorName); addr.Empty() {
return newCtx, sdk.ErrInternal(fmt.Sprintf("%s module account has not been set", authTypes.FeeCollectorName)).Result(), true
}
// all transactions must be of type auth.StdTx
stdTx, ok := tx.(authTypes.StdTx)
if !ok {
// Set a gas meter with limit 0 as to prevent an infinite gas meter attack
// during runTx.
newCtx = SetGasMeter(simulate, ctx, 0)
return newCtx, sdk.ErrInternal("tx must be StdTx").Result(), true
}
//Check whether the chain has reached the hard fork length to execute milestone msgs
if ctx.BlockHeight() < helper.GetAalborgHardForkHeight() && (stdTx.Msg.Type() == checkpointTypes.EventTypeMilestone || stdTx.Msg.Type() == checkpointTypes.EventTypeMilestoneTimeout) {
newCtx = SetGasMeter(simulate, ctx, 0)
return newCtx, sdk.ErrTxDecode("error decoding transaction").Result(), true
}
// get account params
params := ak.GetParams(ctx)
// gas for tx
gasForTx := params.MaxTxGas // stdTx.Fee.Gas
amount, ok := sdk.NewIntFromString(params.TxFees)
if !ok {
return newCtx, sdk.ErrInternal("Invalid param tx fees").Result(), true
}
feeForTx := sdk.Coins{sdk.Coin{Denom: authTypes.FeeToken, Amount: amount}} // stdTx.Fee.Amount
// new gas meter
newCtx = SetGasMeter(simulate, ctx, gasForTx)
// AnteHandlers must have their own defer/recover in order for the BaseApp
// to know how much gas was used! This is because the GasMeter is created in
// the AnteHandler, but if it panics the context won't be set properly in
// runTx's recover call.
defer func() {
if r := recover(); r != nil {
switch rType := r.(type) {
case sdk.ErrorOutOfGas:
log := fmt.Sprintf(
"out of gas in location: %v; gasWanted: %d, gasUsed: %d",
rType.Descriptor, gasForTx, newCtx.GasMeter().GasConsumed(),
)
res = sdk.ErrOutOfGas(log).Result()
res.GasWanted = gasForTx
res.GasUsed = newCtx.GasMeter().GasConsumed()
abort = true
default:
panic(r)
}
}
}()
// validate tx
if err := tx.ValidateBasic(); err != nil {
return newCtx, err.Result(), true
}
if res := ValidateMemo(stdTx, params); !res.IsOK() {
return newCtx, res, true
}
// stdSigs contains the sequence number, account number, and signatures.
// When simulating, this would just be a 0-length slice.
signerAddrs := stdTx.GetSigners()
if len(signerAddrs) == 0 {
return newCtx, sdk.ErrNoSignatures("no signers").Result(), true
}
if len(signerAddrs) > 1 {
return newCtx, sdk.ErrUnauthorized("wrong number of signers").Result(), true
}
isGenesis := ctx.BlockHeight() == 0
// fetch first signer, who's going to pay the fees
signerAcc, res := GetSignerAcc(newCtx, ak, types.AccAddressToHeimdallAddress(signerAddrs[0]))
if !res.IsOK() {
return newCtx, res, true
}
// deduct the fees
if !feeForTx.IsZero() {
res = DeductFees(feeCollector, newCtx, signerAcc, feeForTx)
if !res.IsOK() {
return newCtx, res, true
}
// reload the account as fees have been deducted
signerAcc = ak.GetAccount(newCtx, signerAcc.GetAddress())
}
// stdSigs contains the sequence number, account number, and signatures.
// When simulating, this would just be a 0-length slice.
stdSigs := stdTx.GetSignatures()
// check signature, return account with incremented nonce
signBytes := GetSignBytes(ctx, newCtx.ChainID(), stdTx, signerAcc, isGenesis)
signerAcc, res = processSig(newCtx, signerAcc, stdSigs[0], signBytes, simulate, params, sigGasConsumer)
if !res.IsOK() {
return newCtx, res, true
}
ak.SetAccount(newCtx, signerAcc)
// TODO: tx tags (?)
return newCtx, sdk.Result{GasWanted: gasForTx}, false // continue...
}
}
// GetSignerAcc returns an account for a given address that is expected to sign
// a transaction.
func GetSignerAcc(
ctx sdk.Context,
ak AccountKeeper,
addr types.HeimdallAddress,
) (authTypes.Account, sdk.Result) {
if acc := ak.GetAccount(ctx, addr); acc != nil {
return acc, sdk.Result{}
}
return nil, sdk.ErrUnknownAddress(fmt.Sprintf("account %s does not exist", addr)).Result()
}
// ValidateMemo validates the memo size.
func ValidateMemo(stdTx authTypes.StdTx, params authTypes.Params) sdk.Result {
memoLength := len(stdTx.GetMemo())
if uint64(memoLength) > params.MaxMemoCharacters {
return sdk.ErrMemoTooLarge(
fmt.Sprintf(
"maximum number of characters is %d but received %d characters",
params.MaxMemoCharacters, memoLength,
),
).Result()
}
return sdk.Result{}
}
// verify the signature and increment the sequence. If the account doesn't have
// a pubkey, set it.
func processSig(
ctx sdk.Context,
acc authTypes.Account,
sig authTypes.StdSignature,
signBytes []byte,
simulate bool,
params authTypes.Params,
sigGasConsumer SignatureVerificationGasConsumer,
) (updatedAcc authTypes.Account, res sdk.Result) {
if res := sigGasConsumer(ctx.GasMeter(), sig, params); !res.IsOK() {
return nil, res
}
if !simulate {
var pk secp256k1.PubKeySecp256k1
p, err := authTypes.RecoverPubkey(signBytes, sig.Bytes())
if err != nil {
return nil, sdk.ErrUnauthorized("signature verification failed; verify correct account sequence and chain-id").Result()
}
copy(pk[:], p[:])
if !bytes.Equal(acc.GetAddress().Bytes(), pk.Address().Bytes()) {
return nil, sdk.ErrUnauthorized("signature verification failed; verify correct account sequence and chain-id").Result()
}
if acc.GetPubKey() == nil {
var cryptoPk crypto.PubKey = pk
if err = acc.SetPubKey(cryptoPk); err != nil {
return nil, sdk.ErrUnauthorized("error while updating account pubkey").Result()
}
}
}
if err := acc.SetSequence(acc.GetSequence() + 1); err != nil {
return nil, sdk.ErrUnauthorized("error while updating account sequence").Result()
}
return acc, res
}
// DefaultSigVerificationGasConsumer is the default implementation of SignatureVerificationGasConsumer. It consumes gas
// for signature verification based upon the public key type. The cost is fetched from the given params and is matched
// by the concrete type.
func DefaultSigVerificationGasConsumer(
meter sdk.GasMeter, sig authTypes.StdSignature, params authTypes.Params,
) sdk.Result {
meter.ConsumeGas(params.SigVerifyCostSecp256k1, "ante verify: secp256k1")
return sdk.Result{}
}
// DeductFees deducts fees from the given account.
//
// NOTE: We could use the CoinKeeper (in addition to the AccountKeeper, because
// the CoinKeeper doesn't give us accounts), but it seems easier to do this.
func DeductFees(feeCollector FeeCollector, ctx sdk.Context, acc authTypes.Account, fees sdk.Coins) sdk.Result {
blockTime := ctx.BlockHeader().Time
coins := acc.GetCoins()
if !fees.IsValid() {
return sdk.ErrInsufficientFee(fmt.Sprintf("invalid fee amount: %s", fees)).Result()
}
// verify the account has enough funds to pay for fees
_, hasNeg := coins.SafeSub(fees)
if hasNeg {
return sdk.ErrInsufficientFunds(
fmt.Sprintf("insufficient funds to pay for fees; %s < %s", coins, fees),
).Result()
}
// Validate the account has enough "spendable" coins
spendableCoins := acc.SpendableCoins(blockTime)
if _, hasNeg := spendableCoins.SafeSub(fees); hasNeg {
return sdk.ErrInsufficientFunds(
fmt.Sprintf("insufficient funds to pay for fees; %s < %s", spendableCoins, fees),
).Result()
}
err := feeCollector.SendCoinsFromAccountToModule(ctx, acc.GetAddress(), authTypes.FeeCollectorName, fees)
if err != nil {
return err.Result()
}
return sdk.Result{}
}
// SetGasMeter returns a new context with a gas meter set from a given context.
func SetGasMeter(simulate bool, ctx sdk.Context, gasLimit uint64) sdk.Context {
// In various cases such as simulation and during the genesis block, we do not
// meter any gas utilization.
if simulate || ctx.BlockHeight() == 0 {
return ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
}
return ctx.WithGasMeter(sdk.NewGasMeter(gasLimit))
}
// GetSignBytes returns a slice of bytes to sign over for a given transaction
// and an account.
func GetSignBytes(ctx sdk.Context, chainID string, stdTx authTypes.StdTx, acc authTypes.Account, genesis bool) []byte {
var accNum uint64
if !genesis {
accNum = acc.GetAccountNumber()
}
signBytes := authTypes.StdSignBytes(chainID, accNum, acc.GetSequence(), stdTx.Msg, stdTx.Memo)
if ctx.BlockHeight() > helper.GetNewHexToStringAlgoHeight() {
return signBytes
}
const newData = ",\"data\":\"0x\","
const oldData = ",\"data\":\"0x0\","
if bytes.Contains(signBytes, []byte(newData)) {
signBytes = bytes.Replace(signBytes, []byte(newData), []byte(oldData), 1)
}
return signBytes
}