fix(amm): reduce pending pool probes

This commit is contained in:
Ricardo Guilherme Schmidt 2026-07-17 21:57:02 -03:00
parent ec18833e3a
commit 0fdc130e1d
No known key found for this signature in database
GPG Key ID: 1396EA17DE132FFE
5 changed files with 177 additions and 14 deletions

View File

@ -1548,9 +1548,11 @@ AmmActionCard {
function submissionSnapshot() {
var built = root.buildQuoteRequest()
var poolProbe = root.poolProbeRequest(built.request)
poolProbe.poolId = String(root.quotePayload.poolId || "")
return {
"request": built.request,
"poolProbeRequest": root.poolProbeRequest(built.request),
"poolProbeRequest": poolProbe,
"quoteHash": String(root.quotePayload.quoteHash || ""),
"pairText": qsTr("%1 / %2").arg(root.shortTokenName(root.tokenA)).arg(root.shortTokenName(root.tokenB)),
"feeText": root.feeLabel(root.selectedFeeBps),

View File

@ -588,21 +588,15 @@ void NewPositionRuntime::buildQuoteInputAsync(
});
}
void NewPositionRuntime::quoteAsync(const QVariantMap& request,
const ActiveNetworkSnapshot& network,
bool walletOpen,
bool forceRefresh,
bool isPoolProbe,
ResultCallback callback)
void NewPositionRuntime::quoteFromAccountsAsync(
const QVariantMap& request,
const ActiveNetworkSnapshot& network,
bool walletOpen,
bool forceRefresh,
std::function<bool()> shouldContinue,
ResultCallback callback)
{
QPointer<NewPositionRuntime> guard(this);
const quint64 quoteGeneration = isPoolProbe
? 0 : ++m_userQuoteGeneration;
std::function<bool()> shouldContinue = [guard, quoteGeneration]() {
return guard
&& (quoteGeneration == 0
|| quoteGeneration == guard->m_userQuoteGeneration);
};
buildQuoteInputAsync(request, network, walletOpen, forceRefresh,
shouldContinue,
[guard, shouldContinue,
@ -621,6 +615,67 @@ void NewPositionRuntime::quoteAsync(const QVariantMap& request,
});
}
void NewPositionRuntime::quoteAsync(const QVariantMap& request,
const ActiveNetworkSnapshot& network,
bool walletOpen,
bool forceRefresh,
bool isPoolProbe,
ResultCallback callback)
{
QPointer<NewPositionRuntime> guard(this);
const quint64 quoteGeneration = isPoolProbe
? 0 : ++m_userQuoteGeneration;
std::function<bool()> shouldContinue = [guard, quoteGeneration]() {
return guard
&& (quoteGeneration == 0
|| quoteGeneration == guard->m_userQuoteGeneration);
};
const QString poolId = accountIdHex(
request.value(QStringLiteral("poolId")).toString());
if (!isPoolProbe || poolId.isEmpty()
|| network.status != QStringLiteral("ready")
|| !m_sequencer || !m_sequencer->isConfigured()) {
quoteFromAccountsAsync(
request, network, walletOpen, forceRefresh,
std::move(shouldContinue), std::move(callback));
return;
}
m_sequencer->readAccounts(
{ poolId }, true,
[guard, request, network, walletOpen, forceRefresh, poolId,
shouldContinue = std::move(shouldContinue),
callback = std::move(callback)](
QVector<WalletAccountRead> reads) mutable {
if (!guard || !shouldContinue())
return;
const WalletAccountRead read = reads.value(0);
if (!read.ok()) {
callback(publicError(
QStringLiteral("account_read_failed")).toVariantMap());
return;
}
if (isDefaultAccountRead(read, poolId)) {
callback(QJsonObject {
{ QStringLiteral("schema"), QString::fromLatin1(SCHEMA) },
{ QStringLiteral("status"), QStringLiteral("ok") },
{ QStringLiteral("canSubmit"), false },
{ QStringLiteral("poolStatus"), QStringLiteral("missing_pool") },
{ QStringLiteral("poolId"),
request.value(QStringLiteral("poolId")).toString() },
{ QStringLiteral("tokenAId"),
request.value(QStringLiteral("tokenAId")).toString() },
{ QStringLiteral("tokenBId"),
request.value(QStringLiteral("tokenBId")).toString() },
}.toVariantMap());
return;
}
guard->quoteFromAccountsAsync(
request, network, walletOpen, forceRefresh,
std::move(shouldContinue), std::move(callback));
});
}
void NewPositionRuntime::submitAsync(const QVariantMap& request,
const QString& quoteHash,
const ActiveNetworkSnapshot& network,

View File

@ -77,6 +77,12 @@ private:
bool forceRefresh,
std::function<bool()> shouldContinue,
std::function<void(QJsonObject, QJsonObject)> callback);
void quoteFromAccountsAsync(const QVariantMap& request,
const ActiveNetworkSnapshot& network,
bool walletOpen,
bool forceRefresh,
std::function<bool()> shouldContinue,
ResultCallback callback);
void submitPlanAsync(QJsonObject input,
const QString& quoteHash,
QJsonValue freshLp,

View File

@ -326,6 +326,7 @@ namespace {
{ QStringLiteral("schema"), QStringLiteral("new-position.v2") },
{ QStringLiteral("status"), QStringLiteral("ok") },
{ QStringLiteral("canSubmit"), true },
{ QStringLiteral("poolStatus"), quotePoolStatus },
{ QStringLiteral("quoteHash"), quoteHash },
{ QStringLiteral("requiresFreshLp"), requiresFreshLp },
});
@ -383,6 +384,7 @@ namespace {
}
QString quoteHash = QStringLiteral("sha256:expected");
QString quotePoolStatus = QStringLiteral("active_pool");
bool requiresFreshLp = true;
mutable bool sawFreshLp = false;
mutable int tokenIdsCalls = 0;
@ -836,6 +838,81 @@ int main(int argc, char** argv)
"user quote cancellation must not cancel the pool probe"))
return 1;
LocalRpcServer poolProbeServer;
if (!expect(poolProbeServer.listen(),
"pool-probe sequencer should listen"))
return 1;
QTemporaryFile poolProbeConfig;
if (!expect(poolProbeConfig.open(),
"pool-probe config should open"))
return 1;
poolProbeConfig.write(QJsonDocument(QJsonObject {
{ QStringLiteral("sequencer_addr"), poolProbeServer.endpoint() },
}).toJson(QJsonDocument::Compact));
poolProbeConfig.flush();
FakeWallet poolProbeWallet;
FakeAmmClient poolProbeClient;
SequencerClient poolProbeSequencer(&poolProbeClient);
if (!expect(poolProbeSequencer.configure(poolProbeConfig.fileName()),
"pool-probe sequencer should configure"))
return 1;
NewPositionRuntime poolProbeRuntime(
&poolProbeWallet, &poolProbeClient, &poolProbeSequencer);
const QString poolIdBase58 = QStringLiteral(
"1thX6LZfHDZZKUs92febYZhYRcXddmzfzF2NvTkPNE");
const QString poolIdHex = QStringLiteral(
"000102030405060708090a0b0c0d0e0f"
"101112131415161718191a1b1c1d1e1f");
QVariantMap poolProbeRequest = request;
poolProbeRequest.insert(QStringLiteral("poolId"), poolIdBase58);
QVariantMap poolProbeResult;
poolProbeRuntime.quoteAsync(
poolProbeRequest, readyNetwork(), true, true, true,
[&](QVariantMap result) { poolProbeResult = std::move(result); });
if (!expect(waitForCondition([&]() { return !poolProbeResult.isEmpty(); }),
"default pool probe should complete"))
return 1;
if (!expect(poolProbeResult.value(QStringLiteral("poolStatus")).toString()
== QStringLiteral("missing_pool")
&& poolProbeServer.requestCount() == 1
&& poolProbeClient.pairIdsCalls == 0
&& poolProbeClient.quoteCalls == 0
&& poolProbeClient.normalizedAccountIds.contains(poolIdHex),
"default pool probe should read only the pool account"))
return 1;
poolProbeClient.normalizedBalanceHex = QString(32, QLatin1Char('1'));
poolProbeResult.clear();
poolProbeRuntime.quoteAsync(
poolProbeRequest, readyNetwork(), true, true, true,
[&](QVariantMap result) { poolProbeResult = std::move(result); });
if (!expect(waitForCondition([&]() { return !poolProbeResult.isEmpty(); }),
"nondefault pool probe should complete"))
return 1;
if (!expect(poolProbeResult.value(QStringLiteral("poolStatus")).toString()
== QStringLiteral("active_pool")
&& poolProbeServer.requestCount() == 3
&& poolProbeClient.pairIdsCalls == 1
&& poolProbeClient.quoteCalls == 1,
"nondefault pool should run one validating full quote"))
return 1;
poolProbeServer.failNextRequest();
poolProbeResult.clear();
poolProbeRuntime.quoteAsync(
poolProbeRequest, readyNetwork(), true, true, true,
[&](QVariantMap result) { poolProbeResult = std::move(result); });
if (!expect(waitForCondition([&]() { return !poolProbeResult.isEmpty(); }),
"failed pool probe should complete"))
return 1;
if (!expect(poolProbeResult.value(QStringLiteral("code")).toString()
== QStringLiteral("account_read_failed")
&& poolProbeServer.requestCount() == 4
&& poolProbeClient.pairIdsCalls == 1
&& poolProbeClient.quoteCalls == 1,
"failed pool probe should wait for the next one-account retry"))
return 1;
LocalRpcServer server;
if (!expect(server.listen(), "local sequencer should listen"))
return 1;

View File

@ -441,6 +441,29 @@ TestCase {
compare(form.localErrors.length, 0)
}
function test_submissionSnapshotCarriesPoolAccountForProbe() {
var form = createForm()
form.flowState = flowState({
"schema": "new-position.v2",
"status": "ok",
"canSubmit": true,
"tokenAId": tokenHigh,
"tokenBId": tokenLow,
"poolStatus": "missing_pool",
"poolId": submittedTransactionId,
"instruction": "NewDefinition",
"quoteHash": "sha256:expected",
"actualAmountARaw": "2000000",
"actualAmountBRaw": "3",
"expectedLpRaw": "10"
})
wait(0)
var snapshot = form.submissionSnapshot()
compare(snapshot.poolProbeRequest.poolId, submittedTransactionId)
verify(snapshot.request.poolId === undefined)
}
function test_staleQuoteErrorsDoNotMarkCurrentDraft() {
var quote = {
"schema": "new-position.v2",