go-ethereum/core/token_validator.go
Daniel Liu ad9003c41e
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>
2025-09-09 17:30:56 +08:00

220 lines
8.1 KiB
Go

// Copyright (c) 2018 XDPoSChain
//
// This program 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.
//
// This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"fmt"
"math/big"
"math/rand"
"strings"
ethereum "github.com/XinFinOrg/XDPoSChain"
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/contracts/XDCx/contract"
"github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/tracing"
"github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/log"
)
const (
balanceOfFunction = "balanceOf"
minFeeFunction = "minFee"
getDecimalFunction = "decimals"
)
type SimulatedBackend interface {
CallContractWithState(call ethereum.CallMsg, chain consensus.ChainContext, statedb *state.StateDB) ([]byte, error)
}
// GetTokenAbi return token abi
func GetTokenAbi(tokenAbi string) (*abi.ABI, error) {
contractABI, err := abi.JSON(strings.NewReader(tokenAbi))
if err != nil {
return nil, err
}
return &contractABI, nil
}
// RunContract run smart contract
func RunContract(chain consensus.ChainContext, statedb *state.StateDB, contractAddr common.Address, abi *abi.ABI, method string, args ...interface{}) (interface{}, error) {
input, err := abi.Pack(method, args...)
if err != nil {
return nil, err
}
fakeCaller := common.HexToAddress("0x0000000000000000000000000000000000000001")
statedb.SetBalance(fakeCaller, common.BasePrice, tracing.BalanceChangeUnspecified)
msg := ethereum.CallMsg{To: &contractAddr, Data: input, From: fakeCaller}
result, err := CallContractWithState(msg, chain, statedb)
if err != nil {
return nil, err
}
var unpackResult interface{}
err = abi.UnpackIntoInterface(&unpackResult, method, result)
if err != nil {
return nil, err
}
return unpackResult, nil
}
// FIXME: please use copyState for this function
// CallContractWithState executes a contract call at the given state.
func CallContractWithState(call ethereum.CallMsg, chain consensus.ChainContext, statedb *state.StateDB) ([]byte, error) {
// Ensure message is initialized properly.
call.GasPrice = big.NewInt(0)
if call.Gas == 0 {
call.Gas = 1000000
}
if call.Value == nil {
call.Value = new(big.Int)
}
// Execute the call.
msg := &Message{
From: call.From,
To: call.To,
Value: call.Value,
GasLimit: call.Gas,
GasPrice: call.GasPrice,
GasFeeCap: call.GasFeeCap,
GasTipCap: call.GasTipCap,
Data: call.Data,
AccessList: call.AccessList,
SkipAccountChecks: true,
}
feeCapacity := state.GetTRC21FeeCapacityFromState(statedb)
if msg.To != nil {
if value, ok := feeCapacity[*msg.To]; ok {
msg.BalanceTokenFee = value
}
}
// Create a new environment which holds all relevant information
// about the transaction and calling mechanisms.
txContext := NewEVMTxContext(msg)
evmContext := NewEVMBlockContext(chain.CurrentHeader(), chain, nil)
vmenv := vm.NewEVM(evmContext, txContext, statedb, nil, chain.Config(), vm.Config{})
gaspool := new(GasPool).AddGas(1000000)
owner := common.Address{}
result, err := NewStateTransition(vmenv, msg, gaspool).TransitionDb(owner)
if err != nil {
return nil, err
}
return result.Return(), err
}
// make sure that balance of token is at slot 0
func ValidateXDCXApplyTransaction(chain consensus.ChainContext, blockNumber *big.Int, copyState *state.StateDB, tokenAddr common.Address) error {
if blockNumber == nil || blockNumber.Sign() <= 0 {
blockNumber = chain.CurrentHeader().Number
}
if !chain.Config().IsTIPXDCXReceiver(blockNumber) {
return nil
}
contractABI, err := GetTokenAbi(contract.TRC21ABI)
if err != nil {
return fmt.Errorf("ValidateXDCXApplyTransaction: cannot parse ABI. Err: %v", err)
}
if err := ValidateBalanceSlot(chain, copyState, tokenAddr, contractABI); err != nil {
return err
}
if err := ValidateTokenDecimal(chain, copyState, tokenAddr, contractABI); err != nil {
return err
}
return nil
}
// make sure that balance of token is at slot 0
// make sure that minFee of token is at slot 1
func ValidateXDCZApplyTransaction(chain consensus.ChainContext, blockNumber *big.Int, copyState *state.StateDB, tokenAddr common.Address) error {
if blockNumber == nil || blockNumber.Sign() <= 0 {
blockNumber = chain.CurrentHeader().Number
}
if !chain.Config().IsTIPXDCXReceiver(blockNumber) {
return nil
}
contractABI, err := GetTokenAbi(contract.TRC21ABI)
if err != nil {
return fmt.Errorf("ValidateXDCZApplyTransaction: cannot parse ABI. Err: %v", err)
}
// verify balance slot
if err := ValidateBalanceSlot(chain, copyState, tokenAddr, contractABI); err != nil {
return err
}
// validate minFee slot
if err := ValidateMinFeeSlot(chain, copyState, tokenAddr, contractABI); err != nil {
return err
}
return nil
}
func SetRandomBalance(copyState *state.StateDB, tokenAddr, addr common.Address, randomValue *big.Int) {
slotBalanceTrc21 := state.SlotTRC21Token["balances"]
balanceKey := state.GetLocMappingAtKey(addr.Hash(), slotBalanceTrc21)
copyState.SetState(tokenAddr, common.BigToHash(balanceKey), common.BytesToHash(randomValue.Bytes()))
}
func ValidateBalanceSlot(chain consensus.ChainContext, copyState *state.StateDB, tokenAddr common.Address, contractABI *abi.ABI) error {
randBalance := new(big.Int).SetInt64(int64(rand.Intn(1000000000)))
addr := common.HexToAddress("0x0000000000000000000000000000000000000123")
SetRandomBalance(copyState, tokenAddr, addr, randBalance)
result, err := RunContract(chain, copyState, tokenAddr, contractABI, balanceOfFunction, addr)
if err != nil || result == nil {
return fmt.Errorf("cannot get balance at slot %v . Token: %s . Err: %v", state.SlotTRC21Token["balances"], tokenAddr.Hex(), err)
}
balance, ok := result.(*big.Int)
if !ok {
return fmt.Errorf("invalid balance at slot %v . Token: %s . GotBalance: %v . ResultType: %T", state.SlotTRC21Token["balances"], tokenAddr.Hex(), result, result)
}
if balance.Cmp(randBalance) != 0 {
log.Debug("invalid balance slot", "balance_set_at_slot_0", randBalance, "balance_get_from_abi", balance)
return fmt.Errorf("invalid balance slot. Token: %s", tokenAddr.Hex())
}
return nil
}
func ValidateMinFeeSlot(chain consensus.ChainContext, copyState *state.StateDB, tokenAddr common.Address, contractABI *abi.ABI) error {
randomValue := new(big.Int).SetInt64(int64(rand.Intn(1000000000)))
slotMinFeeTrc21 := state.SlotTRC21Token["minFee"]
copyState.SetState(tokenAddr, common.BigToHash(new(big.Int).SetUint64(slotMinFeeTrc21)), common.BytesToHash(randomValue.Bytes()))
result, err := RunContract(chain, copyState, tokenAddr, contractABI, minFeeFunction)
if err != nil || result == nil {
return fmt.Errorf("cannot get minFee at slot %v . Token: %s. Err: %v", state.SlotTRC21Token["minFee"], tokenAddr.Hex(), err)
}
minFee, ok := result.(*big.Int)
if !ok {
return fmt.Errorf("invalid minFee at slot %v . Token: %s . GotMinFee: %v . ResultType: %T", state.SlotTRC21Token["minFee"], tokenAddr.Hex(), result, result)
}
if minFee.Cmp(randomValue) != 0 {
log.Debug("invalid minFee slot", "minFee_set_at_slot_1", randomValue, "minFee_get_from_abi", minFee)
return fmt.Errorf("invalid minFee slot. Token: %s", tokenAddr.Hex())
}
return nil
}
func ValidateTokenDecimal(chain consensus.ChainContext, copyState *state.StateDB, tokenAddr common.Address, contractABI *abi.ABI) error {
result, err := RunContract(chain, copyState, tokenAddr, contractABI, getDecimalFunction)
if err != nil || result == nil {
return fmt.Errorf("cannot get token decimal. Token: %s . Err: %v", tokenAddr.Hex(), err)
}
return nil
}