#include #include #include template inline void sendToSubscribers(std::string const& message, T& subscribers, boost::asio::io_context::strand& strand) { boost::asio::post(strand, [&subscribers, message](){ for (auto it = subscribers.begin(); it != subscribers.end();) { auto& session = *it; if (session->dead()) { it = subscribers.erase(it); } else { session->send(message); ++it; } } }); } template inline void addSession(std::shared_ptr session, T& subscribers, boost::asio::io_context::strand& strand) { boost::asio::post(strand, [&subscribers, s = std::move(session)](){ subscribers.emplace(s); }); } template inline void removeSession(std::shared_ptr session, T& subscribers, boost::asio::io_context::strand& strand) { boost::asio::post(strand, [&subscribers, s = std::move(session)](){ subscribers.erase(s); }); } void Subscription::subscribe(std::shared_ptr const& session) { addSession(session, subscribers_, strand_); } void Subscription::unsubscribe(std::shared_ptr const& session) { removeSession(session, subscribers_, strand_); } void Subscription::publish(std::string const& message) { sendToSubscribers(message, subscribers_, strand_); } template void SubscriptionMap::subscribe( std::shared_ptr const& session, Key const& account) { addSession(session, subscribers_[account], strand_); } template void SubscriptionMap::unsubscribe( std::shared_ptr const& session, Key const& account) { removeSession(session, subscribers_[account], strand_); } template void SubscriptionMap::publish( std::string const& message, Key const& account) { sendToSubscribers(message, subscribers_[account], strand_); } boost::json::object getLedgerPubMessage( ripple::LedgerInfo const& lgrInfo, ripple::Fees const& fees, std::string const& ledgerRange, std::uint32_t txnCount) { boost::json::object pubMsg; pubMsg["type"] = "ledgerClosed"; pubMsg["ledger_index"] = lgrInfo.seq; pubMsg["ledger_hash"] = to_string(lgrInfo.hash); pubMsg["ledger_time"] = lgrInfo.closeTime.time_since_epoch().count(); pubMsg["fee_ref"] = RPC::toBoostJson(fees.units.jsonClipped()); pubMsg["fee_base"] = RPC::toBoostJson(fees.base.jsonClipped()); pubMsg["reserve_base"] = RPC::toBoostJson(fees.reserve.jsonClipped()); pubMsg["reserve_inc"] = RPC::toBoostJson(fees.increment.jsonClipped()); pubMsg["validated_ledgers"] = ledgerRange; pubMsg["txn_count"] = txnCount; return pubMsg; } boost::json::object SubscriptionManager::subLedger(std::shared_ptr& session) { ledgerSubscribers_.subscribe(session); auto ledgerRange = backend_->fetchLedgerRange(); assert(ledgerRange); auto lgrInfo = backend_->fetchLedgerBySequence(ledgerRange->maxSequence); assert(lgrInfo); std::optional fees; fees = backend_->fetchFees(lgrInfo->seq); assert(fees); std::string range = std::to_string(ledgerRange->minSequence) + "-" + std::to_string(ledgerRange->maxSequence); auto pubMsg = getLedgerPubMessage(*lgrInfo, *fees, range, 0); pubMsg.erase("txn_count"); pubMsg.erase("type"); return pubMsg; } void SubscriptionManager::unsubLedger(std::shared_ptr& session) { ledgerSubscribers_.unsubscribe(session); } void SubscriptionManager::subTransactions(std::shared_ptr& session) { txSubscribers_.subscribe(session); } void SubscriptionManager::unsubTransactions(std::shared_ptr& session) { txSubscribers_.unsubscribe(session); } void SubscriptionManager::subAccount( ripple::AccountID const& account, std::shared_ptr& session) { accountSubscribers_.subscribe(session, account); } void SubscriptionManager::unsubAccount( ripple::AccountID const& account, std::shared_ptr& session) { accountSubscribers_.unsubscribe(session, account); } void SubscriptionManager::subBook( ripple::Book const& book, std::shared_ptr& session) { bookSubscribers_.subscribe(session, book); } void SubscriptionManager::unsubBook( ripple::Book const& book, std::shared_ptr& session) { bookSubscribers_.unsubscribe(session, book); } void SubscriptionManager::pubLedger( ripple::LedgerInfo const& lgrInfo, ripple::Fees const& fees, std::string const& ledgerRange, std::uint32_t txnCount) { ledgerSubscribers_.publish(boost::json::serialize( getLedgerPubMessage(lgrInfo, fees, ledgerRange, txnCount))); } void SubscriptionManager::pubTransaction( Backend::TransactionAndMetadata const& blobs, ripple::LedgerInfo const& lgrInfo) { auto [tx, meta] = RPC::deserializeTxPlusMeta(blobs, lgrInfo.seq); boost::json::object pubObj; pubObj["transaction"] = RPC::toJson(*tx); pubObj["meta"] = RPC::toJson(*meta); RPC::insertDeliveredAmount(pubObj["meta"].as_object(), tx, meta); pubObj["type"] = "transaction"; pubObj["validated"] = true; pubObj["status"] = "closed"; pubObj["ledger_index"] = lgrInfo.seq; pubObj["ledger_hash"] = ripple::strHex(lgrInfo.hash); pubObj["transaction"].as_object()["date"] = lgrInfo.closeTime.time_since_epoch().count(); pubObj["engine_result_code"] = meta->getResult(); std::string token; std::string human; ripple::transResultInfo(meta->getResultTER(), token, human); pubObj["engine_result"] = token; pubObj["engine_result_message"] = human; if (tx->getTxnType() == ripple::ttOFFER_CREATE) { auto account = tx->getAccountID(ripple::sfAccount); auto amount = tx->getFieldAmount(ripple::sfTakerGets); if (account != amount.issue().account) { auto ownerFunds = RPC::accountFunds(*backend_, lgrInfo.seq, amount, account); pubObj["transaction"].as_object()["owner_funds"] = ownerFunds.getText(); } } std::string pubMsg{boost::json::serialize(pubObj)}; txSubscribers_.publish(pubMsg); auto journal = ripple::debugLog(); auto accounts = meta->getAffectedAccounts(journal); for (auto const& account : accounts) accountSubscribers_.publish(pubMsg, account); std::unordered_set alreadySent; for (auto const& node : meta->getNodes()) { if (!node.isFieldPresent(ripple::sfLedgerEntryType)) assert(false); if (node.getFieldU16(ripple::sfLedgerEntryType) == ripple::ltOFFER) { ripple::SField const* field = nullptr; // We need a field that contains the TakerGets and TakerPays // parameters. if (node.getFName() == ripple::sfModifiedNode) field = &ripple::sfPreviousFields; else if (node.getFName() == ripple::sfCreatedNode) field = &ripple::sfNewFields; else if (node.getFName() == ripple::sfDeletedNode) field = &ripple::sfFinalFields; if (field) { auto data = dynamic_cast( node.peekAtPField(*field)); if (data && data->isFieldPresent(ripple::sfTakerPays) && data->isFieldPresent(ripple::sfTakerGets)) { // determine the OrderBook ripple::Book book{ data->getFieldAmount(ripple::sfTakerGets).issue(), data->getFieldAmount(ripple::sfTakerPays).issue()}; if (alreadySent.find(book) == alreadySent.end()) { bookSubscribers_.publish(pubMsg, book); alreadySent.insert(book); } } } } } } void SubscriptionManager::forwardProposedTransaction( boost::json::object const& response) { std::string pubMsg{boost::json::serialize(response)}; txProposedSubscribers_.publish(pubMsg); auto transaction = response.at("transaction").as_object(); auto accounts = RPC::getAccountsFromTransaction(transaction); for (ripple::AccountID const& account : accounts) accountProposedSubscribers_.publish(pubMsg, account); } void SubscriptionManager::forwardManifest(boost::json::object const& response) { std::string pubMsg{boost::json::serialize(response)}; manifestSubscribers_.publish(pubMsg); } void SubscriptionManager::forwardValidation(boost::json::object const& response) { std::string pubMsg{boost::json::serialize(response)}; validationsSubscribers_.publish(std::move(pubMsg)); } void SubscriptionManager::subProposedAccount( ripple::AccountID const& account, std::shared_ptr& session) { accountProposedSubscribers_.subscribe(session, account); } void SubscriptionManager::subManifest(std::shared_ptr& session) { manifestSubscribers_.subscribe(session); } void SubscriptionManager::unsubManifest(std::shared_ptr& session) { manifestSubscribers_.unsubscribe(session); } void SubscriptionManager::subValidation(std::shared_ptr& session) { validationsSubscribers_.subscribe(session); } void SubscriptionManager::unsubValidation(std::shared_ptr& session) { validationsSubscribers_.unsubscribe(session); } void SubscriptionManager::unsubProposedAccount( ripple::AccountID const& account, std::shared_ptr& session) { accountProposedSubscribers_.unsubscribe(session, account); } void SubscriptionManager::subProposedTransactions(std::shared_ptr& session) { txProposedSubscribers_.subscribe(session); } void SubscriptionManager::unsubProposedTransactions(std::shared_ptr& session) { txProposedSubscribers_.unsubscribe(session); }