/
account.go
317 lines (276 loc) · 9.83 KB
/
account.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
package ante
import (
"bytes"
"math/big"
"strconv"
"strings"
tmtypes "github.com/okex/exchain/libs/tendermint/types"
"github.com/ethereum/go-ethereum/common"
ethcore "github.com/ethereum/go-ethereum/core"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/okex/exchain/libs/cosmos-sdk/baseapp"
sdk "github.com/okex/exchain/libs/cosmos-sdk/types"
sdkerrors "github.com/okex/exchain/libs/cosmos-sdk/types/errors"
"github.com/okex/exchain/libs/cosmos-sdk/x/auth"
"github.com/okex/exchain/libs/cosmos-sdk/x/auth/exported"
"github.com/okex/exchain/libs/cosmos-sdk/x/auth/types"
"github.com/okex/exchain/x/evm"
evmtypes "github.com/okex/exchain/x/evm/types"
)
type accountKeeperInterface interface {
SetAccount(ctx sdk.Context, acc exported.Account)
}
type AccountAnteDecorator struct {
ak auth.AccountKeeper
sk types.SupplyKeeper
evmKeeper EVMKeeper
}
// NewAccountVerificationDecorator creates a new AccountVerificationDecorator
func NewAccountAnteDecorator(ak auth.AccountKeeper, ek EVMKeeper, sk types.SupplyKeeper) AccountAnteDecorator {
return AccountAnteDecorator{
ak: ak,
sk: sk,
evmKeeper: ek,
}
}
func accountVerification(ctx *sdk.Context, acc exported.Account, tx *evmtypes.MsgEthereumTx) error {
if ctx.BlockHeight() == 0 && acc.GetAccountNumber() != 0 {
return sdkerrors.Wrapf(
sdkerrors.ErrInvalidSequence,
"invalid account number for height zero (got %d)", acc.GetAccountNumber(),
)
}
const evmDenom = sdk.DefaultBondDenom
feeInts := feeIntsPool.Get().(*[2]big.Int)
defer feeIntsPool.Put(feeInts)
// validate sender has enough funds to pay for gas cost
balance := acc.GetCoins().AmountOf(evmDenom)
if balance.Int.Cmp(tx.CalcCostTo(&feeInts[0])) < 0 {
return sdkerrors.Wrapf(
sdkerrors.ErrInsufficientFunds,
"sender balance < tx gas cost (%s%s < %s%s)", balance.String(), evmDenom, sdk.NewDecFromBigIntWithPrec(tx.Cost(), sdk.Precision).String(), evmDenom,
)
}
return nil
}
func nonceVerificationInCheckTx(ctx sdk.Context, seq uint64, msgEthTx *evmtypes.MsgEthereumTx, isReCheckTx bool) error {
if isReCheckTx {
// recheckTx mode
// sequence must strictly increasing
if msgEthTx.Data.AccountNonce != seq {
return sdkerrors.Wrapf(
sdkerrors.ErrInvalidSequence,
"invalid nonce; got %d, expected %d", msgEthTx.Data.AccountNonce, seq,
)
}
} else {
if baseapp.IsMempoolEnablePendingPool() {
if msgEthTx.Data.AccountNonce < seq {
return sdkerrors.Wrapf(
sdkerrors.ErrInvalidSequence,
"invalid nonce; got %d, expected %d", msgEthTx.Data.AccountNonce, seq,
)
}
} else {
// checkTx mode
checkTxModeNonce := seq
if !baseapp.IsMempoolEnableRecheck() {
// if is enable recheck, the sequence of checkState will increase after commit(), so we do not need
// to add pending txs len in the mempool.
// but, if disable recheck, we will not increase sequence of checkState (even in force recheck case, we
// will also reset checkState), so we will need to add pending txs len to get the right nonce
gPool := baseapp.GetGlobalMempool()
if gPool != nil {
addr := msgEthTx.GetSender(ctx)
if pendingNonce, ok := gPool.GetPendingNonce(addr); ok {
checkTxModeNonce = pendingNonce + 1
}
}
}
if baseapp.IsMempoolEnableSort() {
if msgEthTx.Data.AccountNonce < seq || msgEthTx.Data.AccountNonce > checkTxModeNonce {
accNonceStr := strconv.FormatUint(msgEthTx.Data.AccountNonce, 10)
seqStr := strconv.FormatUint(seq, 10)
checkTxModeNonceStr := strconv.FormatUint(checkTxModeNonce, 10)
errStr := strings.Join([]string{
"invalid nonce; got ", accNonceStr,
", expected in the range of [", seqStr, ", ", checkTxModeNonceStr, "]"},
"")
return sdkerrors.WrapNoStack(sdkerrors.ErrInvalidSequence, errStr)
}
} else {
if msgEthTx.Data.AccountNonce != checkTxModeNonce {
accNonceStr := strconv.FormatUint(msgEthTx.Data.AccountNonce, 10)
checkTxModeNonceStr := strconv.FormatUint(checkTxModeNonce, 10)
errStr := strings.Join([]string{
"invalid nonce; got ", accNonceStr, ", expected ", checkTxModeNonceStr},
"")
return sdkerrors.WrapNoStack(sdkerrors.ErrInvalidSequence, errStr)
}
}
}
}
return nil
}
func nonceVerification(ctx sdk.Context, acc exported.Account, msgEthTx *evmtypes.MsgEthereumTx) (sdk.Context, error) {
seq := acc.GetSequence()
// if multiple transactions are submitted in succession with increasing nonces,
// all will be rejected except the first, since the first needs to be included in a block
// before the sequence increments
if ctx.IsCheckTx() {
ctx.SetAccountNonce(seq)
// will be checkTx and RecheckTx mode
err := nonceVerificationInCheckTx(ctx, seq, msgEthTx, ctx.IsReCheckTx())
if err != nil {
return ctx, err
}
} else {
// only deliverTx mode
if msgEthTx.Data.AccountNonce != seq {
return ctx, sdkerrors.Wrapf(
sdkerrors.ErrInvalidSequence,
"invalid nonce; got %d, expected %d", msgEthTx.Data.AccountNonce, seq,
)
}
}
return ctx, nil
}
func ethGasConsume(ek EVMKeeper, sk types.SupplyKeeper, ctx *sdk.Context, acc exported.Account, accGetGas sdk.Gas, msgEthTx *evmtypes.MsgEthereumTx, simulate bool) error {
gasLimit := msgEthTx.GetGas()
if shouldIntrinsicGas(ek, ctx, msgEthTx) {
gas, err := ethcore.IntrinsicGas(msgEthTx.Data.Payload, []ethtypes.AccessTuple{}, msgEthTx.To() == nil, true, false)
if err != nil {
return sdkerrors.Wrap(err, "failed to compute intrinsic gas cost")
}
// intrinsic gas verification during CheckTx
if ctx.IsCheckTx() && gasLimit < gas {
return sdkerrors.Wrapf(sdkerrors.ErrOutOfGas, "intrinsic gas too low: %d < %d", gasLimit, gas)
}
}
// Charge sender for gas up to limit
if gasLimit != 0 {
feeInts := feeIntsPool.Get().(*[2]big.Int)
defer feeIntsPool.Put(feeInts)
// Cost calculates the fees paid to validators based on gas limit and price
cost := (&feeInts[0]).SetUint64(gasLimit)
cost = cost.Mul(msgEthTx.Data.Price, cost)
const evmDenom = sdk.DefaultBondDenom
feeAmt := sdk.NewDecCoinsFromDec(evmDenom, sdk.NewDecWithBigIntAndPrec(cost, sdk.Precision))
ctx.UpdateFromAccountCache(acc, accGetGas)
err := auth.DeductFees(sk, *ctx, acc, feeAmt)
if err != nil {
return err
}
}
// Set gas meter after ante handler to ignore gaskv costs
auth.SetGasMeter(simulate, ctx, gasLimit)
return nil
}
func shouldIntrinsicGas(ek EVMKeeper, ctx *sdk.Context, msgEthTx *evmtypes.MsgEthereumTx) bool {
if !tmtypes.HigherThanVenus6(ctx.BlockHeight()) {
return true
} else { // e2c tx no need ethcore check gas than Venus6
return !IsE2CTx(ek, ctx, msgEthTx)
}
}
func IsE2CTx(ek EVMKeeper, ctx *sdk.Context, msgEthTx *evmtypes.MsgEthereumTx) bool {
currentGasmeter := ctx.GasMeter()
ctx.SetGasMeter(sdk.NewInfiniteGasMeter())
defer ctx.SetGasMeter(currentGasmeter)
toAddr, ok := evm.EvmConvertJudge(msgEthTx)
if ok && len(toAddr) != 0 && ek.IsMatchSysContractAddress(*ctx, toAddr) {
return true
}
return false
}
func incrementSeq(ctx sdk.Context, msgEthTx *evmtypes.MsgEthereumTx, accAddress sdk.AccAddress, ak auth.AccountKeeper, acc exported.Account) {
if ctx.IsCheckTx() && !ctx.IsReCheckTx() && !baseapp.IsMempoolEnableRecheck() && !ctx.IsTraceTx() {
return
}
// get and set account must be called with an infinite gas meter in order to prevent
// additional gas from being deducted.
infGasMeter := sdk.GetReusableInfiniteGasMeter()
defer sdk.ReturnInfiniteGasMeter(infGasMeter)
ctx.SetGasMeter(infGasMeter)
// increment sequence of all signers
// eth tx only has one signer
if accAddress.Empty() {
accAddress = msgEthTx.AccountAddress()
}
var sacc exported.Account
if acc != nil && bytes.Equal(accAddress, acc.GetAddress()) {
// because we use infinite gas meter, we can don't care about the gas
sacc = acc
} else {
sacc = ak.GetAccount(ctx, accAddress)
}
seq := sacc.GetSequence()
if !baseapp.IsMempoolEnablePendingPool() {
seq++
} else if msgEthTx.Data.AccountNonce == seq {
seq++
}
if err := sacc.SetSequence(seq); err != nil {
panic(err)
}
ak.SetAccount(ctx, sacc)
return
}
func (avd AccountAnteDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
msgEthTx, ok := tx.(*evmtypes.MsgEthereumTx)
if !ok {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", tx)
}
var acc exported.Account
var getAccGasUsed sdk.Gas
address := msgEthTx.AccountAddress()
if address.Empty() && ctx.From() != "" {
msgEthTx.SetFrom(ctx.From())
address = msgEthTx.AccountAddress()
}
if ctx.IsCheckTx() && !address.Empty() && msgEthTx.From == "" {
msgEthTx.SetFrom(common.BytesToAddress(address.Bytes()).String())
}
if !simulate {
if address.Empty() {
panic("sender address cannot be empty")
}
if ctx.IsCheckTx() {
acc = avd.ak.GetAccount(ctx, address)
if acc == nil {
acc = avd.ak.NewAccountWithAddress(ctx, address)
avd.ak.SetAccount(ctx, acc)
}
// on InitChain make sure account number == 0
err = accountVerification(&ctx, acc, msgEthTx)
if err != nil {
return ctx, err
}
}
acc, getAccGasUsed = getAccount(&avd.ak, &ctx, address, acc)
if acc == nil {
return ctx, sdkerrors.Wrapf(
sdkerrors.ErrUnknownAddress,
"account %s (%s) is nil", common.BytesToAddress(address.Bytes()), address,
)
}
// account would not be updated
ctx, err = nonceVerification(ctx, acc, msgEthTx)
if err != nil {
return ctx, err
}
// consume gas for compatible
ctx.GasMeter().ConsumeGas(getAccGasUsed, "get account")
ctx.EnableAccountCache()
// account would be updated
err = ethGasConsume(avd.evmKeeper, avd.sk, &ctx, acc, getAccGasUsed, msgEthTx, simulate)
acc = nil
acc, _ = ctx.GetFromAccountCacheData().(exported.Account)
ctx.DisableAccountCache()
if err != nil {
return ctx, err
}
}
incrementSeq(ctx, msgEthTx, address, avd.ak, acc)
return next(ctx, tx, simulate)
}