diff --git a/src/ripple/app/ledger/impl/LedgerToJson.cpp b/src/ripple/app/ledger/impl/LedgerToJson.cpp index 81e84303933..26f1ca2aefd 100644 --- a/src/ripple/app/ledger/impl/LedgerToJson.cpp +++ b/src/ripple/app/ledger/impl/LedgerToJson.cpp @@ -17,6 +17,7 @@ */ //============================================================================== +#include #include #include #include @@ -26,6 +27,7 @@ #include #include #include +#include namespace ripple { @@ -137,7 +139,6 @@ fillJsonTx( txn->getJson( JsonOptions::none, false, {std::optional(std::ref(hash))})); txJson[jss::hash] = hash; - // TODO set `txJson[jss::close_time_iso]` RPC::insertDeliverMax( txJson[jss::tx_json], txnType, fill.context->apiVersion); @@ -153,6 +154,17 @@ fillJsonTx( txn, {txn->getTransactionID(), fill.ledger.seq(), *stMeta}); } + + const bool validated = RPC::isValidated( + fill.context->ledgerMaster, fill.ledger, fill.context->app); + txJson[jss::validated] = validated; + if (validated) + { + if (auto close_time = + fill.context->ledgerMaster.getCloseTimeBySeq( + fill.ledger.seq())) + txJson[jss::close_time_iso] = to_string_iso(*close_time); + } } else { diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index 27dba963422..7c79c1b68e1 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -3124,6 +3124,8 @@ NetworkOPsImp::transJson( jvObj[jss::transaction][jss::date] = ledger->info().closeTime.time_since_epoch().count(); jvObj[jss::validated] = true; + if (auto close_time = m_ledgerMaster.getCloseTimeBySeq(ledger->seq())) + jvObj[jss::close_time_iso] = to_string_iso(*close_time); // WRITEME: Put the account next seq here } diff --git a/src/ripple/basics/chrono.h b/src/ripple/basics/chrono.h index f50d765d58f..21353e0873a 100644 --- a/src/ripple/basics/chrono.h +++ b/src/ripple/basics/chrono.h @@ -25,6 +25,7 @@ #include #include #include + #include #include #include @@ -43,8 +44,19 @@ using weeks = std::chrono:: /** Clock for measuring the network time. The epoch is January 1, 2000 - epoch_offset = days(10957); // 2000-01-01 + + epoch_offset + = date(2000-01-01) - date(1970-0-01) + = days(10957) + = seconds(946684800) */ + +constexpr static std::chrono::seconds epoch_offset = + date::sys_days{date::year{2000} / 1 / 1} - + date::sys_days{date::year{1970} / 1 / 1}; + +static_assert(epoch_offset.count() == 946684800); + class NetClock { public: @@ -71,7 +83,24 @@ to_string(NetClock::time_point tp) // 2000-01-01 00:00:00 UTC is 946684800s from 1970-01-01 00:00:00 UTC using namespace std::chrono; return to_string( - system_clock::time_point{tp.time_since_epoch() + 946684800s}); + system_clock::time_point{tp.time_since_epoch() + epoch_offset}); +} + +template +std::string +to_string_iso(date::sys_time tp) +{ + using namespace std::chrono; + return date::format("%FT%H:%M:%OSZ", tp); +} + +inline std::string +to_string_iso(NetClock::time_point tp) +{ + // 2000-01-01 00:00:00 UTC is 946684800s from 1970-01-01 00:00:00 UTC + using namespace std::chrono; + return to_string_iso( + system_clock::time_point{tp.time_since_epoch() + epoch_offset}); } /** A clock for measuring elapsed time. diff --git a/src/ripple/core/TimeKeeper.h b/src/ripple/core/TimeKeeper.h index 55970ec8227..e239a2f7565 100644 --- a/src/ripple/core/TimeKeeper.h +++ b/src/ripple/core/TimeKeeper.h @@ -22,6 +22,7 @@ #include #include + #include namespace ripple { @@ -37,7 +38,7 @@ class TimeKeeper : public beast::abstract_clock adjust(std::chrono::system_clock::time_point when) { return time_point(std::chrono::duration_cast( - when.time_since_epoch() - days(10957))); + when.time_since_epoch() - epoch_offset)); } public: diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index 281a410127d..8a701defad8 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -230,6 +230,8 @@ JSS(close); // out: BookChanges JSS(close_flags); // out: LedgerToJson JSS(close_time); // in: Application, out: NetworkOPs, // RCLCxPeerPos, LedgerToJson +JSS(close_time_iso); // out: Tx, NetworkOPs, TransactionEntry + // AccountTx, LedgerToJson JSS(close_time_estimated); // in: Application, out: LedgerToJson JSS(close_time_human); // out: LedgerToJson JSS(close_time_offset); // out: NetworkOPs diff --git a/src/ripple/rpc/handlers/AMMInfo.cpp b/src/ripple/rpc/handlers/AMMInfo.cpp index 11e124afb44..47d3e117e8e 100644 --- a/src/ripple/rpc/handlers/AMMInfo.cpp +++ b/src/ripple/rpc/handlers/AMMInfo.cpp @@ -66,7 +66,7 @@ to_iso8601(NetClock::time_point tp) return date::format( "%Y-%Om-%dT%H:%M:%OS%z", date::sys_time( - system_clock::time_point{tp.time_since_epoch() + 946684800s})); + system_clock::time_point{tp.time_since_epoch() + epoch_offset})); } Json::Value diff --git a/src/ripple/rpc/handlers/AccountTx.cpp b/src/ripple/rpc/handlers/AccountTx.cpp index 2a4ae3648e3..8f130daadf5 100644 --- a/src/ripple/rpc/handlers/AccountTx.cpp +++ b/src/ripple/rpc/handlers/AccountTx.cpp @@ -334,7 +334,6 @@ populateJsonResponse( jvObj[json_tx] = txn->getJson( JsonOptions::include_date, false, {std::ref(hash)}); jvObj[jss::hash] = hash; - // TODO set `jvObj[jss::close_time_iso]` } else jvObj[json_tx] = @@ -351,6 +350,11 @@ populateJsonResponse( insertDeliveredAmount( jvObj[jss::meta], context, txn, *txnMeta); insertNFTSyntheticInJson(jvObj, sttx, *txnMeta); + if (auto closeTime = + context.ledgerMaster.getCloseTimeBySeq( + txnMeta->getIndex())) + jvObj[jss::close_time_iso] = + to_string_iso(*closeTime); } } } diff --git a/src/ripple/rpc/handlers/TransactionEntry.cpp b/src/ripple/rpc/handlers/TransactionEntry.cpp index b7956168848..db826f57d37 100644 --- a/src/ripple/rpc/handlers/TransactionEntry.cpp +++ b/src/ripple/rpc/handlers/TransactionEntry.cpp @@ -17,6 +17,7 @@ */ //============================================================================== +#include #include #include #include @@ -77,7 +78,18 @@ doTransactionEntry(RPC::JsonContext& context) jvResult[jss::tx_json] = sttx->getJson(JsonOptions::none, false, {std::ref(hash)}); jvResult[jss::hash] = hash; - // TODO set `jvResult[jss::close_time_iso]` + + bool const validated = RPC::isValidated( + context.ledgerMaster, *lpLedger, context.app); + + jvResult[jss::validated] = validated; + if (validated) + { + if (auto closeTime = context.ledgerMaster.getCloseTimeBySeq( + lpLedger->seq())) + jvResult[jss::close_time_iso] = + to_string_iso(*closeTime); + } } else jvResult[jss::tx_json] = sttx->getJson(JsonOptions::none); diff --git a/src/ripple/rpc/handlers/Tx.cpp b/src/ripple/rpc/handlers/Tx.cpp index 3769f38a05d..16fa7baa8ba 100644 --- a/src/ripple/rpc/handlers/Tx.cpp +++ b/src/ripple/rpc/handlers/Tx.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,7 @@ struct TxResult std::variant, Blob> meta; bool validated = false; std::optional ctid; + std::optional closeTime; TxSearched searchedAll; }; @@ -140,6 +142,12 @@ doTxPostgres(RPC::Context& context, TxArgs const& args) *(args.hash), res.txn->getLedger(), *meta); } res.validated = true; + + auto const ledgerInfo = + context.app.getRelationalDatabase().getLedgerInfoByIndex( + locator.getLedgerSequence()); + res.closeTime = ledgerInfo->closeTime; + return {res, rpcSUCCESS}; } else @@ -269,6 +277,9 @@ doTxHelp(RPC::Context& context, TxArgs args) } result.validated = isValidated( context.ledgerMaster, ledger->info().seq, ledger->info().hash); + if (result.validated) + result.closeTime = + context.ledgerMaster.getCloseTimeBySeq(txn->getLedger()); // compute outgoing CTID uint32_t lgrSeq = ledger->info().seq; @@ -328,7 +339,9 @@ populateJsonResponse( context.apiVersion); } response[jss::hash] = hash; - // TODO set `response[jss::close_time_iso]` + if (result.closeTime) + response[jss::close_time_iso] = + to_string_iso(*result.closeTime); } else {