github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/cli/util/cancel.go (about)

     1  package util
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/nspcc-dev/neo-go/cli/cmdargs"
     9  	"github.com/nspcc-dev/neo-go/cli/flags"
    10  	"github.com/nspcc-dev/neo-go/cli/options"
    11  	"github.com/nspcc-dev/neo-go/cli/txctx"
    12  	"github.com/nspcc-dev/neo-go/pkg/core/state"
    13  	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
    14  	"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
    15  	"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
    16  	"github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter"
    17  	"github.com/nspcc-dev/neo-go/pkg/util"
    18  	"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
    19  	"github.com/urfave/cli"
    20  )
    21  
    22  func cancelTx(ctx *cli.Context) error {
    23  	args := ctx.Args()
    24  	if len(args) == 0 {
    25  		return cli.NewExitError("transaction hash is missing", 1)
    26  	} else if len(args) > 1 {
    27  		return cli.NewExitError("only one transaction hash is accepted", 1)
    28  	}
    29  
    30  	txHash, err := util.Uint256DecodeStringLE(strings.TrimPrefix(args[0], "0x"))
    31  	if err != nil {
    32  		return cli.NewExitError(fmt.Sprintf("invalid tx hash: %s", args[0]), 1)
    33  	}
    34  
    35  	gctx, cancel := options.GetTimeoutContext(ctx)
    36  	defer cancel()
    37  
    38  	acc, w, err := options.GetAccFromContext(ctx)
    39  	if err != nil {
    40  		return cli.NewExitError(fmt.Errorf("failed to get account from context to sign the conflicting transaction: %w", err), 1)
    41  	}
    42  	defer w.Close()
    43  
    44  	signers, err := cmdargs.GetSignersAccounts(acc, w, nil, transaction.CalledByEntry)
    45  	if err != nil {
    46  		return cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1)
    47  	}
    48  	c, a, exitErr := options.GetRPCWithActor(gctx, ctx, signers)
    49  	if exitErr != nil {
    50  		return exitErr
    51  	}
    52  
    53  	mainTx, _ := c.GetRawTransactionVerbose(txHash)
    54  	if mainTx != nil && !mainTx.Blockhash.Equals(util.Uint256{}) {
    55  		return cli.NewExitError(fmt.Errorf("target transaction %s is accepted at block %s", txHash, mainTx.Blockhash.StringLE()), 1)
    56  	}
    57  
    58  	if mainTx != nil && !mainTx.HasSigner(acc.ScriptHash()) {
    59  		return cli.NewExitError(fmt.Errorf("account %s is not a signer of the conflicting transaction", acc.Address), 1)
    60  	}
    61  
    62  	resHash, resVub, err := a.SendTunedRun([]byte{byte(opcode.RET)}, []transaction.Attribute{{Type: transaction.ConflictsT, Value: &transaction.Conflicts{Hash: txHash}}}, func(r *result.Invoke, t *transaction.Transaction) error {
    63  		err := actor.DefaultCheckerModifier(r, t)
    64  		if err != nil {
    65  			return err
    66  		}
    67  		if mainTx != nil && t.NetworkFee < mainTx.NetworkFee+1 {
    68  			t.NetworkFee = mainTx.NetworkFee + 1
    69  		}
    70  		t.NetworkFee += int64(flags.Fixed8FromContext(ctx, "gas"))
    71  		if mainTx != nil {
    72  			t.ValidUntilBlock = mainTx.ValidUntilBlock
    73  		}
    74  		return nil
    75  	})
    76  	if err != nil {
    77  		return cli.NewExitError(fmt.Errorf("failed to send conflicting transaction: %w", err), 1)
    78  	}
    79  	var res *state.AppExecResult
    80  	if ctx.Bool("await") {
    81  		res, err = a.WaitAny(gctx, resVub, txHash, resHash)
    82  		if err != nil {
    83  			if errors.Is(err, waiter.ErrTxNotAccepted) {
    84  				if mainTx == nil {
    85  					return cli.NewExitError(fmt.Errorf("neither target nor conflicting transaction is accepted before the current height %d (ValidUntilBlock value of conlicting transaction). Main transaction is unknown to the provided RPC node, thus still has chances to be accepted, you may try cancellation again", resVub), 1)
    86  				}
    87  				fmt.Fprintf(ctx.App.Writer, "Neither target nor conflicting transaction is accepted before the current height %d (ValidUntilBlock value of both target and conflicting transactions). Main transaction is not valid anymore, cancellation is successful\n", resVub)
    88  				return nil
    89  			}
    90  			return cli.NewExitError(fmt.Errorf("failed to await target/ conflicting transaction %s/ %s: %w", txHash.StringLE(), resHash.StringLE(), err), 1)
    91  		}
    92  		if txHash.Equals(res.Container) {
    93  			tx, err := c.GetRawTransactionVerbose(txHash)
    94  			if err != nil {
    95  				return cli.NewExitError(fmt.Errorf("target transaction %s is accepted", txHash), 1)
    96  			}
    97  			return cli.NewExitError(fmt.Errorf("target transaction %s is accepted at block %s", txHash, tx.Blockhash.StringLE()), 1)
    98  		}
    99  		fmt.Fprintln(ctx.App.Writer, "Conflicting transaction accepted")
   100  	}
   101  	txctx.DumpTransactionInfo(ctx.App.Writer, resHash, res)
   102  	return nil
   103  }