Skip to content
This repository has been archived by the owner on Oct 18, 2023. It is now read-only.

Commit

Permalink
evm t8n to use ExecuteBlockEphemerally api (ledgerwatch#4642)
Browse files Browse the repository at this point in the history
* evm t8n tool to use ExecuteBlockEphemerally api (ledgerwatch#4512)

* fix to set V, R, S in legacy transaction

* fix to dump post-execution alloc for evm t8n

* close tx in evm t8n

* populate current difficulty and gas used in output result

- update the ExecutionResult to include corresponding info (like
  Difficulty/GasUsed)

* initial attempt at migrating 'evm t8n' to use ExecuteBlockEphemerally

* using ExecutionResult in ExecuteBlockEphemerally

* bypass validations and integrate with EphemeralExecResult

* fixing output of 'evm t8n'

- remaining bits are "stateRoot" in results.txt and "balance" field for one account in
  alloc.txt (for testdata=1)

* get ExecuteBlockEphemerally to accept getTracer lambda

* fix build failure

* test cases for evm t8n

* more test cases for evm t8n

* fix stateRoot computation in evm t8n

* remove reward argument, as EBE itself takes care of it

* final cleanups for migration to using ExecuteBlockEphemerally

* change EBEforBSC to match EBE

* fix linter issues

* manually revert an unwanted diff

* avoid calculating ReceiptHash twice

* linter check

* minor correction

* remove unnecessary logic in EBEforBsc

* fix integration tests

* fix build
  • Loading branch information
sudeepdino008 committed Jul 7, 2022
1 parent 7b80744 commit e13a318
Show file tree
Hide file tree
Showing 60 changed files with 2,390 additions and 504 deletions.
5 changes: 3 additions & 2 deletions accounts/abi/bind/backends/simulated.go
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,8 @@ func (b *SimulatedBackend) callContract(_ context.Context, call ethereum.CallMsg
msg := callMsg{call}

txContext := core.NewEVMTxContext(msg)
evmContext := core.NewEVMBlockContext(block.Header(), b.getHeader, b.m.Engine, nil, b.contractHasTEVM)
header := block.Header()
evmContext := core.NewEVMBlockContext(header, core.GetHashFn(header, b.getHeader), b.m.Engine, nil, b.contractHasTEVM)
// Create a new environment which holds all relevant information
// about the transaction and calling mechanisms.
vmEnv := vm.NewEVM(evmContext, txContext, statedb, b.m.ChainConfig, vm.Config{})
Expand Down Expand Up @@ -696,7 +697,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx types.Transac
b.pendingState.Prepare(tx.Hash(), common.Hash{}, len(b.pendingBlock.Transactions()))
//fmt.Printf("==== Start producing block %d, header: %d\n", b.pendingBlock.NumberU64(), b.pendingHeader.Number.Uint64())
if _, _, err := core.ApplyTransaction(
b.m.ChainConfig, b.getHeader, b.m.Engine,
b.m.ChainConfig, core.GetHashFn(b.pendingHeader, b.getHeader), b.m.Engine,
&b.pendingHeader.Coinbase, b.gasPool,
b.pendingState, state.NewNoopWriter(),
b.pendingHeader, tx,
Expand Down
288 changes: 44 additions & 244 deletions cmd/evm/internal/t8ntool/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,275 +17,63 @@
package t8ntool

import (
"context"
"encoding/binary"
"fmt"
"math/big"

"github.com/holiman/uint256"
"github.com/ledgerwatch/erigon-lib/kv"
"github.com/ledgerwatch/erigon-lib/kv/memdb"
"github.com/ledgerwatch/log/v3"
"golang.org/x/crypto/sha3"

"github.com/ledgerwatch/erigon/common"
"github.com/ledgerwatch/erigon/common/math"
"github.com/ledgerwatch/erigon/consensus/misc"
"github.com/ledgerwatch/erigon/consensus/ethash"
"github.com/ledgerwatch/erigon/core"
"github.com/ledgerwatch/erigon/core/state"
"github.com/ledgerwatch/erigon/core/systemcontracts"
"github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/core/vm"
"github.com/ledgerwatch/erigon/crypto"
"github.com/ledgerwatch/erigon/params"
"github.com/ledgerwatch/erigon/rlp"
"github.com/ledgerwatch/erigon/turbo/trie"
)

type Prestate struct {
Env stEnv `json:"env"`
Pre core.GenesisAlloc `json:"pre"`
}

// ExecutionResult contains the execution status after running a state test, any
// error that might have occurred and a dump of the final state if requested.
type ExecutionResult struct {
StateRoot common.Hash `json:"stateRoot"`
TxRoot common.Hash `json:"txRoot"`
ReceiptRoot common.Hash `json:"receiptRoot"`
LogsHash common.Hash `json:"logsHash"`
Bloom types.Bloom `json:"logsBloom" gencodec:"required"`
Receipts types.Receipts `json:"receipts"`
Rejected []*rejectedTx `json:"rejected,omitempty"`
}

type ommer struct {
Delta uint64 `json:"delta"`
Address common.Address `json:"address"`
}

//go:generate gencodec -type stEnv -field-override stEnvMarshaling -out gen_stenv.go
type stEnv struct {
Coinbase common.Address `json:"currentCoinbase" gencodec:"required"`
Difficulty *big.Int `json:"currentDifficulty" gencodec:"required"`
GasLimit uint64 `json:"currentGasLimit" gencodec:"required"`
Number uint64 `json:"currentNumber" gencodec:"required"`
Timestamp uint64 `json:"currentTimestamp" gencodec:"required"`
BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"`
Ommers []ommer `json:"ommers,omitempty"`
BaseFee *big.Int `json:"currentBaseFee,omitempty"`
Random *common.Hash `json:"currentRandom,omitempty"`
}

type rejectedTx struct {
Index int `json:"index"`
Err string `json:"error"`
Coinbase common.Address `json:"currentCoinbase" gencodec:"required"`
Difficulty *big.Int `json:"currentDifficulty"`
Random *big.Int `json:"currentRandom"`
ParentDifficulty *big.Int `json:"parentDifficulty"`
GasLimit uint64 `json:"currentGasLimit" gencodec:"required"`
Number uint64 `json:"currentNumber" gencodec:"required"`
Timestamp uint64 `json:"currentTimestamp" gencodec:"required"`
ParentTimestamp uint64 `json:"parentTimestamp,omitempty"`
BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"`
Ommers []ommer `json:"ommers,omitempty"`
BaseFee *big.Int `json:"currentBaseFee,omitempty"`
ParentUncleHash common.Hash `json:"parentUncleHash"`
}

type stEnvMarshaling struct {
Coinbase common.UnprefixedAddress
Difficulty *math.HexOrDecimal256
GasLimit math.HexOrDecimal64
Number math.HexOrDecimal64
Timestamp math.HexOrDecimal64
BaseFee *math.HexOrDecimal256
}

// Apply applies a set of transactions to a pre-state
func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
txs types.Transactions, miningReward int64,
getTracerFn func(txIndex int, txHash common.Hash) (tracer vm.Tracer, err error)) (kv.RwDB, *ExecutionResult, error) {

// Capture errors for BLOCKHASH operation, if we haven't been supplied the
// required blockhashes
var hashError error
getHash := func(num uint64) common.Hash {
if pre.Env.BlockHashes == nil {
hashError = fmt.Errorf("getHash(%d) invoked, no blockhashes provided", num)
return common.Hash{}
}
h, ok := pre.Env.BlockHashes[math.HexOrDecimal64(num)]
if !ok {
hashError = fmt.Errorf("getHash(%d) invoked, blockhash for that block not provided", num)
}
return h
}
db := memdb.New()

tx, err := db.BeginRw(context.Background())
if err != nil {
return nil, nil, err
}
defer tx.Rollback()

var (
rules0 = chainConfig.Rules(0)
rules1 = chainConfig.Rules(1)
rules = chainConfig.Rules(pre.Env.Number)
ibs = MakePreState(rules0, tx, pre.Pre)
signer = types.MakeSigner(chainConfig, pre.Env.Number)
gaspool = new(core.GasPool)
blockHash = common.Hash{0x13, 0x37}
rejectedTxs []*rejectedTx
includedTxs types.Transactions
gasUsed = uint64(0)
receipts = make(types.Receipts, 0)
txIndex = 0
)
gaspool.AddGas(pre.Env.GasLimit)

difficulty := new(big.Int)
if pre.Env.Random == nil {
difficulty = pre.Env.Difficulty
} else {
// We are on POS hence difficulty opcode is now supplant with RANDOM
random := pre.Env.Random.Bytes()
difficulty.SetBytes(random)
}
vmContext := vm.BlockContext{
CanTransfer: core.CanTransfer,
Transfer: core.Transfer,
Coinbase: pre.Env.Coinbase,
BlockNumber: pre.Env.Number,
ContractHasTEVM: func(common.Hash) (bool, error) { return false, nil },
Time: pre.Env.Timestamp,
Difficulty: difficulty,
GasLimit: pre.Env.GasLimit,
GetHash: getHash,
}
// If currentBaseFee is defined, add it to the vmContext.
if pre.Env.BaseFee != nil {
vmContext.BaseFee = new(uint256.Int)
overflow := vmContext.BaseFee.SetFromBig(pre.Env.BaseFee)
if overflow {
return nil, nil, fmt.Errorf("pre.Env.BaseFee higher than 2^256-1")
}
}
// If DAO is supported/enabled, we need to handle it here. In geth 'proper', it's
// done in StateProcessor.Process(block, ...), right before transactions are applied.
if chainConfig.DAOForkSupport &&
chainConfig.DAOForkBlock != nil &&
chainConfig.DAOForkBlock.Cmp(new(big.Int).SetUint64(pre.Env.Number)) == 0 {
misc.ApplyDAOHardFork(ibs)
}
systemcontracts.UpgradeBuildInSystemContract(chainConfig, new(big.Int).SetUint64(pre.Env.Number), ibs)

for i, txn := range txs {
msg, err := txn.AsMessage(*signer, pre.Env.BaseFee, rules)
if err != nil {
log.Warn("rejected txn", "index", i, "hash", txn.Hash(), "err", err)
rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()})
continue
}
tracer, err := getTracerFn(txIndex, txn.Hash())
if err != nil {
return nil, nil, err
}
vmConfig.Tracer = tracer
vmConfig.Debug = (tracer != nil)
ibs.Prepare(txn.Hash(), blockHash, txIndex)
txContext := core.NewEVMTxContext(msg)
snapshot := ibs.Snapshot()
evm := vm.NewEVM(vmContext, txContext, ibs, chainConfig, vmConfig)

// (ret []byte, usedGas uint64, failed bool, err error)
msgResult, err := core.ApplyMessage(evm, msg, gaspool, true /* refunds */, false /* gasBailout */)
if err != nil {
ibs.RevertToSnapshot(snapshot)
log.Info("rejected txn", "index", i, "hash", txn.Hash(), "from", msg.From(), "err", err)
rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()})
continue
}
includedTxs = append(includedTxs, txn)
if hashError != nil {
return nil, nil, NewError(ErrorMissingBlockhash, hashError)
}
gasUsed += msgResult.UsedGas

// Receipt:
{
// Create a new receipt for the transaction, storing the intermediate root and
// gas used by the txn.
receipt := &types.Receipt{Type: txn.Type(), CumulativeGasUsed: gasUsed}
if msgResult.Failed() {
receipt.Status = types.ReceiptStatusFailed
} else {
receipt.Status = types.ReceiptStatusSuccessful
}
receipt.TxHash = txn.Hash()
receipt.GasUsed = msgResult.UsedGas

// If the transaction created a contract, store the creation address in the receipt.
if msg.To() == nil {
receipt.ContractAddress = crypto.CreateAddress(evm.TxContext().Origin, txn.GetNonce())
}

// Set the receipt logs and create a bloom for filtering
receipt.Logs = ibs.GetLogs(txn.Hash())
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
// These three are non-consensus fields:
//receipt.BlockHash
//receipt.BlockNumber
receipt.TransactionIndex = uint(txIndex)
receipts = append(receipts, receipt)
}

txIndex++
}
// Add mining reward?
if miningReward > 0 {
// Add mining reward. The mining reward may be `0`, which only makes a difference in the cases
// where
// - the coinbase suicided, or
// - there are only 'bad' transactions, which aren't executed. In those cases,
// the coinbase gets no txfee, so isn't created, and thus needs to be touched
var (
blockReward = uint256.NewInt(uint64(miningReward))
minerReward = uint256.NewInt(0).Set(blockReward)
perOmmer = uint256.NewInt(0).Div(blockReward, uint256.NewInt(32))
)
for _, ommer := range pre.Env.Ommers {
// Add 1/32th for each ommer included
minerReward.Add(minerReward, perOmmer)
// Add (8-delta)/8
reward := uint256.NewInt(8)
reward.Sub(reward, uint256.NewInt(ommer.Delta))
reward.Mul(reward, blockReward)
reward.Div(reward, uint256.NewInt(8))
ibs.AddBalance(ommer.Address, reward)
}
ibs.AddBalance(pre.Env.Coinbase, minerReward)
}

// Commit block
var root common.Hash
if err = ibs.FinalizeTx(rules1, state.NewPlainStateWriter(tx, tx, 1)); err != nil {
return nil, nil, err
}
root, err = trie.CalcRoot("", tx)
if err != nil {
return nil, nil, err
}
if err = tx.Commit(); err != nil {
return nil, nil, err
}

execRs := &ExecutionResult{
StateRoot: root,
TxRoot: types.DeriveSha(includedTxs),
ReceiptRoot: types.DeriveSha(receipts),
Bloom: types.CreateBloom(receipts),
LogsHash: rlpHash(ibs.Logs()),
Receipts: receipts,
Rejected: rejectedTxs,
}
return db, execRs, nil
Coinbase common.UnprefixedAddress
Difficulty *math.HexOrDecimal256
Random *math.HexOrDecimal256
ParentDifficulty *math.HexOrDecimal256
GasLimit math.HexOrDecimal64
Number math.HexOrDecimal64
Timestamp math.HexOrDecimal64
ParentTimestamp math.HexOrDecimal64
BaseFee *math.HexOrDecimal256
}

func MakePreState(chainRules *params.Rules, tx kv.RwTx, accounts core.GenesisAlloc) *state.IntraBlockState {
func MakePreState(chainRules *params.Rules, tx kv.RwTx, accounts core.GenesisAlloc) (*state.PlainStateReader, *state.PlainStateWriter) {
var blockNr uint64 = 0
r, _ := state.NewPlainStateReader(tx), state.NewPlainStateWriter(tx, tx, blockNr)
statedb := state.New(r)
stateReader, stateWriter := state.NewPlainStateReader(tx), state.NewPlainStateWriter(tx, tx, blockNr)
statedb := state.New(stateReader) //ibs
for addr, a := range accounts {
statedb.SetCode(addr, a.Code)
statedb.SetNonce(addr, a.Nonce)
Expand All @@ -299,7 +87,6 @@ func MakePreState(chainRules *params.Rules, tx kv.RwTx, accounts core.GenesisAll

if len(a.Code) > 0 || len(a.Storage) > 0 {
statedb.SetIncarnation(addr, state.FirstContractIncarnation)

var b [8]byte
binary.BigEndian.PutUint64(b[:], state.FirstContractIncarnation)
tx.Put(kv.IncarnationMap, addr[:], b[:])
Expand All @@ -312,12 +99,25 @@ func MakePreState(chainRules *params.Rules, tx kv.RwTx, accounts core.GenesisAll
if err := statedb.CommitBlock(chainRules, state.NewPlainStateWriter(tx, tx, blockNr+1)); err != nil {
panic(err)
}
return statedb
return stateReader, stateWriter
}

func rlpHash(x interface{}) (h common.Hash) {
hw := sha3.NewLegacyKeccak256()
rlp.Encode(hw, x) //nolint:errcheck
hw.Sum(h[:0])
return h
// calcDifficulty is based on ethash.CalcDifficulty. This method is used in case
// the caller does not provide an explicit difficulty, but instead provides only
// parent timestamp + difficulty.
// Note: this method only works for ethash engine.
func calcDifficulty(config *params.ChainConfig, number, currentTime, parentTime uint64,
parentDifficulty *big.Int, parentUncleHash common.Hash) *big.Int {
uncleHash := parentUncleHash
if uncleHash == (common.Hash{}) {
uncleHash = types.EmptyUncleHash
}
parent := &types.Header{
ParentHash: common.Hash{},
UncleHash: uncleHash,
Difficulty: parentDifficulty,
Number: new(big.Int).SetUint64(number - 1),
Time: parentTime,
}
return ethash.CalcDifficulty(config, currentTime, parent.Time, parent.Difficulty, number-1, parent.UncleHash)
}
7 changes: 1 addition & 6 deletions cmd/evm/internal/t8ntool/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,6 @@ var (
Usage: "`stdin` or file name of where to find the transactions to apply.",
Value: "txs.json",
}
RewardFlag = cli.Int64Flag{
Name: "state.reward",
Usage: "Mining reward. Set to -1 to disable",
Value: 0,
}
ChainIDFlag = cli.Int64Flag{
Name: "state.chainid",
Usage: "ChainID to use",
Expand All @@ -103,7 +98,7 @@ var (
"\n\tSyntax <forkname>(+ExtraEip)",
strings.Join(tests.AvailableForks(), "\n\t "),
strings.Join(vm.ActivateableEips(), ", ")),
Value: "Istanbul",
Value: "ArrowGlacier",
}
VerbosityFlag = cli.IntFlag{
Name: "verbosity",
Expand Down
Loading

0 comments on commit e13a318

Please sign in to comment.