mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-07-23 07:19:28 +00:00
fix(amm): submit wallet mutations asynchronously
This commit is contained in:
parent
02a65aaf09
commit
cfaaba3bce
@ -191,6 +191,7 @@ void AmmUiBackend::syncWalletState()
|
|||||||
{
|
{
|
||||||
const WalletUiState& state = m_walletController->state();
|
const WalletUiState& state = m_walletController->state();
|
||||||
const bool walletWasOpen = isWalletOpen();
|
const bool walletWasOpen = isWalletOpen();
|
||||||
|
const bool walletCouldSubmit = walletCanSubmit();
|
||||||
const bool wasReachable = sequencerReachable();
|
const bool wasReachable = sequencerReachable();
|
||||||
const QString previousAddress = sequencerAddr();
|
const QString previousAddress = sequencerAddr();
|
||||||
|
|
||||||
@ -212,6 +213,8 @@ void AmmUiBackend::syncWalletState()
|
|||||||
m_sequencer->configure(state.configPath);
|
m_sequencer->configure(state.configPath);
|
||||||
|
|
||||||
const bool addressChanged = previousAddress != state.sequencerAddress;
|
const bool addressChanged = previousAddress != state.sequencerAddress;
|
||||||
|
if ((walletCouldSubmit && !state.canSubmit()) || addressChanged)
|
||||||
|
m_newPosition->cancelSubmit();
|
||||||
if (addressChanged) {
|
if (addressChanged) {
|
||||||
m_identityRetryTimer->stop();
|
m_identityRetryTimer->stop();
|
||||||
m_pendingTransactions.clear();
|
m_pendingTransactions.clear();
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
#include <QPointer>
|
||||||
#include <QScopedValueRollback>
|
#include <QScopedValueRollback>
|
||||||
#include <libbase58.h>
|
#include <libbase58.h>
|
||||||
|
|
||||||
@ -171,6 +172,7 @@ NewPositionRuntime::NewPositionRuntime(WalletProvider* wallet,
|
|||||||
void NewPositionRuntime::clearWalletAccounts()
|
void NewPositionRuntime::clearWalletAccounts()
|
||||||
{
|
{
|
||||||
++m_walletGeneration;
|
++m_walletGeneration;
|
||||||
|
cancelSubmit();
|
||||||
m_walletPublicAccountIds.clear();
|
m_walletPublicAccountIds.clear();
|
||||||
m_wallet->clearSnapshot();
|
m_wallet->clearSnapshot();
|
||||||
}
|
}
|
||||||
@ -182,11 +184,44 @@ void NewPositionRuntime::setWalletAccounts(const QVector<WalletAccount>& account
|
|||||||
if (account.isPublic)
|
if (account.isPublic)
|
||||||
publicAccountIds.append(account.address);
|
publicAccountIds.append(account.address);
|
||||||
}
|
}
|
||||||
if (publicAccountIds != m_walletPublicAccountIds)
|
if (publicAccountIds != m_walletPublicAccountIds) {
|
||||||
++m_walletGeneration;
|
++m_walletGeneration;
|
||||||
|
cancelSubmit();
|
||||||
|
}
|
||||||
m_walletPublicAccountIds = std::move(publicAccountIds);
|
m_walletPublicAccountIds = std::move(publicAccountIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NewPositionRuntime::cancelSubmit()
|
||||||
|
{
|
||||||
|
if (!m_submitInFlight)
|
||||||
|
return;
|
||||||
|
++m_submitGeneration;
|
||||||
|
m_submitInFlight = false;
|
||||||
|
ResultCallback callback = std::move(m_submitCallback);
|
||||||
|
m_submitCallback = {};
|
||||||
|
if (callback)
|
||||||
|
callback(publicError(QStringLiteral("wallet_unavailable")).toVariantMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
void NewPositionRuntime::finishSubmit(quint64 submitGeneration, QVariantMap result)
|
||||||
|
{
|
||||||
|
if (!m_submitInFlight || submitGeneration != m_submitGeneration)
|
||||||
|
return;
|
||||||
|
m_submitInFlight = false;
|
||||||
|
ResultCallback callback = std::move(m_submitCallback);
|
||||||
|
m_submitCallback = {};
|
||||||
|
if (callback)
|
||||||
|
callback(std::move(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NewPositionRuntime::submitIsCurrent(quint64 submitGeneration,
|
||||||
|
quint64 walletGeneration) const
|
||||||
|
{
|
||||||
|
return m_submitInFlight
|
||||||
|
&& submitGeneration == m_submitGeneration
|
||||||
|
&& walletGeneration == m_walletGeneration;
|
||||||
|
}
|
||||||
|
|
||||||
QJsonArray NewPositionRuntime::walletAccountReads(bool walletOpen, bool refresh) const
|
QJsonArray NewPositionRuntime::walletAccountReads(bool walletOpen, bool refresh) const
|
||||||
{
|
{
|
||||||
if (!walletOpen)
|
if (!walletOpen)
|
||||||
@ -280,14 +315,20 @@ void NewPositionRuntime::contextAsync(const QVariantMap& request,
|
|||||||
}
|
}
|
||||||
const QString configId = configResult.value.value(
|
const QString configId = configResult.value.value(
|
||||||
QStringLiteral("configId")).toString();
|
QStringLiteral("configId")).toString();
|
||||||
|
QPointer<NewPositionRuntime> guard(this);
|
||||||
m_sequencer->readAccounts({ configId }, refreshPublicData,
|
m_sequencer->readAccounts({ configId }, refreshPublicData,
|
||||||
[this, request, network, walletOpen, refreshPublicData,
|
[guard, request, network, walletOpen, refreshPublicData,
|
||||||
callback = std::move(callback)](QVector<WalletAccountRead> configReads) mutable {
|
callback = std::move(callback)](QVector<WalletAccountRead> configReads) mutable {
|
||||||
|
if (!guard)
|
||||||
|
return;
|
||||||
const QJsonObject config = accountReadJson(configReads.value(0));
|
const QJsonObject config = accountReadJson(configReads.value(0));
|
||||||
m_sequencer->readAccounts(m_walletPublicAccountIds, refreshPublicData,
|
guard->m_sequencer->readAccounts(
|
||||||
[this, request, network, walletOpen, refreshPublicData, config,
|
guard->m_walletPublicAccountIds, refreshPublicData,
|
||||||
|
[guard, request, network, walletOpen, refreshPublicData, config,
|
||||||
callback = std::move(callback)](
|
callback = std::move(callback)](
|
||||||
QVector<WalletAccountRead> walletReads) mutable {
|
QVector<WalletAccountRead> walletReads) mutable {
|
||||||
|
if (!guard)
|
||||||
|
return;
|
||||||
const QJsonObject hints = QJsonObject::fromVariantMap(request);
|
const QJsonObject hints = QJsonObject::fromVariantMap(request);
|
||||||
QJsonArray configured;
|
QJsonArray configured;
|
||||||
for (const QString& id : network.tokenIds)
|
for (const QString& id : network.tokenIds)
|
||||||
@ -297,7 +338,7 @@ void NewPositionRuntime::contextAsync(const QVariantMap& request,
|
|||||||
const QJsonArray resolved = variantStringArray(
|
const QJsonArray resolved = variantStringArray(
|
||||||
hints.value(QStringLiteral("resolvedTokenIds")).toVariant());
|
hints.value(QStringLiteral("resolvedTokenIds")).toVariant());
|
||||||
const QJsonArray walletAccounts = accountReadsJson(walletReads);
|
const QJsonArray walletAccounts = accountReadsJson(walletReads);
|
||||||
const AmmClientResult tokenResult = m_client->tokenIds(QJsonObject {
|
const AmmClientResult tokenResult = guard->m_client->tokenIds(QJsonObject {
|
||||||
{ QStringLiteral("ammProgramId"), network.ammProgramId },
|
{ QStringLiteral("ammProgramId"), network.ammProgramId },
|
||||||
{ QStringLiteral("config"), config },
|
{ QStringLiteral("config"), config },
|
||||||
{ QStringLiteral("walletAccounts"), walletAccounts },
|
{ QStringLiteral("walletAccounts"), walletAccounts },
|
||||||
@ -322,12 +363,14 @@ void NewPositionRuntime::contextAsync(const QVariantMap& request,
|
|||||||
.value(QStringLiteral("tokenIds")).toArray()) {
|
.value(QStringLiteral("tokenIds")).toArray()) {
|
||||||
definitionIds.append(id.toString());
|
definitionIds.append(id.toString());
|
||||||
}
|
}
|
||||||
m_sequencer->readAccounts(definitionIds, refreshPublicData,
|
guard->m_sequencer->readAccounts(definitionIds, refreshPublicData,
|
||||||
[this, network, walletOpen, config, walletAccounts,
|
[guard, network, walletOpen, config, walletAccounts,
|
||||||
configured, recent, resolved,
|
configured, recent, resolved,
|
||||||
callback = std::move(callback)](
|
callback = std::move(callback)](
|
||||||
QVector<WalletAccountRead> definitions) mutable {
|
QVector<WalletAccountRead> definitions) mutable {
|
||||||
const AmmClientResult result = m_client->context(QJsonObject {
|
if (!guard)
|
||||||
|
return;
|
||||||
|
const AmmClientResult result = guard->m_client->context(QJsonObject {
|
||||||
{ QStringLiteral("networkId"), network.id },
|
{ QStringLiteral("networkId"), network.id },
|
||||||
{ QStringLiteral("networkFingerprint"), network.fingerprint },
|
{ QStringLiteral("networkFingerprint"), network.fingerprint },
|
||||||
{ QStringLiteral("ammProgramId"), network.ammProgramId },
|
{ QStringLiteral("ammProgramId"), network.ammProgramId },
|
||||||
@ -354,7 +397,7 @@ void NewPositionRuntime::buildQuoteInputAsync(
|
|||||||
const ActiveNetworkSnapshot& network,
|
const ActiveNetworkSnapshot& network,
|
||||||
bool walletOpen,
|
bool walletOpen,
|
||||||
bool forceRefresh,
|
bool forceRefresh,
|
||||||
std::function<void(QJsonObject, QJsonObject)> callback) const
|
std::function<void(QJsonObject, QJsonObject)> callback)
|
||||||
{
|
{
|
||||||
if (network.status != QStringLiteral("ready")) {
|
if (network.status != QStringLiteral("ready")) {
|
||||||
callback({}, publicError(network.status));
|
callback({}, publicError(network.status));
|
||||||
@ -372,12 +415,15 @@ void NewPositionRuntime::buildQuoteInputAsync(
|
|||||||
}
|
}
|
||||||
const QString configId = configResult.value.value(
|
const QString configId = configResult.value.value(
|
||||||
QStringLiteral("configId")).toString();
|
QStringLiteral("configId")).toString();
|
||||||
|
QPointer<NewPositionRuntime> guard(this);
|
||||||
m_sequencer->readAccounts({ configId }, forceRefresh,
|
m_sequencer->readAccounts({ configId }, forceRefresh,
|
||||||
[this, request, network, walletOpen, forceRefresh,
|
[guard, request, network, walletOpen, forceRefresh,
|
||||||
callback = std::move(callback)](QVector<WalletAccountRead> configReads) mutable {
|
callback = std::move(callback)](QVector<WalletAccountRead> configReads) mutable {
|
||||||
|
if (!guard)
|
||||||
|
return;
|
||||||
const QJsonObject config = accountReadJson(configReads.value(0));
|
const QJsonObject config = accountReadJson(configReads.value(0));
|
||||||
const QJsonObject requestObject = QJsonObject::fromVariantMap(request);
|
const QJsonObject requestObject = QJsonObject::fromVariantMap(request);
|
||||||
const AmmClientResult pairResult = m_client->pairIds(QJsonObject {
|
const AmmClientResult pairResult = guard->m_client->pairIds(QJsonObject {
|
||||||
{ QStringLiteral("ammProgramId"), network.ammProgramId },
|
{ QStringLiteral("ammProgramId"), network.ammProgramId },
|
||||||
{ QStringLiteral("config"), config },
|
{ QStringLiteral("config"), config },
|
||||||
{ QStringLiteral("tokenAId"),
|
{ QStringLiteral("tokenAId"),
|
||||||
@ -407,15 +453,20 @@ void NewPositionRuntime::buildQuoteInputAsync(
|
|||||||
pair.value(QStringLiteral("currentTickId")).toString(),
|
pair.value(QStringLiteral("currentTickId")).toString(),
|
||||||
pair.value(QStringLiteral("clockId")).toString(),
|
pair.value(QStringLiteral("clockId")).toString(),
|
||||||
};
|
};
|
||||||
m_sequencer->readAccounts(fixedIds, forceRefresh,
|
guard->m_sequencer->readAccounts(fixedIds, forceRefresh,
|
||||||
[this, requestObject, network, walletOpen, config, pair,
|
[guard, requestObject, network, walletOpen, config, pair,
|
||||||
callback = std::move(callback)](
|
callback = std::move(callback)](
|
||||||
QVector<WalletAccountRead> fixedReads) mutable {
|
QVector<WalletAccountRead> fixedReads) mutable {
|
||||||
m_sequencer->readAccounts(m_walletPublicAccountIds, false,
|
if (!guard)
|
||||||
[this, requestObject, network, walletOpen, config, pair,
|
return;
|
||||||
|
guard->m_sequencer->readAccounts(
|
||||||
|
guard->m_walletPublicAccountIds, false,
|
||||||
|
[guard, requestObject, network, walletOpen, config, pair,
|
||||||
fixedReads = std::move(fixedReads),
|
fixedReads = std::move(fixedReads),
|
||||||
callback = std::move(callback)](
|
callback = std::move(callback)](
|
||||||
QVector<WalletAccountRead> walletReads) mutable {
|
QVector<WalletAccountRead> walletReads) mutable {
|
||||||
|
if (!guard)
|
||||||
|
return;
|
||||||
QStringList selectedIds;
|
QStringList selectedIds;
|
||||||
for (const QString& key : {
|
for (const QString& key : {
|
||||||
QStringLiteral("holdingAId"),
|
QStringLiteral("holdingAId"),
|
||||||
@ -426,8 +477,7 @@ void NewPositionRuntime::buildQuoteInputAsync(
|
|||||||
if (!id.isEmpty() && !selectedIds.contains(id))
|
if (!id.isEmpty() && !selectedIds.contains(id))
|
||||||
selectedIds.append(id);
|
selectedIds.append(id);
|
||||||
}
|
}
|
||||||
auto finish = [this, requestObject, network, walletOpen,
|
auto finish = [requestObject, network, walletOpen, config,
|
||||||
config, pair,
|
|
||||||
fixedReads = std::move(fixedReads),
|
fixedReads = std::move(fixedReads),
|
||||||
walletReads = std::move(walletReads),
|
walletReads = std::move(walletReads),
|
||||||
callback = std::move(callback)](
|
callback = std::move(callback)](
|
||||||
@ -464,7 +514,7 @@ void NewPositionRuntime::buildQuoteInputAsync(
|
|||||||
if (selectedIds.isEmpty())
|
if (selectedIds.isEmpty())
|
||||||
finish({});
|
finish({});
|
||||||
else
|
else
|
||||||
m_sequencer->readAccounts(
|
guard->m_sequencer->readAccounts(
|
||||||
selectedIds, true, std::move(finish));
|
selectedIds, true, std::move(finish));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -477,14 +527,17 @@ void NewPositionRuntime::quoteAsync(const QVariantMap& request,
|
|||||||
bool forceRefresh,
|
bool forceRefresh,
|
||||||
ResultCallback callback)
|
ResultCallback callback)
|
||||||
{
|
{
|
||||||
|
QPointer<NewPositionRuntime> guard(this);
|
||||||
buildQuoteInputAsync(request, network, walletOpen, forceRefresh,
|
buildQuoteInputAsync(request, network, walletOpen, forceRefresh,
|
||||||
[this, callback = std::move(callback)](
|
[guard, callback = std::move(callback)](
|
||||||
QJsonObject input, QJsonObject error) mutable {
|
QJsonObject input, QJsonObject error) mutable {
|
||||||
|
if (!guard)
|
||||||
|
return;
|
||||||
if (!error.isEmpty()) {
|
if (!error.isEmpty()) {
|
||||||
callback(error.toVariantMap());
|
callback(error.toVariantMap());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const AmmClientResult result = m_client->quote(input);
|
const AmmClientResult result = guard->m_client->quote(input);
|
||||||
callback((result.ok ? result.value
|
callback((result.ok ? result.value
|
||||||
: publicError(QStringLiteral("backend_error")))
|
: publicError(QStringLiteral("backend_error")))
|
||||||
.toVariantMap());
|
.toVariantMap());
|
||||||
@ -506,114 +559,154 @@ void NewPositionRuntime::submitAsync(const QVariantMap& request,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_submitInFlight = true;
|
m_submitInFlight = true;
|
||||||
|
const quint64 submitGeneration = ++m_submitGeneration;
|
||||||
const quint64 walletGeneration = m_walletGeneration;
|
const quint64 walletGeneration = m_walletGeneration;
|
||||||
auto finish = [this, callback = std::move(callback)](QVariantMap result) mutable {
|
m_submitCallback = std::move(callback);
|
||||||
m_submitInFlight = false;
|
QPointer<NewPositionRuntime> guard(this);
|
||||||
callback(std::move(result));
|
|
||||||
};
|
|
||||||
buildQuoteInputAsync(request, network, true, true,
|
buildQuoteInputAsync(request, network, true, true,
|
||||||
[this, quoteHash, walletGeneration, finish = std::move(finish)](
|
[guard, quoteHash, submitGeneration, walletGeneration](
|
||||||
QJsonObject input, QJsonObject error) mutable {
|
QJsonObject input, QJsonObject error) mutable {
|
||||||
if (walletGeneration != m_walletGeneration) {
|
if (!guard
|
||||||
finish(publicError(QStringLiteral("wallet_unavailable")).toVariantMap());
|
|| !guard->submitIsCurrent(submitGeneration, walletGeneration))
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
if (!error.isEmpty()) {
|
if (!error.isEmpty()) {
|
||||||
finish(error.toVariantMap());
|
guard->finishSubmit(submitGeneration, error.toVariantMap());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const AmmClientResult quoteResult = m_client->quote(input);
|
const AmmClientResult quoteResult = guard->m_client->quote(input);
|
||||||
if (!quoteResult.ok) {
|
if (!quoteResult.ok) {
|
||||||
finish(publicError(QStringLiteral("backend_error")).toVariantMap());
|
guard->finishSubmit(
|
||||||
|
submitGeneration,
|
||||||
|
publicError(QStringLiteral("backend_error")).toVariantMap());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const QJsonObject quote = quoteResult.value;
|
const QJsonObject quote = quoteResult.value;
|
||||||
if (quote.value(QStringLiteral("quoteHash")).toString() != quoteHash) {
|
if (quote.value(QStringLiteral("quoteHash")).toString() != quoteHash) {
|
||||||
QJsonObject result = publicError(QStringLiteral("quote_changed"));
|
QJsonObject result = publicError(QStringLiteral("quote_changed"));
|
||||||
result.insert(QStringLiteral("quote"), quote);
|
result.insert(QStringLiteral("quote"), quote);
|
||||||
finish(result.toVariantMap());
|
guard->finishSubmit(submitGeneration, result.toVariantMap());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!quote.value(QStringLiteral("canSubmit")).toBool(false)) {
|
if (!quote.value(QStringLiteral("canSubmit")).toBool(false)) {
|
||||||
QJsonObject result = publicError(QStringLiteral("quote_not_submittable"));
|
QJsonObject result = publicError(QStringLiteral("quote_not_submittable"));
|
||||||
result.insert(QStringLiteral("quote"), quote);
|
result.insert(QStringLiteral("quote"), quote);
|
||||||
finish(result.toVariantMap());
|
guard->finishSubmit(submitGeneration, result.toVariantMap());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonValue freshLp;
|
|
||||||
if (quote.value(QStringLiteral("requiresFreshLp")).toBool(false)) {
|
if (quote.value(QStringLiteral("requiresFreshLp")).toBool(false)) {
|
||||||
if (walletGeneration != m_walletGeneration) {
|
guard->m_wallet->createAccountAsync(
|
||||||
finish(publicError(QStringLiteral("wallet_unavailable")).toVariantMap());
|
true,
|
||||||
return;
|
[guard, input = std::move(input), quoteHash,
|
||||||
}
|
submitGeneration, walletGeneration](
|
||||||
const WalletAccountCreation creation = m_wallet->createAccount(true);
|
WalletAccountCreation creation) mutable {
|
||||||
if (!creation.ok() || !creation.publicAccount.ok()) {
|
if (!guard
|
||||||
finish(publicError(QStringLiteral("wallet_submission_failed")).toVariantMap());
|
|| !guard->submitIsCurrent(
|
||||||
return;
|
submitGeneration, walletGeneration))
|
||||||
}
|
return;
|
||||||
freshLp = accountReadJson(creation.publicAccount);
|
if (!creation.ok() || !creation.publicAccount.ok()) {
|
||||||
if (!m_walletPublicAccountIds.contains(creation.accountId))
|
const QString code = creation.failure
|
||||||
m_walletPublicAccountIds.append(creation.accountId);
|
== WalletFailure::WalletUnavailable
|
||||||
|
? QStringLiteral("wallet_unavailable")
|
||||||
|
: QStringLiteral("wallet_submission_failed");
|
||||||
|
guard->finishSubmit(
|
||||||
|
submitGeneration, publicError(code).toVariantMap());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!guard->m_walletPublicAccountIds.contains(creation.accountId))
|
||||||
|
guard->m_walletPublicAccountIds.append(creation.accountId);
|
||||||
|
guard->submitPlanAsync(
|
||||||
|
std::move(input), quoteHash,
|
||||||
|
accountReadJson(creation.publicAccount),
|
||||||
|
submitGeneration, walletGeneration);
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
guard->submitPlanAsync(
|
||||||
|
std::move(input), quoteHash, {},
|
||||||
|
submitGeneration, walletGeneration);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
QJsonObject planInput = input;
|
void NewPositionRuntime::submitPlanAsync(QJsonObject input,
|
||||||
planInput.insert(QStringLiteral("quoteHash"), quoteHash);
|
const QString& quoteHash,
|
||||||
planInput.insert(QStringLiteral("nowMs"), QDateTime::currentMSecsSinceEpoch());
|
QJsonValue freshLp,
|
||||||
if (!freshLp.isUndefined())
|
quint64 submitGeneration,
|
||||||
planInput.insert(QStringLiteral("freshLp"), freshLp);
|
quint64 walletGeneration)
|
||||||
const AmmClientResult planResult = m_client->plan(planInput);
|
{
|
||||||
if (!planResult.ok) {
|
if (!submitIsCurrent(submitGeneration, walletGeneration))
|
||||||
finish(publicError(QStringLiteral("backend_error")).toVariantMap());
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
const QJsonObject plan = planResult.value;
|
|
||||||
if (plan.value(QStringLiteral("status")).toString()
|
|
||||||
!= QStringLiteral("ready")) {
|
|
||||||
const QString code = plan.value(QStringLiteral("code")).toString();
|
|
||||||
finish(publicError(code.isEmpty()
|
|
||||||
? QStringLiteral("wallet_submission_failed") : code).toVariantMap());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QStringList accountIds = jsonStringList(
|
input.insert(QStringLiteral("quoteHash"), quoteHash);
|
||||||
plan.value(QStringLiteral("accountIds")).toArray());
|
input.insert(QStringLiteral("nowMs"), QDateTime::currentMSecsSinceEpoch());
|
||||||
const QVector<bool> signingRequirements = jsonBoolList(
|
if (!freshLp.isUndefined() && !freshLp.isNull())
|
||||||
plan.value(QStringLiteral("signingRequirements")).toArray());
|
input.insert(QStringLiteral("freshLp"), std::move(freshLp));
|
||||||
const QVector<quint32> instruction = jsonUIntList(
|
const AmmClientResult planResult = m_client->plan(input);
|
||||||
plan.value(QStringLiteral("instruction")).toArray());
|
if (!planResult.ok) {
|
||||||
bool deadlineValid = false;
|
finishSubmit(
|
||||||
const qulonglong deadline = plan.value(QStringLiteral("deadlineMs"))
|
submitGeneration,
|
||||||
.toString().toULongLong(&deadlineValid);
|
publicError(QStringLiteral("backend_error")).toVariantMap());
|
||||||
if (!deadlineValid
|
return;
|
||||||
|| static_cast<qulonglong>(QDateTime::currentMSecsSinceEpoch()) >= deadline) {
|
}
|
||||||
finish(publicError(QStringLiteral("transaction_deadline_expired")).toVariantMap());
|
const QJsonObject plan = planResult.value;
|
||||||
|
if (plan.value(QStringLiteral("status")).toString()
|
||||||
|
!= QStringLiteral("ready")) {
|
||||||
|
const QString code = plan.value(QStringLiteral("code")).toString();
|
||||||
|
finishSubmit(
|
||||||
|
submitGeneration,
|
||||||
|
publicError(code.isEmpty()
|
||||||
|
? QStringLiteral("wallet_submission_failed") : code).toVariantMap());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool deadlineValid = false;
|
||||||
|
const qulonglong deadline = plan.value(QStringLiteral("deadlineMs"))
|
||||||
|
.toString().toULongLong(&deadlineValid);
|
||||||
|
if (!deadlineValid
|
||||||
|
|| static_cast<qulonglong>(QDateTime::currentMSecsSinceEpoch()) >= deadline) {
|
||||||
|
finishSubmit(
|
||||||
|
submitGeneration,
|
||||||
|
publicError(QStringLiteral("transaction_deadline_expired")).toVariantMap());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!submitIsCurrent(submitGeneration, walletGeneration))
|
||||||
|
return;
|
||||||
|
|
||||||
|
WalletTransaction transaction {
|
||||||
|
plan.value(QStringLiteral("programId")).toString(),
|
||||||
|
jsonStringList(plan.value(QStringLiteral("accountIds")).toArray()),
|
||||||
|
jsonBoolList(plan.value(QStringLiteral("signingRequirements")).toArray()),
|
||||||
|
jsonUIntList(plan.value(QStringLiteral("instruction")).toArray()),
|
||||||
|
};
|
||||||
|
QPointer<NewPositionRuntime> guard(this);
|
||||||
|
m_wallet->submitPublicTransactionAsync(
|
||||||
|
transaction,
|
||||||
|
[guard, submitGeneration, walletGeneration, deadlineMs =
|
||||||
|
plan.value(QStringLiteral("deadlineMs")), affectedAccountIds =
|
||||||
|
plan.value(QStringLiteral("affectedAccountIds"))](
|
||||||
|
WalletSubmission submission) mutable {
|
||||||
|
if (!guard
|
||||||
|
|| !guard->submitIsCurrent(submitGeneration, walletGeneration))
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
if (walletGeneration != m_walletGeneration) {
|
|
||||||
finish(publicError(QStringLiteral("wallet_unavailable")).toVariantMap());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const WalletSubmission submission = m_wallet->submitPublicTransaction({
|
|
||||||
plan.value(QStringLiteral("programId")).toString(),
|
|
||||||
accountIds,
|
|
||||||
signingRequirements,
|
|
||||||
instruction,
|
|
||||||
});
|
|
||||||
const QString transactionId = submission.accepted()
|
const QString transactionId = submission.accepted()
|
||||||
? base58TransactionId(submission.nativeHash) : QString();
|
? base58TransactionId(submission.nativeHash) : QString();
|
||||||
if (transactionId.isEmpty()) {
|
if (transactionId.isEmpty()) {
|
||||||
finish(publicError(QStringLiteral("wallet_submission_failed")).toVariantMap());
|
const QString code = submission.failure
|
||||||
|
== WalletFailure::WalletUnavailable
|
||||||
|
? QStringLiteral("wallet_unavailable")
|
||||||
|
: QStringLiteral("wallet_submission_failed");
|
||||||
|
guard->finishSubmit(
|
||||||
|
submitGeneration, publicError(code).toVariantMap());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
finish(QJsonObject {
|
guard->finishSubmit(submitGeneration, QJsonObject {
|
||||||
{ QStringLiteral("schema"), QString::fromLatin1(SCHEMA) },
|
{ QStringLiteral("schema"), QString::fromLatin1(SCHEMA) },
|
||||||
{ QStringLiteral("status"), QStringLiteral("submitted") },
|
{ QStringLiteral("status"), QStringLiteral("submitted") },
|
||||||
{ QStringLiteral("transactionId"), transactionId },
|
{ QStringLiteral("transactionId"), transactionId },
|
||||||
{ QStringLiteral("nativeTransactionHash"), submission.nativeHash },
|
{ QStringLiteral("nativeTransactionHash"), submission.nativeHash },
|
||||||
{ QStringLiteral("deadlineMs"), plan.value(QStringLiteral("deadlineMs")) },
|
{ QStringLiteral("deadlineMs"), deadlineMs },
|
||||||
{ QStringLiteral("affectedAccountIds"),
|
{ QStringLiteral("affectedAccountIds"),
|
||||||
plan.value(QStringLiteral("affectedAccountIds")) },
|
affectedAccountIds },
|
||||||
}.toVariantMap());
|
}.toVariantMap());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QVariantMap>
|
#include <QVariantMap>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
@ -15,7 +16,7 @@ class WalletProvider;
|
|||||||
class SequencerClient;
|
class SequencerClient;
|
||||||
struct WalletAccount;
|
struct WalletAccount;
|
||||||
|
|
||||||
class NewPositionRuntime {
|
class NewPositionRuntime : public QObject {
|
||||||
public:
|
public:
|
||||||
using ResultCallback = std::function<void(QVariantMap)>;
|
using ResultCallback = std::function<void(QVariantMap)>;
|
||||||
|
|
||||||
@ -41,6 +42,7 @@ public:
|
|||||||
const ActiveNetworkSnapshot& network,
|
const ActiveNetworkSnapshot& network,
|
||||||
bool walletCanSubmit,
|
bool walletCanSubmit,
|
||||||
ResultCallback callback);
|
ResultCallback callback);
|
||||||
|
void cancelSubmit();
|
||||||
|
|
||||||
QVariantMap context(const QVariantMap& request,
|
QVariantMap context(const QVariantMap& request,
|
||||||
const ActiveNetworkSnapshot& network,
|
const ActiveNetworkSnapshot& network,
|
||||||
@ -65,7 +67,15 @@ private:
|
|||||||
const ActiveNetworkSnapshot& network,
|
const ActiveNetworkSnapshot& network,
|
||||||
bool walletOpen,
|
bool walletOpen,
|
||||||
bool forceRefresh,
|
bool forceRefresh,
|
||||||
std::function<void(QJsonObject, QJsonObject)> callback) const;
|
std::function<void(QJsonObject, QJsonObject)> callback);
|
||||||
|
void submitPlanAsync(QJsonObject input,
|
||||||
|
const QString& quoteHash,
|
||||||
|
QJsonValue freshLp,
|
||||||
|
quint64 submitGeneration,
|
||||||
|
quint64 walletGeneration);
|
||||||
|
void finishSubmit(quint64 submitGeneration, QVariantMap result);
|
||||||
|
bool submitIsCurrent(quint64 submitGeneration,
|
||||||
|
quint64 walletGeneration) const;
|
||||||
|
|
||||||
WalletProvider* m_wallet;
|
WalletProvider* m_wallet;
|
||||||
AmmClient* m_client;
|
AmmClient* m_client;
|
||||||
@ -73,4 +83,6 @@ private:
|
|||||||
QStringList m_walletPublicAccountIds;
|
QStringList m_walletPublicAccountIds;
|
||||||
bool m_submitInFlight = false;
|
bool m_submitInFlight = false;
|
||||||
quint64 m_walletGeneration = 0;
|
quint64 m_walletGeneration = 0;
|
||||||
|
quint64 m_submitGeneration = 0;
|
||||||
|
ResultCallback m_submitCallback;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -15,6 +15,8 @@
|
|||||||
#include <QTemporaryFile>
|
#include <QTemporaryFile>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -143,6 +145,15 @@ namespace {
|
|||||||
return first && second;
|
return first && second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool waitForCondition(const std::function<bool()>& condition)
|
||||||
|
{
|
||||||
|
QElapsedTimer timer;
|
||||||
|
timer.start();
|
||||||
|
while (!condition() && timer.elapsed() < 3000)
|
||||||
|
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
|
||||||
|
return condition();
|
||||||
|
}
|
||||||
|
|
||||||
class FakeWallet final : public WalletProvider {
|
class FakeWallet final : public WalletProvider {
|
||||||
public:
|
public:
|
||||||
WalletSession connect(const WalletPaths&) override
|
WalletSession connect(const WalletPaths&) override
|
||||||
@ -182,6 +193,17 @@ namespace {
|
|||||||
return creation;
|
return creation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void createAccountAsync(bool isPublic, AccountCreationCallback callback) override
|
||||||
|
{
|
||||||
|
WalletAccountCreation creation = createAccount(isPublic);
|
||||||
|
if (deferAccountCreation) {
|
||||||
|
pendingAccountCreation = std::move(callback);
|
||||||
|
pendingCreation = std::move(creation);
|
||||||
|
} else {
|
||||||
|
callback(std::move(creation));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
WalletAccountRead readPublicAccount(const QString& accountId) const override
|
WalletAccountRead readPublicAccount(const QString& accountId) const override
|
||||||
{
|
{
|
||||||
WalletAccountRead read;
|
WalletAccountRead read;
|
||||||
@ -197,6 +219,32 @@ namespace {
|
|||||||
return { WalletFailure::None, transactionHash };
|
return { WalletFailure::None, transactionHash };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void submitPublicTransactionAsync(
|
||||||
|
const WalletTransaction& transaction, SubmissionCallback callback) override
|
||||||
|
{
|
||||||
|
WalletSubmission submission = submitPublicTransaction(transaction);
|
||||||
|
if (deferSubmission) {
|
||||||
|
pendingSubmissionCallback = std::move(callback);
|
||||||
|
pendingSubmission = std::move(submission);
|
||||||
|
} else {
|
||||||
|
callback(std::move(submission));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void finishAccountCreation()
|
||||||
|
{
|
||||||
|
AccountCreationCallback callback = std::move(pendingAccountCreation);
|
||||||
|
if (callback)
|
||||||
|
callback(std::move(pendingCreation));
|
||||||
|
}
|
||||||
|
|
||||||
|
void finishSubmission()
|
||||||
|
{
|
||||||
|
SubmissionCallback callback = std::move(pendingSubmissionCallback);
|
||||||
|
if (callback)
|
||||||
|
callback(std::move(pendingSubmission));
|
||||||
|
}
|
||||||
|
|
||||||
void disconnect() override {}
|
void disconnect() override {}
|
||||||
|
|
||||||
QString transactionHash = QStringLiteral(
|
QString transactionHash = QStringLiteral(
|
||||||
@ -205,6 +253,12 @@ namespace {
|
|||||||
int createdAccounts = 0;
|
int createdAccounts = 0;
|
||||||
int submissions = 0;
|
int submissions = 0;
|
||||||
WalletTransaction submitted;
|
WalletTransaction submitted;
|
||||||
|
bool deferAccountCreation = false;
|
||||||
|
bool deferSubmission = false;
|
||||||
|
AccountCreationCallback pendingAccountCreation;
|
||||||
|
SubmissionCallback pendingSubmissionCallback;
|
||||||
|
WalletAccountCreation pendingCreation;
|
||||||
|
WalletSubmission pendingSubmission;
|
||||||
};
|
};
|
||||||
|
|
||||||
class FakeAmmClient final : public AmmClient {
|
class FakeAmmClient final : public AmmClient {
|
||||||
@ -625,5 +679,113 @@ int main(int argc, char** argv)
|
|||||||
"wallet change should cancel submit before side effects"))
|
"wallet change should cancel submit before side effects"))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
|
LocalRpcServer asyncSubmitServer;
|
||||||
|
if (!expect(asyncSubmitServer.listen(), "async-submit sequencer should listen"))
|
||||||
|
return 1;
|
||||||
|
if (!expect(sequencerConfig.resize(0) && sequencerConfig.seek(0),
|
||||||
|
"async-submit config should rewind"))
|
||||||
|
return 1;
|
||||||
|
sequencerConfig.write(QJsonDocument(QJsonObject {
|
||||||
|
{ QStringLiteral("sequencer_addr"), asyncSubmitServer.endpoint() },
|
||||||
|
}).toJson(QJsonDocument::Compact));
|
||||||
|
sequencerConfig.flush();
|
||||||
|
if (!expect(sequencer.configure(sequencerConfig.fileName()),
|
||||||
|
"async-submit sequencer should configure"))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
FakeWallet asyncWallet;
|
||||||
|
asyncWallet.deferAccountCreation = true;
|
||||||
|
asyncWallet.deferSubmission = true;
|
||||||
|
FakeAmmClient asyncClient;
|
||||||
|
NewPositionRuntime asyncRuntime(&asyncWallet, &asyncClient, &sequencer);
|
||||||
|
int asyncCallbackCount = 0;
|
||||||
|
QVariantMap asyncResult;
|
||||||
|
asyncRuntime.submitAsync(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true,
|
||||||
|
[&](QVariantMap result) {
|
||||||
|
++asyncCallbackCount;
|
||||||
|
asyncResult = std::move(result);
|
||||||
|
});
|
||||||
|
if (!expect(waitForCondition([&]() { return asyncWallet.createdAccounts == 1; }),
|
||||||
|
"submit should asynchronously request a fresh account"))
|
||||||
|
return 1;
|
||||||
|
if (!expect(asyncCallbackCount == 0 && asyncWallet.submissions == 0,
|
||||||
|
"account creation should not block or finish submission"))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
QVariantMap concurrentResult;
|
||||||
|
asyncRuntime.submitAsync(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true,
|
||||||
|
[&](QVariantMap result) { concurrentResult = std::move(result); });
|
||||||
|
if (!expect(concurrentResult.value(QStringLiteral("code")).toString()
|
||||||
|
== QStringLiteral("submit_in_progress"),
|
||||||
|
"concurrent submit should fail while async mutation is pending"))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
asyncWallet.finishAccountCreation();
|
||||||
|
if (!expect(waitForCondition([&]() { return asyncWallet.submissions == 1; }),
|
||||||
|
"fresh account completion should dispatch transaction"))
|
||||||
|
return 1;
|
||||||
|
if (!expect(asyncCallbackCount == 0,
|
||||||
|
"transaction submission should remain asynchronous"))
|
||||||
|
return 1;
|
||||||
|
asyncWallet.finishSubmission();
|
||||||
|
if (!expect(asyncCallbackCount == 1
|
||||||
|
&& asyncResult.value(QStringLiteral("status")).toString()
|
||||||
|
== QStringLiteral("submitted"),
|
||||||
|
"async wallet completion should finish exactly once"))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
FakeWallet cancelledMutationWallet;
|
||||||
|
cancelledMutationWallet.deferAccountCreation = true;
|
||||||
|
FakeAmmClient cancelledMutationClient;
|
||||||
|
NewPositionRuntime cancelledMutationRuntime(
|
||||||
|
&cancelledMutationWallet, &cancelledMutationClient, &sequencer);
|
||||||
|
int cancelledMutationCallbacks = 0;
|
||||||
|
QVariantMap cancelledMutationResult;
|
||||||
|
cancelledMutationRuntime.submitAsync(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true,
|
||||||
|
[&](QVariantMap result) {
|
||||||
|
++cancelledMutationCallbacks;
|
||||||
|
cancelledMutationResult = std::move(result);
|
||||||
|
});
|
||||||
|
if (!expect(waitForCondition(
|
||||||
|
[&]() { return cancelledMutationWallet.createdAccounts == 1; }),
|
||||||
|
"cancellable submit should reach account creation"))
|
||||||
|
return 1;
|
||||||
|
cancelledMutationRuntime.clearWalletAccounts();
|
||||||
|
if (!expect(cancelledMutationCallbacks == 1
|
||||||
|
&& cancelledMutationResult.value(QStringLiteral("code")).toString()
|
||||||
|
== QStringLiteral("wallet_unavailable"),
|
||||||
|
"wallet change should immediately cancel pending mutation"))
|
||||||
|
return 1;
|
||||||
|
cancelledMutationWallet.finishAccountCreation();
|
||||||
|
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
|
||||||
|
if (!expect(cancelledMutationCallbacks == 1
|
||||||
|
&& cancelledMutationWallet.submissions == 0,
|
||||||
|
"stale account completion should have no transaction side effect"))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
FakeWallet destroyedRuntimeWallet;
|
||||||
|
destroyedRuntimeWallet.deferSubmission = true;
|
||||||
|
FakeAmmClient destroyedRuntimeClient;
|
||||||
|
destroyedRuntimeClient.requiresFreshLp = false;
|
||||||
|
int destroyedRuntimeCallbacks = 0;
|
||||||
|
auto destroyedRuntime = std::make_unique<NewPositionRuntime>(
|
||||||
|
&destroyedRuntimeWallet, &destroyedRuntimeClient, &sequencer);
|
||||||
|
destroyedRuntime->submitAsync(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true,
|
||||||
|
[&](QVariantMap) { ++destroyedRuntimeCallbacks; });
|
||||||
|
if (!expect(waitForCondition(
|
||||||
|
[&]() { return destroyedRuntimeWallet.submissions == 1; }),
|
||||||
|
"lifetime test should reach async submission"))
|
||||||
|
return 1;
|
||||||
|
destroyedRuntime.reset();
|
||||||
|
destroyedRuntimeWallet.finishSubmission();
|
||||||
|
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
|
||||||
|
if (!expect(destroyedRuntimeCallbacks == 0,
|
||||||
|
"destroyed runtime should ignore late wallet completion"))
|
||||||
|
return 1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonParseError>
|
#include <QJsonParseError>
|
||||||
|
#include <QPointer>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QVariantList>
|
#include <QVariantList>
|
||||||
#include <QVariantMap>
|
#include <QVariantMap>
|
||||||
@ -109,6 +110,58 @@ WalletAccountRead parsePublicAccount(const QString& accountId, const QString& pa
|
|||||||
read.dataHex = data;
|
read.dataHex = data;
|
||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool encodeTransaction(const WalletTransaction& transaction,
|
||||||
|
QVariantList* signingRequirements,
|
||||||
|
QVariantList* instruction)
|
||||||
|
{
|
||||||
|
if (!isHex(transaction.programId, 64)
|
||||||
|
|| transaction.accountIds.size() != transaction.signingRequirements.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const QString& accountId : transaction.accountIds) {
|
||||||
|
if (!isHex(accountId, 64))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
signingRequirements->reserve(transaction.signingRequirements.size());
|
||||||
|
for (bool required : transaction.signingRequirements)
|
||||||
|
signingRequirements->append(required);
|
||||||
|
|
||||||
|
instruction->reserve(transaction.instruction.size());
|
||||||
|
for (quint32 word : transaction.instruction)
|
||||||
|
instruction->append(word);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
WalletSubmission parseSubmission(const QString& response)
|
||||||
|
{
|
||||||
|
WalletSubmission submission;
|
||||||
|
QJsonParseError parseError;
|
||||||
|
const QJsonDocument document = QJsonDocument::fromJson(response.toUtf8(), &parseError);
|
||||||
|
if (parseError.error != QJsonParseError::NoError || !document.isObject()) {
|
||||||
|
submission.failure = WalletFailure::SubmissionFailed;
|
||||||
|
return submission;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QJsonObject result = document.object();
|
||||||
|
const QJsonValue success = result.value(QStringLiteral("success"));
|
||||||
|
const QJsonValue error = result.value(QStringLiteral("error"));
|
||||||
|
const QString hash = result.value(QStringLiteral("tx_hash")).toString();
|
||||||
|
const bool emptyError = error.isUndefined()
|
||||||
|
|| error.isNull()
|
||||||
|
|| (error.isString() && error.toString().isEmpty());
|
||||||
|
if (!success.isBool()
|
||||||
|
|| !success.toBool()
|
||||||
|
|| !emptyError
|
||||||
|
|| !isHex(hash, 64, false)) {
|
||||||
|
submission.failure = WalletFailure::SubmissionFailed;
|
||||||
|
return submission;
|
||||||
|
}
|
||||||
|
|
||||||
|
submission.nativeHash = hash.toLower();
|
||||||
|
return submission;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LogosWalletProvider::Impl {
|
struct LogosWalletProvider::Impl {
|
||||||
@ -138,12 +191,16 @@ LogosWalletProvider::LogosWalletProvider(LogosModules* logos)
|
|||||||
|
|
||||||
LogosWalletProvider::~LogosWalletProvider()
|
LogosWalletProvider::~LogosWalletProvider()
|
||||||
{
|
{
|
||||||
|
++m_generation;
|
||||||
|
++m_sessionGeneration;
|
||||||
if (m_connected)
|
if (m_connected)
|
||||||
save();
|
save();
|
||||||
}
|
}
|
||||||
|
|
||||||
WalletSession LogosWalletProvider::connect(const WalletPaths& paths)
|
WalletSession LogosWalletProvider::connect(const WalletPaths& paths)
|
||||||
{
|
{
|
||||||
|
++m_generation;
|
||||||
|
++m_sessionGeneration;
|
||||||
clearSnapshot();
|
clearSnapshot();
|
||||||
if (!m_impl->logos)
|
if (!m_impl->logos)
|
||||||
return failedSession(WalletFailure::WalletUnavailable);
|
return failedSession(WalletFailure::WalletUnavailable);
|
||||||
@ -168,6 +225,7 @@ WalletSession LogosWalletProvider::connect(const WalletPaths& paths)
|
|||||||
|
|
||||||
void LogosWalletProvider::connectAsync(const WalletPaths& paths, SessionCallback callback)
|
void LogosWalletProvider::connectAsync(const WalletPaths& paths, SessionCallback callback)
|
||||||
{
|
{
|
||||||
|
++m_sessionGeneration;
|
||||||
clearSnapshot();
|
clearSnapshot();
|
||||||
const quint64 generation = ++m_generation;
|
const quint64 generation = ++m_generation;
|
||||||
if (!m_impl->logos) {
|
if (!m_impl->logos) {
|
||||||
@ -239,6 +297,8 @@ void LogosWalletProvider::connectAsync(const WalletPaths& paths, SessionCallback
|
|||||||
WalletCreation LogosWalletProvider::createWallet(const WalletPaths& paths,
|
WalletCreation LogosWalletProvider::createWallet(const WalletPaths& paths,
|
||||||
const QString& password)
|
const QString& password)
|
||||||
{
|
{
|
||||||
|
++m_generation;
|
||||||
|
++m_sessionGeneration;
|
||||||
clearSnapshot();
|
clearSnapshot();
|
||||||
if (!m_impl->logos)
|
if (!m_impl->logos)
|
||||||
return failedCreation(WalletFailure::WalletUnavailable);
|
return failedCreation(WalletFailure::WalletUnavailable);
|
||||||
@ -368,6 +428,167 @@ WalletAccountCreation LogosWalletProvider::createAccount(bool isPublic)
|
|||||||
return creation;
|
return creation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LogosWalletProvider::createAccountAsync(bool isPublic,
|
||||||
|
AccountCreationCallback callback)
|
||||||
|
{
|
||||||
|
QPointer<LogosWalletProvider> guard(this);
|
||||||
|
if (!m_connected || !m_impl->logos) {
|
||||||
|
QTimer::singleShot(0, [guard, callback = std::move(callback)]() mutable {
|
||||||
|
if (!guard)
|
||||||
|
return;
|
||||||
|
WalletAccountCreation creation;
|
||||||
|
creation.failure = WalletFailure::WalletUnavailable;
|
||||||
|
callback(std::move(creation));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const quint64 sessionGeneration = m_sessionGeneration;
|
||||||
|
auto finish = [guard, sessionGeneration, isPublic,
|
||||||
|
callback = std::move(callback)](
|
||||||
|
WalletAccountCreation creation, QString fallbackBalance) mutable {
|
||||||
|
if (!guard)
|
||||||
|
return;
|
||||||
|
if (sessionGeneration != guard->m_sessionGeneration) {
|
||||||
|
WalletAccountCreation failed;
|
||||||
|
failed.failure = WalletFailure::WalletUnavailable;
|
||||||
|
callback(std::move(failed));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (guard->m_snapshotReady) {
|
||||||
|
WalletAccount account;
|
||||||
|
account.address = creation.accountId;
|
||||||
|
account.isPublic = isPublic;
|
||||||
|
if (isPublic && creation.publicAccount.ok()) {
|
||||||
|
account.balance = littleEndianU128ToDecimal(
|
||||||
|
creation.publicAccount.balanceHex);
|
||||||
|
auto read = std::find_if(
|
||||||
|
guard->m_snapshot.publicAccountReads.begin(),
|
||||||
|
guard->m_snapshot.publicAccountReads.end(),
|
||||||
|
[&creation](const WalletAccountRead& existing) {
|
||||||
|
return existing.accountId == creation.accountId;
|
||||||
|
});
|
||||||
|
if (read == guard->m_snapshot.publicAccountReads.end())
|
||||||
|
guard->m_snapshot.publicAccountReads.append(creation.publicAccount);
|
||||||
|
else
|
||||||
|
*read = creation.publicAccount;
|
||||||
|
} else {
|
||||||
|
account.balance = std::move(fallbackBalance);
|
||||||
|
}
|
||||||
|
auto existing = std::find_if(
|
||||||
|
guard->m_snapshot.accounts.begin(),
|
||||||
|
guard->m_snapshot.accounts.end(),
|
||||||
|
[&creation](const WalletAccount& candidate) {
|
||||||
|
return candidate.address == creation.accountId;
|
||||||
|
});
|
||||||
|
if (existing == guard->m_snapshot.accounts.end())
|
||||||
|
guard->m_snapshot.accounts.append(account);
|
||||||
|
else
|
||||||
|
*existing = account;
|
||||||
|
creation.snapshot = guard->m_snapshot;
|
||||||
|
}
|
||||||
|
callback(std::move(creation));
|
||||||
|
};
|
||||||
|
|
||||||
|
auto created = [guard, sessionGeneration, isPublic,
|
||||||
|
finish = std::move(finish)](QString accountId) mutable {
|
||||||
|
if (!guard)
|
||||||
|
return;
|
||||||
|
if (sessionGeneration != guard->m_sessionGeneration) {
|
||||||
|
WalletAccountCreation failed;
|
||||||
|
failed.failure = WalletFailure::WalletUnavailable;
|
||||||
|
finish(std::move(failed), {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WalletAccountCreation creation;
|
||||||
|
creation.accountId = std::move(accountId);
|
||||||
|
if (!isHex(creation.accountId, 64)) {
|
||||||
|
creation.failure = WalletFailure::CreateFailed;
|
||||||
|
finish(std::move(creation), {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
guard->m_impl->logos->logos_execution_zone.saveAsync(
|
||||||
|
[guard, sessionGeneration, isPublic, creation = std::move(creation),
|
||||||
|
finish = std::move(finish)](int result) mutable {
|
||||||
|
if (!guard)
|
||||||
|
return;
|
||||||
|
if (sessionGeneration != guard->m_sessionGeneration) {
|
||||||
|
WalletAccountCreation failed;
|
||||||
|
failed.failure = WalletFailure::WalletUnavailable;
|
||||||
|
finish(std::move(failed), {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (result != WALLET_FFI_SUCCESS) {
|
||||||
|
creation.failure = WalletFailure::SaveFailed;
|
||||||
|
finish(std::move(creation), {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPublic) {
|
||||||
|
guard->m_impl->logos->logos_execution_zone.get_balanceAsync(
|
||||||
|
creation.accountId, false,
|
||||||
|
[guard, sessionGeneration, creation = std::move(creation),
|
||||||
|
finish = std::move(finish)](QString balance) mutable {
|
||||||
|
if (!guard)
|
||||||
|
return;
|
||||||
|
if (sessionGeneration != guard->m_sessionGeneration) {
|
||||||
|
WalletAccountCreation failed;
|
||||||
|
failed.failure = WalletFailure::WalletUnavailable;
|
||||||
|
finish(std::move(failed), {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
finish(std::move(creation), std::move(balance));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString accountId = creation.accountId;
|
||||||
|
guard->m_impl->logos->logos_execution_zone.get_account_publicAsync(
|
||||||
|
accountId,
|
||||||
|
[guard, sessionGeneration, creation = std::move(creation),
|
||||||
|
finish = std::move(finish)](QString payload) mutable {
|
||||||
|
if (!guard)
|
||||||
|
return;
|
||||||
|
if (sessionGeneration != guard->m_sessionGeneration) {
|
||||||
|
WalletAccountCreation failed;
|
||||||
|
failed.failure = WalletFailure::WalletUnavailable;
|
||||||
|
finish(std::move(failed), {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
creation.publicAccount = parsePublicAccount(
|
||||||
|
creation.accountId, payload);
|
||||||
|
if (creation.publicAccount.ok()) {
|
||||||
|
finish(std::move(creation), {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
guard->m_impl->logos->logos_execution_zone.get_balanceAsync(
|
||||||
|
creation.accountId, true,
|
||||||
|
[guard, sessionGeneration,
|
||||||
|
creation = std::move(creation),
|
||||||
|
finish = std::move(finish)](QString balance) mutable {
|
||||||
|
if (!guard)
|
||||||
|
return;
|
||||||
|
if (sessionGeneration != guard->m_sessionGeneration) {
|
||||||
|
WalletAccountCreation failed;
|
||||||
|
failed.failure = WalletFailure::WalletUnavailable;
|
||||||
|
finish(std::move(failed), {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
finish(std::move(creation), std::move(balance));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isPublic)
|
||||||
|
m_impl->logos->logos_execution_zone.create_account_publicAsync(std::move(created));
|
||||||
|
else
|
||||||
|
m_impl->logos->logos_execution_zone.create_account_privateAsync(std::move(created));
|
||||||
|
}
|
||||||
|
|
||||||
WalletAccountRead LogosWalletProvider::readPublicAccount(const QString& accountId) const
|
WalletAccountRead LogosWalletProvider::readPublicAccount(const QString& accountId) const
|
||||||
{
|
{
|
||||||
if (!m_impl->logos || !isHex(accountId, 64))
|
if (!m_impl->logos || !isHex(accountId, 64))
|
||||||
@ -385,27 +606,12 @@ WalletSubmission LogosWalletProvider::submitPublicTransaction(
|
|||||||
submission.failure = WalletFailure::WalletUnavailable;
|
submission.failure = WalletFailure::WalletUnavailable;
|
||||||
return submission;
|
return submission;
|
||||||
}
|
}
|
||||||
if (!isHex(transaction.programId, 64)
|
QVariantList signingRequirements;
|
||||||
|| transaction.accountIds.size() != transaction.signingRequirements.size()) {
|
QVariantList instruction;
|
||||||
|
if (!encodeTransaction(transaction, &signingRequirements, &instruction)) {
|
||||||
submission.failure = WalletFailure::InvalidRequest;
|
submission.failure = WalletFailure::InvalidRequest;
|
||||||
return submission;
|
return submission;
|
||||||
}
|
}
|
||||||
for (const QString& accountId : transaction.accountIds) {
|
|
||||||
if (!isHex(accountId, 64)) {
|
|
||||||
submission.failure = WalletFailure::InvalidRequest;
|
|
||||||
return submission;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariantList signingRequirements;
|
|
||||||
signingRequirements.reserve(transaction.signingRequirements.size());
|
|
||||||
for (bool required : transaction.signingRequirements)
|
|
||||||
signingRequirements.append(required);
|
|
||||||
|
|
||||||
QVariantList instruction;
|
|
||||||
instruction.reserve(transaction.instruction.size());
|
|
||||||
for (quint32 word : transaction.instruction)
|
|
||||||
instruction.append(word);
|
|
||||||
|
|
||||||
const QString response =
|
const QString response =
|
||||||
m_impl->logos->logos_execution_zone.send_generic_public_transaction(
|
m_impl->logos->logos_execution_zone.send_generic_public_transaction(
|
||||||
@ -413,36 +619,60 @@ WalletSubmission LogosWalletProvider::submitPublicTransaction(
|
|||||||
signingRequirements,
|
signingRequirements,
|
||||||
QVariant::fromValue(instruction),
|
QVariant::fromValue(instruction),
|
||||||
transaction.programId);
|
transaction.programId);
|
||||||
|
return parseSubmission(response);
|
||||||
|
}
|
||||||
|
|
||||||
QJsonParseError parseError;
|
void LogosWalletProvider::submitPublicTransactionAsync(
|
||||||
const QJsonDocument document = QJsonDocument::fromJson(response.toUtf8(), &parseError);
|
const WalletTransaction& transaction, SubmissionCallback callback)
|
||||||
if (parseError.error != QJsonParseError::NoError || !document.isObject()) {
|
{
|
||||||
submission.failure = WalletFailure::SubmissionFailed;
|
QPointer<LogosWalletProvider> guard(this);
|
||||||
return submission;
|
WalletSubmission submission;
|
||||||
|
if (!m_connected || !m_impl->logos) {
|
||||||
|
submission.failure = WalletFailure::WalletUnavailable;
|
||||||
|
QTimer::singleShot(0, [guard, callback = std::move(callback),
|
||||||
|
submission = std::move(submission)]() mutable {
|
||||||
|
if (guard)
|
||||||
|
callback(std::move(submission));
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QJsonObject result = document.object();
|
QVariantList signingRequirements;
|
||||||
const QJsonValue success = result.value(QStringLiteral("success"));
|
QVariantList instruction;
|
||||||
const QJsonValue error = result.value(QStringLiteral("error"));
|
if (!encodeTransaction(transaction, &signingRequirements, &instruction)) {
|
||||||
const QString hash = result.value(QStringLiteral("tx_hash")).toString();
|
submission.failure = WalletFailure::InvalidRequest;
|
||||||
const bool emptyError = error.isUndefined()
|
QTimer::singleShot(0, [guard, callback = std::move(callback),
|
||||||
|| error.isNull()
|
submission = std::move(submission)]() mutable {
|
||||||
|| (error.isString() && error.toString().isEmpty());
|
if (guard)
|
||||||
if (!success.isBool()
|
callback(std::move(submission));
|
||||||
|| !success.toBool()
|
});
|
||||||
|| !emptyError
|
return;
|
||||||
|| !isHex(hash, 64, false)) {
|
|
||||||
submission.failure = WalletFailure::SubmissionFailed;
|
|
||||||
return submission;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
submission.nativeHash = hash.toLower();
|
const quint64 sessionGeneration = m_sessionGeneration;
|
||||||
return submission;
|
m_impl->logos->logos_execution_zone.send_generic_public_transactionAsync(
|
||||||
|
transaction.accountIds,
|
||||||
|
signingRequirements,
|
||||||
|
QVariant::fromValue(instruction),
|
||||||
|
transaction.programId,
|
||||||
|
[guard, sessionGeneration, callback = std::move(callback)](
|
||||||
|
QString response) mutable {
|
||||||
|
if (!guard)
|
||||||
|
return;
|
||||||
|
if (sessionGeneration != guard->m_sessionGeneration) {
|
||||||
|
WalletSubmission failed;
|
||||||
|
failed.failure = WalletFailure::WalletUnavailable;
|
||||||
|
callback(std::move(failed));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback(parseSubmission(response));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogosWalletProvider::disconnect()
|
void LogosWalletProvider::disconnect()
|
||||||
{
|
{
|
||||||
++m_generation;
|
++m_generation;
|
||||||
|
++m_sessionGeneration;
|
||||||
if (m_connected)
|
if (m_connected)
|
||||||
save();
|
save();
|
||||||
clearSnapshot();
|
clearSnapshot();
|
||||||
|
|||||||
@ -2,12 +2,14 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
#include "WalletProvider.h"
|
#include "WalletProvider.h"
|
||||||
|
|
||||||
class LogosAPI;
|
class LogosAPI;
|
||||||
struct LogosModules;
|
struct LogosModules;
|
||||||
|
|
||||||
class LogosWalletProvider final : public WalletProvider {
|
class LogosWalletProvider final : public QObject, public WalletProvider {
|
||||||
public:
|
public:
|
||||||
explicit LogosWalletProvider(LogosAPI* api);
|
explicit LogosWalletProvider(LogosAPI* api);
|
||||||
explicit LogosWalletProvider(LogosModules* logos);
|
explicit LogosWalletProvider(LogosModules* logos);
|
||||||
@ -21,9 +23,12 @@ public:
|
|||||||
void snapshotAsync(bool forceRefresh, SnapshotCallback callback) override;
|
void snapshotAsync(bool forceRefresh, SnapshotCallback callback) override;
|
||||||
void clearSnapshot() override;
|
void clearSnapshot() override;
|
||||||
WalletAccountCreation createAccount(bool isPublic) override;
|
WalletAccountCreation createAccount(bool isPublic) override;
|
||||||
|
void createAccountAsync(bool isPublic, AccountCreationCallback callback) override;
|
||||||
WalletAccountRead readPublicAccount(const QString& accountId) const override;
|
WalletAccountRead readPublicAccount(const QString& accountId) const override;
|
||||||
WalletSubmission submitPublicTransaction(
|
WalletSubmission submitPublicTransaction(
|
||||||
const WalletTransaction& transaction) override;
|
const WalletTransaction& transaction) override;
|
||||||
|
void submitPublicTransactionAsync(
|
||||||
|
const WalletTransaction& transaction, SubmissionCallback callback) override;
|
||||||
void disconnect() override;
|
void disconnect() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -38,4 +43,5 @@ private:
|
|||||||
bool m_snapshotReady = false;
|
bool m_snapshotReady = false;
|
||||||
bool m_connected = false;
|
bool m_connected = false;
|
||||||
quint64 m_generation = 0;
|
quint64 m_generation = 0;
|
||||||
|
quint64 m_sessionGeneration = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -95,6 +95,8 @@ class WalletProvider {
|
|||||||
public:
|
public:
|
||||||
using SessionCallback = std::function<void(WalletSession)>;
|
using SessionCallback = std::function<void(WalletSession)>;
|
||||||
using SnapshotCallback = std::function<void(WalletSnapshot)>;
|
using SnapshotCallback = std::function<void(WalletSnapshot)>;
|
||||||
|
using AccountCreationCallback = std::function<void(WalletAccountCreation)>;
|
||||||
|
using SubmissionCallback = std::function<void(WalletSubmission)>;
|
||||||
|
|
||||||
virtual ~WalletProvider() = default;
|
virtual ~WalletProvider() = default;
|
||||||
|
|
||||||
@ -106,8 +108,11 @@ public:
|
|||||||
virtual void snapshotAsync(bool forceRefresh, SnapshotCallback callback) = 0;
|
virtual void snapshotAsync(bool forceRefresh, SnapshotCallback callback) = 0;
|
||||||
virtual void clearSnapshot() = 0;
|
virtual void clearSnapshot() = 0;
|
||||||
virtual WalletAccountCreation createAccount(bool isPublic) = 0;
|
virtual WalletAccountCreation createAccount(bool isPublic) = 0;
|
||||||
|
virtual void createAccountAsync(bool isPublic, AccountCreationCallback callback) = 0;
|
||||||
virtual WalletAccountRead readPublicAccount(const QString& accountId) const = 0;
|
virtual WalletAccountRead readPublicAccount(const QString& accountId) const = 0;
|
||||||
virtual WalletSubmission submitPublicTransaction(
|
virtual WalletSubmission submitPublicTransaction(
|
||||||
const WalletTransaction& transaction) = 0;
|
const WalletTransaction& transaction) = 0;
|
||||||
|
virtual void submitPublicTransactionAsync(
|
||||||
|
const WalletTransaction& transaction, SubmissionCallback callback) = 0;
|
||||||
virtual void disconnect() = 0;
|
virtual void disconnect() = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -61,6 +61,9 @@ private slots:
|
|||||||
void createdAccountDoesNotRescanWallet();
|
void createdAccountDoesNotRescanWallet();
|
||||||
void dispatchesExactGenericTransaction();
|
void dispatchesExactGenericTransaction();
|
||||||
void rejectsInvalidSubmissionResponses();
|
void rejectsInvalidSubmissionResponses();
|
||||||
|
void walletMutationsUseAsyncSdk();
|
||||||
|
void staleAsyncMutationCannotCrossSession();
|
||||||
|
void destroyedProviderIgnoresLateMutation();
|
||||||
void exposesStableAccountModelRoles();
|
void exposesStableAccountModelRoles();
|
||||||
void fakeProviderImplementsConsumerContract();
|
void fakeProviderImplementsConsumerContract();
|
||||||
void controllerOwnsUiWalletFlow();
|
void controllerOwnsUiWalletFlow();
|
||||||
@ -349,6 +352,108 @@ void LogosWalletProviderTest::rejectsInvalidSubmissionResponses()
|
|||||||
WalletFailure::InvalidRequest);
|
WalletFailure::InvalidRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LogosWalletProviderTest::walletMutationsUseAsyncSdk()
|
||||||
|
{
|
||||||
|
LogosModules modules;
|
||||||
|
modules.logos_execution_zone.sequencerAddress = QStringLiteral("http://sequencer");
|
||||||
|
modules.logos_execution_zone.publicAccountId = ACCOUNT_A;
|
||||||
|
modules.logos_execution_zone.publicAccounts.insert(ACCOUNT_A, publicAccountJson());
|
||||||
|
modules.logos_execution_zone.transactionResponse = QStringLiteral(
|
||||||
|
R"({"success":true,"tx_hash":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"})");
|
||||||
|
LogosWalletProvider provider(&modules);
|
||||||
|
QVERIFY(provider.connect({}).ok());
|
||||||
|
|
||||||
|
modules.logos_execution_zone.deferPublicAccountCreation = true;
|
||||||
|
bool creationFinished = false;
|
||||||
|
WalletAccountCreation creation;
|
||||||
|
const int listCalls = modules.logos_execution_zone.listCalls;
|
||||||
|
const int syncCalls = modules.logos_execution_zone.syncCalls;
|
||||||
|
provider.createAccountAsync(true, [&](WalletAccountCreation result) {
|
||||||
|
creation = std::move(result);
|
||||||
|
creationFinished = true;
|
||||||
|
});
|
||||||
|
QVERIFY(!creationFinished);
|
||||||
|
QVERIFY(modules.logos_execution_zone.pendingPublicAccountCreation);
|
||||||
|
modules.logos_execution_zone.finishPublicAccountCreation();
|
||||||
|
QVERIFY(creationFinished);
|
||||||
|
QVERIFY(creation.ok());
|
||||||
|
QVERIFY(creation.publicAccount.ok());
|
||||||
|
QCOMPARE(creation.accountId, ACCOUNT_A);
|
||||||
|
QCOMPARE(creation.snapshot.accounts.size(), 1);
|
||||||
|
QCOMPARE(modules.logos_execution_zone.listCalls, listCalls);
|
||||||
|
QCOMPARE(modules.logos_execution_zone.syncCalls, syncCalls);
|
||||||
|
|
||||||
|
WalletTransaction transaction {
|
||||||
|
PROGRAM_ID,
|
||||||
|
{ ACCOUNT_A, ACCOUNT_B },
|
||||||
|
{ true, false },
|
||||||
|
{ 7, 0, 4294967295U },
|
||||||
|
};
|
||||||
|
modules.logos_execution_zone.deferSubmission = true;
|
||||||
|
bool submissionFinished = false;
|
||||||
|
WalletSubmission submission;
|
||||||
|
provider.submitPublicTransactionAsync(
|
||||||
|
transaction, [&](WalletSubmission result) {
|
||||||
|
submission = std::move(result);
|
||||||
|
submissionFinished = true;
|
||||||
|
});
|
||||||
|
QVERIFY(!submissionFinished);
|
||||||
|
QCOMPARE(modules.logos_execution_zone.submittedProgramId, PROGRAM_ID);
|
||||||
|
QCOMPARE(modules.logos_execution_zone.submittedAccountIds, transaction.accountIds);
|
||||||
|
QCOMPARE(modules.logos_execution_zone.submittedSigningRequirements,
|
||||||
|
QVariantList({ true, false }));
|
||||||
|
QCOMPARE(modules.logos_execution_zone.submittedInstruction.toList(),
|
||||||
|
QVariantList({ 7U, 0U, 4294967295U }));
|
||||||
|
modules.logos_execution_zone.finishSubmission();
|
||||||
|
QVERIFY(submissionFinished);
|
||||||
|
QVERIFY(submission.accepted());
|
||||||
|
QCOMPARE(submission.nativeHash, QString(64, QLatin1Char('a')));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogosWalletProviderTest::staleAsyncMutationCannotCrossSession()
|
||||||
|
{
|
||||||
|
LogosModules modules;
|
||||||
|
modules.logos_execution_zone.sequencerAddress = QStringLiteral("http://sequencer");
|
||||||
|
modules.logos_execution_zone.publicAccountId = ACCOUNT_A;
|
||||||
|
modules.logos_execution_zone.publicAccounts.insert(ACCOUNT_A, publicAccountJson());
|
||||||
|
modules.logos_execution_zone.deferPublicAccountCreation = true;
|
||||||
|
LogosWalletProvider provider(&modules);
|
||||||
|
QVERIFY(provider.connect({}).ok());
|
||||||
|
|
||||||
|
int callbackCount = 0;
|
||||||
|
WalletAccountCreation creation;
|
||||||
|
provider.createAccountAsync(true, [&](WalletAccountCreation result) {
|
||||||
|
++callbackCount;
|
||||||
|
creation = std::move(result);
|
||||||
|
});
|
||||||
|
provider.disconnect();
|
||||||
|
modules.logos_execution_zone.finishPublicAccountCreation();
|
||||||
|
|
||||||
|
QCOMPARE(callbackCount, 1);
|
||||||
|
QCOMPARE(creation.failure, WalletFailure::WalletUnavailable);
|
||||||
|
QCOMPARE(modules.logos_execution_zone.publicReadCalls, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogosWalletProviderTest::destroyedProviderIgnoresLateMutation()
|
||||||
|
{
|
||||||
|
LogosModules modules;
|
||||||
|
modules.logos_execution_zone.sequencerAddress = QStringLiteral("http://sequencer");
|
||||||
|
modules.logos_execution_zone.transactionResponse = QStringLiteral(
|
||||||
|
R"({"success":true,"tx_hash":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"})");
|
||||||
|
modules.logos_execution_zone.deferSubmission = true;
|
||||||
|
int callbackCount = 0;
|
||||||
|
{
|
||||||
|
LogosWalletProvider provider(&modules);
|
||||||
|
QVERIFY(provider.connect({}).ok());
|
||||||
|
provider.submitPublicTransactionAsync(
|
||||||
|
{ PROGRAM_ID, { ACCOUNT_A }, { true }, { 1 } },
|
||||||
|
[&](WalletSubmission) { ++callbackCount; });
|
||||||
|
}
|
||||||
|
|
||||||
|
modules.logos_execution_zone.finishSubmission();
|
||||||
|
QCOMPARE(callbackCount, 0);
|
||||||
|
}
|
||||||
|
|
||||||
void LogosWalletProviderTest::exposesStableAccountModelRoles()
|
void LogosWalletProviderTest::exposesStableAccountModelRoles()
|
||||||
{
|
{
|
||||||
WalletAccountModel model;
|
WalletAccountModel model;
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
#include <QVariantList>
|
#include <QVariantList>
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
class LogosAPI;
|
class LogosAPI;
|
||||||
|
|
||||||
@ -32,6 +33,10 @@ public:
|
|||||||
int listCalls = 0;
|
int listCalls = 0;
|
||||||
int publicReadCalls = 0;
|
int publicReadCalls = 0;
|
||||||
int submitCalls = 0;
|
int submitCalls = 0;
|
||||||
|
bool deferPublicAccountCreation = false;
|
||||||
|
bool deferSubmission = false;
|
||||||
|
std::function<void(QString)> pendingPublicAccountCreation;
|
||||||
|
std::function<void(QString)> pendingSubmission;
|
||||||
QString openedConfig;
|
QString openedConfig;
|
||||||
QString openedStorage;
|
QString openedStorage;
|
||||||
QString createdConfig;
|
QString createdConfig;
|
||||||
@ -77,6 +82,17 @@ public:
|
|||||||
|
|
||||||
QString create_account_public() { return publicAccountId; }
|
QString create_account_public() { return publicAccountId; }
|
||||||
QString create_account_private() { return privateAccountId; }
|
QString create_account_private() { return privateAccountId; }
|
||||||
|
void create_account_publicAsync(std::function<void(QString)> callback)
|
||||||
|
{
|
||||||
|
if (deferPublicAccountCreation)
|
||||||
|
pendingPublicAccountCreation = std::move(callback);
|
||||||
|
else
|
||||||
|
callback(create_account_public());
|
||||||
|
}
|
||||||
|
void create_account_privateAsync(std::function<void(QString)> callback)
|
||||||
|
{
|
||||||
|
callback(create_account_private());
|
||||||
|
}
|
||||||
|
|
||||||
int get_last_synced_block() const { return lastSyncedBlock; }
|
int get_last_synced_block() const { return lastSyncedBlock; }
|
||||||
int get_current_block_height() const { return currentBlockHeight; }
|
int get_current_block_height() const { return currentBlockHeight; }
|
||||||
@ -150,6 +166,38 @@ public:
|
|||||||
submittedProgramId = programId;
|
submittedProgramId = programId;
|
||||||
return transactionResponse;
|
return transactionResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void send_generic_public_transactionAsync(
|
||||||
|
const QStringList& accountIds,
|
||||||
|
const QVariantList& signingRequirements,
|
||||||
|
const QVariant& instruction,
|
||||||
|
const QString& programId,
|
||||||
|
std::function<void(QString)> callback)
|
||||||
|
{
|
||||||
|
++submitCalls;
|
||||||
|
submittedAccountIds = accountIds;
|
||||||
|
submittedSigningRequirements = signingRequirements;
|
||||||
|
submittedInstruction = instruction;
|
||||||
|
submittedProgramId = programId;
|
||||||
|
if (deferSubmission)
|
||||||
|
pendingSubmission = std::move(callback);
|
||||||
|
else
|
||||||
|
callback(transactionResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
void finishPublicAccountCreation()
|
||||||
|
{
|
||||||
|
auto callback = std::move(pendingPublicAccountCreation);
|
||||||
|
if (callback)
|
||||||
|
callback(publicAccountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void finishSubmission()
|
||||||
|
{
|
||||||
|
auto callback = std::move(pendingSubmission);
|
||||||
|
if (callback)
|
||||||
|
callback(transactionResponse);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct LogosModules {
|
struct LogosModules {
|
||||||
|
|||||||
@ -80,6 +80,11 @@ public:
|
|||||||
return createAccountResult;
|
return createAccountResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void createAccountAsync(bool isPublic, AccountCreationCallback callback) override
|
||||||
|
{
|
||||||
|
callback(createAccount(isPublic));
|
||||||
|
}
|
||||||
|
|
||||||
WalletAccountRead readPublicAccount(const QString& accountId) const override
|
WalletAccountRead readPublicAccount(const QString& accountId) const override
|
||||||
{
|
{
|
||||||
++readCalls;
|
++readCalls;
|
||||||
@ -96,6 +101,12 @@ public:
|
|||||||
return submissionResult;
|
return submissionResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void submitPublicTransactionAsync(
|
||||||
|
const WalletTransaction& transaction, SubmissionCallback callback) override
|
||||||
|
{
|
||||||
|
callback(submitPublicTransaction(transaction));
|
||||||
|
}
|
||||||
|
|
||||||
void disconnect() override { ++disconnectCalls; }
|
void disconnect() override { ++disconnectCalls; }
|
||||||
|
|
||||||
void finishConnect()
|
void finishConnect()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user