Skip to content

Commit

Permalink
add clboss-recent-earnings and clboss-earnings-history
Browse files Browse the repository at this point in the history
  • Loading branch information
ksedgwic committed Aug 12, 2024
1 parent 55479bb commit 13a4e8f
Show file tree
Hide file tree
Showing 6 changed files with 704 additions and 1 deletion.
218 changes: 217 additions & 1 deletion Boss/Mod/EarningsTracker.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
#include"Boss/Mod/EarningsTracker.hpp"
#include"Boss/Msg/CommandFail.hpp"
#include"Boss/Msg/CommandRequest.hpp"
#include"Boss/Msg/CommandResponse.hpp"
#include"Boss/Msg/DbResource.hpp"
#include"Boss/Msg/ForwardFee.hpp"
#include"Boss/Msg/ManifestCommand.hpp"
#include"Boss/Msg/Manifestation.hpp"
#include"Boss/Msg/ProvideStatus.hpp"
#include"Boss/Msg/RequestEarningsInfo.hpp"
#include"Boss/Msg/RequestMoveFunds.hpp"
Expand All @@ -15,6 +20,7 @@

#include<cmath>
#include<map>
#include<iostream>

namespace Boss { namespace Mod {

Expand Down Expand Up @@ -54,7 +60,92 @@ class EarningsTracker::Impl {
>([this](Msg::RequestEarningsInfo const& req) {
return Boss::concurrent(request_earnings_info(req));
});

bus.subscribe<Msg::Manifestation
>([this](Msg::Manifestation const& _) {
return bus.raise(Msg::ManifestCommand{
"clboss-recent-earnings", "[days]",
"Show offchain_earnings_tracker data for the most recent {days} "
"(default 14 days).",
false
}) + bus.raise(Msg::ManifestCommand{
"clboss-earnings-history", "[nodeid]",
"Show earnings and expenditure history for {nodeid} "
"(default all nodes)",
false
});
});
bus.subscribe<Msg::CommandRequest>([this](Msg::CommandRequest const& req) {
auto id = req.id;
auto paramfail = [this, id]() {
return bus.raise(Msg::CommandFail{
id, -32602,
"Parameter failure",
Json::Out::empty_object()
});
};
if (req.command == "clboss-recent-earnings") {
auto days = double(14.0);
auto days_j = Jsmn::Object();
auto params = req.params;
if (params.is_object()) {
if (params.size() > 1)
return paramfail();
if (params.size() == 1) {
if (!params.has("days"))
return paramfail();
days_j = params["days"];
}
} else if (params.is_array()) {
if (params.size() > 1)
return paramfail();
if (params.size() == 1)
days_j = params[0];
}
if (!days_j.is_null()) {
if (!days_j.is_number())
return paramfail();
days = (double) days_j;
}
return db.transact().then([this, id, days](Sqlite3::Tx tx) {
auto report = recent_earnings_report(tx, days);
tx.commit();
return bus.raise(Msg::CommandResponse{
id, report
});
});
} else if (req.command == "clboss-earnings-history") {
auto nodeid = std::string("");
auto nodeid_j = Jsmn::Object();
auto params = req.params;
if (params.is_object()) {
if (params.size() > 1)
return paramfail();
if (params.size() == 1) {
if (!params.has("nodeid"))
return paramfail();
nodeid_j = params["nodeid"];
}
} else if (params.is_array()) {
if (params.size() > 1)
return paramfail();
if (params.size() == 1)
nodeid_j = params[0];
}
if (!nodeid_j.is_null()) {
if (!nodeid_j.is_string())
return paramfail();
nodeid = (std::string) nodeid_j;
}
return db.transact().then([this, id, nodeid](Sqlite3::Tx tx) {
auto report = earnings_history_report(tx, nodeid);
tx.commit();
return bus.raise(Msg::CommandResponse{
id, report
});
});
}
return Ev::lift();
});
bus.subscribe<Msg::SolicitStatus
>([this](Msg::SolicitStatus const& _) {
if (!db)
Expand Down Expand Up @@ -348,6 +439,131 @@ class EarningsTracker::Impl {
});
}

Json::Out recent_earnings_report(Sqlite3::Tx& tx, double days) {
auto cutoff = bucket_time(get_now()) - (days * 24 * 60 * 60);
auto fetch = tx.query(R"QRY(
SELECT node,
SUM(in_earnings) AS total_in_earnings,
SUM(in_expenditures) AS total_in_expenditures,
SUM(out_earnings) AS total_out_earnings,
SUM(out_expenditures) AS total_out_expenditures
FROM "EarningsTracker"
WHERE time_bucket >= :cutoff
GROUP BY node
ORDER BY (total_in_earnings - total_in_expenditures +
total_out_earnings - total_out_expenditures) DESC;
)QRY")
.bind(":cutoff", cutoff)
.execute()
;
auto out = Json::Out();
auto top = out.start_object();
auto recent = top.start_object("recent");
uint64_t total_in_earnings = 0;
uint64_t total_in_expenditures = 0;
uint64_t total_out_earnings = 0;
uint64_t total_out_expenditures = 0;
for (auto& r : fetch) {
auto in_earnings = r.get<std::uint64_t>(1);
auto in_expenditures = r.get<std::uint64_t>(2);
auto out_earnings = r.get<std::uint64_t>(3);
auto out_expenditures = r.get<std::uint64_t>(4);
auto sub = recent.start_object(r.get<std::string>(0));
sub
.field("in_earnings", in_earnings)
.field("in_expenditures", in_expenditures)
.field("out_earnings", out_earnings)
.field("out_expenditures", out_expenditures)
;
sub.end_object();
total_in_earnings += in_earnings;
total_in_expenditures += in_expenditures;
total_out_earnings += out_earnings;
total_out_expenditures += out_expenditures;
}
recent.end_object();
auto total = top.start_object("total");
total
.field("in_earnings", total_in_earnings)
.field("in_expenditures", total_in_expenditures)
.field("out_earnings", total_out_earnings)
.field("out_expenditures", total_out_expenditures)
;
total.end_object();
top.end_object();
return out;
}

Json::Out earnings_history_report(Sqlite3::Tx& tx, std::string nodeid) {
std::string sql;
if (nodeid.empty()) {
sql = R"QRY(
SELECT time_bucket,
SUM(in_earnings) AS total_in_earnings,
SUM(in_expenditures) AS total_in_expenditures,
SUM(out_earnings) AS total_out_earnings,
SUM(out_expenditures) AS total_out_expenditures
FROM "EarningsTracker"
GROUP BY time_bucket
ORDER BY time_bucket;
)QRY";
} else {
sql = R"QRY(
SELECT time_bucket,
in_earnings,
in_expenditures,
out_earnings,
out_expenditures
FROM "EarningsTracker"
WHERE node = :nodeid
ORDER BY time_bucket;
)QRY";
}
auto query = tx.query(sql.c_str());
if (!nodeid.empty()) {
query.bind(":nodeid", nodeid);
}
auto fetch = query.execute();

auto out = Json::Out();
auto top = out.start_object();
auto history = top.start_array("history");
uint64_t total_in_earnings = 0;
uint64_t total_in_expenditures = 0;
uint64_t total_out_earnings = 0;
uint64_t total_out_expenditures = 0;
for (auto& r : fetch) {
auto in_earnings = r.get<std::uint64_t>(1);
auto in_expenditures = r.get<std::uint64_t>(2);
auto out_earnings = r.get<std::uint64_t>(3);
auto out_expenditures = r.get<std::uint64_t>(4);
auto sub = history.start_object();
sub
.field("bucket_time", static_cast<std::uint64_t>(r.get<double>(0)))
.field("in_earnings", in_earnings)
.field("in_expenditures", in_expenditures)
.field("out_earnings", out_earnings)
.field("out_expenditures", out_expenditures)
;
sub.end_object();
total_in_earnings += in_earnings;
total_in_expenditures += in_expenditures;
total_out_earnings += out_earnings;
total_out_expenditures += out_expenditures;
}
history.end_array();
auto total = top.start_object("total");
total
.field("in_earnings", total_in_earnings)
.field("in_expenditures", total_in_expenditures)
.field("out_earnings", total_out_earnings)
.field("out_expenditures", total_out_expenditures)
;
total.end_object();
top.end_object();
return out;
}

public:
Impl() =delete;
Impl(Impl&&) =delete;
Expand Down
2 changes: 2 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,8 @@ TESTS = \
tests/boss/test_needsconnectsolicitor \
tests/boss/test_onchainfeemonitor_samples_init \
tests/boss/test_peerjudge_agetracker \
tests/boss/test_recentearnings \
tests/boss/test_earningshistory \
tests/boss/test_peerjudge_algo \
tests/boss/test_peerjudge_datagatherer \
tests/boss/test_peerstatistician \
Expand Down
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,36 @@ Earlier versions do not record, so if you have been using CLBOSS
before 0.11D, then historical offchain-to-onchain swaps are not
reported.

### `clboss-recent-earnings`, `clboss-earnings-history`

As of CLBOSS version [TBD], earnings and expenditures are tracked on a daily basis.
The following commands have been added to observe the new data:

- **`clboss-recent-earnings`**:
- **Purpose**: Returns a data structure equivalent to the
`offchain_earnings_tracker` collection in `clboss-status`, but
only includes recent earnings and expenditures.
- **Arguments**:
- `days` (optional): Specifies the number of days to include in
the report. Defaults to a fortnight (14 days) if not provided.

- **`clboss-earnings-history`**:
- **Purpose**: Provides a daily breakdown of earnings and expenditures.
- **Arguments**:
- `nodeid` (optional): Limits the history to a particular node if
provided. Without this argument, the values are accumulated
across all peers.
- **Output**:
- The history consists of an array of records showing the earnings
and expenditures for each day.
- The history includes an initial record with a time value of 0,
which contains any legacy earnings and expenditures collected by
CLBOSS before daily tracking was implemented.

These commands enhance the tracking of financial metrics, allowing for
detailed and recent analysis of earnings and expenditures on a daily
basis.

### `--clboss-min-onchain=<satoshis>`

Pass this option to `lightningd` in order to specify a target
Expand Down
10 changes: 10 additions & 0 deletions tests/boss/test_availablerpccommandsannouncer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ auto const help = std::string(R"JSON(
, { "command": "clboss-unmanage nodeid tags"
, "category": "plugin"
}
, { "command": "clboss-recent-earnings [days]"
, "category": "plugin"
}
, { "command": "clboss-earnings-history [nodeid]"
, "category": "plugin"
}
]
}
)JSON");
Expand Down Expand Up @@ -73,6 +79,10 @@ int main() {
assert(commands["clboss-status"].usage == "");
assert(commands["clboss-ignore-onchain"].usage == "[hours]");
assert(commands["clboss-unmanage"].usage == "nodeid tags");
assert(commands.count("clboss-recent-earnings") != 0);
assert(commands["clboss-recent-earnings"].usage == "[days]");
assert(commands.count("clboss-earnings-history") != 0);
assert(commands["clboss-earnings-history"].usage == "[nodeid]");

return Ev::lift();
});
Expand Down
Loading

0 comments on commit 13a4e8f

Please sign in to comment.