mirror of
https://github.com/ZmnSCPxj/clboss.git
synced 2026-08-15 12:50:42 +02:00
194 lines
5.3 KiB
C++
194 lines
5.3 KiB
C++
#include"Boss/Mod/ChannelCandidateInvestigator/Gumshoe.hpp"
|
|
#include"Boss/Mod/ChannelCandidateInvestigator/Manager.hpp"
|
|
#include"Boss/Mod/ChannelCandidateInvestigator/Secretary.hpp"
|
|
#include"Boss/Msg/Init.hpp"
|
|
#include"Boss/Msg/ListpeersResult.hpp"
|
|
#include"Boss/Msg/ProposeChannelCandidates.hpp"
|
|
#include"Boss/Msg/SolicitChannelCandidates.hpp"
|
|
#include"Boss/Msg/TimerRandomHourly.hpp"
|
|
#include"Boss/log.hpp"
|
|
#include"Boss/random_engine.hpp"
|
|
#include"Ev/map.hpp"
|
|
#include"Ln/NodeId.hpp"
|
|
#include"S/Bus.hpp"
|
|
#include"Sqlite3.hpp"
|
|
#include<random>
|
|
|
|
namespace {
|
|
|
|
/* If we are below this number of non-negative candidates,
|
|
* solicit more. */
|
|
auto const min_good_candidates = std::size_t(3);
|
|
|
|
/* Nodes below this score should just be dropped. */
|
|
auto const min_score = std::int64_t(-3);
|
|
|
|
/* Number of candidates to investigate in parallel. */
|
|
auto const max_investigation = std::size_t(8);
|
|
|
|
}
|
|
|
|
namespace Boss { namespace Mod { namespace ChannelCandidateInvestigator {
|
|
|
|
void Manager::start() {
|
|
/* Give the gumshoe the report function. */
|
|
gumshoe.set_report_func([this](Ln::NodeId n, bool success) {
|
|
return db.transact().then([this, n, success](Sqlite3::Tx tx) {
|
|
secretary.update_score( tx, n
|
|
, success ? +1 : -1
|
|
, min_score
|
|
);
|
|
tx.commit();
|
|
|
|
return Ev::lift();
|
|
});
|
|
});
|
|
|
|
bus.subscribe<Msg::Init>([this](Msg::Init const& init) {
|
|
db = init.db;
|
|
|
|
/* Initialize the database. */
|
|
return db.transact().then([this](Sqlite3::Tx tx) {
|
|
secretary.initialize(tx);
|
|
/* Get number of good candidates. */
|
|
auto good_candidates =
|
|
secretary.get_nonnegative_candidates_count(tx);
|
|
tx.commit();
|
|
|
|
if (good_candidates < min_good_candidates)
|
|
return solicit_candidates(good_candidates);
|
|
|
|
return Ev::lift();
|
|
});
|
|
});
|
|
/* When a new candidate is proposed, add it. */
|
|
bus.subscribe<Msg::ProposeChannelCandidates
|
|
>([this](Msg::ProposeChannelCandidates const& c) {
|
|
return db.transact().then([this, c](Sqlite3::Tx tx) {
|
|
secretary.add_candidate(tx, c);
|
|
tx.commit();
|
|
|
|
return Ev::lift();
|
|
});
|
|
});
|
|
|
|
bus.subscribe<Msg::TimerRandomHourly
|
|
>([this](Msg::TimerRandomHourly const& _) {
|
|
/* Construct shared variables. */
|
|
/* Number of good candidates. */
|
|
auto good_candidates = std::make_shared<std::size_t>();
|
|
/* What cases to give to the gumshoe. */
|
|
auto cases = std::make_shared<std::vector<Ln::NodeId>>();
|
|
/* Human-readable text. */
|
|
auto report = std::make_shared<std::string>();
|
|
|
|
return db.transact().then([=](Sqlite3::Tx tx) {
|
|
/* Determine number of good candidates. */
|
|
*good_candidates =
|
|
secretary.get_nonnegative_candidates_count(tx);
|
|
/* Get some nodes for investigation. */
|
|
*cases = secretary.get_for_investigation(
|
|
tx, max_investigation
|
|
);
|
|
/* Get a report. */
|
|
*report = secretary.report(tx);
|
|
/* Finish up. */
|
|
tx.commit();
|
|
|
|
/* Print the report. */
|
|
return Boss::log( bus, Info
|
|
, "ChannelCandidateInvestigator: %s"
|
|
, report->c_str()
|
|
);
|
|
}).then([=]() {
|
|
/* If too few candidates, get more. */
|
|
if (*good_candidates < min_good_candidates)
|
|
return solicit_candidates(*good_candidates);
|
|
/* Even if we have sufficient, have a chance to
|
|
* get more.
|
|
*/
|
|
auto dist = std::uniform_int_distribution<std::size_t>(
|
|
0, 2 * (*good_candidates)
|
|
);
|
|
if (dist(Boss::random_engine) == 0)
|
|
return solicit_candidates(*good_candidates);
|
|
|
|
return Ev::lift();
|
|
}).then([=]() {
|
|
/* Hand over possible cases to the gumshoe. */
|
|
auto to_gumshoe = [this](Ln::NodeId n) {
|
|
return gumshoe.investigate(n).then([]() {
|
|
return Ev::lift<int>(0);
|
|
});
|
|
};
|
|
return Ev::map(to_gumshoe, std::move(*cases));
|
|
}).then([](std::vector<int>) {
|
|
|
|
return Ev::lift();
|
|
});
|
|
});
|
|
|
|
/* Remove candidates we already have a channel with. */
|
|
bus.subscribe<Msg::ListpeersResult
|
|
>([this](Msg::ListpeersResult const& l) {
|
|
auto to_remove = std::make_shared<std::vector<Ln::NodeId>>();
|
|
for (auto i = std::size_t(0); i < l.peers.size(); ++i) {
|
|
auto peer = l.peers[i];
|
|
if (!peer.is_object() || !peer.has("id"))
|
|
continue;
|
|
if (!peer.has("channels"))
|
|
continue;
|
|
auto id = peer["id"];
|
|
if (!id.is_string())
|
|
continue;
|
|
auto node = Ln::NodeId(std::string(id));
|
|
auto chans = peer["channels"];
|
|
if (!chans.is_array())
|
|
continue;
|
|
|
|
for (auto j = std::size_t(0); j < chans.size(); ++j) {
|
|
auto chan = chans[i];
|
|
if (!chan.is_object() || !chan.has("state"))
|
|
continue;
|
|
auto state = chan["state"];
|
|
if (!state.is_string())
|
|
continue;
|
|
auto state_s = std::string(state);
|
|
if (state_s.size() < 8)
|
|
continue;
|
|
auto prefix = std::string( state_s.begin()
|
|
, state_s.begin() + 8
|
|
);
|
|
if ( prefix != "CHANNELD"
|
|
&& prefix != "OPENINGD"
|
|
)
|
|
continue;
|
|
/* We have a live channel with them! */
|
|
to_remove->emplace_back(std::move(node));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Now have the secretary update our records. */
|
|
return db.transact().then([this, to_remove](Sqlite3::Tx tx) {
|
|
for (auto const& n : *to_remove)
|
|
secretary.remove_candidate(tx, n);
|
|
tx.commit();
|
|
return Ev::lift();
|
|
});
|
|
});
|
|
}
|
|
Ev::Io<void> Manager::solicit_candidates(std::size_t good_candidates) {
|
|
return Boss::log( bus, Debug
|
|
, "ChannelCandidateInvestigator: "
|
|
"Have %zu channel candidates, "
|
|
"soliciting more."
|
|
, good_candidates
|
|
).then([this]() {
|
|
return bus.raise(
|
|
Msg::SolicitChannelCandidates{}
|
|
);
|
|
});
|
|
}
|
|
|
|
}}}
|