mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-08-26 22:51:14 +00:00
perf(amm): cache verified token definitions
This commit is contained in:
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user