implement TraceMany method (#24)

* implement `TraceMany`

* fix nil pointer: check ApplyMessage's return error

* fix nil ptr: fill ChainID

* fix txhash: use  common.BigToHash to generate hash
This commit is contained in:
Zhang Guyu 2022-06-15 17:31:34 +08:00 committed by GitHub
parent b48059b86c
commit 0d828c1c74
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -1,57 +1,41 @@
package eth package eth
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"hash" "math"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
txtrace "github.com/DeBankDeFi/etherlib/pkg/txtracev1" txtrace "github.com/DeBankDeFi/etherlib/pkg/txtracev1"
// "github.com/DeBankDeFi/eth/txtrace" // "github.com/DeBankDeFi/eth/txtrace"
"golang.org/x/crypto/sha3"
) )
type helpHash struct {
hashed hash.Hash
}
func newHash() *helpHash {
return &helpHash{hashed: sha3.NewLegacyKeccak256()}
}
func (h *helpHash) Reset() {
h.hashed.Reset()
}
func (h *helpHash) Update(key, val []byte) {
h.hashed.Write(key)
h.hashed.Write(val)
}
func (h *helpHash) Hash() common.Hash {
return common.BytesToHash(h.hashed.Sum(nil))
}
type PreExecTx struct { type PreExecTx struct {
ChainId *big.Int ChainId *big.Int `json:"chainId,omitempty"`
From, To, Data, Value, Gas, GasPrice, Nonce string From *common.Address `json:"from"`
To *common.Address `json:"to"`
Gas *hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"`
MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"`
MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"`
Value *hexutil.Big `json:"value"`
Nonce *hexutil.Uint64 `json:"nonce"`
Data *hexutil.Bytes `json:"data"`
Input *hexutil.Bytes `json:"input"`
} }
type preData struct { type PreExecResult struct {
block *types.Block Trace *[]txtrace.ActionTrace `json:"trace"`
tx *types.Transaction Logs []*types.Log `json:"logs"`
msg types.Message
stateDb *state.StateDB
header *types.Header
} }
// PreExecAPI provides pre exec info for rpc // PreExecAPI provides pre exec info for rpc
@ -63,109 +47,168 @@ func NewPreExecAPI(e *Ethereum) *PreExecAPI {
return &PreExecAPI{e: e} return &PreExecAPI{e: e}
} }
func (api *PreExecAPI) getBlockAndMsg(origin *PreExecTx, number *big.Int) (*types.Block, types.Message) {
fromAddr := common.HexToAddress(origin.From)
toAddr := common.HexToAddress(origin.To)
tx := types.NewTx(&types.LegacyTx{
Nonce: hexutil.MustDecodeUint64(origin.Nonce),
To: &toAddr,
Value: hexutil.MustDecodeBig(origin.Value),
Gas: hexutil.MustDecodeUint64(origin.Gas),
GasPrice: hexutil.MustDecodeBig(origin.GasPrice),
Data: hexutil.MustDecode(origin.Data),
})
number.Add(number, big.NewInt(1))
block := types.NewBlock(
&types.Header{Number: number},
[]*types.Transaction{tx}, nil, nil, newHash())
msg := types.NewMessage(
fromAddr,
&toAddr,
hexutil.MustDecodeUint64(origin.Nonce),
hexutil.MustDecodeBig(origin.Value),
hexutil.MustDecodeUint64(origin.Gas),
hexutil.MustDecodeBig(origin.GasPrice),
tx.GasFeeCap(),
tx.GasTipCap(),
hexutil.MustDecode(origin.Data),
nil, false, true,
)
return block, msg
}
func (api *PreExecAPI) prepareData(ctx context.Context, origin *PreExecTx) (*preData, error) {
var (
d preData
err error
)
bc := api.e.blockchain
d.header, err = api.e.APIBackend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
if err != nil {
return nil, err
}
latestNumber := d.header.Number
parent := api.e.blockchain.GetBlockByNumber(latestNumber.Uint64())
d.stateDb, err = state.New(parent.Header().Root, bc.StateCache(), bc.Snapshots())
if err != nil {
return nil, err
}
d.block, d.msg = api.getBlockAndMsg(origin, latestNumber)
d.tx = d.block.Transactions()[0]
return &d, nil
}
func (api *PreExecAPI) GetLogs(ctx context.Context, origin *PreExecTx) (*types.Receipt, error) { func (api *PreExecAPI) GetLogs(ctx context.Context, origin *PreExecTx) (*types.Receipt, error) {
var ( state, header, err := api.e.APIBackend.StateAndHeaderByNumberOrHash(ctx, rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber))
bc = api.e.blockchain if state == nil || err != nil {
) return nil, err
d, err := api.prepareData(ctx, origin) }
txArgs := ethapi.TransactionArgs{
ChainID: (*hexutil.Big)(big.NewInt(1)),
From: origin.From,
To: origin.To,
Gas: origin.Gas,
GasPrice: origin.GasPrice,
MaxFeePerGas: origin.MaxFeePerGas,
MaxPriorityFeePerGas: origin.MaxPriorityFeePerGas,
Value: origin.Value,
Nonce: origin.Nonce,
Data: origin.Data,
Input: origin.Input,
}
// Get a new instance of the EVM.
msg, err := txArgs.ToMessage(0, header.BaseFee)
if err != nil { if err != nil {
return nil, err return nil, err
} }
gas := d.tx.Gas() evm, vmError, err := api.e.APIBackend.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true})
gp := new(core.GasPool).AddGas(gas)
d.stateDb.Prepare(d.tx.Hash(), 0)
receipt, err := core.ApplyTransactionForPreExec(
bc.Config(), bc, nil, gp, d.stateDb, d.header, d.tx, d.msg, &gas, *bc.GetVMConfig())
if err != nil { if err != nil {
return nil, err return nil, err
} }
return receipt, receipt.Err // Execute the message.
gp := new(core.GasPool).AddGas(math.MaxUint64)
result, err := core.ApplyMessage(evm, msg, gp)
if err := vmError(); err != nil {
return nil, err
}
if err != nil {
return nil, fmt.Errorf("err: %w (supplied gas %d)", err, msg.Gas())
}
receipt := types.NewReceipt(bytes.NewBufferString("").Bytes(), result.Failed(), result.UsedGas)
receipt.GasUsed = result.UsedGas
receipt.Logs = state.Logs()
receipt.BlockHash = header.Hash()
receipt.BlockNumber = header.Number
return receipt, nil
} }
// TraceTransaction tracing pre-exec transaction object. // TraceTransaction tracing pre-exec transaction object.
func (api *PreExecAPI) TraceTransaction(ctx context.Context, origin *PreExecTx) (interface{}, error) { func (api *PreExecAPI) TraceTransaction(ctx context.Context, origin *PreExecTx) (interface{}, error) {
var ( state, header, err := api.e.APIBackend.StateAndHeaderByNumberOrHash(ctx, rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber))
bc = api.e.blockchain if state == nil || err != nil {
tracer *txtrace.OeTracer return nil, err
err error }
) txArgs := ethapi.TransactionArgs{
d, err := api.prepareData(ctx, origin) ChainID: (*hexutil.Big)(big.NewInt(1)),
From: origin.From,
To: origin.To,
Gas: origin.Gas,
GasPrice: origin.GasPrice,
MaxFeePerGas: origin.MaxFeePerGas,
MaxPriorityFeePerGas: origin.MaxPriorityFeePerGas,
Value: origin.Value,
Nonce: origin.Nonce,
Data: origin.Data,
Input: origin.Input,
}
// Get a new instance of the EVM.
msg, err := txArgs.ToMessage(0, header.BaseFee)
if err != nil { if err != nil {
return nil, err return nil, err
} }
txContext := core.NewEVMTxContext(d.msg) tracer := txtrace.NewOeTracer(nil)
txIndex := 0 evm, vmError, err := api.e.APIBackend.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true, Debug: true, Tracer: tracer})
tracer = txtrace.NewOeTracer(nil)
// Run the transaction with tracing enabled.
vmenv := vm.NewEVM(core.NewEVMBlockContext(d.header, bc, nil), txContext, d.stateDb, bc.Config(), vm.Config{Debug: true, Tracer: tracer})
vmenv.Context.BaseFee = big.NewInt(0)
// Call Prepare to clear out the statedb access list
d.stateDb.Prepare(d.tx.Hash(), txIndex)
tracer.SetMessage(d.block.Number(), d.block.Hash(), d.tx.Hash(), uint(txIndex), d.msg.From(), d.msg.To(), *d.msg.Value())
_, err = core.ApplyMessage(vmenv, d.msg, new(core.GasPool).AddGas(d.msg.Gas()))
if err != nil { if err != nil {
return nil, fmt.Errorf("tracing failed: %v", err) return nil, err
} }
// Execute the message.
gp := new(core.GasPool).AddGas(math.MaxUint64)
result, err := core.ApplyMessage(evm, msg, gp)
if err := vmError(); err != nil {
return nil, err
}
if err != nil {
return nil, fmt.Errorf("err: %w (supplied gas %d)", err, msg.Gas())
}
tracer.SetGasUsed(result.UsedGas)
tracer.Finalize() tracer.Finalize()
return tracer.GetResult(), nil return tracer.GetResult(), nil
} }
func (api *PreExecAPI) TraceMany(ctx context.Context, origins []PreExecTx) (*PreExecResult, error) {
state, header, err := api.e.APIBackend.StateAndHeaderByNumberOrHash(ctx, rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber))
if state == nil || err != nil {
return nil, err
}
for i := 0; i < len(origins)-1; i++ {
origin := origins[i]
txArgs := ethapi.TransactionArgs{
ChainID: (*hexutil.Big)(big.NewInt(1)),
From: origin.From,
To: origin.To,
Gas: origin.Gas,
GasPrice: origin.GasPrice,
MaxFeePerGas: origin.MaxFeePerGas,
MaxPriorityFeePerGas: origin.MaxPriorityFeePerGas,
Value: origin.Value,
Nonce: origin.Nonce,
Data: origin.Data,
Input: origin.Input,
}
// Get a new instance of the EVM.
msg, err := txArgs.ToMessage(0, header.BaseFee)
if err != nil {
return nil, err
}
evm, vmError, err := api.e.APIBackend.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true})
if err != nil {
return nil, err
}
// Execute the message.
gp := new(core.GasPool).AddGas(math.MaxUint64)
_, err = core.ApplyMessage(evm, msg, gp)
if err := vmError(); err != nil {
return nil, err
}
if err != nil {
return nil, fmt.Errorf("err: %w (supplied gas %d)", err, msg.Gas())
}
}
origin := origins[len(origins)-1]
txArgs := ethapi.TransactionArgs{
ChainID: (*hexutil.Big)(big.NewInt(1)),
From: origin.From,
To: origin.To,
Gas: origin.Gas,
GasPrice: origin.GasPrice,
MaxFeePerGas: origin.MaxFeePerGas,
MaxPriorityFeePerGas: origin.MaxPriorityFeePerGas,
Value: origin.Value,
Nonce: origin.Nonce,
Data: origin.Data,
Input: origin.Input,
}
// Get a new instance of the EVM.
msg, err := txArgs.ToMessage(0, header.BaseFee)
if err != nil {
return nil, err
}
tracer := txtrace.NewOeTracer(nil)
evm, vmError, err := api.e.APIBackend.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true, Debug: true, Tracer: tracer})
if err != nil {
return nil, err
}
// Execute the message.
gp := new(core.GasPool).AddGas(math.MaxUint64)
txHash := common.BigToHash(big.NewInt(1))
state.Prepare(txHash, len(origins)-1)
result, err := core.ApplyMessage(evm, msg, gp)
if err := vmError(); err != nil {
return nil, err
}
if err != nil {
return nil, fmt.Errorf("err: %w (supplied gas %d)", err, msg.Gas())
}
tracer.SetGasUsed(result.UsedGas)
tracer.Finalize()
return &PreExecResult{Trace: tracer.GetResult(), Logs: state.GetLogs(txHash, header.Hash())}, nil
}