#include"Boss/Mod/OnchainFundsIgnorer.hpp" #include"Boss/Msg/CommandFail.hpp" #include"Boss/Msg/CommandRequest.hpp" #include"Boss/Msg/CommandResponse.hpp" #include"Boss/Msg/DbResource.hpp" #include"Boss/Msg/ManifestCommand.hpp" #include"Boss/Msg/Manifestation.hpp" #include"Boss/Msg/ProvideStatus.hpp" #include"Boss/Msg/RequestGetOnchainIgnoreFlag.hpp" #include"Boss/Msg/ResponseGetOnchainIgnoreFlag.hpp" #include"Boss/Msg/SolicitStatus.hpp" #include"Boss/log.hpp" #include"Ev/Io.hpp" #include"Ev/foreach.hpp" #include"Ev/now.hpp" #include"S/Bus.hpp" #include"Sqlite3.hpp" #include"Util/date.hpp" #include"Util/make_unique.hpp" #include namespace Boss { namespace Mod { class OnchainFundsIgnorer::Impl { private: S::Bus& bus; Sqlite3::Db db; std::vector pending_requesters; void start() { bus.subscribe([this](Msg::DbResource const& m) { db = m.db; auto pending = std::move(pending_requesters); return init_db() + Ev::foreach( [this](void* requester) { return get_flag(requester); } , std::move(pending) ); }); bus.subscribe([this ](Msg::RequestGetOnchainIgnoreFlag const& m) { if (!db) { pending_requesters.push_back(m.requester); return Ev::lift(); } return get_flag(m.requester); }); /* Manifestation. */ bus.subscribe([this](Msg::Manifestation const& _) { return bus.raise(Msg::ManifestCommand{ "clboss-ignore-onchain", "[hours]", "Ignore onchain funds for {hours} " "(default 24 hours).", false }) + bus.raise(Msg::ManifestCommand{ "clboss-notice-onchain", "", "Cancel previous clboss-ignore-onchain and " "resume managing onchain funds.", false }); }); /* Command handling. */ bus.subscribe([this](Msg::CommandRequest const& req) { auto id = req.id; auto succeed = [this, id]() { return bus.raise(Msg::CommandResponse{ id, Json::Out::empty_object() }); }; auto act = Ev::lift(); if (req.command == "clboss-ignore-onchain") { auto paramfail = [this, id]() { return bus.raise(Msg::CommandFail{ id, -32602, "Parameter failure", Json::Out::empty_object() }); }; auto hours = double(24.0); auto hours_j = Jsmn::Object(); auto params = req.params; if (params.is_object()) { if (params.size() > 1) return paramfail(); if (params.size() == 1) { if (!params.has("hours")) return paramfail(); hours_j = params["hours"]; } } else if (params.is_array()) { if (params.size() > 1) return paramfail(); if (params.size() == 1) hours_j = params[0]; } else return paramfail(); if (!hours_j.is_null()) { if (!hours_j.is_number()) return paramfail(); hours = (double) hours_j; } act += set_disableuntil( Ev::now() + (hours * 3600) ); act += Boss::log( bus, Info , "OnchainFundsIgnorer: " "Will ignore onchain funds " "for %f hours starting now." , hours ); act += succeed(); } else if (req.command == "clboss-notice-onchain") { act += set_disableuntil(0); act += Boss::log( bus, Info , "OnchainFundsIgnorer: " "Will notice onchain funds." ); act += succeed(); } return act; }); /* clboss-status */ bus.subscribe([this](Msg::SolicitStatus const& _) { return Ev::lift().then([this]() { return get_disableuntil(); }).then([this](double disableuntil) { auto now = Ev::now(); auto ignore = (now < disableuntil); auto comment = std::string(); if (ignore) comment = "`clboss-notice-onchain` " "to resume managing " "onchain funds."; else comment = "`clboss-ignore-onchain` " "to ignore onchain funds " "temporarily."; auto stats = Json::Out() .start_object() .field( "status" , ignore ? "ignore" : "notice" ) .field("now", now) .field( "now_human" , Util::date(now) ) .field( "disable_until" , disableuntil ) .field( "disable_until_human" , Util::date( disableuntil ) ) .field("comment", comment) .end_object() ; return bus.raise(Msg::ProvideStatus{ "should_monitor_onchain_funds", std::move(stats) }); }); }); } Ev::Io init_db() { return db.transact().then([](Sqlite3::Tx tx) { tx.query_execute(R"QRY( CREATE TABLE IF NOT EXISTS "OnchainFundsIgnorer" ( id INTEGER PRIMARY KEY , disableuntil REAL NOT NULL ); INSERT OR IGNORE INTO "OnchainFundsIgnorer" VALUES(1, 0.0); )QRY"); tx.commit(); return Ev::lift(); }); } Ev::Io get_flag(void* requester) { return get_disableuntil().then([requester, this ](double disableuntil) { auto now = Ev::now(); auto msg = Msg::ResponseGetOnchainIgnoreFlag(); msg.ignore = (now < disableuntil); if (msg.ignore) msg.seconds = disableuntil - now; else msg.seconds = 0; msg.requester = requester; return bus.raise(msg); }); } Ev::Io get_disableuntil() { return db.transact().then([](Sqlite3::Tx tx) { auto rv = double(); auto fetch = tx.query(R"QRY( SELECT disableuntil FROM "OnchainFundsIgnorer"; )QRY").execute(); for (auto& r : fetch) { rv = r.get(0); break; } tx.commit(); return Ev::lift(rv); }); } Ev::Io set_disableuntil(double disableuntil) { return db.transact().then([disableuntil](Sqlite3::Tx tx) { tx.query(R"QRY( UPDATE "OnchainFundsIgnorer" SET disableuntil = :disableuntil; )QRY") .bind(":disableuntil", disableuntil) .execute() ; tx.commit(); return Ev::lift(); }); } public: Impl() =delete; Impl(Impl&&) =delete; Impl(Impl const&) =delete; Impl(S::Bus& bus_) : bus(bus_) { start(); } }; OnchainFundsIgnorer::OnchainFundsIgnorer(OnchainFundsIgnorer&&) =default; OnchainFundsIgnorer::~OnchainFundsIgnorer() =default; OnchainFundsIgnorer::OnchainFundsIgnorer(S::Bus& bus) : pimpl(Util::make_unique(bus)) { } }}