Skip to content

Commit

Permalink
Merge pull request #6385 from onflow/bastian/debug-tx-command
Browse files Browse the repository at this point in the history
[Util] Add command to debug transaction
  • Loading branch information
turbolent authored Aug 27, 2024
2 parents 2d50ebb + 176eef1 commit 6a4b7bc
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 3 deletions.
147 changes: 147 additions & 0 deletions cmd/util/cmd/debug-tx/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package debug_tx

import (
"context"

"github.com/rs/zerolog/log"
"github.com/spf13/cobra"

sdk "github.com/onflow/flow-go-sdk"

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

// use the following command to forward port 9000 from the EN to localhost:9001
// `gcloud compute ssh '--ssh-flag=-A' --no-user-output-enabled --tunnel-through-iap migrationmainnet1-execution-001 --project flow-multi-region -- -NL 9001:localhost:9000`

var (
flagAccessAddress string
flagExecutionAddress string
flagChain string
flagTx string
flagComputeLimit uint64
flagAtLatestBlock bool
flagProposalKeySeq uint64
)

var Cmd = &cobra.Command{
Use: "debug-tx",
Short: "debug a transaction",
Run: run,
}

func init() {

Cmd.Flags().StringVar(
&flagChain,
"chain",
"",
"Chain name",
)
_ = Cmd.MarkFlagRequired("chain")

Cmd.Flags().StringVar(&flagAccessAddress, "access-address", "", "address of the access node")
_ = Cmd.MarkFlagRequired("access-address")

Cmd.Flags().StringVar(&flagExecutionAddress, "execution-address", "", "address of the execution node")
_ = Cmd.MarkFlagRequired("execution-address")

Cmd.Flags().StringVar(&flagTx, "tx", "", "transaction ID")
_ = Cmd.MarkFlagRequired("tx")

Cmd.Flags().Uint64Var(&flagComputeLimit, "compute-limit", 9999, "transaction compute limit")

Cmd.Flags().BoolVar(&flagAtLatestBlock, "at-latest-block", false, "run at latest block")

Cmd.Flags().Uint64Var(&flagProposalKeySeq, "proposal-key-seq", 0, "proposal key sequence number")
}

func run(*cobra.Command, []string) {

chainID := flow.ChainID(flagChain)
chain := chainID.Chain()

txID, err := flow.HexStringToIdentifier(flagTx)
if err != nil {
log.Fatal().Err(err).Msg("failed to parse transaction ID")
}

config, err := grpcclient.NewFlowClientConfig(flagAccessAddress, "", flow.ZeroID, true)
if err != nil {
log.Fatal().Err(err).Msg("failed to create flow client config")
}

flowClient, err := grpcclient.FlowClient(config)
if err != nil {
log.Fatal().Err(err).Msg("failed to create flow client")
}

log.Info().Msg("Fetching transaction ...")

tx, err := flowClient.GetTransaction(context.Background(), sdk.Identifier(txID))
if err != nil {
log.Fatal().Err(err).Msg("failed to fetch transaction")
}

log.Info().Msgf("Fetched transaction: %s", tx.ID())

log.Info().Msg("Fetching transaction result ...")

txResult, err := flowClient.GetTransactionResult(context.Background(), sdk.Identifier(txID))
if err != nil {
log.Fatal().Err(err).Msg("failed to fetch transaction result")
}

log.Info().Msgf("Fetched transaction result: %s at block %s", txResult.Status, txResult.BlockID)

log.Info().Msg("Debugging transaction ...")

debugger := debug.NewRemoteDebugger(
flagExecutionAddress,
chain,
log.Logger,
)

txBody := flow.NewTransactionBody().
SetScript(tx.Script).
SetComputeLimit(flagComputeLimit).
SetPayer(flow.Address(tx.Payer))

for _, argument := range tx.Arguments {
txBody.AddArgument(argument)
}

for _, authorizer := range tx.Authorizers {
txBody.AddAuthorizer(flow.Address(authorizer))
}

proposalKeySequenceNumber := tx.ProposalKey.SequenceNumber
if flagProposalKeySeq != 0 {
proposalKeySequenceNumber = flagProposalKeySeq
}

txBody.SetProposalKey(
flow.Address(tx.ProposalKey.Address),
tx.ProposalKey.KeyIndex,
proposalKeySequenceNumber,
)

var txErr, processErr error
if flagAtLatestBlock {
txErr, processErr = debugger.RunTransaction(txBody)
} else {
txErr, processErr = debugger.RunTransactionAtBlockID(
txBody,
flow.Identifier(txResult.BlockID),
"",
)
}
if txErr != nil {
log.Fatal().Err(txErr).Msg("transaction error")
}
if processErr != nil {
log.Fatal().Err(processErr).Msg("process error")
}
}
2 changes: 2 additions & 0 deletions cmd/util/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
checkpoint_collect_stats "github.com/onflow/flow-go/cmd/util/cmd/checkpoint-collect-stats"
checkpoint_list_tries "github.com/onflow/flow-go/cmd/util/cmd/checkpoint-list-tries"
checkpoint_trie_stats "github.com/onflow/flow-go/cmd/util/cmd/checkpoint-trie-stats"
debug_tx "github.com/onflow/flow-go/cmd/util/cmd/debug-tx"
diff_states "github.com/onflow/flow-go/cmd/util/cmd/diff-states"
epochs "github.com/onflow/flow-go/cmd/util/cmd/epochs/cmd"
export "github.com/onflow/flow-go/cmd/util/cmd/exec-data-json-export"
Expand Down Expand Up @@ -118,6 +119,7 @@ func addCommands() {
rootCmd.AddCommand(run_script.Cmd)
rootCmd.AddCommand(system_addresses.Cmd)
rootCmd.AddCommand(check_storage.Cmd)
rootCmd.AddCommand(debug_tx.Cmd)
}

func initConfig() {
Expand Down
9 changes: 6 additions & 3 deletions utils/debug/remoteDebugger.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ type RemoteDebugger struct {

// Warning : make sure you use the proper flow-go version, same version as the network you are collecting registers
// from, otherwise the execution might differ from the way runs on the network
func NewRemoteDebugger(grpcAddress string,
func NewRemoteDebugger(
grpcAddress string,
chain flow.Chain,
logger zerolog.Logger) *RemoteDebugger {
logger zerolog.Logger,
) *RemoteDebugger {

vm := fvm.NewVirtualMachine()

// no signature processor here
Expand Down Expand Up @@ -57,7 +60,7 @@ func (d *RemoteDebugger) RunTransaction(
return output.Err, nil
}

// RunTransaction runs the transaction and tries to collect the registers at
// RunTransactionAtBlockID runs the transaction and tries to collect the registers at
// the given blockID note that it would be very likely that block is far in the
// past and you can't find the trie to read the registers from.
// if regCachePath is empty, the register values won't be cached
Expand Down

0 comments on commit 6a4b7bc

Please sign in to comment.