diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c66a9e3..6057d768 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,8 @@ target_sources(reporting PRIVATE handlers/RPCHelpers.cpp handlers/AccountTx.cpp handlers/LedgerData.cpp - handlers/BookOffers.cpp) + handlers/BookOffers.cpp + handlers/Ledger.cpp) message(${Boost_LIBRARIES}) diff --git a/handlers/Ledger.cpp b/handlers/Ledger.cpp new file mode 100644 index 00000000..d0baedad --- /dev/null +++ b/handlers/Ledger.cpp @@ -0,0 +1,53 @@ +#include +#include + +boost::json::object +doLedger(boost::json::object const& request, BackendInterface const& backend) +{ + boost::json::object response; + if (!request.contains("ledger_index")) + { + response["error"] = "Please specify a ledger index"; + return response; + } + uint32_t ledgerSequence = request.at("ledger_index").as_int64(); + + auto lgrInfo = backend.fetchLedgerBySequence(ledgerSequence); + if (!lgrInfo) + { + response["error"] = "ledger not found"; + return response; + } + boost::json::object header; + header["ledger_sequence"] = lgrInfo->seq; + header["ledger_hash"] = ripple::strHex(lgrInfo->hash); + header["txns_hash"] = ripple::strHex(lgrInfo->txHash); + header["state_hash"] = ripple::strHex(lgrInfo->accountHash); + header["parent_hash"] = ripple::strHex(lgrInfo->parentHash); + header["total_coins"] = ripple::to_string(lgrInfo->drops); + header["close_flags"] = lgrInfo->closeFlags; + + // Always show fields that contribute to the ledger hash + header["parent_close_time"] = + lgrInfo->parentCloseTime.time_since_epoch().count(); + header["close_time"] = lgrInfo->closeTime.time_since_epoch().count(); + header["close_time_resolution"] = lgrInfo->closeTimeResolution.count(); + auto txns = backend.fetchAllTransactionsInLedger(ledgerSequence); + response["transactions"] = boost::json::value(boost::json::array_kind); + boost::json::array& jsonTransactions = + response.at("transactions").as_array(); + + std::transform( + std::move_iterator(txns.begin()), + std::move_iterator(txns.end()), + std::back_inserter(jsonTransactions), + [](auto obj) { + boost::json::object entry; + auto [sttx, meta] = deserializeTxPlusMeta(obj); + entry["transaction"] = getJson(*sttx); + entry["meta"] = getJson(*meta); + return entry; + }); + + return response; +} diff --git a/reporting/BackendInterface.h b/reporting/BackendInterface.h index 3fd78e0d..287605a2 100644 --- a/reporting/BackendInterface.h +++ b/reporting/BackendInterface.h @@ -54,6 +54,9 @@ public: virtual std::optional fetchTransaction(ripple::uint256 const& hash) const = 0; + virtual std::vector + fetchAllTransactionsInLedger(uint32_t ledgerSequence) const = 0; + virtual LedgerPage fetchLedgerPage( std::optional const& cursor, diff --git a/reporting/CassandraBackend.cpp b/reporting/CassandraBackend.cpp index 2757e83c..fceb51fb 100644 --- a/reporting/CassandraBackend.cpp +++ b/reporting/CassandraBackend.cpp @@ -225,6 +225,23 @@ CassandraBackend::fetchLedgerRange() const } return range; } +std::vector +CassandraBackend::fetchAllTransactionsInLedger(uint32_t ledgerSequence) const +{ + CassandraStatement statement{selectAllTransactionsInLedger_}; + CassandraResult result = executeSyncRead(statement); + if (!result) + { + BOOST_LOG_TRIVIAL(error) << __func__ << " - no rows"; + return {}; + } + std::vector txns; + do + { + txns.push_back({result.getBytes(), result.getBytes()}); + } while (result.nextRow()); + return txns; +} void CassandraBackend::open() @@ -455,9 +472,16 @@ CassandraBackend::open() if (!executeSimpleStatement(query.str())) continue; query = {}; - query << "CREATE TABLE IF NOT EXISTS " << tablePrefix << "transactions" - << " ( hash blob PRIMARY KEY, sequence bigint, transaction " - "blob, metadata blob)"; + query + << "CREATE TABLE IF NOT EXISTS " << tablePrefix << "transactions" + << " ( hash blob PRIMARY KEY, ledger_sequence bigint, transaction " + "blob, metadata blob)"; + if (!executeSimpleStatement(query.str())) + continue; + + query = {}; + query << "CREATE INDEX ON " << tablePrefix + << "transactions(ledger_sequence)"; if (!executeSimpleStatement(query.str())) continue; @@ -558,9 +582,10 @@ CassandraBackend::open() continue; query = {}; - query << "INSERT INTO " << tablePrefix << "transactions" - << " (hash, sequence, transaction, metadata) VALUES (?, ?, " - "?, ?)"; + query + << "INSERT INTO " << tablePrefix << "transactions" + << " (hash, ledger_sequence, transaction, metadata) VALUES (?, ?, " + "?, ?)"; if (!insertTransaction_.prepareStatement(query, session_.get())) continue; @@ -602,6 +627,14 @@ CassandraBackend::open() if (!selectTransaction_.prepareStatement(query, session_.get())) continue; + query = {}; + query << "SELECT transaction,metadata FROM " << tablePrefix + << "transactions" + << " WHERE ledger_sequence = ?"; + if (!selectAllTransactionsInLedger_.prepareStatement( + query, session_.get())) + continue; + query = {}; query << "SELECT key FROM " << tablePrefix << "keys " << " WHERE TOKEN(key) >= ? and created <= ?" diff --git a/reporting/CassandraBackend.h b/reporting/CassandraBackend.h index 4044d5c9..e63004b0 100644 --- a/reporting/CassandraBackend.h +++ b/reporting/CassandraBackend.h @@ -503,6 +503,7 @@ private: CassandraPreparedStatement insertObject_; CassandraPreparedStatement insertTransaction_; CassandraPreparedStatement selectTransaction_; + CassandraPreparedStatement selectAllTransactionsInLedger_; CassandraPreparedStatement selectObject_; CassandraPreparedStatement selectLedgerPageKeys_; CassandraPreparedStatement selectLedgerPage_; @@ -760,6 +761,9 @@ public: std::optional fetchLedgerRange() const override; + std::vector + fetchAllTransactionsInLedger(uint32_t ledgerSequence) const override; + // Synchronously fetch the object with key key and store the result in // pno // @param key the key of the object diff --git a/reporting/Pg.cpp b/reporting/Pg.cpp index d1dd6ba2..bcb075f0 100644 --- a/reporting/Pg.cpp +++ b/reporting/Pg.cpp @@ -761,6 +761,9 @@ CREATE TABLE IF NOT EXISTS transactions ( transaction bytea NOT NULL, metadata bytea NOT NULL ); +-- Index for lookups by ledger hash. +CREATE INDEX IF NOT EXISTS ledgers_ledger_seq_idx ON transactions + USING hash (ledger_seq); -- Table that maps accounts to transactions affecting them. Deletes from the -- ledger table cascade here based on ledger_seq. diff --git a/reporting/PostgresBackend.cpp b/reporting/PostgresBackend.cpp index 048fccbf..3e811fb9 100644 --- a/reporting/PostgresBackend.cpp +++ b/reporting/PostgresBackend.cpp @@ -285,6 +285,31 @@ PostgresBackend::fetchTransaction(ripple::uint256 const& hash) const return {}; } +std::vector +PostgresBackend::fetchAllTransactionsInLedger(uint32_t ledgerSequence) const +{ + PgQuery pgQuery(pgPool_); + std::stringstream sql; + sql << "SELECT transaction, metadata, ledger_seq FROM transactions WHERE " + << "ledger_seq = " << std::to_string(ledgerSequence); + auto res = pgQuery(sql.str().data()); + if (size_t numRows = checkResult(res, 3)) + { + std::vector txns; + for (size_t i = 0; i < numRows; ++i) + { + char const* txn = res.c_str(0, 0); + char const* metadata = res.c_str(0, 1); + std::string_view txnView{txn}; + std::string_view metadataView{metadata}; + txns.push_back( + {{txnView.front(), txnView.back()}, + {metadataView.front(), metadataView.back()}}); + } + return txns; + } + return {}; +} LedgerPage PostgresBackend::fetchLedgerPage( diff --git a/reporting/PostgresBackend.h b/reporting/PostgresBackend.h index 6172663e..a7a52301 100644 --- a/reporting/PostgresBackend.h +++ b/reporting/PostgresBackend.h @@ -34,6 +34,9 @@ public: std::optional fetchTransaction(ripple::uint256 const& hash) const override; + std::vector + fetchAllTransactionsInLedger(uint32_t ledgerSequence) const override; + LedgerPage fetchLedgerPage( std::optional const& cursor, diff --git a/websocket_server_async.cpp b/websocket_server_async.cpp index 73cc3171..4ab6ad60 100644 --- a/websocket_server_async.cpp +++ b/websocket_server_async.cpp @@ -69,6 +69,8 @@ boost::json::object doBookOffers( boost::json::object const& request, BackendInterface const& backend); +boost::json::object +doLedger(boost::json::object const& request, BackendInterface const& backend); boost::json::object buildResponse( @@ -87,6 +89,7 @@ buildResponse( return doAccountTx(request, backend); break; case ledger: + return doLedgerData(request, backend); break; case ledger_data: return doLedgerData(request, backend);