debug

package
v0.42.5-cadence-compil... Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 19, 2025 License: AGPL-3.0 Imports: 27 Imported by: 2

README

Remote Debugger

The remote debugger allows running transactions and scripts against existing network data.

It uses APIs to fetch registers and block info, for example the register value API of the execution nodes, or the execution data API of the access nodes.

This is mostly provided for debugging purpose and should not be used for production level operations.

Optionally, the debugger allows the fetched registers that are necessary for the execution to be written to and read from a cache.

Use the ExecutionNodeStorageSnapshot to fetch the registers and block info from the execution node (live/recent data).

Use the ExecutionDataStorageSnapshot to fetch the execution data from the access node (recent/historic data).

Sample Code

package debug_test

import (
	"context"
	"fmt"
	"os"
	"testing"

	"github.com/onflow/flow/protobuf/go/flow/access"
	"github.com/onflow/flow/protobuf/go/flow/execution"
	"github.com/onflow/flow/protobuf/go/flow/executiondata"
	"github.com/rs/zerolog"
	"github.com/stretchr/testify/require"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"

	"github.com/onflow/flow-go/model/flow"
	"github.com/onflow/flow-go/utils/debug"
)

func getTransaction(chain flow.Chain) *flow.TransactionBody {

	const scriptTemplate = `
	import FlowServiceAccount from 0x%s
	transaction() {
		prepare(signer: &Account) {
			log(signer.balance)
		}
	  }
	`

	script := []byte(fmt.Sprintf(scriptTemplate, chain.ServiceAddress()))
	txBody := flow.NewTransactionBody().
		SetComputeLimit(9999).
		SetScript(script).
		SetPayer(chain.ServiceAddress()).
		SetProposalKey(chain.ServiceAddress(), 0, 0)
	txBody.Authorizers = []flow.Address{chain.ServiceAddress()}

	return txBody
}

func TestDebugger_RunTransactionAgainstExecutionNodeAtBlockID(t *testing.T) {

	host := "execution-001.mainnet26.nodes.onflow.org:9000"

	conn, err := grpc.NewClient(
		host,
		grpc.WithTransportCredentials(insecure.NewCredentials()),
	)
	require.NoError(t, err)
	defer conn.Close()

	executionClient := execution.NewExecutionAPIClient(conn)

	blockID, err := flow.HexStringToIdentifier("e68a9a1fe849d1be80e4c5e414f53e3b59a170b88785e0b22be077ae9c3bbd29")
	require.NoError(t, err)

	header, err := debug.GetExecutionAPIBlockHeader(executionClient, context.Background(), blockID)

	snapshot, err := debug.NewExecutionNodeStorageSnapshot(executionClient, nil, blockID)
	require.NoError(t, err)

	defer snapshot.Close()

	chain := flow.Mainnet.Chain()
	logger := zerolog.New(os.Stdout).With().Logger()
	debugger := debug.NewRemoteDebugger(chain, logger)

	txBody := getTransaction(chain)

	_, txErr, err := debugger.RunTransaction(txBody, snapshot, header)
	require.NoError(t, txErr)
	require.NoError(t, err)
}

func TestDebugger_RunTransactionAgainstAccessNodeAtBlockIDWithFileCache(t *testing.T) {

	host := "access.mainnet.nodes.onflow.org:9000"

	conn, err := grpc.NewClient(
		host,
		grpc.WithTransportCredentials(insecure.NewCredentials()),
	)
	require.NoError(t, err)
	defer conn.Close()

	executionDataClient := executiondata.NewExecutionDataAPIClient(conn)

	var blockHeight uint64 = 100_000_000
	
	blockID, err := flow.HexStringToIdentifier("e68a9a1fe849d1be80e4c5e414f53e3b59a170b88785e0b22be077ae9c3bbd29")
	require.NoError(t, err)

	accessClient := access.NewAccessAPIClient(conn)
	header, err := debug.GetAccessAPIBlockHeader(
		accessClient,
		context.Background(),
		blockID,
	)
	require.NoError(t, err)

	testCacheFile := "test.cache"
	defer os.Remove(testCacheFile)

	cache := debug.NewFileRegisterCache(testCacheFile)

	snapshot, err := debug.NewExecutionDataStorageSnapshot(executionDataClient, cache, blockHeight)
	require.NoError(t, err)

	defer snapshot.Close()

	chain := flow.Mainnet.Chain()
	logger := zerolog.New(os.Stdout).With().Logger()
	debugger := debug.NewRemoteDebugger(chain, logger)

	txBody := getTransaction(chain)

	// the first run will cache the results
	_, txErr, err := debugger.RunTransaction(txBody, snapshot, header)
	require.NoError(t, txErr)
	require.NoError(t, err)

	// the second run should only use the cache.
	_, txErr, err = debugger.RunTransaction(txBody, snapshot, header)
	require.NoError(t, txErr)
	require.NoError(t, err)
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CompareRegisterIDs

func CompareRegisterIDs(a flow.RegisterID, b flow.RegisterID) int

func GetAccessAPIBlockHeader added in v0.38.0

func GetAccessAPIBlockHeader(
	ctx context.Context,
	client access.AccessAPIClient,
	blockID flow.Identifier,
) (*flow.Header, error)

func GetHeapAllocsBytes added in v0.27.0

func GetHeapAllocsBytes() uint64

GetHeapAllocsBytes returns the value of /gc/heap/allocs:bytes.

func SortRegisterEntries

func SortRegisterEntries(registerEntries []flow.RegisterEntry)

func SortRegisterIDs

func SortRegisterIDs(registerIDs []flow.RegisterID)

func WriteResult

func WriteResult(w io.Writer, id flow.Identifier, result Result)

Types

type CachingStorageSnapshot

type CachingStorageSnapshot struct {
	// contains filtered or unexported fields
}

CachingStorageSnapshot is a storage snapshot that caches register values in memory to avoid repeated calls to the backing snapshot.

func NewCachingStorageSnapshot

func NewCachingStorageSnapshot(backing StorageSnapshot) *CachingStorageSnapshot

func (*CachingStorageSnapshot) Get

func (*CachingStorageSnapshot) Set

type CapturingStorageSnapshot

type CapturingStorageSnapshot struct {
	Reads []struct {
		flow.RegisterID
		flow.RegisterValue
	}
	Writes []struct {
		flow.RegisterID
		flow.RegisterValue
	}
	// contains filtered or unexported fields
}

func NewCapturingStorageSnapshot

func NewCapturingStorageSnapshot(backing StorageSnapshot) *CapturingStorageSnapshot

func (*CapturingStorageSnapshot) Get

func (*CapturingStorageSnapshot) Set

type ExecutionDataRemoteClient

type ExecutionDataRemoteClient struct {
	// contains filtered or unexported fields
}

ExecutionDataRemoteClient is a remote client that connects to an access node and uses the execution data API to fetch execution data.

func NewExecutionDataRemoteClient

func NewExecutionDataRemoteClient(address string) (*ExecutionDataRemoteClient, error)

func (*ExecutionDataRemoteClient) Close

func (c *ExecutionDataRemoteClient) Close() error

func (*ExecutionDataRemoteClient) StorageSnapshot

func (c *ExecutionDataRemoteClient) StorageSnapshot(blockHeight uint64, _ flow.Identifier) (snapshot.StorageSnapshot, error)

type ExecutionDataStorageSnapshot added in v0.38.0

type ExecutionDataStorageSnapshot struct {
	Client      executiondata.ExecutionDataAPIClient
	BlockHeight uint64
}

ExecutionDataStorageSnapshot provides a storage snapshot connected to an access node to read the registers (via its execution data API).

func NewExecutionDataStorageSnapshot added in v0.38.0

func NewExecutionDataStorageSnapshot(
	client executiondata.ExecutionDataAPIClient,
	blockHeight uint64,
) (
	*ExecutionDataStorageSnapshot,
	error,
)

func (*ExecutionDataStorageSnapshot) Close added in v0.38.0

func (snapshot *ExecutionDataStorageSnapshot) Close() error

func (*ExecutionDataStorageSnapshot) Get added in v0.38.0

func (snapshot *ExecutionDataStorageSnapshot) Get(
	id flow.RegisterID,
) (
	value flow.RegisterValue,
	err error,
)

type ExecutionNodeRemoteClient

type ExecutionNodeRemoteClient struct {
	// contains filtered or unexported fields
}

ExecutionNodeRemoteClient is a remote client that connects to an execution node and uses the execution API to fetch execution data.

func NewExecutionNodeRemoteClient

func NewExecutionNodeRemoteClient(address string) (*ExecutionNodeRemoteClient, error)

func (*ExecutionNodeRemoteClient) Close

func (c *ExecutionNodeRemoteClient) Close() error

func (*ExecutionNodeRemoteClient) StorageSnapshot

func (c *ExecutionNodeRemoteClient) StorageSnapshot(_ uint64, blockID flow.Identifier) (snapshot.StorageSnapshot, error)

type ExecutionNodeStorageSnapshot added in v0.38.0

type ExecutionNodeStorageSnapshot struct {
	Client  execution.ExecutionAPIClient
	BlockID flow.Identifier
}

ExecutionNodeStorageSnapshot provides a storage snapshot connected to an execution node to read the registers.

func NewExecutionNodeStorageSnapshot added in v0.38.0

func NewExecutionNodeStorageSnapshot(
	client execution.ExecutionAPIClient,
	blockID flow.Identifier,
) (
	*ExecutionNodeStorageSnapshot,
	error,
)

func (*ExecutionNodeStorageSnapshot) Close added in v0.38.0

func (snapshot *ExecutionNodeStorageSnapshot) Close() error

func (*ExecutionNodeStorageSnapshot) Get added in v0.38.0

func (snapshot *ExecutionNodeStorageSnapshot) Get(
	id flow.RegisterID,
) (
	value flow.RegisterValue,
	err error,
)

type FileRegisterCache added in v0.38.0

type FileRegisterCache struct {
	// contains filtered or unexported fields
}

func NewFileRegisterCache added in v0.38.0

func NewFileRegisterCache(filePath string) (*FileRegisterCache, error)

func (*FileRegisterCache) Get added in v0.38.0

func (c *FileRegisterCache) Get(owner, key string) ([]byte, bool)

func (*FileRegisterCache) Persist added in v0.38.0

func (c *FileRegisterCache) Persist() error

func (*FileRegisterCache) Set added in v0.38.0

func (c *FileRegisterCache) Set(owner, key string, value []byte)

type InMemoryRegisterCache added in v0.38.0

type InMemoryRegisterCache struct {
	// contains filtered or unexported fields
}

func NewInMemoryRegisterCache added in v0.38.0

func NewInMemoryRegisterCache() *InMemoryRegisterCache

func (*InMemoryRegisterCache) Get added in v0.38.0

func (c *InMemoryRegisterCache) Get(owner, key string) ([]byte, bool)

func (*InMemoryRegisterCache) Persist added in v0.38.0

func (c *InMemoryRegisterCache) Persist() error

func (*InMemoryRegisterCache) Set added in v0.38.0

func (c *InMemoryRegisterCache) Set(owner, key string, value []byte)

type InterestingCadenceSpanExporter

type InterestingCadenceSpanExporter struct {
	Spans []otelTrace.ReadOnlySpan
}

func (*InterestingCadenceSpanExporter) ExportSpans

func (*InterestingCadenceSpanExporter) Shutdown

func (*InterestingCadenceSpanExporter) WriteSpans

func (s *InterestingCadenceSpanExporter) WriteSpans(writer io.Writer) error

type RegisterCache added in v0.38.0

type RegisterCache interface {
	Get(owner, key string) (value []byte, found bool)
	Set(owner, key string, value []byte)
	Persist() error
}

type RemoteClient

type RemoteClient interface {
	io.Closer
	StorageSnapshot(blockHeight uint64, blockID flow.Identifier) (snapshot.StorageSnapshot, error)
}

type RemoteDebugger added in v0.23.1

type RemoteDebugger struct {
	// contains filtered or unexported fields
}

func NewRemoteDebugger added in v0.23.1

func NewRemoteDebugger(
	chain flow.Chain,
	logger zerolog.Logger,
	vmTransactionExecutionEnabled bool,
	vmScriptExecutionEnabled bool,
	options ...fvm.Option,
) *RemoteDebugger

NewRemoteDebugger creates a new remote debugger. NOTE: Make sure to use the same version of flow-go as the network you are collecting registers from, otherwise the execution might differ from the way it runs on the network

func (*RemoteDebugger) RunSDKTransaction

func (d *RemoteDebugger) RunSDKTransaction(
	tx *sdk.Transaction,
	snapshot StorageSnapshot,
	header *flow.Header,
	computeLimit uint64,
) (
	Result,
	error,
)

func (*RemoteDebugger) RunScript added in v0.23.1

func (d *RemoteDebugger) RunScript(
	code []byte,
	arguments [][]byte,
	snapshot StorageSnapshot,
	blockHeader *flow.Header,
) (
	Result,
	error,
)

RunScript runs the script using the given storage snapshot.

func (*RemoteDebugger) RunTransaction added in v0.23.1

func (d *RemoteDebugger) RunTransaction(
	txBody *flow.TransactionBody,
	snapshot StorageSnapshot,
	blockHeader *flow.Header,
) (
	Result,
	error,
)

RunTransaction runs the transaction using the given storage snapshot.

type Result

type Result struct {
	Snapshot *snapshot.ExecutionSnapshot
	Output   fvm.ProcedureOutput
}

type StorageSnapshot added in v0.38.0

type StorageSnapshot interface {
	snapshot.StorageSnapshot
}

type UpdatableStorageSnapshot

type UpdatableStorageSnapshot interface {
	StorageSnapshot
	Set(id flow.RegisterID, value flow.RegisterValue)
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL