diff --git a/Boss/Mod/ConstructedListpeers.cpp b/Boss/Mod/ConstructedListpeers.cpp new file mode 100644 index 000000000..50815477b --- /dev/null +++ b/Boss/Mod/ConstructedListpeers.cpp @@ -0,0 +1,47 @@ +#include"Boss/Mod/ConstructedListpeers.hpp" +#include"Jsmn/Object.hpp" +#include + +namespace Boss { namespace Mod { + +/* + * IMPORTANT - the msg is no longer directly obtained from + * `listpeers` but rather is constructed by "convolving" the value + * from `listpeerchannels`. Specifically, the top level `peer` + * objects are non-standard and only have what CLBOSS uses ... + */ + +/* This helper is used by tests which use the old listpeers format */ +Boss::Mod::ConstructedListpeers convert_legacy_listpeers(Jsmn::Object const & legacy_listpeers) { + Boss::Mod::ConstructedListpeers cpeers; + for (auto p : legacy_listpeers) { + if (!p.has("id")) + continue; + auto id = Ln::NodeId(std::string(p["id"])); + cpeers[id].connected = false; + if (p.has("connected")) { + auto connected = p["connected"]; + cpeers[id].connected = connected.is_boolean() && bool(connected); + } + auto cs = p["channels"]; + for (auto c : cs) { + cpeers[id].channels.push_back(c); + } + } + return cpeers; +} + +std::ostream& operator<<(std::ostream& os, Boss::Mod::ConstructedListpeers const& o) { + for (auto p : o) { + os << p.first << ':' + << "connected: " << p.second.connected + << ", channels: "; + for (auto c : p.second.channels) { + os << c; + } + } + return os; +} + + +}} diff --git a/Boss/Mod/ConstructedListpeers.hpp b/Boss/Mod/ConstructedListpeers.hpp new file mode 100644 index 000000000..824bb681a --- /dev/null +++ b/Boss/Mod/ConstructedListpeers.hpp @@ -0,0 +1,33 @@ +#ifndef BOSS_MOD_CONSTRUCTEDLISTPEERS_HPP +#define BOSS_MOD_CONSTRUCTEDLISTPEERS_HPP + +#include +#include"Ln/NodeId.hpp" +#include"Jsmn/Object.hpp" + +namespace Boss { namespace Mod { + +/* + * IMPORTANT - the msg is no longer directly obtained from + * `listpeers` but rather is constructed by "convolving" the value + * from `listpeerchannels`. Specifically, the top level `peer` + * objects are non-standard and only have what CLBOSS uses ... + */ + +/** class Boss::Mod::ConstructedListpeers + */ +struct ConstructedListpeer { + bool connected; + std::vector channels; +}; + +typedef std::map ConstructedListpeers; + +std::ostream& operator<<(std::ostream& os, ConstructedListpeers const& o); + + +/* for unit tests w/ legacy listpeer setups */ +Boss::Mod::ConstructedListpeers convert_legacy_listpeers(Jsmn::Object const & legacy_listpeers); + +}} +#endif /* !defined(BOSS_MOD_CONSTRUCTEDLISTPEERS_HPP) */ diff --git a/Boss/Mod/EarningsRebalancer.cpp b/Boss/Mod/EarningsRebalancer.cpp index 7eb0bda61..cca47c3d6 100644 --- a/Boss/Mod/EarningsRebalancer.cpp +++ b/Boss/Mod/EarningsRebalancer.cpp @@ -1,3 +1,4 @@ + #include"Boss/Mod/EarningsRebalancer.hpp" #include"Boss/ModG/RebalanceUnmanagerProxy.hpp" #include"Boss/ModG/ReqResp.hpp" @@ -119,7 +120,7 @@ class EarningsRebalancer::Impl { >([this](Msg::ListpeersResult const& m) { if (m.initial) return Ev::lift(); - return update_balances(m.peers); + return update_balances(m.cpeers); }); /* Command to trigger the algorithm for testing. */ @@ -144,7 +145,7 @@ class EarningsRebalancer::Impl { }); } - Ev::Io update_balances(Jsmn::Object const& peers) { + Ev::Io update_balances(Boss::Mod::ConstructedListpeers const& peers) { auto new_balances = std::map(); try { for (auto p : peers) { @@ -152,11 +153,9 @@ class EarningsRebalancer::Impl { auto receivable = Ln::Amount::sat(0); auto total = Ln::Amount::sat(0); - auto id = Ln::NodeId(std::string( - p["id"] - )); + auto id = p.first; - for (auto c : p["channels"]) { + for (auto c : p.second.channels) { auto state = std::string( c["state"] ); diff --git a/Boss/Mod/InitialRebalancer.cpp b/Boss/Mod/InitialRebalancer.cpp index bbbe7c4bf..fdae07f84 100644 --- a/Boss/Mod/InitialRebalancer.cpp +++ b/Boss/Mod/InitialRebalancer.cpp @@ -76,7 +76,7 @@ class InitialRebalancer::Impl { */ if (m.initial) return Ev::lift(); - return run(m.peers); + return run(m.cpeers); }); } @@ -92,7 +92,7 @@ class InitialRebalancer::Impl { ~Run() =default; explicit - Run( S::Bus& bus, Jsmn::Object const& peers + Run( S::Bus& bus, Boss::Mod::ConstructedListpeers const& peers , MoveRR& move_rr, ExpenseRR& expense_rr , std::set& current_sources , std::set const& unmanaged @@ -101,8 +101,8 @@ class InitialRebalancer::Impl { }; Ev::Io - run(Jsmn::Object const& peers) { - auto ppeers = std::make_shared(peers); + run(Boss::Mod::ConstructedListpeers const& peers) { + auto ppeers = std::make_shared(peers); return Ev::lift().then([this]() { return unmanager.get_unmanaged(); }).then([ this @@ -137,7 +137,7 @@ class InitialRebalancer::Impl::Run::Impl : public std::enable_shared_from_this { private: S::Bus& bus; - Jsmn::Object peers; + Boss::Mod::ConstructedListpeers peers; /* Data about a peer. */ struct Info { @@ -174,13 +174,11 @@ class InitialRebalancer::Impl::Run::Impl auto receivable = Ln::Amount::sat(0); auto total = Ln::Amount::sat(0); - auto id = Ln::NodeId(std::string( - p["id"] - )); + auto id = p.first; if (unmanaged.count(id) != 0) continue; - auto cs = p["channels"]; + auto cs = p.second.channels; for (auto c : cs) { auto state = std::string( c["state"] @@ -468,7 +466,7 @@ class InitialRebalancer::Impl::Run::Impl public: Impl( S::Bus& bus_ - , Jsmn::Object const& peers_ + , Boss::Mod::ConstructedListpeers const& peers_ , MoveRR& move_rr_ , ExpenseRR& expense_rr_ , std::set& current_sources_ @@ -491,7 +489,7 @@ class InitialRebalancer::Impl::Run::Impl }; InitialRebalancer::Impl::Run::Run( S::Bus& bus - , Jsmn::Object const& peers + , Boss::Mod::ConstructedListpeers const& peers , MoveRR& move_rr, ExpenseRR& expense_rr , std::set& current_sources , std::set const& unmanaged diff --git a/Boss/Mod/ListpeersAnalyzer.cpp b/Boss/Mod/ListpeersAnalyzer.cpp index 2ed1708a9..7197f1544 100644 --- a/Boss/Mod/ListpeersAnalyzer.cpp +++ b/Boss/Mod/ListpeersAnalyzer.cpp @@ -15,31 +15,13 @@ ListpeersAnalyzer::ListpeersAnalyzer(S::Bus& bus) { auto ar = Msg::ListpeersAnalyzedResult(); ar.initial = l.initial; - for (auto peer : l.peers) { - if (!peer.is_object() || !peer.has("id")) - continue; - if (!peer.has("connected")) - continue; - if (!peer.has("channels")) - continue; + for (auto peer : l.cpeers) { - auto id_j = peer["id"]; - if (!id_j.is_string()) - continue; - auto id_s = std::string(id_j); - if (!Ln::NodeId::valid_string(id_s)) - continue; - auto id = Ln::NodeId(id_s); - - auto connected_j = peer["connected"]; - if (!connected_j.is_boolean()) - continue; - auto connected = bool(connected_j); + auto id = peer.first; + auto connected = peer.second.connected; auto has_chan = bool(false); - auto chans = peer["channels"]; - if (!chans.is_array()) - continue; + auto chans = peer.second.channels; for (auto chan : chans) { if (!chan.is_object() || !chan.has("state")) continue; diff --git a/Boss/Mod/ListpeersAnnouncer.cpp b/Boss/Mod/ListpeersAnnouncer.cpp index 63d6af125..c7b42eda4 100644 --- a/Boss/Mod/ListpeersAnnouncer.cpp +++ b/Boss/Mod/ListpeersAnnouncer.cpp @@ -1,4 +1,5 @@ #include"Boss/Mod/ListpeersAnnouncer.hpp" +#include"Boss/Mod/ConstructedListpeers.hpp" #include"Boss/ModG/RpcProxy.hpp" #include"Boss/Msg/Init.hpp" #include"Boss/Msg/ListpeersResult.hpp" @@ -15,42 +16,57 @@ namespace Boss { namespace Mod { +/* + * IMPORTANT - the msg is no longer directly obtained from + * `listpeers` but rather is constructed by "convolving" the value + * from `listpeerchannels`. Specifically, the top level `peer` + * objects are non-standard and only have what CLBOSS uses ... + */ + void ListpeersAnnouncer::start() { - auto invalid_listpeers = [this](Jsmn::Object result) { + auto invalid_listpeerchannels = [this](Jsmn::Object result) { auto os = std::ostringstream(); os << result; return Boss::log( bus, Error , "ListpeersAnnouncer: invalid result from " - "`listpeers`." + "`listpeerchannels`." ); }; - auto do_listpeers = [ this - , invalid_listpeers + auto do_listpeerchannels = [ this + , invalid_listpeerchannels ](bool initial) { - return rpc->command("listpeers" + return rpc->command("listpeerchannels" , Json::Out::empty_object() ).then([ this , initial - , invalid_listpeers + , invalid_listpeerchannels ](Jsmn::Object result) { - if (!result.is_object() || !result.has("peers")) - return invalid_listpeers(std::move(result)); - auto peers = result["peers"]; - if (!peers.is_array()) - return invalid_listpeers(std::move(result)); + if (!result.is_object() || !result.has("channels")) + return invalid_listpeerchannels(std::move(result)); + auto channels = result["channels"]; + if (!channels.is_array()) + return invalid_listpeerchannels(std::move(result)); + + Boss::Mod::ConstructedListpeers cpeers; + for (auto c : channels) { + auto id = Ln::NodeId(std::string(c["peer_id"])); + auto connected = c["peer_connected"]; + cpeers[id].connected = connected.is_boolean() && bool(connected); + cpeers[id].channels.push_back(c); + } return bus.raise(Msg::ListpeersResult{ - std::move(peers), initial + std::move(cpeers), initial }); }); }; bus.subscribe([do_listpeers](Msg::Init const& init) { - return Boss::concurrent(do_listpeers(true)); + >([do_listpeerchannels](Msg::Init const& init) { + return Boss::concurrent(do_listpeerchannels(true)); }); bus.subscribe([do_listpeers](Msg::Timer10Minutes const& _) { - return Boss::concurrent(do_listpeers(false)); + >([do_listpeerchannels](Msg::Timer10Minutes const& _) { + return Boss::concurrent(do_listpeerchannels(false)); }); } @@ -60,5 +76,4 @@ ListpeersAnnouncer::ListpeersAnnouncer(S::Bus& bus_) { start(); } ListpeersAnnouncer::~ListpeersAnnouncer() =default; - }} diff --git a/Boss/Mod/ListpeersAnnouncer.hpp b/Boss/Mod/ListpeersAnnouncer.hpp index 18158d9a8..8e00f5a36 100644 --- a/Boss/Mod/ListpeersAnnouncer.hpp +++ b/Boss/Mod/ListpeersAnnouncer.hpp @@ -12,6 +12,11 @@ namespace Boss { namespace Mod { * * @brief announces `listpeers` at `init` and * every 10 minutes thereafter. + * + * IMPORTANT - this msg is no longer directly obtained from + * `listpeers` but rather is constructed by "convolving" the value + * from `listpeerchannels`. Specifically, the top level `peer` + * objects are non-standard and only have what CLBOSS uses ... */ class ListpeersAnnouncer { private: diff --git a/Boss/Mod/NodeBalanceSwapper.cpp b/Boss/Mod/NodeBalanceSwapper.cpp index 070a8d634..639a0f570 100644 --- a/Boss/Mod/NodeBalanceSwapper.cpp +++ b/Boss/Mod/NodeBalanceSwapper.cpp @@ -48,8 +48,8 @@ class NodeBalanceSwapper::Impl : ModG::Swapper { auto total_recv = Ln::Amount::sat(0); auto total_send = Ln::Amount::sat(0); try { - for (auto peer : m.peers) { - auto channels = peer["channels"]; + for (auto peer : m.cpeers) { + auto channels = peer.second.channels; for (auto chan : channels) { /* Skip non-active channels. */ @@ -69,7 +69,7 @@ class NodeBalanceSwapper::Impl : ModG::Swapper { } catch (Jsmn::TypeError const&) { /* Should never happen.... */ auto os = std::ostringstream(); - os << m.peers; + os << m.cpeers; return Boss::log( bus, Error , "NodeBalanceSwapper: " "Unexpected listpeers: %s" diff --git a/Boss/Mod/PeerFromScidMapper.cpp b/Boss/Mod/PeerFromScidMapper.cpp index 59b6e9012..a6d225143 100644 --- a/Boss/Mod/PeerFromScidMapper.cpp +++ b/Boss/Mod/PeerFromScidMapper.cpp @@ -26,17 +26,9 @@ class PeerFromScidMapper::Impl { bus.subscribe([this](Msg::ListpeersResult const& m) { auto tmp = std::map(); - for (auto p : m.peers) { - if (!p.has("id")) - continue; - auto node_j = p["id"]; - if (!node_j.is_string()) - continue; - auto node_s = std::string(node_j); - if (!Ln::NodeId::valid_string(node_s)) - continue; - auto node = Ln::NodeId(node_s); - auto cs = p["channels"]; + for (auto p : m.cpeers) { + auto node = p.first; + auto cs = p.second.channels; for (auto c : cs) { if (!c.has("short_channel_id")) continue; diff --git a/Boss/Mod/PeerJudge/DataGatherer.cpp b/Boss/Mod/PeerJudge/DataGatherer.cpp index 7716cf908..7746248b7 100644 --- a/Boss/Mod/PeerJudge/DataGatherer.cpp +++ b/Boss/Mod/PeerJudge/DataGatherer.cpp @@ -52,7 +52,7 @@ class DataGatherer::Impl { if (running) return Ev::lift(); - auto peers = m.peers; + auto peers = m.cpeers; running = true; auto code = Ev::lift().then([this, peers]() { @@ -64,17 +64,15 @@ class DataGatherer::Impl { return Boss::concurrent(code); }); } - Ev::Io process_peers(Jsmn::Object const& peers) { + Ev::Io process_peers(Boss::Mod::ConstructedListpeers const& peers) { auto infos = std::make_shared>(); return Ev::lift().then([this, peers, infos]() { try { for (auto p : peers) { - auto id = Ln::NodeId(std::string( - p["id"] - )); + auto id = p.first; auto total = Ln::Amount::sat(0); - for (auto c : p["channels"]) { + for (auto c : p.second.channels) { auto state = std::string( c["state"] ); diff --git a/Boss/Msg/ListpeersResult.hpp b/Boss/Msg/ListpeersResult.hpp index fd832d42e..d1d19b6aa 100644 --- a/Boss/Msg/ListpeersResult.hpp +++ b/Boss/Msg/ListpeersResult.hpp @@ -1,7 +1,7 @@ #ifndef BOSS_MSG_LISTPEERSRESULT_HPP #define BOSS_MSG_LISTPEERSRESULT_HPP -#include"Jsmn/Object.hpp" +#include"Boss/Mod/ConstructedListpeers.hpp" namespace Boss { namespace Msg { @@ -10,11 +10,17 @@ namespace Boss { namespace Msg { * @brief announced during `init` to inform all * modules about `listpeers` command result. * Also announced every 10 minutes. + * + * IMPORTANT - this msg is no longer directly obtained from + * `listpeers` but rather is constructed by "convolving" the value + * from `listpeerchannels`. Specifically, the top level `peer` + * objects are non-standard and only have what CLBOSS uses ... */ + struct ListpeersResult { - /* Known to be an array. */ - Jsmn::Object peers; - /* Whether this listpeers was performed during `init` + Boss::Mod::ConstructedListpeers cpeers; + + /* Whether this listpeerchannelss was performed during `init` * or on the 10-minute timer. */ bool initial; diff --git a/Makefile.am b/Makefile.am index 4ae14a735..916fadebc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -139,6 +139,8 @@ libclboss_la_SOURCES = \ Boss/Mod/ConnectFinderByHardcode.hpp \ Boss/Mod/Connector.cpp \ Boss/Mod/Connector.hpp \ + Boss/Mod/ConstructedListpeers.cpp \ + Boss/Mod/ConstructedListpeers.hpp \ Boss/Mod/Dowser.cpp \ Boss/Mod/Dowser.hpp \ Boss/Mod/EarningsRebalancer.cpp \ diff --git a/tests/boss/test_earningsrebalancer.cpp b/tests/boss/test_earningsrebalancer.cpp index 25ac7da55..b4e2c2e1c 100644 --- a/tests/boss/test_earningsrebalancer.cpp +++ b/tests/boss/test_earningsrebalancer.cpp @@ -3,6 +3,7 @@ #include"Boss/Mod/EarningsRebalancer.hpp" #include"Boss/Mod/RebalanceUnmanager.hpp" #include"Boss/Msg/CommandRequest.hpp" +#include"Boss/Mod/ConstructedListpeers.hpp" #include"Boss/Msg/ListpeersResult.hpp" #include"Boss/Msg/RequestEarningsInfo.hpp" #include"Boss/Msg/RequestMoveFunds.hpp" @@ -84,12 +85,12 @@ class Model { .end_object(); } arr.end_array(); - + auto is = std::istringstream(j.output()); auto js = Jsmn::Object(); is >> js; return bus.raise(Boss::Msg::ListpeersResult{ - std::move(js), false + std::move(Boss::Mod::convert_legacy_listpeers(js)), false }); }); } diff --git a/tests/boss/test_forwardfeemonitor.cpp b/tests/boss/test_forwardfeemonitor.cpp index b3cd8eddf..fefa16526 100644 --- a/tests/boss/test_forwardfeemonitor.cpp +++ b/tests/boss/test_forwardfeemonitor.cpp @@ -103,7 +103,7 @@ int main() { /* Give the peers. */ return bus.raise(Boss::Msg::ListpeersResult{ - parse_json(listpeers_result)["peers"], true + Boss::Mod::convert_legacy_listpeers(parse_json(listpeers_result)["peers"]), true }); }).then([&]() { diff --git a/tests/boss/test_initialrebalancer.cpp b/tests/boss/test_initialrebalancer.cpp index 3f89791b1..12cae9dbd 100644 --- a/tests/boss/test_initialrebalancer.cpp +++ b/tests/boss/test_initialrebalancer.cpp @@ -96,7 +96,8 @@ Ev::Io listpeers_result(S::Bus& bus, std::string const& json) { auto js = Jsmn::Object(); is >> js; - return bus.raise(ListpeersResult{std::move(js), false}); + return bus.raise(ListpeersResult{std::move( + Boss::Mod::convert_legacy_listpeers(js)), false}); } Ev::Io multiyield() { diff --git a/tests/boss/test_jitrebalancer.cpp b/tests/boss/test_jitrebalancer.cpp index 588aa42bf..aeca10691 100644 --- a/tests/boss/test_jitrebalancer.cpp +++ b/tests/boss/test_jitrebalancer.cpp @@ -88,6 +88,61 @@ auto const listpeers_result = R"JSON( } )JSON"; +auto const listpeerchannels_result = R"JSON( +{ + "channels": [ + { + "state": "CHANNELD_NORMAL", + "to_us_msat": "750000000msat", + "total_msat": "1000000000msat", + "short_channel_id": "1000x1x0", + "peer_id": "020000000000000000000000000000000000000000000000000000000000000000", + "peer_connected": true + }, + { + "state": "CHANNELD_NORMAL", + "to_us_msat": "0msat", + "total_msat": "1000000000msat", + "short_channel_id": "1000x1x1", + "peer_id": "020000000000000000000000000000000000000000000000000000000000000001", + "peer_connected": true + }, + { + "state": "ONCHAIN", + "to_us_msat": "1000000000msat", + "total_msat": "1000000000msat", + "short_channel_id": "999x1x1", + "peer_id": "020000000000000000000000000000000000000000000000000000000000000001", + "peer_connected": true + }, + { + "state": "CHANNELD_NORMAL", + "to_us_msat": "80000msat", + "total_msat": "1000000000msat", + "short_channel_id": "1000x1x2", + "peer_id": "020000000000000000000000000000000000000000000000000000000000000002", + "peer_connected": true + }, + { + "state": "CHANNELD_NORMAL", + "to_us_msat": "900000000msat", + "total_msat": "1000000000msat", + "short_channel_id": "9999x1x0", + "peer_id": "02000000000000000000000000000000000000000000000000000000000000FF00", + "peer_connected": true + }, + { + "state": "CHANNELD_NORMAL", + "to_us_msat": "900000000msat", + "total_msat": "1000000000msat", + "short_channel_id": "9999x1x1", + "peer_id": "02000000000000000000000000000000000000000000000000000000000000FF01", + "peer_connected": true + } + ] +} +)JSON"; + class DummyEarningsManager { private: S::Bus& bus; @@ -140,10 +195,13 @@ class DummyRpc { ](Boss::Msg::RequestRpcCommand const& m) { if (m.command == "listpeers") { return respond(m.requester, listpeers_result); + } else if (m.command == "listpeerchannels") { + return respond(m.requester, listpeerchannels_result); } else if (m.command == "feerates") { return respond(m.requester, feerates_result); } else { /* Unmocked command. */ + std::cerr << "COMMAND WAS " << m.command << std::endl; assert(0); return Ev::lift(); } @@ -286,7 +344,7 @@ int main() { auto res = parse_json(listpeers_result); auto peers = res["peers"]; return bus.raise(Boss::Msg::ListpeersResult{ - std::move(peers), true + std::move(Boss::Mod::convert_legacy_listpeers(peers)), true }); }).then([&]() { diff --git a/tests/boss/test_peerjudge_datagatherer.cpp b/tests/boss/test_peerjudge_datagatherer.cpp index d034238b3..84f179bb4 100644 --- a/tests/boss/test_peerjudge_datagatherer.cpp +++ b/tests/boss/test_peerjudge_datagatherer.cpp @@ -85,11 +85,12 @@ int main() { /* ListpeersResult model. */ auto listpeers = [&](std::string peers_json) { + std::cerr << "LISTPEERS: " << peers_json << std::endl; auto is = std::stringstream(std::move(peers_json)); auto peers = Jsmn::Object(); is >> peers; return bus.raise(Boss::Msg::ListpeersResult{ - peers, false + Boss::Mod::convert_legacy_listpeers(peers), false }).then([]() { /* Let object run. */ return yield256(); @@ -294,6 +295,12 @@ int main() { } ] } + , { "id": "020000000000000000000000000000000000000000000000000000000000000002" + , "channels": [ { "stateXX": "CHANNELD_NORMAL" + , "total_msat": "1500000000msat" + } + ] + } ] )JSON"); }).then([&]() {