fix(amm): cancel submits after wallet changes

This commit is contained in:
Ricardo Guilherme Schmidt 2026-07-17 20:15:39 -03:00
parent c5e4231890
commit 02a65aaf09
No known key found for this signature in database
GPG Key ID: 1396EA17DE132FFE
3 changed files with 66 additions and 3 deletions

View File

@ -3,6 +3,7 @@
#include <array>
#include <cstddef>
#include <cstdint>
#include <utility>
#include <QByteArray>
#include <QDateTime>
@ -169,17 +170,21 @@ NewPositionRuntime::NewPositionRuntime(WalletProvider* wallet,
void NewPositionRuntime::clearWalletAccounts()
{
++m_walletGeneration;
m_walletPublicAccountIds.clear();
m_wallet->clearSnapshot();
}
void NewPositionRuntime::setWalletAccounts(const QVector<WalletAccount>& accounts)
{
m_walletPublicAccountIds.clear();
QStringList publicAccountIds;
for (const WalletAccount& account : accounts) {
if (account.isPublic)
m_walletPublicAccountIds.append(account.address);
publicAccountIds.append(account.address);
}
if (publicAccountIds != m_walletPublicAccountIds)
++m_walletGeneration;
m_walletPublicAccountIds = std::move(publicAccountIds);
}
QJsonArray NewPositionRuntime::walletAccountReads(bool walletOpen, bool refresh) const
@ -501,13 +506,18 @@ void NewPositionRuntime::submitAsync(const QVariantMap& request,
return;
}
m_submitInFlight = true;
const quint64 walletGeneration = m_walletGeneration;
auto finish = [this, callback = std::move(callback)](QVariantMap result) mutable {
m_submitInFlight = false;
callback(std::move(result));
};
buildQuoteInputAsync(request, network, true, true,
[this, quoteHash, finish = std::move(finish)](
[this, quoteHash, walletGeneration, finish = std::move(finish)](
QJsonObject input, QJsonObject error) mutable {
if (walletGeneration != m_walletGeneration) {
finish(publicError(QStringLiteral("wallet_unavailable")).toVariantMap());
return;
}
if (!error.isEmpty()) {
finish(error.toVariantMap());
return;
@ -533,6 +543,10 @@ void NewPositionRuntime::submitAsync(const QVariantMap& request,
QJsonValue freshLp;
if (quote.value(QStringLiteral("requiresFreshLp")).toBool(false)) {
if (walletGeneration != m_walletGeneration) {
finish(publicError(QStringLiteral("wallet_unavailable")).toVariantMap());
return;
}
const WalletAccountCreation creation = m_wallet->createAccount(true);
if (!creation.ok() || !creation.publicAccount.ok()) {
finish(publicError(QStringLiteral("wallet_submission_failed")).toVariantMap());
@ -576,6 +590,10 @@ void NewPositionRuntime::submitAsync(const QVariantMap& request,
finish(publicError(QStringLiteral("transaction_deadline_expired")).toVariantMap());
return;
}
if (walletGeneration != m_walletGeneration) {
finish(publicError(QStringLiteral("wallet_unavailable")).toVariantMap());
return;
}
const WalletSubmission submission = m_wallet->submitPublicTransaction({
plan.value(QStringLiteral("programId")).toString(),
accountIds,

View File

@ -72,4 +72,5 @@ private:
SequencerClient* m_sequencer;
QStringList m_walletPublicAccountIds;
bool m_submitInFlight = false;
quint64 m_walletGeneration = 0;
};

View File

@ -581,5 +581,49 @@ int main(int argc, char** argv)
"forced follow-up read should complete"))
return 1;
LocalRpcServer cancelledSubmitServer;
if (!expect(cancelledSubmitServer.listen(),
"cancelled-submit sequencer should listen"))
return 1;
cancelledSubmitServer.holdResponses();
if (!expect(sequencerConfig.resize(0) && sequencerConfig.seek(0),
"cancelled-submit config should rewind"))
return 1;
sequencerConfig.write(QJsonDocument(QJsonObject {
{ QStringLiteral("sequencer_addr"), cancelledSubmitServer.endpoint() },
}).toJson(QJsonDocument::Compact));
sequencerConfig.flush();
if (!expect(sequencer.configure(sequencerConfig.fileName()),
"cancelled-submit sequencer should configure"))
return 1;
FakeWallet cancelledWallet;
FakeAmmClient cancelledClient;
NewPositionRuntime cancelledRuntime(
&cancelledWallet, &cancelledClient, &sequencer);
bool cancelledCompleted = false;
QVariantMap cancelledResult;
cancelledRuntime.submitAsync(
request, QStringLiteral("sha256:expected"), readyNetwork(), true,
[&](QVariantMap result) {
cancelledResult = std::move(result);
cancelledCompleted = true;
});
if (!expect(waitForRequestCount(cancelledSubmitServer, 1),
"submit should begin account reads"))
return 1;
cancelledRuntime.clearWalletAccounts();
cancelledRuntime.setWalletAccounts({});
cancelledSubmitServer.releaseNextResponse();
if (!expect(waitForCallbacks(cancelledCompleted, cancelledCompleted),
"cancelled submit should complete"))
return 1;
if (!expect(cancelledResult.value(QStringLiteral("code")).toString()
== QStringLiteral("wallet_unavailable")
&& cancelledWallet.createdAccounts == 0
&& cancelledWallet.submissions == 0,
"wallet change should cancel submit before side effects"))
return 1;
return 0;
}