diff --git a/Boss/Mod/ChannelCandidateInvestigator/Manager.cpp b/Boss/Mod/ChannelCandidateInvestigator/Manager.cpp index 27778fb..35129b2 100644 --- a/Boss/Mod/ChannelCandidateInvestigator/Manager.cpp +++ b/Boss/Mod/ChannelCandidateInvestigator/Manager.cpp @@ -2,6 +2,7 @@ #include"Boss/Mod/ChannelCandidateInvestigator/Manager.hpp" #include"Boss/Mod/ChannelCandidateInvestigator/Secretary.hpp" #include"Boss/Mod/InternetConnectionMonitor.hpp" +#include"Boss/Msg/ChannelCreateResult.hpp" #include"Boss/Msg/Init.hpp" #include"Boss/Msg/ListpeersAnalyzedResult.hpp" #include"Boss/Msg/ProposeChannelCandidates.hpp" @@ -175,6 +176,27 @@ void Manager::start() { return Ev::lift(); }); }); + + /* Remove candidates that we have tried to channel with, regardless + * of whether the attempt failed or succeeded. */ + bus.subscribe([this](Msg::ChannelCreateResult const& c) { + if (!db) + return Ev::lift(); + auto to_remove = std::make_shared(c.node); + return Boss::log( bus, Debug + , "ChannelCandidateInvestigator: " + "Node %s used in channeling attempt, " + "removing from investigation." + , std::string(c.node) + ).then([this]() { + return db.transact(); + }).then([this, to_remove](Sqlite3::Tx tx) { + secretary.remove_candidate(tx, *to_remove); + tx.commit(); + return Ev::lift(); + }); + }); } Ev::Io Manager::solicit_candidates(std::size_t good_candidates) { return Boss::log( bus, Debug diff --git a/Boss/Mod/ChannelCreator/Carpenter.cpp b/Boss/Mod/ChannelCreator/Carpenter.cpp new file mode 100644 index 0000000..0a1fa5e --- /dev/null +++ b/Boss/Mod/ChannelCreator/Carpenter.cpp @@ -0,0 +1,217 @@ +#include"Boss/Mod/ChannelCreator/Carpenter.hpp" +#include"Boss/Mod/Rpc.hpp" +#include"Boss/Mod/Waiter.hpp" +#include"Boss/Msg/ChannelCreateResult.hpp" +#include"Boss/Msg/Init.hpp" +#include"Boss/concurrent.hpp" +#include"Boss/log.hpp" +#include"Ev/Io.hpp" +#include"Ev/foreach.hpp" +#include"Ev/yield.hpp" +#include"Jsmn/Object.hpp" +#include"Json/Out.hpp" +#include"Ln/Amount.hpp" +#include"Ln/NodeId.hpp" +#include"S/Bus.hpp" +#include +#include +#include +#include +#include + +namespace { + +/* Round down the amount to nearest satoshi. */ +Ln::Amount rounddown_to_sat(Ln::Amount in) { + auto msat = in.to_msat(); + auto out_msat = (msat / 1000) * 1000; + return Ln::Amount::msat(out_msat); +} + +} + +namespace Boss { namespace Mod { namespace ChannelCreator { + +void Carpenter::start() { + bus.subscribe< Boss::Msg::Init + >([this](Boss::Msg::Init const& init) { + rpc = &init.rpc; + return Ev::lift(); + }); +} + +Ev::Io +Carpenter::construct(std::map plan) { + assert(!plan.empty()); + + if (!rpc) + return Boss::log( bus, Error + , "ChannelCreator: attempt to " + "construct before RPC available." + ); + + /* Move the plan to shared storage. */ + auto pplan = std::make_shared>( + std::move(plan) + ); + /* Nodes that failed to create. */ + auto pfails = std::make_shared>(); + /* Nodes that successfully created. */ + auto ppasses = std::make_shared>(); + + return Ev::yield().then([this, pplan]() { + /* First, try to connect to all of them in parallel, + * as recommended in the manpage of multifundchannel. */ + auto nodes = std::vector(); + std::transform( pplan->begin(), pplan->end() + , std::back_inserter(nodes) + , [](std::pair const& e){ + return e.first; + }); + return Ev::foreach( std::bind( &Carpenter::connect_1 + , this + , std::placeholders::_1 + ) + , std::move(nodes) + ); + }).then([this]() { + /* Now wait a few seconds as per multifundchannel manpage. */ + return waiter.wait(3.0); + }).then([this, pplan]() { + + /* The plan might have sub-satoshi amounts that are not + * rounded off. + * Just round down everything; fees for each channel are + * likely to dominate anyway. + */ + for (auto& p : *pplan) + p.second = rounddown_to_sat(p.second); + + /* Now construct params. */ + auto params = Json::Out() + .start_object() + .field( "destinations" + , json_plan(*pplan) + ) + .field("feerate", std::string("normal")) + .field("minchannels", (double) 1) + .end_object() + ; + return rpc->command("multifundchannel", std::move(params)); + }).then([this, ppasses, pfails, pplan](Jsmn::Object res) { + auto report = std::ostringstream(); + try { + auto chans = res["channel_ids"]; + auto first = true; + for (auto i = std::size_t(0); i < chans.size(); ++i) { + auto c = chans[i]; + auto node = Ln::NodeId(std::string(c["id"])); + ppasses->push(std::move(node)); + if (first) + first = false; + else + report << ", "; + report << node << ": " + << (*pplan)[node] + ; + } + + auto bads = res["failed"]; + if (bads.size() > 0) + report << "; FAILED: "; + first = true; + for (auto i = std::size_t(0); i < bads.size(); ++i) { + auto b = bads[i]; + auto node = Ln::NodeId(std::string(b["id"])); + pfails->push(std::move(node)); + if (first) + first = false; + else + report << ", "; + report << node; + } + } catch (std::invalid_argument const&) { + auto os = std::ostringstream(); + os << res; + return Boss::log( bus, Error + , "ChannelCreator::Carpenter: " + "Unexpected result from " + "multifundchannel: %s" + , os.str().c_str() + ); + } + + return Boss::log( bus, Info + , "ChannelCreator: Created: %s" + , report.str().c_str() + ); + }).catching([this, pplan, pfails](RpcError const& e) { + /* RPC error means all failed. */ + for (auto const& p : *pplan) + pfails->push(p.first); + return Boss::log( bus, Info + , "ChannelCreator: all channels failed to " + "construct." + ); + }).then([this, pfails]() { + return Boss::concurrent(report_channelings( + std::move(*pfails), false + )); + }).then([this, ppasses]() { + return Boss::concurrent(report_channelings( + std::move(*ppasses), true + )); + }); +} + +Json::Out +Carpenter::json_single_plan(Ln::NodeId const& n, Ln::Amount const& a) { + return Json::Out() + .start_object() + .field("id", std::string(n)) + .field("amount", std::string(a)) + .field("announce", true) + .end_object() + ; +} +Json::Out +Carpenter::json_plan(std::map const& plan) { + auto parms = Json::Out(); + auto arr = parms.start_array(); + for (auto const& p : plan) + arr.entry(json_single_plan(p.first, p.second)); + arr.end_array(); + return parms; +} +Ev::Io +Carpenter::connect_1(Ln::NodeId node) { + return rpc->command("connect" + , Json::Out() + .start_object() + .field("id", std::string(node)) + .end_object() + ).then([](Jsmn::Object) { + return Ev::lift(); + }).catching([](RpcError const& e) { + return Ev::lift(); + }); +} + +Ev::Io +Carpenter::report_channelings(std::queue nodes, bool ok) { + auto pq = std::make_shared>(std::move( + nodes + )); + return Ev::yield().then([this, pq, ok]() { + if (pq->empty()) + return Ev::lift(); + return bus.raise(Msg::ChannelCreateResult{ + std::move(pq->front()), ok + }).then([this, pq, ok](){ + pq->pop(); + return report_channelings(std::move(*pq), ok); + }); + }); +} + +}}} diff --git a/Boss/Mod/ChannelCreator/Carpenter.hpp b/Boss/Mod/ChannelCreator/Carpenter.hpp new file mode 100644 index 0000000..40322e3 --- /dev/null +++ b/Boss/Mod/ChannelCreator/Carpenter.hpp @@ -0,0 +1,64 @@ +#ifndef BOSS_MOD_CHANNELCREATOR_CARPENTER_HPP +#define BOSS_MOD_CHANNELCREATOR_CARPENTER_HPP + +#include +#include + +namespace Boss { namespace Mod { class Rpc; }} +namespace Boss { namespace Mod { class Waiter; }} +namespace Ev { template class Io; } +namespace Json { class Out; } +namespace Ln { class Amount; } +namespace Ln { class NodeId; } +namespace S { class Bus; } + +namespace Boss { namespace Mod { namespace ChannelCreator { + +/** class Boss::Mod::ChannelCreator::Carpenter + * + * @brief Actual module that builds channels + * according to a plan given to it. + */ +class Carpenter { +private: + S::Bus& bus; + Boss::Mod::Waiter& waiter; + Boss::Mod::Rpc* rpc; + + void start(); + + Json::Out + json_plan(std::map const& plan); + Json::Out + json_single_plan(Ln::NodeId const&, Ln::Amount const&); + + Ev::Io + connect_1(Ln::NodeId node); + Ev::Io + report_channelings(std::queue nodes, bool); + +public: + Carpenter() =delete; + Carpenter(Carpenter const&) =delete; + Carpenter(Carpenter&&) =delete; + + explicit + Carpenter( S::Bus& bus_ + , Boss::Mod::Waiter& waiter_ + ) : bus(bus_) + , waiter(waiter_) + , rpc(nullptr) + { start(); } + + /** Boss::Mod::ChannelCreator::Carpenter::construct + * + * @brief have the carpenter construct channels, + * assigning specific values to specific nodes. + */ + Ev::Io + construct(std::map plan); +}; + +}}} + +#endif /* !defined(BOSS_MOD_CHANNELCREATOR_CARPENTER_HPP) */ diff --git a/Boss/Mod/ChannelCreator/Main.cpp b/Boss/Mod/ChannelCreator/Main.cpp index 71961dd..571d896 100644 --- a/Boss/Mod/ChannelCreator/Main.cpp +++ b/Boss/Mod/ChannelCreator/Main.cpp @@ -1,15 +1,18 @@ +#include"Boss/Mod/ChannelCreator/Carpenter.hpp" #include"Boss/Mod/ChannelCreator/Main.hpp" #include"Boss/Mod/ChannelCreator/Manager.hpp" #include"Util/make_unique.hpp" namespace Boss { namespace Mod { namespace ChannelCreator { -/* TODO Carpenter. */ -struct Carpenter {}; - Main::Main( S::Bus& bus + , Boss::Mod::Waiter& waiter , Boss::Mod::ChannelCandidateInvestigator::Main& investigator - ) : manager(Util::make_unique(bus, investigator)) + ) : carpenter(Util::make_unique(bus, waiter)) + , manager(Util::make_unique( bus + , investigator + , *carpenter + )) { } Main::~Main() { } diff --git a/Boss/Mod/ChannelCreator/Main.hpp b/Boss/Mod/ChannelCreator/Main.hpp index f0011c8..6d7b853 100644 --- a/Boss/Mod/ChannelCreator/Main.hpp +++ b/Boss/Mod/ChannelCreator/Main.hpp @@ -10,6 +10,7 @@ namespace Boss { namespace Mod { namespace ChannelCreator { class Carpenter; }}} namespace Boss { namespace Mod { namespace ChannelCreator { class Manager; }}} +namespace Boss { namespace Mod { class Waiter; }} namespace S { class Bus; } namespace Boss { namespace Mod { namespace ChannelCreator { @@ -36,6 +37,7 @@ public: Main(Main&&) =delete; Main( S::Bus& bus + , Boss::Mod::Waiter& waiter , Boss::Mod::ChannelCandidateInvestigator::Main& investigator ); ~Main(); diff --git a/Boss/Mod/ChannelCreator/Manager.cpp b/Boss/Mod/ChannelCreator/Manager.cpp index f583884..e9cc0a5 100644 --- a/Boss/Mod/ChannelCreator/Manager.cpp +++ b/Boss/Mod/ChannelCreator/Manager.cpp @@ -1,4 +1,5 @@ #include"Boss/Mod/ChannelCandidateInvestigator/Main.hpp" +#include"Boss/Mod/ChannelCreator/Carpenter.hpp" #include"Boss/Mod/ChannelCreator/Dowser.hpp" #include"Boss/Mod/ChannelCreator/Manager.hpp" #include"Boss/Mod/ChannelCreator/Planner.hpp" @@ -134,8 +135,7 @@ Manager::on_request_channel_creation(Ln::Amount amt) { if (plan->empty()) return Ev::lift(); - // TODO: instruct carpenter to build channels. - return Ev::lift(); + return carpenter.construct(std::move(*plan)); }); } diff --git a/Boss/Mod/ChannelCreator/Manager.hpp b/Boss/Mod/ChannelCreator/Manager.hpp index 8d05e78..1c4cd86 100644 --- a/Boss/Mod/ChannelCreator/Manager.hpp +++ b/Boss/Mod/ChannelCreator/Manager.hpp @@ -27,6 +27,7 @@ private: S::Bus& bus; Boss::Mod::Rpc* rpc; Boss::Mod::ChannelCandidateInvestigator::Main& investigator; + Boss::Mod::ChannelCreator::Carpenter& carpenter; Ln::NodeId self; void start(); @@ -37,13 +38,14 @@ public: Manager(Manager const&) =delete; Manager(Manager&&) =delete; - /* TODO: require carpenter. */ explicit Manager( S::Bus& bus_ , Boss::Mod::ChannelCandidateInvestigator::Main& investigator_ + , Boss::Mod::ChannelCreator::Carpenter& carpenter_ ) : bus(bus_) , rpc(nullptr) , investigator(investigator_) + , carpenter(carpenter_) , self() { start(); diff --git a/Boss/Mod/all.cpp b/Boss/Mod/all.cpp index 03e5c23..dd6716c 100644 --- a/Boss/Mod/all.cpp +++ b/Boss/Mod/all.cpp @@ -96,7 +96,7 @@ std::shared_ptr all( std::ostream& cout auto investigator = all->install< ChannelCandidateInvestigator::Main >(bus, *imon); all->install(bus); - all->install(bus, *investigator); + all->install(bus, *waiter, *investigator); return all; } diff --git a/Boss/Msg/ChannelCreateResult.hpp b/Boss/Msg/ChannelCreateResult.hpp new file mode 100644 index 0000000..115bdf1 --- /dev/null +++ b/Boss/Msg/ChannelCreateResult.hpp @@ -0,0 +1,22 @@ +#ifndef BOSS_MSG_CHANNELCREATERESULT_HPP +#define BOSS_MSG_CHANNELCREATERESULT_HPP + +#include"Ln/NodeId.hpp" + +namespace Boss { namespace Msg { + +/** struct Boss::Msg::ChannelCreateResult + * + * @brief used to report that we attempted a + * channel open with the node, and whether + * it succeeded or failed. + */ +struct ChannelCreateResult { + Ln::NodeId node; + bool success; +}; + +}} + +#endif /* !defined(BOSS_MSG_CHANNELCREATERESULT_HPP) */ + diff --git a/Makefile.am b/Makefile.am index 7d53e37..92cdd1f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -30,6 +30,8 @@ libclboss_la_SOURCES = \ Boss/Mod/ChannelCandidatePreinvestigator.hpp \ Boss/Mod/ChannelCreationDecider.cpp \ Boss/Mod/ChannelCreationDecider.hpp \ + Boss/Mod/ChannelCreator/Carpenter.cpp \ + Boss/Mod/ChannelCreator/Carpenter.hpp \ Boss/Mod/ChannelCreator/Dowser.cpp \ Boss/Mod/ChannelCreator/Dowser.hpp \ Boss/Mod/ChannelCreator/Main.cpp \ @@ -82,6 +84,7 @@ libclboss_la_SOURCES = \ Boss/Mod/all.hpp \ Boss/Msg/Begin.hpp \ Boss/Msg/Block.hpp \ + Boss/Msg/ChannelCreateResult.hpp \ Boss/Msg/ChannelFunds.hpp \ Boss/Msg/CommandRequest.hpp \ Boss/Msg/CommandResponse.hpp \