cmd: improve pretty-printing, add get-version

- update get-height's description
- update some fields to prefer uint64 rather than `int`

	all those fields would never take negative values and should be
	able to grow quite a lot

- switch commands from display to humanize

	`humanize` gives us some pretty handy human readable
	conversions, so let's just go with it rather than rolling our
	own

- add get-version command

- prettify get-transaction pool

	example out (hash reduced in size):

	AGE             HASH                FEE (µɱ)        FEE (µɱ per-kB) SIZE    in/out
	1 minute ago    1a053e4058839b21c4  9                6.4            1.5 kB  1/2
	1 minute ago    fb61680a1584ca1ec3  9                6.4            1.5 kB  1/2
	1 minute ago    a58de0d2747cdd6a5d  12               6.4            2.0 kB  2/2
	1 minute ago    c54f4b33ed81335f78  308             160.6           2.0 kB  2/2
	57 seconds ago  ab210c55bd9c3efe09  12               6.4            2.0 kB  2/2
	55 seconds ago  88df3311e19b1280b5  9                6.4            1.5 kB  1/2
	51 seconds ago  a3aa674ed3d4eb56c6  127             32.5            4.0 kB  6/2
	51 seconds ago  e896abe686d2f816fc  9                6.4            1.5 kB  1/2
	31 seconds ago  eb6ea6662ce754d025  9                6.4            1.5 kB  1/2
	31 seconds ago  592204223e607cc5d9  9                6.4            1.5 kB  1/2
	24 seconds ago  ba8d52f90fd45dd32d  12               6.4            2.0 kB  2/2
	24 seconds ago  792e68ded7805037c9  12               6.4            2.0 kB  2/2

- move response types to a single file
- rework pretty-printing of txn-based cmds

Signed-off-by: Ciro S. Costa <utxobr@protonmail.com>
This commit is contained in:
Ciro S. Costa 2021-07-15 12:22:43 -04:00
parent 425893dd54
commit 9cf4665b88
19 changed files with 455 additions and 310 deletions

View file

@ -39,7 +39,7 @@ func (c *getBansCommand) RunE(_ *cobra.Command, _ []string) error {
resp, err := client.GetBans(ctx)
if err != nil {
return fmt.Errorf("get block count: %w", err)
return fmt.Errorf("get bans count: %w", err)
}
if c.JSON {

View file

@ -1,12 +1,17 @@
package daemon
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/dustin/go-humanize"
"github.com/spf13/cobra"
"github.com/cirocosta/go-monero/cmd/monero/display"
"github.com/cirocosta/go-monero/cmd/monero/options"
"github.com/cirocosta/go-monero/pkg/constant"
"github.com/cirocosta/go-monero/pkg/rpc/daemon"
)
@ -14,6 +19,9 @@ type getBlockCommand struct {
Height uint64
Hash string
BlockJSON bool
JSON bool
client *daemon.Client
}
func (c *getBlockCommand) Cmd() *cobra.Command {
@ -29,6 +37,8 @@ func (c *getBlockCommand) Cmd() *cobra.Command {
cmd.Flags().StringVar(&c.Hash, "hash",
"", "block hash to retrieve the information of")
cmd.Flags().BoolVar(&c.JSON, "json",
false, "whether or not to output the result as json")
cmd.Flags().BoolVar(&c.BlockJSON, "block-json",
false, "display just the block json (from the `json` field)")
@ -44,6 +54,10 @@ func (c *getBlockCommand) RunE(_ *cobra.Command, _ []string) error {
return fmt.Errorf("client: %w", err)
}
if c.Hash == "" && c.Height == 0 {
return fmt.Errorf("hash or height must be set")
}
resp, err := client.GetBlock(ctx, daemon.GetBlockRequestParameters{
Hash: c.Hash,
Height: c.Height,
@ -52,16 +66,68 @@ func (c *getBlockCommand) RunE(_ *cobra.Command, _ []string) error {
return fmt.Errorf("get block: %w", err)
}
if !c.BlockJSON {
return display.JSON(resp)
if c.JSON {
if !c.BlockJSON {
return display.JSON(resp)
}
inner, err := resp.InnerJSON()
if err != nil {
return fmt.Errorf("inner json: %w", err)
}
return display.JSON(inner)
}
inner, err := resp.InnerJSON()
c.client = client
return c.pretty(ctx, resp)
}
func (c *getBlockCommand) pretty(ctx context.Context, v *daemon.GetBlockResult) error {
table := display.NewTable()
blockDetails, err := v.InnerJSON()
if err != nil {
return fmt.Errorf("inner json: %w", err)
}
return display.JSON(inner)
table.AddRow("Hash:", v.BlockHeader.Hash)
table.AddRow("Height:", v.BlockHeader.Height)
table.AddRow("Age:", humanize.Time(time.Unix(v.BlockHeader.Timestamp, 0)))
table.AddRow("Timestamp:", time.Unix(v.BlockHeader.Timestamp, 0))
table.AddRow("Size:", humanize.IBytes(v.BlockHeader.BlockSize))
table.AddRow("Reward:", fmt.Sprintf("%f XMR", float64(v.BlockHeader.Reward)/constant.XMR))
table.AddRow("Total Fees:")
table.AddRow("Version:", fmt.Sprintf("%d.%d", blockDetails.MajorVersion, blockDetails.MinorVersion))
table.AddRow("Previous Block:", blockDetails.PrevID)
table.AddRow("Nonce:", blockDetails.Nonce)
table.AddRow("Miner TXN Hash:", v.MinerTxHash)
fmt.Println(table)
fmt.Println("")
txnsResult, err := c.client.GetTransactions(ctx, blockDetails.TxHashes)
if err != nil {
return fmt.Errorf("get txns: %w", err)
}
table = display.NewTable()
table.AddRow("HASH", "FEE (µɱ)", "in/out", "SIZE")
for _, txn := range txnsResult.Txs {
txnDetails := &daemon.TransactionJSON{}
if err := json.Unmarshal([]byte(txn.AsJSON), txnDetails); err != nil {
return fmt.Errorf("unsmarshal txjson: %w", err)
}
table.AddRow(
txn.TxHash,
txnDetails.RctSignatures.Txnfee/constant.MicroXMR,
fmt.Sprintf("%d/%d", len(txnDetails.Vin), len(txnDetails.Vout)),
humanize.IBytes(uint64(len(txn.AsHex))/2),
)
}
fmt.Println(table)
return nil
}
func init() {

View file

@ -2,8 +2,10 @@ package daemon
import (
"fmt"
"sort"
"time"
"github.com/dustin/go-humanize"
"github.com/spf13/cobra"
"github.com/cirocosta/go-monero/cmd/monero/display"
@ -53,16 +55,20 @@ func (c *getConnectionsCommand) RunE(_ *cobra.Command, _ []string) error {
func (c *getConnectionsCommand) pretty(v *daemon.GetConnectionsResult) {
table := display.NewTable()
table.AddRow("ADDR", "IN", "STATE", "TIME", "RECV (kB)", "SEND (kB)")
table.AddRow("ADDR", "IN", "STATE", "HEIGHT", "SINCE", "RECV", "SEND")
sort.Slice(v.Connections, func(i, j int) bool {
return v.Connections[i].LiveTime > v.Connections[j].LiveTime
})
for _, connection := range v.Connections {
table.AddRow(
connection.Address,
connection.Incoming,
connection.State,
time.Duration(connection.LiveTime)*time.Second,
connection.RecvCount/1024,
connection.SendCount/1024,
connection.Height,
humanize.Time(time.Now().Add(-1*time.Duration(connection.LiveTime)*time.Second)),
humanize.IBytes(connection.RecvCount),
humanize.IBytes(connection.SendCount),
)
}

View file

@ -17,8 +17,10 @@ type getHeightCommand struct {
func (c *getHeightCommand) Cmd() *cobra.Command {
cmd := &cobra.Command{
Use: "get-height",
Short: "node's current height",
RunE: c.RunE,
Short: "node's current chain height",
Long: `Retrieves the current chain height (most recent block height + 1)
including the hash of the most recent block.`,
RunE: c.RunE,
}
cmd.Flags().BoolVar(&c.JSON, "json",

View file

@ -4,6 +4,7 @@ import (
"fmt"
"time"
"github.com/dustin/go-humanize"
"github.com/spf13/cobra"
"github.com/cirocosta/go-monero/cmd/monero/display"
@ -54,8 +55,8 @@ func (c *getNetStatsCommand) pretty(v *daemon.GetNetStatsResult) {
table := display.NewTable()
table.AddRow("Start Time", time.Unix(v.StartTime, 0))
table.AddRow("Total In", display.ByteSize(v.TotalBytesIn))
table.AddRow("Total Out", display.ByteSize(v.TotalBytesOut))
table.AddRow("Total In", humanize.IBytes(v.TotalBytesIn))
table.AddRow("Total Out", humanize.IBytes(v.TotalBytesOut))
table.AddRow("Total Packets In", v.TotalPacketsIn)
table.AddRow("Total Packets Out", v.TotalPacketsOut)

View file

@ -5,6 +5,7 @@ import (
"sort"
"time"
"github.com/dustin/go-humanize"
"github.com/spf13/cobra"
"github.com/cirocosta/go-monero/cmd/monero/display"
@ -54,7 +55,7 @@ func (c *getPeerListCommand) RunE(_ *cobra.Command, _ []string) error {
func (c *getPeerListCommand) pretty(v *daemon.GetPeerListResult) {
table := display.NewTable()
table.AddRow("TYPE", "HOST", "PORT ", "RPC", "LAST SEEN")
table.AddRow("TYPE", "HOST", "PORT ", "RPC", "SINCE")
for _, peer := range v.GrayList {
table.AddRow("GRAY", peer.Host, peer.Port, peer.RPCPort, "")
@ -64,7 +65,7 @@ func (c *getPeerListCommand) pretty(v *daemon.GetPeerListResult) {
return v.WhiteList[i].LastSeen < v.WhiteList[j].LastSeen
})
for _, peer := range v.WhiteList {
table.AddRow("WHITE", peer.Host, peer.Port, peer.RPCPort, display.Since(time.Unix(peer.LastSeen, 0)))
table.AddRow("WHITE", peer.Host, peer.Port, peer.RPCPort, humanize.Time(time.Unix(peer.LastSeen, 0)))
}
fmt.Println(table)

View file

@ -8,6 +8,7 @@ import (
"github.com/cirocosta/go-monero/cmd/monero/display"
"github.com/cirocosta/go-monero/cmd/monero/options"
"github.com/cirocosta/go-monero/pkg/rpc/daemon"
"github.com/dustin/go-humanize"
"github.com/spf13/cobra"
)
@ -70,7 +71,7 @@ func (c *getPublicNodesCommand) RunE(_ *cobra.Command, _ []string) error {
func (c *getPublicNodesCommand) pretty(v *daemon.GetPublicNodesResult) {
table := display.NewTable()
table.AddRow("TYPE", "HOST", "RPC PORT", "LAST SEEN")
table.AddRow("TYPE", "HOST", "RPC PORT", "SINCE")
for _, peer := range v.GrayList {
table.AddRow("GRAY", peer.Host, peer.RPCPort, "")
}
@ -79,7 +80,7 @@ func (c *getPublicNodesCommand) pretty(v *daemon.GetPublicNodesResult) {
return v.WhiteList[i].LastSeen < v.WhiteList[j].LastSeen
})
for _, peer := range v.WhiteList {
table.AddRow("WHITE", peer.Host, peer.RPCPort, display.Since(time.Unix(peer.LastSeen, 0)))
table.AddRow("WHITE", peer.Host, peer.RPCPort, humanize.Time(time.Unix(peer.LastSeen, 0)))
}
fmt.Println(table)

View file

@ -1,15 +1,23 @@
package daemon
import (
"encoding/json"
"fmt"
"sort"
"time"
"github.com/dustin/go-humanize"
"github.com/spf13/cobra"
"github.com/cirocosta/go-monero/cmd/monero/display"
"github.com/cirocosta/go-monero/cmd/monero/options"
"github.com/cirocosta/go-monero/pkg/constant"
"github.com/cirocosta/go-monero/pkg/rpc/daemon"
)
type getTransactionPoolCommand struct{}
type getTransactionPoolCommand struct {
JSON bool
}
func (c *getTransactionPoolCommand) Cmd() *cobra.Command {
cmd := &cobra.Command{
@ -18,6 +26,9 @@ func (c *getTransactionPoolCommand) Cmd() *cobra.Command {
RunE: c.RunE,
}
cmd.Flags().BoolVar(&c.JSON, "json",
false, "whether or not to output the result as json")
return cmd
}
@ -35,7 +46,39 @@ func (c *getTransactionPoolCommand) RunE(_ *cobra.Command, _ []string) error {
return fmt.Errorf("get block count: %w", err)
}
return display.JSON(resp)
if c.JSON {
return display.JSON(resp)
}
return c.pretty(resp)
}
func (c *getTransactionPoolCommand) pretty(v *daemon.GetTransactionPoolResult) error {
table := display.NewTable()
table.AddRow("AGE", "HASH", "FEE (µɱ)", "FEE (µɱ per-kB)", "SIZE", "in/out")
sort.Slice(v.Transactions, func(i, j int) bool {
return v.Transactions[i].ReceiveTime < v.Transactions[j].ReceiveTime
})
for _, txn := range v.Transactions {
txnDetails := &daemon.TransactionJSON{}
if err := json.Unmarshal([]byte(txn.TxJSON), txnDetails); err != nil {
return fmt.Errorf("unsmarshal txjson: %w", err)
}
table.AddRow(
humanize.Time(time.Unix(txn.ReceiveTime, 0)),
txn.IDHash,
txn.Fee/constant.MicroXMR,
fmt.Sprintf("%4.1f", (float64(txn.Fee)/constant.MicroXMR)/(float64(txn.BlobSize)/1024)),
humanize.IBytes(txn.BlobSize),
fmt.Sprintf("%d/%d", len(txnDetails.Vin), len(txnDetails.Vout)),
)
}
fmt.Println(table)
return nil
}
func init() {

View file

@ -4,6 +4,7 @@ import (
"fmt"
"time"
"github.com/dustin/go-humanize"
"github.com/spf13/cobra"
"github.com/cirocosta/go-monero/cmd/monero/display"
@ -67,7 +68,7 @@ func (c *getTransactionPoolStatsCommand) pretty(v *daemon.GetTransactionPoolStat
table.AddRow("Double Spends:", v.PoolStats.NumDoubleSpends)
table.AddRow("Failing Transactions:", v.PoolStats.NumFailing)
table.AddRow("Not Relayed:", v.PoolStats.NumNotRelayed)
table.AddRow("Oldest:", display.Since(time.Unix(v.PoolStats.Oldest, 0)))
table.AddRow("Oldest:", humanize.Time(time.Unix(v.PoolStats.Oldest, 0)))
table.AddRow("Txns Total:", v.PoolStats.TxsTotal)
table.AddRow("")

View file

@ -1,37 +1,48 @@
package daemon
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/dustin/go-humanize"
"github.com/spf13/cobra"
"github.com/cirocosta/go-monero/cmd/monero/display"
"github.com/cirocosta/go-monero/cmd/monero/options"
"github.com/cirocosta/go-monero/pkg/constant"
"github.com/cirocosta/go-monero/pkg/rpc/daemon"
)
type getTransactionsCommand struct {
Txns []string
type getTransactionCommand struct {
Txn string
Unwrap bool
JSON bool
client *daemon.Client
}
func (c *getTransactionsCommand) Cmd() *cobra.Command {
func (c *getTransactionCommand) Cmd() *cobra.Command {
cmd := &cobra.Command{
Use: "get-transactions",
Short: "lookup one or more transactions by hash",
Use: "get-transaction",
Short: "lookup a transaction, in the pool or not",
RunE: c.RunE,
}
cmd.Flags().StringArrayVar(&c.Txns, "txn",
[]string{}, "hash of a transaction to lookup")
cmd.Flags().StringVar(&c.Txn, "txn",
"", "hash of a transaction to lookup")
cmd.MarkFlagRequired("txn")
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 *getTransactionsCommand) RunE(_ *cobra.Command, _ []string) error {
func (c *getTransactionCommand) RunE(_ *cobra.Command, _ []string) error {
ctx, cancel := options.RootOptions.Context()
defer cancel()
@ -40,23 +51,92 @@ func (c *getTransactionsCommand) RunE(_ *cobra.Command, _ []string) error {
return fmt.Errorf("client: %w", err)
}
resp, err := client.GetTransactions(ctx, c.Txns)
resp, err := client.GetTransactions(ctx, []string{c.Txn})
if err != nil {
return fmt.Errorf("get transactions: %w", err)
}
if !c.Unwrap {
return display.JSON(resp)
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)
}
txns, err := resp.GetTransactions()
if err != nil {
return fmt.Errorf("resp get txns: %w", err)
c.client = client
return c.pretty(ctx, resp)
}
func (c *getTransactionCommand) pretty(ctx context.Context, v *daemon.GetTransactionsResult) error {
table := display.NewTable()
txn := v.Txs[0]
txnDetails := &daemon.TransactionJSON{}
if err := json.Unmarshal([]byte(txn.AsJSON), txnDetails); err != nil {
return fmt.Errorf("unsmarshal txjson: %w", err)
}
return display.JSON(txns)
confirmations := uint64(0)
if txn.InPool == false {
}
table.AddRow("Hash:", txn.TxHash)
table.AddRow("Fee (µɱ):", txnDetails.RctSignatures.Txnfee/constant.MicroXMR)
table.AddRow("In/Out:", fmt.Sprintf("%d/%d", len(txnDetails.Vin), len(txnDetails.Vout)))
table.AddRow("Size:", humanize.IBytes(uint64(len(txn.AsHex))/2))
if txn.InPool == false {
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)
}
confirmations = heightResp.Height - txn.BlockHeight
table.AddRow("Confirmations:", confirmations)
} else {
table.AddRow("Confirmations:", 0)
}
fmt.Println(table)
fmt.Println("")
table = display.NewTable()
table.AddRow("INPUTS")
table.AddRow("KEY IMAGE", "AMOUNT", "KEY OFFSETS")
for _, vin := range txnDetails.Vin {
table.AddRow(
vin.Key.KImage,
vin.Key.Amount,
vin.Key.KeyOffsets,
)
}
fmt.Println(table)
fmt.Println("")
table = display.NewTable()
table.AddRow("OUTPUTS")
table.AddRow("STEALTH ADDR", "AMOUNT", "AMOUNT IDX")
for idx, vout := range txnDetails.Vout {
table.AddRow(
vout.Target.Key,
vout.Amount,
txn.OutputIndices[idx],
)
}
fmt.Println(table)
return nil
}
func init() {
RootCommand.AddCommand((&getTransactionsCommand{}).Cmd())
RootCommand.AddCommand((&getTransactionCommand{}).Cmd())
}

View file

@ -0,0 +1,64 @@
package daemon
import (
"fmt"
"github.com/spf13/cobra"
"github.com/cirocosta/go-monero/cmd/monero/display"
"github.com/cirocosta/go-monero/cmd/monero/options"
"github.com/cirocosta/go-monero/pkg/rpc/daemon"
)
type getVersionCommand struct {
JSON bool
}
func (c *getVersionCommand) Cmd() *cobra.Command {
cmd := &cobra.Command{
Use: "get-version",
Short: "version of the monero daemon",
RunE: c.RunE,
}
cmd.Flags().BoolVar(&c.JSON, "json",
false, "whether or not to output the result as json")
return cmd
}
func (c *getVersionCommand) RunE(_ *cobra.Command, _ []string) error {
ctx, cancel := options.RootOptions.Context()
defer cancel()
client, err := options.RootOptions.Client()
if err != nil {
return fmt.Errorf("client: %w", err)
}
resp, err := client.GetVersion(ctx)
if err != nil {
return fmt.Errorf("get block count: %w", err)
}
if c.JSON {
return display.JSON(resp)
}
c.pretty(resp)
return nil
}
func (c *getVersionCommand) pretty(v *daemon.GetVersionResult) {
table := display.NewTable()
table.AddRow("Release:", v.Release)
table.AddRow("Major:", v.Version>>16)
table.AddRow("Minor:", v.Version&((1<<16)-1))
fmt.Println(table)
}
func init() {
RootCommand.AddCommand((&getVersionCommand{}).Cmd())
}

View file

@ -1,59 +0,0 @@
package display
import (
"strconv"
"strings"
)
const (
Byte = 1 << (10 * iota)
Kilobyte
Megabyte
Gigabyte
Terabyte
Petabyte
Exabyte
)
// ByteSize returns a human-readable byte string of the form 10M, 12.5K, and so forth. The following units are available:
// E: Exabyte
// P: Petabyte
// T: Terabyte
// G: Gigabyte
// M: Megabyte
// K: Kilobyte
// B: Byte
// The unit that results in the smallest number greater than or equal to 1 is always chosen.
//
func ByteSize(bytes uint64) string {
unit := ""
value := float64(bytes)
switch {
case bytes >= Exabyte:
unit = "E"
value = value / Exabyte
case bytes >= Petabyte:
unit = "P"
value = value / Petabyte
case bytes >= Terabyte:
unit = "T"
value = value / Terabyte
case bytes >= Gigabyte:
unit = "G"
value = value / Gigabyte
case bytes >= Megabyte:
unit = "M"
value = value / Megabyte
case bytes >= Kilobyte:
unit = "K"
value = value / Kilobyte
case bytes >= Byte:
unit = "B"
case bytes == 0:
return "0B"
}
result := strconv.FormatFloat(value, 'f', 1, 64)
return strings.TrimSuffix(result, ".0") + unit
}

View file

@ -1,99 +0,0 @@
package display
import (
"fmt"
"strconv"
"time"
)
const (
// HoursInMonth represents the approximate number of hours in a month.
//
// Note that hours per month is approximate due to fluctuations in
// month length.
//
HoursInMonth = 730
// HoursInDay represents the number of hours in a day.
//
HoursInDay = 24
// HoursInWeek represents the number of hours in a week.
//
HoursInWeek = HoursInDay * 7
// HoursInYear represents the number of hours in a year.
//
HoursInYear = HoursInDay * 365
)
// Duration returns a string with a plain English description of the length of
// time that the time.Duration t contains.
//
func Duration(t time.Duration) string {
hours := int(t.Hours())
minutes := int(t.Minutes())
seconds := int(t.Seconds())
if hours >= HoursInYear {
years := hours / HoursInYear
if years == 1 {
return "a year ago"
}
return fmt.Sprintf("%d years ago", years)
}
if hours >= HoursInMonth {
months := hours / HoursInMonth
if months == 1 {
return "about a month ago"
}
return fmt.Sprintf("about %d months ago", months)
}
if hours >= HoursInWeek {
weeks := hours / HoursInWeek
if weeks == 1 {
return "a week ago"
}
return fmt.Sprintf("%d weeks ago", weeks)
}
if hours >= HoursInDay {
days := hours / HoursInDay
if days == 1 {
return "a day ago"
}
return fmt.Sprintf("%d days ago", days)
}
if hours > 0 {
if hours == 1 {
return "an hour ago"
}
return fmt.Sprintf("%d hours ago", hours)
}
if minutes > 0 {
if minutes == 1 {
return "a minute ago"
}
return fmt.Sprintf("%d minutes ago", minutes)
}
if seconds > 2 {
return strconv.Itoa(seconds) + " seconds ago"
}
return "just now"
}
// Since provides an easy way to call Duration without having to call
// time.Since on a time.Time first.
func Since(t time.Time) string {
return Duration(time.Since(t))
}

1
go.mod
View file

@ -3,6 +3,7 @@ module github.com/cirocosta/go-monero
go 1.16
require (
github.com/dustin/go-humanize v1.0.0
github.com/golangci/golangci-lint v1.41.1
github.com/gosuri/uitable v0.0.4
github.com/mattn/go-isatty v0.0.13 // indirect

1
go.sum
View file

@ -136,6 +136,7 @@ github.com/denis-tingajkin/go-header v0.4.2/go.mod h1:eLRHAVXzE5atsKAnNRDB90WHCF
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=

View file

@ -7,5 +7,7 @@ const (
// A monero corresponds to 1e12 atomic units.
//
XMR = 1_000_000_000_000 * AtomicUnit
XMR = 1_000_000_000_000 * AtomicUnit
MilliXMR = 1_000_000_000 * AtomicUnit
MicroXMR = 1_000_000 * AtomicUnit
)

View file

@ -16,35 +16,6 @@ const (
endpointGetTransactions = "/get_transactions"
)
type GetTransactionPoolResult struct {
Credits int `json:"credits"`
SpentKeyImages []struct {
IDHash string `json:"id_hash"`
TxsHashes []string `json:"txs_hashes"`
} `json:"spent_key_images"`
Status string `json:"status"`
TopHash string `json:"top_hash"`
Transactions []struct {
BlobSize int `json:"blob_size"`
DoNotRelay bool `json:"do_not_relay"`
DoubleSpendSeen bool `json:"double_spend_seen"`
Fee int `json:"fee"`
IDHash string `json:"id_hash"`
KeptByBlock bool `json:"kept_by_block"`
LastFailedHeight int `json:"last_failed_height"`
LastFailedIDHash string `json:"last_failed_id_hash"`
LastRelayedTime int `json:"last_relayed_time"`
MaxUsedBlockHeight int `json:"max_used_block_height"`
MaxUsedBlockIDHash string `json:"max_used_block_id_hash"`
ReceiveTime int `json:"receive_time"`
Relayed bool `json:"relayed"`
TxBlob string `json:"tx_blob"`
TxJSON string `json:"tx_json"`
Weight int `json:"weight"`
} `json:"transactions"`
Untrusted bool `json:"untrusted"`
}
func (c *Client) GetTransactionPool(ctx context.Context) (*GetTransactionPoolResult, error) {
resp := &GetTransactionPoolResult{}
@ -111,36 +82,15 @@ func (c *Client) GetNetStats(ctx context.Context) (*GetNetStatsResult, error) {
return resp, nil
}
type GetTransactionsResult struct {
Credits int `json:"credits"`
Status string `json:"status"`
TopHash string `json:"top_hash"`
Txs []struct {
AsHex string `json:"as_hex"`
AsJSON string `json:"as_json"`
BlockHeight int `json:"block_height"`
BlockTimestamp int `json:"block_timestamp"`
DoubleSpendSeen bool `json:"double_spend_seen"`
InPool bool `json:"in_pool"`
OutputIndices []int `json:"output_indices"`
PrunableAsHex string `json:"prunable_as_hex"`
PrunableHash string `json:"prunable_hash"`
PrunedAsHex string `json:"pruned_as_hex"`
TxHash string `json:"tx_hash"`
} `json:"txs"`
TxsAsHex []string `json:"txs_as_hex"`
Untrusted bool `json:"untrusted"`
}
func (r *GetTransactionsResult) GetTransactions() ([]*GetTransactionsResultJSONTxn, error) {
txns := make([]*GetTransactionsResultJSONTxn, len(r.Txs))
func (r *GetTransactionsResult) GetTransactions() ([]*TransactionJSON, error) {
txns := make([]*TransactionJSON, len(r.Txs))
for idx, txn := range r.Txs {
if len(txn.AsJSON) == 0 {
return nil, fmt.Errorf("txn '%s' w/ empty `.as_json`", txn.TxHash)
}
t := &GetTransactionsResultJSONTxn{}
t := &TransactionJSON{}
if err := json.Unmarshal([]byte(txn.AsJSON), t); err != nil {
return nil, fmt.Errorf("unmarshal txn '%s': %w", txn.TxHash, err)
}
@ -151,55 +101,6 @@ func (r *GetTransactionsResult) GetTransactions() ([]*GetTransactionsResultJSONT
return txns, nil
}
type GetTransactionsResultJSONTxn struct {
Version int `json:"version"`
UnlockTime int `json:"unlock_time"`
Vin []struct {
Key struct {
Amount int `json:"amount"`
KeyOffsets []int `json:"key_offsets"`
KImage string `json:"k_image"`
} `json:"key"`
} `json:"vin"`
Vout []struct {
Amount int `json:"amount"`
Target struct {
Key string `json:"key"`
} `json:"target"`
} `json:"vout"`
Extra []int `json:"extra"`
RctSignatures struct {
Type int `json:"type"`
Txnfee int `json:"txnFee"`
Ecdhinfo []struct {
Amount string `json:"amount"`
} `json:"ecdhInfo"`
Outpk []string `json:"outPk"`
} `json:"rct_signatures"`
RctsigPrunable struct {
Nbp int `json:"nbp"`
Bp []struct {
A string `json:"A"`
S string `json:"S"`
T1 string `json:"T1"`
T2 string `json:"T2"`
Taux string `json:"taux"`
Mu string `json:"mu"`
L []string `json:"L"`
R []string `json:"R"`
LowerA string `json:"a"`
B string `json:"b"`
T string `json:"t"`
} `json:"bp"`
Clsags []struct {
S []string `json:"s"`
C1 string `json:"c1"`
D string `json:"D"`
} `json:"CLSAGs"`
Pseudoouts []string `json:"pseudoOuts"`
} `json:"rctsig_prunable"`
}
func (c *Client) GetTransactions(ctx context.Context, txns []string) (*GetTransactionsResult, error) {
resp := &GetTransactionsResult{}

View file

@ -9,6 +9,7 @@ import (
const (
methodGetAlternateChains = "get_alternate_chains"
methodGetBans = "get_bans"
methodGetVersion = "get_version"
methodGetBlock = "get_block"
methodGetBlockCount = "get_block_count"
methodGetBlockHeaderByHash = "get_block_header_by_hash"
@ -81,6 +82,20 @@ func (c *Client) GetBans(ctx context.Context) (*GetBansResult, error) {
return resp, nil
}
// GetVersion retrieves the version of monerod that the node uses.
//
// (restricted)
//
func (c *Client) GetVersion(ctx context.Context) (*GetVersionResult, error) {
resp := &GetVersionResult{}
if err := c.JSONRPC(ctx, methodGetVersion, nil, resp); err != nil {
return nil, fmt.Errorf("jsonrpc: %w", err)
}
return resp, nil
}
// GenerateBlocksRequestParameters is the set of parameters to be passed to the
// GenerateBlocks RPC method.
//

View file

@ -137,6 +137,15 @@ type HardForkInfoResult struct {
RPCResultFooter `json:",inline"`
}
// GetVersionResult is the result of a call to the GetVersion RPC method.
//
type GetVersionResult struct {
Release bool `json:"release"`
Version uint64 `json:"version"`
RPCResultFooter `json:",inline"`
}
// GetBansResult is the result of a call to the GetBans RPC method.
//
type GetBansResult struct {
@ -268,26 +277,26 @@ type GetPeerListResult struct {
type GetConnectionsResult struct {
Connections []struct {
Address string `json:"address"`
AvgDownload int `json:"avg_download"`
AvgUpload int `json:"avg_upload"`
AvgDownload uint64 `json:"avg_download"`
AvgUpload uint64 `json:"avg_upload"`
ConnectionID string `json:"connection_id"`
CurrentDownload int `json:"current_download"`
CurrentUpload int `json:"current_upload"`
Height int `json:"height"`
CurrentDownload uint64 `json:"current_download"`
CurrentUpload uint64 `json:"current_upload"`
Height uint64 `json:"height"`
Host string `json:"host"`
Incoming bool `json:"incoming"`
IP string `json:"ip"`
LiveTime int64 `json:"live_time"`
LiveTime uint64 `json:"live_time"`
LocalIP bool `json:"local_ip"`
Localhost bool `json:"localhost"`
PeerID string `json:"peer_id"`
Port string `json:"port"`
RecvCount int `json:"recv_count"`
RecvIdleTime int `json:"recv_idle_time"`
SendCount int `json:"send_count"`
SendIdleTime int `json:"send_idle_time"`
RecvCount uint64 `json:"recv_count"`
RecvIdleTime uint64 `json:"recv_idle_time"`
SendCount uint64 `json:"send_count"`
SendIdleTime uint64 `json:"send_idle_time"`
State string `json:"state"`
SupportFlags int `json:"support_flags"`
SupportFlags uint64 `json:"support_flags"`
} `json:"connections"`
RPCResultFooter `json:",inline"`
@ -451,7 +460,7 @@ type BlockHeader struct {
// Timestamp is the unix timestamp at which the block was
// recorded into the blockchain.
//
Timestamp uint64 `json:"timestamp"`
Timestamp int64 `json:"timestamp"`
// WideCumulativeDifficulty is the cumulative difficulty of all
// blocks in the blockchain as a hexadecimal string
@ -532,7 +541,7 @@ type GetBlockResultJSON struct {
// Vout lists the transaction outputs.
//
Vout []struct {
Amount int64 `json:"amount"`
Amount uint64 `json:"amount"`
Target struct {
Key string `json:"key"`
} `json:"target"`
@ -557,6 +566,16 @@ type GetBlockResultJSON struct {
TxHashes []string `json:"tx_hashes"`
}
func (c *GetBlockResultJSON) MinerOutputs() uint64 {
res := uint64(0)
for _, vout := range c.MinerTx.Vout {
res += vout.Amount
}
return res
}
// SyncInfoResult is the result of a call to the SyncInfo RPC method.
//
type SyncInfoResult struct {
@ -643,3 +662,102 @@ type GetTransactionPoolStatsResult struct {
RPCResultFooter `json:",inline"`
}
type GetTransactionsResult struct {
Credits int `json:"credits"`
Status string `json:"status"`
TopHash string `json:"top_hash"`
Txs []struct {
AsHex string `json:"as_hex"`
AsJSON string `json:"as_json"`
BlockHeight uint64 `json:"block_height"`
BlockTimestamp int64 `json:"block_timestamp"`
DoubleSpendSeen bool `json:"double_spend_seen"`
InPool bool `json:"in_pool"`
OutputIndices []int `json:"output_indices"`
PrunableAsHex string `json:"prunable_as_hex"`
PrunableHash string `json:"prunable_hash"`
PrunedAsHex string `json:"pruned_as_hex"`
TxHash string `json:"tx_hash"`
} `json:"txs"`
TxsAsHex []string `json:"txs_as_hex"`
Untrusted bool `json:"untrusted"`
}
type TransactionJSON struct {
Version int `json:"version"`
UnlockTime int `json:"unlock_time"`
Vin []struct {
Key struct {
Amount int `json:"amount"`
KeyOffsets []int `json:"key_offsets"`
KImage string `json:"k_image"`
} `json:"key"`
} `json:"vin"`
Vout []struct {
Amount int `json:"amount"`
Target struct {
Key string `json:"key"`
} `json:"target"`
} `json:"vout"`
Extra []int `json:"extra"`
RctSignatures struct {
Type int `json:"type"`
Txnfee int `json:"txnFee"`
Ecdhinfo []struct {
Amount string `json:"amount"`
} `json:"ecdhInfo"`
Outpk []string `json:"outPk"`
} `json:"rct_signatures"`
RctsigPrunable struct {
Nbp int `json:"nbp"`
Bp []struct {
A string `json:"A"`
S string `json:"S"`
T1 string `json:"T1"`
T2 string `json:"T2"`
Taux string `json:"taux"`
Mu string `json:"mu"`
L []string `json:"L"`
R []string `json:"R"`
LowerA string `json:"a"`
B string `json:"b"`
T string `json:"t"`
} `json:"bp"`
Clsags []struct {
S []string `json:"s"`
C1 string `json:"c1"`
D string `json:"D"`
} `json:"CLSAGs"`
Pseudoouts []string `json:"pseudoOuts"`
} `json:"rctsig_prunable"`
}
type GetTransactionPoolResult struct {
Credits int `json:"credits"`
SpentKeyImages []struct {
IDHash string `json:"id_hash"`
TxsHashes []string `json:"txs_hashes"`
} `json:"spent_key_images"`
Status string `json:"status"`
TopHash string `json:"top_hash"`
Transactions []struct {
BlobSize uint64 `json:"blob_size"`
DoNotRelay bool `json:"do_not_relay"`
DoubleSpendSeen bool `json:"double_spend_seen"`
Fee uint64 `json:"fee"`
IDHash string `json:"id_hash"`
KeptByBlock bool `json:"kept_by_block"`
LastFailedHeight uint64 `json:"last_failed_height"`
LastFailedIDHash string `json:"last_failed_id_hash"`
LastRelayedTime uint64 `json:"last_relayed_time"`
MaxUsedBlockHeight uint64 `json:"max_used_block_height"`
MaxUsedBlockIDHash string `json:"max_used_block_id_hash"`
ReceiveTime int64 `json:"receive_time"`
Relayed bool `json:"relayed"`
TxBlob string `json:"tx_blob"`
TxJSON string `json:"tx_json"`
Weight uint64 `json:"weight"`
} `json:"transactions"`
Untrusted bool `json:"untrusted"`
}