This repository has been archived on 2024-04-07. You can view files and clone it, but cannot push or open issues or pull requests.
go-monero/cmd/monero/commands/daemon/get_transaction.go
2022-10-05 09:40:23 +02:00

217 lines
5 KiB
Go

package daemon
import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"time"
"github.com/dustin/go-humanize"
"github.com/spf13/cobra"
"git.gammaspectra.live/P2Pool/go-monero/cmd/monero/display"
"git.gammaspectra.live/P2Pool/go-monero/cmd/monero/options"
"git.gammaspectra.live/P2Pool/go-monero/pkg/constant"
"git.gammaspectra.live/P2Pool/go-monero/pkg/rpc/daemon"
)
type getTransactionCommand struct {
Txn string
Unwrap bool
JSON bool
client *daemon.Client
}
func (c *getTransactionCommand) Cmd() *cobra.Command {
cmd := &cobra.Command{
Use: "get-transaction",
Short: "lookup a transaction, in the pool or not",
RunE: c.RunE,
}
cmd.Flags().StringVarP(&c.Txn, "transaction", "t",
"", "hash of a transaction to lookup")
_ = cmd.MarkFlagRequired("transaction")
cmd.Flags().BoolVar(&c.JSON, "json",
false, "whether or not to output the result as json")
cmd.Flags().BoolVar(&c.Unwrap, "unwrap",
false, "whether or not to unwrap the json representation of the transaction")
return cmd
}
func (c *getTransactionCommand) RunE(_ *cobra.Command, _ []string) error {
ctx, cancel := options.RootOpts.Context()
defer cancel()
client, err := options.RootOpts.Client()
if err != nil {
return fmt.Errorf("client: %w", err)
}
resp, err := client.GetTransactions(ctx, []string{c.Txn})
if err != nil {
return fmt.Errorf("get transactions: %w", err)
}
if c.JSON {
if !c.Unwrap {
return display.JSON(resp)
}
txns, err := resp.GetTransactions()
if err != nil {
return fmt.Errorf("resp get txns: %w", err)
}
return display.JSON(txns)
}
c.client = client
return c.pretty(ctx, resp)
}
func (c *getTransactionCommand) pretty(ctx context.Context, v *daemon.GetTransactionsResult) error {
if len(v.Txs) == 0 {
return nil
}
txn := v.Txs[0]
txnDetails := &daemon.TransactionJSON{}
if err := json.Unmarshal([]byte(txn.AsJSON), txnDetails); err != nil {
return fmt.Errorf("unsmarshal txjson: %w", err)
}
if err := c.prettyHeader(ctx, txn, txnDetails); err != nil {
return err
}
if err := c.prettyOutputs(txn, txnDetails); err != nil {
return err
}
return c.prettyInputs(ctx, txnDetails)
}
// nolint:forbidigo
func (c *getTransactionCommand) prettyHeader(
ctx context.Context,
txn daemon.GetTransactionsResultTransaction,
txnDetails *daemon.TransactionJSON,
) error {
table := display.NewTable()
fee := float64(txnDetails.RctSignatures.Txnfee)
size := len(txn.AsHex) / 2
table.AddRow("Hash:", txn.TxHash)
table.AddRow("Fee (µɱ):", fee/constant.MicroXMR)
table.AddRow("Fee per kB (µɱ):", (fee/constant.MicroXMR)/(float64(size)/1024))
table.AddRow("In/Out:", fmt.Sprintf("%d/%d", len(txnDetails.Vin), len(txnDetails.Vout)))
table.AddRow("Size:", humanize.IBytes(uint64(len(txn.AsHex))/2))
table.AddRow("Public Key:", hex.EncodeToString(txnDetails.Extra[1:33]))
if !txn.InPool {
table.AddRow("Age:", humanize.Time(time.Unix(txn.BlockTimestamp, 0)))
table.AddRow("Block:", txn.BlockHeight)
heightResp, err := c.client.GetHeight(ctx)
if err != nil {
return fmt.Errorf("get block count: %w", err)
}
table.AddRow("Confirmations:", heightResp.Height-txn.BlockHeight)
} else {
table.AddRow("Confirmations:", 0)
}
fmt.Println(table)
fmt.Println("")
return nil
}
// nolint:forbidigo
func (c *getTransactionCommand) prettyOutputs(
txn daemon.GetTransactionsResultTransaction,
txnDetails *daemon.TransactionJSON,
) error {
table := display.NewTable()
table.AddRow("Outputs")
table.AddRow()
table.AddRow("", "STEALTH ADDR", "AMOUNT", "AMOUNT IDX")
for idx, vout := range txnDetails.Vout {
amount := "?"
if vout.Amount != 0 {
amount = display.PreciseXMR(vout.Amount)
}
var outIdx interface{} = "?"
if len(txn.OutputIndices) != 0 {
outIdx = txn.OutputIndices[idx]
}
table.AddRow(idx, vout.Target.Key, amount, outIdx)
}
fmt.Println(table)
fmt.Println("")
return nil
}
// nolint:forbidigo
func (c *getTransactionCommand) prettyInputs(
ctx context.Context,
txnDetails *daemon.TransactionJSON,
) error {
for _, vin := range txnDetails.Vin {
outsResp, err := c.client.GetOuts(ctx, decodeOffsets(vin.Key.KeyOffsets), true)
if err != nil {
return fmt.Errorf("outs: %w", err)
}
fmt.Println()
table := display.NewTable()
table.AddRow("Input Key Image:", vin.Key.KImage)
fmt.Println(table)
fmt.Println()
table = display.NewTable()
table.AddRow("", "RING MEMBER", "TXID", "BLK", "AGE")
for idx, out := range outsResp.Outs {
blockHeaderResp, err := c.client.GetBlockHeaderByHeight(ctx, out.Height)
if err != nil {
return fmt.Errorf("get block header by height %d: %w", out.Height, err)
}
table.AddRow(idx, out.Key, out.Txid, out.Height,
humanize.Time(time.Unix(blockHeaderResp.BlockHeader.Timestamp, 0)),
)
}
fmt.Println(table)
fmt.Println()
}
return nil
}
func decodeOffsets(offsets []uint) []uint {
accum := uint(0)
res := make([]uint, len(offsets))
for idx, offset := range offsets {
accum += offset
res[idx] = accum
}
return res
}
func init() {
RootCommand.AddCommand((&getTransactionCommand{}).Cmd())
}