fix(amm): back off network identity probes

This commit is contained in:
Ricardo Guilherme Schmidt 2026-07-17 19:38:35 -03:00
parent 57c3712bb8
commit df5152c5ee
No known key found for this signature in database
GPG Key ID: 1396EA17DE132FFE
5 changed files with 60 additions and 3 deletions

View File

@ -1,5 +1,8 @@
#include "ActiveNetwork.h" #include "ActiveNetwork.h"
#include <algorithm>
#include <iterator>
#include <QFile> #include <QFile>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
@ -30,6 +33,7 @@ bool ActiveNetwork::load()
m_network = {}; m_network = {};
m_network.status = QStringLiteral("config_missing"); m_network.status = QStringLiteral("config_missing");
m_expectedIdentity.clear(); m_expectedIdentity.clear();
m_failedIdentityProbes = 0;
const QByteArray selected = qgetenv(NETWORK_ENV); const QByteArray selected = qgetenv(NETWORK_ENV);
m_network.id = selected.isEmpty() m_network.id = selected.isEmpty()
@ -93,8 +97,28 @@ bool ActiveNetwork::needsIdentityProbe() const
|| m_network.status == QStringLiteral("network_unknown"); || m_network.status == QStringLiteral("network_unknown");
} }
int ActiveNetwork::identityRetryDelayMs() const
{
static constexpr int retryDelays[] = {
1000,
2000,
4000,
8000,
16000,
30000,
};
if (m_failedIdentityProbes <= 0
|| m_network.status != QStringLiteral("network_unknown")) {
return 0;
}
const int index = (std::min)(m_failedIdentityProbes,
static_cast<int>(std::size(retryDelays))) - 1;
return retryDelays[index];
}
void ActiveNetwork::sequencerChanged(bool available) void ActiveNetwork::sequencerChanged(bool available)
{ {
m_failedIdentityProbes = 0;
if (isConfigured()) if (isConfigured())
clearIdentity(available ? QStringLiteral("loading") : QStringLiteral("network_unknown")); clearIdentity(available ? QStringLiteral("loading") : QStringLiteral("network_unknown"));
} }
@ -103,6 +127,8 @@ void ActiveNetwork::reachabilityChanged(bool reachable, bool wasReachable)
{ {
if (!isConfigured()) if (!isConfigured())
return; return;
if (!reachable || !wasReachable)
m_failedIdentityProbes = 0;
if (!reachable) if (!reachable)
clearIdentity(QStringLiteral("network_unknown")); clearIdentity(QStringLiteral("network_unknown"));
else if (!wasReachable) else if (!wasReachable)
@ -118,10 +144,13 @@ void ActiveNetwork::beginIdentityProbe()
void ActiveNetwork::finishIdentityProbe(const QString& identity) void ActiveNetwork::finishIdentityProbe(const QString& identity)
{ {
if (identity.isEmpty()) { if (identity.isEmpty()) {
m_failedIdentityProbes = (std::min)(m_failedIdentityProbes + 1, 6);
clearIdentity(QStringLiteral("network_unknown")); clearIdentity(QStringLiteral("network_unknown"));
} else if (identity != m_expectedIdentity) { } else if (identity != m_expectedIdentity) {
m_failedIdentityProbes = 0;
clearIdentity(QStringLiteral("network_mismatch")); clearIdentity(QStringLiteral("network_mismatch"));
} else { } else {
m_failedIdentityProbes = 0;
m_network.status = QStringLiteral("ready"); m_network.status = QStringLiteral("ready");
m_network.fingerprint = (isDevnet() ? QStringLiteral("channel:") m_network.fingerprint = (isDevnet() ? QStringLiteral("channel:")
: QStringLiteral("block10:")) : QStringLiteral("block10:"))

View File

@ -19,6 +19,7 @@ public:
bool isConfigured() const; bool isConfigured() const;
bool isDevnet() const; bool isDevnet() const;
bool needsIdentityProbe() const; bool needsIdentityProbe() const;
int identityRetryDelayMs() const;
ActiveNetworkSnapshot snapshot() const { return m_network; } ActiveNetworkSnapshot snapshot() const { return m_network; }
void sequencerChanged(bool available); void sequencerChanged(bool available);
@ -33,4 +34,5 @@ private:
ActiveNetworkSnapshot m_network; ActiveNetworkSnapshot m_network;
QString m_expectedIdentity; QString m_expectedIdentity;
int m_failedIdentityProbes = 0;
}; };

View File

@ -69,13 +69,17 @@ AmmUiBackend::AmmUiBackend(LogosAPI* logosAPI, QObject* parent)
m_newPosition(std::make_unique<NewPositionRuntime>( m_newPosition(std::make_unique<NewPositionRuntime>(
m_wallet.get(), m_ammClient.get(), m_sequencer.get())), m_wallet.get(), m_ammClient.get(), m_sequencer.get())),
m_net(new QNetworkAccessManager(this)), m_net(new QNetworkAccessManager(this)),
m_transactionTimer(new QTimer(this)) m_transactionTimer(new QTimer(this)),
m_identityRetryTimer(new QTimer(this))
{ {
setNewPositionQuoteResult({}); setNewPositionQuoteResult({});
setNewPositionSubmitResult({}); setNewPositionSubmitResult({});
m_transactionTimer->setInterval(5000); m_transactionTimer->setInterval(5000);
connect(m_transactionTimer, &QTimer::timeout, connect(m_transactionTimer, &QTimer::timeout,
this, &AmmUiBackend::pollTransactions); this, &AmmUiBackend::pollTransactions);
m_identityRetryTimer->setSingleShot(true);
connect(m_identityRetryTimer, &QTimer::timeout,
this, &AmmUiBackend::probeNetworkIdentity);
m_network.load(); m_network.load();
connect(m_walletController.get(), &WalletController::stateChanged, connect(m_walletController.get(), &WalletController::stateChanged,
@ -157,8 +161,6 @@ void AmmUiBackend::refreshNewPositionContext(QVariantMap request)
else { else {
request = m_newPositionHints; request = m_newPositionHints;
} }
if (m_network.status() == QStringLiteral("network_unknown"))
probeNetworkIdentity();
const quint64 generation = ++m_contextGeneration; const quint64 generation = ++m_contextGeneration;
m_newPosition->contextAsync( m_newPosition->contextAsync(
request, m_network.snapshot(), isWalletOpen(), refreshWalletAccounts, request, m_network.snapshot(), isWalletOpen(), refreshWalletAccounts,
@ -222,11 +224,13 @@ void AmmUiBackend::syncWalletState()
const bool addressChanged = previousAddress != state.sequencerAddress; const bool addressChanged = previousAddress != state.sequencerAddress;
if (addressChanged) { if (addressChanged) {
m_identityRetryTimer->stop();
m_pendingTransactions.clear(); m_pendingTransactions.clear();
m_transactionTimer->stop(); m_transactionTimer->stop();
m_network.sequencerChanged(!state.sequencerAddress.isEmpty()); m_network.sequencerChanged(!state.sequencerAddress.isEmpty());
} }
if (addressChanged || wasReachable != state.sequencerReachable) { if (addressChanged || wasReachable != state.sequencerReachable) {
m_identityRetryTimer->stop();
m_network.reachabilityChanged(state.sequencerReachable, wasReachable); m_network.reachabilityChanged(state.sequencerReachable, wasReachable);
} }
if (walletWasOpen && !state.isWalletOpen) if (walletWasOpen && !state.isWalletOpen)
@ -245,6 +249,7 @@ void AmmUiBackend::syncWalletState()
void AmmUiBackend::probeNetworkIdentity() void AmmUiBackend::probeNetworkIdentity()
{ {
if (m_identityProbeInFlight if (m_identityProbeInFlight
|| m_identityRetryTimer->isActive()
|| !m_network.isConfigured() || !m_network.isConfigured()
|| sequencerAddr().isEmpty()) { || sequencerAddr().isEmpty()) {
return; return;
@ -283,6 +288,9 @@ void AmmUiBackend::probeNetworkIdentity()
? channelIdFromResponse(payload) ? channelIdFromResponse(payload)
: blockHashFromResponse(payload); : blockHashFromResponse(payload);
m_network.finishIdentityProbe(actual); m_network.finishIdentityProbe(actual);
const int retryDelay = m_network.identityRetryDelayMs();
if (retryDelay > 0)
m_identityRetryTimer->start(retryDelay);
reply->deleteLater(); reply->deleteLater();
publishNetworkContext(); publishNetworkContext();
}); });

View File

@ -74,6 +74,7 @@ private:
QNetworkAccessManager* m_net; QNetworkAccessManager* m_net;
QTimer* m_transactionTimer; QTimer* m_transactionTimer;
QTimer* m_identityRetryTimer;
ActiveNetwork m_network; ActiveNetwork m_network;
QVariantMap m_newPositionHints; QVariantMap m_newPositionHints;

View File

@ -40,7 +40,24 @@ int main()
"loaded network should await identity")) "loaded network should await identity"))
return 1; return 1;
network.finishIdentityProbe({});
if (!expect(network.identityRetryDelayMs() == 1000,
"first failed identity probe should back off for one second"))
return 1;
network.finishIdentityProbe({});
if (!expect(network.identityRetryDelayMs() == 2000,
"repeated identity failures should increase the delay"))
return 1;
for (int attempt = 0; attempt < 8; ++attempt)
network.finishIdentityProbe({});
if (!expect(network.identityRetryDelayMs() == 30000,
"identity retry delay should cap at thirty seconds"))
return 1;
network.sequencerChanged(true); network.sequencerChanged(true);
if (!expect(network.identityRetryDelayMs() == 0,
"sequencer changes should reset identity retry backoff"))
return 1;
network.finishIdentityProbe(QString(64, QLatin1Char('d'))); network.finishIdentityProbe(QString(64, QLatin1Char('d')));
if (!expect(network.status() == QStringLiteral("network_mismatch"), if (!expect(network.status() == QStringLiteral("network_mismatch"),
"wrong identity should block the network")) "wrong identity should block the network"))