diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index 63a71b33..5794a57a 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -359,7 +359,7 @@ toJson(ripple::SLE const& sle) } boost::json::object -toJson(ripple::LedgerHeader const& lgrInfo, bool const binary) +toJson(ripple::LedgerHeader const& lgrInfo, bool const binary, std::uint32_t const apiVersion) { boost::json::object header; if (binary) { @@ -372,11 +372,16 @@ toJson(ripple::LedgerHeader const& lgrInfo, bool const binary) header[JS(close_time_resolution)] = lgrInfo.closeTimeResolution.count(); header[JS(close_time_iso)] = ripple::to_string_iso(lgrInfo.closeTime); header[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); - header[JS(ledger_index)] = std::to_string(lgrInfo.seq); header[JS(parent_close_time)] = lgrInfo.parentCloseTime.time_since_epoch().count(); header[JS(parent_hash)] = ripple::strHex(lgrInfo.parentHash); header[JS(total_coins)] = ripple::to_string(lgrInfo.drops); header[JS(transaction_hash)] = ripple::strHex(lgrInfo.txHash); + + if (apiVersion < 2u) { + header[JS(ledger_index)] = std::to_string(lgrInfo.seq); + } else { + header[JS(ledger_index)] = lgrInfo.seq; + } } header[JS(closed)] = true; return header; diff --git a/src/rpc/RPCHelpers.h b/src/rpc/RPCHelpers.h index 46afd0b0..a3754625 100644 --- a/src/rpc/RPCHelpers.h +++ b/src/rpc/RPCHelpers.h @@ -115,10 +115,11 @@ toJson(ripple::SLE const& sle); * * @param entry The LedgerHeader to convert. * @param binary Whether to convert in hex format. + * @param apiVersion The api version * @return The JSON object. */ boost::json::object -toJson(ripple::LedgerHeader const& info, bool binary); +toJson(ripple::LedgerHeader const& info, bool binary, std::uint32_t apiVersion); boost::json::object toJson(ripple::TxMeta const& meta); diff --git a/src/rpc/handlers/Ledger.cpp b/src/rpc/handlers/Ledger.cpp index b26c6a09..1296fecb 100644 --- a/src/rpc/handlers/Ledger.cpp +++ b/src/rpc/handlers/Ledger.cpp @@ -61,7 +61,7 @@ LedgerHandler::process(LedgerHandler::Input input, Context const& ctx) const auto const lgrInfo = std::get(lgrInfoOrStatus); Output output; - output.header = toJson(lgrInfo, input.binary); + output.header = toJson(lgrInfo, input.binary, ctx.apiVersion); if (input.transactions) { output.header[JS(transactions)] = boost::json::value(boost::json::array_kind); @@ -86,8 +86,13 @@ LedgerHandler::process(LedgerHandler::Input input, Context const& ctx) const if (!input.binary) { boost::json::object entry; entry[JS(validated)] = true; - // same with rippled, ledger_index is a string here - entry[JS(ledger_index)] = std::to_string(lgrInfo.seq); + + if (ctx.apiVersion < 2u) { + entry[JS(ledger_index)] = std::to_string(lgrInfo.seq); + } else { + entry[JS(ledger_index)] = lgrInfo.seq; + } + entry[JS(close_time_iso)] = isoTimeStr; entry[JS(ledger_hash)] = ripple::strHex(lgrInfo.hash); if (txn.contains(JS(hash))) { diff --git a/src/rpc/handlers/LedgerData.cpp b/src/rpc/handlers/LedgerData.cpp index e889a92d..643bbcf0 100644 --- a/src/rpc/handlers/LedgerData.cpp +++ b/src/rpc/handlers/LedgerData.cpp @@ -105,7 +105,7 @@ LedgerDataHandler::process(Input input, Context const& ctx) const // no marker -> first call, return header information if ((!input.marker) && (!input.diffMarker)) { - output.header = toJson(lgrInfo, input.binary); + output.header = toJson(lgrInfo, input.binary, ctx.apiVersion); } else { if (input.marker && !sharedPtrBackend_->fetchLedgerObject(*(input.marker), lgrInfo.seq, ctx.yield)) return Error{Status{RippledError::rpcINVALID_PARAMS, "markerDoesNotExist"}}; diff --git a/unittests/rpc/RPCHelpersTests.cpp b/unittests/rpc/RPCHelpersTests.cpp index 2791471d..0724a623 100644 --- a/unittests/rpc/RPCHelpersTests.cpp +++ b/unittests/rpc/RPCHelpersTests.cpp @@ -443,7 +443,7 @@ TEST_F(RPCHelpersTest, DeliverMaxAliasV2) TEST_F(RPCHelpersTest, LedgerHeaderJson) { auto const ledgerHeader = CreateLedgerInfo(INDEX1, 30); - auto const binJson = toJson(ledgerHeader, true); + auto const binJson = toJson(ledgerHeader, true, 1u); auto constexpr EXPECTBIN = R"({ "ledger_data": "0000001E000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", @@ -469,7 +469,35 @@ TEST_F(RPCHelpersTest, LedgerHeaderJson) INDEX1, 30 ); - auto json = toJson(ledgerHeader, false); + auto json = toJson(ledgerHeader, false, 1u); + // remove platform-related close_time_human field + json.erase(JS(close_time_human)); + EXPECT_EQ(json, boost::json::parse(EXPECTJSON)); +} + +TEST_F(RPCHelpersTest, LedgerHeaderJsonV2) +{ + auto const ledgerHeader = CreateLedgerInfo(INDEX1, 30); + + auto const EXPECTJSON = fmt::format( + R"({{ + "account_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "close_flags": 0, + "close_time": 0, + "close_time_resolution": 0, + "close_time_iso": "2000-01-01T00:00:00Z", + "ledger_hash": "{}", + "ledger_index": {}, + "parent_close_time": 0, + "parent_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "total_coins": "0", + "transaction_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "closed": true + }})", + INDEX1, + 30 + ); + auto json = toJson(ledgerHeader, false, 2u); // remove platform-related close_time_human field json.erase(JS(close_time_human)); EXPECT_EQ(json, boost::json::parse(EXPECTJSON)); diff --git a/unittests/rpc/handlers/LedgerDataTests.cpp b/unittests/rpc/handlers/LedgerDataTests.cpp index 5e03d654..71956868 100644 --- a/unittests/rpc/handlers/LedgerDataTests.cpp +++ b/unittests/rpc/handlers/LedgerDataTests.cpp @@ -262,9 +262,7 @@ TEST_F(RPCLedgerDataHandlerTest, NoMarker) mockBackendPtr->updateRange(RANGEMIN); // min mockBackendPtr->updateRange(RANGEMAX); // max - EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1); - ON_CALL(*rawBackendPtr, fetchLedgerBySequence(RANGEMAX, _)) - .WillByDefault(Return(CreateLedgerInfo(LEDGERHASH, RANGEMAX))); + EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).WillOnce(Return(CreateLedgerInfo(LEDGERHASH, RANGEMAX))); // when 'type' not specified, default to all the types auto limitLine = 5; @@ -284,8 +282,7 @@ TEST_F(RPCLedgerDataHandlerTest, NoMarker) bbs.push_back(ticket.getSerializer().peekData()); } - ON_CALL(*rawBackendPtr, doFetchLedgerObjects).WillByDefault(Return(bbs)); - EXPECT_CALL(*rawBackendPtr, doFetchLedgerObjects).Times(1); + EXPECT_CALL(*rawBackendPtr, doFetchLedgerObjects).WillOnce(Return(bbs)); runSpawn([&, this](auto yield) { auto const handler = AnyHandler{LedgerDataHandler{mockBackendPtr}}; @@ -293,7 +290,9 @@ TEST_F(RPCLedgerDataHandlerTest, NoMarker) auto output = handler.process(req, Context{yield}); ASSERT_TRUE(output); EXPECT_TRUE(output->as_object().contains("ledger")); - //"close_time_human" 's format depends on platform, might be sightly different + + // Note: the format of "close_time_human" depends on the platform and might differ per platform. It is however + // guaranteed to be consistent on the same platform. EXPECT_EQ(output->as_object().at("ledger").as_object().erase("close_time_human"), 1); EXPECT_EQ(output->as_object().at("ledger"), json::parse(ledgerExpected)); EXPECT_EQ(output->as_object().at("marker").as_string(), INDEX2); @@ -303,6 +302,64 @@ TEST_F(RPCLedgerDataHandlerTest, NoMarker) }); } +TEST_F(RPCLedgerDataHandlerTest, Version2) +{ + static auto const ledgerExpected = R"({ + "account_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "close_flags": 0, + "close_time": 0, + "close_time_resolution": 0, + "close_time_iso": "2000-01-01T00:00:00Z", + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "parent_close_time": 0, + "parent_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "total_coins": "0", + "transaction_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "closed": true + })"; + + auto const rawBackendPtr = dynamic_cast(mockBackendPtr.get()); + ASSERT_NE(rawBackendPtr, nullptr); + mockBackendPtr->updateRange(RANGEMIN); // min + mockBackendPtr->updateRange(RANGEMAX); // max + + EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).WillOnce(Return(CreateLedgerInfo(LEDGERHASH, RANGEMAX))); + + // When 'type' not specified, default to all the types + auto limitLine = 5; + auto limitTicket = 5; + + std::vector bbs; + EXPECT_CALL(*rawBackendPtr, doFetchSuccessorKey).Times(limitLine + limitTicket); + ON_CALL(*rawBackendPtr, doFetchSuccessorKey(_, RANGEMAX, _)).WillByDefault(Return(ripple::uint256{INDEX2})); + + while ((limitLine--) != 0) { + auto const line = CreateRippleStateLedgerObject("USD", ACCOUNT2, 10, ACCOUNT, 100, ACCOUNT2, 200, TXNID, 123); + bbs.push_back(line.getSerializer().peekData()); + } + + while ((limitTicket--) != 0) { + auto const ticket = CreateTicketLedgerObject(ACCOUNT, limitTicket); + bbs.push_back(ticket.getSerializer().peekData()); + } + + EXPECT_CALL(*rawBackendPtr, doFetchLedgerObjects).WillOnce(Return(bbs)); + + runSpawn([&, this](auto yield) { + auto const handler = AnyHandler{LedgerDataHandler{mockBackendPtr}}; + auto const req = json::parse(R"({"limit":10})"); + auto output = handler.process(req, Context{.yield = yield, .apiVersion = 2}); + ASSERT_TRUE(output); + EXPECT_TRUE(output->as_object().contains("ledger")); + + // Note: the format of "close_time_human" depends on the platform and might differ per platform. It is however + // guaranteed to be consistent on the same platform. + EXPECT_EQ(output->as_object().at("ledger").as_object().erase("close_time_human"), 1); + EXPECT_EQ(output->as_object().at("ledger"), json::parse(ledgerExpected)); + }); +} + TEST_F(RPCLedgerDataHandlerTest, TypeFilter) { static auto const ledgerExpected = R"({ @@ -359,7 +416,9 @@ TEST_F(RPCLedgerDataHandlerTest, TypeFilter) auto output = handler.process(req, Context{yield}); ASSERT_TRUE(output); EXPECT_TRUE(output->as_object().contains("ledger")); - //"close_time_human" 's format depends on platform, might be sightly different + + // Note: the format of "close_time_human" depends on the platform and might differ per platform. It is however + // guaranteed to be consistent on the same platform. EXPECT_EQ(output->as_object().at("ledger").as_object().erase("close_time_human"), 1); EXPECT_EQ(output->as_object().at("ledger"), json::parse(ledgerExpected)); EXPECT_EQ(output->as_object().at("marker").as_string(), INDEX2); @@ -422,7 +481,9 @@ TEST_F(RPCLedgerDataHandlerTest, TypeFilterAMM) auto output = handler.process(req, Context{yield}); ASSERT_TRUE(output); EXPECT_TRUE(output->as_object().contains("ledger")); - //"close_time_human" 's format depends on platform, might be sightly different + + // Note: the format of "close_time_human" depends on the platform and might differ per platform. It is however + // guaranteed to be consistent on the same platform. EXPECT_EQ(output->as_object().at("ledger").as_object().erase("close_time_human"), 1); EXPECT_EQ(output->as_object().at("ledger"), json::parse(ledgerExpected)); EXPECT_EQ(output->as_object().at("marker").as_string(), INDEX2); diff --git a/unittests/rpc/handlers/LedgerTests.cpp b/unittests/rpc/handlers/LedgerTests.cpp index fe015cc4..ac5d4ed2 100644 --- a/unittests/rpc/handlers/LedgerTests.cpp +++ b/unittests/rpc/handlers/LedgerTests.cpp @@ -623,7 +623,7 @@ TEST_F(RPCLedgerHandlerTest, TransactionsExpandNotBinaryV2) "close_time_resolution": 0, "closed": true, "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index": "30", + "ledger_index": 30, "parent_close_time": 0, "close_time_iso": "2000-01-01T00:00:00Z", "parent_hash": "0000000000000000000000000000000000000000000000000000000000000000", @@ -635,7 +635,7 @@ TEST_F(RPCLedgerHandlerTest, TransactionsExpandNotBinaryV2) "close_time_iso": "2000-01-01T00:00:00Z", "hash": "70436A9332F7CD928FAEC1A41269A677739D8B11F108CE23AE23CBF0C9113F8C", "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", - "ledger_index": "30", + "ledger_index": 30, "tx_json": { "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",