#include"Boss/Mod/NodeBalanceSwapper.hpp" #include"Boss/ModG/Swapper.hpp" #include"Boss/Msg/ListpeersResult.hpp" #include"Boss/Msg/OnchainFee.hpp" #include"Boss/log.hpp" #include"Jsmn/Object.hpp" #include"Ln/Amount.hpp" #include"S/Bus.hpp" #include"Util/make_unique.hpp" #include namespace { /* The minimum percentage of total channel capacity that is * incoming. * * Cannot be higher than 50. */ auto const min_receivable_percent = double(35); /* If fees are high, we do not swap, ***unless*** our incoming * capacity is below this percentage, in which case we end up * triggering anyway. */ auto const min_receivable_percent_highfees = double(2.5); } namespace Boss { namespace Mod { class NodeBalanceSwapper::Impl : ModG::Swapper { private: std::unique_ptr fees_low; void start() { bus.subscribe([this](Msg::OnchainFee const& m) { if (!fees_low) fees_low = Util::make_unique(); *fees_low = m.fees_low; return Ev::lift(); }); bus.subscribe([this](Msg::ListpeersResult const& m) { if (!fees_low) /* Pretend fees are high temporarily. */ fees_low = Util::make_unique(false); /* Analyze the result. */ 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 chan : channels) { /* Skip non-active channels. */ if ( std::string(chan["state"]) != "CHANNELD_NORMAL" ) continue; /* Skip unpublished channels, * they cannot be used for * forwarding anyway. */ if (chan["private"]) continue; auto recv = Ln::Amount(); auto send = Ln::Amount(); compute_sendable_receivable( send, recv, chan ); total_recv += recv; total_send += send; } } } catch (Jsmn::TypeError const&) { /* Should never happen.... */ auto os = std::ostringstream(); os << m.peers; return Boss::log( bus, Error , "NodeBalanceSwapper: " "Unexpected listpeers: %s" , os.str().c_str() ); } /* Avoid division by 0. */ if ( total_recv == Ln::Amount::sat(0) && total_send == Ln::Amount::sat(0) ) return Ev::lift(); auto total = total_recv + total_send; auto recv_ratio = total_recv / total; auto recv_percent = recv_ratio * 100; if (recv_percent >= min_receivable_percent) /* Nothing to do. */ return Ev::lift(); auto f = [ this , total_recv , total_send , recv_percent ]( Ln::Amount& amount , std::string& why ) { amount = ( (total_send - total_recv) * 2 ) / 3; auto os = std::ostringstream(); os << "receivable = " << total_recv << ", " << "spendable = " << total_send << "; " << "%receivable = " << recv_percent << "; " ; if (*fees_low) { os << "sending funds onchain"; why = os.str(); return true; } else if ( recv_percent > min_receivable_percent_highfees ) { os << "but fees are high"; why = os.str(); return false; } else { os << "fees are high but we have " << "too little receivable" ; why = os.str(); return false; } }; return trigger_swap(f); }); } void compute_sendable_receivable( Ln::Amount& send , Ln::Amount& recv , Jsmn::Object const& c ) { auto to_us = Ln::Amount(std::string(c["to_us_msat"])); auto total = Ln::Amount(std::string(c["total_msat"])); auto their = total - to_us; for (auto h : c["htlcs"]) their -= Ln::Amount(std::string(h["amount_msat"])); auto our_reserve = Ln::Amount(std::string( c["our_reserve_msat"] )); auto their_reserve = Ln::Amount(std::string( c["their_reserve_msat"] )); send = to_us - our_reserve; recv = their - their_reserve; } public: explicit Impl(S::Bus& bus_) : ModG::Swapper( bus_ , "NodeBalanceSwapper" , "incoming_capacity_swapper" ) { start(); } }; NodeBalanceSwapper::NodeBalanceSwapper(NodeBalanceSwapper&&) =default; NodeBalanceSwapper& NodeBalanceSwapper::operator=(NodeBalanceSwapper&&) =default; NodeBalanceSwapper::~NodeBalanceSwapper() =default; NodeBalanceSwapper::NodeBalanceSwapper(S::Bus& bus) : pimpl(Util::make_unique(bus)) { } }}