eth/tracers: live chain tracing with hooks #29189 (#1352)

Here we add a Go API for running tracing plugins within the main block import process.

As an advanced user of geth, you can now create a Go file in eth/tracers/live/, and within
that file register your custom tracer implementation. Then recompile geth and select your tracer
on the command line. Hooks defined in the tracer will run whenever a block is processed.

The hook system is defined in package core/tracing. It uses a struct with callbacks, instead of
requiring an interface, for several reasons:

- We plan to keep this API stable long-term. The core/tracing hook API does not depend on
  on deep geth internals.
- There are a lot of hooks, and tracers will only need some of them. Using a struct allows you
   to implement only the hooks you want to actually use.

All existing tracers in eth/tracers/native have been rewritten to use the new hook system.

This change breaks compatibility with the vm.EVMLogger interface that we used to have.
If you are a user of vm.EVMLogger, please migrate to core/tracing, and sorry for breaking
your stuff. But we just couldn't have both the old and new tracing APIs coexist in the EVM.

---------

Co-authored-by: Sina M <1591639+s1na@users.noreply.github.com>
Co-authored-by: Matthieu Vachon <matthieu.o.vachon@gmail.com>
Co-authored-by: Delweng <delweng@gmail.com>
Co-authored-by: Martin HS <martin@swende.se>
This commit is contained in:
Daniel Liu 2025-09-09 17:30:56 +08:00 committed by GitHub
parent 7be9ab1ed9
commit ad9003c41e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
94 changed files with 2995 additions and 1252 deletions

View file

@ -6,6 +6,7 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus"
@ -591,7 +592,7 @@ func DoSettleBalance(coinbase common.Address, takerOrder, makerOrder *tradingsta
tradingstate.SetSubRelayerFee(makerOrder.ExchangeAddress, newRelayerMakerFee, common.RelayerFee, statedb) tradingstate.SetSubRelayerFee(makerOrder.ExchangeAddress, newRelayerMakerFee, common.RelayerFee, statedb)
masternodeOwner := statedb.GetOwner(coinbase) masternodeOwner := statedb.GetOwner(coinbase)
statedb.AddBalance(masternodeOwner, matchingFee) statedb.AddBalance(masternodeOwner, matchingFee, tracing.BalanceChangeUnspecified)
err = tradingstate.SetTokenBalance(takerOrder.UserAddress, newTakerInTotal, settleBalance.Taker.InToken, statedb) err = tradingstate.SetTokenBalance(takerOrder.UserAddress, newTakerInTotal, settleBalance.Taker.InToken, statedb)
if err != nil { if err != nil {
@ -680,7 +681,7 @@ func (XDCx *XDCX) ProcessCancelOrder(header *types.Header, tradingStateDB *tradi
} }
masternodeOwner := statedb.GetOwner(coinbase) masternodeOwner := statedb.GetOwner(coinbase)
// relayers pay XDC for masternode // relayers pay XDC for masternode
statedb.AddBalance(masternodeOwner, common.RelayerCancelFee) statedb.AddBalance(masternodeOwner, common.RelayerCancelFee, tracing.BalanceChangeUnspecified)
relayerOwner := tradingstate.GetRelayerOwner(originOrder.ExchangeAddress, statedb) relayerOwner := tradingstate.GetRelayerOwner(originOrder.ExchangeAddress, statedb)
switch originOrder.Side { switch originOrder.Side {

View file

@ -6,6 +6,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -139,7 +140,7 @@ func SubRelayerFee(relayer common.Address, fee *big.Int, statedb *state.StateDB)
} else { } else {
balance = new(big.Int).Sub(balance, fee) balance = new(big.Int).Sub(balance, fee)
statedb.SetState(common.RelayerRegistrationSMC, locHashDeposit, common.BigToHash(balance)) statedb.SetState(common.RelayerRegistrationSMC, locHashDeposit, common.BigToHash(balance))
statedb.SubBalance(common.RelayerRegistrationSMC, fee) statedb.SubBalance(common.RelayerRegistrationSMC, fee, tracing.BalanceChangeUnspecified)
log.Debug("ApplyXDCXMatchedTransaction settle balance: SubRelayerFee AFTER", "relayer", relayer.String(), "balance", balance) log.Debug("ApplyXDCXMatchedTransaction settle balance: SubRelayerFee AFTER", "relayer", relayer.String(), "balance", balance)
return nil return nil
} }
@ -162,7 +163,7 @@ func AddTokenBalance(addr common.Address, value *big.Int, token common.Address,
if token == common.XDCNativeAddressBinary { if token == common.XDCNativeAddressBinary {
balance := statedb.GetBalance(addr) balance := statedb.GetBalance(addr)
log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD TOKEN XDC NATIVE BEFORE", "token", common.XDCNativeAddress, "address", addr.String(), "balance", balance, "orderValue", value) log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD TOKEN XDC NATIVE BEFORE", "token", common.XDCNativeAddress, "address", addr.String(), "balance", balance, "orderValue", value)
statedb.AddBalance(addr, value) statedb.AddBalance(addr, value, tracing.BalanceChangeUnspecified)
balance = statedb.GetBalance(addr) balance = statedb.GetBalance(addr)
log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD XDC NATIVE BALANCE AFTER", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value) log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD XDC NATIVE BALANCE AFTER", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
@ -192,7 +193,7 @@ func SubTokenBalance(addr common.Address, value *big.Int, token common.Address,
if balance.Cmp(value) < 0 { if balance.Cmp(value) < 0 {
return errors.Errorf("value %s in token %s not enough , have : %s , want : %s ", addr.String(), token.String(), balance, value) return errors.Errorf("value %s in token %s not enough , have : %s , want : %s ", addr.String(), token.String(), balance, value)
} }
statedb.SubBalance(addr, value) statedb.SubBalance(addr, value, tracing.BalanceChangeUnspecified)
balance = statedb.GetBalance(addr) balance = statedb.GetBalance(addr)
log.Debug("ApplyXDCXMatchedTransaction settle balance: SUB XDC NATIVE BALANCE AFTER", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value) log.Debug("ApplyXDCXMatchedTransaction settle balance: SUB XDC NATIVE BALANCE AFTER", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
return nil return nil
@ -323,7 +324,7 @@ func GetTokenBalance(addr common.Address, token common.Address, statedb *state.S
func SetTokenBalance(addr common.Address, balance *big.Int, token common.Address, statedb *state.StateDB) error { func SetTokenBalance(addr common.Address, balance *big.Int, token common.Address, statedb *state.StateDB) error {
// XDC native // XDC native
if token == common.XDCNativeAddressBinary { if token == common.XDCNativeAddressBinary {
statedb.SetBalance(addr, balance) statedb.SetBalance(addr, balance, tracing.BalanceChangeUnspecified)
return nil return nil
} }
@ -344,5 +345,5 @@ func SetSubRelayerFee(relayer common.Address, balance *big.Int, fee *big.Int, st
locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locBig, RelayerStructMappingSlot["_deposit"]) locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locBig, RelayerStructMappingSlot["_deposit"])
locHashDeposit := common.BigToHash(locBigDeposit) locHashDeposit := common.BigToHash(locBigDeposit)
statedb.SetState(common.RelayerRegistrationSMC, locHashDeposit, common.BigToHash(balance)) statedb.SetState(common.RelayerRegistrationSMC, locHashDeposit, common.BigToHash(balance))
statedb.SubBalance(common.RelayerRegistrationSMC, fee) statedb.SubBalance(common.RelayerRegistrationSMC, fee, tracing.BalanceChangeUnspecified)
} }

View file

@ -6,6 +6,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -91,7 +92,7 @@ func SubRelayerFee(relayer common.Address, fee *big.Int, statedb *state.StateDB)
} else { } else {
balance = new(big.Int).Sub(balance, fee) balance = new(big.Int).Sub(balance, fee)
statedb.SetState(common.RelayerRegistrationSMC, locHashDeposit, common.BigToHash(balance)) statedb.SetState(common.RelayerRegistrationSMC, locHashDeposit, common.BigToHash(balance))
statedb.SubBalance(common.RelayerRegistrationSMC, fee) statedb.SubBalance(common.RelayerRegistrationSMC, fee, tracing.BalanceChangeUnspecified)
log.Debug("ApplyXDCXMatchedTransaction settle balance: SubRelayerFee AFTER", "relayer", relayer.String(), "balance", balance) log.Debug("ApplyXDCXMatchedTransaction settle balance: SubRelayerFee AFTER", "relayer", relayer.String(), "balance", balance)
return nil return nil
} }
@ -114,7 +115,7 @@ func AddTokenBalance(addr common.Address, value *big.Int, token common.Address,
if token == common.XDCNativeAddressBinary { if token == common.XDCNativeAddressBinary {
balance := statedb.GetBalance(addr) balance := statedb.GetBalance(addr)
log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD TOKEN XDC NATIVE BEFORE", "token", common.XDCNativeAddress, "address", addr.String(), "balance", balance, "orderValue", value) log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD TOKEN XDC NATIVE BEFORE", "token", common.XDCNativeAddress, "address", addr.String(), "balance", balance, "orderValue", value)
statedb.AddBalance(addr, value) statedb.AddBalance(addr, value, tracing.BalanceChangeUnspecified)
balance = statedb.GetBalance(addr) balance = statedb.GetBalance(addr)
log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD XDC NATIVE BALANCE AFTER", "token", common.XDCNativeAddress, "address", addr.String(), "balance", balance, "orderValue", value) log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD XDC NATIVE BALANCE AFTER", "token", common.XDCNativeAddress, "address", addr.String(), "balance", balance, "orderValue", value)
@ -144,7 +145,7 @@ func SubTokenBalance(addr common.Address, value *big.Int, token common.Address,
if balance.Cmp(value) < 0 { if balance.Cmp(value) < 0 {
return errors.Errorf("value %s in token %s not enough , have : %s , want : %s ", addr.String(), common.XDCNativeAddress, balance, value) return errors.Errorf("value %s in token %s not enough , have : %s , want : %s ", addr.String(), common.XDCNativeAddress, balance, value)
} }
statedb.SubBalance(addr, value) statedb.SubBalance(addr, value, tracing.BalanceChangeUnspecified)
balance = statedb.GetBalance(addr) balance = statedb.GetBalance(addr)
log.Debug("ApplyXDCXMatchedTransaction settle balance: SUB XDC NATIVE BALANCE AFTER", "token", common.XDCNativeAddress, "address", addr.String(), "balance", balance, "orderValue", value) log.Debug("ApplyXDCXMatchedTransaction settle balance: SUB XDC NATIVE BALANCE AFTER", "token", common.XDCNativeAddress, "address", addr.String(), "balance", balance, "orderValue", value)
@ -276,7 +277,7 @@ func GetTokenBalance(addr common.Address, token common.Address, statedb *state.S
func SetTokenBalance(addr common.Address, balance *big.Int, token common.Address, statedb *state.StateDB) error { func SetTokenBalance(addr common.Address, balance *big.Int, token common.Address, statedb *state.StateDB) error {
// XDC native // XDC native
if token == common.XDCNativeAddressBinary { if token == common.XDCNativeAddressBinary {
statedb.SetBalance(addr, balance) statedb.SetBalance(addr, balance, tracing.BalanceChangeUnspecified)
return nil return nil
} }
@ -297,5 +298,5 @@ func SetSubRelayerFee(relayer common.Address, balance *big.Int, fee *big.Int, st
locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locBig, RelayerStructMappingSlot["_deposit"]) locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locBig, RelayerStructMappingSlot["_deposit"])
locHashDeposit := common.BigToHash(locBigDeposit) locHashDeposit := common.BigToHash(locBigDeposit)
statedb.SetState(common.RelayerRegistrationSMC, locHashDeposit, common.BigToHash(balance)) statedb.SetState(common.RelayerRegistrationSMC, locHashDeposit, common.BigToHash(balance))
statedb.SubBalance(common.RelayerRegistrationSMC, fee) statedb.SubBalance(common.RelayerRegistrationSMC, fee, tracing.BalanceChangeUnspecified)
} }

View file

@ -11,6 +11,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
) )
@ -670,7 +671,7 @@ func DoSettleBalance(coinbase common.Address, takerOrder, makerOrder *lendingsta
mapBalances[settleBalance.Maker.OutToken][common.LendingLockAddressBinary] = newCollateralTokenLock mapBalances[settleBalance.Maker.OutToken][common.LendingLockAddressBinary] = newCollateralTokenLock
} }
masternodeOwner := statedb.GetOwner(coinbase) masternodeOwner := statedb.GetOwner(coinbase)
statedb.AddBalance(masternodeOwner, matchingFee) statedb.AddBalance(masternodeOwner, matchingFee, tracing.BalanceChangeUnspecified)
for token, balances := range mapBalances { for token, balances := range mapBalances {
for adrr, value := range balances { for adrr, value := range balances {
err := lendingstate.SetTokenBalance(adrr, value, token, statedb) err := lendingstate.SetTokenBalance(adrr, value, token, statedb)
@ -747,7 +748,7 @@ func (l *Lending) ProcessCancelOrder(header *types.Header, lendingStateDB *lendi
// relayers pay XDC for masternode // relayers pay XDC for masternode
lendingstate.SubRelayerFee(originOrder.Relayer, common.RelayerLendingCancelFee, statedb) lendingstate.SubRelayerFee(originOrder.Relayer, common.RelayerLendingCancelFee, statedb)
masternodeOwner := statedb.GetOwner(coinbase) masternodeOwner := statedb.GetOwner(coinbase)
statedb.AddBalance(masternodeOwner, common.RelayerLendingCancelFee) statedb.AddBalance(masternodeOwner, common.RelayerLendingCancelFee, tracing.BalanceChangeUnspecified)
relayerOwner := lendingstate.GetRelayerOwner(originOrder.Relayer, statedb) relayerOwner := lendingstate.GetRelayerOwner(originOrder.Relayer, statedb)
switch originOrder.Side { switch originOrder.Side {
case lendingstate.Investing: case lendingstate.Investing:

View file

@ -43,6 +43,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/bloombits" "github.com/XinFinOrg/XDPoSChain/core/bloombits"
"github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
@ -737,7 +738,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM
// Set infinite balance to the fake caller account. // Set infinite balance to the fake caller account.
from := stateDB.GetOrNewStateObject(call.From) from := stateDB.GetOrNewStateObject(call.From)
from.SetBalance(math.MaxBig256) from.SetBalance(math.MaxBig256, tracing.BalanceChangeUnspecified)
// Execute the call. // Execute the call.
msg := &core.Message{ msg := &core.Message{

View file

@ -79,6 +79,8 @@ It expects the genesis file or the network name [ mainnet | testnet | devnet ] a
utils.MetricsInfluxDBTokenFlag, utils.MetricsInfluxDBTokenFlag,
utils.MetricsInfluxDBBucketFlag, utils.MetricsInfluxDBBucketFlag,
utils.MetricsInfluxDBOrganizationFlag, utils.MetricsInfluxDBOrganizationFlag,
utils.VMTraceFlag,
utils.VMTraceConfigFlag,
}, utils.DatabaseFlags), }, utils.DatabaseFlags),
Description: ` Description: `
The import command imports blocks from an RLP-encoded form. The form can be one file The import command imports blocks from an RLP-encoded form. The form can be one file

View file

@ -44,6 +44,7 @@ import (
// Force-load the tracer engines to trigger registration // Force-load the tracer engines to trigger registration
_ "github.com/XinFinOrg/XDPoSChain/eth/tracers/js" _ "github.com/XinFinOrg/XDPoSChain/eth/tracers/js"
_ "github.com/XinFinOrg/XDPoSChain/eth/tracers/live"
_ "github.com/XinFinOrg/XDPoSChain/eth/tracers/native" _ "github.com/XinFinOrg/XDPoSChain/eth/tracers/native"
) )
@ -121,6 +122,8 @@ var (
//utils.VMEnableDebugFlag, //utils.VMEnableDebugFlag,
utils.Enable0xPrefixFlag, utils.Enable0xPrefixFlag,
utils.EnableXDCPrefixFlag, utils.EnableXDCPrefixFlag,
utils.VMTraceFlag,
utils.VMTraceConfigFlag,
utils.NetworkIdFlag, utils.NetworkIdFlag,
utils.HTTPCORSDomainFlag, utils.HTTPCORSDomainFlag,
utils.AuthListenFlag, utils.AuthListenFlag,

View file

@ -32,6 +32,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/core/vm/runtime" "github.com/XinFinOrg/XDPoSChain/core/vm/runtime"
@ -80,7 +81,7 @@ func runCmd(ctx *cli.Context) error {
} }
var ( var (
tracer vm.EVMLogger tracer *tracing.Hooks
debugLogger *logger.StructLogger debugLogger *logger.StructLogger
statedb *state.StateDB statedb *state.StateDB
chainConfig *params.ChainConfig chainConfig *params.ChainConfig
@ -91,7 +92,7 @@ func runCmd(ctx *cli.Context) error {
tracer = logger.NewJSONLogger(logconfig, os.Stdout) tracer = logger.NewJSONLogger(logconfig, os.Stdout)
} else if ctx.Bool(DebugFlag.Name) { } else if ctx.Bool(DebugFlag.Name) {
debugLogger = logger.NewStructLogger(logconfig) debugLogger = logger.NewStructLogger(logconfig)
tracer = debugLogger tracer = debugLogger.Hooks()
} else { } else {
debugLogger = logger.NewStructLogger(logconfig) debugLogger = logger.NewStructLogger(logconfig)
} }
@ -235,9 +236,7 @@ Gas used: %d
`, execTime, mem.HeapObjects, mem.Alloc, mem.TotalAlloc, mem.NumGC, initialGas-leftOverGas) `, execTime, mem.HeapObjects, mem.Alloc, mem.TotalAlloc, mem.NumGC, initialGas-leftOverGas)
} }
if tracer != nil { if tracer == nil {
tracer.CaptureEnd(ret, initialGas-leftOverGas, err)
} else {
fmt.Printf("%#x\n", ret) fmt.Printf("%#x\n", ret)
if err != nil { if err != nil {
fmt.Printf(" error: %v\n", err) fmt.Printf(" error: %v\n", err)

View file

@ -57,20 +57,13 @@ func stateTestCmd(ctx *cli.Context) error {
EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name), EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name),
} }
var ( var cfg vm.Config
tracer vm.EVMLogger
debugger *logger.StructLogger
)
switch { switch {
case ctx.Bool(MachineFlag.Name): case ctx.Bool(MachineFlag.Name):
tracer = logger.NewJSONLogger(config, os.Stderr) cfg.Tracer = logger.NewJSONLogger(config, os.Stderr)
case ctx.Bool(DebugFlag.Name): case ctx.Bool(DebugFlag.Name):
debugger = logger.NewStructLogger(config) cfg.Tracer = logger.NewStructLogger(config).Hooks()
tracer = debugger
default:
debugger = logger.NewStructLogger(config)
} }
// Load the test content from the input file // Load the test content from the input file
src, err := os.ReadFile(ctx.Args().First()) src, err := os.ReadFile(ctx.Args().First())
@ -82,9 +75,6 @@ func stateTestCmd(ctx *cli.Context) error {
return err return err
} }
// Iterate over all the tests, run them and aggregate the results // Iterate over all the tests, run them and aggregate the results
cfg := vm.Config{
Tracer: tracer,
}
results := make([]StatetestResult, 0, len(tests)) results := make([]StatetestResult, 0, len(tests))
for key, test := range tests { for key, test := range tests {
for _, st := range test.Subtests() { for _, st := range test.Subtests() {
@ -103,16 +93,7 @@ func stateTestCmd(ctx *cli.Context) error {
if ctx.Bool(MachineFlag.Name) && s != nil { if ctx.Bool(MachineFlag.Name) && s != nil {
fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%x\"}\n", s.IntermediateRoot(false)) fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%x\"}\n", s.IntermediateRoot(false))
} }
results = append(results, *result) results = append(results, *result)
// Print any structured logs collected
if ctx.Bool(DebugFlag.Name) {
if debugger != nil {
fmt.Fprintln(os.Stderr, "#### TRACE ####")
logger.WriteTrace(os.Stderr, debugger.StructLogs())
}
}
} }
} }
out, _ := json.MarshalIndent(results, "", " ") out, _ := json.MarshalIndent(results, "", " ")

1
cmd/evm/testdata/31/README.md vendored Normal file
View file

@ -0,0 +1 @@
This test does some EVM execution, and can be used to test the tracers and trace-outputs.

16
cmd/evm/testdata/31/alloc.json vendored Normal file
View file

@ -0,0 +1,16 @@
{
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : {
"balance" : "0x016345785d8a0000",
"code" : "0x",
"nonce" : "0x00",
"storage" : {
}
},
"0x1111111111111111111111111111111111111111" : {
"balance" : "0x1",
"code" : "0x604060406040604000",
"nonce" : "0x00",
"storage" : {
}
}
}

20
cmd/evm/testdata/31/env.json vendored Normal file
View file

@ -0,0 +1,20 @@
{
"currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
"currentNumber" : "0x01",
"currentTimestamp" : "0x03e8",
"currentGasLimit" : "0x1000000000",
"previousHash" : "0xe4e2a30b340bec696242b67584264f878600dce98354ae0b6328740fd4ff18da",
"currentDataGasUsed" : "0x2000",
"parentTimestamp" : "0x00",
"parentDifficulty" : "0x00",
"parentUncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"parentBeaconBlockRoot" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"currentRandom" : "0x0000000000000000000000000000000000000000000000000000000000020000",
"withdrawals" : [
],
"parentBaseFee" : "0x08",
"parentGasUsed" : "0x00",
"parentGasLimit" : "0x1000000000",
"parentExcessBlobGas" : "0x1000",
"parentBlobGasUsed" : "0x2000"
}

View file

@ -0,0 +1,6 @@
{"pc":0,"op":96,"gas":"0x13498","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":2,"op":96,"gas":"0x13495","gasCost":"0x3","memSize":0,"stack":["0x40"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":4,"op":96,"gas":"0x13492","gasCost":"0x3","memSize":0,"stack":["0x40","0x40"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":6,"op":96,"gas":"0x1348f","gasCost":"0x3","memSize":0,"stack":["0x40","0x40","0x40"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":8,"op":0,"gas":"0x1348c","gasCost":"0x0","memSize":0,"stack":["0x40","0x40","0x40","0x40"],"depth":1,"refund":0,"opName":"STOP"}
{"output":"","gasUsed":"0xc"}

14
cmd/evm/testdata/31/txs.json vendored Normal file
View file

@ -0,0 +1,14 @@
[
{
"gas": "0x186a0",
"gasPrice": "0x600",
"input": "0x",
"nonce": "0x0",
"to": "0x1111111111111111111111111111111111111111",
"value": "0x1",
"v" : "0x0",
"r" : "0x0",
"s" : "0x0",
"secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"
}
]

View file

@ -19,6 +19,7 @@ package utils
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"encoding/json"
"fmt" "fmt"
"math" "math"
"math/big" "math/big"
@ -375,8 +376,18 @@ var (
Usage: "Record information useful for VM and contract debugging", Usage: "Record information useful for VM and contract debugging",
Category: flags.VMCategory, Category: flags.VMCategory,
} }
VMTraceFlag = &cli.StringFlag{
Name: "vmtrace",
Usage: "Name of tracer which should record internal VM operations (costly)",
Category: flags.VMCategory,
}
VMTraceConfigFlag = &cli.StringFlag{
Name: "vmtrace-config",
Usage: "Tracer configuration (JSON)",
Category: flags.VMCategory,
}
// API options // API options.
RPCGlobalGasCapFlag = &cli.Uint64Flag{ RPCGlobalGasCapFlag = &cli.Uint64Flag{
Name: "rpc-gascap", Name: "rpc-gascap",
Usage: "Sets a cap on gas that can be used in eth_call/estimateGas (0=infinite)", Usage: "Sets a cap on gas that can be used in eth_call/estimateGas (0=infinite)",
@ -1658,6 +1669,19 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
cfg.GasPrice = big.NewInt(1) cfg.GasPrice = big.NewInt(1)
} }
} }
// VM tracing config.
if ctx.IsSet(VMTraceFlag.Name) {
if name := ctx.String(VMTraceFlag.Name); name != "" {
var config string
if ctx.IsSet(VMTraceConfigFlag.Name) {
config = ctx.String(VMTraceConfigFlag.Name)
}
cfg.VMTrace = name
cfg.VMTraceConfig = config
}
}
} }
// RegisterEthService adds an Ethereum client to the stack. // RegisterEthService adds an Ethereum client to the stack.
@ -1836,10 +1860,25 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (chain *core.B
cache.TrieCleanLimit = ctx.Int(CacheFlag.Name) * ctx.Int(CacheGCFlag.Name) / 100 cache.TrieCleanLimit = ctx.Int(CacheFlag.Name) * ctx.Int(CacheGCFlag.Name) / 100
} }
vmcfg := vm.Config{EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name)} vmcfg := vm.Config{EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name)}
if ctx.IsSet(VMTraceFlag.Name) {
if name := ctx.String(VMTraceFlag.Name); name != "" {
var config json.RawMessage
if ctx.IsSet(VMTraceConfigFlag.Name) {
config = json.RawMessage(ctx.String(VMTraceConfigFlag.Name))
}
t, err := tracers.LiveDirectory.New(name, config)
if err != nil {
Fatalf("Failed to create tracer %q: %v", name, err)
}
vmcfg.Tracer = t
}
}
// Disable transaction indexing/unindexing by default.
chain, err = core.NewBlockChain(chainDb, cache, config, engine, vmcfg) chain, err = core.NewBlockChain(chainDb, cache, config, engine, vmcfg)
if err != nil { if err != nil {
Fatalf("Can't create BlockChain: %v", err) Fatalf("Can't create BlockChain: %v", err)
} }
return chain, chainDb return chain, chainDb
} }

View file

@ -27,6 +27,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/consensus/misc" "github.com/XinFinOrg/XDPoSChain/consensus/misc"
"github.com/XinFinOrg/XDPoSChain/consensus/misc/eip1559" "github.com/XinFinOrg/XDPoSChain/consensus/misc/eip1559"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/trie" "github.com/XinFinOrg/XDPoSChain/trie"
@ -448,7 +449,7 @@ var (
// AccumulateRewards credits the coinbase of the given block with the mining // AccumulateRewards credits the coinbase of the given block with the mining
// reward. The total reward consists of the static block reward and rewards for // reward. The total reward consists of the static block reward and rewards for
// included uncles. The coinbase of each uncle block is also rewarded. // included uncles. The coinbase of each uncle block is also rewarded.
func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) { func accumulateRewards(config *params.ChainConfig, stateDB *state.StateDB, header *types.Header, uncles []*types.Header) {
// Select the correct block reward based on chain progression // Select the correct block reward based on chain progression
blockReward := FrontierBlockReward blockReward := FrontierBlockReward
if config.IsByzantium(header.Number) { if config.IsByzantium(header.Number) {
@ -462,10 +463,10 @@ func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header
r.Sub(r, header.Number) r.Sub(r, header.Number)
r.Mul(r, blockReward) r.Mul(r, blockReward)
r.Div(r, big8) r.Div(r, big8)
state.AddBalance(uncle.Coinbase, r) stateDB.AddBalance(uncle.Coinbase, r, tracing.BalanceIncreaseRewardMineUncle)
r.Div(blockReward, big32) r.Div(blockReward, big32)
reward.Add(reward, r) reward.Add(reward, r)
} }
state.AddBalance(header.Coinbase, reward) stateDB.AddBalance(header.Coinbase, reward, tracing.BalanceIncreaseRewardMineBlock)
} }

View file

@ -22,6 +22,7 @@ import (
"math/big" "math/big"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
) )
@ -40,10 +41,11 @@ var (
// ensure it conforms to DAO hard-fork rules. // ensure it conforms to DAO hard-fork rules.
// //
// DAO hard-fork extension to the header validity: // DAO hard-fork extension to the header validity:
// a) if the node is no-fork, do not accept blocks in the [fork, fork+10) range //
// with the fork specific extra-data set // a) if the node is no-fork, do not accept blocks in the [fork, fork+10) range
// b) if the node is pro-fork, require blocks in the specific range to have the // with the fork specific extra-data set
// unique extra-data set. // b) if the node is pro-fork, require blocks in the specific range to have the
// unique extra-data set.
func VerifyDAOHeaderExtraData(config *params.ChainConfig, header *types.Header) error { func VerifyDAOHeaderExtraData(config *params.ChainConfig, header *types.Header) error {
// Short circuit validation if the node doesn't care about the DAO fork // Short circuit validation if the node doesn't care about the DAO fork
if config.DAOForkBlock == nil { if config.DAOForkBlock == nil {
@ -79,7 +81,7 @@ func ApplyDAOHardFork(statedb *state.StateDB) {
// Move every DAO account and extra-balance account funds into the refund contract // Move every DAO account and extra-balance account funds into the refund contract
for _, addr := range params.DAODrainList() { for _, addr := range params.DAODrainList() {
statedb.AddBalance(params.DAORefundContract, statedb.GetBalance(addr)) statedb.AddBalance(params.DAORefundContract, statedb.GetBalance(addr), tracing.BalanceIncreaseDaoContract)
statedb.SetBalance(addr, new(big.Int)) statedb.SetBalance(addr, new(big.Int), tracing.BalanceDecreaseDaoAccount)
} }
} }

View file

@ -41,6 +41,7 @@ import (
contractValidator "github.com/XinFinOrg/XDPoSChain/contracts/validator/contract" contractValidator "github.com/XinFinOrg/XDPoSChain/contracts/validator/contract"
"github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
@ -211,6 +212,7 @@ type BlockChain struct {
prefetcher Prefetcher // Block state prefetcher interface prefetcher Prefetcher // Block state prefetcher interface
processor Processor // Block transaction processor interface processor Processor // Block transaction processor interface
vmConfig vm.Config vmConfig vm.Config
logger *tracing.Hooks
IPCEndpoint string IPCEndpoint string
Client bind.ContractBackend // Global ipc client instance. Client bind.ContractBackend // Global ipc client instance.
@ -259,6 +261,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
downloadingBlock: lru.NewCache[common.Hash, struct{}](blockCacheLimit), downloadingBlock: lru.NewCache[common.Hash, struct{}](blockCacheLimit),
engine: engine, engine: engine,
vmConfig: vmConfig, vmConfig: vmConfig,
logger: vmConfig.Tracer,
badBlocks: lru.NewCache[common.Hash, *types.Header](badBlockLimit), badBlocks: lru.NewCache[common.Hash, *types.Header](badBlockLimit),
blocksHashCache: lru.NewCache[uint64, []common.Hash](blocksHashCacheLimit), blocksHashCache: lru.NewCache[uint64, []common.Hash](blocksHashCacheLimit),
resultTrade: lru.NewCache[common.Hash, interface{}](tradingstate.OrderCacheLimit), resultTrade: lru.NewCache[common.Hash, interface{}](tradingstate.OrderCacheLimit),
@ -306,6 +309,25 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
} }
} }
if bc.logger != nil && bc.logger.OnBlockchainInit != nil {
bc.logger.OnBlockchainInit(chainConfig)
}
if bc.logger != nil && bc.logger.OnGenesisBlock != nil {
if block := bc.CurrentBlock(); block.NumberU64() == 0 {
alloc, err := getGenesisState(bc.db, block.Hash())
if err != nil {
return nil, fmt.Errorf("failed to get genesis state: %w", err)
}
if alloc == nil {
return nil, fmt.Errorf("live blockchain tracer requires genesis alloc to be set")
}
bc.logger.OnGenesisBlock(bc.genesisBlock, alloc)
}
}
// Start future block processor. // Start future block processor.
bc.wg.Add(1) bc.wg.Add(1)
go bc.futureBlocksLoop() go bc.futureBlocksLoop()
@ -1727,7 +1749,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, []
} }
// Retrieve the parent block and it's state to execute on top // Retrieve the parent block and it's state to execute on top
start := time.Now() start := time.Now()
parent := it.previous() parent := it.previous()
if parent == nil { if parent == nil {
parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1) parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
@ -1737,6 +1758,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, []
if err != nil { if err != nil {
return it.index, events, coalescedLogs, err return it.index, events, coalescedLogs, err
} }
statedb.SetLogger(bc.logger)
// If we have a followup block, run that against the current state to pre-cache // If we have a followup block, run that against the current state to pre-cache
// transactions and probabilistically some of the account/storage trie nodes. // transactions and probabilistically some of the account/storage trie nodes.
@ -1745,7 +1767,10 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, []
if followup, err := it.peek(); followup != nil && err == nil { if followup, err := it.peek(); followup != nil && err == nil {
go func(start time.Time) { go func(start time.Time) {
throwaway, _ := state.New(parent.Root, bc.stateCache) throwaway, _ := state.New(parent.Root, bc.stateCache)
bc.prefetcher.Prefetch(followup, throwaway, bc.vmConfig, &followupInterrupt) // Disable tracing for prefetcher executions.
vmCfg := bc.vmConfig
vmCfg.Tracer = nil
bc.prefetcher.Prefetch(followup, throwaway, vmCfg, &followupInterrupt)
blockPrefetchExecuteTimer.Update(time.Since(start)) blockPrefetchExecuteTimer.Update(time.Since(start))
if followupInterrupt.Load() { if followupInterrupt.Load() {
@ -1755,70 +1780,27 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, []
} }
} }
// Process block using the parent state as reference point. // The traced section of block import.
t0 := time.Now() res, err := bc.processBlock(block, parent, statedb)
isTIPXDCXReceiver := bc.Config().IsTIPXDCXReceiver(block.Number())
tradingState, lendingState, err := bc.processTradingAndLendingStates(isTIPXDCXReceiver, block, parent, statedb)
if err != nil {
bc.reportBlock(block, nil, err)
followupInterrupt.Store(true)
return it.index, events, coalescedLogs, err
}
feeCapacity := state.GetTRC21FeeCapacityFromStateWithCache(parent.Root, statedb)
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, tradingState, bc.vmConfig, feeCapacity)
t1 := time.Now()
if err != nil {
bc.reportBlock(block, receipts, err)
followupInterrupt.Store(true)
return it.index, events, coalescedLogs, err
}
// Validate the state using the default validator
err = bc.validator.ValidateState(block, statedb, receipts, usedGas)
if err != nil {
bc.reportBlock(block, receipts, err)
return it.index, events, coalescedLogs, err
}
t2 := time.Now()
proctime := time.Since(start)
// Write the block to the chain and get the status.
status, err := bc.writeBlockWithState(block, receipts, statedb, tradingState, lendingState)
t3 := time.Now()
followupInterrupt.Store(true) followupInterrupt.Store(true)
if err != nil { if err != nil {
return it.index, events, coalescedLogs, err return it.index, events, coalescedLogs, err
} }
// Report the import stats before returning the various results
stats.processed++
stats.usedGas += res.usedGas
// Update the metrics subsystem with all the measurements switch res.status {
accountReadTimer.Update(statedb.AccountReads)
accountHashTimer.Update(statedb.AccountHashes)
accountUpdateTimer.Update(statedb.AccountUpdates)
accountCommitTimer.Update(statedb.AccountCommits)
storageReadTimer.Update(statedb.StorageReads)
storageHashTimer.Update(statedb.StorageHashes)
storageUpdateTimer.Update(statedb.StorageUpdates)
storageCommitTimer.Update(statedb.StorageCommits)
trieAccess := statedb.AccountReads + statedb.AccountHashes + statedb.AccountUpdates + statedb.AccountCommits
trieAccess += statedb.StorageReads + statedb.StorageHashes + statedb.StorageUpdates + statedb.StorageCommits
blockInsertTimer.UpdateSince(start)
blockExecutionTimer.Update(t1.Sub(t0) - trieAccess)
blockValidationTimer.Update(t2.Sub(t1))
blockWriteTimer.Update(t3.Sub(t2))
switch status {
case CanonStatTy: case CanonStatTy:
log.Debug("Inserted new block from downloader", "number", block.Number(), "hash", block.Hash(), "uncles", len(block.Uncles()), log.Debug("Inserted new block from downloader", "number", block.Number(), "hash", block.Hash(), "uncles", len(block.Uncles()),
"txs", len(block.Transactions()), "gas", block.GasUsed(), "elapsed", common.PrettyDuration(time.Since(start))) "txs", len(block.Transactions()), "gas", block.GasUsed(), "elapsed", common.PrettyDuration(time.Since(start)))
coalescedLogs = append(coalescedLogs, logs...) coalescedLogs = append(coalescedLogs, res.logs...)
events = append(events, ChainEvent{block, block.Hash(), logs}) events = append(events, ChainEvent{block, block.Hash(), res.logs})
lastCanon = block lastCanon = block
// Only count canonical blocks for GC processing time // Only count canonical blocks for GC processing time
bc.gcproc += proctime bc.gcproc += res.procTime
bc.UpdateBlocksHashCache(block) bc.UpdateBlocksHashCache(block)
if bc.chainConfig.IsTIPXDCX(block.Number()) && bc.chainConfig.XDPoS != nil && block.NumberU64() > bc.chainConfig.XDPoS.Epoch { if bc.chainConfig.IsTIPXDCX(block.Number()) && bc.chainConfig.XDPoS != nil && block.NumberU64() > bc.chainConfig.XDPoS.Epoch {
bc.logExchangeData(block) bc.logExchangeData(block)
@ -1830,9 +1812,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, []
events = append(events, ChainSideEvent{block}) events = append(events, ChainSideEvent{block})
bc.UpdateBlocksHashCache(block) bc.UpdateBlocksHashCache(block)
} }
blockInsertTimer.UpdateSince(start)
stats.processed++
stats.usedGas += usedGas
dirty, _ := bc.stateCache.TrieDB().Size() dirty, _ := bc.stateCache.TrieDB().Size()
stats.report(chain, it.index, dirty) stats.report(chain, it.index, dirty)
@ -1873,6 +1852,96 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, []
return it.index, events, coalescedLogs, nil return it.index, events, coalescedLogs, nil
} }
// blockProcessingResult is a summary of block processing
// used for updating the stats.
type blockProcessingResult struct {
usedGas uint64
procTime time.Duration
status WriteStatus
logs []*types.Log
}
// processBlock executes and validates the given block. If there was no error
// it writes the block and associated state to database.
func (bc *BlockChain) processBlock(block *types.Block, parent *types.Header, statedb *state.StateDB) (_ *blockProcessingResult, blockEndErr error) {
var (
err error
startTime = time.Now()
)
// TODO(daniel): implement CurrentFinalBlock() and CurrentSafeBlock(), ref PR #29189
if bc.logger != nil && bc.logger.OnBlockStart != nil {
td := bc.GetTd(block.ParentHash(), block.NumberU64()-1)
bc.logger.OnBlockStart(tracing.BlockEvent{
Block: block,
TD: td,
// Finalized: bc.CurrentFinalBlock(),
// Safe: bc.CurrentSafeBlock(),
})
}
if bc.logger != nil && bc.logger.OnBlockEnd != nil {
defer func() {
bc.logger.OnBlockEnd(blockEndErr)
}()
}
// Process block using the parent state as reference point.
pstart := time.Now()
isTIPXDCXReceiver := bc.Config().IsTIPXDCXReceiver(block.Number())
tradingState, lendingState, err := bc.processTradingAndLendingStates(isTIPXDCXReceiver, block, parent, statedb)
if err != nil {
bc.reportBlock(block, nil, err)
return nil, err
}
feeCapacity := state.GetTRC21FeeCapacityFromStateWithCache(parent.Root, statedb)
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, tradingState, bc.vmConfig, feeCapacity)
if err != nil {
bc.reportBlock(block, receipts, err)
return nil, err
}
ptime := time.Since(pstart)
vstart := time.Now()
// Validate the state using the default validator
err = bc.validator.ValidateState(block, statedb, receipts, usedGas)
if err != nil {
bc.reportBlock(block, receipts, err)
return nil, err
}
vtime := time.Since(vstart)
proctime := time.Since(startTime) // processing + validation
// Update the metrics touched during block processing and validation
accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing)
storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete(in processing)
accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete(in validation)
storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete(in validation)
accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete(in validation)
storageHashTimer.Update(statedb.StorageHashes) // Storage hashes are complete(in validation)
triehash := statedb.AccountHashes + statedb.StorageHashes // The time spent on tries hashing
trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates // The time spent on tries update
blockExecutionTimer.Update(ptime - (statedb.AccountReads + statedb.StorageReads)) // The time spent on EVM processing
blockValidationTimer.Update(vtime - (triehash + trieUpdate)) // The time spent on block validation
// Write the block to the chain and get the status.
var (
wstart = time.Now()
status WriteStatus
)
status, err = bc.writeBlockWithState(block, receipts, statedb, tradingState, lendingState)
if err != nil {
return nil, err
}
// Update the metrics touched during block commit
accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them
storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them
blockWriteTimer.Update(time.Since(wstart) - statedb.AccountCommits - statedb.StorageCommits)
elapsed := time.Since(startTime) + 1 // prevent zero division
blockInsertTimer.Update(elapsed)
return &blockProcessingResult{usedGas: usedGas, procTime: proctime, status: status, logs: logs}, nil
}
// insertSidechain is called when an import batch hits upon a pruned ancestor // insertSidechain is called when an import batch hits upon a pruned ancestor
// error, which happens when a sidechain with a sufficiently old fork-block is // error, which happens when a sidechain with a sufficiently old fork-block is
// found. // found.

View file

@ -21,6 +21,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
@ -123,6 +124,6 @@ func CanTransfer(db vm.StateDB, addr common.Address, amount *big.Int) bool {
// Transfer subtracts amount from sender and adds amount to recipient using the given Db // Transfer subtracts amount from sender and adds amount to recipient using the given Db
func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) { func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) {
db.SubBalance(sender, amount) db.SubBalance(sender, amount, tracing.BalanceChangeTransfer)
db.AddBalance(recipient, amount) db.AddBalance(recipient, amount, tracing.BalanceChangeTransfer)
} }

View file

@ -27,6 +27,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common/math" "github.com/XinFinOrg/XDPoSChain/common/math"
"github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/ethdb"
@ -66,6 +67,37 @@ type Genesis struct {
BaseFee *big.Int `json:"baseFeePerGas"` BaseFee *big.Int `json:"baseFeePerGas"`
} }
func getGenesisState(db ethdb.Database, blockhash common.Hash) (alloc types.GenesisAlloc, err error) {
blob := rawdb.ReadGenesisStateSpec(db, blockhash)
if len(blob) != 0 {
if err := alloc.UnmarshalJSON(blob); err != nil {
return nil, err
}
return alloc, nil
}
// Genesis allocation is missing and there are several possibilities:
// the node is legacy which doesn't persist the genesis allocation or
// the persisted allocation is just lost.
// - supported networks(mainnet, testnets), recover with defined allocations
// - private network, can't recover
var genesis *Genesis
switch blockhash {
case params.MainnetGenesisHash:
genesis = DefaultGenesisBlock()
case params.TestnetGenesisHash:
genesis = DefaultTestnetGenesisBlock()
case params.DevnetGenesisHash:
genesis = DefaultDevnetGenesisBlock()
}
if genesis != nil {
return genesis.Alloc, nil
}
return nil, nil
}
// field type overrides for gencodec // field type overrides for gencodec
type genesisSpecMarshaling struct { type genesisSpecMarshaling struct {
Nonce math.HexOrDecimal64 Nonce math.HexOrDecimal64
@ -186,7 +218,7 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
} }
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(db)) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(db))
for addr, account := range g.Alloc { for addr, account := range g.Alloc {
statedb.AddBalance(addr, account.Balance) statedb.AddBalance(addr, account.Balance, tracing.BalanceIncreaseGenesisBalance)
statedb.SetCode(addr, account.Code) statedb.SetCode(addr, account.Code)
statedb.SetNonce(addr, account.Nonce) statedb.SetNonce(addr, account.Nonce)
for key, value := range account.Storage { for key, value := range account.Storage {

View file

@ -83,3 +83,10 @@ func WriteChainConfig(db ethdb.KeyValueWriter, hash common.Hash, cfg *params.Cha
} }
return db.Put(configKey(hash), data) return db.Put(configKey(hash), data)
} }
// ReadGenesisStateSpec retrieves the genesis state specification based on the
// given genesis (block-)hash.
func ReadGenesisStateSpec(db ethdb.KeyValueReader, blockhash common.Hash) []byte {
data, _ := db.Get(genesisStateSpecKey(blockhash))
return data
}

View file

@ -58,8 +58,9 @@ var (
// used by old db, now only used for conversion // used by old db, now only used for conversion
oldReceiptsPrefix = []byte("receipts-") oldReceiptsPrefix = []byte("receipts-")
preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage
configPrefix = []byte("ethereum-config-") // config prefix for the db configPrefix = []byte("ethereum-config-") // config prefix for the db
genesisPrefix = []byte("ethereum-genesis-") // genesis state prefix for the db
// Chain index prefixes (use `i` + single byte to avoid mixing data types). // Chain index prefixes (use `i` + single byte to avoid mixing data types).
BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress
@ -176,3 +177,8 @@ func IsCodeKey(key []byte) (bool, []byte) {
func configKey(hash common.Hash) []byte { func configKey(hash common.Hash) []byte {
return append(configPrefix, hash.Bytes()...) return append(configPrefix, hash.Bytes()...)
} }
// genesisStateSpecKey = genesisPrefix + hash
func genesisStateSpecKey(hash common.Hash) []byte {
return append(genesisPrefix, hash.Bytes()...)
}

View file

@ -24,6 +24,7 @@ import (
"time" "time"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
@ -233,6 +234,9 @@ func (s *stateObject) SetState(db Database, key, value common.Hash) {
key: key, key: key,
prevalue: prev, prevalue: prev,
}) })
if s.db.logger != nil && s.db.logger.OnStorageChange != nil {
s.db.logger.OnStorageChange(s.address, key, prev, value)
}
s.setState(key, value) s.setState(key, value)
} }
@ -340,7 +344,7 @@ func (s *stateObject) commitTrie(db Database) (*trie.NodeSet, error) {
// AddBalance adds amount to s's balance. // AddBalance adds amount to s's balance.
// It is used to add funds to the destination account of a transfer. // It is used to add funds to the destination account of a transfer.
func (s *stateObject) AddBalance(amount *big.Int) { func (s *stateObject) AddBalance(amount *big.Int, reason tracing.BalanceChangeReason) {
// EIP161: We must check emptiness for the objects such that the account // EIP161: We must check emptiness for the objects such that the account
// clearing (0,0,0 objects) can take effect. // clearing (0,0,0 objects) can take effect.
if amount.Sign() == 0 { if amount.Sign() == 0 {
@ -349,23 +353,26 @@ func (s *stateObject) AddBalance(amount *big.Int) {
} }
return return
} }
s.SetBalance(new(big.Int).Add(s.Balance(), amount)) s.SetBalance(new(big.Int).Add(s.Balance(), amount), reason)
} }
// SubBalance removes amount from s's balance. // SubBalance removes amount from s's balance.
// It is used to remove funds from the origin account of a transfer. // It is used to remove funds from the origin account of a transfer.
func (s *stateObject) SubBalance(amount *big.Int) { func (s *stateObject) SubBalance(amount *big.Int, reason tracing.BalanceChangeReason) {
if amount.Sign() == 0 { if amount.Sign() == 0 {
return return
} }
s.SetBalance(new(big.Int).Sub(s.Balance(), amount)) s.SetBalance(new(big.Int).Sub(s.Balance(), amount), reason)
} }
func (s *stateObject) SetBalance(amount *big.Int) { func (s *stateObject) SetBalance(amount *big.Int, reason tracing.BalanceChangeReason) {
s.db.journal.append(balanceChange{ s.db.journal.append(balanceChange{
account: &s.address, account: &s.address,
prev: new(big.Int).Set(s.data.Balance), prev: new(big.Int).Set(s.data.Balance),
}) })
if s.db.logger != nil && s.db.logger.OnBalanceChange != nil {
s.db.logger.OnBalanceChange(s.address, s.Balance(), amount, reason)
}
s.setBalance(amount) s.setBalance(amount)
} }
@ -437,6 +444,9 @@ func (s *stateObject) SetCode(codeHash common.Hash, code []byte) {
prevhash: s.CodeHash(), prevhash: s.CodeHash(),
prevcode: prevcode, prevcode: prevcode,
}) })
if s.db.logger != nil && s.db.logger.OnCodeChange != nil {
s.db.logger.OnCodeChange(s.address, common.BytesToHash(s.CodeHash()), prevcode, codeHash, code)
}
s.setCode(codeHash, code) s.setCode(codeHash, code)
} }
@ -451,6 +461,9 @@ func (s *stateObject) SetNonce(nonce uint64) {
account: &s.address, account: &s.address,
prev: s.data.Nonce, prev: s.data.Nonce,
}) })
if s.db.logger != nil && s.db.logger.OnNonceChange != nil {
s.db.logger.OnNonceChange(s.address, s.data.Nonce, nonce)
}
s.setNonce(nonce) s.setNonce(nonce)
} }

View file

@ -24,6 +24,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/ethdb"
@ -48,11 +49,11 @@ func TestDump(t *testing.T) {
// generate a few entries // generate a few entries
obj1 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01})) obj1 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01}))
obj1.AddBalance(big.NewInt(22)) obj1.AddBalance(big.NewInt(22), tracing.BalanceChangeUnspecified)
obj2 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01, 0x02})) obj2 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01, 0x02}))
obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3}) obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3})
obj3 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x02})) obj3 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x02}))
obj3.SetBalance(big.NewInt(44)) obj3.SetBalance(big.NewInt(44), tracing.BalanceChangeUnspecified)
// write some of them to the trie // write some of them to the trie
s.state.updateStateObject(obj1) s.state.updateStateObject(obj1)
@ -100,13 +101,13 @@ func TestIterativeDump(t *testing.T) {
// generate a few entries // generate a few entries
obj1 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01})) obj1 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01}))
obj1.AddBalance(big.NewInt(22)) obj1.AddBalance(big.NewInt(22), tracing.BalanceChangeUnspecified)
obj2 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01, 0x02})) obj2 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01, 0x02}))
obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3}) obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3})
obj3 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x02})) obj3 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x02}))
obj3.SetBalance(big.NewInt(44)) obj3.SetBalance(big.NewInt(44), tracing.BalanceChangeUnspecified)
obj4 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x00})) obj4 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x00}))
obj4.AddBalance(big.NewInt(1337)) obj4.AddBalance(big.NewInt(1337), tracing.BalanceChangeUnspecified)
// write some of them to the trie // write some of them to the trie
s.state.updateStateObject(obj1) s.state.updateStateObject(obj1)
@ -202,7 +203,7 @@ func TestSnapshot2(t *testing.T) {
// db, trie are already non-empty values // db, trie are already non-empty values
so0 := state.getStateObject(stateobjaddr0) so0 := state.getStateObject(stateobjaddr0)
so0.SetBalance(big.NewInt(42)) so0.SetBalance(big.NewInt(42), tracing.BalanceChangeUnspecified)
so0.SetNonce(43) so0.SetNonce(43)
so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'}) so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'})
so0.selfDestructed = false so0.selfDestructed = false
@ -214,7 +215,7 @@ func TestSnapshot2(t *testing.T) {
// and one with deleted == true // and one with deleted == true
so1 := state.getStateObject(stateobjaddr1) so1 := state.getStateObject(stateobjaddr1)
so1.SetBalance(big.NewInt(52)) so1.SetBalance(big.NewInt(52), tracing.BalanceChangeUnspecified)
so1.SetNonce(53) so1.SetNonce(53)
so1.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e', '2'}), []byte{'c', 'a', 'f', 'e', '2'}) so1.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e', '2'}), []byte{'c', 'a', 'f', 'e', '2'})
so1.selfDestructed = true so1.selfDestructed = true

View file

@ -26,6 +26,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
@ -45,8 +46,9 @@ type revision struct {
// * Contracts // * Contracts
// * Accounts // * Accounts
type StateDB struct { type StateDB struct {
db Database db Database
trie Trie trie Trie
logger *tracing.Hooks
// This map holds 'live' objects, which will get modified while processing a state transition. // This map holds 'live' objects, which will get modified while processing a state transition.
stateObjects map[common.Address]*stateObject stateObjects map[common.Address]*stateObject
@ -130,6 +132,11 @@ func New(root common.Hash, db Database) (*StateDB, error) {
}, nil }, nil
} }
// SetLogger sets the logger for account update hooks.
func (s *StateDB) SetLogger(l *tracing.Hooks) {
s.logger = l
}
// setError remembers the first non-nil error it is called with. // setError remembers the first non-nil error it is called with.
func (s *StateDB) setError(err error) { func (s *StateDB) setError(err error) {
if s.dbErr == nil { if s.dbErr == nil {
@ -169,6 +176,9 @@ func (s *StateDB) AddLog(log *types.Log) {
log.TxHash = s.thash log.TxHash = s.thash
log.TxIndex = uint(s.txIndex) log.TxIndex = uint(s.txIndex)
log.Index = s.logSize log.Index = s.logSize
if s.logger != nil && s.logger.OnLog != nil {
s.logger.OnLog(log)
}
s.logs[s.thash] = append(s.logs[s.thash], log) s.logs[s.thash] = append(s.logs[s.thash], log)
s.logSize++ s.logSize++
} }
@ -366,25 +376,25 @@ func (s *StateDB) HasSelfDestructed(addr common.Address) bool {
*/ */
// AddBalance adds amount to the account associated with addr. // AddBalance adds amount to the account associated with addr.
func (s *StateDB) AddBalance(addr common.Address, amount *big.Int) { func (s *StateDB) AddBalance(addr common.Address, amount *big.Int, reason tracing.BalanceChangeReason) {
stateObject := s.GetOrNewStateObject(addr) stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil { if stateObject != nil {
stateObject.AddBalance(amount) stateObject.AddBalance(amount, reason)
} }
} }
// SubBalance subtracts amount from the account associated with addr. // SubBalance subtracts amount from the account associated with addr.
func (s *StateDB) SubBalance(addr common.Address, amount *big.Int) { func (s *StateDB) SubBalance(addr common.Address, amount *big.Int, reason tracing.BalanceChangeReason) {
stateObject := s.GetOrNewStateObject(addr) stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil { if stateObject != nil {
stateObject.SubBalance(amount) stateObject.SubBalance(amount, reason)
} }
} }
func (s *StateDB) SetBalance(addr common.Address, amount *big.Int) { func (s *StateDB) SetBalance(addr common.Address, amount *big.Int, reason tracing.BalanceChangeReason) {
stateObject := s.GetOrNewStateObject(addr) stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil { if stateObject != nil {
stateObject.SetBalance(amount) stateObject.SetBalance(amount, reason)
} }
} }
@ -434,13 +444,20 @@ func (s *StateDB) SelfDestruct(addr common.Address) {
if stateObject == nil { if stateObject == nil {
return return
} }
var (
prev = new(big.Int).Set(stateObject.Balance())
n = new(big.Int)
)
s.journal.append(selfDestructChange{ s.journal.append(selfDestructChange{
account: &addr, account: &addr,
prev: stateObject.selfDestructed, prev: stateObject.selfDestructed,
prevbalance: new(big.Int).Set(stateObject.Balance()), prevbalance: prev,
}) })
if s.logger != nil && s.logger.OnBalanceChange != nil && prev.Sign() > 0 {
s.logger.OnBalanceChange(addr, prev, n, tracing.BalanceDecreaseSelfdestruct)
}
stateObject.markSelfdestructed() stateObject.markSelfdestructed()
stateObject.data.Balance = new(big.Int) stateObject.data.Balance = n
} }
func (s *StateDB) Selfdestruct6780(addr common.Address) { func (s *StateDB) Selfdestruct6780(addr common.Address) {
@ -763,6 +780,11 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
// We need to maintain account deletions explicitly (will remain // We need to maintain account deletions explicitly (will remain
// set indefinitely). // set indefinitely).
s.stateObjectsDestruct[obj.address] = struct{}{} s.stateObjectsDestruct[obj.address] = struct{}{}
// If ether was sent to account post-selfdestruct it is burnt.
if bal := obj.Balance(); s.logger != nil && s.logger.OnBalanceChange != nil && obj.selfDestructed && bal.Sign() != 0 {
s.logger.OnBalanceChange(obj.address, bal, new(big.Int), tracing.BalanceDecreaseSelfdestructBurn)
}
} else { } else {
obj.finalise() obj.finalise()
} }

View file

@ -31,6 +31,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
) )
@ -44,7 +45,7 @@ func TestUpdateLeaks(t *testing.T) {
// Update it with some accounts // Update it with some accounts
for i := byte(0); i < 255; i++ { for i := byte(0); i < 255; i++ {
addr := common.BytesToAddress([]byte{i}) addr := common.BytesToAddress([]byte{i})
state.AddBalance(addr, big.NewInt(int64(11*i))) state.AddBalance(addr, big.NewInt(int64(11*i)), tracing.BalanceChangeUnspecified)
state.SetNonce(addr, uint64(42*i)) state.SetNonce(addr, uint64(42*i))
if i%2 == 0 { if i%2 == 0 {
state.SetState(addr, common.BytesToHash([]byte{i, i, i}), common.BytesToHash([]byte{i, i, i, i})) state.SetState(addr, common.BytesToHash([]byte{i, i, i}), common.BytesToHash([]byte{i, i, i, i}))
@ -77,7 +78,7 @@ func TestIntermediateLeaks(t *testing.T) {
finalState, _ := New(types.EmptyRootHash, NewDatabase(finalDb)) finalState, _ := New(types.EmptyRootHash, NewDatabase(finalDb))
modify := func(state *StateDB, addr common.Address, i, tweak byte) { modify := func(state *StateDB, addr common.Address, i, tweak byte) {
state.SetBalance(addr, big.NewInt(int64(11*i)+int64(tweak))) state.SetBalance(addr, big.NewInt(int64(11*i)+int64(tweak)), tracing.BalanceChangeUnspecified)
state.SetNonce(addr, uint64(42*i+tweak)) state.SetNonce(addr, uint64(42*i+tweak))
if i%2 == 0 { if i%2 == 0 {
state.SetState(addr, common.Hash{i, i, i, 0}, common.Hash{}) state.SetState(addr, common.Hash{i, i, i, 0}, common.Hash{})
@ -154,7 +155,7 @@ func TestCopy(t *testing.T) {
for i := byte(0); i < 255; i++ { for i := byte(0); i < 255; i++ {
obj := orig.GetOrNewStateObject(common.BytesToAddress([]byte{i})) obj := orig.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
obj.AddBalance(big.NewInt(int64(i))) obj.AddBalance(big.NewInt(int64(i)), tracing.BalanceChangeUnspecified)
orig.updateStateObject(obj) orig.updateStateObject(obj)
} }
orig.Finalise(false) orig.Finalise(false)
@ -171,9 +172,9 @@ func TestCopy(t *testing.T) {
copyObj := copy.GetOrNewStateObject(common.BytesToAddress([]byte{i})) copyObj := copy.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
ccopyObj := ccopy.GetOrNewStateObject(common.BytesToAddress([]byte{i})) ccopyObj := ccopy.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
origObj.AddBalance(big.NewInt(2 * int64(i))) origObj.AddBalance(big.NewInt(2*int64(i)), tracing.BalanceChangeUnspecified)
copyObj.AddBalance(big.NewInt(3 * int64(i))) copyObj.AddBalance(big.NewInt(3*int64(i)), tracing.BalanceChangeUnspecified)
ccopyObj.AddBalance(big.NewInt(4 * int64(i))) ccopyObj.AddBalance(big.NewInt(4*int64(i)), tracing.BalanceChangeUnspecified)
orig.updateStateObject(origObj) orig.updateStateObject(origObj)
copy.updateStateObject(copyObj) copy.updateStateObject(copyObj)
@ -253,14 +254,14 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
{ {
name: "SetBalance", name: "SetBalance",
fn: func(a testAction, s *StateDB) { fn: func(a testAction, s *StateDB) {
s.SetBalance(addr, big.NewInt(a.args[0])) s.SetBalance(addr, big.NewInt(a.args[0]), tracing.BalanceChangeUnspecified)
}, },
args: make([]int64, 1), args: make([]int64, 1),
}, },
{ {
name: "AddBalance", name: "AddBalance",
fn: func(a testAction, s *StateDB) { fn: func(a testAction, s *StateDB) {
s.AddBalance(addr, big.NewInt(a.args[0])) s.AddBalance(addr, big.NewInt(a.args[0]), tracing.BalanceChangeUnspecified)
}, },
args: make([]int64, 1), args: make([]int64, 1),
}, },
@ -487,7 +488,7 @@ func TestTouchDelete(t *testing.T) {
s.state.Reset(root) s.state.Reset(root)
snapshot := s.state.Snapshot() snapshot := s.state.Snapshot()
s.state.AddBalance(common.Address{}, new(big.Int)) s.state.AddBalance(common.Address{}, new(big.Int), tracing.BalanceChangeUnspecified)
if len(s.state.journal.dirties) != 1 { if len(s.state.journal.dirties) != 1 {
t.Fatal("expected one dirty state object") t.Fatal("expected one dirty state object")
@ -719,7 +720,7 @@ func TestDeleteCreateRevert(t *testing.T) {
state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase())) state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()))
addr := common.BytesToAddress([]byte("so")) addr := common.BytesToAddress([]byte("so"))
state.SetBalance(addr, big.NewInt(1)) state.SetBalance(addr, big.NewInt(1), tracing.BalanceChangeUnspecified)
root, _ := state.Commit(false) root, _ := state.Commit(false)
state.Reset(root) state.Reset(root)
@ -729,7 +730,7 @@ func TestDeleteCreateRevert(t *testing.T) {
state.Finalise(true) state.Finalise(true)
id := state.Snapshot() id := state.Snapshot()
state.SetBalance(addr, big.NewInt(2)) state.SetBalance(addr, big.NewInt(2), tracing.BalanceChangeUnspecified)
state.RevertToSnapshot(id) state.RevertToSnapshot(id)
// Commit the entire state and make sure we don't crash and have the correct state // Commit the entire state and make sure we don't crash and have the correct state
@ -746,7 +747,7 @@ func TestDeleteCreateRevert(t *testing.T) {
func TestCopyOfCopy(t *testing.T) { func TestCopyOfCopy(t *testing.T) {
state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase())) state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()))
addr := common.HexToAddress("aaaa") addr := common.HexToAddress("aaaa")
state.SetBalance(addr, big.NewInt(42)) state.SetBalance(addr, big.NewInt(42), tracing.BalanceChangeUnspecified)
if got := state.Copy().GetBalance(addr).Uint64(); got != 42 { if got := state.Copy().GetBalance(addr).Uint64(); got != 42 {
t.Fatalf("1st copy fail, expected 42, got %v", got) t.Fatalf("1st copy fail, expected 42, got %v", got)
@ -768,9 +769,9 @@ func TestCopyCommitCopy(t *testing.T) {
skey := common.HexToHash("aaa") skey := common.HexToHash("aaa")
sval := common.HexToHash("bbb") sval := common.HexToHash("bbb")
state.SetBalance(addr, big.NewInt(42)) // Change the account trie state.SetBalance(addr, big.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie
state.SetCode(addr, []byte("hello")) // Change an external metadata state.SetCode(addr, []byte("hello")) // Change an external metadata
state.SetState(addr, skey, sval) // Change the storage trie state.SetState(addr, skey, sval) // Change the storage trie
if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42) t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42)
@ -840,9 +841,9 @@ func TestCopyCopyCommitCopy(t *testing.T) {
skey := common.HexToHash("aaa") skey := common.HexToHash("aaa")
sval := common.HexToHash("bbb") sval := common.HexToHash("bbb")
state.SetBalance(addr, big.NewInt(42)) // Change the account trie state.SetBalance(addr, big.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie
state.SetCode(addr, []byte("hello")) // Change an external metadata state.SetCode(addr, []byte("hello")) // Change an external metadata
state.SetState(addr, skey, sval) // Change the storage trie state.SetState(addr, skey, sval) // Change the storage trie
if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42) t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42)

View file

@ -23,6 +23,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/ethdb"
@ -50,8 +51,8 @@ func makeTestState() (Database, common.Hash, []*testAccount) {
obj := state.GetOrNewStateObject(common.BytesToAddress([]byte{i})) obj := state.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
acc := &testAccount{address: common.BytesToAddress([]byte{i})} acc := &testAccount{address: common.BytesToAddress([]byte{i})}
obj.AddBalance(big.NewInt(int64(11 * i))) obj.AddBalance(big.NewInt(11*int64(i)), tracing.BalanceChangeUnspecified)
acc.balance = big.NewInt(int64(11 * i)) acc.balance = big.NewInt(11 * int64(i))
obj.SetNonce(uint64(42 * i)) obj.SetNonce(uint64(42 * i))
acc.nonce = uint64(42 * i) acc.nonce = uint64(42 * i)

View file

@ -6,6 +6,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/lru" "github.com/XinFinOrg/XDPoSChain/common/lru"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
) )
var ( var (
@ -146,5 +147,5 @@ func UpdateTRC21Fee(statedb *StateDB, newBalance map[common.Address]*big.Int, to
balanceKey := GetLocMappingAtKey(token.Hash(), slotTokensState) balanceKey := GetLocMappingAtKey(token.Hash(), slotTokensState)
statedb.SetState(common.TRC21IssuerSMC, common.BigToHash(balanceKey), common.BigToHash(value)) statedb.SetState(common.TRC21IssuerSMC, common.BigToHash(balanceKey), common.BigToHash(value))
} }
statedb.SubBalance(common.TRC21IssuerSMC, totalFeeUsed) statedb.SubBalance(common.TRC21IssuerSMC, totalFeeUsed, tracing.BalanceChangeUnspecified)
} }

View file

@ -18,7 +18,6 @@ package core
import ( import (
"fmt" "fmt"
"math/big" "math/big"
"runtime" "runtime"
"strings" "strings"
@ -29,6 +28,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/misc" "github.com/XinFinOrg/XDPoSChain/consensus/misc"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
@ -66,7 +66,7 @@ func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consen
// Process returns the receipts and logs accumulated during the process and // Process returns the receipts and logs accumulated during the process and
// returns the amount of gas that was used in the process. If any of the // returns the amount of gas that was used in the process. If any of the
// transactions failed to execute due to insufficient gas it will return an error. // transactions failed to execute due to insufficient gas it will return an error.
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, tradingState *tradingstate.TradingStateDB, cfg vm.Config, balanceFee map[common.Address]*big.Int) (types.Receipts, []*types.Log, uint64, error) { func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, tradingState *tradingstate.TradingStateDB, cfg vm.Config, tokensFee map[common.Address]*big.Int) (types.Receipts, []*types.Log, uint64, error) {
var ( var (
receipts types.Receipts receipts types.Receipts
usedGas = new(uint64) usedGas = new(uint64)
@ -89,6 +89,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, tra
totalFeeUsed := big.NewInt(0) totalFeeUsed := big.NewInt(0)
blockContext := NewEVMBlockContext(header, p.bc, nil) blockContext := NewEVMBlockContext(header, p.bc, nil)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, tradingState, p.config, cfg) vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, tradingState, p.config, cfg)
signer := types.MakeSigner(p.config, blockNumber)
coinbaseOwner := getCoinbaseOwner(p.bc, statedb, header, nil) coinbaseOwner := getCoinbaseOwner(p.bc, statedb, header, nil)
// Iterate over and process the individual transactions // Iterate over and process the individual transactions
for i, tx := range block.Transactions() { for i, tx := range block.Transactions() {
@ -117,8 +118,20 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, tra
return nil, nil, 0, err return nil, nil, 0, err
} }
} }
var balanceFee *big.Int
if tx.To() != nil {
if value, ok := tokensFee[*tx.To()]; ok {
balanceFee = value
}
}
msg, err := TransactionToMessage(tx, signer, balanceFee, blockNumber, header.BaseFee)
if err != nil {
return nil, nil, 0, err
}
statedb.SetTxContext(tx.Hash(), i) statedb.SetTxContext(tx.Hash(), i)
receipt, gas, err, tokenFeeUsed := applyTransaction(p.config, balanceFee, gp, statedb, coinbaseOwner, blockNumber, header.BaseFee, blockHash, tx, usedGas, vmenv)
receipt, gas, err, tokenFeeUsed := ApplyTransactionWithEVM(msg, p.config, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv, balanceFee, coinbaseOwner)
if err != nil { if err != nil {
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
} }
@ -126,8 +139,8 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, tra
allLogs = append(allLogs, receipt.Logs...) allLogs = append(allLogs, receipt.Logs...)
if tokenFeeUsed { if tokenFeeUsed {
fee := common.GetGasFee(block.Header().Number.Uint64(), gas) fee := common.GetGasFee(block.Header().Number.Uint64(), gas)
balanceFee[*tx.To()] = new(big.Int).Sub(balanceFee[*tx.To()], fee) tokensFee[*tx.To()] = new(big.Int).Sub(tokensFee[*tx.To()], fee)
balanceUpdated[*tx.To()] = balanceFee[*tx.To()] balanceUpdated[*tx.To()] = tokensFee[*tx.To()]
totalFeeUsed = totalFeeUsed.Add(totalFeeUsed, fee) totalFeeUsed = totalFeeUsed.Add(totalFeeUsed, fee)
} }
} }
@ -137,7 +150,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, tra
return receipts, allLogs, *usedGas, nil return receipts, allLogs, *usedGas, nil
} }
func (p *StateProcessor) ProcessBlockNoValidator(cBlock *CalculatedBlock, statedb *state.StateDB, tradingState *tradingstate.TradingStateDB, cfg vm.Config, balanceFee map[common.Address]*big.Int) (types.Receipts, []*types.Log, uint64, error) { func (p *StateProcessor) ProcessBlockNoValidator(cBlock *CalculatedBlock, statedb *state.StateDB, tradingState *tradingstate.TradingStateDB, cfg vm.Config, tokensFee map[common.Address]*big.Int) (types.Receipts, []*types.Log, uint64, error) {
block := cBlock.block block := cBlock.block
var ( var (
receipts types.Receipts receipts types.Receipts
@ -168,6 +181,7 @@ func (p *StateProcessor) ProcessBlockNoValidator(cBlock *CalculatedBlock, stated
} }
blockContext := NewEVMBlockContext(header, p.bc, nil) blockContext := NewEVMBlockContext(header, p.bc, nil)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, tradingState, p.config, cfg) vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, tradingState, p.config, cfg)
signer := types.MakeSigner(p.config, blockNumber)
coinbaseOwner := getCoinbaseOwner(p.bc, statedb, header, nil) coinbaseOwner := getCoinbaseOwner(p.bc, statedb, header, nil)
// Iterate over and process the individual transactions // Iterate over and process the individual transactions
receipts = make([]*types.Receipt, block.Transactions().Len()) receipts = make([]*types.Receipt, block.Transactions().Len())
@ -197,8 +211,18 @@ func (p *StateProcessor) ProcessBlockNoValidator(cBlock *CalculatedBlock, stated
return nil, nil, 0, err return nil, nil, 0, err
} }
} }
var balanceFee *big.Int
if tx.To() != nil {
if value, ok := tokensFee[*tx.To()]; ok {
balanceFee = value
}
}
msg, err := TransactionToMessage(tx, signer, balanceFee, blockNumber, header.BaseFee)
if err != nil {
return nil, nil, 0, err
}
statedb.SetTxContext(tx.Hash(), i) statedb.SetTxContext(tx.Hash(), i)
receipt, gas, err, tokenFeeUsed := applyTransaction(p.config, balanceFee, gp, statedb, coinbaseOwner, blockNumber, header.BaseFee, blockHash, tx, usedGas, vmenv) receipt, gas, err, tokenFeeUsed := ApplyTransactionWithEVM(msg, p.config, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv, balanceFee, coinbaseOwner)
if err != nil { if err != nil {
return nil, nil, 0, err return nil, nil, 0, err
} }
@ -209,8 +233,8 @@ func (p *StateProcessor) ProcessBlockNoValidator(cBlock *CalculatedBlock, stated
allLogs = append(allLogs, receipt.Logs...) allLogs = append(allLogs, receipt.Logs...)
if tokenFeeUsed { if tokenFeeUsed {
fee := common.GetGasFee(block.Header().Number.Uint64(), gas) fee := common.GetGasFee(block.Header().Number.Uint64(), gas)
balanceFee[*tx.To()] = new(big.Int).Sub(balanceFee[*tx.To()], fee) tokensFee[*tx.To()] = new(big.Int).Sub(tokensFee[*tx.To()], fee)
balanceUpdated[*tx.To()] = balanceFee[*tx.To()] balanceUpdated[*tx.To()] = tokensFee[*tx.To()]
totalFeeUsed = totalFeeUsed.Add(totalFeeUsed, fee) totalFeeUsed = totalFeeUsed.Add(totalFeeUsed, fee)
} }
} }
@ -220,7 +244,10 @@ func (p *StateProcessor) ProcessBlockNoValidator(cBlock *CalculatedBlock, stated
return receipts, allLogs, *usedGas, nil return receipts, allLogs, *usedGas, nil
} }
func applyTransaction(config *params.ChainConfig, tokensFee map[common.Address]*big.Int, gp *GasPool, statedb *state.StateDB, coinbaseOwner common.Address, blockNumber, baseFee *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, uint64, error, bool) { // ApplyTransactionWithEVM attempts to apply a transaction to the given state database
// and uses the input parameters for its environment similar to ApplyTransaction. However,
// this method takes an already created EVM instance as input.
func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM, balanceFee *big.Int, coinbaseOwner common.Address) (receipt *types.Receipt, gasUsed uint64, err error, tokenFeeUsed bool) {
to := tx.To() to := tx.To()
if to != nil { if to != nil {
if *to == common.BlockSignersBinary && config.IsTIPSigning(blockNumber) { if *to == common.BlockSignersBinary && config.IsTIPSigning(blockNumber) {
@ -240,17 +267,14 @@ func applyTransaction(config *params.ChainConfig, tokensFee map[common.Address]*
return ApplyEmptyTransaction(config, statedb, blockNumber, blockHash, tx, usedGas) return ApplyEmptyTransaction(config, statedb, blockNumber, blockHash, tx, usedGas)
} }
var balanceFee *big.Int if evm.Config.Tracer != nil && evm.Config.Tracer.OnTxStart != nil {
if to != nil { evm.Config.Tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
if value, ok := tokensFee[*to]; ok { if evm.Config.Tracer.OnTxEnd != nil {
balanceFee = value defer func() {
evm.Config.Tracer.OnTxEnd(receipt, err)
}()
} }
} }
msg, err := TransactionToMessage(tx, types.MakeSigner(config, blockNumber), balanceFee, blockNumber, baseFee)
if err != nil {
return nil, 0, err, false
}
// Create a new context to be used in the EVM environment // Create a new context to be used in the EVM environment
txContext := NewEVMTxContext(msg) txContext := NewEVMTxContext(msg)
@ -395,7 +419,7 @@ func applyTransaction(config *params.ChainConfig, tokensFee map[common.Address]*
hBalance.SetString(bal+"000000000000000000", 10) hBalance.SetString(bal+"000000000000000000", 10)
log.Info("address", addr, "with_balance", bal, "XDC") log.Info("address", addr, "with_balance", bal, "XDC")
addrBin := common.HexToAddress(addr) addrBin := common.HexToAddress(addr)
statedb.SetBalance(addrBin, hBalance) statedb.SetBalance(addrBin, hBalance, tracing.BalanceChangeUnspecified)
} }
} }
} }
@ -403,7 +427,6 @@ func applyTransaction(config *params.ChainConfig, tokensFee map[common.Address]*
// Apply the transaction to the current state (included in the env) // Apply the transaction to the current state (included in the env)
result, err := ApplyMessage(evm, msg, gp, coinbaseOwner) result, err := ApplyMessage(evm, msg, gp, coinbaseOwner)
if err != nil { if err != nil {
return nil, 0, err, false return nil, 0, err, false
} }
@ -419,7 +442,7 @@ func applyTransaction(config *params.ChainConfig, tokensFee map[common.Address]*
// Create a new receipt for the transaction, storing the intermediate root and gas used // Create a new receipt for the transaction, storing the intermediate root and gas used
// by the tx. // by the tx.
receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: *usedGas} receipt = &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: *usedGas}
if result.Failed() { if result.Failed() {
receipt.Status = types.ReceiptStatusFailed receipt.Status = types.ReceiptStatusFailed
} else { } else {
@ -461,11 +484,23 @@ func getCoinbaseOwner(bc *BlockChain, statedb *state.StateDB, header *types.Head
// for the transaction, gas used and an error if the transaction failed, // for the transaction, gas used and an error if the transaction failed,
// indicating the block was invalid. // indicating the block was invalid.
func ApplyTransaction(config *params.ChainConfig, tokensFee map[common.Address]*big.Int, bc *BlockChain, author *common.Address, gp *GasPool, statedb *state.StateDB, XDCxState *tradingstate.TradingStateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, uint64, error, bool) { func ApplyTransaction(config *params.ChainConfig, tokensFee map[common.Address]*big.Int, bc *BlockChain, author *common.Address, gp *GasPool, statedb *state.StateDB, XDCxState *tradingstate.TradingStateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, uint64, error, bool) {
var balanceFee *big.Int
if tx.To() != nil {
if value, ok := tokensFee[*tx.To()]; ok {
balanceFee = value
}
}
// Create a new context to be used in the EVM environment // Create a new context to be used in the EVM environment
blockContext := NewEVMBlockContext(header, bc, author) blockContext := NewEVMBlockContext(header, bc, author)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, XDCxState, config, cfg) vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, XDCxState, config, cfg)
signer := types.MakeSigner(config, header.Number)
msg, err := TransactionToMessage(tx, signer, balanceFee, header.Number, header.BaseFee)
if err != nil {
return nil, 0, err, false
}
coinbaseOwner := getCoinbaseOwner(bc, statedb, header, author) coinbaseOwner := getCoinbaseOwner(bc, statedb, header, author)
return applyTransaction(config, tokensFee, gp, statedb, coinbaseOwner, header.Number, header.BaseFee, header.Hash(), tx, usedGas, vmenv) return ApplyTransactionWithEVM(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv, balanceFee, coinbaseOwner)
} }
func ApplySignTransaction(config *params.ChainConfig, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64) (*types.Receipt, uint64, error, bool) { func ApplySignTransaction(config *params.ChainConfig, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64) (*types.Receipt, uint64, error, bool) {

View file

@ -233,7 +233,7 @@ func TestStateProcessorErrors(t *testing.T) {
txs: []*types.Transaction{ txs: []*types.Transaction{
mkDynamicTx(0, common.Address{}, params.TxGas-1000, big.NewInt(0), big.NewInt(0)), mkDynamicTx(0, common.Address{}, params.TxGas-1000, big.NewInt(0), big.NewInt(0)),
}, },
want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: transaction type not supported", want: "transaction type not supported",
}, },
} { } {
block := GenerateBadBlock(t, genesis, ethash.NewFaker(), tt.txs, gspec.Config) block := GenerateBadBlock(t, genesis, ethash.NewFaker(), tt.txs, gspec.Config)

View file

@ -22,6 +22,7 @@ import (
"math/big" "math/big"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
@ -265,11 +266,15 @@ func (st *StateTransition) buyGas() error {
if err := st.gp.SubGas(st.msg.GasLimit); err != nil { if err := st.gp.SubGas(st.msg.GasLimit); err != nil {
return err return err
} }
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil {
st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, tracing.GasChangeTxInitialBalance)
}
st.gasRemaining += st.msg.GasLimit st.gasRemaining += st.msg.GasLimit
st.initialGas = st.msg.GasLimit st.initialGas = st.msg.GasLimit
if st.msg.BalanceTokenFee == nil { if st.msg.BalanceTokenFee == nil {
st.state.SubBalance(st.msg.From, mgval) st.state.SubBalance(st.msg.From, mgval, tracing.BalanceDecreaseGasBuy)
} }
return nil return nil
} }
@ -354,13 +359,6 @@ func (st *StateTransition) TransitionDb(owner common.Address) (*ExecutionResult,
return nil, err return nil, err
} }
if tracer := st.evm.Config.Tracer; tracer != nil {
st.evm.Config.Tracer.CaptureTxStart(st.initialGas)
defer func() {
st.evm.Config.Tracer.CaptureTxEnd(st.gasRemaining)
}()
}
var ( var (
msg = st.msg msg = st.msg
sender = vm.AccountRef(st.from()) sender = vm.AccountRef(st.from())
@ -376,6 +374,9 @@ func (st *StateTransition) TransitionDb(owner common.Address) (*ExecutionResult,
if st.gasRemaining < gas { if st.gasRemaining < gas {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas) return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas)
} }
if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil {
t.OnGasChange(st.gasRemaining, st.gasRemaining-gas, tracing.GasChangeTxIntrinsicGas)
}
st.gasRemaining -= gas st.gasRemaining -= gas
// Check whether the init code size has been exceeded. // Check whether the init code size has been exceeded.
@ -418,7 +419,7 @@ func (st *StateTransition) TransitionDb(owner common.Address) (*ExecutionResult,
if st.evm.Context.BlockNumber.Cmp(common.TIPTRC21Fee) > 0 { if st.evm.Context.BlockNumber.Cmp(common.TIPTRC21Fee) > 0 {
if (owner != common.Address{}) { if (owner != common.Address{}) {
st.state.AddBalance(owner, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), msg.GasPrice)) st.state.AddBalance(owner, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), msg.GasPrice), tracing.BalanceIncreaseRewardTransactionFee)
} }
} else { } else {
effectiveTip := msg.GasPrice effectiveTip := msg.GasPrice
@ -428,7 +429,7 @@ func (st *StateTransition) TransitionDb(owner common.Address) (*ExecutionResult,
effectiveTip = msg.GasTipCap effectiveTip = msg.GasTipCap
} }
} }
st.state.AddBalance(st.evm.Context.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip)) st.state.AddBalance(st.evm.Context.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip), tracing.BalanceIncreaseRewardTransactionFee)
} }
return &ExecutionResult{ return &ExecutionResult{
@ -444,13 +445,23 @@ func (st *StateTransition) refundGas(refundQuotient uint64) {
if refund > st.state.GetRefund() { if refund > st.state.GetRefund() {
refund = st.state.GetRefund() refund = st.state.GetRefund()
} }
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && refund > 0 {
st.evm.Config.Tracer.OnGasChange(st.gasRemaining, st.gasRemaining+refund, tracing.GasChangeTxRefunds)
}
st.gasRemaining += refund st.gasRemaining += refund
if st.msg.BalanceTokenFee == nil { if st.msg.BalanceTokenFee == nil {
// Return ETH for remaining gas, exchanged at the original rate. // Return ETH for remaining gas, exchanged at the original rate.
remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gasRemaining), st.msg.GasPrice) remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gasRemaining), st.msg.GasPrice)
st.state.AddBalance(st.from(), remaining) st.state.AddBalance(st.from(), remaining, tracing.BalanceIncreaseGasReturn)
} }
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining > 0 {
st.evm.Config.Tracer.OnGasChange(st.gasRemaining, 0, tracing.GasChangeTxLeftOverReturned)
}
// Also return remaining gas to the block gas counter so it is // Also return remaining gas to the block gas counter so it is
// available for the next transaction. // available for the next transaction.
st.gp.AddGas(st.gasRemaining) st.gp.AddGas(st.gasRemaining)

View file

@ -27,6 +27,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/contracts/XDCx/contract" "github.com/XinFinOrg/XDPoSChain/contracts/XDCx/contract"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
) )
@ -57,7 +58,7 @@ func RunContract(chain consensus.ChainContext, statedb *state.StateDB, contractA
return nil, err return nil, err
} }
fakeCaller := common.HexToAddress("0x0000000000000000000000000000000000000001") fakeCaller := common.HexToAddress("0x0000000000000000000000000000000000000001")
statedb.SetBalance(fakeCaller, common.BasePrice) statedb.SetBalance(fakeCaller, common.BasePrice, tracing.BalanceChangeUnspecified)
msg := ethereum.CallMsg{To: &contractAddr, Data: input, From: fakeCaller} msg := ethereum.CallMsg{To: &contractAddr, Data: input, From: fakeCaller}
result, err := CallContractWithState(msg, chain, statedb) result, err := CallContractWithState(msg, chain, statedb)
if err != nil { if err != nil {

275
core/tracing/hooks.go Normal file
View file

@ -0,0 +1,275 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package tracing
import (
"math/big"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/holiman/uint256"
)
// OpContext provides the context at which the opcode is being
// executed in, including the memory, stack and various contract-level information.
type OpContext interface {
MemoryData() []byte
StackData() []uint256.Int
Caller() common.Address
Address() common.Address
CallValue() *big.Int
CallInput() []byte
}
// StateDB gives tracers access to the whole state.
type StateDB interface {
GetBalance(common.Address) *big.Int
GetNonce(common.Address) uint64
GetCode(common.Address) []byte
GetState(common.Address, common.Hash) common.Hash
Exist(common.Address) bool
GetRefund() uint64
}
// VMContext provides the context for the EVM execution.
type VMContext struct {
Coinbase common.Address
BlockNumber *big.Int
Time uint64
Random *common.Hash
// Effective tx gas price
GasPrice *big.Int
ChainConfig *params.ChainConfig
StateDB StateDB
}
// BlockEvent is emitted upon tracing an incoming block.
// It contains the block as well as consensus related information.
type BlockEvent struct {
Block *types.Block
TD *big.Int
Finalized *types.Header
Safe *types.Header
}
type (
/*
- VM events -
*/
// TxStartHook is called before the execution of a transaction starts.
// Call simulations don't come with a valid signature. `from` field
// to be used for address of the caller.
TxStartHook = func(vm *VMContext, tx *types.Transaction, from common.Address)
// TxEndHook is called after the execution of a transaction ends.
TxEndHook = func(receipt *types.Receipt, err error)
// EnterHook is invoked when the processing of a message starts.
EnterHook = func(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int)
// ExitHook is invoked when the processing of a message ends.
// `revert` is true when there was an error during the execution.
// Exceptionally, before the homestead hardfork a contract creation that
// ran out of gas when attempting to persist the code to database did not
// count as a call failure and did not cause a revert of the call. This will
// be indicated by `reverted == false` and `err == ErrCodeStoreOutOfGas`.
ExitHook = func(depth int, output []byte, gasUsed uint64, err error, reverted bool)
// OpcodeHook is invoked just prior to the execution of an opcode.
OpcodeHook = func(pc uint64, op byte, gas, cost uint64, scope OpContext, rData []byte, depth int, err error)
// FaultHook is invoked when an error occurs during the execution of an opcode.
FaultHook = func(pc uint64, op byte, gas, cost uint64, scope OpContext, depth int, err error)
// GasChangeHook is invoked when the gas changes.
GasChangeHook = func(old, new uint64, reason GasChangeReason)
/*
- Chain events -
*/
// BlockchainInitHook is called when the blockchain is initialized.
BlockchainInitHook = func(chainConfig *params.ChainConfig)
// BlockStartHook is called before executing `block`.
// `td` is the total difficulty prior to `block`.
BlockStartHook = func(event BlockEvent)
// BlockEndHook is called after executing a block.
BlockEndHook = func(err error)
// SkippedBlockHook indicates a block was skipped during processing
// due to it being known previously. This can happen e.g. when recovering
// from a crash.
SkippedBlockHook = func(event BlockEvent)
// GenesisBlockHook is called when the genesis block is being processed.
GenesisBlockHook = func(genesis *types.Block, alloc types.GenesisAlloc)
/*
- State events -
*/
// BalanceChangeHook is called when the balance of an account changes.
BalanceChangeHook = func(addr common.Address, prev, new *big.Int, reason BalanceChangeReason)
// NonceChangeHook is called when the nonce of an account changes.
NonceChangeHook = func(addr common.Address, prev, new uint64)
// CodeChangeHook is called when the code of an account changes.
CodeChangeHook = func(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte)
// StorageChangeHook is called when the storage of an account changes.
StorageChangeHook = func(addr common.Address, slot common.Hash, prev, new common.Hash)
// LogHook is called when a log is emitted.
LogHook = func(log *types.Log)
)
type Hooks struct {
// VM events
OnTxStart TxStartHook
OnTxEnd TxEndHook
OnEnter EnterHook
OnExit ExitHook
OnOpcode OpcodeHook
OnFault FaultHook
OnGasChange GasChangeHook
// Chain events
OnBlockchainInit BlockchainInitHook
OnBlockStart BlockStartHook
OnBlockEnd BlockEndHook
OnSkippedBlock SkippedBlockHook
OnGenesisBlock GenesisBlockHook
// State events
OnBalanceChange BalanceChangeHook
OnNonceChange NonceChangeHook
OnCodeChange CodeChangeHook
OnStorageChange StorageChangeHook
OnLog LogHook
}
// BalanceChangeReason is used to indicate the reason for a balance change, useful
// for tracing and reporting.
type BalanceChangeReason byte
const (
BalanceChangeUnspecified BalanceChangeReason = 0
// Issuance
// BalanceIncreaseRewardMineUncle is a reward for mining an uncle block.
BalanceIncreaseRewardMineUncle BalanceChangeReason = 1
// BalanceIncreaseRewardMineBlock is a reward for mining a block.
BalanceIncreaseRewardMineBlock BalanceChangeReason = 2
// BalanceIncreaseWithdrawal is ether withdrawn from the beacon chain.
BalanceIncreaseWithdrawal BalanceChangeReason = 3
// BalanceIncreaseGenesisBalance is ether allocated at the genesis block.
BalanceIncreaseGenesisBalance BalanceChangeReason = 4
// Transaction fees
// BalanceIncreaseRewardTransactionFee is the transaction tip increasing block builder's balance.
BalanceIncreaseRewardTransactionFee BalanceChangeReason = 5
// BalanceDecreaseGasBuy is spent to purchase gas for execution a transaction.
// Part of this gas will be burnt as per EIP-1559 rules.
BalanceDecreaseGasBuy BalanceChangeReason = 6
// BalanceIncreaseGasReturn is ether returned for unused gas at the end of execution.
BalanceIncreaseGasReturn BalanceChangeReason = 7
// DAO fork
// BalanceIncreaseDaoContract is ether sent to the DAO refund contract.
BalanceIncreaseDaoContract BalanceChangeReason = 8
// BalanceDecreaseDaoAccount is ether taken from a DAO account to be moved to the refund contract.
BalanceDecreaseDaoAccount BalanceChangeReason = 9
// BalanceChangeTransfer is ether transferred via a call.
// it is a decrease for the sender and an increase for the recipient.
BalanceChangeTransfer BalanceChangeReason = 10
// BalanceChangeTouchAccount is a transfer of zero value. It is only there to
// touch-create an account.
BalanceChangeTouchAccount BalanceChangeReason = 11
// BalanceIncreaseSelfdestruct is added to the recipient as indicated by a selfdestructing account.
BalanceIncreaseSelfdestruct BalanceChangeReason = 12
// BalanceDecreaseSelfdestruct is deducted from a contract due to self-destruct.
BalanceDecreaseSelfdestruct BalanceChangeReason = 13
// BalanceDecreaseSelfdestructBurn is ether that is sent to an already self-destructed
// account within the same tx (captured at end of tx).
// Note it doesn't account for a self-destruct which appoints itself as recipient.
BalanceDecreaseSelfdestructBurn BalanceChangeReason = 14
)
// GasChangeReason is used to indicate the reason for a gas change, useful
// for tracing and reporting.
//
// There is essentially two types of gas changes, those that can be emitted once per transaction
// and those that can be emitted on a call basis, so possibly multiple times per transaction.
//
// They can be recognized easily by their name, those that start with `GasChangeTx` are emitted
// once per transaction, while those that start with `GasChangeCall` are emitted on a call basis.
type GasChangeReason byte
const (
GasChangeUnspecified GasChangeReason = 0
// GasChangeTxInitialBalance is the initial balance for the call which will be equal to the gasLimit of the call. There is only
// one such gas change per transaction.
GasChangeTxInitialBalance GasChangeReason = 1
// GasChangeTxIntrinsicGas is the amount of gas that will be charged for the intrinsic cost of the transaction, there is
// always exactly one of those per transaction.
GasChangeTxIntrinsicGas GasChangeReason = 2
// GasChangeTxRefunds is the sum of all refunds which happened during the tx execution (e.g. storage slot being cleared)
// this generates an increase in gas. There is at most one of such gas change per transaction.
GasChangeTxRefunds GasChangeReason = 3
// GasChangeTxLeftOverReturned is the amount of gas left over at the end of transaction's execution that will be returned
// to the chain. This change will always be a negative change as we "drain" left over gas towards 0. If there was no gas
// left at the end of execution, no such even will be emitted. The returned gas's value in Wei is returned to caller.
// There is at most one of such gas change per transaction.
GasChangeTxLeftOverReturned GasChangeReason = 4
// GasChangeCallInitialBalance is the initial balance for the call which will be equal to the gasLimit of the call. There is only
// one such gas change per call.
GasChangeCallInitialBalance GasChangeReason = 5
// GasChangeCallLeftOverReturned is the amount of gas left over that will be returned to the caller, this change will always
// be a negative change as we "drain" left over gas towards 0. If there was no gas left at the end of execution, no such even
// will be emitted.
GasChangeCallLeftOverReturned GasChangeReason = 6
// GasChangeCallLeftOverRefunded is the amount of gas that will be refunded to the call after the child call execution it
// executed completed. This value is always positive as we are giving gas back to the you, the left over gas of the child.
// If there was no gas left to be refunded, no such even will be emitted.
GasChangeCallLeftOverRefunded GasChangeReason = 7
// GasChangeCallContractCreation is the amount of gas that will be burned for a CREATE.
GasChangeCallContractCreation GasChangeReason = 8
// GasChangeContractCreation is the amount of gas that will be burned for a CREATE2.
GasChangeCallContractCreation2 GasChangeReason = 9
// GasChangeCallCodeStorage is the amount of gas that will be charged for code storage.
GasChangeCallCodeStorage GasChangeReason = 10
// GasChangeCallOpCode is the amount of gas that will be charged for an opcode executed by the EVM, exact opcode that was
// performed can be check by `OnOpcode` handling.
GasChangeCallOpCode GasChangeReason = 11
// GasChangeCallPrecompiledContract is the amount of gas that will be charged for a precompiled contract execution.
GasChangeCallPrecompiledContract GasChangeReason = 12
// GasChangeCallStorageColdAccess is the amount of gas that will be charged for a cold storage access as controlled by EIP2929 rules.
GasChangeCallStorageColdAccess GasChangeReason = 13
// GasChangeCallFailedExecution is the burning of the remaining gas when the execution failed without a revert.
GasChangeCallFailedExecution GasChangeReason = 14
// GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as
// it will be "manually" tracked by a direct emit of the gas change event.
GasChangeIgnored GasChangeReason = 0xFF
)

View file

@ -31,6 +31,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/event" "github.com/XinFinOrg/XDPoSChain/event"
@ -229,7 +230,7 @@ func (c *testChain) State() (*state.StateDB, error) {
c.statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(db)) c.statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(db))
// simulate that the new head block included tx0 and tx1 // simulate that the new head block included tx0 and tx1
c.statedb.SetNonce(c.address, 2) c.statedb.SetNonce(c.address, 2)
c.statedb.SetBalance(c.address, new(big.Int).SetUint64(params.Ether)) c.statedb.SetBalance(c.address, new(big.Int).SetUint64(params.Ether), tracing.BalanceChangeUnspecified)
*c.trigger = false *c.trigger = false
} }
return stdb, nil return stdb, nil
@ -250,7 +251,7 @@ func TestStateChangeDuringReset(t *testing.T) {
) )
// setup pool with 2 transaction in it // setup pool with 2 transaction in it
statedb.SetBalance(address, new(big.Int).SetUint64(params.Ether)) statedb.SetBalance(address, new(big.Int).SetUint64(params.Ether), tracing.BalanceChangeUnspecified)
blockchain := &testChain{&testBlockChain{statedb, 1000000000, new(event.Feed)}, address, &trigger} blockchain := &testChain{&testBlockChain{statedb, 1000000000, new(event.Feed)}, address, &trigger}
tx0 := transaction(0, 100000, key) tx0 := transaction(0, 100000, key)
@ -283,7 +284,7 @@ func TestStateChangeDuringReset(t *testing.T) {
func testAddBalance(pool *TxPool, addr common.Address, amount *big.Int) { func testAddBalance(pool *TxPool, addr common.Address, amount *big.Int) {
pool.mu.Lock() pool.mu.Lock()
pool.currentState.AddBalance(addr, amount) pool.currentState.AddBalance(addr, amount, tracing.BalanceChangeUnspecified)
pool.mu.Unlock() pool.mu.Unlock()
} }
@ -443,9 +444,8 @@ func TestChainFork(t *testing.T) {
addr := crypto.PubkeyToAddress(key.PublicKey) addr := crypto.PubkeyToAddress(key.PublicKey)
resetState := func() { resetState := func() {
db := rawdb.NewMemoryDatabase() statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(db)) statedb.AddBalance(addr, big.NewInt(100000000000000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(addr, big.NewInt(100000000000000))
pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed)} pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed)}
<-pool.requestReset(nil, nil) <-pool.requestReset(nil, nil)
@ -473,9 +473,8 @@ func TestDoubleNonce(t *testing.T) {
addr := crypto.PubkeyToAddress(key.PublicKey) addr := crypto.PubkeyToAddress(key.PublicKey)
resetState := func() { resetState := func() {
db := rawdb.NewMemoryDatabase() statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(db)) statedb.AddBalance(addr, big.NewInt(100000000000000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(addr, big.NewInt(100000000000000))
pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed)} pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed)}
<-pool.requestReset(nil, nil) <-pool.requestReset(nil, nil)
@ -2589,7 +2588,7 @@ func BenchmarkMultiAccountBatchInsert(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
key, _ := crypto.GenerateKey() key, _ := crypto.GenerateKey()
account := crypto.PubkeyToAddress(key.PublicKey) account := crypto.PubkeyToAddress(key.PublicKey)
pool.currentState.AddBalance(account, big.NewInt(1000000)) pool.currentState.AddBalance(account, big.NewInt(1000000), tracing.BalanceChangeUnspecified)
tx := transaction(uint64(0), 100000, key) tx := transaction(uint64(0), 100000, key)
batches[i] = tx batches[i] = tx
} }

View file

@ -20,6 +20,7 @@ import (
"math/big" "math/big"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -159,14 +160,28 @@ func (c *Contract) Caller() common.Address {
} }
// UseGas attempts the use gas and subtracts it and returns true on success // UseGas attempts the use gas and subtracts it and returns true on success
func (c *Contract) UseGas(gas uint64) (ok bool) { func (c *Contract) UseGas(gas uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) (ok bool) {
if c.Gas < gas { if c.Gas < gas {
return false return false
} }
if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored {
logger.OnGasChange(c.Gas, c.Gas-gas, reason)
}
c.Gas -= gas c.Gas -= gas
return true return true
} }
// RefundGas refunds gas to the contract
func (c *Contract) RefundGas(gas uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) {
if gas == 0 {
return
}
if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored {
logger.OnGasChange(c.Gas, c.Gas+gas, reason)
}
c.Gas += gas
}
// Address returns the contracts address // Address returns the contracts address
func (c *Contract) Address() common.Address { func (c *Contract) Address() common.Address {
return c.self.Address() return c.self.Address()

View file

@ -24,6 +24,7 @@ import (
"math/big" "math/big"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/vm/privacy" "github.com/XinFinOrg/XDPoSChain/core/vm/privacy"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/crypto/blake2b" "github.com/XinFinOrg/XDPoSChain/crypto/blake2b"
@ -155,7 +156,7 @@ func ActivePrecompiles(rules params.Rules) []common.Address {
// - the returned bytes, // - the returned bytes,
// - the _remaining_ gas, // - the _remaining_ gas,
// - any error that occurred // - any error that occurred
func RunPrecompiledContract(evm *EVM, p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { func RunPrecompiledContract(evm *EVM, p PrecompiledContract, input []byte, suppliedGas uint64, logger *tracing.Hooks) (ret []byte, remainingGas uint64, err error) {
if evm != nil { if evm != nil {
if evm.chainConfig.IsTIPXDCXReceiver(evm.Context.BlockNumber) { if evm.chainConfig.IsTIPXDCXReceiver(evm.Context.BlockNumber) {
switch p := p.(type) { switch p := p.(type) {
@ -171,6 +172,9 @@ func RunPrecompiledContract(evm *EVM, p PrecompiledContract, input []byte, suppl
if suppliedGas < gasCost { if suppliedGas < gasCost {
return nil, 0, ErrOutOfGas return nil, 0, ErrOutOfGas
} }
if logger != nil && logger.OnGasChange != nil {
logger.OnGasChange(suppliedGas, suppliedGas-gasCost, tracing.GasChangeCallPrecompiledContract)
}
suppliedGas -= gasCost suppliedGas -= gasCost
output, err := p.Run(input) output, err := p.Run(input)
return output, suppliedGas, err return output, suppliedGas, err

View file

@ -36,7 +36,7 @@ func FuzzPrecompiledContracts(f *testing.F) {
return return
} }
inWant := string(input) inWant := string(input)
RunPrecompiledContract(nil, p, input, gas) RunPrecompiledContract(nil, p, input, gas, nil)
if inHave := string(input); inWant != inHave { if inHave := string(input); inWant != inHave {
t.Errorf("Precompiled %v modified input data", a) t.Errorf("Precompiled %v modified input data", a)
} }

View file

@ -514,7 +514,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
in := common.Hex2Bytes(test.input) in := common.Hex2Bytes(test.input)
gas := p.RequiredGas(in) gas := p.RequiredGas(in)
t.Run(fmt.Sprintf("%s-Gas=%d", test.name, gas), func(t *testing.T) { t.Run(fmt.Sprintf("%s-Gas=%d", test.name, gas), func(t *testing.T) {
if res, _, err := RunPrecompiledContract(nil, p, in, gas); err != nil { if res, _, err := RunPrecompiledContract(nil, p, in, gas, nil); err != nil {
t.Error(err) t.Error(err)
} else if common.Bytes2Hex(res) != test.expected { } else if common.Bytes2Hex(res) != test.expected {
t.Errorf("Expected %v, got %v", test.expected, common.Bytes2Hex(res)) t.Errorf("Expected %v, got %v", test.expected, common.Bytes2Hex(res))
@ -533,7 +533,7 @@ func testXDCxPrecompiled(addr string, test precompiledTest, t *testing.T) {
in := common.Hex2Bytes(test.input) in := common.Hex2Bytes(test.input)
gas := p.RequiredGas(in) gas := p.RequiredGas(in)
t.Run(fmt.Sprintf("%s-Gas=%d", test.name, gas), func(t *testing.T) { t.Run(fmt.Sprintf("%s-Gas=%d", test.name, gas), func(t *testing.T) {
if res, _, err := RunPrecompiledContract(nil, p, in, gas); err != nil { if res, _, err := RunPrecompiledContract(nil, p, in, gas, nil); err != nil {
t.Error(err) t.Error(err)
} else if common.Bytes2Hex(res) != test.expected { } else if common.Bytes2Hex(res) != test.expected {
t.Errorf("Expected %v, got %v", test.expected, common.Bytes2Hex(res)) t.Errorf("Expected %v, got %v", test.expected, common.Bytes2Hex(res))
@ -547,7 +547,7 @@ func testPrecompiledWithEmptyTradingState(addr string, test precompiledTest, t *
in := common.Hex2Bytes(test.input) in := common.Hex2Bytes(test.input)
gas := p.RequiredGas(in) gas := p.RequiredGas(in)
t.Run(fmt.Sprintf("testPrecompiledWithEmptyTradingState-%s-Gas=%d", test.name, gas), func(t *testing.T) { t.Run(fmt.Sprintf("testPrecompiledWithEmptyTradingState-%s-Gas=%d", test.name, gas), func(t *testing.T) {
if res, _, err := RunPrecompiledContract(nil, p, in, gas); err != nil { if res, _, err := RunPrecompiledContract(nil, p, in, gas, nil); err != nil {
t.Error(err) t.Error(err)
} else if common.Bytes2Hex(res) != test.expected { } else if common.Bytes2Hex(res) != test.expected {
t.Errorf("Expected %v, got %v", test.expected, common.Bytes2Hex(res)) t.Errorf("Expected %v, got %v", test.expected, common.Bytes2Hex(res))
@ -561,7 +561,7 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
gas := p.RequiredGas(in) - 1 gas := p.RequiredGas(in) - 1
t.Run(fmt.Sprintf("%s-Gas=%d", test.name, gas), func(t *testing.T) { t.Run(fmt.Sprintf("%s-Gas=%d", test.name, gas), func(t *testing.T) {
_, _, err := RunPrecompiledContract(nil, p, in, gas) _, _, err := RunPrecompiledContract(nil, p, in, gas, nil)
if err.Error() != "out of gas" { if err.Error() != "out of gas" {
t.Errorf("Expected error [out of gas], got [%v]", err) t.Errorf("Expected error [out of gas], got [%v]", err)
} }
@ -578,7 +578,7 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing
in := common.Hex2Bytes(test.input) in := common.Hex2Bytes(test.input)
gas := p.RequiredGas(in) gas := p.RequiredGas(in)
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
_, _, err := RunPrecompiledContract(nil, p, in, gas) _, _, err := RunPrecompiledContract(nil, p, in, gas, nil)
if err.Error() != test.expectedError.Error() { if err.Error() != test.expectedError.Error() {
t.Errorf("Expected error [%v], got [%v]", test.expectedError, err) t.Errorf("Expected error [%v], got [%v]", test.expectedError, err)
} }
@ -608,7 +608,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
bench.ResetTimer() bench.ResetTimer()
for i := 0; i < bench.N; i++ { for i := 0; i < bench.N; i++ {
copy(data, in) copy(data, in)
res, _, err = RunPrecompiledContract(nil, p, data, reqGas) res, _, err = RunPrecompiledContract(nil, p, data, reqGas, nil)
} }
bench.StopTimer() bench.StopTimer()
//Check if it is correct //Check if it is correct

View file

@ -19,6 +19,7 @@ package vm
import ( import (
"errors" "errors"
"fmt" "fmt"
"math"
) )
// List evm execution errors // List evm execution errors
@ -70,3 +71,122 @@ type ErrInvalidOpCode struct {
} }
func (e *ErrInvalidOpCode) Error() string { return fmt.Sprintf("invalid opcode: %s", e.opcode) } func (e *ErrInvalidOpCode) Error() string { return fmt.Sprintf("invalid opcode: %s", e.opcode) }
// rpcError is the same interface as the one defined in rpc/errors.go
// but we do not want to depend on rpc package here so we redefine it.
//
// It's used to ensure that the VMError implements the RPC error interface.
type rpcError interface {
Error() string // returns the message
ErrorCode() int // returns the code
}
var _ rpcError = (*VMError)(nil)
// VMError wraps a VM error with an additional stable error code. The error
// field is the original error that caused the VM error and must be one of the
// VM error defined at the top of this file.
//
// If the error is not one of the known error above, the error code will be
// set to VMErrorCodeUnknown.
type VMError struct {
error
code int
}
func VMErrorFromErr(err error) error {
if err == nil {
return nil
}
return &VMError{
error: err,
code: vmErrorCodeFromErr(err),
}
}
func (e *VMError) Error() string {
return e.error.Error()
}
func (e *VMError) Unwrap() error {
return e.error
}
func (e *VMError) ErrorCode() int {
return e.code
}
const (
// We start the error code at 1 so that we can use 0 later for some possible extension. There
// is no unspecified value for the code today because it should always be set to a valid value
// that could be VMErrorCodeUnknown if the error is not mapped to a known error code.
VMErrorCodeOutOfGas = 1 + iota
VMErrorCodeCodeStoreOutOfGas
VMErrorCodeDepth
VMErrorCodeInsufficientBalance
VMErrorCodeContractAddressCollision
VMErrorCodeExecutionReverted
VMErrorCodeMaxCodeSizeExceeded
VMErrorCodeInvalidJump
VMErrorCodeWriteProtection
VMErrorCodeReturnDataOutOfBounds
VMErrorCodeGasUintOverflow
VMErrorCodeInvalidCode
VMErrorCodeNonceUintOverflow
VMErrorCodeStackUnderflow
VMErrorCodeStackOverflow
VMErrorCodeInvalidOpCode
// VMErrorCodeUnknown explicitly marks an error as unknown, this is useful when error is converted
// from an actual `error` in which case if the mapping is not known, we can use this value to indicate that.
VMErrorCodeUnknown = math.MaxInt - 1
)
func vmErrorCodeFromErr(err error) int {
switch {
case errors.Is(err, ErrOutOfGas):
return VMErrorCodeOutOfGas
case errors.Is(err, ErrCodeStoreOutOfGas):
return VMErrorCodeCodeStoreOutOfGas
case errors.Is(err, ErrDepth):
return VMErrorCodeDepth
case errors.Is(err, ErrInsufficientBalance):
return VMErrorCodeInsufficientBalance
case errors.Is(err, ErrContractAddressCollision):
return VMErrorCodeContractAddressCollision
case errors.Is(err, ErrExecutionReverted):
return VMErrorCodeExecutionReverted
case errors.Is(err, ErrMaxCodeSizeExceeded):
return VMErrorCodeMaxCodeSizeExceeded
case errors.Is(err, ErrInvalidJump):
return VMErrorCodeInvalidJump
case errors.Is(err, ErrWriteProtection):
return VMErrorCodeWriteProtection
case errors.Is(err, ErrReturnDataOutOfBounds):
return VMErrorCodeReturnDataOutOfBounds
case errors.Is(err, ErrGasUintOverflow):
return VMErrorCodeGasUintOverflow
case errors.Is(err, ErrInvalidCode):
return VMErrorCodeInvalidCode
case errors.Is(err, ErrNonceUintOverflow):
return VMErrorCodeNonceUintOverflow
default:
// Dynamic errors
if v := (*ErrStackUnderflow)(nil); errors.As(err, &v) {
return VMErrorCodeStackUnderflow
}
if v := (*ErrStackOverflow)(nil); errors.As(err, &v) {
return VMErrorCodeStackOverflow
}
if v := (*ErrInvalidOpCode)(nil); errors.As(err, &v) {
return VMErrorCodeInvalidOpCode
}
return VMErrorCodeUnknown
}
}

View file

@ -17,11 +17,13 @@
package vm package vm
import ( import (
"errors"
"math/big" "math/big"
"sync/atomic" "sync/atomic"
"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate" "github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
@ -177,6 +179,13 @@ func (evm *EVM) Interpreter() *EVMInterpreter {
// the necessary steps to create accounts and reverses the state in case of an // the necessary steps to create accounts and reverses the state in case of an
// execution error or failed value transfer. // execution error or failed value transfer.
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
// Capture the tracer start/end events in debug mode
if evm.Config.Tracer != nil {
evm.captureBegin(evm.depth, CALL, caller.Address(), addr, input, gas, value)
defer func(startGas uint64) {
evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err)
}(gas)
}
// Fail if we're trying to execute above the call depth limit // Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth return nil, gas, ErrDepth
@ -187,44 +196,18 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
} }
snapshot := evm.StateDB.Snapshot() snapshot := evm.StateDB.Snapshot()
p, isPrecompile := evm.precompile(addr) p, isPrecompile := evm.precompile(addr)
debug := evm.Config.Tracer != nil
if !evm.StateDB.Exist(addr) { if !evm.StateDB.Exist(addr) {
if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 { if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 {
// Calling a non existing account, don't do anything, but ping the tracer // Calling a non-existing account, don't do anything.
if debug {
if evm.depth == 0 {
evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
evm.Config.Tracer.CaptureEnd(ret, 0, nil)
} else {
evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value)
evm.Config.Tracer.CaptureExit(ret, 0, nil)
}
}
return nil, gas, nil return nil, gas, nil
} }
evm.StateDB.CreateAccount(addr) evm.StateDB.CreateAccount(addr)
} }
evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value) evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value)
// Capture the tracer start/end events in debug mode
if debug {
if evm.depth == 0 {
evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
defer func(startGas uint64) { // Lazy evaluation of the parameters
evm.Config.Tracer.CaptureEnd(ret, startGas-gas, err)
}(gas)
} else {
// Handle tracer events for entering and exiting a call frame
evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value)
defer func(startGas uint64) {
evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
}(gas)
}
}
if isPrecompile { if isPrecompile {
ret, gas, err = RunPrecompiledContract(evm, p, input, gas) ret, gas, err = RunPrecompiledContract(evm, p, input, gas, evm.Config.Tracer)
} else { } else {
// Initialise a new contract and set the code that is to be used by the EVM. // Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only. // The contract is a scoped environment for this execution context only.
@ -242,11 +225,15 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
} }
} }
// When an error was returned by the EVM or when setting the creation code // When an error was returned by the EVM or when setting the creation code
// above we revert to the snapshot and consume any gas remaining. Additionally // above we revert to the snapshot and consume any gas remaining. Additionally,
// when we're in homestead this also counts for code storage gas errors. // when we're in homestead this also counts for code storage gas errors.
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
}
gas = 0 gas = 0
} }
// TODO: consider clearing up unused snapshots: // TODO: consider clearing up unused snapshots:
@ -264,6 +251,13 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
// CallCode differs from Call in the sense that it executes the given address' // CallCode differs from Call in the sense that it executes the given address'
// code with the caller as context. // code with the caller as context.
func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil {
evm.captureBegin(evm.depth, CALLCODE, caller.Address(), addr, input, gas, value)
defer func(startGas uint64) {
evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err)
}(gas)
}
// Fail if we're trying to execute above the call depth limit // Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth return nil, gas, ErrDepth
@ -277,17 +271,9 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
} }
var snapshot = evm.StateDB.Snapshot() var snapshot = evm.StateDB.Snapshot()
// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil {
evm.Config.Tracer.CaptureEnter(CALLCODE, caller.Address(), addr, input, gas, value)
defer func(startGas uint64) {
evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
}(gas)
}
// It is allowed to call precompiles, even via delegatecall // It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile { if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(evm, p, input, gas) ret, gas, err = RunPrecompiledContract(evm, p, input, gas, evm.Config.Tracer)
} else { } else {
addrCopy := addr addrCopy := addr
// Initialise a new contract and set the code that is to be used by the EVM. // Initialise a new contract and set the code that is to be used by the EVM.
@ -300,6 +286,10 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
}
gas = 0 gas = 0
} }
} }
@ -312,27 +302,26 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
// DelegateCall differs from CallCode in the sense that it executes the given address' // DelegateCall differs from CallCode in the sense that it executes the given address'
// code with the caller as context and the caller is set to the caller of the caller. // code with the caller as context and the caller is set to the caller of the caller.
func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) { func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth
}
var snapshot = evm.StateDB.Snapshot()
// Invoke tracer hooks that signal entering/exiting a call frame // Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil { if evm.Config.Tracer != nil {
// NOTE: caller must, at all times be a contract. It should never happen // NOTE: caller must, at all times be a contract. It should never happen
// that caller is something other than a Contract. // that caller is something other than a Contract.
parent := caller.(*Contract) parent := caller.(*Contract)
// DELEGATECALL inherits value from parent call // DELEGATECALL inherits value from parent call
evm.Config.Tracer.CaptureEnter(DELEGATECALL, caller.Address(), addr, input, gas, parent.value) evm.captureBegin(evm.depth, DELEGATECALL, caller.Address(), addr, input, gas, parent.value)
defer func(startGas uint64) { defer func(startGas uint64) {
evm.Config.Tracer.CaptureExit(ret, startGas-gas, err) evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err)
}(gas) }(gas)
} }
// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth
}
var snapshot = evm.StateDB.Snapshot()
// It is allowed to call precompiles, even via delegatecall // It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile { if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(evm, p, input, gas) ret, gas, err = RunPrecompiledContract(evm, p, input, gas, evm.Config.Tracer)
} else { } else {
addrCopy := addr addrCopy := addr
// Initialise a new contract and make initialise the delegate values // Initialise a new contract and make initialise the delegate values
@ -344,6 +333,9 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
}
gas = 0 gas = 0
} }
} }
@ -355,6 +347,13 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
// Opcodes that attempt to perform such modifications will result in exceptions // Opcodes that attempt to perform such modifications will result in exceptions
// instead of performing the modifications. // instead of performing the modifications.
func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) { func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil {
evm.captureBegin(evm.depth, STATICCALL, caller.Address(), addr, input, gas, nil)
defer func(startGas uint64) {
evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err)
}(gas)
}
// Fail if we're trying to execute above the call depth limit // Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth return nil, gas, ErrDepth
@ -371,19 +370,11 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
// but is the correct thing to do and matters on other networks, in tests, and potential // but is the correct thing to do and matters on other networks, in tests, and potential
// future scenarios // future scenarios
if evm.ChainConfig().IsTIPXDCXCancellationFee(evm.Context.BlockNumber) { if evm.ChainConfig().IsTIPXDCXCancellationFee(evm.Context.BlockNumber) {
evm.StateDB.AddBalance(addr, big0) evm.StateDB.AddBalance(addr, big0, tracing.BalanceChangeTouchAccount)
}
// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil {
evm.Config.Tracer.CaptureEnter(STATICCALL, caller.Address(), addr, input, gas, nil)
defer func(startGas uint64) {
evm.Config.Tracer.CaptureExit(ret, startGas-gas, err)
}(gas)
} }
if p, isPrecompile := evm.precompile(addr); isPrecompile { if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(evm, p, input, gas) ret, gas, err = RunPrecompiledContract(evm, p, input, gas, evm.Config.Tracer)
} else { } else {
// At this point, we use a copy of address. If we don't, the go compiler will // At this point, we use a copy of address. If we don't, the go compiler will
// leak the 'contract' to the outer scope, and make allocation for 'contract' // leak the 'contract' to the outer scope, and make allocation for 'contract'
@ -403,6 +394,10 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
}
gas = 0 gas = 0
} }
} }
@ -422,7 +417,13 @@ func (c *codeAndHash) Hash() common.Hash {
} }
// create creates a new contract using code as deployment code. // create creates a new contract using code as deployment code.
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address, typ OpCode) ([]byte, common.Address, uint64, error) { func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas uint64, err error) {
if evm.Config.Tracer != nil {
evm.captureBegin(evm.depth, typ, caller.Address(), address, codeAndHash.code, gas, value)
defer func(startGas uint64) {
evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err)
}(gas)
}
// Depth check execution. Fail if we're trying to execute above the // Depth check execution. Fail if we're trying to execute above the
// limit. // limit.
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
@ -444,6 +445,10 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
// Ensure there's no existing contract already at the designated address // Ensure there's no existing contract already at the designated address
contractHash := evm.StateDB.GetCodeHash(address) contractHash := evm.StateDB.GetCodeHash(address)
if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) { if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) {
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
}
return nil, common.Address{}, 0, ErrContractAddressCollision return nil, common.Address{}, 0, ErrContractAddressCollision
} }
// Create a new account on the state // Create a new account on the state
@ -459,15 +464,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
contract := NewContract(caller, AccountRef(address), value, gas) contract := NewContract(caller, AccountRef(address), value, gas)
contract.SetCodeOptionalHash(&address, codeAndHash) contract.SetCodeOptionalHash(&address, codeAndHash)
if evm.Config.Tracer != nil { ret, err = evm.interpreter.Run(contract, nil, false)
if evm.depth == 0 {
evm.Config.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value)
} else {
evm.Config.Tracer.CaptureEnter(typ, caller.Address(), address, codeAndHash.code, gas, value)
}
}
ret, err := evm.interpreter.Run(contract, nil, false)
// Check whether the max code size has been exceeded, assign err if the case. // Check whether the max code size has been exceeded, assign err if the case.
if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize { if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize {
@ -485,7 +482,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
// by the error checking condition below. // by the error checking condition below.
if err == nil { if err == nil {
createDataGas := uint64(len(ret)) * params.CreateDataGas createDataGas := uint64(len(ret)) * params.CreateDataGas
if contract.UseGas(createDataGas) { if contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
evm.StateDB.SetCode(address, ret) evm.StateDB.SetCode(address, ret)
} else { } else {
err = ErrCodeStoreOutOfGas err = ErrCodeStoreOutOfGas
@ -493,22 +490,15 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
} }
// When an error was returned by the EVM or when setting the creation code // When an error was returned by the EVM or when setting the creation code
// above we revert to the snapshot and consume any gas remaining. Additionally // above we revert to the snapshot and consume any gas remaining. Additionally,
// when we're in homestead this also counts for code storage gas errors. // when we're in homestead this also counts for code storage gas errors.
if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
contract.UseGas(contract.Gas) contract.UseGas(contract.Gas, evm.Config.Tracer, tracing.GasChangeCallFailedExecution)
} }
} }
if evm.Config.Tracer != nil {
if evm.depth == 0 {
evm.Config.Tracer.CaptureEnd(ret, gas-contract.Gas, err)
} else {
evm.Config.Tracer.CaptureExit(ret, gas-contract.Gas, err)
}
}
return ret, address, contract.Gas, err return ret, address, contract.Gas, err
} }
@ -530,3 +520,44 @@ func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *
// ChainConfig returns the environment's chain configuration // ChainConfig returns the environment's chain configuration
func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig } func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig }
func (evm *EVM) captureBegin(depth int, typ OpCode, from common.Address, to common.Address, input []byte, startGas uint64, value *big.Int) {
tracer := evm.Config.Tracer
if tracer.OnEnter != nil {
tracer.OnEnter(depth, byte(typ), from, to, input, startGas, value)
}
if tracer.OnGasChange != nil {
tracer.OnGasChange(0, startGas, tracing.GasChangeCallInitialBalance)
}
}
func (evm *EVM) captureEnd(depth int, startGas uint64, leftOverGas uint64, ret []byte, err error) {
tracer := evm.Config.Tracer
if leftOverGas != 0 && tracer.OnGasChange != nil {
tracer.OnGasChange(leftOverGas, 0, tracing.GasChangeCallLeftOverReturned)
}
var reverted bool
if err != nil {
reverted = true
}
if !evm.chainRules.IsHomestead && errors.Is(err, ErrCodeStoreOutOfGas) {
reverted = false
}
if tracer.OnExit != nil {
tracer.OnExit(depth, ret, startGas-leftOverGas, VMErrorFromErr(err), reverted)
}
}
// GetVMContext provides context about the block being executed as well as state
// to the tracers.
func (evm *EVM) GetVMContext() *tracing.VMContext {
return &tracing.VMContext{
Coinbase: evm.Context.Coinbase,
BlockNumber: evm.Context.BlockNumber,
Time: evm.Context.Time,
Random: evm.Context.Random,
GasPrice: evm.TxContext.GasPrice,
ChainConfig: evm.ChainConfig(),
StateDB: evm.StateDB,
}
}

View file

@ -20,6 +20,7 @@ import (
"math" "math"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
"github.com/holiman/uint256" "github.com/holiman/uint256"
@ -244,7 +245,6 @@ func opKeccak256(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
if evm.Config.EnablePreimageRecording { if evm.Config.EnablePreimageRecording {
evm.StateDB.AddPreimage(interpreter.hasherBuf, data) evm.StateDB.AddPreimage(interpreter.hasherBuf, data)
} }
size.SetBytes(interpreter.hasherBuf[:]) size.SetBytes(interpreter.hasherBuf[:])
return nil, nil return nil, nil
} }
@ -600,13 +600,13 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
// reuse size int for stackvalue // reuse size int for stackvalue
stackvalue := size stackvalue := size
scope.Contract.UseGas(gas) scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation)
//TODO: use uint256.Int instead of converting with toBig()
// TODO(daniel): use uint256.Int instead of converting with toBig()
var bigVal = big0 var bigVal = big0
if !value.IsZero() { if !value.IsZero() {
bigVal = value.ToBig() bigVal = value.ToBig()
} }
res, addr, returnGas, suberr := interpreter.evm.Create(scope.Contract, input, gas, bigVal) res, addr, returnGas, suberr := interpreter.evm.Create(scope.Contract, input, gas, bigVal)
// Push item on the stack based on the returned error. If the ruleset is // Push item on the stack based on the returned error. If the ruleset is
// homestead we must check for CodeStoreOutOfGasError (homestead only // homestead we must check for CodeStoreOutOfGasError (homestead only
@ -620,7 +620,8 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
stackvalue.SetBytes(addr.Bytes()) stackvalue.SetBytes(addr.Bytes())
} }
scope.Stack.push(&stackvalue) scope.Stack.push(&stackvalue)
scope.Contract.Gas += returnGas
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
if suberr == ErrExecutionReverted { if suberr == ErrExecutionReverted {
interpreter.returnData = res // set REVERT data to return data buffer interpreter.returnData = res // set REVERT data to return data buffer
@ -643,10 +644,10 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
) )
// Apply EIP150 // Apply EIP150
gas -= gas / 64 gas -= gas / 64
scope.Contract.UseGas(gas) scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
// reuse size int for stackvalue // reuse size int for stackvalue
stackvalue := size stackvalue := size
//TODO: use uint256.Int instead of converting with toBig() // TODO(daniel): use uint256.Int instead of converting with toBig()
bigEndowment := big0 bigEndowment := big0
if !endowment.IsZero() { if !endowment.IsZero() {
bigEndowment = endowment.ToBig() bigEndowment = endowment.ToBig()
@ -660,7 +661,8 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
stackvalue.SetBytes(addr.Bytes()) stackvalue.SetBytes(addr.Bytes())
} }
scope.Stack.push(&stackvalue) scope.Stack.push(&stackvalue)
scope.Contract.Gas += returnGas
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
if suberr == ErrExecutionReverted { if suberr == ErrExecutionReverted {
interpreter.returnData = res // set REVERT data to return data buffer interpreter.returnData = res // set REVERT data to return data buffer
@ -687,7 +689,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
} }
var bigVal = big0 var bigVal = big0
//TODO: use uint256.Int instead of converting with toBig() // TODO(daniel): use uint256.Int instead of converting with toBig()
// By using big0 here, we save an alloc for the most common case (non-ether-transferring contract calls), // By using big0 here, we save an alloc for the most common case (non-ether-transferring contract calls),
// but it would make more sense to extend the usage of uint256.Int // but it would make more sense to extend the usage of uint256.Int
if !value.IsZero() { if !value.IsZero() {
@ -706,7 +708,8 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
if err == nil || err == ErrExecutionReverted { if err == nil || err == ErrExecutionReverted {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
} }
scope.Contract.Gas += returnGas
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
interpreter.returnData = ret interpreter.returnData = ret
return ret, nil return ret, nil
@ -724,7 +727,7 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
// Get arguments from the memory. // Get arguments from the memory.
args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))
//TODO: use uint256.Int instead of converting with toBig() // TODO(daniel): use uint256.Int instead of converting with toBig()
var bigVal = big0 var bigVal = big0
if !value.IsZero() { if !value.IsZero() {
gas += params.CallStipend gas += params.CallStipend
@ -741,7 +744,8 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
if err == nil || err == ErrExecutionReverted { if err == nil || err == ErrExecutionReverted {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
} }
scope.Contract.Gas += returnGas
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
interpreter.returnData = ret interpreter.returnData = ret
return ret, nil return ret, nil
@ -769,7 +773,8 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext
if err == nil || err == ErrExecutionReverted { if err == nil || err == ErrExecutionReverted {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
} }
scope.Contract.Gas += returnGas
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
interpreter.returnData = ret interpreter.returnData = ret
return ret, nil return ret, nil
@ -797,7 +802,8 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
if err == nil || err == ErrExecutionReverted { if err == nil || err == ErrExecutionReverted {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
} }
scope.Contract.Gas += returnGas
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
interpreter.returnData = ret interpreter.returnData = ret
return ret, nil return ret, nil
@ -832,11 +838,15 @@ func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext
} }
beneficiary := scope.Stack.pop() beneficiary := scope.Stack.pop()
balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address())
interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance) interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, tracing.BalanceIncreaseSelfdestruct)
interpreter.evm.StateDB.SelfDestruct(scope.Contract.Address()) interpreter.evm.StateDB.SelfDestruct(scope.Contract.Address())
if tracer := interpreter.evm.Config.Tracer; tracer != nil { if tracer := interpreter.evm.Config.Tracer; tracer != nil {
tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) if tracer.OnEnter != nil {
tracer.CaptureExit([]byte{}, 0, nil) tracer.OnEnter(interpreter.evm.depth, byte(SELFDESTRUCT), scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance)
}
if tracer.OnExit != nil {
tracer.OnExit(interpreter.evm.depth, []byte{}, 0, nil, false)
}
} }
return nil, errStopToken return nil, errStopToken
} }
@ -847,12 +857,16 @@ func opSelfdestruct6780(pc *uint64, interpreter *EVMInterpreter, scope *ScopeCon
} }
beneficiary := scope.Stack.pop() beneficiary := scope.Stack.pop()
balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address())
interpreter.evm.StateDB.SubBalance(scope.Contract.Address(), balance) interpreter.evm.StateDB.SubBalance(scope.Contract.Address(), balance, tracing.BalanceDecreaseSelfdestruct)
interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance) interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, tracing.BalanceIncreaseSelfdestruct)
interpreter.evm.StateDB.Selfdestruct6780(scope.Contract.Address()) interpreter.evm.StateDB.Selfdestruct6780(scope.Contract.Address())
if tracer := interpreter.evm.Config.Tracer; tracer != nil { if tracer := interpreter.evm.Config.Tracer; tracer != nil {
tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) if tracer.OnEnter != nil {
tracer.CaptureExit([]byte{}, 0, nil) tracer.OnEnter(interpreter.evm.depth, byte(SELFDESTRUCT), scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance)
}
if tracer.OnExit != nil {
tracer.OnExit(interpreter.evm.depth, []byte{}, 0, nil, false)
}
} }
return nil, errStopToken return nil, errStopToken
} }

View file

@ -20,6 +20,7 @@ import (
"math/big" "math/big"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
) )
@ -28,8 +29,8 @@ import (
type StateDB interface { type StateDB interface {
CreateAccount(common.Address) CreateAccount(common.Address)
SubBalance(common.Address, *big.Int) SubBalance(common.Address, *big.Int, tracing.BalanceChangeReason)
AddBalance(common.Address, *big.Int) AddBalance(common.Address, *big.Int, tracing.BalanceChangeReason)
GetBalance(common.Address) *big.Int GetBalance(common.Address) *big.Int
GetNonce(common.Address) uint64 GetNonce(common.Address) uint64

View file

@ -17,18 +17,22 @@
package vm package vm
import ( import (
"math/big"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/math" "github.com/XinFinOrg/XDPoSChain/common/math"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/holiman/uint256"
) )
// Config are the configuration options for the Interpreter // Config are the configuration options for the Interpreter
type Config struct { type Config struct {
Tracer EVMLogger // Opcode logger Tracer *tracing.Hooks
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
ExtraEips []int // Additional EIPS that are to be enabled ExtraEips []int // Additional EIPS that are to be enabled
} }
// ScopeContext contains the things that are per-call, such as stack and memory, // ScopeContext contains the things that are per-call, such as stack and memory,
@ -39,6 +43,45 @@ type ScopeContext struct {
Contract *Contract Contract *Contract
} }
// MemoryData returns the underlying memory slice. Callers must not modify the contents
// of the returned data.
func (ctx *ScopeContext) MemoryData() []byte {
if ctx.Memory == nil {
return nil
}
return ctx.Memory.Data()
}
// MemoryData returns the stack data. Callers must not modify the contents
// of the returned data.
func (ctx *ScopeContext) StackData() []uint256.Int {
if ctx.Stack == nil {
return nil
}
return ctx.Stack.Data()
}
// Caller returns the current caller.
func (ctx *ScopeContext) Caller() common.Address {
return ctx.Contract.Caller()
}
// Address returns the address where this scope of execution is taking place.
func (ctx *ScopeContext) Address() common.Address {
return ctx.Contract.Address()
}
// CallValue returns the value supplied with this call.
func (ctx *ScopeContext) CallValue() *big.Int {
return ctx.Contract.Value()
}
// CallInput returns the input/calldata with this call. Callers must not modify
// the contents of the returned data.
func (ctx *ScopeContext) CallInput() []byte {
return ctx.Contract.Input
}
// EVMInterpreter represents an EVM interpreter // EVMInterpreter represents an EVM interpreter
type EVMInterpreter struct { type EVMInterpreter struct {
evm *EVM evm *EVM
@ -148,8 +191,8 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
res []byte // result of the opcode execution function res []byte // result of the opcode execution function
debug = in.evm.Config.Tracer != nil debug = in.evm.Config.Tracer != nil
) )
// Don't move this deferred function, it's placed before the capturestate-deferred method, // Don't move this deferred function, it's placed before the OnOpcode-deferred method,
// so that it gets executed _after_: the capturestate needs the stacks before // so that it gets executed _after_: the OnOpcode needs the stacks before
// they are returned to the pools // they are returned to the pools
defer func() { defer func() {
returnStack(stack) returnStack(stack)
@ -157,13 +200,15 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
contract.Input = input contract.Input = input
if debug { if debug {
defer func() { defer func() { // this deferred method handles exit-with-error
if err != nil { if err == nil {
if !logged { return
in.evm.Config.Tracer.CaptureState(pcCopy, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err) }
} else { if !logged && in.evm.Config.Tracer.OnOpcode != nil {
in.evm.Config.Tracer.CaptureFault(pcCopy, op, gasCopy, cost, callContext, in.evm.depth, err) in.evm.Config.Tracer.OnOpcode(pcCopy, byte(op), gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err))
} }
if logged && in.evm.Config.Tracer.OnFault != nil {
in.evm.Config.Tracer.OnFault(pcCopy, byte(op), gasCopy, cost, callContext, in.evm.depth, VMErrorFromErr(err))
} }
}() }()
} }
@ -188,9 +233,10 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
} else if sLen > operation.maxStack { } else if sLen > operation.maxStack {
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack} return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
} }
if !contract.UseGas(cost) { if !contract.UseGas(cost, in.evm.Config.Tracer, tracing.GasChangeIgnored) {
return nil, ErrOutOfGas return nil, ErrOutOfGas
} }
if operation.dynamicGas != nil { if operation.dynamicGas != nil {
// All ops with a dynamic memory usage also has a dynamic gas cost. // All ops with a dynamic memory usage also has a dynamic gas cost.
var memorySize uint64 var memorySize uint64
@ -214,22 +260,33 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
var dynamicCost uint64 var dynamicCost uint64
dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize) dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
cost += dynamicCost // for tracing cost += dynamicCost // for tracing
if err != nil || !contract.UseGas(dynamicCost) { if err != nil || !contract.UseGas(dynamicCost, in.evm.Config.Tracer, tracing.GasChangeIgnored) {
return nil, ErrOutOfGas return nil, ErrOutOfGas
} }
// Do tracing before memory expansion // Do tracing before memory expansion
if debug { if debug {
in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err) if in.evm.Config.Tracer.OnGasChange != nil {
logged = true in.evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, tracing.GasChangeCallOpCode)
}
if in.evm.Config.Tracer.OnOpcode != nil {
in.evm.Config.Tracer.OnOpcode(pc, byte(op), gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err))
logged = true
}
} }
if memorySize > 0 { if memorySize > 0 {
mem.Resize(memorySize) mem.Resize(memorySize)
} }
} else if debug { } else if debug {
in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err) if in.evm.Config.Tracer.OnGasChange != nil {
logged = true in.evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, tracing.GasChangeCallOpCode)
}
if in.evm.Config.Tracer.OnOpcode != nil {
in.evm.Config.Tracer.OnOpcode(pc, byte(op), gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err))
logged = true
}
} }
// execute the operation // execute the operation
res, err = operation.execute(&pc, in, callContext) res, err = operation.execute(&pc, in, callContext)
if err != nil { if err != nil {

View file

@ -1,43 +0,0 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package vm
import (
"math/big"
"github.com/XinFinOrg/XDPoSChain/common"
)
// EVMLogger is used to collect execution traces from an EVM transaction
// execution. CaptureState is called for each step of the VM with the
// current VM state.
// Note that reference types are actual VM data structures; make copies
// if you need to retain them beyond the current call.
type EVMLogger interface {
// Transaction level
CaptureTxStart(gasLimit uint64)
CaptureTxEnd(restGas uint64)
// Top call frame
CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int)
CaptureEnd(output []byte, gasUsed uint64, err error)
// Rest of call frames
CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int)
CaptureExit(output []byte, gasUsed uint64, err error)
// Opcode level
CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error)
}

View file

@ -21,6 +21,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/math" "github.com/XinFinOrg/XDPoSChain/common/math"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
) )
@ -169,7 +170,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc {
evm.StateDB.AddAddressToAccessList(addr) evm.StateDB.AddAddressToAccessList(addr)
// Charge the remaining difference here already, to correctly calculate available // Charge the remaining difference here already, to correctly calculate available
// gas for call // gas for call
if !contract.UseGas(coldCost) { if !contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
return 0, ErrOutOfGas return 0, ErrOutOfGas
} }
} }

View file

@ -119,6 +119,9 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
sender = vm.AccountRef(cfg.Origin) sender = vm.AccountRef(cfg.Origin)
rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber) rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber)
) )
if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxStart != nil {
cfg.EVMConfig.Tracer.OnTxStart(vmenv.GetVMContext(), types.NewTx(&types.LegacyTx{To: &address, Data: input, Value: cfg.Value, Gas: cfg.GasLimit}), cfg.Origin)
}
// Execute the preparatory steps for state transition which includes: // Execute the preparatory steps for state transition which includes:
// - prepare accessList(post-berlin) // - prepare accessList(post-berlin)
// - reset transient storage(eip 1153) // - reset transient storage(eip 1153)
@ -154,6 +157,9 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
sender = vm.AccountRef(cfg.Origin) sender = vm.AccountRef(cfg.Origin)
rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber) rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber)
) )
if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxStart != nil {
cfg.EVMConfig.Tracer.OnTxStart(vmenv.GetVMContext(), types.NewTx(&types.LegacyTx{Data: input, Value: cfg.Value, Gas: cfg.GasLimit}), cfg.Origin)
}
// Execute the preparatory steps for state transition which includes: // Execute the preparatory steps for state transition which includes:
// - prepare accessList(post-berlin) // - prepare accessList(post-berlin)
// - reset transient storage(eip 1153) // - reset transient storage(eip 1153)
@ -183,6 +189,9 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er
statedb = cfg.State statedb = cfg.State
rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber) rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber)
) )
if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxStart != nil {
cfg.EVMConfig.Tracer.OnTxStart(vmenv.GetVMContext(), types.NewTx(&types.LegacyTx{To: &address, Data: input, Value: cfg.Value, Gas: cfg.GasLimit}), cfg.Origin)
}
// Execute the preparatory steps for state transition which includes: // Execute the preparatory steps for state transition which includes:
// - prepare accessList(post-berlin) // - prepare accessList(post-berlin)
// - reset transient storage(eip 1153) // - reset transient storage(eip 1153)

View file

@ -32,6 +32,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/eth/tracers"
"github.com/XinFinOrg/XDPoSChain/eth/tracers/logger" "github.com/XinFinOrg/XDPoSChain/eth/tracers/logger"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
@ -511,7 +512,7 @@ func TestEip2929Cases(t *testing.T) {
code, ops) code, ops)
Execute(code, nil, &Config{ Execute(code, nil, &Config{
EVMConfig: vm.Config{ EVMConfig: vm.Config{
Tracer: logger.NewMarkdownLogger(nil, os.Stdout), Tracer: logger.NewMarkdownLogger(nil, os.Stdout).Hooks(),
ExtraEips: []int{2929}, ExtraEips: []int{2929},
}, },
}) })
@ -664,7 +665,7 @@ func TestColdAccountAccessCost(t *testing.T) {
tracer := logger.NewStructLogger(nil) tracer := logger.NewStructLogger(nil)
Execute(tc.code, nil, &Config{ Execute(tc.code, nil, &Config{
EVMConfig: vm.Config{ EVMConfig: vm.Config{
Tracer: tracer, Tracer: tracer.Hooks(),
}, },
}) })
have := tracer.StructLogs()[tc.step].GasCost have := tracer.StructLogs()[tc.step].GasCost
@ -676,3 +677,226 @@ func TestColdAccountAccessCost(t *testing.T) {
} }
} }
} }
func TestRuntimeJSTracer(t *testing.T) {
jsTracers := []string{
`{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, steps:0,
step: function() { this.steps++},
fault: function() {},
result: function() {
return [this.enters, this.exits,this.enterGas,this.gasUsed, this.steps].join(",")
},
enter: function(frame) {
this.enters++;
this.enterGas = frame.getGas();
},
exit: function(res) {
this.exits++;
this.gasUsed = res.getGasUsed();
}}`,
`{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, steps:0,
fault: function() {},
result: function() {
return [this.enters, this.exits,this.enterGas,this.gasUsed, this.steps].join(",")
},
enter: function(frame) {
this.enters++;
this.enterGas = frame.getGas();
},
exit: function(res) {
this.exits++;
this.gasUsed = res.getGasUsed();
}}`}
tests := []struct {
code []byte
// One result per tracer
results []string
}{
{
// CREATE
code: []byte{
// Store initcode in memory at 0x00 (5 bytes left-padded to 32 bytes)
byte(vm.PUSH5),
// Init code: PUSH1 0, PUSH1 0, RETURN (3 steps)
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN),
byte(vm.PUSH1), 0,
byte(vm.MSTORE),
// length, offset, value
byte(vm.PUSH1), 5, byte(vm.PUSH1), 27, byte(vm.PUSH1), 0,
byte(vm.CREATE),
byte(vm.POP),
},
results: []string{`"1,1,952853,6,12"`, `"1,1,952853,6,0"`},
},
{
// CREATE2
code: []byte{
// Store initcode in memory at 0x00 (5 bytes left-padded to 32 bytes)
byte(vm.PUSH5),
// Init code: PUSH1 0, PUSH1 0, RETURN (3 steps)
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN),
byte(vm.PUSH1), 0,
byte(vm.MSTORE),
// salt, length, offset, value
byte(vm.PUSH1), 1, byte(vm.PUSH1), 5, byte(vm.PUSH1), 27, byte(vm.PUSH1), 0,
byte(vm.CREATE2),
byte(vm.POP),
},
results: []string{`"1,1,952844,6,13"`, `"1,1,952844,6,0"`},
},
{
// CALL
code: []byte{
// outsize, outoffset, insize, inoffset
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0,
byte(vm.PUSH1), 0, // value
byte(vm.PUSH1), 0xbb, //address
byte(vm.GAS), // gas
byte(vm.CALL),
byte(vm.POP),
},
results: []string{`"1,1,981796,6,13"`, `"1,1,981796,6,0"`},
},
{
// CALLCODE
code: []byte{
// outsize, outoffset, insize, inoffset
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0,
byte(vm.PUSH1), 0, // value
byte(vm.PUSH1), 0xcc, //address
byte(vm.GAS), // gas
byte(vm.CALLCODE),
byte(vm.POP),
},
results: []string{`"1,1,981796,6,13"`, `"1,1,981796,6,0"`},
},
{
// STATICCALL
code: []byte{
// outsize, outoffset, insize, inoffset
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0,
byte(vm.PUSH1), 0xdd, //address
byte(vm.GAS), // gas
byte(vm.STATICCALL),
byte(vm.POP),
},
results: []string{`"1,1,981799,6,12"`, `"1,1,981799,6,0"`},
},
{
// DELEGATECALL
code: []byte{
// outsize, outoffset, insize, inoffset
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0,
byte(vm.PUSH1), 0xee, //address
byte(vm.GAS), // gas
byte(vm.DELEGATECALL),
byte(vm.POP),
},
results: []string{`"1,1,981799,6,12"`, `"1,1,981799,6,0"`},
},
{
// CALL self-destructing contract
code: []byte{
// outsize, outoffset, insize, inoffset
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0,
byte(vm.PUSH1), 0, // value
byte(vm.PUSH1), 0xff, //address
byte(vm.GAS), // gas
byte(vm.CALL),
byte(vm.POP),
},
results: []string{`"2,2,0,5003,12"`, `"2,2,0,5003,0"`},
},
}
calleeCode := []byte{
byte(vm.PUSH1), 0,
byte(vm.PUSH1), 0,
byte(vm.RETURN),
}
suicideCode := []byte{
byte(vm.PUSH1), 0xaa,
byte(vm.SELFDESTRUCT),
}
main := common.HexToAddress("0xaa")
for i, jsTracer := range jsTracers {
for j, tc := range tests {
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
statedb.SetCode(main, tc.code)
statedb.SetCode(common.HexToAddress("0xbb"), calleeCode)
statedb.SetCode(common.HexToAddress("0xcc"), calleeCode)
statedb.SetCode(common.HexToAddress("0xdd"), calleeCode)
statedb.SetCode(common.HexToAddress("0xee"), calleeCode)
statedb.SetCode(common.HexToAddress("0xff"), suicideCode)
tracer, err := tracers.DefaultDirectory.New(jsTracer, new(tracers.Context), nil)
if err != nil {
t.Fatal(err)
}
_, _, err = Call(main, nil, &Config{
GasLimit: 1000000,
State: statedb,
EVMConfig: vm.Config{
Tracer: tracer.Hooks,
}})
if err != nil {
t.Fatal("didn't expect error", err)
}
res, err := tracer.GetResult()
if err != nil {
t.Fatal(err)
}
if have, want := string(res), tc.results[i]; have != want {
t.Errorf("wrong result for tracer %d testcase %d, have \n%v\nwant\n%v\n", i, j, have, want)
}
}
}
}
func TestJSTracerCreateTx(t *testing.T) {
jsTracer := `
{enters: 0, exits: 0,
step: function() {},
fault: function() {},
result: function() { return [this.enters, this.exits].join(",") },
enter: function(frame) { this.enters++ },
exit: function(res) { this.exits++ }}`
code := []byte{byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN)}
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()))
tracer, err := tracers.DefaultDirectory.New(jsTracer, new(tracers.Context), nil)
if err != nil {
t.Fatal(err)
}
_, _, _, err = Create(code, &Config{
State: statedb,
EVMConfig: vm.Config{
Tracer: tracer.Hooks,
}})
if err != nil {
t.Fatal(err)
}
res, err := tracer.GetResult()
if err != nil {
t.Fatal(err)
}
if have, want := string(res), `"0,0"`; have != want {
t.Errorf("wrong result for tracer, have \n%v\nwant\n%v\n", have, want)
}
}
func BenchmarkTracerStepVsCallFrame(b *testing.B) {
// Simply pushes and pops some values in a loop
code := []byte{
byte(vm.JUMPDEST),
byte(vm.PUSH1), 0,
byte(vm.PUSH1), 0,
byte(vm.POP),
byte(vm.POP),
byte(vm.PUSH1), 0, // jumpdestination
byte(vm.JUMP),
}
benchmarkNonModifyingCode(10000000, code, "tracer-step-10M", b)
benchmarkNonModifyingCode(10000000, code, "tracer-call-frame-10M", b)
}

View file

@ -38,6 +38,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/bloombits" "github.com/XinFinOrg/XDPoSChain/core/bloombits"
"github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/txpool" "github.com/XinFinOrg/XDPoSChain/core/txpool"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
@ -262,7 +263,7 @@ func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *st
if vmConfig == nil { if vmConfig == nil {
vmConfig = b.eth.blockchain.GetVMConfig() vmConfig = b.eth.blockchain.GetVMConfig()
} }
state.SetBalance(msg.From, math.MaxBig256) state.SetBalance(msg.From, math.MaxBig256, tracing.BalanceChangeUnspecified)
txContext := core.NewEVMTxContext(msg) txContext := core.NewEVMTxContext(msg)
var context vm.BlockContext var context vm.BlockContext
if blockCtx != nil { if blockCtx != nil {
@ -423,7 +424,7 @@ func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, re
return b.eth.StateAtBlock(ctx, block, reexec, base, readOnly, preferDisk) return b.eth.StateAtBlock(ctx, block, reexec, base, readOnly, preferDisk)
} }
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) { func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
return b.eth.stateAtTransaction(ctx, block, txIndex, reexec) return b.eth.stateAtTransaction(ctx, block, txIndex, reexec)
} }

View file

@ -27,6 +27,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/trie" "github.com/XinFinOrg/XDPoSChain/trie"
@ -65,35 +66,37 @@ func (h resultHash) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h resultHash) Less(i, j int) bool { return bytes.Compare(h[i].Bytes(), h[j].Bytes()) < 0 } func (h resultHash) Less(i, j int) bool { return bytes.Compare(h[i].Bytes(), h[j].Bytes()) < 0 }
func TestAccountRange(t *testing.T) { func TestAccountRange(t *testing.T) {
t.Parallel()
var ( var (
statedb = state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), &trie.Config{Preimages: true}) statedb = state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), &trie.Config{Preimages: true})
state, _ = state.New(common.Hash{}, statedb) sdb, _ = state.New(common.Hash{}, statedb)
addrs = [AccountRangeMaxResults * 2]common.Address{} addrs = [AccountRangeMaxResults * 2]common.Address{}
m = map[common.Address]bool{} m = map[common.Address]bool{}
) )
for i := range addrs { for i := range addrs {
hash := common.HexToHash(fmt.Sprintf("%x", i)) hash := common.HexToHash(fmt.Sprintf("%x", i))
addr := common.BytesToAddress(crypto.Keccak256Hash(hash.Bytes()).Bytes()) addr := common.BytesToAddress(crypto.Keccak256Hash(hash.Bytes()).Bytes())
addrs[i] = addr addrs[i] = addr
state.SetBalance(addrs[i], big.NewInt(1)) sdb.SetBalance(addrs[i], big.NewInt(1), tracing.BalanceChangeUnspecified)
if _, ok := m[addr]; ok { if _, ok := m[addr]; ok {
t.Fatalf("bad") t.Fatalf("bad")
} else { } else {
m[addr] = true m[addr] = true
} }
} }
state.Commit(true) sdb.Commit(true)
root := state.IntermediateRoot(true) root := sdb.IntermediateRoot(true)
trie, err := statedb.OpenTrie(root) trie, err := statedb.OpenTrie(root)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
accountRangeTest(t, &trie, state, common.Hash{}, AccountRangeMaxResults/2, AccountRangeMaxResults/2) accountRangeTest(t, &trie, sdb, common.Hash{}, AccountRangeMaxResults/2, AccountRangeMaxResults/2)
// test pagination // test pagination
firstResult := accountRangeTest(t, &trie, state, common.Hash{}, AccountRangeMaxResults, AccountRangeMaxResults) firstResult := accountRangeTest(t, &trie, sdb, common.Hash{}, AccountRangeMaxResults, AccountRangeMaxResults)
secondResult := accountRangeTest(t, &trie, state, common.BytesToHash(firstResult.Next), AccountRangeMaxResults, AccountRangeMaxResults) secondResult := accountRangeTest(t, &trie, sdb, common.BytesToHash(firstResult.Next), AccountRangeMaxResults, AccountRangeMaxResults)
hList := make(resultHash, 0) hList := make(resultHash, 0)
for addr1 := range firstResult.Accounts { for addr1 := range firstResult.Accounts {
@ -111,7 +114,7 @@ func TestAccountRange(t *testing.T) {
// set and get an even split between the first and second sets. // set and get an even split between the first and second sets.
sort.Sort(hList) sort.Sort(hList)
middleH := hList[AccountRangeMaxResults/2] middleH := hList[AccountRangeMaxResults/2]
middleResult := accountRangeTest(t, &trie, state, middleH, AccountRangeMaxResults, AccountRangeMaxResults) middleResult := accountRangeTest(t, &trie, sdb, middleH, AccountRangeMaxResults, AccountRangeMaxResults)
missing, infirst, insecond := 0, 0, 0 missing, infirst, insecond := 0, 0, 0
for h := range middleResult.Accounts { for h := range middleResult.Accounts {
if _, ok := firstResult.Accounts[h]; ok { if _, ok := firstResult.Accounts[h]; ok {

View file

@ -18,6 +18,7 @@
package eth package eth
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
@ -47,6 +48,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/eth/filters" "github.com/XinFinOrg/XDPoSChain/eth/filters"
"github.com/XinFinOrg/XDPoSChain/eth/gasprice" "github.com/XinFinOrg/XDPoSChain/eth/gasprice"
"github.com/XinFinOrg/XDPoSChain/eth/hooks" "github.com/XinFinOrg/XDPoSChain/eth/hooks"
"github.com/XinFinOrg/XDPoSChain/eth/tracers"
"github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/event" "github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/internal/ethapi" "github.com/XinFinOrg/XDPoSChain/internal/ethapi"
@ -184,6 +186,17 @@ func New(stack *node.Node, config *ethconfig.Config, XDCXServ *XDCx.XDCX, lendin
Preimages: config.Preimages, Preimages: config.Preimages,
} }
) )
if config.VMTrace != "" {
var traceConfig json.RawMessage
if config.VMTraceConfig != "" {
traceConfig = json.RawMessage(config.VMTraceConfig)
}
t, err := tracers.LiveDirectory.New(config.VMTrace, traceConfig)
if err != nil {
return nil, fmt.Errorf("Failed to create tracer %s: %v", config.VMTrace, err)
}
vmConfig.Tracer = t
}
if eth.chainConfig.XDPoS != nil { if eth.chainConfig.XDPoS != nil {
c := eth.engine.(*XDPoS.XDPoS) c := eth.engine.(*XDPoS.XDPoS)
c.GetXDCXService = func() utils.TradingService { c.GetXDCXService = func() utils.TradingService {

View file

@ -105,6 +105,10 @@ type Config struct {
// Enables tracking of SHA3 preimages in the VM // Enables tracking of SHA3 preimages in the VM
EnablePreimageRecording bool EnablePreimageRecording bool
// Enables VM tracing
VMTrace string
VMTraceConfig string
// RPCGasCap is the global gas cap for eth-call variants. // RPCGasCap is the global gas cap for eth-call variants.
RPCGasCap uint64 RPCGasCap uint64

View file

@ -16,6 +16,7 @@ import (
contractValidator "github.com/XinFinOrg/XDPoSChain/contracts/validator/contract" contractValidator "github.com/XinFinOrg/XDPoSChain/contracts/validator/contract"
"github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/eth/util" "github.com/XinFinOrg/XDPoSChain/eth/util"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
@ -293,7 +294,7 @@ func AttachConsensusV1Hooks(adaptor *XDPoS.XDPoS, bc *core.BlockChain, chainConf
} }
if len(rewards) > 0 { if len(rewards) > 0 {
for holder, reward := range rewards { for holder, reward := range rewards {
stateBlock.AddBalance(holder, reward) stateBlock.AddBalance(holder, reward, tracing.BalanceIncreaseRewardMineBlock)
} }
} }
voterResults[signer] = rewards voterResults[signer] = rewards

View file

@ -13,6 +13,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/contracts" "github.com/XinFinOrg/XDPoSChain/contracts"
"github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/eth/util" "github.com/XinFinOrg/XDPoSChain/eth/util"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
@ -299,7 +300,7 @@ func AttachConsensusV2Hooks(adaptor *XDPoS.XDPoS, bc *core.BlockChain, chainConf
} }
if len(rewards) > 0 { if len(rewards) > 0 {
for holder, reward := range rewards { for holder, reward := range rewards {
stateBlock.AddBalance(holder, reward) stateBlock.AddBalance(holder, reward, tracing.BalanceIncreaseRewardMineBlock)
} }
} }
rewardResults[signer] = rewards rewardResults[signer] = rewards
@ -337,7 +338,7 @@ func AttachConsensusV2Hooks(adaptor *XDPoS.XDPoS, bc *core.BlockChain, chainConf
} }
if len(rewards) > 0 { if len(rewards) > 0 {
for holder, reward := range rewards { for holder, reward := range rewards {
stateBlock.AddBalance(holder, reward) stateBlock.AddBalance(holder, reward, tracing.BalanceIncreaseRewardMineBlock)
rewardSum.Add(rewardSum, reward) rewardSum.Add(rewardSum, reward)
} }
} }

View file

@ -190,7 +190,7 @@ func (eth *Ethereum) StateAtBlock(ctx context.Context, block *types.Block, reexe
} }
// stateAtTransaction returns the execution environment of a certain transaction. // stateAtTransaction returns the execution environment of a certain transaction.
func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) { func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
// Short circuit if it's genesis block. // Short circuit if it's genesis block.
if block.NumberU64() == 0 { if block.NumberU64() == 0 {
return nil, vm.BlockContext{}, nil, nil, errors.New("no transaction in genesis") return nil, vm.BlockContext{}, nil, nil, errors.New("no transaction in genesis")
@ -224,7 +224,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
txContext := core.NewEVMTxContext(msg) txContext := core.NewEVMTxContext(msg)
context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil) context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil)
if idx == txIndex { if idx == txIndex {
return msg, context, statedb, release, nil return tx, context, statedb, release, nil
} }
// Not yet the searched for transaction, execute on top of the current state // Not yet the searched for transaction, execute on top of the current state
vmenv := vm.NewEVM(context, txContext, statedb, nil, eth.blockchain.Config(), vm.Config{}) vmenv := vm.NewEVM(context, txContext, statedb, nil, eth.blockchain.Config(), vm.Config{})

View file

@ -88,7 +88,7 @@ type Backend interface {
Engine() consensus.Engine Engine() consensus.Engine
ChainDb() ethdb.Database ChainDb() ethdb.Database
StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, StateReleaseFunc, error) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, StateReleaseFunc, error)
StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, StateReleaseFunc, error) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.StateDB, StateReleaseFunc, error)
} }
// API is the collection of tracing APIs exposed over the private debugging endpoint. // API is the collection of tracing APIs exposed over the private debugging endpoint.
@ -280,14 +280,12 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
TxIndex: i, TxIndex: i,
TxHash: tx.Hash(), TxHash: tx.Hash(),
} }
res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config) res, err := api.traceTx(ctx, tx, msg, txctx, blockCtx, task.statedb, config)
if err != nil { if err != nil {
task.results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()} task.results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()}
log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err)
break break
} }
// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
task.statedb.Finalise(api.backend.ChainConfig().IsEIP158(task.block.Number()))
task.results[i] = &txTraceResult{TxHash: tx.Hash(), Result: res} task.results[i] = &txTraceResult{TxHash: tx.Hash(), Result: res}
} }
// Tracing state is used up, queue it for de-referencing. Note the // Tracing state is used up, queue it for de-referencing. Note the
@ -580,7 +578,6 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
var ( var (
txs = block.Transactions() txs = block.Transactions()
blockHash = block.Hash() blockHash = block.Hash()
is158 = api.backend.ChainConfig().IsEIP158(block.Number())
blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number()) signer = types.MakeSigner(api.backend.ChainConfig(), block.Number())
results = make([]*txTraceResult, len(txs)) results = make([]*txTraceResult, len(txs))
@ -604,14 +601,11 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
TxIndex: i, TxIndex: i,
TxHash: tx.Hash(), TxHash: tx.Hash(),
} }
res, err := api.traceTx(ctx, msg, txctx, blockCtx, statedb, config) res, err := api.traceTx(ctx, tx, msg, txctx, blockCtx, statedb, config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
results[i] = &txTraceResult{TxHash: tx.Hash(), Result: res} results[i] = &txTraceResult{TxHash: tx.Hash(), Result: res}
// Finalize the state so any modifications are written to the trie
// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
statedb.Finalise(is158)
} }
return results, nil return results, nil
} }
@ -659,7 +653,7 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat
// concurrent use. // concurrent use.
// See: https://github.com/ethereum/go-ethereum/issues/29114 // See: https://github.com/ethereum/go-ethereum/issues/29114
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config) res, err := api.traceTx(ctx, txs[task.index], msg, txctx, blockCtx, task.statedb, config)
if err != nil { if err != nil {
results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()} results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()}
continue continue
@ -742,11 +736,22 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *
if err != nil { if err != nil {
return nil, err return nil, err
} }
msg, vmctx, statedb, release, err := api.backend.StateAtTransaction(ctx, block, int(index), reexec) tx, vmctx, statedb, release, err := api.backend.StateAtTransaction(ctx, block, int(index), reexec)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer release() defer release()
feeCapacity := state.GetTRC21FeeCapacityFromState(statedb)
var balance *big.Int
if tx.To() != nil {
if value, ok := feeCapacity[*tx.To()]; ok {
balance = value
}
}
msg, err := core.TransactionToMessage(tx, types.MakeSigner(api.backend.ChainConfig(), block.Number()), balance, block.Number(), block.BaseFee())
if err != nil {
return nil, err
}
txctx := &Context{ txctx := &Context{
BlockHash: blockHash, BlockHash: blockHash,
@ -754,7 +759,7 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *
TxIndex: int(index), TxIndex: int(index),
TxHash: hash, TxHash: hash,
} }
return api.traceTx(ctx, msg, txctx, vmctx, statedb, config) return api.traceTx(ctx, tx, msg, txctx, vmctx, statedb, config)
} }
// TraceCall lets you trace a given eth_call. It collects the structured logs // TraceCall lets you trace a given eth_call. It collects the structured logs
@ -815,39 +820,49 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
config.BlockOverrides.Apply(&vmctx) config.BlockOverrides.Apply(&vmctx)
} }
// Execute the trace // Execute the trace
msg, err := args.ToMessage(api.backend, block.Number(), api.backend.RPCGasCap(), vmctx.BaseFee) if err := args.CallDefaults(api.backend.RPCGasCap(), vmctx.BaseFee, api.backend.ChainConfig().ChainId); err != nil {
if err != nil {
return nil, err return nil, err
} }
var traceConfig *TraceConfig var (
msg = args.ToMessage(api.backend, vmctx.BaseFee)
tx = args.ToTransaction()
traceConfig *TraceConfig
)
if config != nil { if config != nil {
traceConfig = &config.TraceConfig traceConfig = &config.TraceConfig
} }
return api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig) return api.traceTx(ctx, tx, msg, new(Context), vmctx, statedb, traceConfig)
} }
// traceTx configures a new tracer according to the provided configuration, and // traceTx configures a new tracer according to the provided configuration, and
// executes the given message in the provided environment. The return value will // executes the given message in the provided environment. The return value will
// be tracer dependent. // be tracer dependent.
func (api *API) traceTx(ctx context.Context, message *core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) {
var ( var (
tracer Tracer tracer *Tracer
err error err error
timeout = defaultTraceTimeout timeout = defaultTraceTimeout
txContext = core.NewEVMTxContext(message) usedGas uint64
) )
if config == nil { if config == nil {
config = &TraceConfig{} config = &TraceConfig{}
} }
// Default tracer is the struct logger // Default tracer is the struct logger
tracer = logger.NewStructLogger(config.Config) if config.Tracer == nil {
if config.Tracer != nil { logger := logger.NewStructLogger(config.Config)
tracer = &Tracer{
Hooks: logger.Hooks(),
GetResult: logger.GetResult,
Stop: logger.Stop,
}
} else {
tracer, err = DefaultDirectory.New(*config.Tracer, txctx, config.TracerConfig) tracer, err = DefaultDirectory.New(*config.Tracer, txctx, config.TracerConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
vmenv := vm.NewEVM(vmctx, txContext, statedb, nil, api.backend.ChainConfig(), vm.Config{Tracer: tracer, NoBaseFee: true}) vmenv := vm.NewEVM(vmctx, vm.TxContext{GasPrice: big.NewInt(0)}, statedb, nil, api.backend.ChainConfig(), vm.Config{Tracer: tracer.Hooks, NoBaseFee: true})
statedb.SetLogger(tracer.Hooks)
// Define a meaningful timeout of a single transaction trace // Define a meaningful timeout of a single transaction trace
if config.Timeout != nil { if config.Timeout != nil {
@ -866,9 +881,18 @@ func (api *API) traceTx(ctx context.Context, message *core.Message, txctx *Conte
}() }()
defer cancel() defer cancel()
feeCapacity := state.GetTRC21FeeCapacityFromState(statedb)
var balance *big.Int
if tx.To() != nil {
if value, ok := feeCapacity[*tx.To()]; ok {
balance = value
}
}
// Call SetTxContext to clear out the statedb access list // Call SetTxContext to clear out the statedb access list
statedb.SetTxContext(txctx.TxHash, txctx.TxIndex) statedb.SetTxContext(txctx.TxHash, txctx.TxIndex)
if _, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.GasLimit), common.Address{}); err != nil { _, _, err, _ = core.ApplyTransactionWithEVM(message, api.backend.ChainConfig(), new(core.GasPool).AddGas(message.GasLimit), statedb, vmctx.BlockNumber, txctx.BlockHash, tx, &usedGas, vmenv, balance, common.Address{})
if err != nil {
return nil, fmt.Errorf("tracing failed: %w", err) return nil, fmt.Errorf("tracing failed: %w", err)
} }
return tracer.GetResult() return tracer.GetResult()

View file

@ -159,7 +159,7 @@ func (b *testBackend) StateAtBlock(ctx context.Context, block *types.Block, reex
return statedb, release, nil return statedb, release, nil
} }
func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, StateReleaseFunc, error) { func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.StateDB, StateReleaseFunc, error) {
parent := b.chain.GetBlock(block.ParentHash(), block.NumberU64()-1) parent := b.chain.GetBlock(block.ParentHash(), block.NumberU64()-1)
if parent == nil { if parent == nil {
return nil, vm.BlockContext{}, nil, nil, errBlockNotFound return nil, vm.BlockContext{}, nil, nil, errBlockNotFound
@ -178,7 +178,7 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block
txContext := core.NewEVMTxContext(msg) txContext := core.NewEVMTxContext(msg)
context := core.NewEVMBlockContext(block.Header(), b.chain, nil) context := core.NewEVMBlockContext(block.Header(), b.chain, nil)
if idx == txIndex { if idx == txIndex {
return msg, context, statedb, release, nil return tx, context, statedb, release, nil
} }
vmenv := vm.NewEVM(context, txContext, statedb, nil, b.chainConfig, vm.Config{}) vmenv := vm.NewEVM(context, txContext, statedb, nil, b.chainConfig, vm.Config{})
owner := common.Address{} owner := common.Address{}
@ -667,7 +667,6 @@ func TestTracingWithOverrides(t *testing.T) {
From: &accounts[0].addr, From: &accounts[0].addr,
// BLOCKNUMBER PUSH1 MSTORE // BLOCKNUMBER PUSH1 MSTORE
Input: newRPCBytes(common.Hex2Bytes("4360005260206000f3")), Input: newRPCBytes(common.Hex2Bytes("4360005260206000f3")),
//&hexutil.Bytes{0x43}, // blocknumber
}, },
config: &TraceCallConfig{ config: &TraceCallConfig{
BlockOverrides: &ethapi.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))}, BlockOverrides: &ethapi.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))},

View file

@ -14,17 +14,14 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package tracers is a collection of JavaScript transaction tracers.
package tracers package tracers
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt"
"math/big" "math/big"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/tracing"
) )
// Context contains some contextual infos for a transaction execution that is not // Context contains some contextual infos for a transaction execution that is not
@ -36,17 +33,19 @@ type Context struct {
TxHash common.Hash // Hash of the transaction being traced (zero if dangling call) TxHash common.Hash // Hash of the transaction being traced (zero if dangling call)
} }
// Tracer interface extends vm.EVMLogger and additionally // The set of methods that must be exposed by a tracer
// allows collecting the tracing result. // for it to be available through the RPC interface.
type Tracer interface { // This involves a method to retrieve results and one to
vm.EVMLogger // stop tracing.
GetResult() (json.RawMessage, error) type Tracer struct {
*tracing.Hooks
GetResult func() (json.RawMessage, error)
// Stop terminates execution of the tracer at the first opportune moment. // Stop terminates execution of the tracer at the first opportune moment.
Stop(err error) Stop func(err error)
} }
type ctorFn func(*Context, json.RawMessage) (Tracer, error) type ctorFn func(*Context, json.RawMessage) (*Tracer, error)
type jsCtorFn func(string, *Context, json.RawMessage) (Tracer, error) type jsCtorFn func(string, *Context, json.RawMessage) (*Tracer, error)
type elem struct { type elem struct {
ctor ctorFn ctor ctorFn
@ -79,7 +78,7 @@ func (d *directory) RegisterJSEval(f jsCtorFn) {
// New returns a new instance of a tracer, by iterating through the // New returns a new instance of a tracer, by iterating through the
// registered lookups. Name is either name of an existing tracer // registered lookups. Name is either name of an existing tracer
// or an arbitrary JS code. // or an arbitrary JS code.
func (d *directory) New(name string, ctx *Context, cfg json.RawMessage) (Tracer, error) { func (d *directory) New(name string, ctx *Context, cfg json.RawMessage) (*Tracer, error) {
if elem, ok := d.elems[name]; ok { if elem, ok := d.elems[name]; ok {
return elem.ctor(ctx, cfg) return elem.ctor(ctx, cfg)
} }
@ -97,27 +96,3 @@ func (d *directory) IsJS(name string) bool {
// JS eval will execute JS code // JS eval will execute JS code
return true return true
} }
const (
memoryPadLimit = 1024 * 1024
)
// GetMemoryCopyPadded returns offset + size as a new slice.
// It zero-pads the slice if it extends beyond memory bounds.
func GetMemoryCopyPadded(m *vm.Memory, offset, size int64) ([]byte, error) {
if offset < 0 || size < 0 {
return nil, errors.New("offset or size must not be negative")
}
if int(offset+size) < m.Len() { // slice fully inside memory
return m.GetCopy(offset, size), nil
}
paddingNeeded := int(offset+size) - m.Len()
if paddingNeeded > memoryPadLimit {
return nil, fmt.Errorf("reached limit for padding memory slice: %d", paddingNeeded)
}
cpy := make([]byte, size)
if overlap := int64(m.Len()) - offset; overlap > 0 {
copy(cpy, m.GetPtr(offset, overlap))
}
return cpy, nil
}

View file

@ -0,0 +1,10 @@
# Filling test cases
To fill test cases for the built-in tracers, the `makeTest.js` script can be used. Given a transaction on a dev/test network, `makeTest.js` will fetch its prestate and then traces with the given configuration.
In the Geth console do:
```terminal
let tx = '0x...'
loadScript('makeTest.js')
makeTest(tx, { tracer: 'callTracer' })
```

View file

@ -18,6 +18,7 @@ package tracetest
import ( import (
"encoding/json" "encoding/json"
"fmt"
"math/big" "math/big"
"os" "os"
"path/filepath" "path/filepath"
@ -32,6 +33,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/eth/tracers" "github.com/XinFinOrg/XDPoSChain/eth/tracers"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
@ -133,21 +135,26 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
GasLimit: uint64(test.Context.GasLimit), GasLimit: uint64(test.Context.GasLimit),
BaseFee: test.Genesis.BaseFee, BaseFee: test.Genesis.BaseFee,
} }
statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc) state = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc)
) )
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig) tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig)
if err != nil { if err != nil {
t.Fatalf("failed to create call tracer: %v", err) t.Fatalf("failed to create call tracer: %v", err)
} }
state.SetLogger(tracer.Hooks)
msg, err := core.TransactionToMessage(tx, signer, nil, nil, context.BaseFee) msg, err := core.TransactionToMessage(tx, signer, nil, nil, context.BaseFee)
if err != nil { if err != nil {
t.Fatalf("failed to prepare transaction for tracing: %v", err) t.Fatalf("failed to prepare transaction for tracing: %v", err)
} }
evm := vm.NewEVM(context, core.NewEVMTxContext(msg), statedb, nil, test.Genesis.Config, vm.Config{Tracer: tracer}) evm := vm.NewEVM(context, core.NewEVMTxContext(msg), state, nil, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()), common.Address{}) vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()), common.Address{})
if err != nil { if err != nil {
t.Fatalf("failed to execute transaction: %v", err) t.Fatalf("failed to execute transaction: %v", err)
} }
tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil)
// Retrieve the trace result and compare against the expected. // Retrieve the trace result and compare against the expected.
res, err := tracer.GetResult() res, err := tracer.GetResult()
if err != nil { if err != nil {
@ -232,7 +239,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
if err != nil { if err != nil {
b.Fatalf("failed to prepare transaction for tracing: %v", err) b.Fatalf("failed to prepare transaction for tracing: %v", err)
} }
statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc) state := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc)
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()
@ -241,8 +248,8 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
if err != nil { if err != nil {
b.Fatalf("failed to create call tracer: %v", err) b.Fatalf("failed to create call tracer: %v", err)
} }
evm := vm.NewEVM(context, txContext, statedb, nil, test.Genesis.Config, vm.Config{Tracer: tracer}) evm := vm.NewEVM(context, txContext, state, nil, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
snap := statedb.Snapshot() snap := state.Snapshot()
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
if _, err = st.TransitionDb(common.Address{}); err != nil { if _, err = st.TransitionDb(common.Address{}); err != nil {
b.Fatalf("failed to execute transaction: %v", err) b.Fatalf("failed to execute transaction: %v", err)
@ -250,19 +257,19 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
if _, err = tracer.GetResult(); err != nil { if _, err = tracer.GetResult(); err != nil {
b.Fatal(err) b.Fatal(err)
} }
statedb.RevertToSnapshot(snap) state.RevertToSnapshot(snap)
} }
} }
func TestInternals(t *testing.T) { func TestInternals(t *testing.T) {
var ( var (
config = params.MainnetChainConfig
to = common.HexToAddress("0x00000000000000000000000000000000deadbeef") to = common.HexToAddress("0x00000000000000000000000000000000deadbeef")
origin = common.HexToAddress("0x00000000000000000000000000000000feed") originHex = "0x71562b71999873db5b286df957af199ec94617f7"
txContext = vm.TxContext{ origin = common.HexToAddress(originHex)
Origin: origin, signer = types.LatestSigner(config)
GasPrice: big.NewInt(1), key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
} context = vm.BlockContext{
context = vm.BlockContext{
CanTransfer: core.CanTransfer, CanTransfer: core.CanTransfer,
Transfer: core.Transfer, Transfer: core.Transfer,
Coinbase: common.Address{}, Coinbase: common.Address{},
@ -270,9 +277,10 @@ func TestInternals(t *testing.T) {
Time: 5, Time: 5,
Difficulty: big.NewInt(0x30000), Difficulty: big.NewInt(0x30000),
GasLimit: uint64(6000000), GasLimit: uint64(6000000),
BaseFee: new(big.Int),
} }
) )
mkTracer := func(name string, cfg json.RawMessage) tracers.Tracer { mkTracer := func(name string, cfg json.RawMessage) *tracers.Tracer {
tr, err := tracers.DefaultDirectory.New(name, nil, cfg) tr, err := tracers.DefaultDirectory.New(name, nil, cfg)
if err != nil { if err != nil {
t.Fatalf("failed to create call tracer: %v", err) t.Fatalf("failed to create call tracer: %v", err)
@ -283,7 +291,7 @@ func TestInternals(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
name string name string
code []byte code []byte
tracer tracers.Tracer tracer *tracers.Tracer
want string want string
}{ }{
{ {
@ -297,13 +305,13 @@ func TestInternals(t *testing.T) {
byte(vm.CALL), byte(vm.CALL),
}, },
tracer: mkTracer("callTracer", nil), tracer: mkTracer("callTracer", nil),
want: `{"from":"0x000000000000000000000000000000000000feed","gas":"0x13880","gasUsed":"0x54d8","to":"0x00000000000000000000000000000000deadbeef","input":"0x","calls":[{"from":"0x00000000000000000000000000000000deadbeef","gas":"0xe01a","gasUsed":"0x0","to":"0x00000000000000000000000000000000000000ff","input":"0x","value":"0x0","type":"CALL"}],"value":"0x0","type":"CALL"}`, want: fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x54d8","to":"0x00000000000000000000000000000000deadbeef","input":"0x","calls":[{"from":"0x00000000000000000000000000000000deadbeef","gas":"0xe01a","gasUsed":"0x0","to":"0x00000000000000000000000000000000000000ff","input":"0x","value":"0x0","type":"CALL"}],"value":"0x0","type":"CALL"}`, originHex),
}, },
{ {
name: "Stack depletion in LOG0", name: "Stack depletion in LOG0",
code: []byte{byte(vm.LOG3)}, code: []byte{byte(vm.LOG3)},
tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)), tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)),
want: `{"from":"0x000000000000000000000000000000000000feed","gas":"0x13880","gasUsed":"0x13880","to":"0x00000000000000000000000000000000deadbeef","input":"0x","error":"stack underflow (0 \u003c=\u003e 5)","value":"0x0","type":"CALL"}`, want: fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x13880","to":"0x00000000000000000000000000000000deadbeef","input":"0x","error":"stack underflow (0 \u003c=\u003e 5)","value":"0x0","type":"CALL"}`, originHex),
}, },
{ {
name: "Mem expansion in LOG0", name: "Mem expansion in LOG0",
@ -316,7 +324,7 @@ func TestInternals(t *testing.T) {
byte(vm.LOG0), byte(vm.LOG0),
}, },
tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)), tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)),
want: `{"from":"0x000000000000000000000000000000000000feed","gas":"0x13880","gasUsed":"0x5b9e","to":"0x00000000000000000000000000000000deadbeef","input":"0x","logs":[{"address":"0x00000000000000000000000000000000deadbeef","topics":[],"data":"0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","position":"0x0"}],"value":"0x0","type":"CALL"}`, want: fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x5b9e","to":"0x00000000000000000000000000000000deadbeef","input":"0x","logs":[{"address":"0x00000000000000000000000000000000deadbeef","topics":[],"data":"0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","position":"0x0"}],"value":"0x0","type":"CALL"}`, originHex),
}, },
{ {
// Leads to OOM on the prestate tracer // Leads to OOM on the prestate tracer
@ -335,7 +343,7 @@ func TestInternals(t *testing.T) {
byte(vm.LOG0), byte(vm.LOG0),
}, },
tracer: mkTracer("prestateTracer", nil), tracer: mkTracer("prestateTracer", nil),
want: `{"0x0000000000000000000000000000000000000000":{"balance":"0x0"},"0x000000000000000000000000000000000000feed":{"balance":"0x1c6bf52647880"},"0x00000000000000000000000000000000deadbeef":{"balance":"0x0","code":"0x6001600052600164ffffffffff60016000f560ff6000a0"}}`, want: fmt.Sprintf(`{"0x0000000000000000000000000000000000000000":{"balance":"0x0"},"0x00000000000000000000000000000000deadbeef":{"balance":"0x0","code":"0x6001600052600164ffffffffff60016000f560ff6000a0"},"%s":{"balance":"0x1c6bf52634000"}}`, originHex),
}, },
{ {
// CREATE2 which requires padding memory by prestate tracer // CREATE2 which requires padding memory by prestate tracer
@ -354,11 +362,11 @@ func TestInternals(t *testing.T) {
byte(vm.LOG0), byte(vm.LOG0),
}, },
tracer: mkTracer("prestateTracer", nil), tracer: mkTracer("prestateTracer", nil),
want: `{"0x0000000000000000000000000000000000000000":{"balance":"0x0"},"0x000000000000000000000000000000000000feed":{"balance":"0x1c6bf52647880"},"0x00000000000000000000000000000000deadbeef":{"balance":"0x0","code":"0x6001600052600160ff60016000f560ff6000a0"},"0x91ff9a805d36f54e3e272e230f3e3f5c1b330804":{"balance":"0x0"}}`, want: fmt.Sprintf(`{"0x0000000000000000000000000000000000000000":{"balance":"0x0"},"0x00000000000000000000000000000000deadbeef":{"balance":"0x0","code":"0x6001600052600160ff60016000f560ff6000a0"},"%s":{"balance":"0x1c6bf52634000"}}`, originHex),
}, },
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), state := tests.MakePreState(rawdb.NewMemoryDatabase(),
types.GenesisAlloc{ types.GenesisAlloc{
to: types.Account{ to: types.Account{
Code: tc.code, Code: tc.code,
@ -367,21 +375,31 @@ func TestInternals(t *testing.T) {
Balance: big.NewInt(500000000000000), Balance: big.NewInt(500000000000000),
}, },
}) })
evm := vm.NewEVM(context, txContext, statedb, nil, params.MainnetChainConfig, vm.Config{Tracer: tc.tracer}) state.SetLogger(tc.tracer.Hooks)
msg := &core.Message{ tx, err := types.SignNewTx(key, signer, &types.LegacyTx{
To: &to, To: &to,
From: origin, Value: big.NewInt(0),
Value: big.NewInt(0), Gas: 80000,
GasLimit: 80000, GasPrice: big.NewInt(1),
GasPrice: big.NewInt(0), })
GasFeeCap: big.NewInt(0), if err != nil {
GasTipCap: big.NewInt(0), t.Fatalf("test %v: failed to sign transaction: %v", tc.name, err)
SkipAccountChecks: false,
} }
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(msg.GasLimit)) txContext := vm.TxContext{
if _, err := st.TransitionDb(common.Address{}); err != nil { Origin: origin,
GasPrice: tx.GasPrice(),
}
evm := vm.NewEVM(context, txContext, state, nil, config, vm.Config{Tracer: tc.tracer.Hooks})
msg, err := core.TransactionToMessage(tx, signer, nil, nil, big.NewInt(0))
if err != nil {
t.Fatalf("test %v: failed to create message: %v", tc.name, err)
}
tc.tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()), common.Address{})
if err != nil {
t.Fatalf("test %v: failed to execute transaction: %v", tc.name, err) t.Fatalf("test %v: failed to execute transaction: %v", tc.name, err)
} }
tc.tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil)
// Retrieve the trace result and compare against the expected // Retrieve the trace result and compare against the expected
res, err := tc.tracer.GetResult() res, err := tc.tracer.GetResult()
if err != nil { if err != nil {
@ -444,13 +462,14 @@ func testContractTracer(tracerName string, dirPath string, t *testing.T) {
Difficulty: (*big.Int)(test.Context.Difficulty), Difficulty: (*big.Int)(test.Context.Difficulty),
GasLimit: uint64(test.Context.GasLimit), GasLimit: uint64(test.Context.GasLimit),
} }
statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc) state = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc)
) )
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig) tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig)
if err != nil { if err != nil {
t.Fatalf("failed to create call tracer: %v", err) t.Fatalf("failed to create call tracer: %v", err)
} }
evm := vm.NewEVM(context, txContext, statedb, nil, test.Genesis.Config, vm.Config{Tracer: tracer}) evm := vm.NewEVM(context, txContext, state, nil, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
msg, err := core.TransactionToMessage(tx, signer, nil, nil, nil) msg, err := core.TransactionToMessage(tx, signer, nil, nil, nil)
if err != nil { if err != nil {
t.Fatalf("failed to prepare transaction for tracing: %v", err) t.Fatalf("failed to prepare transaction for tracing: %v", err)

View file

@ -16,11 +16,9 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/eth/tracers"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
"github.com/XinFinOrg/XDPoSChain/tests" "github.com/XinFinOrg/XDPoSChain/tests"
// Force-load the native, to trigger registration
"github.com/XinFinOrg/XDPoSChain/eth/tracers"
) )
// flatCallTrace is the result of a callTracerParity run. // flatCallTrace is the result of a callTracerParity run.
@ -95,23 +93,26 @@ func flatCallTracerTestRunner(tracerName string, filename string, dirPath string
Difficulty: (*big.Int)(test.Context.Difficulty), Difficulty: (*big.Int)(test.Context.Difficulty),
GasLimit: uint64(test.Context.GasLimit), GasLimit: uint64(test.Context.GasLimit),
} }
statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc) state := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc)
// Create the tracer, the EVM environment and run it // Create the tracer, the EVM environment and run it
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig) tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig)
if err != nil { if err != nil {
return fmt.Errorf("failed to create call tracer: %v", err) return fmt.Errorf("failed to create call tracer: %v", err)
} }
state.SetLogger(tracer.Hooks)
msg, err := core.TransactionToMessage(tx, signer, nil, context.BlockNumber, context.BaseFee) msg, err := core.TransactionToMessage(tx, signer, nil, context.BlockNumber, context.BaseFee)
if err != nil { if err != nil {
return fmt.Errorf("failed to prepare transaction for tracing: %v", err) return fmt.Errorf("failed to prepare transaction for tracing: %v", err)
} }
evm := vm.NewEVM(context, core.NewEVMTxContext(msg), statedb, nil, test.Genesis.Config, vm.Config{Tracer: tracer}) evm := vm.NewEVM(context, core.NewEVMTxContext(msg), state, nil, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()), common.Address{})
if _, err = st.TransitionDb(common.Address{}); err != nil { if err != nil {
return fmt.Errorf("failed to execute transaction: %v", err) return fmt.Errorf("failed to execute transaction: %v", err)
} }
tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil)
// Retrieve the trace result and compare against the etalon // Retrieve the trace result and compare against the etalon
res, err := tracer.GetResult() res, err := tracer.GetResult()
@ -123,7 +124,7 @@ func flatCallTracerTestRunner(tracerName string, filename string, dirPath string
return fmt.Errorf("failed to unmarshal trace result: %v", err) return fmt.Errorf("failed to unmarshal trace result: %v", err)
} }
if !jsonEqualFlat(ret, test.Result) { if !jsonEqualFlat(ret, test.Result) {
t.Logf("tracer name: %s", tracerName) t.Logf("test %s failed", filename)
// uncomment this for easier debugging // uncomment this for easier debugging
// have, _ := json.MarshalIndent(ret, "", " ") // have, _ := json.MarshalIndent(ret, "", " ")

View file

@ -0,0 +1,48 @@
// makeTest generates a test for the configured tracer by running
// a prestate reassembled and a call trace run, assembling all the
// gathered information into a test case.
var makeTest = function(tx, traceConfig) {
// Generate the genesis block from the block, transaction and prestate data
var block = eth.getBlock(eth.getTransaction(tx).blockHash);
var genesis = eth.getBlock(block.parentHash);
delete genesis.gasUsed;
delete genesis.logsBloom;
delete genesis.parentHash;
delete genesis.receiptsRoot;
delete genesis.sha3Uncles;
delete genesis.size;
delete genesis.transactions;
delete genesis.transactionsRoot;
delete genesis.uncles;
genesis.gasLimit = genesis.gasLimit.toString();
genesis.number = genesis.number.toString();
genesis.timestamp = genesis.timestamp.toString();
genesis.alloc = debug.traceTransaction(tx, {tracer: "prestateTracer"});
for (var key in genesis.alloc) {
var nonce = genesis.alloc[key].nonce;
if (nonce) {
genesis.alloc[key].nonce = nonce.toString();
}
}
genesis.config = admin.nodeInfo.protocols.eth.config;
// Generate the call trace and produce the test input
var result = debug.traceTransaction(tx, traceConfig);
delete result.time;
console.log(JSON.stringify({
genesis: genesis,
context: {
number: block.number.toString(),
difficulty: block.difficulty,
timestamp: block.timestamp.toString(),
gasLimit: block.gasLimit.toString(),
miner: block.miner,
},
input: eth.getRawTransaction(tx),
result: result,
}, null, 2));
}

View file

@ -103,22 +103,37 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) {
GasLimit: uint64(test.Context.GasLimit), GasLimit: uint64(test.Context.GasLimit),
BaseFee: test.Genesis.BaseFee, BaseFee: test.Genesis.BaseFee,
} }
statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc) state = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc)
) )
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig) tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig)
if err != nil { if err != nil {
t.Fatalf("failed to create call tracer: %v", err) t.Fatalf("failed to create call tracer: %v", err)
} }
// msg, err := core.TransactionToMessage(tx, signer, nil, context.BlockNumber, context.BaseFee)
// if err != nil {
// t.Fatalf("failed to prepare transaction for tracing: %v", err)
// }
// evm := vm.NewEVM(context, core.NewEVMTxContext(msg), statedb, nil, test.Genesis.Config, vm.Config{Tracer: tracer})
// st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
// if _, err = st.TransitionDb(common.Address{}); err != nil {
// t.Fatalf("failed to execute transaction: %v", err)
// }
// // Retrieve the trace result and compare against the expected.
state.SetLogger(tracer.Hooks)
msg, err := core.TransactionToMessage(tx, signer, nil, context.BlockNumber, context.BaseFee) msg, err := core.TransactionToMessage(tx, signer, nil, context.BlockNumber, context.BaseFee)
if err != nil { if err != nil {
t.Fatalf("failed to prepare transaction for tracing: %v", err) t.Fatalf("failed to prepare transaction for tracing: %v", err)
} }
evm := vm.NewEVM(context, core.NewEVMTxContext(msg), statedb, nil, test.Genesis.Config, vm.Config{Tracer: tracer}) evm := vm.NewEVM(context, core.NewEVMTxContext(msg), state, nil, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
if _, err = st.TransitionDb(common.Address{}); err != nil { vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()), common.Address{})
if err != nil {
t.Fatalf("failed to execute transaction: %v", err) t.Fatalf("failed to execute transaction: %v", err)
} }
// Retrieve the trace result and compare against the expected. tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil)
// Retrieve the trace result and compare against the expected
res, err := tracer.GetResult() res, err := tracer.GetResult()
if err != nil { if err != nil {
t.Fatalf("failed to retrieve trace result: %v", err) t.Fatalf("failed to retrieve trace result: %v", err)

View file

@ -56,6 +56,16 @@
"value": "0x0", "value": "0x0",
"gas": "0x1f97e", "gas": "0x1f97e",
"gasUsed": "0x72de", "gasUsed": "0x72de",
"input": "0x2e1a7d4d00000000000000000000000000000000000000000000000014d1120d7b160000" "input": "0x2e1a7d4d00000000000000000000000000000000000000000000000014d1120d7b160000",
"calls": [{
"from":"0x6c06b16512b332e6cd8293a2974872674716ce18",
"gas":"0x8fc",
"gasUsed":"0x0",
"to":"0x66fdfd05e46126a07465ad24e40cc0597bc1ef31",
"input":"0x",
"error":"insufficient balance for transfer",
"value":"0x14d1120d7b160000",
"type":"CALL"
}]
} }
} }

View file

@ -63,12 +63,29 @@
"address": "0x5f8a7e007172ba80afbff1b15f800eb0b260f224" "address": "0x5f8a7e007172ba80afbff1b15f800eb0b260f224"
}, },
"traceAddress": [], "traceAddress": [],
"subtraces": 0, "subtraces": 1,
"transactionPosition": 74, "transactionPosition": 74,
"transactionHash": "0x5ef60b27ac971c22a7d484e546e50093ca62300c8986d165154e47773764b6a4", "transactionHash": "0x5ef60b27ac971c22a7d484e546e50093ca62300c8986d165154e47773764b6a4",
"blockNumber": 1555279, "blockNumber": 1555279,
"blockHash": "0xd6c98d1b87dfa92a210d99bad2873adaf0c9e51fe43addc63fd9cca03a5c6f46", "blockHash": "0xd6c98d1b87dfa92a210d99bad2873adaf0c9e51fe43addc63fd9cca03a5c6f46",
"time": "209.346µs" "time": "209.346µs"
},
{
"action": {
"balance": "0x0",
"callType": "callcode",
"from": "0x5f8a7e007172ba80afbff1b15f800eb0b260f224",
"gas": "0xab31",
"to": "0x0000000000000000000000000000000000000004",
"value": "0x13"
},
"error": "insufficient balance for transfer",
"result": {},
"subtraces": 0,
"traceAddress": [
0
],
"type": "call"
} }
] ]
} }

View file

@ -64,9 +64,23 @@
"gasUsed": "0x72de", "gasUsed": "0x72de",
"output": "0x" "output": "0x"
}, },
"subtraces": 0, "subtraces": 1,
"traceAddress": [], "traceAddress": [],
"type": "call" "type": "call"
},
{
"action": {
"callType": "call",
"from": "0x6c06b16512b332e6cd8293a2974872674716ce18",
"gas": "0x8fc",
"to": "0x66fdfd05e46126a07465ad24e40cc0597bc1ef31",
"value": "0x14d1120d7b160000"
},
"error": "insufficient balance for transfer",
"result": {},
"subtraces": 0,
"traceAddress": [0],
"type": "call"
} }
] ]
} }

View file

@ -70,12 +70,25 @@
"output": "0x" "output": "0x"
}, },
"traceAddress": [], "traceAddress": [],
"subtraces": 0, "subtraces": 1,
"transactionPosition": 26, "transactionPosition": 26,
"transactionHash": "0xcb1090fa85d2a3da8326b75333e92b3dca89963c895d9c981bfdaa64643135e4", "transactionHash": "0xcb1090fa85d2a3da8326b75333e92b3dca89963c895d9c981bfdaa64643135e4",
"blockNumber": 839247, "blockNumber": 839247,
"blockHash": "0xce7ff7d84ca97f0f89d6065e2c12409a795c9f607cdb14aef0713cad5d7e311c", "blockHash": "0xce7ff7d84ca97f0f89d6065e2c12409a795c9f607cdb14aef0713cad5d7e311c",
"time": "182.267µs" "time": "182.267µs"
},
{
"action": {
"from": "0x76554b33410b6d90b7dc889bfed0451ad195f27e",
"gas": "0x25a18",
"init": "0x0000000000000000000000000000000000000000000000000000000000000000",
"value": "0xa"
},
"error": "insufficient balance for transfer",
"result": {},
"subtraces": 0,
"traceAddress": [0],
"type": "create"
} }
] ]
} }

View file

@ -63,13 +63,28 @@
"address": "0x1d99a1a3efa9181f540f9e24fa6e4e08eb7844ca" "address": "0x1d99a1a3efa9181f540f9e24fa6e4e08eb7844ca"
}, },
"traceAddress": [], "traceAddress": [],
"subtraces": 1, "subtraces": 2,
"transactionPosition": 14, "transactionPosition": 14,
"transactionHash": "0xdd76f02407e2f8329303ba688e111cae4f7008ad0d14d6e42c5698424ea36d79", "transactionHash": "0xdd76f02407e2f8329303ba688e111cae4f7008ad0d14d6e42c5698424ea36d79",
"blockNumber": 1555146, "blockNumber": 1555146,
"blockHash": "0xafb4f1dd27b9054c805acb81a88ed04384788cb31d84164c21874935c81e5c7e", "blockHash": "0xafb4f1dd27b9054c805acb81a88ed04384788cb31d84164c21874935c81e5c7e",
"time": "187.145µs" "time": "187.145µs"
}, },
{
"action": {
"from": "0x1d99a1a3efa9181f540f9e24fa6e4e08eb7844ca",
"gas": "0x4e79",
"init": "0x5a",
"value": "0x1"
},
"error": "insufficient balance for transfer",
"result": {},
"subtraces": 0,
"traceAddress": [
0
],
"type": "create"
},
{ {
"type": "suicide", "type": "suicide",
"action": { "action": {
@ -79,7 +94,7 @@
}, },
"result": null, "result": null,
"traceAddress": [ "traceAddress": [
0 1
], ],
"subtraces": 0, "subtraces": 0,
"transactionPosition": 14, "transactionPosition": 14,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,63 @@
{
"genesis": {
"baseFeePerGas": "875000000",
"difficulty": "0",
"extraData": "0xd983010d05846765746888676f312e32312e318664617277696e",
"gasLimit": "11511229",
"hash": "0xd462585c6c5a3b3bf14850ebcde71b6615b9aaf6541403f9a0457212dd0502e0",
"miner": "0x0000000000000000000000000000000000000000",
"mixHash": "0xfa51e868d6a7c0728f18800e4cc8d4cc1c87430cc9975e947eb6c9c03599b4e2",
"nonce": "0x0000000000000000",
"number": "1",
"stateRoot": "0xd2ebe0a7f3572ffe3e5b4c78147376d3fca767f236e4dd23f9151acfec7cb0d1",
"timestamp": "1699617692",
"totalDifficulty": "0",
"withdrawals": [],
"withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"alloc": {
"0x0000000000000000000000000000000000000000": {
"balance": "0x5208"
},
"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": {
"balance": "0x8ac7230489e80000"
}
},
"config": {
"chainId": 1337,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"muirGlacierBlock": 0,
"berlinBlock": 0,
"londonBlock": 0,
"eip1559Block": 0,
"arrowGlacierBlock": 0,
"grayGlacierBlock": 0,
"shanghaiTime": 0,
"terminalTotalDifficulty": 0,
"terminalTotalDifficultyPassed": true,
"isDev": true
}
},
"context": {
"number": "2",
"difficulty": "0",
"timestamp": "1699617847",
"gasLimit": "11522469",
"miner": "0x0000000000000000000000000000000000000000"
},
"input": "0x02f902b48205398084b2d05e0085011b1f3f8083031ca88080b90258608060405234801561001057600080fd5b5060405161001d906100e3565b604051809103906000f080158015610039573d6000803e3d6000fd5b50600080546001600160a01b0319166001600160a01b039290921691821781556040517fc66247bafd1305823857fb4c3e651e684d918df8554ef560bbbcb025fdd017039190a26000546040516360fe47b160e01b8152600560048201526001600160a01b03909116906360fe47b190602401600060405180830381600087803b1580156100c657600080fd5b505af11580156100da573d6000803e3d6000fd5b505050506100ef565b60ca8061018e83390190565b6091806100fd6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806380de699314602d575b600080fd5b600054603f906001600160a01b031681565b6040516001600160a01b03909116815260200160405180910390f3fea2646970667358221220dab781465e7f4cf20304cc388130a763508e20edd25b4bc8ea8f57743a0de8da64736f6c634300081700336080604052348015600f57600080fd5b5060ac8061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c806360fe47b11460375780636d4ce63c146049575b600080fd5b60476042366004605e565b600055565b005b60005460405190815260200160405180910390f35b600060208284031215606f57600080fd5b503591905056fea264697066735822122049e09da6320793487d58eaa7b97f802618a062cbc35f08ca1ce92c17349141f864736f6c63430008170033c080a01d4fce93ad08bf413052645721f20e6136830cf5a2759fa57e76a134e90899a7a0399a72832d52118991dc04c4f9e1c0fec3d5e441ad7d4b055f0cf03130d8f815",
"result": {
"0x0000000000000000000000000000000000000000": {
"balance": "0x5208"
},
"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": {
"balance": "0x8ac7230489e80000"
}
}
}

View file

@ -9,59 +9,6 @@ import (
_ "github.com/XinFinOrg/XDPoSChain/eth/tracers/native" _ "github.com/XinFinOrg/XDPoSChain/eth/tracers/native"
) )
// To generate a new callTracer test, copy paste the makeTest method below into
// a Geth console and call it with a transaction hash you which to export.
/*
// makeTest generates a callTracer test by running a prestate reassembled and a
// call trace run, assembling all the gathered information into a test case.
var makeTest = function(tx, rewind) {
// Generate the genesis block from the block, transaction and prestate data
var block = eth.getBlock(eth.getTransaction(tx).blockHash);
var genesis = eth.getBlock(block.parentHash);
delete genesis.gasUsed;
delete genesis.logsBloom;
delete genesis.parentHash;
delete genesis.receiptsRoot;
delete genesis.sha3Uncles;
delete genesis.size;
delete genesis.transactions;
delete genesis.transactionsRoot;
delete genesis.uncles;
genesis.gasLimit = genesis.gasLimit.toString();
genesis.number = genesis.number.toString();
genesis.timestamp = genesis.timestamp.toString();
genesis.alloc = debug.traceTransaction(tx, {tracer: "prestateTracer", rewind: rewind});
for (var key in genesis.alloc) {
var nonce = genesis.alloc[key].nonce;
if (nonce) {
genesis.alloc[key].nonce = nonce.toString();
}
}
genesis.config = admin.nodeInfo.protocols.eth.config;
// Generate the call trace and produce the test input
var result = debug.traceTransaction(tx, {tracer: "callTracer", rewind: rewind});
delete result.time;
console.log(JSON.stringify({
genesis: genesis,
context: {
number: block.number.toString(),
difficulty: block.difficulty,
timestamp: block.timestamp.toString(),
gasLimit: block.gasLimit.toString(),
miner: block.miner,
},
input: eth.getRawTransaction(tx),
result: result,
}, null, 2));
}
*/
// camel converts a snake cased input string into a camel cased output. // camel converts a snake cased input string into a camel cased output.
func camel(str string) string { func camel(str string) string {
pieces := strings.Split(str, "_") pieces := strings.Split(str, "_")

View file

@ -0,0 +1,81 @@
// Copyright 2023 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package internal
import (
"errors"
"fmt"
"github.com/holiman/uint256"
)
const (
memoryPadLimit = 1024 * 1024
)
// GetMemoryCopyPadded returns offset + size as a new slice.
// It zero-pads the slice if it extends beyond memory bounds.
func GetMemoryCopyPadded(m []byte, offset, size int64) ([]byte, error) {
if offset < 0 || size < 0 {
return nil, errors.New("offset or size must not be negative")
}
length := int64(len(m))
if offset+size < length { // slice fully inside memory
return memoryCopy(m, offset, size), nil
}
paddingNeeded := offset + size - length
if paddingNeeded > memoryPadLimit {
return nil, fmt.Errorf("reached limit for padding memory slice: %d", paddingNeeded)
}
cpy := make([]byte, size)
if overlap := length - offset; overlap > 0 {
copy(cpy, MemoryPtr(m, offset, overlap))
}
return cpy, nil
}
func memoryCopy(m []byte, offset, size int64) (cpy []byte) {
if size == 0 {
return nil
}
if len(m) > int(offset) {
cpy = make([]byte, size)
copy(cpy, m[offset:offset+size])
return
}
return
}
// MemoryPtr returns a pointer to a slice of memory.
func MemoryPtr(m []byte, offset, size int64) []byte {
if size == 0 {
return nil
}
if len(m) > int(offset) {
return m[offset : offset+size]
}
return nil
}
// Back returns the n'th item in stack
func StackBack(st []uint256.Int, n int) *uint256.Int {
return &st[len(st)-n-1]
}

View file

@ -0,0 +1,60 @@
// Copyright 2023 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package internal
import (
"testing"
"github.com/XinFinOrg/XDPoSChain/core/vm"
)
func TestMemCopying(t *testing.T) {
for i, tc := range []struct {
memsize int64
offset int64
size int64
wantErr string
wantSize int
}{
{0, 0, 100, "", 100}, // Should pad up to 100
{0, 100, 0, "", 0}, // No need to pad (0 size)
{100, 50, 100, "", 100}, // Should pad 100-150
{100, 50, 5, "", 5}, // Wanted range fully within memory
{100, -50, 0, "offset or size must not be negative", 0}, // Error
{0, 1, 1024*1024 + 1, "reached limit for padding memory slice: 1048578", 0}, // Error
{10, 0, 1024*1024 + 100, "reached limit for padding memory slice: 1048666", 0}, // Error
} {
mem := vm.NewMemory()
mem.Resize(uint64(tc.memsize))
cpy, err := GetMemoryCopyPadded(mem.Data(), tc.offset, tc.size)
if want := tc.wantErr; want != "" {
if err == nil {
t.Fatalf("test %d: want '%v' have no error", i, want)
}
if have := err.Error(); want != have {
t.Fatalf("test %d: want '%v' have '%v'", i, want, have)
}
continue
}
if err != nil {
t.Fatalf("test %d: unexpected error: %v", i, err)
}
if want, have := tc.wantSize, len(cpy); have != want {
t.Fatalf("test %d: want %v have %v", i, want, have)
}
}
}

View file

@ -24,11 +24,15 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/hexutil" "github.com/XinFinOrg/XDPoSChain/common/hexutil"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/eth/tracers" "github.com/XinFinOrg/XDPoSChain/eth/tracers"
"github.com/XinFinOrg/XDPoSChain/eth/tracers/internal"
jsassets "github.com/XinFinOrg/XDPoSChain/eth/tracers/js/internal/tracers" jsassets "github.com/XinFinOrg/XDPoSChain/eth/tracers/js/internal/tracers"
"github.com/dop251/goja" "github.com/dop251/goja"
"github.com/holiman/uint256"
) )
var assetTracers = make(map[string]string) var assetTracers = make(map[string]string)
@ -40,9 +44,9 @@ func init() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
type ctorFn = func(*tracers.Context, json.RawMessage) (tracers.Tracer, error) type ctorFn = func(*tracers.Context, json.RawMessage) (*tracers.Tracer, error)
lookup := func(code string) ctorFn { lookup := func(code string) ctorFn {
return func(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { return func(ctx *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) {
return newJsTracer(code, ctx, cfg) return newJsTracer(code, ctx, cfg)
} }
} }
@ -95,7 +99,7 @@ func fromBuf(vm *goja.Runtime, bufType goja.Value, buf goja.Value, allowString b
// JS functions on the relevant EVM hooks. It uses Goja as its JS engine. // JS functions on the relevant EVM hooks. It uses Goja as its JS engine.
type jsTracer struct { type jsTracer struct {
vm *goja.Runtime vm *goja.Runtime
env *vm.EVM env *tracing.VMContext
toBig toBigFn // Converts a hex string into a JS bigint toBig toBigFn // Converts a hex string into a JS bigint
toBuf toBufFn // Converts a []byte into a JS buffer toBuf toBufFn // Converts a []byte into a JS buffer
fromBuf fromBufFn // Converts an array, hex string or Uint8Array to a []byte fromBuf fromBufFn // Converts an array, hex string or Uint8Array to a []byte
@ -103,7 +107,6 @@ type jsTracer struct {
activePrecompiles []common.Address // List of active precompiles at current block activePrecompiles []common.Address // List of active precompiles at current block
traceStep bool // True if tracer object exposes a `step()` method traceStep bool // True if tracer object exposes a `step()` method
traceFrame bool // True if tracer object exposes the `enter()` and `exit()` methods traceFrame bool // True if tracer object exposes the `enter()` and `exit()` methods
gasLimit uint64 // Amount of gas bought for the whole tx
err error // Any error that should stop tracing err error // Any error that should stop tracing
obj *goja.Object // Trace object obj *goja.Object // Trace object
@ -133,7 +136,7 @@ type jsTracer struct {
// The methods `result` and `fault` are required to be present. // The methods `result` and `fault` are required to be present.
// The methods `step`, `enter`, and `exit` are optional, but note that // The methods `step`, `enter`, and `exit` are optional, but note that
// `enter` and `exit` always go together. // `enter` and `exit` always go together.
func newJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { func newJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) {
vm := goja.New() vm := goja.New()
// By default field names are exported to JS as is, i.e. capitalized. // By default field names are exported to JS as is, i.e. capitalized.
vm.SetFieldNameMapper(goja.UncapFieldNameMapper()) vm.SetFieldNameMapper(goja.UncapFieldNameMapper())
@ -216,30 +219,62 @@ func newJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage) (tracer
t.frameValue = t.frame.setupObject() t.frameValue = t.frame.setupObject()
t.frameResultValue = t.frameResult.setupObject() t.frameResultValue = t.frameResult.setupObject()
t.logValue = t.log.setupObject() t.logValue = t.log.setupObject()
return t, nil
return &tracers.Tracer{
Hooks: &tracing.Hooks{
OnTxStart: t.OnTxStart,
OnTxEnd: t.OnTxEnd,
OnEnter: t.OnEnter,
OnExit: t.OnExit,
OnOpcode: t.OnOpcode,
OnFault: t.OnFault,
},
GetResult: t.GetResult,
Stop: t.Stop,
}, nil
} }
// CaptureTxStart implements the Tracer interface and is invoked at the beginning of // OnTxStart implements the Tracer interface and is invoked at the beginning of
// transaction processing. // transaction processing.
func (t *jsTracer) CaptureTxStart(gasLimit uint64) { func (t *jsTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
t.gasLimit = gasLimit
}
// CaptureTxEnd implements the Tracer interface and is invoked at the end of
// transaction processing.
func (t *jsTracer) CaptureTxEnd(restGas uint64) {
t.ctx["gasUsed"] = t.vm.ToValue(t.gasLimit - restGas)
}
// CaptureStart implements the Tracer interface to initialize the tracing operation.
func (t *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
cancel := func(err error) {
t.err = err
t.env.Cancel()
}
t.env = env t.env = env
// Need statedb access for db object
db := &dbObj{db: env.StateDB, vm: t.vm, toBig: t.toBig, toBuf: t.toBuf, fromBuf: t.fromBuf} db := &dbObj{db: env.StateDB, vm: t.vm, toBig: t.toBig, toBuf: t.toBuf, fromBuf: t.fromBuf}
t.dbValue = db.setupObject() t.dbValue = db.setupObject()
// Update list of precompiles based on current block
rules := env.ChainConfig.Rules(env.BlockNumber)
t.activePrecompiles = vm.ActivePrecompiles(rules)
t.ctx["block"] = t.vm.ToValue(t.env.BlockNumber.Uint64())
t.ctx["gas"] = t.vm.ToValue(tx.Gas())
gasPriceBig, err := t.toBig(t.vm, env.GasPrice.String())
if err != nil {
t.err = err
return
}
t.ctx["gasPrice"] = gasPriceBig
}
// OnTxEnd implements the Tracer interface and is invoked at the end of
// transaction processing.
func (t *jsTracer) OnTxEnd(receipt *types.Receipt, err error) {
if t.err != nil {
return
}
if err != nil {
// Don't override vm error
if _, ok := t.ctx["error"]; !ok {
t.ctx["error"] = t.vm.ToValue(err.Error())
}
return
}
t.ctx["gasUsed"] = t.vm.ToValue(receipt.GasUsed)
}
// onStart implements the Tracer interface to initialize the tracing operation.
func (t *jsTracer) onStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
if t.err != nil {
return
}
if create { if create {
t.ctx["type"] = t.vm.ToValue("CREATE") t.ctx["type"] = t.vm.ToValue("CREATE")
} else { } else {
@ -247,43 +282,32 @@ func (t *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Addr
} }
fromVal, err := t.toBuf(t.vm, from.Bytes()) fromVal, err := t.toBuf(t.vm, from.Bytes())
if err != nil { if err != nil {
cancel(err) t.err = err
return return
} }
t.ctx["from"] = fromVal t.ctx["from"] = fromVal
toVal, err := t.toBuf(t.vm, to.Bytes()) toVal, err := t.toBuf(t.vm, to.Bytes())
if err != nil { if err != nil {
cancel(err) t.err = err
return return
} }
t.ctx["to"] = toVal t.ctx["to"] = toVal
inputVal, err := t.toBuf(t.vm, input) inputVal, err := t.toBuf(t.vm, input)
if err != nil { if err != nil {
cancel(err) t.err = err
return return
} }
t.ctx["input"] = inputVal t.ctx["input"] = inputVal
t.ctx["gas"] = t.vm.ToValue(t.gasLimit)
gasPriceBig, err := t.toBig(t.vm, env.TxContext.GasPrice.String())
if err != nil {
cancel(err)
return
}
t.ctx["gasPrice"] = gasPriceBig
valueBig, err := t.toBig(t.vm, value.String()) valueBig, err := t.toBig(t.vm, value.String())
if err != nil { if err != nil {
cancel(err) t.err = err
return return
} }
t.ctx["value"] = valueBig t.ctx["value"] = valueBig
t.ctx["block"] = t.vm.ToValue(env.Context.BlockNumber.Uint64())
// Update list of precompiles based on current block
rules := env.ChainConfig().Rules(env.Context.BlockNumber)
t.activePrecompiles = vm.ActivePrecompiles(rules)
} }
// CaptureState implements the Tracer interface to trace a single step of VM execution. // OnOpcode implements the Tracer interface to trace a single step of VM execution.
func (t *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { func (t *jsTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
if !t.traceStep { if !t.traceStep {
return return
} }
@ -292,10 +316,10 @@ func (t *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope
} }
log := t.log log := t.log
log.op.op = op log.op.op = vm.OpCode(op)
log.memory.memory = scope.Memory log.memory.memory = scope.MemoryData()
log.stack.stack = scope.Stack log.stack.stack = scope.StackData()
log.contract.contract = scope.Contract log.contract.scope = scope
log.pc = pc log.pc = pc
log.gas = gas log.gas = gas
log.cost = cost log.cost = cost
@ -307,20 +331,23 @@ func (t *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope
} }
} }
// CaptureFault implements the Tracer interface to trace an execution fault // OnFault implements the Tracer interface to trace an execution fault
func (t *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { func (t *jsTracer) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) {
if t.err != nil { if t.err != nil {
return return
} }
// Other log fields have been already set as part of the last CaptureState. // Other log fields have been already set as part of the last OnOpcode.
t.log.err = err t.log.err = err
if _, err := t.fault(t.obj, t.logValue, t.dbValue); err != nil { if _, err := t.fault(t.obj, t.logValue, t.dbValue); err != nil {
t.onError("fault", err) t.onError("fault", err)
} }
} }
// CaptureEnd is called after the call finishes to finalize the tracing. // onEnd is called after the call finishes to finalize the tracing.
func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { func (t *jsTracer) onEnd(output []byte, gasUsed uint64, err error, reverted bool) {
if t.err != nil {
return
}
if err != nil { if err != nil {
t.ctx["error"] = t.vm.ToValue(err.Error()) t.ctx["error"] = t.vm.ToValue(err.Error())
} }
@ -332,16 +359,20 @@ func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
t.ctx["output"] = outputVal t.ctx["output"] = outputVal
} }
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). // OnEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *jsTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { func (t *jsTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
if !t.traceFrame {
return
}
if t.err != nil { if t.err != nil {
return return
} }
if depth == 0 {
t.onStart(from, to, vm.OpCode(typ) == vm.CREATE, input, gas, value)
return
}
if !t.traceFrame {
return
}
t.frame.typ = typ.String() t.frame.typ = vm.OpCode(typ).String()
t.frame.from = from t.frame.from = from
t.frame.to = to t.frame.to = to
t.frame.input = common.CopyBytes(input) t.frame.input = common.CopyBytes(input)
@ -356,9 +387,16 @@ func (t *jsTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Ad
} }
} }
// CaptureExit is called when EVM exits a scope, even if the scope didn't // OnExit is called when EVM exits a scope, even if the scope didn't
// execute any code. // execute any code.
func (t *jsTracer) CaptureExit(output []byte, gasUsed uint64, err error) { func (t *jsTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
if t.err != nil {
return
}
if depth == 0 {
t.onEnd(output, gasUsed, err, reverted)
return
}
if !t.traceFrame { if !t.traceFrame {
return return
} }
@ -374,6 +412,9 @@ func (t *jsTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
// GetResult calls the Javascript 'result' function and returns its value, or any accumulated error // GetResult calls the Javascript 'result' function and returns its value, or any accumulated error
func (t *jsTracer) GetResult() (json.RawMessage, error) { func (t *jsTracer) GetResult() (json.RawMessage, error) {
if t.err != nil {
return nil, t.err
}
ctx := t.vm.ToValue(t.ctx) ctx := t.vm.ToValue(t.ctx)
res, err := t.result(t.obj, ctx, t.dbValue) res, err := t.result(t.obj, ctx, t.dbValue)
if err != nil { if err != nil {
@ -383,7 +424,7 @@ func (t *jsTracer) GetResult() (json.RawMessage, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return json.RawMessage(encoded), t.err return encoded, t.err
} }
// Stop terminates execution of the tracer at the first opportune moment. // Stop terminates execution of the tracer at the first opportune moment.
@ -396,9 +437,6 @@ func (t *jsTracer) Stop(err error) {
// execution. // execution.
func (t *jsTracer) onError(context string, err error) { func (t *jsTracer) onError(context string, err error) {
t.err = wrapError(context, err) t.err = wrapError(context, err)
// `env` is set on CaptureStart which comes before any JS execution.
// So it should be non-nil.
t.env.Cancel()
} }
func wrapError(context string, err error) error { func wrapError(context string, err error) error {
@ -577,7 +615,7 @@ func (o *opObj) setupObject() *goja.Object {
} }
type memoryObj struct { type memoryObj struct {
memory *vm.Memory memory []byte
vm *goja.Runtime vm *goja.Runtime
toBig toBigFn toBig toBigFn
toBuf toBufFn toBuf toBufFn
@ -605,7 +643,7 @@ func (mo *memoryObj) slice(begin, end int64) ([]byte, error) {
if end < begin || begin < 0 { if end < begin || begin < 0 {
return nil, fmt.Errorf("tracer accessed out of bound memory: offset %d, end %d", begin, end) return nil, fmt.Errorf("tracer accessed out of bound memory: offset %d, end %d", begin, end)
} }
slice, err := tracers.GetMemoryCopyPadded(mo.memory, begin, end-begin) slice, err := internal.GetMemoryCopyPadded(mo.memory, begin, end-begin)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -628,14 +666,14 @@ func (mo *memoryObj) GetUint(addr int64) goja.Value {
// getUint returns the 32 bytes at the specified address interpreted as a uint. // getUint returns the 32 bytes at the specified address interpreted as a uint.
func (mo *memoryObj) getUint(addr int64) (*big.Int, error) { func (mo *memoryObj) getUint(addr int64) (*big.Int, error) {
if mo.memory.Len() < int(addr)+32 || addr < 0 { if len(mo.memory) < int(addr)+32 || addr < 0 {
return nil, fmt.Errorf("tracer accessed out of bound memory: available %d, offset %d, size %d", mo.memory.Len(), addr, 32) return nil, fmt.Errorf("tracer accessed out of bound memory: available %d, offset %d, size %d", len(mo.memory), addr, 32)
} }
return new(big.Int).SetBytes(mo.memory.GetPtr(addr, 32)), nil return new(big.Int).SetBytes(internal.MemoryPtr(mo.memory, addr, 32)), nil
} }
func (mo *memoryObj) Length() int { func (mo *memoryObj) Length() int {
return mo.memory.Len() return len(mo.memory)
} }
func (m *memoryObj) setupObject() *goja.Object { func (m *memoryObj) setupObject() *goja.Object {
@ -647,7 +685,7 @@ func (m *memoryObj) setupObject() *goja.Object {
} }
type stackObj struct { type stackObj struct {
stack *vm.Stack stack []uint256.Int
vm *goja.Runtime vm *goja.Runtime
toBig toBigFn toBig toBigFn
} }
@ -668,14 +706,14 @@ func (s *stackObj) Peek(idx int) goja.Value {
// peek returns the nth-from-the-top element of the stack. // peek returns the nth-from-the-top element of the stack.
func (s *stackObj) peek(idx int) (*big.Int, error) { func (s *stackObj) peek(idx int) (*big.Int, error) {
if len(s.stack.Data()) <= idx || idx < 0 { if len(s.stack) <= idx || idx < 0 {
return nil, fmt.Errorf("tracer accessed out of bound stack: size %d, index %d", len(s.stack.Data()), idx) return nil, fmt.Errorf("tracer accessed out of bound stack: size %d, index %d", len(s.stack), idx)
} }
return s.stack.Back(idx).ToBig(), nil return internal.StackBack(s.stack, idx).ToBig(), nil
} }
func (s *stackObj) Length() int { func (s *stackObj) Length() int {
return len(s.stack.Data()) return len(s.stack)
} }
func (s *stackObj) setupObject() *goja.Object { func (s *stackObj) setupObject() *goja.Object {
@ -686,7 +724,7 @@ func (s *stackObj) setupObject() *goja.Object {
} }
type dbObj struct { type dbObj struct {
db vm.StateDB db tracing.StateDB
vm *goja.Runtime vm *goja.Runtime
toBig toBigFn toBig toBigFn
toBuf toBufFn toBuf toBufFn
@ -778,14 +816,14 @@ func (do *dbObj) setupObject() *goja.Object {
} }
type contractObj struct { type contractObj struct {
contract *vm.Contract scope tracing.OpContext
vm *goja.Runtime vm *goja.Runtime
toBig toBigFn toBig toBigFn
toBuf toBufFn toBuf toBufFn
} }
func (co *contractObj) GetCaller() goja.Value { func (co *contractObj) GetCaller() goja.Value {
caller := co.contract.Caller().Bytes() caller := co.scope.Caller().Bytes()
res, err := co.toBuf(co.vm, caller) res, err := co.toBuf(co.vm, caller)
if err != nil { if err != nil {
co.vm.Interrupt(err) co.vm.Interrupt(err)
@ -795,7 +833,7 @@ func (co *contractObj) GetCaller() goja.Value {
} }
func (co *contractObj) GetAddress() goja.Value { func (co *contractObj) GetAddress() goja.Value {
addr := co.contract.Address().Bytes() addr := co.scope.Address().Bytes()
res, err := co.toBuf(co.vm, addr) res, err := co.toBuf(co.vm, addr)
if err != nil { if err != nil {
co.vm.Interrupt(err) co.vm.Interrupt(err)
@ -805,7 +843,7 @@ func (co *contractObj) GetAddress() goja.Value {
} }
func (co *contractObj) GetValue() goja.Value { func (co *contractObj) GetValue() goja.Value {
value := co.contract.Value() value := co.scope.CallValue()
res, err := co.toBig(co.vm, value.String()) res, err := co.toBig(co.vm, value.String())
if err != nil { if err != nil {
co.vm.Interrupt(err) co.vm.Interrupt(err)
@ -815,7 +853,7 @@ func (co *contractObj) GetValue() goja.Value {
} }
func (co *contractObj) GetInput() goja.Value { func (co *contractObj) GetInput() goja.Value {
input := common.CopyBytes(co.contract.Input) input := common.CopyBytes(co.scope.CallInput())
res, err := co.toBuf(co.vm, input) res, err := co.toBuf(co.vm, input)
if err != nil { if err != nil {
co.vm.Interrupt(err) co.vm.Interrupt(err)

View file

@ -26,6 +26,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/eth/tracers" "github.com/XinFinOrg/XDPoSChain/eth/tracers"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
@ -60,9 +61,9 @@ func testCtx() *vmContext {
return &vmContext{ctx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txContext: vm.TxContext{GasPrice: big.NewInt(100000)}} return &vmContext{ctx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txContext: vm.TxContext{GasPrice: big.NewInt(100000)}}
} }
func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainConfig, contractCode []byte) (json.RawMessage, error) { func runTrace(tracer *tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainConfig, contractCode []byte) (json.RawMessage, error) {
var ( var (
env = vm.NewEVM(vmctx.ctx, vmctx.txContext, &dummyStatedb{}, nil, chaincfg, vm.Config{Tracer: tracer}) env = vm.NewEVM(vmctx.ctx, vmctx.txContext, &dummyStatedb{}, nil, chaincfg, vm.Config{Tracer: tracer.Hooks})
gasLimit uint64 = 31000 gasLimit uint64 = 31000
startGas uint64 = 10000 startGas uint64 = 10000
value = big.NewInt(0) value = big.NewInt(0)
@ -73,12 +74,12 @@ func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCon
contract.Code = contractCode contract.Code = contractCode
} }
tracer.CaptureTxStart(gasLimit) tracer.OnTxStart(env.GetVMContext(), types.NewTx(&types.LegacyTx{Gas: gasLimit}), contract.Caller())
tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, value) tracer.OnEnter(0, byte(vm.CALL), contract.Caller(), contract.Address(), []byte{}, startGas, value)
ret, err := env.Interpreter().Run(contract, []byte{}, false) ret, err := env.Interpreter().Run(contract, []byte{}, false)
tracer.CaptureEnd(ret, startGas-contract.Gas, err) tracer.OnExit(0, ret, startGas-contract.Gas, err, true)
// Rest gas assumes no refund // Rest gas assumes no refund
tracer.CaptureTxEnd(contract.Gas) tracer.OnTxEnd(&types.Receipt{GasUsed: gasLimit - contract.Gas}, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -180,15 +181,16 @@ func TestHaltBetweenSteps(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{GasPrice: big.NewInt(1)}, &dummyStatedb{}, nil, params.TestChainConfig, vm.Config{Tracer: tracer})
scope := &vm.ScopeContext{ scope := &vm.ScopeContext{
Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0), Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0),
} }
tracer.CaptureStart(env, common.Address{}, common.Address{}, false, []byte{}, 0, big.NewInt(0)) env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{GasPrice: big.NewInt(1)}, &dummyStatedb{}, nil, params.TestChainConfig, vm.Config{Tracer: tracer.Hooks})
tracer.CaptureState(0, 0, 0, 0, scope, nil, 0, nil) tracer.OnTxStart(env.GetVMContext(), types.NewTx(&types.LegacyTx{}), common.Address{})
tracer.OnEnter(0, byte(vm.CALL), common.Address{}, common.Address{}, []byte{}, 0, big.NewInt(0))
tracer.OnOpcode(0, 0, 0, 0, scope, nil, 0, nil)
timeout := errors.New("stahp") timeout := errors.New("stahp")
tracer.Stop(timeout) tracer.Stop(timeout)
tracer.CaptureState(0, 0, 0, 0, scope, nil, 0, nil) tracer.OnOpcode(0, 0, 0, 0, scope, nil, 0, nil)
if _, err := tracer.GetResult(); !strings.Contains(err.Error(), timeout.Error()) { if _, err := tracer.GetResult(); !strings.Contains(err.Error(), timeout.Error()) {
t.Errorf("Expected timeout error, got %v", err) t.Errorf("Expected timeout error, got %v", err)
@ -204,9 +206,10 @@ func TestNoStepExec(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{GasPrice: big.NewInt(100000)}, &dummyStatedb{}, nil, params.TestChainConfig, vm.Config{Tracer: tracer}) env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{GasPrice: big.NewInt(100)}, &dummyStatedb{}, nil, params.TestChainConfig, vm.Config{Tracer: tracer.Hooks})
tracer.CaptureStart(env, common.Address{}, common.Address{}, false, []byte{}, 1000, big.NewInt(0)) tracer.OnTxStart(env.GetVMContext(), types.NewTx(&types.LegacyTx{}), common.Address{})
tracer.CaptureEnd(nil, 0, nil) tracer.OnEnter(0, byte(vm.CALL), common.Address{}, common.Address{}, []byte{}, 1000, big.NewInt(0))
tracer.OnExit(0, nil, 0, nil, false)
ret, err := tracer.GetResult() ret, err := tracer.GetResult()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -293,8 +296,8 @@ func TestEnterExit(t *testing.T) {
scope := &vm.ScopeContext{ scope := &vm.ScopeContext{
Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0), Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0),
} }
tracer.CaptureEnter(vm.CALL, scope.Contract.Caller(), scope.Contract.Address(), []byte{}, 1000, new(big.Int)) tracer.OnEnter(1, byte(vm.CALL), scope.Contract.Caller(), scope.Contract.Address(), []byte{}, 1000, new(big.Int))
tracer.CaptureExit([]byte{}, 400, nil) tracer.OnExit(1, []byte{}, 400, nil, false)
have, err := tracer.GetResult() have, err := tracer.GetResult()
if err != nil { if err != nil {

31
eth/tracers/live.go Normal file
View file

@ -0,0 +1,31 @@
package tracers
import (
"encoding/json"
"errors"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
)
type ctorFunc func(config json.RawMessage) (*tracing.Hooks, error)
// LiveDirectory is the collection of tracers which can be used
// during normal block import operations.
var LiveDirectory = liveDirectory{elems: make(map[string]ctorFunc)}
type liveDirectory struct {
elems map[string]ctorFunc
}
// Register registers a tracer constructor by name.
func (d *liveDirectory) Register(name string, f ctorFunc) {
d.elems[name] = f
}
// New instantiates a tracer by name.
func (d *liveDirectory) New(name string, config json.RawMessage) (*tracing.Hooks, error) {
if f, ok := d.elems[name]; ok {
return f(config)
}
return nil, errors.New("not found")
}

96
eth/tracers/live/noop.go Normal file
View file

@ -0,0 +1,96 @@
package live
import (
"encoding/json"
"math/big"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/eth/tracers"
"github.com/XinFinOrg/XDPoSChain/params"
)
func init() {
tracers.LiveDirectory.Register("noop", newNoopTracer)
}
// noop is a no-op live tracer. It's there to
// catch changes in the tracing interface, as well as
// for testing live tracing performance. Can be removed
// as soon as we have a real live tracer.
type noop struct{}
func newNoopTracer(_ json.RawMessage) (*tracing.Hooks, error) {
t := &noop{}
return &tracing.Hooks{
OnTxStart: t.OnTxStart,
OnTxEnd: t.OnTxEnd,
OnEnter: t.OnEnter,
OnExit: t.OnExit,
OnOpcode: t.OnOpcode,
OnFault: t.OnFault,
OnGasChange: t.OnGasChange,
OnBlockchainInit: t.OnBlockchainInit,
OnBlockStart: t.OnBlockStart,
OnBlockEnd: t.OnBlockEnd,
OnSkippedBlock: t.OnSkippedBlock,
OnGenesisBlock: t.OnGenesisBlock,
OnBalanceChange: t.OnBalanceChange,
OnNonceChange: t.OnNonceChange,
OnCodeChange: t.OnCodeChange,
OnStorageChange: t.OnStorageChange,
OnLog: t.OnLog,
}, nil
}
func (t *noop) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
}
func (t *noop) OnFault(pc uint64, op byte, gas, cost uint64, _ tracing.OpContext, depth int, err error) {
}
func (t *noop) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
}
func (t *noop) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
}
func (t *noop) OnTxStart(vm *tracing.VMContext, tx *types.Transaction, from common.Address) {
}
func (t *noop) OnTxEnd(receipt *types.Receipt, err error) {
}
func (t *noop) OnBlockStart(ev tracing.BlockEvent) {
}
func (t *noop) OnBlockEnd(err error) {
}
func (t *noop) OnSkippedBlock(ev tracing.BlockEvent) {}
func (t *noop) OnBlockchainInit(chainConfig *params.ChainConfig) {
}
func (t *noop) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) {
}
func (t *noop) OnBalanceChange(a common.Address, prev, new *big.Int, reason tracing.BalanceChangeReason) {
}
func (t *noop) OnNonceChange(a common.Address, prev, new uint64) {
}
func (t *noop) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) {
}
func (t *noop) OnStorageChange(a common.Address, k, prev, new common.Hash) {
}
func (t *noop) OnLog(l *types.Log) {
}
func (t *noop) OnGasChange(old, new uint64, reason tracing.GasChangeReason) {
}

View file

@ -17,9 +17,8 @@
package logger package logger
import ( import (
"math/big"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
) )
@ -132,17 +131,20 @@ func NewAccessListTracer(acl types.AccessList, from, to common.Address, precompi
} }
} }
func (a *AccessListTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { func (a *AccessListTracer) Hooks() *tracing.Hooks {
return &tracing.Hooks{
OnOpcode: a.OnOpcode,
}
} }
// CaptureState captures all opcodes that touch storage or addresses and adds them to the accesslist. // OnOpcode captures all opcodes that touch storage or addresses and adds them to the accesslist.
func (a *AccessListTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { func (a *AccessListTracer) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
stack := scope.Stack stackData := scope.StackData()
stackData := stack.Data()
stackLen := len(stackData) stackLen := len(stackData)
op := vm.OpCode(opcode)
if (op == vm.SLOAD || op == vm.SSTORE) && stackLen >= 1 { if (op == vm.SLOAD || op == vm.SSTORE) && stackLen >= 1 {
slot := common.Hash(stackData[stackLen-1].Bytes32()) slot := common.Hash(stackData[stackLen-1].Bytes32())
a.list.addSlot(scope.Contract.Address(), slot) a.list.addSlot(scope.Address(), slot)
} }
if (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT) && stackLen >= 1 { if (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT) && stackLen >= 1 {
addr := common.Address(stackData[stackLen-1].Bytes20()) addr := common.Address(stackData[stackLen-1].Bytes20())
@ -158,20 +160,6 @@ func (a *AccessListTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint6
} }
} }
func (*AccessListTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
}
func (*AccessListTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {}
func (*AccessListTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
}
func (*AccessListTracer) CaptureExit(output []byte, gasUsed uint64, err error) {}
func (*AccessListTracer) CaptureTxStart(gasLimit uint64) {}
func (*AccessListTracer) CaptureTxEnd(restGas uint64) {}
// AccessList returns the current accesslist maintained by the tracer. // AccessList returns the current accesslist maintained by the tracer.
func (a *AccessListTracer) AccessList() types.AccessList { func (a *AccessListTracer) AccessList() types.AccessList {
return a.list.accessList() return a.list.accessList()

View file

@ -28,6 +28,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/hexutil" "github.com/XinFinOrg/XDPoSChain/common/hexutil"
"github.com/XinFinOrg/XDPoSChain/common/math" "github.com/XinFinOrg/XDPoSChain/common/math"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
@ -108,14 +109,13 @@ func (s *StructLog) ErrorString() string {
// contract their storage. // contract their storage.
type StructLogger struct { type StructLogger struct {
cfg Config cfg Config
env *vm.EVM env *tracing.VMContext
storage map[common.Address]Storage storage map[common.Address]Storage
logs []StructLog logs []StructLog
output []byte output []byte
err error err error
gasLimit uint64 usedGas uint64
usedGas uint64
interrupt atomic.Bool // Atomic flag to signal execution interruption interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption reason error // Textual reason for the interruption
@ -132,6 +132,15 @@ func NewStructLogger(cfg *Config) *StructLogger {
return logger return logger
} }
func (l *StructLogger) Hooks() *tracing.Hooks {
return &tracing.Hooks{
OnTxStart: l.OnTxStart,
OnTxEnd: l.OnTxEnd,
OnExit: l.OnExit,
OnOpcode: l.OnOpcode,
}
}
// Reset clears the data held by the logger. // Reset clears the data held by the logger.
func (l *StructLogger) Reset() { func (l *StructLogger) Reset() {
l.storage = make(map[common.Address]Storage) l.storage = make(map[common.Address]Storage)
@ -140,15 +149,10 @@ func (l *StructLogger) Reset() {
l.err = nil l.err = nil
} }
// CaptureStart implements the EVMLogger interface to initialize the tracing operation. // OnOpcode logs a new structured log message and pushes it out to the environment
func (l *StructLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
l.env = env
}
// CaptureState logs a new structured log message and pushes it out to the environment
// //
// CaptureState also tracks SLOAD/SSTORE ops to track storage change. // OnOpcode also tracks SLOAD/SSTORE ops to track storage change.
func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
// If tracing was interrupted, set the error and stop // If tracing was interrupted, set the error and stop
if l.interrupt.Load() { if l.interrupt.Load() {
return return
@ -158,49 +162,47 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s
return return
} }
memory := scope.Memory op := vm.OpCode(opcode)
stack := scope.Stack memory := scope.MemoryData()
contract := scope.Contract stack := scope.StackData()
// Copy a snapshot of the current memory state to a new buffer // Copy a snapshot of the current memory state to a new buffer
var mem []byte var mem []byte
if l.cfg.EnableMemory { if l.cfg.EnableMemory {
mem = make([]byte, len(memory.Data())) mem = make([]byte, len(memory))
copy(mem, memory.Data()) copy(mem, memory)
} }
// Copy a snapshot of the current stack state to a new buffer // Copy a snapshot of the current stack state to a new buffer
var stck []uint256.Int var stck []uint256.Int
if !l.cfg.DisableStack { if !l.cfg.DisableStack {
stck = make([]uint256.Int, len(stack.Data())) stck = make([]uint256.Int, len(stack))
for i, item := range stack.Data() { copy(stck, stack)
stck[i] = item
}
} }
stackData := stack.Data() contractAddr := scope.Address()
stackLen := len(stackData) stackLen := len(stack)
// Copy a snapshot of the current storage to a new container // Copy a snapshot of the current storage to a new container
var storage Storage var storage Storage
if !l.cfg.DisableStorage && (op == vm.SLOAD || op == vm.SSTORE) { if !l.cfg.DisableStorage && (op == vm.SLOAD || op == vm.SSTORE) {
// initialise new changed values storage container for this contract // initialise new changed values storage container for this contract
// if not present. // if not present.
if l.storage[contract.Address()] == nil { if l.storage[contractAddr] == nil {
l.storage[contract.Address()] = make(Storage) l.storage[contractAddr] = make(Storage)
} }
// capture SLOAD opcodes and record the read entry in the local storage // capture SLOAD opcodes and record the read entry in the local storage
if op == vm.SLOAD && stackLen >= 1 { if op == vm.SLOAD && stackLen >= 1 {
var ( var (
address = common.Hash(stackData[stackLen-1].Bytes32()) address = common.Hash(stack[stackLen-1].Bytes32())
value = l.env.StateDB.GetState(contract.Address(), address) value = l.env.StateDB.GetState(contractAddr, address)
) )
l.storage[contract.Address()][address] = value l.storage[contractAddr][address] = value
storage = l.storage[contract.Address()].Copy() storage = l.storage[contractAddr].Copy()
} else if op == vm.SSTORE && stackLen >= 2 { } else if op == vm.SSTORE && stackLen >= 2 {
// capture SSTORE opcodes and record the written entry in the local storage. // capture SSTORE opcodes and record the written entry in the local storage.
var ( var (
value = common.Hash(stackData[stackLen-2].Bytes32()) value = common.Hash(stack[stackLen-2].Bytes32())
address = common.Hash(stackData[stackLen-1].Bytes32()) address = common.Hash(stack[stackLen-1].Bytes32())
) )
l.storage[contract.Address()][address] = value l.storage[contractAddr][address] = value
storage = l.storage[contract.Address()].Copy() storage = l.storage[contractAddr].Copy()
} }
} }
var rdata []byte var rdata []byte
@ -209,17 +211,15 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s
copy(rdata, rData) copy(rdata, rData)
} }
// create a new snapshot of the EVM. // create a new snapshot of the EVM.
log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err} log := StructLog{pc, op, gas, cost, mem, len(memory), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err}
l.logs = append(l.logs, log) l.logs = append(l.logs, log)
} }
// CaptureFault implements the EVMLogger interface to trace an execution fault // OnExit is called a call frame finishes processing.
// while running an opcode. func (l *StructLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
func (l *StructLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { if depth != 0 {
} return
}
// CaptureEnd is called after the call finishes to finalize the tracing.
func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
l.output = output l.output = output
l.err = err l.err = err
if l.cfg.Debug { if l.cfg.Debug {
@ -230,12 +230,6 @@ func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
} }
} }
func (l *StructLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
}
func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {
}
func (l *StructLogger) GetResult() (json.RawMessage, error) { func (l *StructLogger) GetResult() (json.RawMessage, error) {
// Tracing aborted // Tracing aborted
if l.reason != nil { if l.reason != nil {
@ -262,12 +256,19 @@ func (l *StructLogger) Stop(err error) {
l.interrupt.Store(true) l.interrupt.Store(true)
} }
func (l *StructLogger) CaptureTxStart(gasLimit uint64) { func (l *StructLogger) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
l.gasLimit = gasLimit l.env = env
} }
func (l *StructLogger) CaptureTxEnd(restGas uint64) { func (l *StructLogger) OnTxEnd(receipt *types.Receipt, err error) {
l.usedGas = l.gasLimit - restGas if err != nil {
// Don't override vm error
if l.err == nil {
l.err = err
}
return
}
l.usedGas = receipt.GasUsed
} }
// StructLogs returns the captured log entries. // StructLogs returns the captured log entries.
@ -329,7 +330,7 @@ func WriteLogs(writer io.Writer, logs []*types.Log) {
type mdLogger struct { type mdLogger struct {
out io.Writer out io.Writer
cfg *Config cfg *Config
env *vm.EVM env *tracing.VMContext
} }
// NewMarkdownLogger creates a logger which outputs information in a format adapted // NewMarkdownLogger creates a logger which outputs information in a format adapted
@ -342,8 +343,25 @@ func NewMarkdownLogger(cfg *Config, writer io.Writer) *mdLogger {
return l return l
} }
func (t *mdLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { func (t *mdLogger) Hooks() *tracing.Hooks {
return &tracing.Hooks{
OnTxStart: t.OnTxStart,
OnEnter: t.OnEnter,
OnExit: t.OnExit,
OnOpcode: t.OnOpcode,
OnFault: t.OnFault,
}
}
func (t *mdLogger) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
t.env = env t.env = env
}
func (t *mdLogger) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
if depth != 0 {
return
}
create := vm.OpCode(typ) == vm.CREATE
if !create { if !create {
fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `%#x`\nGas: `%d`\nValue `%v` wei\n", fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `%#x`\nGas: `%d`\nValue `%v` wei\n",
from.String(), to.String(), from.String(), to.String(),
@ -360,15 +378,22 @@ func (t *mdLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Addr
`) `)
} }
// CaptureState also tracks SLOAD/SSTORE ops to track storage change. func (t *mdLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
func (t *mdLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { if depth == 0 {
stack := scope.Stack fmt.Fprintf(t.out, "\nOutput: `%#x`\nConsumed gas: `%d`\nError: `%v`\n",
output, gasUsed, err)
}
}
// OnOpcode also tracks SLOAD/SSTORE ops to track storage change.
func (t *mdLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
stack := scope.StackData()
fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost) fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost)
if !t.cfg.DisableStack { if !t.cfg.DisableStack {
// format stack // format stack
var a []string var a []string
for _, elem := range stack.Data() { for _, elem := range stack {
a = append(a, elem.Hex()) a = append(a, elem.Hex())
} }
b := fmt.Sprintf("[%v]", strings.Join(a, ",")) b := fmt.Sprintf("[%v]", strings.Join(a, ","))
@ -381,24 +406,10 @@ func (t *mdLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope
} }
} }
func (t *mdLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { func (t *mdLogger) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) {
fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err) fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err)
} }
func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
fmt.Fprintf(t.out, "\nOutput: `%#x`\nConsumed gas: `%d`\nError: `%v`\n",
output, gasUsed, err)
}
func (t *mdLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
}
func (t *mdLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
func (*mdLogger) CaptureTxStart(gasLimit uint64) {}
func (*mdLogger) CaptureTxEnd(restGas uint64) {}
// ExecutionResult groups all structured logs emitted by the EVM // ExecutionResult groups all structured logs emitted by the EVM
// while replaying a transaction in debug mode as well as transaction // while replaying a transaction in debug mode as well as transaction
// execution status, the amount of gas used and the return value // execution status, the amount of gas used and the return value

View file

@ -19,58 +19,59 @@ package logger
import ( import (
"encoding/json" "encoding/json"
"io" "io"
"math/big"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/math" "github.com/XinFinOrg/XDPoSChain/common/math"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
) )
type JSONLogger struct { type jsonLogger struct {
encoder *json.Encoder encoder *json.Encoder
cfg *Config cfg *Config
env *vm.EVM env *tracing.VMContext
} }
// NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects // NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects
// into the provided stream. // into the provided stream.
func NewJSONLogger(cfg *Config, writer io.Writer) *JSONLogger { func NewJSONLogger(cfg *Config, writer io.Writer) *tracing.Hooks {
l := &JSONLogger{encoder: json.NewEncoder(writer), cfg: cfg} l := &jsonLogger{encoder: json.NewEncoder(writer), cfg: cfg}
if l.cfg == nil { if l.cfg == nil {
l.cfg = &Config{} l.cfg = &Config{}
} }
return l return &tracing.Hooks{
OnTxStart: l.OnTxStart,
OnExit: l.OnExit,
OnOpcode: l.OnOpcode,
OnFault: l.OnFault,
}
} }
func (l *JSONLogger) CaptureStart(env *vm.EVM, from, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { func (l *jsonLogger) OnFault(pc uint64, op byte, gas uint64, cost uint64, scope tracing.OpContext, depth int, err error) {
l.env = env
}
func (l *JSONLogger) CaptureFault(pc uint64, op vm.OpCode, gas uint64, cost uint64, scope *vm.ScopeContext, depth int, err error) {
// TODO: Add rData to this interface as well // TODO: Add rData to this interface as well
l.CaptureState(pc, op, gas, cost, scope, nil, depth, err) l.OnOpcode(pc, op, gas, cost, scope, nil, depth, err)
} }
// CaptureState outputs state information on the logger. func (l *jsonLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
func (l *JSONLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { memory := scope.MemoryData()
memory := scope.Memory stack := scope.StackData()
stack := scope.Stack
log := StructLog{ log := StructLog{
Pc: pc, Pc: pc,
Op: op, Op: vm.OpCode(op),
Gas: gas, Gas: gas,
GasCost: cost, GasCost: cost,
MemorySize: memory.Len(), MemorySize: len(memory),
Depth: depth, Depth: depth,
RefundCounter: l.env.StateDB.GetRefund(), RefundCounter: l.env.StateDB.GetRefund(),
Err: err, Err: err,
} }
if l.cfg.EnableMemory { if l.cfg.EnableMemory {
log.Memory = memory.Data() log.Memory = memory
} }
if !l.cfg.DisableStack { if !l.cfg.DisableStack {
log.Stack = stack.Data() log.Stack = stack
} }
if l.cfg.EnableReturnData { if l.cfg.EnableReturnData {
log.ReturnData = rData log.ReturnData = rData
@ -78,8 +79,10 @@ func (l *JSONLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, sco
l.encoder.Encode(log) l.encoder.Encode(log)
} }
// CaptureEnd is triggered at end of execution. func (l *jsonLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error) { if depth > 0 {
return
}
type endLog struct { type endLog struct {
Output string `json:"output"` Output string `json:"output"`
GasUsed math.HexOrDecimal64 `json:"gasUsed"` GasUsed math.HexOrDecimal64 `json:"gasUsed"`
@ -92,11 +95,6 @@ func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error) {
l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), errMsg}) l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), errMsg})
} }
func (l *JSONLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { func (l *jsonLogger) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
l.env = env
} }
func (l *JSONLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
func (l *JSONLogger) CaptureTxStart(gasLimit uint64) {}
func (l *JSONLogger) CaptureTxEnd(restGas uint64) {}

View file

@ -55,12 +55,12 @@ func (*dummyStatedb) SetState(_ common.Address, _ common.Hash, _ common.Hash) {}
func TestStoreCapture(t *testing.T) { func TestStoreCapture(t *testing.T) {
var ( var (
logger = NewStructLogger(nil) logger = NewStructLogger(nil)
env = vm.NewEVM(vm.BlockContext{}, vm.TxContext{}, &dummyStatedb{}, nil, params.TestChainConfig, vm.Config{Tracer: logger}) env = vm.NewEVM(vm.BlockContext{}, vm.TxContext{}, &dummyStatedb{}, nil, params.TestChainConfig, vm.Config{Tracer: logger.Hooks()})
contract = vm.NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), 100000) contract = vm.NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), 100000)
) )
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)} contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)}
var index common.Hash var index common.Hash
logger.CaptureStart(env, common.Address{}, contract.Address(), false, nil, 0, nil) logger.OnTxStart(env.GetVMContext(), nil, common.Address{})
_, err := env.Interpreter().Run(contract, []byte{}, false) _, err := env.Interpreter().Run(contract, []byte{}, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View file

@ -23,6 +23,8 @@ import (
"sync/atomic" "sync/atomic"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/eth/tracers" "github.com/XinFinOrg/XDPoSChain/eth/tracers"
) )
@ -46,20 +48,26 @@ func init() {
// 0xc281d19e-0: 1 // 0xc281d19e-0: 1
// } // }
type fourByteTracer struct { type fourByteTracer struct {
noopTracer
ids map[string]int // ids aggregates the 4byte ids found ids map[string]int // ids aggregates the 4byte ids found
interrupt atomic.Bool // Atomic flag to signal execution interruption interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption reason error // Textual reason for the interruption
activePrecompiles []common.Address // Updated on CaptureStart based on given rules activePrecompiles []common.Address // Updated on tx start based on given rules
} }
// newFourByteTracer returns a native go tracer which collects // newFourByteTracer returns a native go tracer which collects
// 4 byte-identifiers of a tx, and implements vm.EVMLogger. // 4 byte-identifiers of a tx, and implements vm.EVMLogger.
func newFourByteTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) { func newFourByteTracer(ctx *tracers.Context, _ json.RawMessage) (*tracers.Tracer, error) {
t := &fourByteTracer{ t := &fourByteTracer{
ids: make(map[string]int), ids: make(map[string]int),
} }
return t, nil return &tracers.Tracer{
Hooks: &tracing.Hooks{
OnTxStart: t.OnTxStart,
OnEnter: t.OnEnter,
},
GetResult: t.GetResult,
Stop: t.Stop,
}, nil
} }
// isPrecompiled returns whether the addr is a precompile. Logic borrowed from newJsTracer in eth/tracers/js/tracer.go // isPrecompiled returns whether the addr is a precompile. Logic borrowed from newJsTracer in eth/tracers/js/tracer.go
@ -78,20 +86,14 @@ func (t *fourByteTracer) store(id []byte, size int) {
t.ids[key] += 1 t.ids[key] += 1
} }
// CaptureStart implements the EVMLogger interface to initialize the tracing operation. func (t *fourByteTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
func (t *fourByteTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
// Update list of precompiles based on current block // Update list of precompiles based on current block
rules := env.ChainConfig().Rules(env.Context.BlockNumber) rules := env.ChainConfig.Rules(env.BlockNumber)
t.activePrecompiles = vm.ActivePrecompiles(rules) t.activePrecompiles = vm.ActivePrecompiles(rules)
// Save the outer calldata also
if len(input) >= 4 {
t.store(input[0:4], len(input)-4)
}
} }
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). // OnEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *fourByteTracer) CaptureEnter(op vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { func (t *fourByteTracer) OnEnter(depth int, opcode byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
// Skip if tracing was interrupted // Skip if tracing was interrupted
if t.interrupt.Load() { if t.interrupt.Load() {
return return
@ -99,6 +101,7 @@ func (t *fourByteTracer) CaptureEnter(op vm.OpCode, from common.Address, to comm
if len(input) < 4 { if len(input) < 4 {
return return
} }
op := vm.OpCode(opcode)
// primarily we want to avoid CREATE/CREATE2/SELFDESTRUCT // primarily we want to avoid CREATE/CREATE2/SELFDESTRUCT
if op != vm.DELEGATECALL && op != vm.STATICCALL && if op != vm.DELEGATECALL && op != vm.STATICCALL &&
op != vm.CALL && op != vm.CALLCODE { op != vm.CALL && op != vm.CALLCODE {

View file

@ -25,9 +25,10 @@ import (
"github.com/XinFinOrg/XDPoSChain/accounts/abi" "github.com/XinFinOrg/XDPoSChain/accounts/abi"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/hexutil" "github.com/XinFinOrg/XDPoSChain/common/hexutil"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/eth/tracers" "github.com/XinFinOrg/XDPoSChain/eth/tracers"
"github.com/XinFinOrg/XDPoSChain/log"
) )
//go:generate go run github.com/fjl/gencodec -type callFrame -field-override callFrameMarshaling -out gen_callframe_json.go //go:generate go run github.com/fjl/gencodec -type callFrame -field-override callFrameMarshaling -out gen_callframe_json.go
@ -59,7 +60,8 @@ type callFrame struct {
Logs []callLog `json:"logs,omitempty" rlp:"optional"` Logs []callLog `json:"logs,omitempty" rlp:"optional"`
// Placed at end on purpose. The RLP will be decoded to 0 instead of // Placed at end on purpose. The RLP will be decoded to 0 instead of
// nil if there are non-empty elements after in the struct. // nil if there are non-empty elements after in the struct.
Value *big.Int `json:"value,omitempty" rlp:"optional"` Value *big.Int `json:"value,omitempty" rlp:"optional"`
revertedSnapshot bool
} }
func (f callFrame) TypeString() string { func (f callFrame) TypeString() string {
@ -67,16 +69,17 @@ func (f callFrame) TypeString() string {
} }
func (f callFrame) failed() bool { func (f callFrame) failed() bool {
return len(f.Error) > 0 return len(f.Error) > 0 && f.revertedSnapshot
} }
func (f *callFrame) processOutput(output []byte, err error) { func (f *callFrame) processOutput(output []byte, err error, reverted bool) {
output = common.CopyBytes(output) output = common.CopyBytes(output)
if err == nil { if err == nil {
f.Output = output f.Output = output
return return
} }
f.Error = err.Error() f.Error = err.Error()
f.revertedSnapshot = reverted
if f.Type == vm.CREATE || f.Type == vm.CREATE2 { if f.Type == vm.CREATE || f.Type == vm.CREATE2 {
f.To = nil f.To = nil
} }
@ -102,10 +105,10 @@ type callFrameMarshaling struct {
} }
type callTracer struct { type callTracer struct {
noopTracer
callstack []callFrame callstack []callFrame
config callTracerConfig config callTracerConfig
gasLimit uint64 gasLimit uint64
depth int
interrupt atomic.Bool // Atomic flag to signal execution interruption interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption reason error // Textual reason for the interruption
} }
@ -117,7 +120,25 @@ type callTracerConfig struct {
// newCallTracer returns a native go tracer which tracks // newCallTracer returns a native go tracer which tracks
// call frames of a tx, and implements vm.EVMLogger. // call frames of a tx, and implements vm.EVMLogger.
func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) {
t, err := newCallTracerObject(ctx, cfg)
if err != nil {
return nil, err
}
return &tracers.Tracer{
Hooks: &tracing.Hooks{
OnTxStart: t.OnTxStart,
OnTxEnd: t.OnTxEnd,
OnEnter: t.OnEnter,
OnExit: t.OnExit,
OnLog: t.OnLog,
},
GetResult: t.GetResult,
Stop: t.Stop,
}, nil
}
func newCallTracerObject(ctx *tracers.Context, cfg json.RawMessage) (*callTracer, error) {
var config callTracerConfig var config callTracerConfig
if cfg != nil { if cfg != nil {
if err := json.Unmarshal(cfg, &config); err != nil { if err := json.Unmarshal(cfg, &config); err != nil {
@ -126,84 +147,13 @@ func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, e
} }
// First callframe contains tx context info // First callframe contains tx context info
// and is populated on start and end. // and is populated on start and end.
return &callTracer{callstack: make([]callFrame, 1), config: config}, nil return &callTracer{callstack: make([]callFrame, 0, 1), config: config}, nil
} }
// CaptureStart implements the EVMLogger interface to initialize the tracing operation. // OnEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { func (t *callTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
toCopy := to t.depth = depth
t.callstack[0] = callFrame{ if t.config.OnlyTopCall && depth > 0 {
Type: vm.CALL,
From: from,
To: &toCopy,
Input: common.CopyBytes(input),
Gas: t.gasLimit,
Value: value,
}
if create {
t.callstack[0].Type = vm.CREATE
}
}
// CaptureEnd is called after the call finishes to finalize the tracing.
func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
t.callstack[0].processOutput(output, err)
}
// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
func (t *callTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
// skip if the previous op caused an error
if err != nil {
return
}
// Only logs need to be captured via opcode processing
if !t.config.WithLog {
return
}
// Avoid processing nested calls when only caring about top call
if t.config.OnlyTopCall && depth > 1 {
return
}
// Skip if tracing was interrupted
if t.interrupt.Load() {
return
}
switch op {
case vm.LOG0, vm.LOG1, vm.LOG2, vm.LOG3, vm.LOG4:
size := int(op - vm.LOG0)
stack := scope.Stack
stackData := stack.Data()
// Don't modify the stack
mStart := stackData[len(stackData)-1]
mSize := stackData[len(stackData)-2]
topics := make([]common.Hash, size)
for i := 0; i < size; i++ {
topic := stackData[len(stackData)-2-(i+1)]
topics[i] = common.Hash(topic.Bytes32())
}
data, err := tracers.GetMemoryCopyPadded(scope.Memory, int64(mStart.Uint64()), int64(mSize.Uint64()))
if err != nil {
// mSize was unrealistically large
log.Warn("failed to copy CREATE2 input", "err", err, "tracer", "callTracer", "offset", mStart, "size", mSize)
return
}
log := callLog{
Address: scope.Contract.Address(),
Topics: topics,
Data: hexutil.Bytes(data),
Position: hexutil.Uint(len(t.callstack[len(t.callstack)-1].Calls)),
}
t.callstack[len(t.callstack)-1].Logs = append(t.callstack[len(t.callstack)-1].Logs, log)
}
}
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
if t.config.OnlyTopCall {
return return
} }
// Skip if tracing was interrupted // Skip if tracing was interrupted
@ -213,48 +163,92 @@ func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.
toCopy := to toCopy := to
call := callFrame{ call := callFrame{
Type: typ, Type: vm.OpCode(typ),
From: from, From: from,
To: &toCopy, To: &toCopy,
Input: common.CopyBytes(input), Input: common.CopyBytes(input),
Gas: gas, Gas: gas,
Value: value, Value: value,
} }
if depth == 0 {
call.Gas = t.gasLimit
}
t.callstack = append(t.callstack, call) t.callstack = append(t.callstack, call)
} }
// CaptureExit is called when EVM exits a scope, even if the scope didn't // OnExit is called when EVM exits a scope, even if the scope didn't
// execute any code. // execute any code.
func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) { func (t *callTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
if depth == 0 {
t.captureEnd(output, gasUsed, err, reverted)
return
}
t.depth = depth - 1
if t.config.OnlyTopCall { if t.config.OnlyTopCall {
return return
} }
size := len(t.callstack) size := len(t.callstack)
if size <= 1 { if size <= 1 {
return return
} }
// pop call // Pop call.
call := t.callstack[size-1] call := t.callstack[size-1]
t.callstack = t.callstack[:size-1] t.callstack = t.callstack[:size-1]
size -= 1 size -= 1
call.GasUsed = gasUsed call.GasUsed = gasUsed
call.processOutput(output, err) call.processOutput(output, err, reverted)
// Nest call into parent.
t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call) t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call)
} }
func (t *callTracer) CaptureTxStart(gasLimit uint64) { func (t *callTracer) captureEnd(output []byte, gasUsed uint64, err error, reverted bool) {
t.gasLimit = gasLimit if len(t.callstack) != 1 {
return
}
t.callstack[0].processOutput(output, err, reverted)
} }
func (t *callTracer) CaptureTxEnd(restGas uint64) { func (t *callTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
t.callstack[0].GasUsed = t.gasLimit - restGas t.gasLimit = tx.Gas()
}
func (t *callTracer) OnTxEnd(receipt *types.Receipt, err error) {
// Error happened during tx validation.
if err != nil {
return
}
t.callstack[0].GasUsed = receipt.GasUsed
if t.config.WithLog { if t.config.WithLog {
// Logs are not emitted when the call fails // Logs are not emitted when the call fails
clearFailedLogs(&t.callstack[0], false) clearFailedLogs(&t.callstack[0], false)
} }
} }
func (t *callTracer) OnLog(log *types.Log) {
// Only logs need to be captured via opcode processing
if !t.config.WithLog {
return
}
// Avoid processing nested calls when only caring about top call
if t.config.OnlyTopCall && t.depth > 0 {
return
}
// Skip if tracing was interrupted
if t.interrupt.Load() {
return
}
l := callLog{
Address: log.Address,
Topics: log.Topics,
Data: log.Data,
Position: hexutil.Uint(len(t.callstack[len(t.callstack)-1].Calls)),
}
t.callstack[len(t.callstack)-1].Logs = append(t.callstack[len(t.callstack)-1].Logs, l)
}
// GetResult returns the json-encoded nested list of call traces, and any // GetResult returns the json-encoded nested list of call traces, and any
// error arising from the encoding or forceful termination (via `Stop`). // error arising from the encoding or forceful termination (via `Stop`).
func (t *callTracer) GetResult() (json.RawMessage, error) { func (t *callTracer) GetResult() (json.RawMessage, error) {
@ -266,7 +260,7 @@ func (t *callTracer) GetResult() (json.RawMessage, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return json.RawMessage(res), t.reason return res, t.reason
} }
// Stop terminates execution of the tracer at the first opportune moment. // Stop terminates execution of the tracer at the first opportune moment.

View file

@ -25,6 +25,8 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/hexutil" "github.com/XinFinOrg/XDPoSChain/common/hexutil"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/eth/tracers" "github.com/XinFinOrg/XDPoSChain/eth/tracers"
) )
@ -112,7 +114,7 @@ type flatCallTracer struct {
config flatCallTracerConfig config flatCallTracerConfig
ctx *tracers.Context // Holds tracer context data ctx *tracers.Context // Holds tracer context data
reason error // Textual reason for the interruption reason error // Textual reason for the interruption
activePrecompiles []common.Address // Updated on CaptureStart based on given rules activePrecompiles []common.Address // Updated on tx start based on given rules
} }
type flatCallTracerConfig struct { type flatCallTracerConfig struct {
@ -121,7 +123,7 @@ type flatCallTracerConfig struct {
} }
// newFlatCallTracer returns a new flatCallTracer. // newFlatCallTracer returns a new flatCallTracer.
func newFlatCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { func newFlatCallTracer(ctx *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) {
var config flatCallTracerConfig var config flatCallTracerConfig
if cfg != nil { if cfg != nil {
if err := json.Unmarshal(cfg, &config); err != nil { if err := json.Unmarshal(cfg, &config); err != nil {
@ -131,45 +133,31 @@ func newFlatCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Trace
// Create inner call tracer with default configuration, don't forward // Create inner call tracer with default configuration, don't forward
// the OnlyTopCall or WithLog to inner for now // the OnlyTopCall or WithLog to inner for now
tracer, err := tracers.DefaultDirectory.New("callTracer", ctx, nil) t, err := newCallTracerObject(ctx, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
t, ok := tracer.(*callTracer)
if !ok { ft := &flatCallTracer{tracer: t, ctx: ctx, config: config}
return nil, errors.New("internal error: embedded tracer has wrong type") return &tracers.Tracer{
Hooks: &tracing.Hooks{
OnTxStart: ft.OnTxStart,
OnTxEnd: ft.OnTxEnd,
OnEnter: ft.OnEnter,
OnExit: ft.OnExit,
},
Stop: ft.Stop,
GetResult: ft.GetResult,
}, nil
}
// OnEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *flatCallTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
t.tracer.OnEnter(depth, typ, from, to, input, gas, value)
if depth == 0 {
return
} }
return &flatCallTracer{tracer: t, ctx: ctx, config: config}, nil
}
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
func (t *flatCallTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
t.tracer.CaptureStart(env, from, to, create, input, gas, value)
// Update list of precompiles based on current block
rules := env.ChainConfig().Rules(env.Context.BlockNumber)
t.activePrecompiles = vm.ActivePrecompiles(rules)
}
// CaptureEnd is called after the call finishes to finalize the tracing.
func (t *flatCallTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
t.tracer.CaptureEnd(output, gasUsed, err)
}
// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
func (t *flatCallTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
t.tracer.CaptureState(pc, op, gas, cost, scope, rData, depth, err)
}
// CaptureFault implements the EVMLogger interface to trace an execution fault.
func (t *flatCallTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
t.tracer.CaptureFault(pc, op, gas, cost, scope, depth, err)
}
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *flatCallTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
t.tracer.CaptureEnter(typ, from, to, input, gas, value)
// Child calls must have a value, even if it's zero. // Child calls must have a value, even if it's zero.
// Practically speaking, only STATICCALL has nil value. Set it to zero. // Practically speaking, only STATICCALL has nil value. Set it to zero.
if t.tracer.callstack[len(t.tracer.callstack)-1].Value == nil && value == nil { if t.tracer.callstack[len(t.tracer.callstack)-1].Value == nil && value == nil {
@ -177,11 +165,14 @@ func (t *flatCallTracer) CaptureEnter(typ vm.OpCode, from common.Address, to com
} }
} }
// CaptureExit is called when EVM exits a scope, even if the scope didn't // OnExit is called when EVM exits a scope, even if the scope didn't
// execute any code. // execute any code.
func (t *flatCallTracer) CaptureExit(output []byte, gasUsed uint64, err error) { func (t *flatCallTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
t.tracer.CaptureExit(output, gasUsed, err) t.tracer.OnExit(depth, output, gasUsed, err, reverted)
if depth == 0 {
return
}
// Parity traces don't include CALL/STATICCALLs to precompiles. // Parity traces don't include CALL/STATICCALLs to precompiles.
// By default we remove them from the callstack. // By default we remove them from the callstack.
if t.config.IncludePrecompiles { if t.config.IncludePrecompiles {
@ -201,12 +192,15 @@ func (t *flatCallTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
} }
} }
func (t *flatCallTracer) CaptureTxStart(gasLimit uint64) { func (t *flatCallTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
t.tracer.CaptureTxStart(gasLimit) t.tracer.OnTxStart(env, tx, from)
// Update list of precompiles based on current block
rules := env.ChainConfig.Rules(env.BlockNumber)
t.activePrecompiles = vm.ActivePrecompiles(rules)
} }
func (t *flatCallTracer) CaptureTxEnd(restGas uint64) { func (t *flatCallTracer) OnTxEnd(receipt *types.Receipt, err error) {
t.tracer.CaptureTxEnd(restGas) t.tracer.OnTxEnd(receipt, err)
} }
// GetResult returns an empty json object. // GetResult returns an empty json object.

View file

@ -8,6 +8,8 @@ import (
"sync/atomic" "sync/atomic"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/eth/tracers" "github.com/XinFinOrg/XDPoSChain/eth/tracers"
) )
@ -17,7 +19,6 @@ func init() {
} }
type contractTracer struct { type contractTracer struct {
env *vm.EVM
Addrs map[string]string Addrs map[string]string
config contractTracerConfig config contractTracerConfig
interrupt uint32 // Atomic flag to signal execution interruption interrupt uint32 // Atomic flag to signal execution interruption
@ -29,7 +30,7 @@ type contractTracerConfig struct {
} }
// NewContractTracer returns a native go tracer which tracks the contracr was created // NewContractTracer returns a native go tracer which tracks the contracr was created
func NewContractTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { func NewContractTracer(ctx *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) {
var config contractTracerConfig var config contractTracerConfig
if cfg != nil { if cfg != nil {
if err := json.Unmarshal(cfg, &config); err != nil { if err := json.Unmarshal(cfg, &config); err != nil {
@ -46,28 +47,28 @@ func NewContractTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Trace
t.reason = fmt.Errorf("opcode %s not defined", t.config.OpCode) t.reason = fmt.Errorf("opcode %s not defined", t.config.OpCode)
return nil, t.reason return nil, t.reason
} }
return t, nil return &tracers.Tracer{
Hooks: &tracing.Hooks{
OnTxStart: t.OnTxStart,
OnTxEnd: t.OnTxEnd,
OnEnter: t.OnEnter,
OnExit: t.OnExit,
OnOpcode: t.OnOpcode,
OnFault: t.OnFault,
},
GetResult: t.GetResult,
Stop: t.Stop,
}, nil
} }
func (t *contractTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { func (*contractTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
t.env = env
// When not searching for opcodes, record the contract address.
if create && t.config.OpCode == "" {
t.Addrs[addrToHex(to)] = ""
}
} }
func (t *contractTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { func (*contractTracer) OnTxEnd(receipt *types.Receipt, err error) {}
}
func (*contractTracer) CaptureTxStart(gasLimit uint64) {} func (t *contractTracer) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
func (*contractTracer) CaptureTxEnd(restGas uint64) {}
func (t *contractTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
// Skip if tracing was interrupted // Skip if tracing was interrupted
if atomic.LoadUint32(&t.interrupt) > 0 { if atomic.LoadUint32(&t.interrupt) > 0 {
t.env.Cancel()
return return
} }
// If the OpCode is empty , exit early. // If the OpCode is empty , exit early.
@ -75,19 +76,24 @@ func (t *contractTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64,
return return
} }
targetOp := vm.StringToOp(t.config.OpCode) targetOp := vm.StringToOp(t.config.OpCode)
op := vm.OpCode(opcode)
if op == targetOp { if op == targetOp {
addr := scope.Contract.Address() addr := scope.Address()
t.Addrs[addrToHex(addr)] = "" t.Addrs[addrToHex(addr)] = ""
} }
} }
func (t *contractTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) { func (t *contractTracer) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) {
} }
func (t *contractTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { func (t *contractTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
create := vm.OpCode(typ) == vm.CREATE
if create && t.config.OpCode == "" {
t.Addrs[addrToHex(to)] = ""
}
} }
func (t *contractTracer) CaptureExit(output []byte, gasUsed uint64, err error) { func (t *contractTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
} }
func (t *contractTracer) GetResult() (json.RawMessage, error) { func (t *contractTracer) GetResult() (json.RawMessage, error) {

View file

@ -21,7 +21,8 @@ import (
"math/big" "math/big"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/eth/tracers" "github.com/XinFinOrg/XDPoSChain/eth/tracers"
) )
@ -33,18 +34,18 @@ func init() {
// runs multiple tracers in one go. // runs multiple tracers in one go.
type muxTracer struct { type muxTracer struct {
names []string names []string
tracers []tracers.Tracer tracers []*tracers.Tracer
} }
// newMuxTracer returns a new mux tracer. // newMuxTracer returns a new mux tracer.
func newMuxTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { func newMuxTracer(ctx *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) {
var config map[string]json.RawMessage var config map[string]json.RawMessage
if cfg != nil { if cfg != nil {
if err := json.Unmarshal(cfg, &config); err != nil { if err := json.Unmarshal(cfg, &config); err != nil {
return nil, err return nil, err
} }
} }
objects := make([]tracers.Tracer, 0, len(config)) objects := make([]*tracers.Tracer, 0, len(config))
names := make([]string, 0, len(config)) names := make([]string, 0, len(config))
for k, v := range config { for k, v := range config {
t, err := tracers.DefaultDirectory.New(k, ctx, v) t, err := tracers.DefaultDirectory.New(k, ctx, v)
@ -55,61 +56,120 @@ func newMuxTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, er
names = append(names, k) names = append(names, k)
} }
return &muxTracer{names: names, tracers: objects}, nil t := &muxTracer{names: names, tracers: objects}
return &tracers.Tracer{
Hooks: &tracing.Hooks{
OnTxStart: t.OnTxStart,
OnTxEnd: t.OnTxEnd,
OnEnter: t.OnEnter,
OnExit: t.OnExit,
OnOpcode: t.OnOpcode,
OnFault: t.OnFault,
OnGasChange: t.OnGasChange,
OnBalanceChange: t.OnBalanceChange,
OnNonceChange: t.OnNonceChange,
OnCodeChange: t.OnCodeChange,
OnStorageChange: t.OnStorageChange,
OnLog: t.OnLog,
},
GetResult: t.GetResult,
Stop: t.Stop,
}, nil
} }
// CaptureStart implements the EVMLogger interface to initialize the tracing operation. func (t *muxTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
func (t *muxTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureStart(env, from, to, create, input, gas, value) if t.OnOpcode != nil {
t.OnOpcode(pc, op, gas, cost, scope, rData, depth, err)
}
} }
} }
// CaptureEnd is called after the call finishes to finalize the tracing. func (t *muxTracer) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) {
func (t *muxTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureEnd(output, gasUsed, err) if t.OnFault != nil {
t.OnFault(pc, op, gas, cost, scope, depth, err)
}
} }
} }
// CaptureState implements the EVMLogger interface to trace a single step of VM execution. func (t *muxTracer) OnGasChange(old, new uint64, reason tracing.GasChangeReason) {
func (t *muxTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureState(pc, op, gas, cost, scope, rData, depth, err) if t.OnGasChange != nil {
t.OnGasChange(old, new, reason)
}
} }
} }
// CaptureFault implements the EVMLogger interface to trace an execution fault. func (t *muxTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
func (t *muxTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureFault(pc, op, gas, cost, scope, depth, err) if t.OnEnter != nil {
t.OnEnter(depth, typ, from, to, input, gas, value)
}
} }
} }
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). func (t *muxTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
func (t *muxTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureEnter(typ, from, to, input, gas, value) if t.OnExit != nil {
t.OnExit(depth, output, gasUsed, err, reverted)
}
} }
} }
// CaptureExit is called when EVM exits a scope, even if the scope didn't func (t *muxTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
// execute any code.
func (t *muxTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureExit(output, gasUsed, err) if t.OnTxStart != nil {
t.OnTxStart(env, tx, from)
}
} }
} }
func (t *muxTracer) CaptureTxStart(gasLimit uint64) { func (t *muxTracer) OnTxEnd(receipt *types.Receipt, err error) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureTxStart(gasLimit) if t.OnTxEnd != nil {
t.OnTxEnd(receipt, err)
}
} }
} }
func (t *muxTracer) CaptureTxEnd(restGas uint64) { func (t *muxTracer) OnBalanceChange(a common.Address, prev, new *big.Int, reason tracing.BalanceChangeReason) {
for _, t := range t.tracers { for _, t := range t.tracers {
t.CaptureTxEnd(restGas) if t.OnBalanceChange != nil {
t.OnBalanceChange(a, prev, new, reason)
}
}
}
func (t *muxTracer) OnNonceChange(a common.Address, prev, new uint64) {
for _, t := range t.tracers {
if t.OnNonceChange != nil {
t.OnNonceChange(a, prev, new)
}
}
}
func (t *muxTracer) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) {
for _, t := range t.tracers {
if t.OnCodeChange != nil {
t.OnCodeChange(a, prevCodeHash, prev, codeHash, code)
}
}
}
func (t *muxTracer) OnStorageChange(a common.Address, k, prev, new common.Hash) {
for _, t := range t.tracers {
if t.OnStorageChange != nil {
t.OnStorageChange(a, k, prev, new)
}
}
}
func (t *muxTracer) OnLog(log *types.Log) {
for _, t := range t.tracers {
if t.OnLog != nil {
t.OnLog(log)
}
} }
} }

View file

@ -21,7 +21,8 @@ import (
"math/big" "math/big"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/eth/tracers" "github.com/XinFinOrg/XDPoSChain/eth/tracers"
) )
@ -31,32 +32,59 @@ func init() {
type noopTracer struct{} type noopTracer struct{}
func newNoopTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) { // newNoopTracer returns a new noop tracer.
return &noopTracer{}, nil func newNoopTracer(ctx *tracers.Context, _ json.RawMessage) (*tracers.Tracer, error) {
t := &noopTracer{}
return &tracers.Tracer{
Hooks: &tracing.Hooks{
OnTxStart: t.OnTxStart,
OnTxEnd: t.OnTxEnd,
OnEnter: t.OnEnter,
OnExit: t.OnExit,
OnOpcode: t.OnOpcode,
OnFault: t.OnFault,
OnGasChange: t.OnGasChange,
OnBalanceChange: t.OnBalanceChange,
OnNonceChange: t.OnNonceChange,
OnCodeChange: t.OnCodeChange,
OnStorageChange: t.OnStorageChange,
OnLog: t.OnLog,
},
GetResult: t.GetResult,
Stop: t.Stop,
}, nil
} }
func (t *noopTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { func (t *noopTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
} }
// CaptureEnd is called after the call finishes to finalize the tracing. func (t *noopTracer) OnFault(pc uint64, op byte, gas, cost uint64, _ tracing.OpContext, depth int, err error) {
func (t *noopTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
} }
func (t *noopTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { func (t *noopTracer) OnGasChange(old, new uint64, reason tracing.GasChangeReason) {}
func (t *noopTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
} }
func (t *noopTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) { func (t *noopTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
} }
func (t *noopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { func (*noopTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
} }
func (t *noopTracer) CaptureExit(output []byte, gasUsed uint64, err error) { func (*noopTracer) OnTxEnd(receipt *types.Receipt, err error) {}
func (*noopTracer) OnBalanceChange(a common.Address, prev, new *big.Int, reason tracing.BalanceChangeReason) {
} }
func (*noopTracer) CaptureTxStart(gasLimit uint64) {} func (*noopTracer) OnNonceChange(a common.Address, prev, new uint64) {}
func (*noopTracer) CaptureTxEnd(restGas uint64) {} func (*noopTracer) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) {
}
func (*noopTracer) OnStorageChange(a common.Address, k, prev, new common.Hash) {}
func (*noopTracer) OnLog(log *types.Log) {}
// GetResult returns an empty json object. // GetResult returns an empty json object.
func (t *noopTracer) GetResult() (json.RawMessage, error) { func (t *noopTracer) GetResult() (json.RawMessage, error) {

View file

@ -24,9 +24,12 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/hexutil" "github.com/XinFinOrg/XDPoSChain/common/hexutil"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/eth/tracers" "github.com/XinFinOrg/XDPoSChain/eth/tracers"
"github.com/XinFinOrg/XDPoSChain/eth/tracers/internal"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
) )
@ -36,13 +39,14 @@ func init() {
tracers.DefaultDirectory.Register("prestateTracer", newPrestateTracer, false) tracers.DefaultDirectory.Register("prestateTracer", newPrestateTracer, false)
} }
type state = map[common.Address]*account type stateMap = map[common.Address]*account
type account struct { type account struct {
Balance *big.Int `json:"balance,omitempty"` Balance *big.Int `json:"balance,omitempty"`
Code []byte `json:"code,omitempty"` Code []byte `json:"code,omitempty"`
Nonce uint64 `json:"nonce,omitempty"` Nonce uint64 `json:"nonce,omitempty"`
Storage map[common.Hash]common.Hash `json:"storage,omitempty"` Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
empty bool
} }
func (a *account) exists() bool { func (a *account) exists() bool {
@ -55,13 +59,10 @@ type accountMarshaling struct {
} }
type prestateTracer struct { type prestateTracer struct {
noopTracer env *tracing.VMContext
env *vm.EVM pre stateMap
pre state post stateMap
post state
create bool
to common.Address to common.Address
gasLimit uint64 // Amount of gas bought for the whole tx
config prestateTracerConfig config prestateTracerConfig
interrupt atomic.Bool // Atomic flag to signal execution interruption interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption reason error // Textual reason for the interruption
@ -73,70 +74,33 @@ type prestateTracerConfig struct {
DiffMode bool `json:"diffMode"` // If true, this tracer will return state modifications DiffMode bool `json:"diffMode"` // If true, this tracer will return state modifications
} }
func newPrestateTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { func newPrestateTracer(ctx *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) {
var config prestateTracerConfig var config prestateTracerConfig
if cfg != nil { if cfg != nil {
if err := json.Unmarshal(cfg, &config); err != nil { if err := json.Unmarshal(cfg, &config); err != nil {
return nil, err return nil, err
} }
} }
return &prestateTracer{ t := &prestateTracer{
pre: state{}, pre: stateMap{},
post: state{}, post: stateMap{},
config: config, config: config,
created: make(map[common.Address]bool), created: make(map[common.Address]bool),
deleted: make(map[common.Address]bool), deleted: make(map[common.Address]bool),
}
return &tracers.Tracer{
Hooks: &tracing.Hooks{
OnTxStart: t.OnTxStart,
OnTxEnd: t.OnTxEnd,
OnOpcode: t.OnOpcode,
},
GetResult: t.GetResult,
Stop: t.Stop,
}, nil }, nil
} }
// CaptureStart implements the EVMLogger interface to initialize the tracing operation. // OnOpcode implements the EVMLogger interface to trace a single step of VM execution.
func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { func (t *prestateTracer) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
t.env = env
t.create = create
t.to = to
t.lookupAccount(from)
t.lookupAccount(to)
t.lookupAccount(env.Context.Coinbase)
// The recipient balance includes the value transferred.
toBal := new(big.Int).Sub(t.pre[to].Balance, value)
t.pre[to].Balance = toBal
if env.ChainConfig().Rules(env.Context.BlockNumber).IsEIP158 && create {
t.pre[to].Nonce--
}
// The sender balance is after reducing: value and gasLimit.
// We need to re-add them to get the pre-tx balance.
fromBal := new(big.Int).Set(t.pre[from].Balance)
gasPrice := env.TxContext.GasPrice
consumedGas := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(t.gasLimit))
fromBal.Add(fromBal, new(big.Int).Add(value, consumedGas))
t.pre[from].Balance = fromBal
t.pre[from].Nonce--
if create && t.config.DiffMode {
t.created[to] = true
}
}
// CaptureEnd is called after the call finishes to finalize the tracing.
func (t *prestateTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
if t.config.DiffMode {
return
}
if t.create {
// Keep existing account prior to contract creation at that address
if s := t.pre[t.to]; s != nil && !s.exists() {
// Exclude newly created contract.
delete(t.pre, t.to)
}
}
}
// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
if err != nil { if err != nil {
return return
} }
@ -144,10 +108,10 @@ func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64,
if t.interrupt.Load() { if t.interrupt.Load() {
return return
} }
stack := scope.Stack op := vm.OpCode(opcode)
stackData := stack.Data() stackData := scope.StackData()
stackLen := len(stackData) stackLen := len(stackData)
caller := scope.Contract.Address() caller := scope.Address()
switch { switch {
case stackLen >= 1 && (op == vm.SLOAD || op == vm.SSTORE): case stackLen >= 1 && (op == vm.SLOAD || op == vm.SSTORE):
slot := common.Hash(stackData[stackLen-1].Bytes32()) slot := common.Hash(stackData[stackLen-1].Bytes32())
@ -169,7 +133,7 @@ func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64,
case stackLen >= 4 && op == vm.CREATE2: case stackLen >= 4 && op == vm.CREATE2:
offset := stackData[stackLen-2] offset := stackData[stackLen-2]
size := stackData[stackLen-3] size := stackData[stackLen-3]
init, err := tracers.GetMemoryCopyPadded(scope.Memory, int64(offset.Uint64()), int64(size.Uint64())) init, err := internal.GetMemoryCopyPadded(scope.MemoryData(), int64(offset.Uint64()), int64(size.Uint64()))
if err != nil { if err != nil {
log.Warn("failed to copy CREATE2 input", "err", err, "tracer", "prestateTracer", "offset", offset, "size", size) log.Warn("failed to copy CREATE2 input", "err", err, "tracer", "prestateTracer", "offset", offset, "size", size)
return return
@ -182,15 +146,62 @@ func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64,
} }
} }
func (t *prestateTracer) CaptureTxStart(gasLimit uint64) { func (t *prestateTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
t.gasLimit = gasLimit t.env = env
} if tx.To() == nil {
t.to = crypto.CreateAddress(from, env.StateDB.GetNonce(from))
func (t *prestateTracer) CaptureTxEnd(restGas uint64) { t.created[t.to] = true
if !t.config.DiffMode { } else {
return t.to = *tx.To()
} }
t.lookupAccount(from)
t.lookupAccount(t.to)
t.lookupAccount(env.Coinbase)
}
func (t *prestateTracer) OnTxEnd(receipt *types.Receipt, err error) {
if err != nil {
return
}
if t.config.DiffMode {
t.processDiffState()
}
// the new created contracts' prestate were empty, so delete them
for a := range t.created {
// the created contract maybe exists in statedb before the creating tx
if s := t.pre[a]; s != nil && s.empty {
delete(t.pre, a)
}
}
}
// GetResult returns the json-encoded nested list of call traces, and any
// error arising from the encoding or forceful termination (via `Stop`).
func (t *prestateTracer) GetResult() (json.RawMessage, error) {
var res []byte
var err error
if t.config.DiffMode {
res, err = json.Marshal(struct {
Post stateMap `json:"post"`
Pre stateMap `json:"pre"`
}{t.post, t.pre})
} else {
res, err = json.Marshal(t.pre)
}
if err != nil {
return nil, err
}
return json.RawMessage(res), t.reason
}
// Stop terminates execution of the tracer at the first opportune moment.
func (t *prestateTracer) Stop(err error) {
t.reason = err
t.interrupt.Store(true)
}
func (t *prestateTracer) processDiffState() {
for addr, state := range t.pre { for addr, state := range t.pre {
// The deleted account's state is pruned from `post` but kept in `pre` // The deleted account's state is pruned from `post` but kept in `pre`
if _, ok := t.deleted[addr]; ok { if _, ok := t.deleted[addr]; ok {
@ -240,38 +251,6 @@ func (t *prestateTracer) CaptureTxEnd(restGas uint64) {
delete(t.pre, addr) delete(t.pre, addr)
} }
} }
// the new created contracts' prestate were empty, so delete them
for a := range t.created {
// the created contract maybe exists in statedb before the creating tx
if s := t.pre[a]; s != nil && !s.exists() {
delete(t.pre, a)
}
}
}
// GetResult returns the json-encoded nested list of call traces, and any
// error arising from the encoding or forceful termination (via `Stop`).
func (t *prestateTracer) GetResult() (json.RawMessage, error) {
var res []byte
var err error
if t.config.DiffMode {
res, err = json.Marshal(struct {
Post state `json:"post"`
Pre state `json:"pre"`
}{t.post, t.pre})
} else {
res, err = json.Marshal(t.pre)
}
if err != nil {
return nil, err
}
return json.RawMessage(res), t.reason
}
// Stop terminates execution of the tracer at the first opportune moment.
func (t *prestateTracer) Stop(err error) {
t.reason = err
t.interrupt.Store(true)
} }
// lookupAccount fetches details of an account and adds it to the prestate // lookupAccount fetches details of an account and adds it to the prestate
@ -281,12 +260,16 @@ func (t *prestateTracer) lookupAccount(addr common.Address) {
return return
} }
t.pre[addr] = &account{ acc := &account{
Balance: t.env.StateDB.GetBalance(addr), Balance: t.env.StateDB.GetBalance(addr),
Nonce: t.env.StateDB.GetNonce(addr), Nonce: t.env.StateDB.GetNonce(addr),
Code: t.env.StateDB.GetCode(addr), Code: t.env.StateDB.GetCode(addr),
Storage: make(map[common.Hash]common.Hash), Storage: make(map[common.Hash]common.Hash),
} }
if !acc.exists() {
acc.empty = true
}
t.pre[addr] = acc
} }
// lookupStorage fetches the requested storage slot and adds // lookupStorage fetches the requested storage slot and adds

View file

@ -79,7 +79,8 @@ func BenchmarkTransactionTrace(b *testing.B) {
Code: []byte{}, Code: []byte{},
Balance: big.NewInt(500000000000000), Balance: big.NewInt(500000000000000),
} }
statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc) state := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc)
// Create the tracer, the EVM environment and run it // Create the tracer, the EVM environment and run it
tracer := logger.NewStructLogger(&logger.Config{ tracer := logger.NewStructLogger(&logger.Config{
Debug: false, Debug: false,
@ -87,7 +88,7 @@ func BenchmarkTransactionTrace(b *testing.B) {
//EnableMemory: false, //EnableMemory: false,
//EnableReturnData: false, //EnableReturnData: false,
}) })
evm := vm.NewEVM(context, txContext, statedb, nil, params.AllEthashProtocolChanges, vm.Config{Tracer: tracer}) evm := vm.NewEVM(context, txContext, state, nil, params.AllEthashProtocolChanges, vm.Config{Tracer: tracer.Hooks()})
msg, err := core.TransactionToMessage(tx, signer, nil, nil, context.BaseFee) msg, err := core.TransactionToMessage(tx, signer, nil, nil, context.BaseFee)
if err != nil { if err != nil {
b.Fatalf("failed to prepare transaction for tracing: %v", err) b.Fatalf("failed to prepare transaction for tracing: %v", err)
@ -96,54 +97,16 @@ func BenchmarkTransactionTrace(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
snap := statedb.Snapshot() snap := state.Snapshot()
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
_, err = st.TransitionDb(common.Address{}) _, err = st.TransitionDb(common.Address{})
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
statedb.RevertToSnapshot(snap) state.RevertToSnapshot(snap)
if have, want := len(tracer.StructLogs()), 244752; have != want { if have, want := len(tracer.StructLogs()), 244752; have != want {
b.Fatalf("trace wrong, want %d steps, have %d", want, have) b.Fatalf("trace wrong, want %d steps, have %d", want, have)
} }
tracer.Reset() tracer.Reset()
} }
} }
func TestMemCopying(t *testing.T) {
for i, tc := range []struct {
memsize int64
offset int64
size int64
wantErr string
wantSize int
}{
{0, 0, 100, "", 100}, // Should pad up to 100
{0, 100, 0, "", 0}, // No need to pad (0 size)
{100, 50, 100, "", 100}, // Should pad 100-150
{100, 50, 5, "", 5}, // Wanted range fully within memory
{100, -50, 0, "offset or size must not be negative", 0}, // Error
{0, 1, 1024*1024 + 1, "reached limit for padding memory slice: 1048578", 0}, // Error
{10, 0, 1024*1024 + 100, "reached limit for padding memory slice: 1048666", 0}, // Error
} {
mem := vm.NewMemory()
mem.Resize(uint64(tc.memsize))
cpy, err := GetMemoryCopyPadded(mem, tc.offset, tc.size)
if want := tc.wantErr; want != "" {
if err == nil {
t.Fatalf("test %d: want '%v' have no error", i, want)
}
if have := err.Error(); want != have {
t.Fatalf("test %d: want '%v' have '%v'", i, want, have)
}
continue
}
if err != nil {
t.Fatalf("test %d: unexpected error: %v", i, err)
}
if want, have := tc.wantSize, len(cpy); have != want {
t.Fatalf("test %d: want %v have %v", i, want, have)
}
}
}

View file

@ -43,6 +43,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
@ -542,41 +543,41 @@ type OverrideAccount struct {
type StateOverride map[common.Address]OverrideAccount type StateOverride map[common.Address]OverrideAccount
// Apply overrides the fields of specified accounts into the given state. // Apply overrides the fields of specified accounts into the given state.
func (diff *StateOverride) Apply(state *state.StateDB) error { func (diff *StateOverride) Apply(statedb *state.StateDB) error {
if diff == nil { if diff == nil {
return nil return nil
} }
for addr, account := range *diff { for addr, account := range *diff {
// Override account nonce. // Override account nonce.
if account.Nonce != nil { if account.Nonce != nil {
state.SetNonce(addr, uint64(*account.Nonce)) statedb.SetNonce(addr, uint64(*account.Nonce))
} }
// Override account(contract) code. // Override account(contract) code.
if account.Code != nil { if account.Code != nil {
state.SetCode(addr, *account.Code) statedb.SetCode(addr, *account.Code)
} }
// Override account balance. // Override account balance.
if account.Balance != nil { if account.Balance != nil {
state.SetBalance(addr, (*big.Int)(*account.Balance)) statedb.SetBalance(addr, (*big.Int)(*account.Balance), tracing.BalanceChangeUnspecified)
} }
if account.State != nil && account.StateDiff != nil { if account.State != nil && account.StateDiff != nil {
return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex())
} }
// Replace entire state if caller requires. // Replace entire state if caller requires.
if account.State != nil { if account.State != nil {
state.SetStorage(addr, *account.State) statedb.SetStorage(addr, *account.State)
} }
// Apply state diff into specified accounts. // Apply state diff into specified accounts.
if account.StateDiff != nil { if account.StateDiff != nil {
for key, value := range *account.StateDiff { for key, value := range *account.StateDiff {
state.SetState(addr, key, value) statedb.SetState(addr, key, value)
} }
} }
} }
// Now finalize the changes. Finalize is normally performed between transactions. // Now finalize the changes. Finalize is normally performed between transactions.
// By using finalize, the overrides are semantically behaving as // By using finalize, the overrides are semantically behaving as
// if they were created in a transaction just before the tracing occur. // if they were created in a transaction just before the tracing occur.
state.Finalise(false) statedb.Finalise(false)
return nil return nil
} }
@ -1158,13 +1159,12 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash
if blockOverrides != nil { if blockOverrides != nil {
blockOverrides.Apply(&blockCtx) blockOverrides.Apply(&blockCtx)
} }
msg, err := args.ToMessage(b, header.Number, globalGasCap, blockCtx.BaseFee) if err := args.CallDefaults(globalGasCap, blockCtx.BaseFee, b.ChainConfig().ChainId); err != nil {
if err != nil {
return nil, err return nil, err
} }
msg := args.ToMessage(b, blockCtx.BaseFee)
msg.BalanceTokenFee = new(big.Int).SetUint64(msg.GasLimit) msg.BalanceTokenFee = new(big.Int).SetUint64(msg.GasLimit)
msg.BalanceTokenFee.Mul(msg.BalanceTokenFee, msg.GasPrice) msg.BalanceTokenFee.Mul(msg.BalanceTokenFee, msg.GasPrice)
evm, vmError, err := b.GetEVM(ctx, msg, statedb, XDCxState, header, &vm.Config{NoBaseFee: true}, &blockCtx) evm, vmError, err := b.GetEVM(ctx, msg, statedb, XDCxState, header, &vm.Config{NoBaseFee: true}, &blockCtx)
if err != nil { if err != nil {
return nil, err return nil, err
@ -1178,8 +1178,7 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash
// Execute the message. // Execute the message.
gp := new(core.GasPool).AddGas(gomath.MaxUint64) gp := new(core.GasPool).AddGas(gomath.MaxUint64)
owner := common.Address{} result, err := core.ApplyMessage(evm, msg, gp, common.Address{})
result, err := core.ApplyMessage(evm, msg, gp, owner)
if err := vmError(); err != nil { if err := vmError(); err != nil {
return nil, err return nil, err
} }
@ -1801,10 +1800,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
statedb := db.Copy() statedb := db.Copy()
// Set the accesslist to the last al // Set the accesslist to the last al
args.AccessList = &accessList args.AccessList = &accessList
msg, err := args.ToMessage(b, block.Number(), b.RPCGasCap(), header.BaseFee) msg := args.ToMessage(b, header.BaseFee)
if err != nil {
return nil, 0, nil, err
}
feeCapacity := state.GetTRC21FeeCapacityFromState(statedb) feeCapacity := state.GetTRC21FeeCapacityFromState(statedb)
var balanceTokenFee *big.Int var balanceTokenFee *big.Int
@ -1815,7 +1811,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
// Apply the transaction with the access list tracer // Apply the transaction with the access list tracer
tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles) tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles)
config := vm.Config{Tracer: tracer, NoBaseFee: true} config := vm.Config{Tracer: tracer.Hooks(), NoBaseFee: true}
vmenv, _, err := b.GetEVM(ctx, msg, statedb, XDCxState, header, &config, nil) vmenv, _, err := b.GetEVM(ctx, msg, statedb, XDCxState, header, &config, nil)
if err != nil { if err != nil {
return nil, 0, nil, err return nil, 0, nil, err
@ -1823,7 +1819,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
// TODO: determine the value of owner // TODO: determine the value of owner
res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit), owner) res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit), owner)
if err != nil { if err != nil {
return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.toTransaction().Hash(), err) return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.ToTransaction().Hash(), err)
} }
if tracer.Equal(prevTracer) { if tracer.Equal(prevTracer) {
return accessList, res.UsedGas, res.Err, nil return accessList, res.UsedGas, res.Err, nil
@ -2115,7 +2111,7 @@ func (s *TransactionAPI) SendTransaction(ctx context.Context, args TransactionAr
return common.Hash{}, err return common.Hash{}, err
} }
// Assemble the transaction and sign with the wallet // Assemble the transaction and sign with the wallet
tx := args.toTransaction() tx := args.ToTransaction()
var chainID *big.Int var chainID *big.Int
if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) { if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) {
@ -2137,7 +2133,7 @@ func (s *TransactionAPI) FillTransaction(ctx context.Context, args TransactionAr
return nil, err return nil, err
} }
// Assemble the transaction and obtain rlp // Assemble the transaction and obtain rlp
tx := args.toTransaction() tx := args.ToTransaction()
data, err := tx.MarshalBinary() data, err := tx.MarshalBinary()
if err != nil { if err != nil {
return nil, err return nil, err
@ -3042,7 +3038,7 @@ func (s *TransactionAPI) SignTransaction(ctx context.Context, args TransactionAr
return nil, err return nil, err
} }
// Before actually sign the transaction, ensure the transaction fee is reasonable. // Before actually sign the transaction, ensure the transaction fee is reasonable.
tx := args.toTransaction() tx := args.ToTransaction()
if err := checkTxFee(tx.GasPrice(), tx.Gas(), s.b.RPCTxFeeCap()); err != nil { if err := checkTxFee(tx.GasPrice(), tx.Gas(), s.b.RPCTxFeeCap()); err != nil {
return nil, err return nil, err
} }
@ -3090,7 +3086,7 @@ func (s *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, g
if err := sendArgs.setDefaults(ctx, s.b, false); err != nil { if err := sendArgs.setDefaults(ctx, s.b, false); err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
matchTx := sendArgs.toTransaction() matchTx := sendArgs.ToTransaction()
// Before replacing the old transaction, ensure the _new_ transaction fee is reasonable. // Before replacing the old transaction, ensure the _new_ transaction fee is reasonable.
var price = matchTx.GasPrice() var price = matchTx.GasPrice()
@ -3121,7 +3117,7 @@ func (s *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, g
if gasLimit != nil && *gasLimit != 0 { if gasLimit != nil && *gasLimit != 0 {
sendArgs.Gas = gasLimit sendArgs.Gas = gasLimit
} }
signedTx, err := s.sign(sendArgs.from(), sendArgs.toTransaction()) signedTx, err := s.sign(sendArgs.from(), sendArgs.ToTransaction())
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }

View file

@ -226,13 +226,91 @@ type AccountBackend interface {
AccountManager() *accounts.Manager AccountManager() *accounts.Manager
} }
// CallDefaults sanitizes the transaction arguments, often filling in zero values,
// for the purpose of eth_call class of RPC methods.
func (args *TransactionArgs) CallDefaults(globalGasCap uint64, baseFee *big.Int, chainID *big.Int) error {
// Reject invalid combinations of pre- and post-1559 fee styles
if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) {
return errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
}
if args.ChainID == nil {
args.ChainID = (*hexutil.Big)(chainID)
} else {
if have := (*big.Int)(args.ChainID); have.Cmp(chainID) != 0 {
return fmt.Errorf("chainId does not match node's (have=%v, want=%v)", have, chainID)
}
}
if args.Gas == nil {
gas := globalGasCap
if gas == 0 {
gas = uint64(math.MaxUint64 / 2)
}
args.Gas = (*hexutil.Uint64)(&gas)
} else {
if globalGasCap > 0 && globalGasCap < uint64(*args.Gas) {
log.Warn("Caller gas above allowance, capping", "requested", args.Gas, "cap", globalGasCap)
args.Gas = (*hexutil.Uint64)(&globalGasCap)
}
}
if args.Nonce == nil {
args.Nonce = new(hexutil.Uint64)
}
if args.Value == nil {
args.Value = new(hexutil.Big)
}
if baseFee == nil {
// If there's no basefee, then it must be a non-1559 execution
if args.GasPrice == nil {
args.GasPrice = new(hexutil.Big)
}
} else {
// A basefee is provided, necessitating 1559-type execution
if args.MaxFeePerGas == nil {
args.MaxFeePerGas = new(hexutil.Big)
}
if args.MaxPriorityFeePerGas == nil {
args.MaxPriorityFeePerGas = new(hexutil.Big)
}
}
return nil
}
// ToMessage converts the transaction arguments to the Message type used by the // ToMessage converts the transaction arguments to the Message type used by the
// core evm. This method is used in calls and traces that do not require a real // core evm. This method is used in calls and traces that do not require a real
// live transaction. // live transaction.
func (args *TransactionArgs) ToMessage(b AccountBackend, number *big.Int, globalGasCap uint64, baseFee *big.Int) (*core.Message, error) { func (args *TransactionArgs) ToMessage(b AccountBackend, baseFee *big.Int) *core.Message {
// Reject invalid combinations of pre- and post-1559 fee styles var (
if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { gasPrice *big.Int
return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") gasFeeCap *big.Int
gasTipCap *big.Int
)
if baseFee == nil {
gasPrice = args.GasPrice.ToInt()
gasFeeCap, gasTipCap = gasPrice, gasPrice
} else {
// A basefee is provided, necessitating 1559-type execution
if args.GasPrice != nil {
// User specified the legacy gas field, convert to 1559 gas typing
gasPrice = args.GasPrice.ToInt()
gasFeeCap, gasTipCap = gasPrice, gasPrice
} else {
// User specified 1559 gas fields (or none), use those
gasFeeCap = args.MaxFeePerGas.ToInt()
gasTipCap = args.MaxPriorityFeePerGas.ToInt()
// Backfill the legacy gasPrice for EVM execution, unless we're all zeroes
gasPrice = new(big.Int)
if gasFeeCap.BitLen() > 0 || gasTipCap.BitLen() > 0 {
gasPrice = gasPrice.Add(gasTipCap, baseFee)
if gasPrice.Cmp(gasFeeCap) > 0 {
gasPrice = gasFeeCap
}
}
}
}
var accessList types.AccessList
if args.AccessList != nil {
accessList = *args.AccessList
} }
// Set sender address or use zero address if none specified. // Set sender address or use zero address if none specified.
@ -245,84 +323,18 @@ func (args *TransactionArgs) ToMessage(b AccountBackend, number *big.Int, global
} }
} }
// Set default gas & gas price if none were set return &core.Message{
gas := globalGasCap
if args.Gas != nil {
gas = uint64(*args.Gas)
}
if gas == 0 {
gas = math.MaxUint64 / 2
}
if globalGasCap != 0 && globalGasCap < gas {
log.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap)
gas = globalGasCap
}
var (
gasPrice *big.Int
gasFeeCap *big.Int
gasTipCap *big.Int
)
if baseFee == nil {
// If there's no basefee, then it must be a non-1559 execution
gasPrice = new(big.Int)
if args.GasPrice != nil {
gasPrice = args.GasPrice.ToInt()
}
if gasPrice.Sign() <= 0 {
gasPrice = new(big.Int).SetUint64(defaultGasPrice)
}
gasFeeCap, gasTipCap = gasPrice, gasPrice
} else {
// A basefee is provided, necessitating 1559-type execution
if args.GasPrice != nil {
// User specified the legacy gas field, convert to 1559 gas typing
gasPrice = args.GasPrice.ToInt()
gasFeeCap, gasTipCap = gasPrice, gasPrice
} else {
// User specified 1559 gas fields (or none), use those
gasFeeCap = new(big.Int)
if args.MaxFeePerGas != nil {
gasFeeCap = args.MaxFeePerGas.ToInt()
}
gasTipCap = new(big.Int)
if args.MaxPriorityFeePerGas != nil {
gasTipCap = args.MaxPriorityFeePerGas.ToInt()
}
// Backfill the legacy gasPrice for EVM execution, unless we're all zeroes
gasPrice = new(big.Int)
if gasFeeCap.BitLen() > 0 || gasTipCap.BitLen() > 0 {
gasPrice = gasPrice.Add(gasTipCap, baseFee)
if gasPrice.Cmp(gasFeeCap) > 0 {
gasPrice = gasFeeCap
}
}
}
}
value := new(big.Int)
if args.Value != nil {
value = args.Value.ToInt()
}
data := args.data()
var accessList types.AccessList
if args.AccessList != nil {
accessList = *args.AccessList
}
msg := &core.Message{
From: addr, From: addr,
To: args.To, To: args.To,
Nonce: 0, Value: (*big.Int)(args.Value),
Value: value, GasLimit: uint64(*args.Gas),
GasLimit: gas,
GasPrice: gasPrice, GasPrice: gasPrice,
GasFeeCap: gasFeeCap, GasFeeCap: gasFeeCap,
GasTipCap: gasTipCap, GasTipCap: gasTipCap,
Data: data, Data: args.data(),
AccessList: accessList, AccessList: accessList,
SkipAccountChecks: true, SkipAccountChecks: true,
} }
return msg, nil
} }
// toTransaction converts the arguments to a transaction. // toTransaction converts the arguments to a transaction.

View file

@ -17,6 +17,7 @@
package tests package tests
import ( import (
"bufio"
"bytes" "bytes"
"fmt" "fmt"
"reflect" "reflect"
@ -76,23 +77,24 @@ func withTrace(t *testing.T, gasLimit uint64, test func(vm.Config) error) {
} }
// Test failed, re-run with tracing enabled. // Test failed, re-run with tracing enabled.
t.Error(err)
if gasLimit > traceErrorLimit { if gasLimit > traceErrorLimit {
t.Log("gas limit too high for EVM trace") t.Log("gas limit too high for EVM trace")
return return
} }
tracer := logger.NewStructLogger(nil) buf := new(bytes.Buffer)
config.Tracer = tracer w := bufio.NewWriter(buf)
config.Tracer = logger.NewJSONLogger(&logger.Config{}, w)
err2 := test(config) err2 := test(config)
if !reflect.DeepEqual(err, err2) { if !reflect.DeepEqual(err, err2) {
t.Errorf("different error for second run: %v", err2) t.Errorf("different error for second run: %v", err2)
} }
buf := new(bytes.Buffer) w.Flush()
logger.WriteTrace(buf, tracer.StructLogs())
if buf.Len() == 0 { if buf.Len() == 0 {
t.Log("no EVM operation logs generated") t.Log("no EVM operation logs generated")
} else { } else {
t.Log("EVM operation log:\n" + buf.String()) t.Log("EVM operation log:\n" + buf.String())
} }
t.Logf("EVM output: %#x", tracer.Output()) // t.Logf("EVM output: 0x%x", tracer.Output())
t.Logf("EVM error: %v", tracer.Error()) // t.Logf("EVM error: %v", tracer.Error())
} }

View file

@ -30,6 +30,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
@ -191,7 +192,7 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB
for addr, a := range accounts { for addr, a := range accounts {
statedb.SetCode(addr, a.Code) statedb.SetCode(addr, a.Code)
statedb.SetNonce(addr, a.Nonce) statedb.SetNonce(addr, a.Nonce)
statedb.SetBalance(addr, a.Balance) statedb.SetBalance(addr, a.Balance, tracing.BalanceChangeUnspecified)
for k, v := range a.Storage { for k, v := range a.Storage {
statedb.SetState(addr, k, v) statedb.SetState(addr, k, v)
} }