2020-05-20 15:18:46 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2020-10-15 11:29:28 +02:00
|
|
|
"encoding/json"
|
2020-05-20 15:18:46 +02:00
|
|
|
"fmt"
|
|
|
|
|
"os"
|
|
|
|
|
"path"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
|
2020-09-08 19:35:08 +02:00
|
|
|
"github.com/lightninglabs/faraday/frdrpc"
|
2022-11-21 17:12:17 +01:00
|
|
|
"github.com/urfave/cli"
|
2020-05-20 15:18:46 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var onChainReportCommand = cli.Command{
|
2020-09-09 10:41:37 +02:00
|
|
|
Name: "audit",
|
2020-05-20 15:18:46 +02:00
|
|
|
Category: "reporting",
|
|
|
|
|
Usage: "Get a report of node activity.",
|
2020-10-15 11:29:28 +02:00
|
|
|
Description: `
|
|
|
|
|
Create a report containing all of your node's activity over
|
|
|
|
|
the period specified. Transactions are sorted into a set of
|
|
|
|
|
lightning-specific entry types. Fiat values can optionally be
|
|
|
|
|
included using the --enable_fiat flag. To write the report
|
|
|
|
|
directly to a csv, set a target directory using the --csv_path
|
|
|
|
|
flag.
|
|
|
|
|
|
|
|
|
|
These reports can optionally be created with custom categories.
|
|
|
|
|
This requires providing a name for the category, and a set of
|
|
|
|
|
regular expressions which identify the labels of transactions
|
|
|
|
|
belonging in the category. To directly string match, just provide
|
|
|
|
|
the string itself. These regular expressions are matched against
|
|
|
|
|
the labels for on chain transactions and the invoices on memos
|
|
|
|
|
(at present we cannot match forwarding events and payments).
|
|
|
|
|
|
|
|
|
|
Categories should be expressed as a json array with the
|
|
|
|
|
following format:
|
|
|
|
|
--categories='[
|
|
|
|
|
{
|
|
|
|
|
"name": "test",
|
|
|
|
|
"on_chain": true,
|
|
|
|
|
"label_patterns": ["test[0-9]*", "example(1|2)"]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"name": "category 2",
|
|
|
|
|
...
|
|
|
|
|
},
|
|
|
|
|
]'
|
|
|
|
|
`,
|
2020-05-20 15:18:46 +02:00
|
|
|
Flags: []cli.Flag{
|
|
|
|
|
cli.Int64Flag{
|
|
|
|
|
Name: "start_time",
|
|
|
|
|
Usage: "(optional) The unix timestamp in seconds " +
|
2020-11-20 11:32:19 +00:00
|
|
|
"from which the report should be generated, " +
|
2020-05-20 15:18:46 +02:00
|
|
|
"defaults to one week ago",
|
|
|
|
|
},
|
|
|
|
|
cli.Int64Flag{
|
|
|
|
|
Name: "end_time",
|
2020-11-20 11:32:19 +00:00
|
|
|
Usage: "(optional) The unix timestamp in seconds " +
|
|
|
|
|
"until which the report should be generated. " +
|
2020-05-20 15:18:46 +02:00
|
|
|
"If not set, the report will be produced " +
|
|
|
|
|
"until the present.",
|
|
|
|
|
},
|
|
|
|
|
cli.StringFlag{
|
|
|
|
|
Name: "csv_path",
|
|
|
|
|
Usage: "A path to write node_report.csv to. If not " +
|
|
|
|
|
"set, the command will output the report. " +
|
|
|
|
|
"Note that write permissions are required.",
|
|
|
|
|
},
|
2020-06-26 08:46:46 +02:00
|
|
|
cli.BoolFlag{
|
2020-08-19 09:32:18 +02:00
|
|
|
Name: "enable_fiat",
|
|
|
|
|
Usage: "Create a report with fiat conversions.",
|
2020-06-26 08:46:46 +02:00
|
|
|
},
|
2020-10-15 11:29:28 +02:00
|
|
|
cli.StringFlag{
|
|
|
|
|
Name: "categories",
|
|
|
|
|
Usage: "A set of custom categories to create the " +
|
|
|
|
|
"report with, expressed as a json array.",
|
|
|
|
|
},
|
2020-10-22 13:56:05 +02:00
|
|
|
cli.BoolFlag{
|
|
|
|
|
Name: "loop-category",
|
|
|
|
|
Usage: "Add a custom category called 'loop' " +
|
|
|
|
|
"containing all transactions associated with " +
|
|
|
|
|
"Lightning Labs Loop swaps. Note that this " +
|
|
|
|
|
"category currently does not include off " +
|
|
|
|
|
"chain payments.",
|
|
|
|
|
},
|
2020-12-02 11:36:57 +02:00
|
|
|
cli.BoolFlag{
|
|
|
|
|
Name: "pool-category",
|
|
|
|
|
Usage: "Add a custom category called 'pool' " +
|
|
|
|
|
"containing all transactions associated with " +
|
|
|
|
|
"Lightning Labs Pool trades. Note that this " +
|
|
|
|
|
"category currently does not include off " +
|
|
|
|
|
"chain payments.",
|
|
|
|
|
},
|
2021-08-30 11:22:27 +02:00
|
|
|
fiatBackendFlag,
|
2021-06-09 14:19:59 +02:00
|
|
|
cli.StringFlag{
|
|
|
|
|
Name: "prices_csv_path",
|
|
|
|
|
Usage: "Path to a CSV file containing custom fiat " +
|
|
|
|
|
"price data. This is only required if " +
|
|
|
|
|
"'fiat_backend' is set to 'custom'.",
|
|
|
|
|
},
|
|
|
|
|
cli.StringFlag{
|
|
|
|
|
Name: "custom_price_currency",
|
|
|
|
|
Usage: "The currency that the custom prices are " +
|
|
|
|
|
"quoted in. This is only required if " +
|
|
|
|
|
"'fiat_backend' is set to 'custom'.",
|
2021-04-19 12:53:44 +02:00
|
|
|
},
|
2020-05-20 15:18:46 +02:00
|
|
|
},
|
|
|
|
|
Action: queryOnChainReport,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func queryOnChainReport(ctx *cli.Context) error {
|
|
|
|
|
client, cleanup := getClient(ctx)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
2021-04-19 12:53:44 +02:00
|
|
|
fiatBackend, err := parseFiatBackend(ctx.String("fiat_backend"))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 10:25:03 +02:00
|
|
|
startTime := ctx.Int64("start_time")
|
|
|
|
|
endTime := ctx.Int64("end_time")
|
|
|
|
|
|
|
|
|
|
// nolint: prealloc
|
|
|
|
|
var filteredPrices []*frdrpc.BitcoinPrice
|
2021-06-09 14:19:59 +02:00
|
|
|
|
|
|
|
|
if fiatBackend == frdrpc.FiatBackend_CUSTOM {
|
2021-06-10 10:25:03 +02:00
|
|
|
customPrices, err := parsePricesFromCSV(
|
2021-06-09 14:19:59 +02:00
|
|
|
ctx.String("prices_csv_path"),
|
|
|
|
|
ctx.String("custom_price_currency"),
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2021-06-10 10:25:03 +02:00
|
|
|
|
|
|
|
|
filteredPrices, err = filterPrices(
|
|
|
|
|
customPrices, startTime, endTime,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2021-06-09 14:19:59 +02:00
|
|
|
}
|
|
|
|
|
|
2020-05-20 15:18:46 +02:00
|
|
|
// Set start and end times from user specified values, defaulting
|
|
|
|
|
// to zero if they are not set.
|
2020-09-09 10:41:37 +02:00
|
|
|
req := &frdrpc.NodeAuditRequest{
|
2021-06-10 10:25:03 +02:00
|
|
|
StartTime: uint64(startTime),
|
|
|
|
|
EndTime: uint64(endTime),
|
2021-06-09 14:19:59 +02:00
|
|
|
DisableFiat: !ctx.IsSet("enable_fiat"),
|
|
|
|
|
FiatBackend: fiatBackend,
|
2021-06-10 10:25:03 +02:00
|
|
|
CustomPrices: filteredPrices,
|
2020-05-20 15:18:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If start time is zero, default to a week ago.
|
|
|
|
|
if req.StartTime == 0 {
|
|
|
|
|
weekAgo := time.Now().Add(time.Hour * 24 * 7 * -1)
|
|
|
|
|
req.StartTime = uint64(weekAgo.Unix())
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-15 11:29:28 +02:00
|
|
|
var (
|
|
|
|
|
categoryStr = ctx.String("categories")
|
|
|
|
|
categories []*frdrpc.CustomCategory
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// If we have custom categories set, unmarshal them and add them to
|
|
|
|
|
// our request.
|
|
|
|
|
if categoryStr != "" {
|
|
|
|
|
err := json.Unmarshal([]byte(categoryStr), &categories)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
req.CustomCategories = categories
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-22 13:56:05 +02:00
|
|
|
if ctx.Bool("loop-category") {
|
|
|
|
|
req.CustomCategories = append(
|
|
|
|
|
req.CustomCategories,
|
|
|
|
|
&frdrpc.CustomCategory{
|
|
|
|
|
Name: "loop",
|
|
|
|
|
OnChain: true,
|
|
|
|
|
OffChain: true,
|
|
|
|
|
LabelPatterns: []string{
|
|
|
|
|
"loopd --",
|
|
|
|
|
"swap",
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-02 11:36:57 +02:00
|
|
|
if ctx.Bool("pool-category") {
|
|
|
|
|
req.CustomCategories = append(
|
|
|
|
|
req.CustomCategories, &frdrpc.CustomCategory{
|
|
|
|
|
Name: "pool",
|
|
|
|
|
OnChain: true,
|
|
|
|
|
OffChain: true,
|
|
|
|
|
LabelPatterns: []string{
|
|
|
|
|
"poold --",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-20 15:18:46 +02:00
|
|
|
rpcCtx := context.Background()
|
2020-09-09 10:41:37 +02:00
|
|
|
report, err := client.NodeAudit(rpcCtx, req)
|
2020-05-20 15:18:46 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we did not request a csv, just print the response and return.
|
|
|
|
|
if !ctx.IsSet("csv_path") {
|
|
|
|
|
printRespJSON(report)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
csvPath := ctx.String("csv_path")
|
|
|
|
|
fmt.Printf("Outputting node_report.csv to %v\n", csvPath)
|
|
|
|
|
|
|
|
|
|
file, err := os.Create(path.Join(csvPath, "node_report.csv"))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
|
if err := file.Close(); err != nil {
|
|
|
|
|
fmt.Printf("could not close file: %v\n", err)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
2021-06-09 12:13:03 +02:00
|
|
|
var headers string
|
|
|
|
|
if len(report.Reports) > 0 {
|
|
|
|
|
headers = fmt.Sprintf(
|
|
|
|
|
CSVHeaders,
|
|
|
|
|
report.Reports[0].BtcPrice.Currency,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
csvStrs := []string{headers}
|
2020-05-20 15:18:46 +02:00
|
|
|
for _, report := range report.Reports {
|
2021-06-09 14:12:10 +02:00
|
|
|
csvStrs = append(csvStrs, writeToCSV(report))
|
2020-05-20 15:18:46 +02:00
|
|
|
}
|
|
|
|
|
csvString := strings.Join(csvStrs, "\n")
|
|
|
|
|
|
|
|
|
|
_, err = file.WriteString(csvString)
|
|
|
|
|
return err
|
|
|
|
|
}
|