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 <algorithm>
#include <iterator>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
@ -30,6 +33,7 @@ bool ActiveNetwork::load()
m_network = {};
m_network.status = QStringLiteral("config_missing");
m_expectedIdentity.clear();
m_failedIdentityProbes = 0;
const QByteArray selected = qgetenv(NETWORK_ENV);
m_network.id = selected.isEmpty()
@ -93,8 +97,28 @@ bool ActiveNetwork::needsIdentityProbe() const
|| 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)
{
m_failedIdentityProbes = 0;
if (isConfigured())
clearIdentity(available ? QStringLiteral("loading") : QStringLiteral("network_unknown"));
}
@ -103,6 +127,8 @@ void ActiveNetwork::reachabilityChanged(bool reachable, bool wasReachable)
{
if (!isConfigured())
return;
if (!reachable || !wasReachable)
m_failedIdentityProbes = 0;
if (!reachable)
clearIdentity(QStringLiteral("network_unknown"));
else if (!wasReachable)
@ -118,10 +144,13 @@ void ActiveNetwork::beginIdentityProbe()
void ActiveNetwork::finishIdentityProbe(const QString& identity)
{
if (identity.isEmpty()) {
m_failedIdentityProbes = (std::min)(m_failedIdentityProbes + 1, 6);
clearIdentity(QStringLiteral("network_unknown"));
} else if (identity != m_expectedIdentity) {
m_failedIdentityProbes = 0;
clearIdentity(QStringLiteral("network_mismatch"));
} else {
m_failedIdentityProbes = 0;
m_network.status = QStringLiteral("ready");
m_network.fingerprint = (isDevnet() ? QStringLiteral("channel:")
: QStringLiteral("block10:"))

View File

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

View File

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

View File

@ -40,7 +40,24 @@ int main()
"loaded network should await identity"))
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);
if (!expect(network.identityRetryDelayMs() == 0,
"sequencer changes should reset identity retry backoff"))
return 1;
network.finishIdentityProbe(QString(64, QLatin1Char('d')));
if (!expect(network.status() == QStringLiteral("network_mismatch"),
"wrong identity should block the network"))