mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-07-19 13:29:56 +00:00
fix(amm): cancel superseded quote work
This commit is contained in:
parent
942920b51c
commit
ec18833e3a
@ -182,7 +182,7 @@ QtObject {
|
||||
}
|
||||
root.activeQuoteRequestId = ++root.operationSerial
|
||||
root.backend.requestNewPositionQuote(
|
||||
built.request, root.activeQuoteRequestId, true)
|
||||
built.request, root.activeQuoteRequestId, true, false)
|
||||
}
|
||||
|
||||
function acceptQuoteResult(result) {
|
||||
@ -299,7 +299,8 @@ QtObject {
|
||||
const pending = root.pendingPoolProbes[0]
|
||||
root.poolProbeInFlight = true
|
||||
root.poolProbeRequestId = ++root.operationSerial
|
||||
root.backend.requestNewPositionQuote(pending.request, root.poolProbeRequestId, true)
|
||||
root.backend.requestNewPositionQuote(
|
||||
pending.request, root.poolProbeRequestId, true, true)
|
||||
}
|
||||
|
||||
function finishPoolProbe(pending, quote) {
|
||||
|
||||
@ -164,10 +164,11 @@ void AmmUiBackend::refreshNewPositionContext(QVariantMap request)
|
||||
|
||||
void AmmUiBackend::requestNewPositionQuote(QVariantMap request,
|
||||
int requestId,
|
||||
bool forceRefresh)
|
||||
bool forceRefresh,
|
||||
bool isPoolProbe)
|
||||
{
|
||||
m_newPosition->quoteAsync(
|
||||
request, m_network.snapshot(), isWalletOpen(), forceRefresh,
|
||||
request, m_network.snapshot(), isWalletOpen(), forceRefresh, isPoolProbe,
|
||||
[this, requestId](QVariantMap result) {
|
||||
result.insert(QStringLiteral("requestId"), requestId);
|
||||
setNewPositionQuoteResult(std::move(result));
|
||||
|
||||
@ -47,7 +47,8 @@ public slots:
|
||||
void refreshNewPositionContext(QVariantMap request) override;
|
||||
void requestNewPositionQuote(QVariantMap request,
|
||||
int requestId,
|
||||
bool forceRefresh) override;
|
||||
bool forceRefresh,
|
||||
bool isPoolProbe) override;
|
||||
void requestNewPositionSubmit(QVariantMap request,
|
||||
QString quoteHash,
|
||||
int requestId) override;
|
||||
|
||||
@ -36,7 +36,7 @@ class AmmUiBackend
|
||||
PROP(QVariantMap newPositionQuoteResult READONLY)
|
||||
PROP(QVariantMap newPositionSubmitResult READONLY)
|
||||
SLOT(void refreshNewPositionContext(QVariantMap request))
|
||||
SLOT(void requestNewPositionQuote(QVariantMap request, int requestId, bool forceRefresh))
|
||||
SLOT(void requestNewPositionQuote(QVariantMap request, int requestId, bool forceRefresh, bool isPoolProbe))
|
||||
SLOT(void requestNewPositionSubmit(QVariantMap request, QString quoteHash, int requestId))
|
||||
|
||||
// Wallet lifecycle. createNewDefault() is the happy path: it creates a
|
||||
|
||||
@ -451,8 +451,11 @@ void NewPositionRuntime::buildQuoteInputAsync(
|
||||
const ActiveNetworkSnapshot& network,
|
||||
bool walletOpen,
|
||||
bool forceRefresh,
|
||||
std::function<bool()> shouldContinue,
|
||||
std::function<void(QJsonObject, QJsonObject)> callback)
|
||||
{
|
||||
if (!shouldContinue())
|
||||
return;
|
||||
if (network.status != QStringLiteral("ready")) {
|
||||
callback({}, publicError(network.status));
|
||||
return;
|
||||
@ -472,8 +475,9 @@ void NewPositionRuntime::buildQuoteInputAsync(
|
||||
QPointer<NewPositionRuntime> guard(this);
|
||||
m_sequencer->readAccounts({ configId }, forceRefresh,
|
||||
[guard, request, network, walletOpen, forceRefresh,
|
||||
shouldContinue,
|
||||
callback = std::move(callback)](QVector<WalletAccountRead> configReads) mutable {
|
||||
if (!guard)
|
||||
if (!guard || !shouldContinue())
|
||||
return;
|
||||
const QJsonObject config = accountReadJson(configReads.value(0));
|
||||
const QJsonObject requestObject = QJsonObject::fromVariantMap(request);
|
||||
@ -509,9 +513,10 @@ void NewPositionRuntime::buildQuoteInputAsync(
|
||||
};
|
||||
guard->m_sequencer->readAccounts(fixedIds, forceRefresh,
|
||||
[guard, requestObject, network, walletOpen, config, pair,
|
||||
shouldContinue,
|
||||
callback = std::move(callback)](
|
||||
QVector<WalletAccountRead> fixedReads) mutable {
|
||||
if (!guard)
|
||||
if (!guard || !shouldContinue())
|
||||
return;
|
||||
QStringList selectedIds;
|
||||
for (const QString& key : {
|
||||
@ -531,15 +536,19 @@ void NewPositionRuntime::buildQuoteInputAsync(
|
||||
[guard, requestObject, network, walletOpen, config, pair,
|
||||
selectedIds = std::move(selectedIds),
|
||||
fixedReads = std::move(fixedReads),
|
||||
shouldContinue,
|
||||
callback = std::move(callback)](
|
||||
QVector<WalletAccountRead> walletReads) mutable {
|
||||
if (!guard)
|
||||
if (!guard || !shouldContinue())
|
||||
return;
|
||||
auto finish = [requestObject, network, walletOpen, config,
|
||||
fixedReads = std::move(fixedReads),
|
||||
walletReads = std::move(walletReads),
|
||||
shouldContinue,
|
||||
callback = std::move(callback)](
|
||||
QVector<WalletAccountRead> selectedReads) mutable {
|
||||
if (!shouldContinue())
|
||||
return;
|
||||
QHash<QString, WalletAccountRead> walletById;
|
||||
for (const WalletAccountRead& read : walletReads)
|
||||
walletById.insert(read.accountId, read);
|
||||
@ -583,13 +592,23 @@ 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);
|
||||
};
|
||||
buildQuoteInputAsync(request, network, walletOpen, forceRefresh,
|
||||
[guard, callback = std::move(callback)](
|
||||
shouldContinue,
|
||||
[guard, shouldContinue,
|
||||
callback = std::move(callback)](
|
||||
QJsonObject input, QJsonObject error) mutable {
|
||||
if (!guard)
|
||||
if (!guard || !shouldContinue())
|
||||
return;
|
||||
if (!error.isEmpty()) {
|
||||
callback(error.toVariantMap());
|
||||
@ -626,11 +645,17 @@ void NewPositionRuntime::submitAsync(const QVariantMap& request,
|
||||
const quint64 walletGeneration = m_walletGeneration;
|
||||
m_submitCallback = std::move(callback);
|
||||
QPointer<NewPositionRuntime> guard(this);
|
||||
std::function<bool()> shouldContinue =
|
||||
[guard, submitGeneration, walletGeneration]() {
|
||||
return guard
|
||||
&& guard->submitIsCurrent(submitGeneration, walletGeneration);
|
||||
};
|
||||
buildQuoteInputAsync(request, network, true, true,
|
||||
[guard, quoteHash, submitGeneration, walletGeneration](
|
||||
shouldContinue,
|
||||
[guard, quoteHash, submitGeneration, walletGeneration,
|
||||
shouldContinue](
|
||||
QJsonObject input, QJsonObject error) mutable {
|
||||
if (!guard
|
||||
|| !guard->submitIsCurrent(submitGeneration, walletGeneration))
|
||||
if (!guard || !shouldContinue())
|
||||
return;
|
||||
if (!error.isEmpty()) {
|
||||
guard->finishSubmit(submitGeneration, error.toVariantMap());
|
||||
|
||||
@ -36,6 +36,7 @@ public:
|
||||
const ActiveNetworkSnapshot& network,
|
||||
bool walletOpen,
|
||||
bool forceRefresh,
|
||||
bool isPoolProbe,
|
||||
ResultCallback callback);
|
||||
void submitAsync(const QVariantMap& request,
|
||||
const QString& quoteHash,
|
||||
@ -74,6 +75,7 @@ private:
|
||||
const ActiveNetworkSnapshot& network,
|
||||
bool walletOpen,
|
||||
bool forceRefresh,
|
||||
std::function<bool()> shouldContinue,
|
||||
std::function<void(QJsonObject, QJsonObject)> callback);
|
||||
void submitPlanAsync(QJsonObject input,
|
||||
const QString& quoteHash,
|
||||
@ -104,6 +106,7 @@ private:
|
||||
bool m_submitInFlight = false;
|
||||
quint64 m_walletGeneration = 0;
|
||||
quint64 m_contextGeneration = 0;
|
||||
quint64 m_userQuoteGeneration = 0;
|
||||
quint64 m_submitGeneration = 0;
|
||||
ResultCallback m_submitCallback;
|
||||
};
|
||||
|
||||
@ -298,6 +298,7 @@ namespace {
|
||||
|
||||
AmmClientResult pairIds(const QJsonObject&) const override
|
||||
{
|
||||
++pairIdsCalls;
|
||||
return success({
|
||||
{ QStringLiteral("status"), QStringLiteral("ok") },
|
||||
{ QStringLiteral("tokenAId"), QStringLiteral("token-a") },
|
||||
@ -320,6 +321,7 @@ namespace {
|
||||
|
||||
AmmClientResult quote(const QJsonObject&) const override
|
||||
{
|
||||
++quoteCalls;
|
||||
return success({
|
||||
{ QStringLiteral("schema"), QStringLiteral("new-position.v2") },
|
||||
{ QStringLiteral("status"), QStringLiteral("ok") },
|
||||
@ -385,6 +387,8 @@ namespace {
|
||||
mutable bool sawFreshLp = false;
|
||||
mutable int tokenIdsCalls = 0;
|
||||
mutable int contextCalls = 0;
|
||||
mutable int pairIdsCalls = 0;
|
||||
mutable int quoteCalls = 0;
|
||||
mutable int planCalls = 0;
|
||||
mutable int planFailuresRemaining = 0;
|
||||
mutable QStringList freshLpAccountIds;
|
||||
@ -767,6 +771,71 @@ int main(int argc, char** argv)
|
||||
"superseded context should stop before downstream work"))
|
||||
return 1;
|
||||
|
||||
LocalRpcServer staleQuoteServer;
|
||||
if (!expect(staleQuoteServer.listen(),
|
||||
"stale-quote sequencer should listen"))
|
||||
return 1;
|
||||
staleQuoteServer.holdResponses();
|
||||
QTemporaryFile staleQuoteConfig;
|
||||
if (!expect(staleQuoteConfig.open(),
|
||||
"stale-quote config should open"))
|
||||
return 1;
|
||||
staleQuoteConfig.write(QJsonDocument(QJsonObject {
|
||||
{ QStringLiteral("sequencer_addr"), staleQuoteServer.endpoint() },
|
||||
}).toJson(QJsonDocument::Compact));
|
||||
staleQuoteConfig.flush();
|
||||
FakeWallet staleQuoteWallet;
|
||||
FakeAmmClient staleQuoteClient;
|
||||
SequencerClient staleQuoteSequencer(&staleQuoteClient);
|
||||
if (!expect(staleQuoteSequencer.configure(staleQuoteConfig.fileName()),
|
||||
"stale-quote sequencer should configure"))
|
||||
return 1;
|
||||
NewPositionRuntime staleQuoteRuntime(
|
||||
&staleQuoteWallet, &staleQuoteClient, &staleQuoteSequencer);
|
||||
int staleQuoteCallbacks = 0;
|
||||
int latestQuoteCallbacks = 0;
|
||||
staleQuoteRuntime.quoteAsync(
|
||||
request, readyNetwork(), true, false, false,
|
||||
[&](QVariantMap) { ++staleQuoteCallbacks; });
|
||||
if (!expect(waitForRequestCount(staleQuoteServer, 1),
|
||||
"first quote should begin the config read"))
|
||||
return 1;
|
||||
staleQuoteRuntime.quoteAsync(
|
||||
request, readyNetwork(), true, false, false,
|
||||
[&](QVariantMap) { ++latestQuoteCallbacks; });
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
|
||||
if (!expect(staleQuoteServer.requestCount() == 1,
|
||||
"superseding quote should share the active config read"))
|
||||
return 1;
|
||||
staleQuoteServer.releaseNextResponse();
|
||||
if (!expect(waitForCondition([&]() { return latestQuoteCallbacks == 1; }),
|
||||
"latest quote should complete"))
|
||||
return 1;
|
||||
if (!expect(staleQuoteCallbacks == 0
|
||||
&& staleQuoteClient.pairIdsCalls == 1
|
||||
&& staleQuoteClient.quoteCalls == 1,
|
||||
"superseded user quote should stop before pair work"))
|
||||
return 1;
|
||||
|
||||
int poolProbeCallbacks = 0;
|
||||
int concurrentUserCallbacks = 0;
|
||||
staleQuoteRuntime.quoteAsync(
|
||||
request, readyNetwork(), true, false, true,
|
||||
[&](QVariantMap) { ++poolProbeCallbacks; });
|
||||
staleQuoteRuntime.quoteAsync(
|
||||
request, readyNetwork(), true, false, false,
|
||||
[&](QVariantMap) { ++concurrentUserCallbacks; });
|
||||
if (!expect(waitForCondition([&]() {
|
||||
return poolProbeCallbacks == 1
|
||||
&& concurrentUserCallbacks == 1;
|
||||
}),
|
||||
"pool probe and user quote should complete independently"))
|
||||
return 1;
|
||||
if (!expect(staleQuoteClient.pairIdsCalls == 3
|
||||
&& staleQuoteClient.quoteCalls == 3,
|
||||
"user quote cancellation must not cancel the pool probe"))
|
||||
return 1;
|
||||
|
||||
LocalRpcServer server;
|
||||
if (!expect(server.listen(), "local sequencer should listen"))
|
||||
return 1;
|
||||
@ -828,7 +897,7 @@ int main(int argc, char** argv)
|
||||
refreshClient.normalizedAccountIds.count(selectedAccountHex);
|
||||
QVariantMap selectedQuote;
|
||||
selectedRuntime.quoteAsync(
|
||||
selectedRequest, readyNetwork(), true, false,
|
||||
selectedRequest, readyNetwork(), true, false, false,
|
||||
[&](QVariantMap result) { selectedQuote = std::move(result); });
|
||||
if (!expect(waitForCondition([&]() { return !selectedQuote.isEmpty(); }),
|
||||
"selected-holding quote should complete"))
|
||||
|
||||
@ -38,6 +38,7 @@ TestCase {
|
||||
property int contextRefreshCalls: 0
|
||||
property int contextRequestId: 0
|
||||
property int quoteCalls: 0
|
||||
property var quotePoolProbeFlags: []
|
||||
property int submitCalls: 0
|
||||
property var lastContextRefreshRequest: ({})
|
||||
|
||||
@ -49,8 +50,12 @@ TestCase {
|
||||
newPositionSubmitResult = result
|
||||
}
|
||||
|
||||
function requestNewPositionQuote(request, requestId, forceRefresh) {
|
||||
function requestNewPositionQuote(request, requestId, forceRefresh,
|
||||
isPoolProbe) {
|
||||
++quoteCalls
|
||||
quotePoolProbeFlags = quotePoolProbeFlags.concat([
|
||||
isPoolProbe === true
|
||||
])
|
||||
var result = JSON.parse(JSON.stringify(quoteResult || ({})))
|
||||
result.requestId = requestId
|
||||
newPositionQuoteResult = result
|
||||
@ -218,6 +223,22 @@ TestCase {
|
||||
wait(0)
|
||||
page.flow.pollPendingPool()
|
||||
compare(backend.quoteCalls, 1)
|
||||
compare(backend.quotePoolProbeFlags.length, 1)
|
||||
compare(backend.quotePoolProbeFlags[0], true)
|
||||
}
|
||||
|
||||
function test_userQuoteUsesInteractiveRequestLane() {
|
||||
var backend = createTemporaryObject(backendComponent, testCase)
|
||||
var page = createTemporaryObject(pageComponent, testCase, { "backend": backend })
|
||||
page.flow.pendingQuoteRequest = {
|
||||
"ok": true,
|
||||
"request": ({})
|
||||
}
|
||||
page.flow.active = true
|
||||
page.flow.requestQuoteNow(page.flow.quoteSerial)
|
||||
|
||||
verify(backend.quoteCalls > 0)
|
||||
compare(backend.quotePoolProbeFlags[0], false)
|
||||
}
|
||||
|
||||
function test_activePoolSubmissionDoesNotStartPoolCreationProbe() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user