mirror of
https://github.com/ZmnSCPxj/clboss.git
synced 2026-08-13 12:33:20 +02:00
299 lines
7.4 KiB
C++
299 lines
7.4 KiB
C++
#include"Boss/Mod/EarningsTracker.hpp"
|
|
#include"Boss/Msg/DbResource.hpp"
|
|
#include"Boss/Msg/ForwardFee.hpp"
|
|
#include"Boss/Msg/ProvideStatus.hpp"
|
|
#include"Boss/Msg/RequestEarningsInfo.hpp"
|
|
#include"Boss/Msg/RequestMoveFunds.hpp"
|
|
#include"Boss/Msg/ResponseEarningsInfo.hpp"
|
|
#include"Boss/Msg/ResponseMoveFunds.hpp"
|
|
#include"Boss/Msg/SolicitStatus.hpp"
|
|
#include"Boss/concurrent.hpp"
|
|
#include"Ev/Io.hpp"
|
|
#include"S/Bus.hpp"
|
|
#include"Sqlite3.hpp"
|
|
#include"Util/make_unique.hpp"
|
|
#include<map>
|
|
|
|
namespace Boss { namespace Mod {
|
|
|
|
class EarningsTracker::Impl {
|
|
private:
|
|
S::Bus& bus;
|
|
Sqlite3::Db db;
|
|
|
|
/* Information of a pending MoveFunds. */
|
|
struct Pending {
|
|
Ln::NodeId source;
|
|
Ln::NodeId destination;
|
|
};
|
|
/* Maps a requester to the source-destination of the movefunds. */
|
|
std::map<void*, Pending> pendings;
|
|
|
|
void start() {
|
|
bus.subscribe<Msg::DbResource
|
|
>([this](Msg::DbResource const& r) {
|
|
db = r.db;
|
|
return init();
|
|
});
|
|
bus.subscribe<Msg::ForwardFee
|
|
>([this](Msg::ForwardFee const& f) {
|
|
return forward_fee(f.in_id, f.out_id, f.fee);
|
|
});
|
|
bus.subscribe<Msg::RequestMoveFunds
|
|
>([this](Msg::RequestMoveFunds const& req) {
|
|
return request_move_funds(req);
|
|
});
|
|
bus.subscribe<Msg::ResponseMoveFunds
|
|
>([this](Msg::ResponseMoveFunds const& rsp) {
|
|
return response_move_funds(rsp);
|
|
});
|
|
bus.subscribe<Msg::RequestEarningsInfo
|
|
>([this](Msg::RequestEarningsInfo const& req) {
|
|
return Boss::concurrent(request_earnings_info(req));
|
|
});
|
|
|
|
bus.subscribe<Msg::SolicitStatus
|
|
>([this](Msg::SolicitStatus const& _) {
|
|
if (!db)
|
|
return Ev::lift();
|
|
return status();
|
|
});
|
|
}
|
|
|
|
Ev::Io<void> init() {
|
|
return db.transact().then([](Sqlite3::Tx tx) {
|
|
tx.query_execute(R"QRY(
|
|
CREATE TABLE IF NOT EXISTS "EarningsTracker"
|
|
( node TEXT PRIMARY KEY
|
|
, in_earnings INTEGER NOT NULL
|
|
, in_expenditures INTEGER NOT NULL
|
|
, out_earnings INTEGER NOT NULL
|
|
, out_expenditures INTEGER NOT NULL
|
|
);
|
|
)QRY");
|
|
|
|
tx.commit();
|
|
return Ev::lift();
|
|
});
|
|
}
|
|
/* Ensure the given node has an entry. */
|
|
void ensure(Sqlite3::Tx& tx, Ln::NodeId const& node) {
|
|
tx.query(R"QRY(
|
|
INSERT OR IGNORE
|
|
INTO "EarningsTracker"
|
|
VALUES(:node, 0, 0, 0, 0);
|
|
)QRY")
|
|
.bind(":node", std::string(node))
|
|
.execute()
|
|
;
|
|
}
|
|
Ev::Io<void> forward_fee( Ln::NodeId const& in
|
|
, Ln::NodeId const& out
|
|
, Ln::Amount fee
|
|
) {
|
|
return db.transact().then([this, in, out, fee
|
|
](Sqlite3::Tx tx) {
|
|
ensure(tx, in);
|
|
ensure(tx, out);
|
|
|
|
tx.query(R"QRY(
|
|
UPDATE "EarningsTracker"
|
|
SET in_earnings = in_earnings + :fee
|
|
WHERE node = :node
|
|
;
|
|
)QRY")
|
|
.bind(":node", std::string(in))
|
|
.bind(":fee", fee.to_msat())
|
|
.execute()
|
|
;
|
|
tx.query(R"QRY(
|
|
UPDATE "EarningsTracker"
|
|
SET out_earnings = out_earnings + :fee
|
|
WHERE node = :node
|
|
;
|
|
)QRY")
|
|
.bind(":node", std::string(out))
|
|
.bind(":fee", fee.to_msat())
|
|
.execute()
|
|
;
|
|
|
|
tx.commit();
|
|
return Ev::lift();
|
|
});
|
|
}
|
|
Ev::Io<void>
|
|
request_move_funds(Boss::Msg::RequestMoveFunds const& req) {
|
|
auto& pending = pendings[req.requester];
|
|
pending.source = req.source;
|
|
pending.destination = req.destination;
|
|
return Ev::lift();
|
|
}
|
|
Ev::Io<void>
|
|
response_move_funds(Boss::Msg::ResponseMoveFunds const& rsp) {
|
|
auto requester = rsp.requester;
|
|
auto fee = rsp.fee_spent;
|
|
return db.transact().then([this, requester, fee
|
|
](Sqlite3::Tx tx) {
|
|
auto it = pendings.find(requester);
|
|
if (it == pendings.end())
|
|
/* Not in our table, huh, weird. */
|
|
return Ev::lift();
|
|
auto& pending = it->second;
|
|
|
|
ensure(tx, pending.source);
|
|
ensure(tx, pending.destination);
|
|
|
|
/* Source gets in-expenditures since it gets more
|
|
* incoming capacity (for more earnings for the
|
|
* incoming direction). */
|
|
tx.query(R"QRY(
|
|
UPDATE "EarningsTracker"
|
|
SET in_expenditures = in_expenditures + :fee
|
|
WHERE node = :node
|
|
;
|
|
)QRY")
|
|
.bind(":node", std::string(pending.source))
|
|
.bind(":fee", fee.to_msat())
|
|
.execute()
|
|
;
|
|
/* Destination gets out-expenditures for same
|
|
* reason.
|
|
*/
|
|
tx.query(R"QRY(
|
|
UPDATE "EarningsTracker"
|
|
SET out_expenditures = out_expenditures + :fee
|
|
WHERE node = :node
|
|
;
|
|
)QRY")
|
|
.bind( ":node"
|
|
, std::string(pending.destination)
|
|
)
|
|
.bind(":fee", fee.to_msat())
|
|
.execute()
|
|
;
|
|
|
|
/* Erase it. */
|
|
pendings.erase(it);
|
|
|
|
tx.commit();
|
|
return Ev::lift();
|
|
});
|
|
}
|
|
|
|
Ev::Io<void>
|
|
request_earnings_info(Msg::RequestEarningsInfo const& req) {
|
|
auto requester = req.requester;
|
|
auto node = req.node;
|
|
return db.transact().then([this, requester, node
|
|
](Sqlite3::Tx tx) {
|
|
auto in_earnings = Ln::Amount::sat(0);
|
|
auto in_expenditures = Ln::Amount::sat(0);
|
|
auto out_earnings = Ln::Amount::sat(0);
|
|
auto out_expenditures = Ln::Amount::sat(0);
|
|
|
|
auto fetch = tx.query(R"QRY(
|
|
SELECT in_earnings, in_expenditures
|
|
, out_earnings, out_expenditures
|
|
FROM "EarningsTracker"
|
|
WHERE node = :node
|
|
;
|
|
)QRY")
|
|
.bind(":node", std::string(node))
|
|
.execute()
|
|
;
|
|
for (auto& r : fetch) {
|
|
in_earnings = Ln::Amount::msat(
|
|
r.get<std::uint64_t>(0)
|
|
);
|
|
in_expenditures = Ln::Amount::msat(
|
|
r.get<std::uint64_t>(1)
|
|
);
|
|
out_earnings = Ln::Amount::msat(
|
|
r.get<std::uint64_t>(2)
|
|
);
|
|
out_expenditures = Ln::Amount::msat(
|
|
r.get<std::uint64_t>(3)
|
|
);
|
|
}
|
|
|
|
tx.commit();
|
|
return bus.raise(Msg::ResponseEarningsInfo{
|
|
requester, node,
|
|
in_earnings, in_expenditures,
|
|
out_earnings, out_expenditures
|
|
});
|
|
});
|
|
}
|
|
|
|
Ev::Io<void> status() {
|
|
return db.transact().then([this](Sqlite3::Tx tx) {
|
|
auto fetch = tx.query(R"QRY(
|
|
SELECT node
|
|
, in_earnings, in_expenditures
|
|
, out_earnings, out_expenditures
|
|
FROM "EarningsTracker"
|
|
;
|
|
)QRY").execute();
|
|
|
|
uint64_t total_in_earnings = 0;
|
|
uint64_t total_in_expenditures = 0;
|
|
uint64_t total_out_earnings = 0;
|
|
uint64_t total_out_expenditures = 0;
|
|
|
|
auto out = Json::Out();
|
|
auto obj = out.start_object();
|
|
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 = obj.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;
|
|
}
|
|
|
|
auto sub = obj.start_object("total");
|
|
sub
|
|
.field("in_earnings", total_in_earnings)
|
|
.field("in_expenditures", total_in_expenditures)
|
|
.field("out_earnings", total_out_earnings)
|
|
.field("out_expenditures", total_out_expenditures)
|
|
;
|
|
sub.end_object();
|
|
|
|
obj.end_object();
|
|
|
|
tx.commit();
|
|
|
|
return bus.raise(Msg::ProvideStatus{
|
|
"offchain_earnings_tracker",
|
|
std::move(out)
|
|
});
|
|
});
|
|
}
|
|
|
|
public:
|
|
Impl() =delete;
|
|
Impl(Impl&&) =delete;
|
|
Impl(Impl const&) =delete;
|
|
|
|
explicit
|
|
Impl(S::Bus& bus_) : bus(bus_) { start(); }
|
|
};
|
|
|
|
EarningsTracker::EarningsTracker(EarningsTracker&&) =default;
|
|
EarningsTracker::~EarningsTracker() =default;
|
|
|
|
EarningsTracker::EarningsTracker(S::Bus& bus)
|
|
: pimpl(Util::make_unique<Impl>(bus)) { }
|
|
|
|
}}
|