/
state_transition.go
153 lines (131 loc) · 5.74 KB
/
state_transition.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
package evm
import (
"math/big"
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/evmos/ethermint/x/evm/types"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
)
// ApplyTransaction runs and attempts to perform a state transition with the given transaction (i.e Message), that will
// only be persisted (committed) to the underlying KVStore if the transaction does not fail.
//
// # Gas tracking
//
// Ethereum consumes gas according to the EVM opcodes instead of general reads and writes to store. Because of this, the
// state transition needs to ignore the SDK gas consumption mechanism defined by the GasKVStore and instead consume the
// amount of gas used by the VM execution. The amount of gas used is tracked by the EVM and returned in the execution
// result.
//
// Prior to the execution, the starting tx gas meter is saved and replaced with an infinite gas meter in a new context
// in order to ignore the SDK gas consumption config values (read, write, has, delete).
// After the execution, the gas used from the message execution will be added to the starting gas consumed, taking into
// consideration the amount of gas returned. Finally, the context is updated with the EVM gas consumed value prior to
// returning.
//
// For relevant discussion see: https://github.com/cosmos/cosmos-sdk/discussions/9072
func (k *Keeper) ApplyTransaction(ctx sdk.Context, tx *ethtypes.Transaction) (*types.MsgEthereumTxResponse, error) {
var (
bloom *big.Int
bloomReceipt ethtypes.Bloom
)
cfg, err := k.evmkeeper.EVMConfig(ctx, sdk.ConsAddress(ctx.BlockHeader().ProposerAddress), k.evmkeeper.ChainID())
if err != nil {
return nil, errorsmod.Wrap(err, "failed to load evm config")
}
txConfig := k.evmkeeper.TxConfig(ctx, tx.Hash())
// get the signer according to the chain rules from the config and block height
signer := ethtypes.MakeSigner(cfg.ChainConfig, big.NewInt(ctx.BlockHeight()))
msg, err := tx.AsMessage(signer, cfg.BaseFee)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to return ethereum transaction as core message")
}
// snapshot to contain the tx processing and post processing in same scope
var commit func()
tmpCtx := ctx
if k.hasHook {
// Create a cache context to revert state when tx hooks fails,
// the cache context is only committed when both tx and hooks executed successfully.
// Didn't use `Snapshot` because the context stack has exponential complexity on certain operations,
// thus restricted to be used only inside `ApplyMessage`.
tmpCtx, commit = ctx.CacheContext()
}
// pass true to commit the StateDB
res, err := k.evmkeeper.ApplyMessageWithConfig(tmpCtx, msg, nil, true, cfg, txConfig)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to apply ethereum core message")
}
logs := types.LogsToEthereum(res.Logs)
// Compute block bloom filter
if len(logs) > 0 {
bloom = k.evmkeeper.GetBlockBloomTransient(ctx)
bloom.Or(bloom, big.NewInt(0).SetBytes(ethtypes.LogsBloom(logs)))
bloomReceipt = ethtypes.BytesToBloom(bloom.Bytes())
}
cumulativeGasUsed := res.GasUsed
if ctx.BlockGasMeter() != nil {
limit := ctx.BlockGasMeter().Limit()
cumulativeGasUsed += ctx.BlockGasMeter().GasConsumed()
if cumulativeGasUsed > limit {
cumulativeGasUsed = limit
}
}
var contractAddr common.Address
if msg.To() == nil {
contractAddr = crypto.CreateAddress(msg.From(), msg.Nonce())
}
receipt := ðtypes.Receipt{
Type: tx.Type(),
PostState: nil, // TODO: intermediate state root
CumulativeGasUsed: cumulativeGasUsed,
Bloom: bloomReceipt,
Logs: logs,
TxHash: txConfig.TxHash,
ContractAddress: contractAddr,
GasUsed: res.GasUsed,
BlockHash: txConfig.BlockHash,
BlockNumber: big.NewInt(ctx.BlockHeight()),
TransactionIndex: txConfig.TxIndex,
}
if !res.Failed() {
receipt.Status = ethtypes.ReceiptStatusSuccessful
// Only call hooks if tx executed successfully.
if err = k.evmkeeper.PostTxProcessing(tmpCtx, msg, receipt); err != nil {
// If hooks return error, revert the whole tx.
res.VmError = types.ErrPostTxProcessing.Error()
k.evmkeeper.Logger(ctx).Error("tx post processing failed", "error", err)
// If the tx failed in post processing hooks, we should clear the logs
res.Logs = nil
} else if commit != nil {
// PostTxProcessing is successful, commit the tmpCtx
commit()
// Since the post processing can alter the log, we need to update the result
res.Logs = types.NewLogsFromEth(receipt.Logs)
ctx.EventManager().EmitEvents(tmpCtx.EventManager().Events())
}
}
// refund gas in order to match the Ethereum gas consumption instead of the default SDK one.
if err = k.evmkeeper.RefundGas(ctx, msg, msg.Gas()-res.GasUsed, cfg.Params.EvmDenom); err != nil {
return nil, errorsmod.Wrapf(err, "failed to refund gas leftover gas to sender %s", msg.From())
}
// if eip1559 transaction,burn the base fee
if ethtypes.DynamicFeeTxType == tx.Type() {
if err := k.burnBaseFee(ctx, res.GasUsed, cfg.BaseFee); err != nil {
return nil, errorsmod.Wrapf(err, "failed to burn base fee")
}
}
if len(receipt.Logs) > 0 {
// Update transient block bloom filter
k.evmkeeper.SetBlockBloomTransient(ctx, receipt.Bloom.Big())
k.evmkeeper.SetLogSizeTransient(ctx, uint64(txConfig.LogIndex)+uint64(len(receipt.Logs)))
}
k.evmkeeper.SetTxIndexTransient(ctx, uint64(txConfig.TxIndex)+1)
totalGasUsed, err := k.evmkeeper.AddTransientGasUsed(ctx, res.GasUsed)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to add transient gas used")
}
// reset the gas meter for current cosmos transaction
k.evmkeeper.ResetGasMeterAndConsumeGas(ctx, totalGasUsed)
return res, nil
}