mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-26 08:30:30 +00:00
Merge remote-tracking branch 'origin/develop' into dangell7/batch-v1
This commit is contained in:
@@ -1091,10 +1091,13 @@ AMMWithdraw::singleWithdrawEPrice(
|
||||
// t = T*(T + A*E*(f - 2))/(T*f - A*E)
|
||||
Number const ae = amountBalance * ePrice;
|
||||
auto const f = getFee(tfee);
|
||||
auto tokNoRoundCb = [&] {
|
||||
return lptAMMBalance * (lptAMMBalance + ae * (f - 2)) / (lptAMMBalance * f - ae);
|
||||
};
|
||||
auto tokProdCb = [&] { return (lptAMMBalance + ae * (f - 2)) / (lptAMMBalance * f - ae); };
|
||||
auto const denom = lptAMMBalance * f - ae;
|
||||
// fixCleanup3_3_0: guard against division by zero
|
||||
// when ePrice == lptAMMBalance*f/amountBalance
|
||||
if (view.rules().enabled(fixCleanup3_3_0) && denom == beast::kZero)
|
||||
return {tecAMM_FAILED, STAmount{}};
|
||||
auto tokNoRoundCb = [&] { return lptAMMBalance * (lptAMMBalance + ae * (f - 2)) / denom; };
|
||||
auto tokProdCb = [&] { return (lptAMMBalance + ae * (f - 2)) / denom; };
|
||||
auto const tokensAdj =
|
||||
getRoundedLPTokens(view.rules(), tokNoRoundCb, lptAMMBalance, tokProdCb, IsDeposit::No);
|
||||
if (tokensAdj <= beast::kZero)
|
||||
|
||||
@@ -2229,6 +2229,31 @@ private:
|
||||
ammAlice.withdraw(alice_, XRPAmount{9'999'999'999});
|
||||
BEAST_EXPECT(ammAlice.expectBalances(XRPAmount{1}, USD(10'000), IOUAmount{100}));
|
||||
});
|
||||
|
||||
// singleWithdrawEPrice: crafted ePrice = lptAMMBalance*f/amountBalance
|
||||
// makes the denominator (T*f - A*E) exactly zero.
|
||||
// Pre-fixCleanup3_3_0: std::overflow_error escapes to the
|
||||
// transactor backstop and is returned as tefEXCEPTION.
|
||||
// Post-fixCleanup3_3_0: denominator check returns tecAMM_FAILED.
|
||||
//
|
||||
// Pool: USD(100)/EUR(100), baseFee=1000 (1%).
|
||||
// Alice is the creator so her discounted fee is 100 (0.1%), f=0.001.
|
||||
// ePrice = lptAMMBalance(100) * f(0.001) / amountBalance(100) = 0.001
|
||||
testAMM(
|
||||
[&](AMM& ammAlice, Env& env) {
|
||||
auto const err =
|
||||
env.enabled(fixCleanup3_3_0) ? Ter(tecAMM_FAILED) : Ter(tefEXCEPTION);
|
||||
ammAlice.withdraw(
|
||||
WithdrawArg{
|
||||
.account = alice_,
|
||||
.asset1Out = USD(0),
|
||||
.maxEP = IOUAmount{1, -3}, // ePrice=0.001 → denom=0
|
||||
.err = err});
|
||||
},
|
||||
{{USD(100), EUR(100)}},
|
||||
1000,
|
||||
std::nullopt,
|
||||
{all - fixCleanup3_3_0, all});
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -3,12 +3,21 @@
|
||||
#include <test/jtx/Oracle.h>
|
||||
#include <test/jtx/amount.h>
|
||||
|
||||
#include <xrpld/app/ledger/OpenLedger.h>
|
||||
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/OpenView.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -312,11 +321,91 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testNullTxReadMeta()
|
||||
{
|
||||
testcase("Null txRead metadata");
|
||||
using namespace jtx;
|
||||
|
||||
// Verify that iteratePriceData handles a null txRead result
|
||||
// gracefully (returns early) rather than crashing with a
|
||||
// nullptr dereference. This simulates local data corruption
|
||||
// where a transaction referenced by sfPreviousTxnID is missing
|
||||
// from the ledger's transaction map.
|
||||
Env env(*this);
|
||||
auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
|
||||
|
||||
Account const owner{"owner"};
|
||||
env.fund(XRP(1'000), owner);
|
||||
|
||||
// Create oracle with XRP/USD and XRP/EUR
|
||||
Oracle oracle(
|
||||
env,
|
||||
{.owner = owner,
|
||||
.series = {{"XRP", "USD", 740, 1}, {"XRP", "EUR", 840, 1}},
|
||||
.fee = baseFee});
|
||||
|
||||
// Update oracle to only have XRP/EUR, pushing XRP/USD into
|
||||
// history. iteratePriceData will need to read historical tx
|
||||
// metadata to find the XRP/USD price.
|
||||
oracle.set(UpdateArg{.series = {{"XRP", "EUR", 850, 1}}, .fee = baseFee});
|
||||
|
||||
OraclesData const oracles{{owner, oracle.documentID()}};
|
||||
|
||||
// Precondition: with an uncorrupted oracle, the historical
|
||||
// traversal must succeed and produce a price for XRP/USD.
|
||||
// This proves the test reaches iteratePriceData's history
|
||||
// path; without it, a future change that breaks the setup
|
||||
// could turn the post-corruption assertion into a vacuous
|
||||
// pass (objectNotFound is reachable from many unrelated
|
||||
// code paths).
|
||||
{
|
||||
auto const ret = Oracle::aggregatePrice(env, "XRP", "USD", oracles);
|
||||
BEAST_EXPECT(!ret.isMember(jss::error));
|
||||
BEAST_EXPECT(ret.isMember(jss::median));
|
||||
}
|
||||
|
||||
// Simulate data corruption: modify the oracle SLE in the open
|
||||
// ledger to have a bogus sfPreviousTxnID that doesn't exist in
|
||||
// any ledger. sfPreviousTxnLgrSeq still points to a valid closed
|
||||
// ledger, so getLedgerBySeq succeeds but txRead returns null.
|
||||
auto const oracleKeylet = keylet::oracle(owner, oracle.documentID());
|
||||
uint256 const bogusTxnID{0xABCABCAB};
|
||||
bool const modified = env.app().getOpenLedger().modify(
|
||||
[&oracleKeylet, &bogusTxnID](OpenView& view, beast::Journal) -> bool {
|
||||
auto const sle = view.read(oracleKeylet);
|
||||
if (!sle)
|
||||
return false;
|
||||
auto replacement = std::make_shared<SLE>(*sle, sle->key());
|
||||
replacement->setFieldH256(sfPreviousTxnID, bogusTxnID);
|
||||
view.rawReplace(replacement);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Confirm the injection actually took effect: modify must
|
||||
// report success, and re-reading the SLE must show the
|
||||
// bogus hash. Otherwise the failure-mode assertion below
|
||||
// would not be exercising the null-txRead path at all.
|
||||
BEAST_EXPECT(modified);
|
||||
if (auto const sle = env.current()->read(oracleKeylet); BEAST_EXPECT(sle))
|
||||
BEAST_EXPECT(sle->getFieldH256(sfPreviousTxnID) == bogusTxnID);
|
||||
|
||||
// Query for XRP/USD using the "current" (open) ledger.
|
||||
// The oracle SLE now has a bogus sfPreviousTxnID. The current
|
||||
// oracle only has EUR, so iteratePriceData will try to read
|
||||
// history. txRead returns null for the bogus hash, and the
|
||||
// null check should cause a graceful early return instead of
|
||||
// a nullptr dereference.
|
||||
auto const ret = Oracle::aggregatePrice(env, "XRP", "USD", oracles);
|
||||
BEAST_EXPECT(ret[jss::error].asString() == "objectNotFound");
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testErrors();
|
||||
testRpc();
|
||||
testNullTxReadMeta();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -58,7 +58,6 @@
|
||||
#include <xrpl/resource/Disposition.h>
|
||||
#include <xrpl/resource/Fees.h>
|
||||
#include <xrpl/resource/Gossip.h>
|
||||
#include <xrpl/server/Handoff.h>
|
||||
#include <xrpl/server/LoadFeeTrack.h>
|
||||
#include <xrpl/server/NetworkOPs.h>
|
||||
#include <xrpl/shamap/SHAMapNodeID.h>
|
||||
@@ -68,6 +67,7 @@
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/completion_condition.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
@@ -392,13 +392,15 @@ PeerImp::removeTxQueue(uint256 const& hash)
|
||||
void
|
||||
PeerImp::charge(Resource::Charge const& fee, std::string const& context)
|
||||
{
|
||||
if ((usage_.charge(fee, context) == Resource::Disposition::Drop) &&
|
||||
usage_.disconnect(pJournal_) && strand_.running_in_this_thread())
|
||||
{
|
||||
// Sever the connection
|
||||
overlay_.incPeerDisconnectCharges();
|
||||
fail("charge: Resources");
|
||||
}
|
||||
dispatch(strand_, [this, self = shared_from_this(), fee, context]() {
|
||||
if (usage_.charge(fee, context) == Resource::Disposition::Drop &&
|
||||
usage_.disconnect(pJournal_))
|
||||
{
|
||||
// Sever the connection.
|
||||
overlay_.incPeerDisconnectCharges();
|
||||
fail("charge: Resources");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <expected>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -349,13 +350,15 @@ doLedgerGrpc(RPC::GRPCContext<org::xrpl::rpc::v1::GetLedgerRequest>& context)
|
||||
auto end = std::chrono::system_clock::now();
|
||||
auto duration =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count() * 1.0;
|
||||
// Guard the per-item rates: an empty ledger has zero objects and/or zero
|
||||
// transactions, and dividing by zero is undefined for these doubles.
|
||||
auto const numObjects = response.ledger_objects().objects_size();
|
||||
auto const numTxns = response.transactions_list().transactions_size();
|
||||
std::string const msPerObj = numObjects > 0 ? std::to_string(duration / numObjects) : "n/a";
|
||||
std::string const msPerTxn = numTxns > 0 ? std::to_string(duration / numTxns) : "n/a";
|
||||
JLOG(context.j.warn()) << __func__ << " - Extract time = " << duration
|
||||
<< " - num objects = " << response.ledger_objects().objects_size()
|
||||
<< " - num txns = " << response.transactions_list().transactions_size()
|
||||
<< " - ms per obj "
|
||||
<< duration / response.ledger_objects().objects_size()
|
||||
<< " - ms per txn "
|
||||
<< duration / response.transactions_list().transactions_size();
|
||||
<< " - num objects = " << numObjects << " - num txns = " << numTxns
|
||||
<< " - ms per obj " << msPerObj << " - ms per txn " << msPerTxn;
|
||||
|
||||
return {response, status};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user