diff --git a/cmd/common/selector.go b/cmd/common/selector.go index d9033e89..20fd70a7 100644 --- a/cmd/common/selector.go +++ b/cmd/common/selector.go @@ -20,8 +20,12 @@ var ( noParaTime bool ) -// SelectorFlags contains the common selector flags for network/paratime/wallet. -var SelectorFlags *flag.FlagSet +var ( + // SelectorFlags contains the common selector flags for network/paratime/wallet. + SelectorFlags *flag.FlagSet + // SelectorNPFlags contains the common selector flags for network/paratime. + SelectorNPFlags *flag.FlagSet +) // NPASelection contains the network/paratime/account selection. type NPASelection struct { @@ -90,6 +94,11 @@ func init() { SelectorFlags.BoolVar(&noParaTime, "no-paratime", false, "explicitly set that no paratime should be used") SelectorFlags.StringVar(&selectedAccount, "account", "", "explicitly set account to use") + SelectorNPFlags = flag.NewFlagSet("", flag.ContinueOnError) + SelectorNPFlags.StringVar(&selectedNetwork, "network", "", "explicitly set network to use") + SelectorNPFlags.StringVar(&selectedParaTime, "paratime", "", "explicitly set paratime to use") + SelectorNPFlags.BoolVar(&noParaTime, "no-paratime", false, "explicitly set that no paratime should be used") + // Backward compatibility. SelectorFlags.StringVar(&selectedAccount, "wallet", "", "explicitly set account to use. OBSOLETE, USE --account INSTEAD!") err := SelectorFlags.MarkHidden("wallet") diff --git a/cmd/common/transaction.go b/cmd/common/transaction.go index 7ca450e0..639159e0 100644 --- a/cmd/common/transaction.go +++ b/cmd/common/transaction.go @@ -249,44 +249,59 @@ func SignParaTimeTransaction( return ts.UnverifiedTransaction(), meta, nil } -// PrintTransactionBeforeSigning prints the transaction and asks the user for confirmation. -func PrintTransactionBeforeSigning(npa *NPASelection, tx interface{}) { - fmt.Printf("You are about to sign the following transaction:\n") - +// PrintTransaction prints the transaction which can be either signed or unsigned. +func PrintTransaction(npa *NPASelection, tx interface{}) { + var isParaTimeTx bool switch rtx := tx.(type) { - case *consensusTx.Transaction: - // Consensus transaction. + case consensusPretty.PrettyPrinter: + // Signed or unsigned consensus transaction. ctx := context.Background() ctx = context.WithValue(ctx, consensusPretty.ContextKeyTokenSymbol, npa.Network.Denomination.Symbol) ctx = context.WithValue(ctx, consensusPretty.ContextKeyTokenValueExponent, npa.Network.Denomination.Decimals) + + // Set up chain context for signature verification during pretty-printing. + coreSignature.UnsafeResetChainContext() + coreSignature.SetChainContext(npa.Network.ChainContext) rtx.PrettyPrint(ctx, "", os.Stdout) - default: + case *types.Transaction, *types.UnverifiedTransaction: // TODO: Add pretty variant for paratime transactions. formatted, err := json.MarshalIndent(tx, "", " ") cobra.CheckErr(err) fmt.Println(string(formatted)) + isParaTimeTx = true + default: + fmt.Printf("[unsupported transaction type: %T]\n", tx) } - fmt.Println() - fmt.Printf("Account: %s", npa.AccountName) - if len(npa.Account.Description) > 0 { - fmt.Printf(" (%s)", npa.Account.Description) - } fmt.Println() fmt.Printf("Network: %s", npa.NetworkName) if len(npa.Network.Description) > 0 { fmt.Printf(" (%s)", npa.Network.Description) } + fmt.Println() - if _, isParaTimeTx := tx.(*types.Transaction); isParaTimeTx && npa.ParaTime != nil { - fmt.Printf("Paratime: %s", npa.ParaTimeName) + if isParaTimeTx && npa.ParaTime != nil { + fmt.Printf("ParaTime: %s", npa.ParaTimeName) if len(npa.ParaTime.Description) > 0 { fmt.Printf(" (%s)", npa.ParaTime.Description) } fmt.Println() } else { - fmt.Println("Paratime: none (consensus layer)") + fmt.Println("ParaTime: none (consensus layer)") } +} + +// PrintTransactionBeforeSigning prints the transaction and asks the user for confirmation. +func PrintTransactionBeforeSigning(npa *NPASelection, tx interface{}) { + fmt.Printf("You are about to sign the following transaction:\n") + + PrintTransaction(npa, tx) + + fmt.Printf("Account: %s", npa.AccountName) + if len(npa.Account.Description) > 0 { + fmt.Printf(" (%s)", npa.Account.Description) + } + fmt.Println() // Ask the user to confirm signing this transaction. Confirm("Sign this transaction?", "signing aborted") diff --git a/cmd/tx.go b/cmd/tx.go index 0fccfbd4..466cf3f0 100644 --- a/cmd/tx.go +++ b/cmd/tx.go @@ -5,9 +5,11 @@ import ( "encoding/json" "fmt" "io/ioutil" + "reflect" "github.com/spf13/cobra" + "github.com/oasisprotocol/oasis-core/go/common/cbor" consensusTx "github.com/oasisprotocol/oasis-core/go/consensus/api/transaction" "github.com/oasisprotocol/oasis-sdk/client-sdk/go/connection" @@ -25,7 +27,7 @@ var ( txSubmitCmd = &cobra.Command{ Use: "submit ", - Short: "Submit a previously signed transaction", + Short: "Submit a transaction", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { cfg := cliConfig.Global() @@ -40,31 +42,105 @@ var ( rawTx, err := ioutil.ReadFile(filename) cobra.CheckErr(err) - // Determine what kind of a transaction this is by attempting to decode it as either a - // consensus layer transaction or a runtime transaction. - var ( - sigTx interface{} - consTx consensusTx.SignedTransaction - rtTx types.UnverifiedTransaction - ) - if err = json.Unmarshal(rawTx, &consTx); err != nil { - // Failed to unmarshal as consensus transaction, try as runtime transaction. - if err = json.Unmarshal(rawTx, &rtTx); err != nil { - cobra.CheckErr(fmt.Errorf("malformed transaction")) - } - sigTx = &rtTx - } else { - sigTx = &consTx + tx, err := tryDecodeTx(rawTx) + cobra.CheckErr(err) + + var sigTx, meta interface{} + switch dtx := tx.(type) { + case *consensusTx.SignedTransaction, *types.UnverifiedTransaction: + // Signed transaction, just broadcast. + sigTx = tx + case *consensusTx.Transaction: + // Unsigned consensus transaction, sign first. + acc := common.LoadAccount(cfg, npa.AccountName) + sigTx, err = common.SignConsensusTransaction(ctx, npa, acc, conn, dtx) + cobra.CheckErr(err) + case *types.Transaction: + // Unsigned runtime transaction, sign first. + acc := common.LoadAccount(cfg, npa.AccountName) + sigTx, meta, err = common.SignParaTimeTransaction(ctx, npa, acc, conn, dtx) + cobra.CheckErr(err) } // Broadcast signed transaction. - common.BroadcastTransaction(ctx, npa.ParaTime, conn, sigTx, nil, nil) + common.BroadcastTransaction(ctx, npa.ParaTime, conn, sigTx, meta, nil) + }, + } + + txShowCmd = &cobra.Command{ + Use: "show ", + Short: "Pretty print a transaction", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + cfg := cliConfig.Global() + npa := common.GetNPASelection(cfg) + filename := args[0] + + rawTx, err := ioutil.ReadFile(filename) + cobra.CheckErr(err) + + tx, err := tryDecodeTx(rawTx) + cobra.CheckErr(err) + + common.PrintTransaction(npa, tx) }, } ) +func tryDecodeTx(rawTx []byte) (any, error) { + // Determine what kind of a transaction this is by attempting to decode it as either a + // consensus layer transaction or a runtime transaction. Either could also be unsigned. + txTypes := []struct { + typ any + valFn func(v any) bool + }{ + // Consensus transactions. + { + consensusTx.SignedTransaction{}, + func(v any) bool { + tx := v.(*consensusTx.SignedTransaction) + return len(tx.Blob) > 0 && tx.Signature.SanityCheck(tx.Signature.PublicKey) == nil + }, + }, + { + consensusTx.Transaction{}, + func(v any) bool { + tx := v.(*consensusTx.Transaction) + return tx.SanityCheck() == nil + }, + }, + // Runtime transactions. + { + types.UnverifiedTransaction{}, + func(v any) bool { + tx := v.(*types.UnverifiedTransaction) + return len(tx.Body) > 0 && len(tx.AuthProofs) > 0 + }, + }, + { + types.Transaction{}, + func(v any) bool { + tx := v.(*types.Transaction) + return tx.ValidateBasic() == nil + }, + }, + } + for _, txType := range txTypes { + v := reflect.New(reflect.TypeOf(txType.typ)).Interface() + if err := json.Unmarshal(rawTx, v); err != nil || !txType.valFn(v) { + if err := cbor.Unmarshal(rawTx, v); err != nil || !txType.valFn(v) { + continue + } + } + return v, nil + } + return nil, fmt.Errorf("malformed transaction") +} + func init() { txSubmitCmd.Flags().AddFlagSet(common.SelectorFlags) + txShowCmd.Flags().AddFlagSet(common.SelectorNPFlags) txCmd.AddCommand(txSubmitCmd) + txCmd.AddCommand(txShowCmd) }