forked from cerc-io/laconicd-deprecated
docs: update ADR-001 (#122)
* docs: update ADR-001 * update * apply transaction changes:
This commit is contained in:
parent
bb91d8d93d
commit
5af5dd9956
@ -2,16 +2,17 @@
|
|||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
- 2021-06-14: updates after implementation
|
||||||
- 2021-05-15: first draft
|
- 2021-05-15: first draft
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
DRAFT, Not Implemented
|
PROPOSED, Implemented
|
||||||
|
|
||||||
## Abstract
|
## Abstract
|
||||||
|
|
||||||
The current ADR proposes a state machine breaking change to the EVM module state operations
|
The current ADR proposes a state machine breaking change to the EVM module state operations
|
||||||
(`Keeper`, `StateDB` and `StateTransition`) with the goal of reducing code maintainance, increase
|
(`Keeper`, `StateDB` and `StateTransition`) with the goal of reducing code maintenance, increase
|
||||||
performance, and document all the transaction and state cycles and flows.
|
performance, and document all the transaction and state cycles and flows.
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
@ -50,18 +51,18 @@ state transition".
|
|||||||
|
|
||||||
Upon a state transition, these objects will be modified and marked as 'dirty' (a.k.a stateless
|
Upon a state transition, these objects will be modified and marked as 'dirty' (a.k.a stateless
|
||||||
update) on the `CommitStateDB`. Then, at every `EndBlock`, the state of these modified objects will
|
update) on the `CommitStateDB`. Then, at every `EndBlock`, the state of these modified objects will
|
||||||
be 'finalized' and commited to the store, resetting all the dirty list of objects.
|
be 'finalized' and committed to the store, resetting all the dirty list of objects.
|
||||||
|
|
||||||
The core issue arises when a chain that uses the EVM module can have also have their account and
|
The core issue arises when a chain that uses the EVM module can have also have their account and
|
||||||
balances updated through operations from other modules. This means that an EVM state object can be
|
balances updated through operations from other modules. This means that an EVM state object can be
|
||||||
modified through an EVM transaction (`evm.MsgEthereumTx`) and other transactions like `bank.MsgSend`
|
modified through an EVM transaction (`evm.MsgEthereumTx`) and other transactions like `bank.MsgSend`
|
||||||
or `ibctransfer.MsgTransfer`. This can lead to unexpected behaviors like state overwrites, due to
|
or `ibctransfer.MsgTransfer`. This can lead to unexpected behaviors like state overwrites, due to
|
||||||
the current behaviour that caches the dirty state on the EVM instead of commiting any changes
|
the current behavior that caches the dirty state on the EVM instead of committing any changes
|
||||||
directly.
|
directly.
|
||||||
|
|
||||||
### State Transition
|
### State Transition
|
||||||
|
|
||||||
A general EVM state transition is performed by calling the ethereum `vm.EVM` `Create` or `Call` functions, depending on wheather the transaction creates a contract or performs a transfer or call to a given contract.
|
A general EVM state transition is performed by calling the ethereum `vm.EVM` `Create` or `Call` functions, depending on whether the transaction creates a contract or performs a transfer or call to a given contract.
|
||||||
|
|
||||||
In the case of the `x/evm` module, it currently uses a modified version of Geth's `TransitionDB`, that wraps these two `vm.EVM` methods. The reason for using this modified function, is due to several reasons:
|
In the case of the `x/evm` module, it currently uses a modified version of Geth's `TransitionDB`, that wraps these two `vm.EVM` methods. The reason for using this modified function, is due to several reasons:
|
||||||
|
|
||||||
@ -97,28 +98,49 @@ type Keeper struct {
|
|||||||
|
|
||||||
This means that a `Keeper` pointer will now directly be passed to the `vm.EVM` for accessing the state and performing state transitions.
|
This means that a `Keeper` pointer will now directly be passed to the `vm.EVM` for accessing the state and performing state transitions.
|
||||||
|
|
||||||
The ABCI `BeginBlock` and `EndBlock` are have now been refactored to only (1) reset cached fields, and (2) keep track of internal mappings (hashes, height, etc).
|
The ABCI `BeginBlock` and `EndBlock` are have now been refactored to only keep track of internal fields (hashes, block bloom, etc).
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (k *Keeper) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) {
|
func (k *Keeper) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) {
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
// reset cache values and context
|
// update context
|
||||||
k.ResetCacheFields(ctx)
|
k.WithContext(ctx)
|
||||||
|
//...
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k Keeper) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.ValidatorUpdate {
|
func (k Keeper) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.ValidatorUpdate {
|
||||||
// NOTE: UpdateAccounts, Commit and Reset execution steps have been removed in favor of directly
|
// NOTE: UpdateAccounts, Commit and Reset execution steps have been removed in favor of directly
|
||||||
// updating the state.
|
// updating the state.
|
||||||
|
|
||||||
// set the block bloom filter bytes to store
|
// Gas costs are handled within msg handler so costs should be ignored
|
||||||
bloom := ethtypes.BytesToBloom(k.Bloom.Bytes())
|
infCtx := ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
|
||||||
k.SetBlockBloom(ctx, req.Height, bloom)
|
k.WithContext(ctx)
|
||||||
|
|
||||||
|
// get the block bloom bytes from the transient store and set it to the persistent storage
|
||||||
|
bloomBig, found := k.GetBlockBloomTransient()
|
||||||
|
if !found {
|
||||||
|
bloomBig = big.NewInt(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
bloom := ethtypes.BytesToBloom(bloomBig.Bytes())
|
||||||
|
k.SetBlockBloom(infCtx, req.Height, bloom)
|
||||||
|
k.WithContext(ctx)
|
||||||
|
|
||||||
return []abci.ValidatorUpdate{}
|
return []abci.ValidatorUpdate{}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The new `StateDB` (`Keeper`) will adopt the use of the [`TransientStore`](https://docs.cosmos.network/master/core/store.html#transient-store) that discards the existing values of the store when the block is commited.
|
||||||
|
|
||||||
|
The fields that have been modified to use the `TransientStore` are:
|
||||||
|
|
||||||
|
- Block bloom filter (cleared at the end of every block)
|
||||||
|
- Tx index (updated on every transaction)
|
||||||
|
- Gas amount refunded (updated on every transaction)
|
||||||
|
- Suicided account (cleared at the end of every block)
|
||||||
|
- `AccessList` address and slot (cleared at the end of every block)
|
||||||
|
|
||||||
### State Objects
|
### State Objects
|
||||||
|
|
||||||
The `stateObject` type will be completely removed in favor of updating the store directly through
|
The `stateObject` type will be completely removed in favor of updating the store directly through
|
||||||
@ -126,95 +148,106 @@ the use of the auth `AccountKeeper` and the bank `Keeper`. For the storage `Stat
|
|||||||
evm module `Keeper` will store these values directly on the KVStore using the EVM module store key
|
evm module `Keeper` will store these values directly on the KVStore using the EVM module store key
|
||||||
and corresponding prefix keys.
|
and corresponding prefix keys.
|
||||||
|
|
||||||
For accounts marked as 'suicided', a new relationship will be added to the `Keeper` to map `Address
|
|
||||||
(bytes) -> suicided (bool)`.
|
|
||||||
|
|
||||||
```go
|
|
||||||
// HasSuicided implements the vm.StoreDB interface
|
|
||||||
func (k Keeper) HasSuicided(address common.Address) bool {
|
|
||||||
store := prefix.NewStore(k.ctx.KVStore(csdb.storeKey), KeyPrefixSuicide)
|
|
||||||
key := types.KeySuicide(address.Bytes())
|
|
||||||
return store.Has(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Suicide implements the vm.StoreDB interface
|
|
||||||
func (k Keeper) Suicide(address common.Address) bool {
|
|
||||||
store := prefix.NewStore(k.ctx.KVStore(csdb.storeKey), KeyPrefixSuicide)
|
|
||||||
key := types.KeySuicide(address.Bytes())
|
|
||||||
store.Set(key, []byte{0x1})
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### State Transition
|
### State Transition
|
||||||
|
|
||||||
The state transition logic will be refactored to use the `ApplyMessage` function from the `core/`
|
The state transition logic will be refactored to use the [`ApplyTransaction`](https://github.com/ethereum/go-ethereum/blob/v1.10.3/core/state_processor.go#L137-L150) function from the `core`
|
||||||
package of go-ethereum as the backbone. This method calls creates a go-ethereum `StateTransition`
|
package of go-ethereum as reference. This method calls creates a go-ethereum `StateTransition`
|
||||||
instance and, as it name implies, applies a Ethereum message to execute it and update the state.
|
instance and, as it name implies, applies a Ethereum message to execute it and update the state.
|
||||||
This `ApplyMessage` call will be wrapped in the `Keeper`'s `TransitionDb` function, which will
|
This `ApplyMessage` call will be wrapped in the `Keeper`'s `ApplyTransaction` function, which will
|
||||||
generate the required arguments for this call (EVM, chain config, and gas pool), thus performing the
|
generate the required arguments for this call (EVM, `core.Message`, chain config, and gas pool), thus performing the
|
||||||
same gas accounting as before.
|
same gas accounting as before.
|
||||||
|
|
||||||
This will lead to the switching from the existing Ethermint's evm `StateTransition` type to the
|
|
||||||
go-ethereum `vm.ApplyMessage` type, thus reducing code necessary perform a state transition.
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (k *Keeper) TransitionDb(ctx sdk.Context, msg core.Message) (*types.ExecutionResult, error) {
|
func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumTxResponse, error) {
|
||||||
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), types.MetricKeyTransitionDB)
|
// ...
|
||||||
|
cfg, found := k.GetChainConfig(infCtx)
|
||||||
initialGasMeter := ctx.GasMeter()
|
|
||||||
|
|
||||||
// NOTE: Since CRUD operations on the SDK store consume gasm we need to set up an infinite gas meter so that we only consume
|
|
||||||
// the gas used by the Ethereum message execution.
|
|
||||||
// Not setting the infinite gas meter here would mean that we are incurring in additional gas costs
|
|
||||||
k.ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
|
|
||||||
|
|
||||||
params := k.GetParams(ctx)
|
|
||||||
cfg, found := k.GetChainConfig(ctx)
|
|
||||||
if !found {
|
if !found {
|
||||||
// error
|
// return error
|
||||||
}
|
}
|
||||||
|
|
||||||
evm := k.NewEVM(msg, cfg.EthereumConfig(chainID))
|
ethCfg := cfg.EthereumConfig(chainID)
|
||||||
gasPool := &core.GasPool(ctx.BlockGasMeter().Limit()) // available gas left in the block for the tx execution
|
|
||||||
|
signer := MakeSigner(ethCfg, height)
|
||||||
|
|
||||||
|
msg, err := tx.AsMessage(signer)
|
||||||
|
if err != nil {
|
||||||
|
// return error
|
||||||
|
}
|
||||||
|
|
||||||
|
evm := k.NewEVM(msg, ethCfg)
|
||||||
|
|
||||||
|
k.IncreaseTxIndexTransient()
|
||||||
|
|
||||||
// create an ethereum StateTransition instance and run TransitionDb
|
// create an ethereum StateTransition instance and run TransitionDb
|
||||||
result, err := core.ApplyMessage(evm, msg, gasPool)
|
res, err := k.ApplyMessage(evm, msg, ethCfg)
|
||||||
// return precheck errors (nonce, signature, balance and gas)
|
|
||||||
// NOTE: these should be checked previously on the AnteHandler
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// log error
|
// return error
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The gas used on the state transition will
|
// ...
|
||||||
// be returned in the execution result so we need to deduct it from the transaction (?) GasMeter // TODO: double-check
|
|
||||||
initialGasMeter.ConsumeGas(resp.UsedGas, "evm state transition")
|
|
||||||
|
|
||||||
// set the gas meter to current_gas = initial_gas - used_gas
|
return res, nil
|
||||||
k.ctx = k.ctx.WithGasMeter(initialGasMeter)
|
|
||||||
|
|
||||||
// return the VM Execution error (see go-ethereum/core/vm/errors.go)
|
|
||||||
if result.Err != nil {
|
|
||||||
// log error
|
|
||||||
return result.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// return logs
|
|
||||||
executionRes := &ExecutionResult{
|
|
||||||
Response: &MsgEthereumTxResponse{
|
|
||||||
Ret: result.ret,
|
|
||||||
},
|
|
||||||
GasInfo: GasInfo{
|
|
||||||
GasConsumed: result.UsedGas,
|
|
||||||
GasLimit: gasPool,
|
|
||||||
}
|
|
||||||
|
|
||||||
return executionRes, nil
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The EVM is created then as follows:
|
`ApplyMessage` computes the new state by applying the given message against the existing state. If
|
||||||
|
the message fails, the VM execution error with the reason will be returned to the client and the
|
||||||
|
transaction won't be committed to the store.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (k *Keeper) ApplyMessage(evm *vm.EVM, msg core.Message, cfg *params.ChainConfig) (*types.MsgEthereumTxResponse, error) {
|
||||||
|
var (
|
||||||
|
ret []byte // return bytes from evm execution
|
||||||
|
vmErr error // vm errors do not effect consensus and are therefore not assigned to err
|
||||||
|
)
|
||||||
|
|
||||||
|
sender := vm.AccountRef(msg.From())
|
||||||
|
contractCreation := msg.To() == nil
|
||||||
|
|
||||||
|
// transaction gas meter (tracks limit and usage)
|
||||||
|
gasConsumed := k.ctx.GasMeter().GasConsumed()
|
||||||
|
leftoverGas := k.ctx.GasMeter().Limit() - k.ctx.GasMeter().GasConsumedToLimit()
|
||||||
|
|
||||||
|
// NOTE: Since CRUD operations on the SDK store consume gas we need to set up an infinite gas meter so that we only consume
|
||||||
|
// the gas used by the Ethereum message execution.
|
||||||
|
// Not setting the infinite gas meter here would mean that we are incurring in additional gas costs
|
||||||
|
k.WithContext(k.ctx.WithGasMeter(sdk.NewInfiniteGasMeter()))
|
||||||
|
|
||||||
|
// NOTE: gas limit is the GasLimit defined in the message minus the Intrinsic Gas that has already been
|
||||||
|
// consumed on the AnteHandler.
|
||||||
|
|
||||||
|
// ensure gas is consistent during CheckTx
|
||||||
|
if k.ctx.IsCheckTx() {
|
||||||
|
// check gas consumption correctness
|
||||||
|
}
|
||||||
|
|
||||||
|
if contractCreation {
|
||||||
|
ret, _, leftoverGas, vmErr = evm.Create(sender, msg.Data(), leftoverGas, msg.Value())
|
||||||
|
} else {
|
||||||
|
ret, leftoverGas, vmErr = evm.Call(sender, *msg.To(), msg.Data(), leftoverGas, msg.Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
// refund gas prior to handling the vm error in order to set the updated gas meter
|
||||||
|
if err := k.RefundGas(msg, leftoverGas); err != nil {
|
||||||
|
// return error
|
||||||
|
}
|
||||||
|
|
||||||
|
if vmErr != nil {
|
||||||
|
if errors.Is(vmErr, vm.ErrExecutionReverted) {
|
||||||
|
// return error with revert reason
|
||||||
|
}
|
||||||
|
|
||||||
|
// return execution error
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.MsgEthereumTxResponse{
|
||||||
|
Ret: ret,
|
||||||
|
Reverted: false,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The EVM is created as follows:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (k *Keeper) NewEVM(msg core.Message, config *params.ChainConfig) *vm.EVM {
|
func (k *Keeper) NewEVM(msg core.Message, config *params.ChainConfig) *vm.EVM {
|
||||||
@ -223,32 +256,17 @@ func (k *Keeper) NewEVM(msg core.Message, config *params.ChainConfig) *vm.EVM {
|
|||||||
Transfer: core.Transfer,
|
Transfer: core.Transfer,
|
||||||
GetHash: k.GetHashFn(),
|
GetHash: k.GetHashFn(),
|
||||||
Coinbase: common.Address{}, // there's no beneficiary since we're not mining
|
Coinbase: common.Address{}, // there's no beneficiary since we're not mining
|
||||||
BlockNumber: big.NewInt(k.ctx.BlockHeight()),
|
GasLimit: blockGasMeter.Limit(),
|
||||||
Time: big.NewInt(k.ctx.BlockHeader().Time.Unix()),
|
BlockNumber: blockHeight,
|
||||||
Difficulty: big.NewInt(0), // unused. Only required in PoW context
|
Time: blockTime,
|
||||||
GasLimit: gasLimit,
|
Difficulty: 0, // unused. Only required in PoW context
|
||||||
}
|
}
|
||||||
|
|
||||||
txCtx := core.NewEVMTxContext(msg)
|
txCtx := core.NewEVMTxContext(msg)
|
||||||
vmConfig := k.VMConfig(st.Debug)
|
vmConfig := k.VMConfig()
|
||||||
|
|
||||||
return vm.NewEVM(blockCtx, txCtx, k, config, vmConfig)
|
return vm.NewEVM(blockCtx, txCtx, k, config, vmConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k Keeper) VMConfig(debug bool) vm.Config{
|
|
||||||
params := k.GetParams(ctx)
|
|
||||||
|
|
||||||
eips := make([]int, len(params.ExtraEIPs))
|
|
||||||
for i, eip := range params.ExtraEIPs {
|
|
||||||
eips[i] = int(eip)
|
|
||||||
}
|
|
||||||
|
|
||||||
return vm.Config{
|
|
||||||
ExtraEips: eips,
|
|
||||||
Tracer: vm.NewJSONLogger(&vm.LogConfig{Debug: debug}, os.Stderr),
|
|
||||||
Debug: debug,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Consequences
|
## Consequences
|
||||||
@ -268,20 +286,18 @@ since no chain that uses this code is in a production ready-state (at the moment
|
|||||||
- Defines a single option for accessing the store through the `Keeper`, thus removing the
|
- Defines a single option for accessing the store through the `Keeper`, thus removing the
|
||||||
`CommitStateDB` type.
|
`CommitStateDB` type.
|
||||||
- State operations and tests are now all located in the `evm/keeper/` package
|
- State operations and tests are now all located in the `evm/keeper/` package
|
||||||
- Removes the concept of `stateObject` by commiting to the store directly
|
- Removes the concept of `stateObject` by committing to the store directly
|
||||||
- Delete operations on `EndBlock` for updating and commiting dirty state objects.
|
- Delete operations on `EndBlock` for updating and committing dirty state objects.
|
||||||
- Split the state transition functionality (`NewEVM` from `TransitionDb`) allows to further
|
- Split the state transition functionality to modularize components that can be beneficial for further customization (eg: using an alternative EVM)
|
||||||
modularize certain components that can be beneficial for customization (eg: using other EVMs other
|
|
||||||
than Geth's)
|
|
||||||
|
|
||||||
### Negative
|
### Negative
|
||||||
|
|
||||||
- Increases the dependency of external packages (eg: `go-ethereum`)
|
- Increases the dependency of external packages (eg: `go-ethereum`)
|
||||||
- Some state changes will have to be kept in store (eg: suicide state)
|
|
||||||
|
|
||||||
### Neutral
|
### Neutral
|
||||||
|
|
||||||
- Some of the fields from the `CommitStateDB` will have to be added to the `Keeper`
|
- Some of the fields from the `CommitStateDB` will have to be added to the `Keeper`
|
||||||
|
- Some state changes will have to be kept in store (eg: suicide state)
|
||||||
|
|
||||||
## Further Discussions
|
## Further Discussions
|
||||||
|
|
||||||
|
@ -114,6 +114,8 @@ func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumT
|
|||||||
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), types.MetricKeyTransitionDB)
|
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), types.MetricKeyTransitionDB)
|
||||||
|
|
||||||
gasMeter := k.ctx.GasMeter() // tx gas meter
|
gasMeter := k.ctx.GasMeter() // tx gas meter
|
||||||
|
|
||||||
|
// ignore gas consumption costs
|
||||||
infCtx := k.ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
|
infCtx := k.ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
|
||||||
|
|
||||||
cfg, found := k.GetChainConfig(infCtx)
|
cfg, found := k.GetChainConfig(infCtx)
|
||||||
@ -122,6 +124,7 @@ func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumT
|
|||||||
}
|
}
|
||||||
ethCfg := cfg.EthereumConfig(k.eip155ChainID)
|
ethCfg := cfg.EthereumConfig(k.eip155ChainID)
|
||||||
|
|
||||||
|
// get the latest signer according to the chain rules from the config
|
||||||
signer := ethtypes.MakeSigner(ethCfg, big.NewInt(k.ctx.BlockHeight()))
|
signer := ethtypes.MakeSigner(ethCfg, big.NewInt(k.ctx.BlockHeight()))
|
||||||
|
|
||||||
msg, err := tx.AsMessage(signer)
|
msg, err := tx.AsMessage(signer)
|
||||||
@ -133,6 +136,8 @@ func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumT
|
|||||||
|
|
||||||
k.IncreaseTxIndexTransient()
|
k.IncreaseTxIndexTransient()
|
||||||
|
|
||||||
|
// set the original gas meter to apply the message and perform the state transition
|
||||||
|
|
||||||
k.WithContext(k.ctx.WithGasMeter(gasMeter))
|
k.WithContext(k.ctx.WithGasMeter(gasMeter))
|
||||||
// create an ethereum StateTransition instance and run TransitionDb
|
// create an ethereum StateTransition instance and run TransitionDb
|
||||||
res, err := k.ApplyMessage(evm, msg, ethCfg)
|
res, err := k.ApplyMessage(evm, msg, ethCfg)
|
||||||
@ -140,6 +145,8 @@ func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumT
|
|||||||
return nil, stacktrace.Propagate(err, "failed to apply ethereum core message")
|
return nil, stacktrace.Propagate(err, "failed to apply ethereum core message")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set the ethereum-formatted hash to the tx result as the tendermint hash is different
|
||||||
|
// NOTE: see https://github.com/tendermint/tendermint/issues/6539 for reference.
|
||||||
txHash := tx.Hash()
|
txHash := tx.Hash()
|
||||||
res.Hash = txHash.Hex()
|
res.Hash = txHash.Hex()
|
||||||
res.Logs = types.NewLogsFromEth(k.GetTxLogs(txHash))
|
res.Logs = types.NewLogsFromEth(k.GetTxLogs(txHash))
|
||||||
@ -163,6 +170,30 @@ func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumT
|
|||||||
// TODO: (@fedekunze) currently we consume the entire gas limit in the ante handler, so if a transaction fails
|
// TODO: (@fedekunze) currently we consume the entire gas limit in the ante handler, so if a transaction fails
|
||||||
// the amount spent will be grater than the gas spent in an Ethereum tx (i.e here the leftover gas won't be refunded).
|
// the amount spent will be grater than the gas spent in an Ethereum tx (i.e here the leftover gas won't be refunded).
|
||||||
|
|
||||||
|
// ApplyMessage computes the new state by applying the given message against the existing state.
|
||||||
|
// If the message fails, the VM execution error with the reason will be returned to the client
|
||||||
|
// and the transaction won't be committed to the store.
|
||||||
|
//
|
||||||
|
// Reverted state
|
||||||
|
//
|
||||||
|
// The transaction is never "reverted" since there is no snapshot + rollback performed on the StateDB.
|
||||||
|
// Only successful transactions are written to the store during DeliverTx mode.
|
||||||
|
//
|
||||||
|
// Prechecks and Preprocessing
|
||||||
|
//
|
||||||
|
// All relevant state transition prechecks for the MsgEthereumTx are performed on the AnteHandler,
|
||||||
|
// prior to running the transaction against the state. The prechecks run are the following:
|
||||||
|
//
|
||||||
|
// 1. the nonce of the message caller is correct
|
||||||
|
// 2. caller has enough balance to cover transaction fee(gaslimit * gasprice)
|
||||||
|
// 3. the amount of gas required is available in the block
|
||||||
|
// 4. the purchased gas is enough to cover intrinsic usage
|
||||||
|
// 5. there is no overflow when calculating intrinsic gas
|
||||||
|
// 6. caller has enough balance to cover asset transfer for **topmost** call
|
||||||
|
//
|
||||||
|
// The preprocessing steps performed by the AnteHandler are:
|
||||||
|
//
|
||||||
|
// 1. set up the initial access list (iff fork > Berlin)
|
||||||
func (k *Keeper) ApplyMessage(evm *vm.EVM, msg core.Message, cfg *params.ChainConfig) (*types.MsgEthereumTxResponse, error) {
|
func (k *Keeper) ApplyMessage(evm *vm.EVM, msg core.Message, cfg *params.ChainConfig) (*types.MsgEthereumTxResponse, error) {
|
||||||
var (
|
var (
|
||||||
ret []byte // return bytes from evm execution
|
ret []byte // return bytes from evm execution
|
||||||
@ -176,7 +207,7 @@ func (k *Keeper) ApplyMessage(evm *vm.EVM, msg core.Message, cfg *params.ChainCo
|
|||||||
gasConsumed := k.ctx.GasMeter().GasConsumed()
|
gasConsumed := k.ctx.GasMeter().GasConsumed()
|
||||||
leftoverGas := k.ctx.GasMeter().Limit() - k.ctx.GasMeter().GasConsumedToLimit()
|
leftoverGas := k.ctx.GasMeter().Limit() - k.ctx.GasMeter().GasConsumedToLimit()
|
||||||
|
|
||||||
// NOTE: Since CRUD operations on the SDK store consume gasm we need to set up an infinite gas meter so that we only consume
|
// NOTE: Since CRUD operations on the SDK store consume gas we need to set up an infinite gas meter so that we only consume
|
||||||
// the gas used by the Ethereum message execution.
|
// the gas used by the Ethereum message execution.
|
||||||
// Not setting the infinite gas meter here would mean that we are incurring in additional gas costs
|
// Not setting the infinite gas meter here would mean that we are incurring in additional gas costs
|
||||||
k.WithContext(k.ctx.WithGasMeter(sdk.NewInfiniteGasMeter()))
|
k.WithContext(k.ctx.WithGasMeter(sdk.NewInfiniteGasMeter()))
|
||||||
@ -204,7 +235,7 @@ func (k *Keeper) ApplyMessage(evm *vm.EVM, msg core.Message, cfg *params.ChainCo
|
|||||||
|
|
||||||
if vmErr != nil {
|
if vmErr != nil {
|
||||||
if errors.Is(vmErr, vm.ErrExecutionReverted) {
|
if errors.Is(vmErr, vm.ErrExecutionReverted) {
|
||||||
// unpack the return data bytes from the err if the execution has been reverted on the VM
|
// unpack the return data bytes from the err if the execution has been "reverted" on the VM
|
||||||
return nil, stacktrace.Propagate(types.NewExecErrorWithReson(ret), "transaction reverted")
|
return nil, stacktrace.Propagate(types.NewExecErrorWithReson(ret), "transaction reverted")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user