good morning!!!!

Skip to content
Snippets Groups Projects
debug_api.go 9.15 KiB
Newer Older
  • Learn to ignore specific revisions
  • b00ris's avatar
    b00ris committed
    package commands
    
    import (
    	"context"
    	"fmt"
    
    	jsoniter "github.com/json-iterator/go"
    
    	"github.com/ledgerwatch/erigon-lib/kv"
    
    Alex Sharov's avatar
    Alex Sharov committed
    	"github.com/ledgerwatch/erigon/common"
    	"github.com/ledgerwatch/erigon/common/changeset"
    	"github.com/ledgerwatch/erigon/common/hexutil"
    	"github.com/ledgerwatch/erigon/consensus/ethash"
    	"github.com/ledgerwatch/erigon/core/rawdb"
    	"github.com/ledgerwatch/erigon/core/state"
    	"github.com/ledgerwatch/erigon/core/types"
    	"github.com/ledgerwatch/erigon/eth/stagedsync/stages"
    	"github.com/ledgerwatch/erigon/eth/tracers"
    	"github.com/ledgerwatch/erigon/ethdb"
    	"github.com/ledgerwatch/erigon/internal/ethapi"
    	"github.com/ledgerwatch/erigon/rpc"
    	"github.com/ledgerwatch/erigon/turbo/transactions"
    
    b00ris's avatar
    b00ris committed
    )
    
    
    // AccountRangeMaxResults is the maximum number of results to be returned per call
    const AccountRangeMaxResults = 256
    
    
    // PrivateDebugAPI Exposed RPC endpoints for debugging use
    
    b00ris's avatar
    b00ris committed
    type PrivateDebugAPI interface {
    	StorageRangeAt(ctx context.Context, blockHash common.Hash, txIndex uint64, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error)
    
    	TraceTransaction(ctx context.Context, hash common.Hash, config *tracers.TraceConfig, stream *jsoniter.Stream) error
    
    	TraceBlockByHash(ctx context.Context, hash common.Hash, config *tracers.TraceConfig, stream *jsoniter.Stream) error
    	TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *tracers.TraceConfig, stream *jsoniter.Stream) error
    
    	AccountRange(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, start []byte, maxResults int, nocode, nostorage bool) (state.IteratorDump, error)
    
    b00ris's avatar
    b00ris committed
    	GetModifiedAccountsByNumber(ctx context.Context, startNum rpc.BlockNumber, endNum *rpc.BlockNumber) ([]common.Address, error)
    	GetModifiedAccountsByHash(_ context.Context, startHash common.Hash, endHash *common.Hash) ([]common.Address, error)
    
    	TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *tracers.TraceConfig, stream *jsoniter.Stream) error
    
    ledgerwatch's avatar
    ledgerwatch committed
    	AccountAt(ctx context.Context, blockHash common.Hash, txIndex uint64, account common.Address) (*AccountResult, error)
    
    b00ris's avatar
    b00ris committed
    }
    
    
    // PrivateDebugAPIImpl is implementation of the PrivateDebugAPI interface based on remote Db access
    
    b00ris's avatar
    b00ris committed
    type PrivateDebugAPIImpl struct {
    
    Alex Sharov's avatar
    Alex Sharov committed
    	GasCap uint64
    
    b00ris's avatar
    b00ris committed
    }
    
    // NewPrivateDebugAPI returns PrivateDebugAPIImpl instance
    
    func NewPrivateDebugAPI(base *BaseAPI, db kv.RoDB, gascap uint64) *PrivateDebugAPIImpl {
    
    b00ris's avatar
    b00ris committed
    	return &PrivateDebugAPIImpl{
    
    Alex Sharov's avatar
    Alex Sharov committed
    		BaseAPI: base,
    		db:      db,
    		GasCap:  gascap,
    
    b00ris's avatar
    b00ris committed
    	}
    }
    
    
    // StorageRangeAt implements debug_storageRangeAt. Returns information about a range of storage locations (if any) for the given address.
    
    b00ris's avatar
    b00ris committed
    func (api *PrivateDebugAPIImpl) StorageRangeAt(ctx context.Context, blockHash common.Hash, txIndex uint64, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) {
    
    Alex Sharov's avatar
    Alex Sharov committed
    	tx, err := api.db.BeginRo(ctx)
    
    	if err != nil {
    		return StorageRangeResult{}, err
    	}
    	defer tx.Rollback()
    
    
    Alex Sharov's avatar
    Alex Sharov committed
    	chainConfig, err := api.chainConfig(tx)
    
    	if err != nil {
    		return StorageRangeResult{}, err
    	}
    
    	block, err := api.blockByHashWithSenders(tx, blockHash)
    
    	if err != nil {
    		return StorageRangeResult{}, err
    	}
    	if block == nil {
    		return StorageRangeResult{}, nil
    	}
    	getHeader := func(hash common.Hash, number uint64) *types.Header {
    		return rawdb.ReadHeader(tx, hash, number)
    	}
    
    
    	contractHasTEVM := func(contractHash common.Hash) (bool, error) { return false, nil }
    	if api.TevmEnabled {
    		contractHasTEVM = ethdb.GetHasTEVM(tx)
    	}
    
    
    	_, _, _, _, stateReader, err := transactions.ComputeTxEnv(ctx, block, chainConfig, getHeader, contractHasTEVM, ethash.NewFaker(), tx, blockHash, txIndex)
    
    b00ris's avatar
    b00ris committed
    	if err != nil {
    		return StorageRangeResult{}, err
    	}
    	return StorageRangeAt(stateReader, contractAddress, keyStart, maxResult)
    }
    
    
    // AccountRange implements debug_accountRange. Returns a range of accounts involved in the given block rangeb
    
    func (api *PrivateDebugAPIImpl) AccountRange(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, startKey []byte, maxResults int, excludeCode, excludeStorage bool) (state.IteratorDump, error) {
    
    Alex Sharov's avatar
    Alex Sharov committed
    	tx, err := api.db.BeginRo(ctx)
    
    	if err != nil {
    		return state.IteratorDump{}, err
    	}
    	defer tx.Rollback()
    
    
    b00ris's avatar
    b00ris committed
    	var blockNumber uint64
    
    	if number, ok := blockNrOrHash.Number(); ok {
    		if number == rpc.PendingBlockNumber {
    			return state.IteratorDump{}, fmt.Errorf("accountRange for pending block not supported")
    		}
    		if number == rpc.LatestBlockNumber {
    			var err error
    
    
    Alex Sharov's avatar
    Alex Sharov committed
    			blockNumber, err = stages.GetStageProgress(tx, stages.Execution)
    
    b00ris's avatar
    b00ris committed
    			if err != nil {
    
    				return state.IteratorDump{}, fmt.Errorf("last block has not found: %w", err)
    
    b00ris's avatar
    b00ris committed
    			}
    		} else {
    			blockNumber = uint64(number)
    		}
    
    	} else if hash, ok := blockNrOrHash.Hash(); ok {
    
    		block, err1 := api.blockByHashWithSenders(tx, hash)
    
    		if err1 != nil {
    			return state.IteratorDump{}, err1
    		}
    
    b00ris's avatar
    b00ris committed
    		if block == nil {
    			return state.IteratorDump{}, fmt.Errorf("block %s not found", hash.Hex())
    		}
    		blockNumber = block.NumberU64()
    	}
    
    
    	if maxResults > AccountRangeMaxResults || maxResults <= 0 {
    		maxResults = AccountRangeMaxResults
    
    b00ris's avatar
    b00ris committed
    	}
    
    
    	dumper := state.NewDumper(tx, blockNumber)
    
    	res, err := dumper.IteratorDump(excludeCode, excludeStorage, common.BytesToAddress(startKey), maxResults)
    
    b00ris's avatar
    b00ris committed
    	if err != nil {
    		return state.IteratorDump{}, err
    	}
    
    	hash, err := rawdb.ReadCanonicalHash(tx, blockNumber)
    
    	if err != nil {
    		return state.IteratorDump{}, err
    	}
    
    b00ris's avatar
    b00ris committed
    	if hash != (common.Hash{}) {
    
    Alex Sharov's avatar
    Alex Sharov committed
    		header := rawdb.ReadHeader(tx, hash, blockNumber)
    
    b00ris's avatar
    b00ris committed
    		if header != nil {
    			res.Root = header.Root.String()
    		}
    	}
    
    
    b00ris's avatar
    b00ris committed
    	return res, nil
    
    b00ris's avatar
    b00ris committed
    }
    
    
    // GetModifiedAccountsByNumber implements debug_getModifiedAccountsByNumber. Returns a list of accounts modified in the given block.
    
    func (api *PrivateDebugAPIImpl) GetModifiedAccountsByNumber(ctx context.Context, startNumber rpc.BlockNumber, endNumber *rpc.BlockNumber) ([]common.Address, error) {
    
    Alex Sharov's avatar
    Alex Sharov committed
    	tx, err := api.db.BeginRo(ctx)
    
    	if err != nil {
    		return nil, err
    
    b00ris's avatar
    b00ris committed
    	}
    
    	defer tx.Rollback()
    
    b00ris's avatar
    b00ris committed
    
    
    Alex Sharov's avatar
    Alex Sharov committed
    	latestBlock, err := stages.GetStageProgress(tx, stages.Finish)
    
    b00ris's avatar
    b00ris committed
    	if err != nil {
    		return nil, err
    	}
    
    
    	// forces negative numbers to fail (too large) but allows zero
    	startNum := uint64(startNumber.Int64())
    	if startNum > latestBlock {
    		return nil, fmt.Errorf("start block (%d) is later than the latest block (%d)", startNum, latestBlock)
    	}
    
    b00ris's avatar
    b00ris committed
    
    
    	endNum := startNum + 1 // allows for single param calls
    
    	if endNumber != nil {
    		// forces negative numbers to fail (too large) but allows zero
    
    		endNum = uint64(endNumber.Int64()) + 1
    
    b00ris's avatar
    b00ris committed
    	}
    
    
    	// is endNum too big?
    	if endNum > latestBlock {
    		return nil, fmt.Errorf("end block (%d) is later than the latest block (%d)", endNum, latestBlock)
    
    b00ris's avatar
    b00ris committed
    	}
    
    
    	if startNum > endNum {
    		return nil, fmt.Errorf("start block (%d) must be less than or equal to end block (%d)", startNum, endNum)
    	}
    
    
    	return changeset.GetModifiedAccounts(tx, startNum, endNum)
    
    b00ris's avatar
    b00ris committed
    }
    
    
    // GetModifiedAccountsByHash implements debug_getModifiedAccountsByHash. Returns a list of accounts modified in the given block.
    
    func (api *PrivateDebugAPIImpl) GetModifiedAccountsByHash(ctx context.Context, startHash common.Hash, endHash *common.Hash) ([]common.Address, error) {
    
    Alex Sharov's avatar
    Alex Sharov committed
    	tx, err := api.db.BeginRo(ctx)
    
    	if err != nil {
    		return nil, err
    	}
    	defer tx.Rollback()
    
    
    	startBlock, err := api.blockByHashWithSenders(tx, startHash)
    
    	if err != nil {
    		return nil, err
    	}
    
    b00ris's avatar
    b00ris committed
    	if startBlock == nil {
    		return nil, fmt.Errorf("start block %x not found", startHash)
    	}
    
    	startNum := startBlock.NumberU64()
    
    	endNum := startNum + 1 // allows for single parameter calls
    
    b00ris's avatar
    b00ris committed
    
    
    	if endHash != nil {
    
    		endBlock, err := api.blockByHashWithSenders(tx, *endHash)
    
    		if err != nil {
    			return nil, err
    		}
    
    b00ris's avatar
    b00ris committed
    		if endBlock == nil {
    			return nil, fmt.Errorf("end block %x not found", *endHash)
    		}
    
    		endNum = endBlock.NumberU64() + 1
    
    b00ris's avatar
    b00ris committed
    	}
    
    
    	if startNum > endNum {
    		return nil, fmt.Errorf("start block (%d) must be less than or equal to end block (%d)", startNum, endNum)
    
    b00ris's avatar
    b00ris committed
    	}
    
    
    	return changeset.GetModifiedAccounts(tx, startNum, endNum)
    
    b00ris's avatar
    b00ris committed
    }
    
    ledgerwatch's avatar
    ledgerwatch committed
    
    func (api *PrivateDebugAPIImpl) AccountAt(ctx context.Context, blockHash common.Hash, txIndex uint64, address common.Address) (*AccountResult, error) {
    
    Alex Sharov's avatar
    Alex Sharov committed
    	tx, err := api.db.BeginRo(ctx)
    
    ledgerwatch's avatar
    ledgerwatch committed
    	if err != nil {
    		return nil, err
    	}
    	defer tx.Rollback()
    
    
    Alex Sharov's avatar
    Alex Sharov committed
    	chainConfig, err := api.chainConfig(tx)
    
    ledgerwatch's avatar
    ledgerwatch committed
    	if err != nil {
    		return nil, err
    	}
    
    	block, err := api.blockByHashWithSenders(tx, blockHash)
    
    	if err != nil {
    		return nil, err
    	}
    	if block == nil {
    		return nil, nil
    	}
    
    Evgeny Danilenko's avatar
    Evgeny Danilenko committed
    	getHeader := func(hash common.Hash, number uint64) *types.Header {
    
    		return rawdb.ReadHeader(tx, hash, number)
    	}
    
    	contractHasTEVM := func(contractHash common.Hash) (bool, error) { return false, nil }
    	if api.TevmEnabled {
    		contractHasTEVM = ethdb.GetHasTEVM(tx)
    	}
    
    	_, _, _, ibs, _, err := transactions.ComputeTxEnv(ctx, block, chainConfig, getHeader, contractHasTEVM, ethash.NewFaker(), tx, blockHash, txIndex)
    
    ledgerwatch's avatar
    ledgerwatch committed
    	if err != nil {
    		return nil, err
    	}
    	result := &AccountResult{}
    	result.Balance.ToInt().Set(ibs.GetBalance(address).ToBig())
    	result.Nonce = hexutil.Uint64(ibs.GetNonce(address))
    	result.Code = ibs.GetCode(address)
    	result.CodeHash = ibs.GetCodeHash(address)
    	return result, nil
    }
    
    type AccountResult struct {
    	Balance  hexutil.Big    `json:"balance"`
    	Nonce    hexutil.Uint64 `json:"nonce"`
    	Code     hexutil.Bytes  `json:"code"`
    	CodeHash common.Hash    `json:"codeHash"`
    }