mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-11 15:25:52 +00:00
@@ -102,6 +102,19 @@ BackendInterface::fetchLedgerObject(
|
||||
return dbObj;
|
||||
}
|
||||
|
||||
std::optional<std::uint32_t>
|
||||
BackendInterface::fetchLedgerObjectSeq(
|
||||
ripple::uint256 const& key,
|
||||
std::uint32_t const sequence,
|
||||
boost::asio::yield_context yield
|
||||
) const
|
||||
{
|
||||
auto seq = doFetchLedgerObjectSeq(key, sequence, yield);
|
||||
if (!seq)
|
||||
LOG(gLog.trace()) << "Missed in db";
|
||||
return seq;
|
||||
}
|
||||
|
||||
std::vector<Blob>
|
||||
BackendInterface::fetchLedgerObjects(
|
||||
std::vector<ripple::uint256> const& keys,
|
||||
|
||||
@@ -378,6 +378,19 @@ public:
|
||||
std::optional<Blob>
|
||||
fetchLedgerObject(ripple::uint256 const& key, std::uint32_t sequence, boost::asio::yield_context yield) const;
|
||||
|
||||
/**
|
||||
* @brief Fetches a specific ledger object sequence.
|
||||
*
|
||||
* Currently the real fetch happens in doFetchLedgerObjectSeq
|
||||
*
|
||||
* @param key The key of the object
|
||||
* @param sequence The ledger sequence to fetch for
|
||||
* @param yield The coroutine context
|
||||
* @return The sequence in unit32_t on success; nullopt otherwise
|
||||
*/
|
||||
std::optional<std::uint32_t>
|
||||
fetchLedgerObjectSeq(ripple::uint256 const& key, std::uint32_t sequence, boost::asio::yield_context yield) const;
|
||||
|
||||
/**
|
||||
* @brief Fetches all ledger objects by their keys.
|
||||
*
|
||||
@@ -407,6 +420,17 @@ public:
|
||||
virtual std::optional<Blob>
|
||||
doFetchLedgerObject(ripple::uint256 const& key, std::uint32_t sequence, boost::asio::yield_context yield) const = 0;
|
||||
|
||||
/**
|
||||
* @brief The database-specific implementation for fetching a ledger object sequence.
|
||||
*
|
||||
* @param key The key to fetch for
|
||||
* @param sequence The ledger sequence to fetch for
|
||||
* @param yield The coroutine context
|
||||
* @return The sequence in unit32_t on success; nullopt otherwise
|
||||
*/
|
||||
virtual std::optional<std::uint32_t>
|
||||
doFetchLedgerObjectSeq(ripple::uint256 const& key, std::uint32_t sequence, boost::asio::yield_context yield) const = 0;
|
||||
|
||||
/**
|
||||
* @brief The database-specific implementation for fetching ledger objects.
|
||||
*
|
||||
|
||||
@@ -567,6 +567,25 @@ public:
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::uint32_t>
|
||||
doFetchLedgerObjectSeq(ripple::uint256 const& key, std::uint32_t const sequence, boost::asio::yield_context yield)
|
||||
const override
|
||||
{
|
||||
LOG(log_.debug()) << "Fetching ledger object for seq " << sequence << ", key = " << ripple::to_string(key);
|
||||
if (auto const res = executor_.read(yield, schema_->selectObject, key, sequence); res) {
|
||||
if (auto const result = res->template get<Blob, std::uint32_t>(); result) {
|
||||
auto [_ ,seq] = result.value();
|
||||
return seq;
|
||||
} else {
|
||||
LOG(log_.debug()) << "Could not fetch ledger object sequence - no rows";
|
||||
}
|
||||
} else {
|
||||
LOG(log_.error()) << "Could not fetch ledger object sequence: " << res.error();
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<TransactionAndMetadata>
|
||||
fetchTransaction(ripple::uint256 const& hash, boost::asio::yield_context yield) const override
|
||||
{
|
||||
|
||||
@@ -162,17 +162,26 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input input, Context const& ctx)
|
||||
return Error{*status};
|
||||
|
||||
auto const lgrInfo = std::get<ripple::LedgerHeader>(lgrInfoOrStatus);
|
||||
auto const ledgerObject = sharedPtrBackend_->fetchLedgerObject(key, lgrInfo.seq, ctx.yield);
|
||||
auto output = LedgerEntryHandler::Output{};
|
||||
auto ledgerObject = sharedPtrBackend_->fetchLedgerObject(key, lgrInfo.seq, ctx.yield);
|
||||
|
||||
if (!ledgerObject || ledgerObject->empty()) {
|
||||
if (not input.includeDeleted)
|
||||
return Error{Status{"entryNotFound"}};
|
||||
auto const deletedSeq = sharedPtrBackend_->fetchLedgerObjectSeq(key, lgrInfo.seq, ctx.yield);
|
||||
if (!deletedSeq)
|
||||
return Error{Status{"entryNotFound"}};
|
||||
ledgerObject = sharedPtrBackend_->fetchLedgerObject(key, deletedSeq.value() - 1, ctx.yield);
|
||||
if (!ledgerObject || ledgerObject->empty())
|
||||
return Error{Status{"entryNotFound"}};
|
||||
output.deletedLedgerIndex = deletedSeq.value();
|
||||
}
|
||||
|
||||
ripple::STLedgerEntry const sle{ripple::SerialIter{ledgerObject->data(), ledgerObject->size()}, key};
|
||||
|
||||
if (input.expectedType != ripple::ltANY && sle.getType() != input.expectedType)
|
||||
return Error{Status{"unexpectedLedgerType"}};
|
||||
|
||||
auto output = LedgerEntryHandler::Output{};
|
||||
output.index = ripple::strHex(key);
|
||||
output.ledgerIndex = lgrInfo.seq;
|
||||
output.ledgerHash = ripple::strHex(lgrInfo.hash);
|
||||
@@ -220,6 +229,9 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, LedgerEntryHandl
|
||||
{JS(index), output.index},
|
||||
};
|
||||
|
||||
if (output.deletedLedgerIndex)
|
||||
object["deleted_ledger_index"] = *(output.deletedLedgerIndex);
|
||||
|
||||
if (output.nodeBinary) {
|
||||
object[JS(node_binary)] = *(output.nodeBinary);
|
||||
} else {
|
||||
@@ -337,6 +349,9 @@ tag_invoke(boost::json::value_to_tag<LedgerEntryHandler::Input>, boost::json::va
|
||||
input.oracleNode = parseOracleFromJson(jv.at(JS(oracle)));
|
||||
}
|
||||
|
||||
if (jsonObject.contains("include_deleted"))
|
||||
input.includeDeleted = jv.at("include_deleted").as_bool();
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ public:
|
||||
std::string ledgerHash;
|
||||
std::optional<boost::json::object> node;
|
||||
std::optional<std::string> nodeBinary;
|
||||
std::optional<uint32_t> deletedLedgerIndex;
|
||||
bool validated = true;
|
||||
};
|
||||
|
||||
@@ -103,6 +104,7 @@ public:
|
||||
std::optional<uint32_t> chainClaimId;
|
||||
std::optional<uint32_t> createAccountClaimId;
|
||||
std::optional<ripple::uint256> oracleNode;
|
||||
bool includeDeleted = false;
|
||||
};
|
||||
|
||||
using Result = HandlerReturnType<Output>;
|
||||
@@ -314,6 +316,7 @@ public:
|
||||
meta::WithCustomError{modifiers::ToNumber{}, Status(ClioError::rpcMALFORMED_ORACLE_DOCUMENT_ID)}},
|
||||
}}},
|
||||
{JS(ledger), check::Deprecated{}},
|
||||
{"include_deleted", validation::Type<bool>{}},
|
||||
};
|
||||
|
||||
return rpcSpec;
|
||||
|
||||
@@ -154,6 +154,13 @@ struct MockBackend : public BackendInterface {
|
||||
(const, override)
|
||||
);
|
||||
|
||||
MOCK_METHOD(
|
||||
std::optional<std::uint32_t>,
|
||||
doFetchLedgerObjectSeq,
|
||||
(ripple::uint256 const&, std::uint32_t const, boost::asio::yield_context),
|
||||
(const, override)
|
||||
);
|
||||
|
||||
MOCK_METHOD(
|
||||
std::vector<LedgerObject>,
|
||||
fetchLedgerDiff,
|
||||
|
||||
@@ -61,6 +61,8 @@ constexpr static auto RANGEMIN = 10;
|
||||
constexpr static auto RANGEMAX = 30;
|
||||
constexpr static auto LEDGERHASH = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652";
|
||||
constexpr static auto TOKENID = "000827103B94ECBB7BF0A0A6ED62B3607801A27B65F4679F4AD1D4850000C0EA";
|
||||
constexpr static auto NFTID = "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004";
|
||||
constexpr static auto TXNID = "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD";
|
||||
|
||||
class RPCLedgerEntryTest : public HandlerBaseTest {};
|
||||
|
||||
@@ -2620,3 +2622,321 @@ TEST(RPCLedgerEntrySpecTest, DeprecatedFields)
|
||||
EXPECT_EQ(warning.at("id").as_int64(), static_cast<int64_t>(rpc::WarningCode::warnRPC_DEPRECATED));
|
||||
EXPECT_NE(warning.at("message").as_string().find("Field 'ledger' is deprecated."), std::string::npos) << warning;
|
||||
}
|
||||
|
||||
// Same as BinaryFalse with include_deleted set to true
|
||||
// Expected Result: same as BinaryFalse
|
||||
TEST_F(RPCLedgerEntryTest, BinaryFalseIncludeDeleted)
|
||||
{
|
||||
static auto constexpr OUT = R"({
|
||||
"ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652",
|
||||
"ledger_index": 30,
|
||||
"validated": true,
|
||||
"index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD",
|
||||
"node": {
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Amount": "100",
|
||||
"Balance": "200",
|
||||
"Destination": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "PayChannel",
|
||||
"OwnerNode": "0",
|
||||
"PreviousTxnID": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD",
|
||||
"PreviousTxnLgrSeq": 400,
|
||||
"PublicKey": "020000000000000000000000000000000000000000000000000000000000000000",
|
||||
"SettleDelay": 300,
|
||||
"index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD"
|
||||
}
|
||||
})";
|
||||
|
||||
backend->setRange(RANGEMIN, RANGEMAX);
|
||||
// return valid ledgerinfo
|
||||
auto const ledgerinfo = CreateLedgerHeader(LEDGERHASH, RANGEMAX);
|
||||
EXPECT_CALL(*backend, fetchLedgerBySequence(RANGEMAX, _)).WillRepeatedly(Return(ledgerinfo));
|
||||
|
||||
// return valid ledger entry which can be deserialized
|
||||
auto const ledgerEntry = CreatePaymentChannelLedgerObject(ACCOUNT, ACCOUNT2, 100, 200, 300, INDEX1, 400);
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject(ripple::uint256{INDEX1}, RANGEMAX, _))
|
||||
.WillRepeatedly(Return(ledgerEntry.getSerializer().peekData()));
|
||||
|
||||
runSpawn([&, this](auto yield) {
|
||||
auto const handler = AnyHandler{LedgerEntryHandler{backend}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
"index": "{}",
|
||||
"include_deleted": true
|
||||
}})",
|
||||
INDEX1
|
||||
));
|
||||
auto const output = handler.process(req, Context{yield});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_EQ(*output.result, json::parse(OUT));
|
||||
});
|
||||
}
|
||||
|
||||
// Test for object is deleted in the latest sequence
|
||||
// Expected Result: return the latest object that is not deleted
|
||||
TEST_F(RPCLedgerEntryTest, LedgerEntryDeleted)
|
||||
{
|
||||
static auto constexpr OUT = R"({
|
||||
"ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652",
|
||||
"ledger_index": 30,
|
||||
"validated": true,
|
||||
"index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD",
|
||||
"deleted_ledger_index": 30,
|
||||
"node": {
|
||||
"Amount": "123",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "NFTokenOffer",
|
||||
"NFTokenID": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004",
|
||||
"NFTokenOfferNode": "0",
|
||||
"Owner": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"OwnerNode": "0",
|
||||
"PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"PreviousTxnLgrSeq": 0,
|
||||
"index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD"
|
||||
}
|
||||
})";
|
||||
backend->setRange(RANGEMIN, RANGEMAX);
|
||||
auto const ledgerinfo = CreateLedgerHeader(LEDGERHASH, RANGEMAX);
|
||||
EXPECT_CALL(*backend, fetchLedgerBySequence(RANGEMAX, _)).WillRepeatedly(Return(ledgerinfo));
|
||||
// return valid ledger entry which can be deserialized
|
||||
auto const offer = CreateNFTBuyOffer(NFTID, ACCOUNT);
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject(ripple::uint256{INDEX1}, RANGEMAX, _))
|
||||
.WillOnce(Return(std::optional<Blob>{}));
|
||||
EXPECT_CALL(*backend, doFetchLedgerObjectSeq(ripple::uint256{INDEX1}, RANGEMAX, _))
|
||||
.WillOnce(Return(uint32_t{RANGEMAX}));
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject(ripple::uint256{INDEX1}, RANGEMAX - 1, _))
|
||||
.WillOnce(Return(offer.getSerializer().peekData()));
|
||||
runSpawn([&, this](auto yield) {
|
||||
auto const handler = AnyHandler{LedgerEntryHandler{backend}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
"index": "{}",
|
||||
"include_deleted": true
|
||||
}})",
|
||||
INDEX1
|
||||
));
|
||||
auto const output = handler.process(req, Context{yield});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_EQ(*output.result, json::parse(OUT));
|
||||
});
|
||||
}
|
||||
|
||||
// Test for object not exist in database
|
||||
// Expected Result: return entryNotFound error
|
||||
TEST_F(RPCLedgerEntryTest, LedgerEntryNotExist)
|
||||
{
|
||||
auto const ledgerinfo = CreateLedgerHeader(LEDGERHASH, RANGEMAX);
|
||||
EXPECT_CALL(*backend, fetchLedgerBySequence(RANGEMAX, _)).WillRepeatedly(Return(ledgerinfo));
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject(ripple::uint256{INDEX1}, RANGEMAX, _))
|
||||
.WillOnce(Return(std::optional<Blob>{}));
|
||||
EXPECT_CALL(*backend, doFetchLedgerObjectSeq(ripple::uint256{INDEX1}, RANGEMAX, _))
|
||||
.WillOnce(Return(uint32_t{RANGEMAX}));
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject(ripple::uint256{INDEX1}, RANGEMAX - 1, _))
|
||||
.WillOnce(Return(std::optional<Blob>{}));
|
||||
|
||||
runSpawn([&, this](auto yield) {
|
||||
auto const handler = AnyHandler{LedgerEntryHandler{backend}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
"index": "{}",
|
||||
"include_deleted": true
|
||||
}})",
|
||||
INDEX1
|
||||
));
|
||||
auto const output = handler.process(req, Context{yield});
|
||||
ASSERT_FALSE(output);
|
||||
auto const err = rpc::makeError(output.result.error());
|
||||
auto const myerr = err.at("error").as_string();
|
||||
EXPECT_EQ(myerr, "entryNotFound");
|
||||
});
|
||||
}
|
||||
|
||||
// Same as BinaryFalse with include_deleted set to false
|
||||
// Expected Result: same as BinaryFalse
|
||||
TEST_F(RPCLedgerEntryTest, BinaryFalseIncludeDeleteFalse)
|
||||
{
|
||||
static auto constexpr OUT = R"({
|
||||
"ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652",
|
||||
"ledger_index": 30,
|
||||
"validated": true,
|
||||
"index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD",
|
||||
"node": {
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Amount": "100",
|
||||
"Balance": "200",
|
||||
"Destination": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "PayChannel",
|
||||
"OwnerNode": "0",
|
||||
"PreviousTxnID": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD",
|
||||
"PreviousTxnLgrSeq": 400,
|
||||
"PublicKey": "020000000000000000000000000000000000000000000000000000000000000000",
|
||||
"SettleDelay": 300,
|
||||
"index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD"
|
||||
}
|
||||
})";
|
||||
|
||||
backend->setRange(RANGEMIN, RANGEMAX);
|
||||
// return valid ledgerinfo
|
||||
auto const ledgerinfo = CreateLedgerHeader(LEDGERHASH, RANGEMAX);
|
||||
EXPECT_CALL(*backend, fetchLedgerBySequence(RANGEMAX, _)).WillRepeatedly(Return(ledgerinfo));
|
||||
|
||||
// return valid ledger entry which can be deserialized
|
||||
auto const ledgerEntry = CreatePaymentChannelLedgerObject(ACCOUNT, ACCOUNT2, 100, 200, 300, INDEX1, 400);
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject(ripple::uint256{INDEX1}, RANGEMAX, _))
|
||||
.WillRepeatedly(Return(ledgerEntry.getSerializer().peekData()));
|
||||
|
||||
runSpawn([&, this](auto yield) {
|
||||
auto const handler = AnyHandler{LedgerEntryHandler{backend}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
"payment_channel": "{}",
|
||||
"include_deleted": false
|
||||
}})",
|
||||
INDEX1
|
||||
));
|
||||
auto const output = handler.process(req, Context{yield});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_EQ(*output.result, json::parse(OUT));
|
||||
});
|
||||
}
|
||||
|
||||
// Test when an object is updated and include_deleted is set to true
|
||||
// Expected Result: return the latest object that is not deleted (latest object in this test)
|
||||
TEST_F(RPCLedgerEntryTest, ObjectUpdateIncludeDelete)
|
||||
{
|
||||
static auto constexpr OUT = R"({
|
||||
"ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652",
|
||||
"ledger_index": 30,
|
||||
"validated": true,
|
||||
"index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD",
|
||||
"node": {
|
||||
"Balance": {
|
||||
"currency": "USD",
|
||||
"issuer": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun",
|
||||
"value": "10"
|
||||
},
|
||||
"Flags": 0,
|
||||
"HighLimit": {
|
||||
"currency": "USD",
|
||||
"issuer": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun",
|
||||
"value": "200"
|
||||
},
|
||||
"LedgerEntryType": "RippleState",
|
||||
"LowLimit": {
|
||||
"currency": "USD",
|
||||
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"value": "100"
|
||||
},
|
||||
"PreviousTxnID": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD",
|
||||
"PreviousTxnLgrSeq": 123,
|
||||
"index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD"
|
||||
}
|
||||
})";
|
||||
|
||||
backend->setRange(RANGEMIN, RANGEMAX);
|
||||
// return valid ledgerinfo
|
||||
auto const ledgerinfo = CreateLedgerHeader(LEDGERHASH, RANGEMAX);
|
||||
EXPECT_CALL(*backend, fetchLedgerBySequence(RANGEMAX, _)).WillRepeatedly(Return(ledgerinfo));
|
||||
|
||||
// return valid ledger entry which can be deserialized
|
||||
auto const line1 = CreateRippleStateLedgerObject("USD", ACCOUNT2, 10, ACCOUNT, 100, ACCOUNT2, 200, TXNID, 123);
|
||||
auto const line2 = CreateRippleStateLedgerObject("USD", ACCOUNT, 10, ACCOUNT2, 100, ACCOUNT, 200, TXNID, 123);
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject(ripple::uint256{INDEX1}, RANGEMAX, _))
|
||||
.WillRepeatedly(Return(line1.getSerializer().peekData()));
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject(ripple::uint256{INDEX1}, RANGEMAX - 1, _))
|
||||
.WillRepeatedly(Return(line2.getSerializer().peekData()));
|
||||
|
||||
runSpawn([&, this](auto yield) {
|
||||
auto const handler = AnyHandler{LedgerEntryHandler{backend}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
"index": "{}",
|
||||
"include_deleted": true
|
||||
}})",
|
||||
INDEX1
|
||||
));
|
||||
auto const output = handler.process(req, Context{yield});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_EQ(*output.result, json::parse(OUT));
|
||||
});
|
||||
}
|
||||
|
||||
// Test when an object is deleted several sequence ago and include_deleted is set to true
|
||||
// Expected Result: return the latest object that is not deleted
|
||||
TEST_F(RPCLedgerEntryTest, ObjectDeletedPreviously)
|
||||
{
|
||||
static auto constexpr OUT = R"({
|
||||
"ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652",
|
||||
"ledger_index": 30,
|
||||
"validated": true,
|
||||
"index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD",
|
||||
"deleted_ledger_index": 26,
|
||||
"node": {
|
||||
"Amount": "123",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "NFTokenOffer",
|
||||
"NFTokenID": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004",
|
||||
"NFTokenOfferNode": "0",
|
||||
"Owner": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"OwnerNode": "0",
|
||||
"PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"PreviousTxnLgrSeq": 0,
|
||||
"index": "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD"
|
||||
}
|
||||
})";
|
||||
backend->setRange(RANGEMIN, RANGEMAX);
|
||||
auto const ledgerinfo = CreateLedgerHeader(LEDGERHASH, RANGEMAX);
|
||||
EXPECT_CALL(*backend, fetchLedgerBySequence(RANGEMAX, _)).WillRepeatedly(Return(ledgerinfo));
|
||||
// return valid ledger entry which can be deserialized
|
||||
auto const offer = CreateNFTBuyOffer(NFTID, ACCOUNT);
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject(ripple::uint256{INDEX1}, RANGEMAX, _))
|
||||
.WillOnce(Return(std::optional<Blob>{}));
|
||||
EXPECT_CALL(*backend, doFetchLedgerObjectSeq(ripple::uint256{INDEX1}, RANGEMAX, _))
|
||||
.WillOnce(Return(uint32_t{RANGEMAX - 4}));
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject(ripple::uint256{INDEX1}, RANGEMAX - 5, _))
|
||||
.WillOnce(Return(offer.getSerializer().peekData()));
|
||||
runSpawn([&, this](auto yield) {
|
||||
auto const handler = AnyHandler{LedgerEntryHandler{backend}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
"index": "{}",
|
||||
"include_deleted": true
|
||||
}})",
|
||||
INDEX1
|
||||
));
|
||||
auto const output = handler.process(req, Context{yield});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_EQ(*output.result, json::parse(OUT));
|
||||
});
|
||||
}
|
||||
|
||||
// Test for object seq not exist in database
|
||||
// Expected Result: return entryNotFound error
|
||||
TEST_F(RPCLedgerEntryTest, ObjectSeqNotExist)
|
||||
{
|
||||
auto const ledgerinfo = CreateLedgerHeader(LEDGERHASH, RANGEMAX);
|
||||
EXPECT_CALL(*backend, fetchLedgerBySequence(RANGEMAX, _)).WillRepeatedly(Return(ledgerinfo));
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject(ripple::uint256{INDEX1}, RANGEMAX, _))
|
||||
.WillOnce(Return(std::optional<Blob>{}));
|
||||
EXPECT_CALL(*backend, doFetchLedgerObjectSeq(ripple::uint256{INDEX1}, RANGEMAX, _))
|
||||
.WillOnce(Return(std::nullopt));
|
||||
|
||||
runSpawn([&, this](auto yield) {
|
||||
auto const handler = AnyHandler{LedgerEntryHandler{backend}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
"index": "{}",
|
||||
"include_deleted": true
|
||||
}})",
|
||||
INDEX1
|
||||
));
|
||||
auto const output = handler.process(req, Context{yield});
|
||||
ASSERT_FALSE(output);
|
||||
auto const err = rpc::makeError(output.result.error());
|
||||
auto const myerr = err.at("error").as_string();
|
||||
EXPECT_EQ(myerr, "entryNotFound");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user