Skip to content

Commit

Permalink
feat(cmd): Add tx show subcommand for pretty-printing transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
kostko committed Feb 17, 2023
1 parent e4ec75a commit c99f5a5
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 34 deletions.
13 changes: 11 additions & 2 deletions cmd/common/selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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")
Expand Down
45 changes: 30 additions & 15 deletions cmd/common/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
110 changes: 93 additions & 17 deletions cmd/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -25,7 +27,7 @@ var (

txSubmitCmd = &cobra.Command{
Use: "submit <filename.json>",
Short: "Submit a previously signed transaction",
Short: "Submit a transaction",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
cfg := cliConfig.Global()
Expand All @@ -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 <filename.json>",
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)
}

0 comments on commit c99f5a5

Please sign in to comment.