perf(amm): cache verified token definitions

This commit is contained in:
Ricardo Guilherme Schmidt
2026-07-17 21:39:09 -03:00
parent 6ccd37b231
commit b71be34edf
8 changed files with 744 additions and 15 deletions
+66 -5
View File
@@ -109,12 +109,33 @@ WalletAccountRead accountRead(const WalletAccount& account)
AmmUiBackend::AmmUiBackend(LogosAPI* logosAPI, QObject* parent)
: AmmUiBackendSimpleSource(parent),
m_logosAPI(logosAPI ? logosAPI : new LogosAPI("amm_ui", this)),
m_wallet(std::make_unique<LogosWalletProvider>(m_logosAPI)),
m_ownedWallet(std::make_unique<LogosWalletProvider>(m_logosAPI)),
m_wallet(m_ownedWallet.get()),
m_definitionCache(*m_wallet),
m_walletController(std::make_unique<WalletController>(
*m_wallet, QStringLiteral("AmmUI"))),
m_networkManager(new QNetworkAccessManager(this)),
m_tokenIdl(resource(QStringLiteral(":/amm/idl/token-idl.json"))),
m_ammIdl(resource(QStringLiteral(":/amm/idl/amm-idl.json")))
{
initialize();
}
AmmUiBackend::AmmUiBackend(WalletProvider& wallet, QObject* parent)
: AmmUiBackendSimpleSource(parent),
m_logosAPI(nullptr),
m_wallet(&wallet),
m_definitionCache(*m_wallet),
m_walletController(std::make_unique<WalletController>(
*m_wallet, QStringLiteral("AmmUI"))),
m_networkManager(new QNetworkAccessManager(this)),
m_tokenIdl(resource(QStringLiteral(":/amm/idl/token-idl.json"))),
m_ammIdl(resource(QStringLiteral(":/amm/idl/amm-idl.json")))
{
initialize();
}
void AmmUiBackend::initialize()
{
setAssets({});
setAssetStatus(QStringLiteral("idle"));
@@ -198,6 +219,10 @@ bool AmmUiBackend::setPrimaryAccount(QString accountId)
void AmmUiBackend::syncWalletState()
{
const WalletUiState& state = m_walletController->state();
if (state.syncStatus == QStringLiteral("opening")
|| state.syncStatus == QStringLiteral("syncing")) {
m_definitionCache.cancelPending();
}
const QString previousAddress = sequencerAddr();
const bool wasReachable = sequencerReachable();
setIsWalletOpen(state.isWalletOpen);
@@ -274,41 +299,71 @@ void AmmUiBackend::refreshPortfolio()
{
const quint64 generation = ++m_portfolioGeneration;
if (!m_walletController->state().isWalletOpen) {
m_definitionCache.cancelPending();
setAssets({});
setAssetStatus(QStringLiteral("idle"));
setAssetError({});
return;
}
if (m_network.status() != QStringLiteral("ready")) {
invalidateDefinitionCache();
setAssets({});
setAssetStatus(QStringLiteral("blocked"));
setAssetError(m_network.status());
return;
}
if (m_tokenIdl.isEmpty()) {
invalidateDefinitionCache();
setAssetStatus(QStringLiteral("error"));
setAssetError(QStringLiteral("token_idl_missing"));
return;
}
const TokenDefinitionCacheKey key = definitionCacheKey(m_network.snapshot());
setAssetStatus(QStringLiteral("loading"));
setAssetError({});
m_wallet->readPublicAccountsAsync(
m_network.snapshot().tokenIds,
[this, generation](QVector<WalletAccountRead> reads) {
applyDefinitions(generation, reads);
if (m_appliedDefinitionKey && *m_appliedDefinitionKey == key
&& m_definitionCache.contains(key)) {
applyWalletPortfolio(generation);
return;
}
m_definitionCache.read(
key,
[this, generation, key](QVector<WalletAccountRead> reads) {
applyDefinitions(generation, key, reads);
});
}
TokenDefinitionCacheKey AmmUiBackend::definitionCacheKey(
const ActiveNetworkSnapshot& network) const
{
return {
network.id,
network.fingerprint,
sequencerAddr(),
network.tokenIds,
};
}
void AmmUiBackend::invalidateDefinitionCache()
{
m_definitionCache.clear();
m_appliedDefinitionKey.reset();
}
void AmmUiBackend::applyDefinitions(
quint64 generation,
const TokenDefinitionCacheKey& key,
const QVector<WalletAccountRead>& reads)
{
if (generation != m_portfolioGeneration)
return;
const ActiveNetworkSnapshot network = m_network.snapshot();
if (!(key == definitionCacheKey(network)))
return;
const WalletDecodeResult decoded = WalletIdlDecoder::decode(m_tokenIdl, reads);
if (!decoded.ok() || reads.size() != network.tokenIds.size()
|| decoded.accounts.size() != reads.size()) {
invalidateDefinitionCache();
setAssetStatus(QStringLiteral("error"));
setAssetError(decoded.error.isEmpty()
? QStringLiteral("definition_decode_failed")
@@ -338,6 +393,7 @@ void AmmUiBackend::applyDefinitions(
if (m_tokenProgramId.isEmpty())
m_tokenProgramId = read.programOwner;
else if (m_tokenProgramId != read.programOwner) {
invalidateDefinitionCache();
setAssets({});
setAssetStatus(QStringLiteral("error"));
setAssetError(QStringLiteral("token_program_mismatch"));
@@ -349,6 +405,7 @@ void AmmUiBackend::applyDefinitions(
m_tokens.append(std::move(token));
}
if (m_tokenProgramId.isEmpty()) {
invalidateDefinitionCache();
setAssets({});
setAssetStatus(QStringLiteral("error"));
setAssetError(QStringLiteral("definitions_unavailable"));
@@ -356,6 +413,10 @@ void AmmUiBackend::applyDefinitions(
}
m_idlRegistry.registerProgram(
m_tokenProgramId, QStringLiteral("Token"), m_tokenIdl);
if (unavailable > 0)
invalidateDefinitionCache();
else
m_appliedDefinitionKey = key;
setAssetError(unavailable > 0
? QStringLiteral("some_definitions_unavailable")
: QString());
+13 -1
View File
@@ -2,6 +2,7 @@
#define AMM_UI_BACKEND_H
#include <memory>
#include <optional>
#include <QHash>
#include <QString>
@@ -10,6 +11,7 @@
#include "rep_AmmUiBackend_source.h"
#include "ActiveNetwork.h"
#include "TokenDefinitionCache.h"
#include "WalletAccountModel.h"
#include "WalletIdlDecoder.h"
@@ -24,6 +26,8 @@ class AmmUiBackend : public AmmUiBackendSimpleSource {
public:
explicit AmmUiBackend(LogosAPI* logosAPI = nullptr, QObject* parent = nullptr);
// The injected provider must outlive the backend.
explicit AmmUiBackend(WalletProvider& wallet, QObject* parent = nullptr);
~AmmUiBackend() override;
WalletAccountModel* accountModel() const;
@@ -51,14 +55,21 @@ private:
void syncWalletState();
void publishNetworkState();
void initialize();
void probeNetworkIdentity();
void refreshPortfolio();
TokenDefinitionCacheKey definitionCacheKey(
const ActiveNetworkSnapshot& network) const;
void invalidateDefinitionCache();
void applyDefinitions(quint64 generation,
const TokenDefinitionCacheKey& key,
const QVector<WalletAccountRead>& reads);
void applyWalletPortfolio(quint64 generation);
LogosAPI* m_logosAPI;
std::unique_ptr<LogosWalletProvider> m_wallet;
std::unique_ptr<LogosWalletProvider> m_ownedWallet;
WalletProvider* m_wallet;
TokenDefinitionCache m_definitionCache;
std::unique_ptr<WalletController> m_walletController;
QNetworkAccessManager* m_networkManager;
ActiveNetwork m_network;
@@ -67,6 +78,7 @@ private:
WalletIdlRegistry m_idlRegistry;
QVector<TokenInfo> m_tokens;
QString m_tokenProgramId;
std::optional<TokenDefinitionCacheKey> m_appliedDefinitionKey;
bool m_identityProbeInFlight = false;
quint64 m_portfolioGeneration = 0;
};
+104
View File
@@ -0,0 +1,104 @@
#include "TokenDefinitionCache.h"
#include <utility>
bool TokenDefinitionCacheKey::isReusable() const
{
return !networkId.isEmpty()
&& !networkFingerprint.isEmpty()
&& !sequencerAddress.isEmpty()
&& !tokenIds.isEmpty();
}
bool TokenDefinitionCacheKey::operator==(const TokenDefinitionCacheKey& other) const
{
return networkId == other.networkId
&& networkFingerprint == other.networkFingerprint
&& sequencerAddress == other.sequencerAddress
&& tokenIds == other.tokenIds;
}
TokenDefinitionCache::TokenDefinitionCache(WalletProvider& provider)
: m_provider(provider),
m_state(std::make_shared<State>())
{
}
TokenDefinitionCache::~TokenDefinitionCache()
{
clear();
}
void TokenDefinitionCache::read(const TokenDefinitionCacheKey& key, Callback callback)
{
if (contains(key)) {
callback(m_state->cachedReads);
return;
}
if (m_state->inFlight && m_state->inFlight->key == key) {
m_state->inFlight->callbacks.append(std::move(callback));
return;
}
cancelPending();
const auto request = std::make_shared<InFlight>();
request->key = key;
request->callbacks.append(std::move(callback));
m_state->inFlight = request;
const std::weak_ptr<State> state = m_state;
m_provider.readPublicAccountsAsync(
key.tokenIds,
[state, request](QVector<WalletAccountRead> reads) mutable {
const std::shared_ptr<State> lockedState = state.lock();
if (!lockedState || request->cancelled)
return;
if (lockedState->inFlight == request) {
lockedState->inFlight.reset();
if (TokenDefinitionCache::isComplete(request->key, reads)) {
lockedState->cachedKey = request->key;
lockedState->cachedReads = reads;
}
}
QVector<Callback> callbacks = std::move(request->callbacks);
for (Callback& callback : callbacks)
callback(reads);
});
}
bool TokenDefinitionCache::contains(const TokenDefinitionCacheKey& key) const
{
return m_state->cachedKey && *m_state->cachedKey == key;
}
void TokenDefinitionCache::cancelPending()
{
if (m_state->inFlight) {
m_state->inFlight->cancelled = true;
m_state->inFlight->callbacks.clear();
}
m_state->inFlight.reset();
}
void TokenDefinitionCache::clear()
{
m_state->cachedKey.reset();
m_state->cachedReads.clear();
cancelPending();
}
bool TokenDefinitionCache::isComplete(
const TokenDefinitionCacheKey& key,
const QVector<WalletAccountRead>& reads)
{
if (!key.isReusable() || reads.size() != key.tokenIds.size())
return false;
for (qsizetype index = 0; index < reads.size(); ++index) {
const WalletAccountRead& read = reads.at(index);
if (!read.ok() || read.accountId != key.tokenIds.at(index)
|| read.programOwner.isEmpty() || read.dataHex.isEmpty()) {
return false;
}
}
return true;
}
+53
View File
@@ -0,0 +1,53 @@
#pragma once
#include <functional>
#include <memory>
#include <optional>
#include <QString>
#include <QStringList>
#include <QVector>
#include "WalletProvider.h"
struct TokenDefinitionCacheKey {
QString networkId;
QString networkFingerprint;
QString sequencerAddress;
QStringList tokenIds;
bool isReusable() const;
bool operator==(const TokenDefinitionCacheKey& other) const;
};
class TokenDefinitionCache final {
public:
using Callback = std::function<void(QVector<WalletAccountRead>)>;
explicit TokenDefinitionCache(WalletProvider& provider);
~TokenDefinitionCache();
void read(const TokenDefinitionCacheKey& key, Callback callback);
bool contains(const TokenDefinitionCacheKey& key) const;
void cancelPending();
void clear();
private:
struct InFlight {
TokenDefinitionCacheKey key;
QVector<Callback> callbacks;
bool cancelled = false;
};
struct State {
std::optional<TokenDefinitionCacheKey> cachedKey;
QVector<WalletAccountRead> cachedReads;
std::shared_ptr<InFlight> inFlight;
};
static bool isComplete(const TokenDefinitionCacheKey& key,
const QVector<WalletAccountRead>& reads);
WalletProvider& m_provider;
std::shared_ptr<State> m_state;
};