good morning!!!!

Skip to content
Snippets Groups Projects
debug_api.go 8.4 KiB
Newer Older
b00ris's avatar
b00ris committed
package commands

import (
	"context"
	"fmt"
	jsoniter "github.com/json-iterator/go"
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"
	"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/adapter"
	"github.com/ledgerwatch/erigon/turbo/transactions"
b00ris's avatar
b00ris committed
)

// 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
b00ris's avatar
b00ris committed
	AccountRange(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, start []byte, maxResults int, nocode, nostorage, incompletes bool) (state.IteratorDump, error)
	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
	db     ethdb.RoKV
	GasCap uint64
b00ris's avatar
b00ris committed
}

// NewPrivateDebugAPI returns PrivateDebugAPIImpl instance
Alex Sharov's avatar
Alex Sharov committed
func NewPrivateDebugAPI(base *BaseAPI, db ethdb.RoKV, 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()

	bc := adapter.NewBlockGetter(tx)
Alex Sharov's avatar
Alex Sharov committed
	chainConfig, err := api.chainConfig(tx)
	if err != nil {
		return StorageRangeResult{}, err
	}
	getHeader := func(hash common.Hash, number uint64) *types.Header { return rawdb.ReadHeader(tx, hash, number) }
Evgeny Danilenko's avatar
Evgeny Danilenko committed
	checkTEVM := ethdb.GetCheckTEVM(tx)
	_, _, _, _, stateReader, err := transactions.ComputeTxEnv(ctx, bc, chainConfig, getHeader, checkTEVM, 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 range
func (api *PrivateDebugAPIImpl) AccountRange(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, startKey []byte, maxResults int, excludeCode, excludeStorage, excludeMissingPreimages 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 := rawdb.ReadBlockByHash(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 > eth.AccountRangeMaxResults || maxResults <= 0 {
		maxResults = eth.AccountRangeMaxResults
	}

	dumper := state.NewDumper(tx, blockNumber)
	res, err := dumper.IteratorDump(excludeCode, excludeStorage, excludeMissingPreimages, 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 // allows for single param calls
	if endNumber != nil {
		// forces negative numbers to fail (too large) but allows zero
		endNum = uint64(endNumber.Int64())
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 := rawdb.ReadBlockByHash(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 // allows for single parameter calls
b00ris's avatar
b00ris committed

	if endHash != nil {
		endBlock, err := rawdb.ReadBlockByHash(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()
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()

	bc := adapter.NewBlockGetter(tx)
Alex Sharov's avatar
Alex Sharov committed
	chainConfig, err := api.chainConfig(tx)
ledgerwatch's avatar
ledgerwatch committed
	if err != nil {
		return nil, err
	}
Evgeny Danilenko's avatar
Evgeny Danilenko committed
	getHeader := func(hash common.Hash, number uint64) *types.Header {
		return rawdb.ReadHeader(tx, hash, number)
	}
Evgeny Danilenko's avatar
Evgeny Danilenko committed
	checkTEVM := ethdb.GetCheckTEVM(tx)
	_, _, _, ibs, _, err := transactions.ComputeTxEnv(ctx, bc, chainConfig, getHeader, checkTEVM, 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"`
}