mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-07-25 08:13:12 +00:00
feat(wallet): implement asynchronous connection and snapshot handling
This commit is contained in:
parent
1692581f60
commit
90ba65f2d0
@ -12,9 +12,11 @@ Popup {
|
|||||||
property var snapshot: ({})
|
property var snapshot: ({})
|
||||||
property Component summary: null
|
property Component summary: null
|
||||||
property bool confirmationPending: false
|
property bool confirmationPending: false
|
||||||
|
property bool confirmEnabled: true
|
||||||
|
|
||||||
signal canceled
|
signal canceled
|
||||||
signal confirmed(var snapshot)
|
signal confirmed(var snapshot)
|
||||||
|
signal summaryEdited(var snapshot)
|
||||||
|
|
||||||
modal: true
|
modal: true
|
||||||
dim: true
|
dim: true
|
||||||
@ -43,6 +45,10 @@ Popup {
|
|||||||
cancelButton.forceActiveFocus()
|
cancelButton.forceActiveFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateSnapshot(nextSnapshot) {
|
||||||
|
root.snapshot = root.cloneSnapshot(nextSnapshot)
|
||||||
|
}
|
||||||
|
|
||||||
function cancel() {
|
function cancel() {
|
||||||
if (root.busy)
|
if (root.busy)
|
||||||
return
|
return
|
||||||
@ -52,7 +58,7 @@ Popup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function confirm() {
|
function confirm() {
|
||||||
if (root.busy)
|
if (root.busy || !root.confirmEnabled)
|
||||||
return
|
return
|
||||||
root.confirmationPending = true
|
root.confirmationPending = true
|
||||||
root.confirmed(root.snapshot)
|
root.confirmed(root.snapshot)
|
||||||
@ -62,6 +68,16 @@ Popup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: summaryLoader.item
|
||||||
|
ignoreUnknownSignals: true
|
||||||
|
|
||||||
|
function onSnapshotEdited(snapshot) {
|
||||||
|
root.updateSnapshot(snapshot)
|
||||||
|
root.summaryEdited(root.snapshot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onBusyChanged: {
|
onBusyChanged: {
|
||||||
if (!root.busy && root.confirmationPending) {
|
if (!root.busy && root.confirmationPending) {
|
||||||
root.confirmationPending = false
|
root.confirmationPending = false
|
||||||
@ -145,7 +161,7 @@ Popup {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
implicitHeight: 44
|
implicitHeight: 44
|
||||||
text: root.busy ? qsTr("Submitting...") : root.confirmText
|
text: root.busy ? qsTr("Submitting...") : root.confirmText
|
||||||
enabled: !root.busy
|
enabled: !root.busy && root.confirmEnabled
|
||||||
Accessible.name: text
|
Accessible.name: text
|
||||||
onClicked: root.confirm()
|
onClicked: root.confirm()
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,11 @@ Item {
|
|||||||
property bool busy: false
|
property bool busy: false
|
||||||
|
|
||||||
readonly property bool connected: root.wallet !== null && root.wallet.isWalletOpen
|
readonly property bool connected: root.wallet !== null && root.wallet.isWalletOpen
|
||||||
|
readonly property string syncStatus: root.wallet
|
||||||
|
? String(root.wallet.walletSyncStatus || "closed")
|
||||||
|
: "closed"
|
||||||
|
readonly property bool syncing: root.syncStatus === "opening"
|
||||||
|
|| root.syncStatus === "syncing"
|
||||||
readonly property bool compactLayout: root.compact || root.viewportWidth < 680
|
readonly property bool compactLayout: root.compact || root.viewportWidth < 680
|
||||||
readonly property string selectedAddress: root.accountAt(root.selectedIndex, "address")
|
readonly property string selectedAddress: root.accountAt(root.selectedIndex, "address")
|
||||||
readonly property string selectedName: root.accountAt(root.selectedIndex, "name")
|
readonly property string selectedName: root.accountAt(root.selectedIndex, "name")
|
||||||
@ -129,10 +134,13 @@ Item {
|
|||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: !root.connected
|
visible: !root.connected
|
||||||
enabled: root.wallet !== null && !root.busy
|
enabled: root.wallet !== null && !root.busy && !root.syncing
|
||||||
implicitHeight: 40
|
implicitHeight: 40
|
||||||
implicitWidth: root.compactLayout ? 40 : 108
|
implicitWidth: root.compactLayout ? 40 : 108
|
||||||
text: root.compactLayout ? "" : root.busy ? qsTr("Connecting...") : qsTr("Connect")
|
text: root.compactLayout ? ""
|
||||||
|
: root.syncStatus === "opening" ? qsTr("Opening...")
|
||||||
|
: root.syncStatus === "syncing" ? qsTr("Syncing...")
|
||||||
|
: root.busy ? qsTr("Connecting...") : qsTr("Connect")
|
||||||
display: root.compactLayout ? AbstractButton.IconOnly : AbstractButton.TextBesideIcon
|
display: root.compactLayout ? AbstractButton.IconOnly : AbstractButton.TextBesideIcon
|
||||||
icon.source: Qt.resolvedUrl("icons/account.svg")
|
icon.source: Qt.resolvedUrl("icons/account.svg")
|
||||||
icon.color: "#ffffff"
|
icon.color: "#ffffff"
|
||||||
@ -198,7 +206,7 @@ Item {
|
|||||||
Layout.preferredWidth: 8
|
Layout.preferredWidth: 8
|
||||||
Layout.preferredHeight: 8
|
Layout.preferredHeight: 8
|
||||||
radius: 4
|
radius: 4
|
||||||
color: "#22c55e"
|
color: root.syncing ? "#f59e0b" : "#22c55e"
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
#include "LogosWalletProvider.h"
|
#include "LogosWalletProvider.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonParseError>
|
#include <QJsonParseError>
|
||||||
|
#include <QTimer>
|
||||||
#include <QVariantList>
|
#include <QVariantList>
|
||||||
#include <QVariantMap>
|
#include <QVariantMap>
|
||||||
|
|
||||||
@ -73,6 +76,39 @@ WalletCreation failedCreation(WalletFailure failure)
|
|||||||
creation.snapshot.failure = failure;
|
creation.snapshot.failure = failure;
|
||||||
return creation;
|
return creation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WalletAccountRead parsePublicAccount(const QString& accountId, const QString& payload)
|
||||||
|
{
|
||||||
|
WalletAccountRead read;
|
||||||
|
read.accountId = accountId;
|
||||||
|
if (!isHex(accountId, 64))
|
||||||
|
return read;
|
||||||
|
|
||||||
|
QJsonParseError parseError;
|
||||||
|
const QJsonDocument document = QJsonDocument::fromJson(payload.toUtf8(), &parseError);
|
||||||
|
if (parseError.error != QJsonParseError::NoError || !document.isObject())
|
||||||
|
return read;
|
||||||
|
|
||||||
|
const QJsonObject account = document.object();
|
||||||
|
const QString owner = account.value(QStringLiteral("program_owner")).toString();
|
||||||
|
const QString balance = account.value(QStringLiteral("balance")).toString();
|
||||||
|
const QString nonce = account.value(QStringLiteral("nonce")).toString();
|
||||||
|
const QString data = account.value(QStringLiteral("data")).toString();
|
||||||
|
if (!isHex(owner, 64)
|
||||||
|
|| !isHex(balance, 32)
|
||||||
|
|| !isHex(nonce, 32)
|
||||||
|
|| data.size() % 2 != 0
|
||||||
|
|| !isHex(data, data.size())) {
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
|
||||||
|
read.status = QStringLiteral("ok");
|
||||||
|
read.programOwner = owner;
|
||||||
|
read.balanceHex = balance;
|
||||||
|
read.nonceHex = nonce;
|
||||||
|
read.dataHex = data;
|
||||||
|
return read;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LogosWalletProvider::Impl {
|
struct LogosWalletProvider::Impl {
|
||||||
@ -130,6 +166,76 @@ WalletSession LogosWalletProvider::connect(const WalletPaths& paths)
|
|||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LogosWalletProvider::connectAsync(const WalletPaths& paths, SessionCallback callback)
|
||||||
|
{
|
||||||
|
clearSnapshot();
|
||||||
|
const quint64 generation = ++m_generation;
|
||||||
|
if (!m_impl->logos) {
|
||||||
|
QTimer::singleShot(0, [callback = std::move(callback)]() mutable {
|
||||||
|
callback(failedSession(WalletFailure::WalletUnavailable));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto finishOpen = [this, generation, callback = std::move(callback)](
|
||||||
|
bool adopted, WalletFailure failure) mutable {
|
||||||
|
if (generation != m_generation)
|
||||||
|
return;
|
||||||
|
if (failure != WalletFailure::None) {
|
||||||
|
callback(failedSession(failure));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_connected = true;
|
||||||
|
loadSnapshotAsync(generation,
|
||||||
|
[this, generation, adopted, callback = std::move(callback)](
|
||||||
|
WalletSnapshot snapshot) mutable {
|
||||||
|
if (generation != m_generation)
|
||||||
|
return;
|
||||||
|
WalletSession session;
|
||||||
|
session.adopted = adopted;
|
||||||
|
session.failure = snapshot.failure;
|
||||||
|
session.snapshot = std::move(snapshot);
|
||||||
|
callback(std::move(session));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
auto openStored = [this, generation, paths, finishOpen]() mutable {
|
||||||
|
if (generation != m_generation)
|
||||||
|
return;
|
||||||
|
if (!QFileInfo::exists(paths.storage)) {
|
||||||
|
finishOpen(false, WalletFailure::WalletMissing);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_impl->logos->logos_execution_zone.openAsync(
|
||||||
|
paths.config, paths.storage,
|
||||||
|
[this, generation, finishOpen](int result) mutable {
|
||||||
|
if (generation != m_generation)
|
||||||
|
return;
|
||||||
|
finishOpen(false, result == WALLET_FFI_SUCCESS
|
||||||
|
? WalletFailure::None : WalletFailure::OpenFailed);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
m_impl->logos->logos_execution_zone.get_sequencer_addrAsync(
|
||||||
|
[this, generation, finishOpen, openStored](QString address) mutable {
|
||||||
|
if (generation != m_generation)
|
||||||
|
return;
|
||||||
|
if (!address.isEmpty()) {
|
||||||
|
finishOpen(true, WalletFailure::None);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_impl->logos->logos_execution_zone.list_accountsAsync(
|
||||||
|
[this, generation, finishOpen, openStored](QVariantList accounts) mutable {
|
||||||
|
if (generation != m_generation)
|
||||||
|
return;
|
||||||
|
if (!accounts.isEmpty())
|
||||||
|
finishOpen(true, WalletFailure::None);
|
||||||
|
else
|
||||||
|
openStored();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
WalletCreation LogosWalletProvider::createWallet(const WalletPaths& paths,
|
WalletCreation LogosWalletProvider::createWallet(const WalletPaths& paths,
|
||||||
const QString& password)
|
const QString& password)
|
||||||
{
|
{
|
||||||
@ -180,6 +286,26 @@ WalletSnapshot LogosWalletProvider::snapshot(bool forceRefresh)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LogosWalletProvider::snapshotAsync(bool forceRefresh, SnapshotCallback callback)
|
||||||
|
{
|
||||||
|
if (m_snapshotReady && !forceRefresh) {
|
||||||
|
const WalletSnapshot snapshot = m_snapshot;
|
||||||
|
QTimer::singleShot(0, [callback = std::move(callback), snapshot]() mutable {
|
||||||
|
callback(snapshot);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!m_connected) {
|
||||||
|
WalletSnapshot snapshot;
|
||||||
|
snapshot.failure = WalletFailure::WalletUnavailable;
|
||||||
|
QTimer::singleShot(0, [callback = std::move(callback), snapshot]() mutable {
|
||||||
|
callback(snapshot);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loadSnapshotAsync(++m_generation, std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
void LogosWalletProvider::clearSnapshot()
|
void LogosWalletProvider::clearSnapshot()
|
||||||
{
|
{
|
||||||
m_snapshot = {};
|
m_snapshot = {};
|
||||||
@ -208,45 +334,47 @@ WalletAccountCreation LogosWalletProvider::createAccount(bool isPublic)
|
|||||||
|
|
||||||
if (isPublic)
|
if (isPublic)
|
||||||
creation.publicAccount = readPublicAccount(creation.accountId);
|
creation.publicAccount = readPublicAccount(creation.accountId);
|
||||||
|
if (m_snapshotReady) {
|
||||||
clearSnapshot();
|
WalletAccount account;
|
||||||
creation.snapshot = snapshot(true);
|
account.address = creation.accountId;
|
||||||
|
account.isPublic = isPublic;
|
||||||
|
if (isPublic && creation.publicAccount.ok()) {
|
||||||
|
account.balance = littleEndianU128ToDecimal(creation.publicAccount.balanceHex);
|
||||||
|
auto read = std::find_if(
|
||||||
|
m_snapshot.publicAccountReads.begin(),
|
||||||
|
m_snapshot.publicAccountReads.end(),
|
||||||
|
[&creation](const WalletAccountRead& existing) {
|
||||||
|
return existing.accountId == creation.accountId;
|
||||||
|
});
|
||||||
|
if (read == m_snapshot.publicAccountReads.end())
|
||||||
|
m_snapshot.publicAccountReads.append(creation.publicAccount);
|
||||||
|
else
|
||||||
|
*read = creation.publicAccount;
|
||||||
|
} else {
|
||||||
|
account.balance = m_impl->logos->logos_execution_zone.get_balance(
|
||||||
|
creation.accountId, isPublic);
|
||||||
|
}
|
||||||
|
auto existing = std::find_if(
|
||||||
|
m_snapshot.accounts.begin(), m_snapshot.accounts.end(),
|
||||||
|
[&creation](const WalletAccount& candidate) {
|
||||||
|
return candidate.address == creation.accountId;
|
||||||
|
});
|
||||||
|
if (existing == m_snapshot.accounts.end())
|
||||||
|
m_snapshot.accounts.append(account);
|
||||||
|
else
|
||||||
|
*existing = account;
|
||||||
|
creation.snapshot = m_snapshot;
|
||||||
|
}
|
||||||
return creation;
|
return creation;
|
||||||
}
|
}
|
||||||
|
|
||||||
WalletAccountRead LogosWalletProvider::readPublicAccount(const QString& accountId) const
|
WalletAccountRead LogosWalletProvider::readPublicAccount(const QString& accountId) const
|
||||||
{
|
{
|
||||||
WalletAccountRead read;
|
|
||||||
read.accountId = accountId;
|
|
||||||
if (!m_impl->logos || !isHex(accountId, 64))
|
if (!m_impl->logos || !isHex(accountId, 64))
|
||||||
return read;
|
return WalletAccountRead { accountId };
|
||||||
|
return parsePublicAccount(
|
||||||
QJsonParseError parseError;
|
accountId,
|
||||||
const QJsonDocument document = QJsonDocument::fromJson(
|
m_impl->logos->logos_execution_zone.get_account_public(accountId));
|
||||||
m_impl->logos->logos_execution_zone.get_account_public(accountId).toUtf8(),
|
|
||||||
&parseError);
|
|
||||||
if (parseError.error != QJsonParseError::NoError || !document.isObject())
|
|
||||||
return read;
|
|
||||||
|
|
||||||
const QJsonObject account = document.object();
|
|
||||||
const QString owner = account.value(QStringLiteral("program_owner")).toString();
|
|
||||||
const QString balance = account.value(QStringLiteral("balance")).toString();
|
|
||||||
const QString nonce = account.value(QStringLiteral("nonce")).toString();
|
|
||||||
const QString data = account.value(QStringLiteral("data")).toString();
|
|
||||||
if (!isHex(owner, 64)
|
|
||||||
|| !isHex(balance, 32)
|
|
||||||
|| !isHex(nonce, 32)
|
|
||||||
|| data.size() % 2 != 0
|
|
||||||
|| !isHex(data, data.size())) {
|
|
||||||
return read;
|
|
||||||
}
|
|
||||||
|
|
||||||
read.status = QStringLiteral("ok");
|
|
||||||
read.programOwner = owner;
|
|
||||||
read.balanceHex = balance;
|
|
||||||
read.nonceHex = nonce;
|
|
||||||
read.dataHex = data;
|
|
||||||
return read;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WalletSubmission LogosWalletProvider::submitPublicTransaction(
|
WalletSubmission LogosWalletProvider::submitPublicTransaction(
|
||||||
@ -314,6 +442,7 @@ WalletSubmission LogosWalletProvider::submitPublicTransaction(
|
|||||||
|
|
||||||
void LogosWalletProvider::disconnect()
|
void LogosWalletProvider::disconnect()
|
||||||
{
|
{
|
||||||
|
++m_generation;
|
||||||
if (m_connected)
|
if (m_connected)
|
||||||
save();
|
save();
|
||||||
clearSnapshot();
|
clearSnapshot();
|
||||||
@ -375,6 +504,161 @@ WalletSnapshot LogosWalletProvider::loadSnapshot()
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LogosWalletProvider::loadSnapshotAsync(quint64 generation, SnapshotCallback callback)
|
||||||
|
{
|
||||||
|
if (!m_impl->logos || generation != m_generation)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_impl->logos->logos_execution_zone.get_current_block_heightAsync(
|
||||||
|
[this, generation, callback = std::move(callback)](int currentHeight) mutable {
|
||||||
|
if (generation != m_generation)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto afterSync = [this, generation, currentHeight,
|
||||||
|
callback = std::move(callback)](int syncResult) mutable {
|
||||||
|
if (generation != m_generation)
|
||||||
|
return;
|
||||||
|
if (syncResult != WALLET_FFI_SUCCESS) {
|
||||||
|
WalletSnapshot failed;
|
||||||
|
failed.failure = WalletFailure::ReadFailed;
|
||||||
|
callback(std::move(failed));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_impl->logos->logos_execution_zone.get_last_synced_blockAsync(
|
||||||
|
[this, generation, currentHeight,
|
||||||
|
callback = std::move(callback)](int lastSynced) mutable {
|
||||||
|
if (generation != m_generation)
|
||||||
|
return;
|
||||||
|
m_impl->logos->logos_execution_zone.get_sequencer_addrAsync(
|
||||||
|
[this, generation, currentHeight, lastSynced,
|
||||||
|
callback = std::move(callback)](QString address) mutable {
|
||||||
|
if (generation != m_generation)
|
||||||
|
return;
|
||||||
|
m_impl->logos->logos_execution_zone.list_accountsAsync(
|
||||||
|
[this, generation, currentHeight, lastSynced,
|
||||||
|
address = std::move(address),
|
||||||
|
callback = std::move(callback)](
|
||||||
|
QVariantList entries) mutable {
|
||||||
|
if (generation != m_generation)
|
||||||
|
return;
|
||||||
|
|
||||||
|
struct SnapshotState {
|
||||||
|
WalletSnapshot snapshot;
|
||||||
|
QVector<WalletAccountRead> publicReads;
|
||||||
|
QVector<bool> publicFlags;
|
||||||
|
qsizetype remaining = 0;
|
||||||
|
SnapshotCallback callback;
|
||||||
|
};
|
||||||
|
auto state = std::make_shared<SnapshotState>();
|
||||||
|
state->snapshot.currentBlockHeight = static_cast<quint64>(
|
||||||
|
qMax(0, currentHeight));
|
||||||
|
state->snapshot.lastSyncedBlock = static_cast<quint64>(
|
||||||
|
qMax(0, lastSynced));
|
||||||
|
state->snapshot.sequencerAddress = std::move(address);
|
||||||
|
state->snapshot.accounts.resize(entries.size());
|
||||||
|
state->publicReads.resize(entries.size());
|
||||||
|
state->publicFlags.resize(entries.size());
|
||||||
|
state->remaining = entries.size();
|
||||||
|
state->callback = std::move(callback);
|
||||||
|
|
||||||
|
for (qsizetype index = 0; index < entries.size(); ++index) {
|
||||||
|
const QVariantMap entry = entries.at(index).toMap();
|
||||||
|
const QString accountId = entry
|
||||||
|
.value(QStringLiteral("account_id")).toString();
|
||||||
|
if (entry.isEmpty() || !isHex(accountId, 64)) {
|
||||||
|
state->snapshot.failure = WalletFailure::ReadFailed;
|
||||||
|
state->callback(std::move(state->snapshot));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state->snapshot.accounts[index] = WalletAccount {
|
||||||
|
accountId,
|
||||||
|
{},
|
||||||
|
entry.value(QStringLiteral("is_public"), true).toBool(),
|
||||||
|
};
|
||||||
|
state->publicFlags[index] =
|
||||||
|
state->snapshot.accounts.at(index).isPublic;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto finishOne = std::make_shared<std::function<void()>>();
|
||||||
|
*finishOne = [this, generation, state, finishOne]() mutable {
|
||||||
|
if (generation != m_generation || --state->remaining > 0)
|
||||||
|
return;
|
||||||
|
for (qsizetype index = 0;
|
||||||
|
index < state->publicReads.size(); ++index) {
|
||||||
|
if (state->publicFlags.at(index))
|
||||||
|
state->snapshot.publicAccountReads.append(
|
||||||
|
state->publicReads.at(index));
|
||||||
|
}
|
||||||
|
m_impl->logos->logos_execution_zone.saveAsync(
|
||||||
|
[this, generation, state](int result) mutable {
|
||||||
|
if (generation != m_generation)
|
||||||
|
return;
|
||||||
|
if (result != WALLET_FFI_SUCCESS)
|
||||||
|
state->snapshot.failure = WalletFailure::SaveFailed;
|
||||||
|
if (state->snapshot.ok()) {
|
||||||
|
m_snapshot = state->snapshot;
|
||||||
|
m_snapshotReady = true;
|
||||||
|
}
|
||||||
|
state->callback(std::move(state->snapshot));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (entries.isEmpty()) {
|
||||||
|
state->remaining = 1;
|
||||||
|
(*finishOne)();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (qsizetype index = 0; index < entries.size(); ++index) {
|
||||||
|
const WalletAccount account = state->snapshot.accounts.at(index);
|
||||||
|
if (!account.isPublic) {
|
||||||
|
m_impl->logos->logos_execution_zone.get_balanceAsync(
|
||||||
|
account.address, false,
|
||||||
|
[state, finishOne, index](QString balance) {
|
||||||
|
state->snapshot.accounts[index].balance =
|
||||||
|
std::move(balance);
|
||||||
|
(*finishOne)();
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_impl->logos->logos_execution_zone.get_account_publicAsync(
|
||||||
|
account.address,
|
||||||
|
[this, state, finishOne, index,
|
||||||
|
accountId = account.address](QString payload) {
|
||||||
|
const WalletAccountRead read =
|
||||||
|
parsePublicAccount(accountId, payload);
|
||||||
|
state->publicReads[index] = read;
|
||||||
|
if (read.ok()) {
|
||||||
|
state->snapshot.accounts[index].balance =
|
||||||
|
littleEndianU128ToDecimal(read.balanceHex);
|
||||||
|
(*finishOne)();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_impl->logos->logos_execution_zone.get_balanceAsync(
|
||||||
|
accountId, true,
|
||||||
|
[state, finishOne, index](QString balance) {
|
||||||
|
state->snapshot.accounts[index].balance =
|
||||||
|
std::move(balance);
|
||||||
|
(*finishOne)();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (currentHeight > 0) {
|
||||||
|
m_impl->logos->logos_execution_zone.sync_to_blockAsync(
|
||||||
|
currentHeight, std::move(afterSync));
|
||||||
|
} else {
|
||||||
|
afterSync(WALLET_FFI_SUCCESS);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
bool LogosWalletProvider::save() const
|
bool LogosWalletProvider::save() const
|
||||||
{
|
{
|
||||||
return m_impl->logos
|
return m_impl->logos
|
||||||
|
|||||||
@ -14,9 +14,11 @@ public:
|
|||||||
~LogosWalletProvider() override;
|
~LogosWalletProvider() override;
|
||||||
|
|
||||||
WalletSession connect(const WalletPaths& paths) override;
|
WalletSession connect(const WalletPaths& paths) override;
|
||||||
|
void connectAsync(const WalletPaths& paths, SessionCallback callback) override;
|
||||||
WalletCreation createWallet(const WalletPaths& paths,
|
WalletCreation createWallet(const WalletPaths& paths,
|
||||||
const QString& password) override;
|
const QString& password) override;
|
||||||
WalletSnapshot snapshot(bool forceRefresh = false) override;
|
WalletSnapshot snapshot(bool forceRefresh = false) override;
|
||||||
|
void snapshotAsync(bool forceRefresh, SnapshotCallback callback) override;
|
||||||
void clearSnapshot() override;
|
void clearSnapshot() override;
|
||||||
WalletAccountCreation createAccount(bool isPublic) override;
|
WalletAccountCreation createAccount(bool isPublic) override;
|
||||||
WalletAccountRead readPublicAccount(const QString& accountId) const override;
|
WalletAccountRead readPublicAccount(const QString& accountId) const override;
|
||||||
@ -27,6 +29,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
bool sharedWalletIsOpen() const;
|
bool sharedWalletIsOpen() const;
|
||||||
WalletSnapshot loadSnapshot();
|
WalletSnapshot loadSnapshot();
|
||||||
|
void loadSnapshotAsync(quint64 generation, SnapshotCallback callback);
|
||||||
bool save() const;
|
bool save() const;
|
||||||
|
|
||||||
struct Impl;
|
struct Impl;
|
||||||
@ -34,4 +37,5 @@ private:
|
|||||||
WalletSnapshot m_snapshot;
|
WalletSnapshot m_snapshot;
|
||||||
bool m_snapshotReady = false;
|
bool m_snapshotReady = false;
|
||||||
bool m_connected = false;
|
bool m_connected = false;
|
||||||
|
quint64 m_generation = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,6 +5,9 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
@ -25,6 +28,17 @@ QString toLocalPath(const QString& path)
|
|||||||
return QUrl::fromUserInput(path).toLocalFile();
|
return QUrl::fromUserInput(path).toLocalFile();
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString configuredSequencer(const QString& path)
|
||||||
|
{
|
||||||
|
QFile file(path);
|
||||||
|
if (!file.open(QIODevice::ReadOnly))
|
||||||
|
return {};
|
||||||
|
const QJsonDocument document = QJsonDocument::fromJson(file.readAll());
|
||||||
|
if (!document.isObject())
|
||||||
|
return {};
|
||||||
|
return document.object().value(QStringLiteral("sequencer_addr")).toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WalletController::WalletController(WalletProvider& wallet,
|
WalletController::WalletController(WalletProvider& wallet,
|
||||||
@ -38,7 +52,10 @@ WalletController::WalletController(WalletProvider& wallet,
|
|||||||
m_reachabilityTimer(new QTimer(this))
|
m_reachabilityTimer(new QTimer(this))
|
||||||
{
|
{
|
||||||
m_state.walletHome = defaultWalletHome();
|
m_state.walletHome = defaultWalletHome();
|
||||||
|
m_state.configPath = defaultConfigPath();
|
||||||
|
m_state.storagePath = defaultStoragePath();
|
||||||
m_state.walletExists = QFileInfo::exists(defaultStoragePath());
|
m_state.walletExists = QFileInfo::exists(defaultStoragePath());
|
||||||
|
m_state.sequencerAddress = configuredSequencer(defaultConfigPath());
|
||||||
|
|
||||||
m_reachabilityTimer->setInterval(10000);
|
m_reachabilityTimer->setInterval(10000);
|
||||||
connect(m_reachabilityTimer, &QTimer::timeout,
|
connect(m_reachabilityTimer, &QTimer::timeout,
|
||||||
@ -83,20 +100,60 @@ void WalletController::openOnStartup()
|
|||||||
|
|
||||||
const QString config = defaultConfigPath();
|
const QString config = defaultConfigPath();
|
||||||
const QString storage = defaultStoragePath();
|
const QString storage = defaultStoragePath();
|
||||||
const WalletSession session = m_wallet.connect({ config, storage });
|
beginOpen(config, storage);
|
||||||
if (session.failure == WalletFailure::WalletMissing)
|
}
|
||||||
return;
|
|
||||||
if (!session.ok()) {
|
bool WalletController::beginOpen(const QString& config, const QString& storage)
|
||||||
qWarning() << "WalletController: wallet connection failed"
|
{
|
||||||
<< walletFailureCode(session.failure);
|
if (m_state.syncStatus == QStringLiteral("opening")
|
||||||
return;
|
|| m_state.syncStatus == QStringLiteral("syncing")) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const quint64 generation = ++m_operationGeneration;
|
||||||
m_state.configPath = config;
|
m_state.configPath = config;
|
||||||
m_state.storagePath = storage;
|
m_state.storagePath = storage;
|
||||||
m_state.walletExists = QFileInfo::exists(storage) || session.adopted;
|
m_state.syncStatus = QStringLiteral("opening");
|
||||||
m_state.isWalletOpen = true;
|
m_state.syncError.clear();
|
||||||
applySnapshot(session.snapshot);
|
const QString endpoint = configuredSequencer(config);
|
||||||
|
if (!endpoint.isEmpty())
|
||||||
|
m_state.sequencerAddress = endpoint;
|
||||||
|
emit stateChanged();
|
||||||
|
|
||||||
|
QTimer::singleShot(0, this, [this, generation]() {
|
||||||
|
if (generation == m_operationGeneration
|
||||||
|
&& m_state.syncStatus == QStringLiteral("opening")) {
|
||||||
|
m_state.syncStatus = QStringLiteral("syncing");
|
||||||
|
emit stateChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
m_wallet.connectAsync({ config, storage },
|
||||||
|
[this, generation, config, storage](WalletSession session) {
|
||||||
|
if (generation != m_operationGeneration)
|
||||||
|
return;
|
||||||
|
if (session.failure == WalletFailure::WalletMissing) {
|
||||||
|
m_state.syncStatus = QStringLiteral("closed");
|
||||||
|
m_state.walletExists = false;
|
||||||
|
emit stateChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!session.ok()) {
|
||||||
|
qWarning() << "WalletController: wallet connection failed"
|
||||||
|
<< walletFailureCode(session.failure);
|
||||||
|
m_state.syncStatus = QStringLiteral("error");
|
||||||
|
m_state.syncError = walletFailureCode(session.failure);
|
||||||
|
emit stateChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_state.configPath = config;
|
||||||
|
m_state.storagePath = storage;
|
||||||
|
m_state.walletExists = QFileInfo::exists(storage) || session.adopted;
|
||||||
|
m_state.isWalletOpen = true;
|
||||||
|
m_state.syncStatus = QStringLiteral("ready");
|
||||||
|
applySnapshot(session.snapshot);
|
||||||
|
});
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString WalletController::createDefaultWallet(const QString& password)
|
QString WalletController::createDefaultWallet(const QString& password)
|
||||||
@ -130,6 +187,7 @@ QString WalletController::createWallet(const QString& configPath,
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_state.isWalletOpen = true;
|
m_state.isWalletOpen = true;
|
||||||
|
m_state.syncStatus = QStringLiteral("ready");
|
||||||
applySnapshot(creation.snapshot);
|
applySnapshot(creation.snapshot);
|
||||||
return creation.mnemonic;
|
return creation.mnemonic;
|
||||||
}
|
}
|
||||||
@ -140,26 +198,17 @@ bool WalletController::open()
|
|||||||
? defaultConfigPath() : m_state.configPath;
|
? defaultConfigPath() : m_state.configPath;
|
||||||
const QString storage = m_state.storagePath.isEmpty()
|
const QString storage = m_state.storagePath.isEmpty()
|
||||||
? defaultStoragePath() : m_state.storagePath;
|
? defaultStoragePath() : m_state.storagePath;
|
||||||
const WalletSession session = m_wallet.connect({ config, storage });
|
|
||||||
if (!session.ok()) {
|
|
||||||
qWarning() << "WalletController: wallet open failed"
|
|
||||||
<< walletFailureCode(session.failure);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_state.configPath = config;
|
|
||||||
m_state.storagePath = storage;
|
|
||||||
m_state.walletExists = true;
|
|
||||||
m_state.isWalletOpen = true;
|
|
||||||
QSettings(SETTINGS_ORG, m_settingsApplication).setValue(DISCONNECTED_KEY, false);
|
QSettings(SETTINGS_ORG, m_settingsApplication).setValue(DISCONNECTED_KEY, false);
|
||||||
applySnapshot(session.snapshot);
|
return beginOpen(config, storage);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WalletController::disconnect()
|
void WalletController::disconnect()
|
||||||
{
|
{
|
||||||
|
++m_operationGeneration;
|
||||||
m_wallet.disconnect();
|
m_wallet.disconnect();
|
||||||
m_state.isWalletOpen = false;
|
m_state.isWalletOpen = false;
|
||||||
|
m_state.syncStatus = QStringLiteral("closed");
|
||||||
|
m_state.syncError.clear();
|
||||||
m_accountModel->replaceAccounts({});
|
m_accountModel->replaceAccounts({});
|
||||||
QSettings(SETTINGS_ORG, m_settingsApplication).setValue(DISCONNECTED_KEY, true);
|
QSettings(SETTINGS_ORG, m_settingsApplication).setValue(DISCONNECTED_KEY, true);
|
||||||
emit stateChanged();
|
emit stateChanged();
|
||||||
@ -184,13 +233,26 @@ QString WalletController::createAccount(bool isPublic)
|
|||||||
|
|
||||||
void WalletController::refresh()
|
void WalletController::refresh()
|
||||||
{
|
{
|
||||||
const WalletSnapshot next = m_wallet.snapshot(true);
|
if (!m_state.isWalletOpen || m_state.syncStatus == QStringLiteral("syncing"))
|
||||||
if (next.ok()) {
|
return;
|
||||||
applySnapshot(next);
|
const quint64 generation = ++m_operationGeneration;
|
||||||
} else {
|
m_state.syncStatus = QStringLiteral("syncing");
|
||||||
qWarning() << "WalletController: wallet refresh failed"
|
m_state.syncError.clear();
|
||||||
<< walletFailureCode(next.failure);
|
emit stateChanged();
|
||||||
}
|
m_wallet.snapshotAsync(true, [this, generation](WalletSnapshot next) {
|
||||||
|
if (generation != m_operationGeneration)
|
||||||
|
return;
|
||||||
|
if (next.ok()) {
|
||||||
|
m_state.syncStatus = QStringLiteral("ready");
|
||||||
|
applySnapshot(next);
|
||||||
|
} else {
|
||||||
|
qWarning() << "WalletController: wallet refresh failed"
|
||||||
|
<< walletFailureCode(next.failure);
|
||||||
|
m_state.syncStatus = QStringLiteral("error");
|
||||||
|
m_state.syncError = walletFailureCode(next.failure);
|
||||||
|
emit stateChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QString WalletController::balance(const QString& accountId, bool isPublic)
|
QString WalletController::balance(const QString& accountId, bool isPublic)
|
||||||
@ -208,7 +270,8 @@ void WalletController::applySnapshot(const WalletSnapshot& snapshot)
|
|||||||
m_accountModel->replaceAccounts(snapshot.accounts);
|
m_accountModel->replaceAccounts(snapshot.accounts);
|
||||||
m_state.lastSyncedBlock = static_cast<int>(snapshot.lastSyncedBlock);
|
m_state.lastSyncedBlock = static_cast<int>(snapshot.lastSyncedBlock);
|
||||||
m_state.currentBlockHeight = static_cast<int>(snapshot.currentBlockHeight);
|
m_state.currentBlockHeight = static_cast<int>(snapshot.currentBlockHeight);
|
||||||
m_state.sequencerAddress = snapshot.sequencerAddress;
|
if (!snapshot.sequencerAddress.isEmpty())
|
||||||
|
m_state.sequencerAddress = snapshot.sequencerAddress;
|
||||||
emit stateChanged();
|
emit stateChanged();
|
||||||
checkReachability();
|
checkReachability();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,13 @@ struct WalletUiState {
|
|||||||
int currentBlockHeight = 0;
|
int currentBlockHeight = 0;
|
||||||
QString sequencerAddress;
|
QString sequencerAddress;
|
||||||
bool sequencerReachable = true;
|
bool sequencerReachable = true;
|
||||||
|
QString syncStatus = QStringLiteral("closed");
|
||||||
|
QString syncError;
|
||||||
|
|
||||||
|
bool canSubmit() const
|
||||||
|
{
|
||||||
|
return isWalletOpen && syncStatus == QStringLiteral("ready");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class WalletController final : public QObject {
|
class WalletController final : public QObject {
|
||||||
@ -54,6 +61,7 @@ private:
|
|||||||
QString defaultStoragePath() const;
|
QString defaultStoragePath() const;
|
||||||
|
|
||||||
void openOnStartup();
|
void openOnStartup();
|
||||||
|
bool beginOpen(const QString& config, const QString& storage);
|
||||||
void applySnapshot(const WalletSnapshot& snapshot);
|
void applySnapshot(const WalletSnapshot& snapshot);
|
||||||
void checkReachability();
|
void checkReachability();
|
||||||
|
|
||||||
@ -64,4 +72,5 @@ private:
|
|||||||
QNetworkAccessManager* m_network;
|
QNetworkAccessManager* m_network;
|
||||||
QTimer* m_reachabilityTimer;
|
QTimer* m_reachabilityTimer;
|
||||||
bool m_started = false;
|
bool m_started = false;
|
||||||
|
quint64 m_operationGeneration = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
@ -92,12 +93,17 @@ struct WalletSubmission {
|
|||||||
|
|
||||||
class WalletProvider {
|
class WalletProvider {
|
||||||
public:
|
public:
|
||||||
|
using SessionCallback = std::function<void(WalletSession)>;
|
||||||
|
using SnapshotCallback = std::function<void(WalletSnapshot)>;
|
||||||
|
|
||||||
virtual ~WalletProvider() = default;
|
virtual ~WalletProvider() = default;
|
||||||
|
|
||||||
virtual WalletSession connect(const WalletPaths& paths) = 0;
|
virtual WalletSession connect(const WalletPaths& paths) = 0;
|
||||||
|
virtual void connectAsync(const WalletPaths& paths, SessionCallback callback) = 0;
|
||||||
virtual WalletCreation createWallet(const WalletPaths& paths,
|
virtual WalletCreation createWallet(const WalletPaths& paths,
|
||||||
const QString& password) = 0;
|
const QString& password) = 0;
|
||||||
virtual WalletSnapshot snapshot(bool forceRefresh = false) = 0;
|
virtual WalletSnapshot snapshot(bool forceRefresh = false) = 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 WalletAccountRead readPublicAccount(const QString& accountId) const = 0;
|
virtual WalletAccountRead readPublicAccount(const QString& accountId) const = 0;
|
||||||
|
|||||||
@ -39,6 +39,7 @@ QVariantMap accountEntry(const QString& id, bool isPublic)
|
|||||||
{ QStringLiteral("is_public"), isPublic },
|
{ QStringLiteral("is_public"), isPublic },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class LogosWalletProviderTest : public QObject {
|
class LogosWalletProviderTest : public QObject {
|
||||||
@ -52,12 +53,13 @@ private slots:
|
|||||||
void fallsBackToBalanceWhenPublicReadFails();
|
void fallsBackToBalanceWhenPublicReadFails();
|
||||||
void createsAndPersistsAccounts();
|
void createsAndPersistsAccounts();
|
||||||
void preservesCreatedAccountWhenPublicReadFails();
|
void preservesCreatedAccountWhenPublicReadFails();
|
||||||
void preservesCreatedAccountWhenSnapshotRefreshFails();
|
void createdAccountDoesNotRescanWallet();
|
||||||
void dispatchesExactGenericTransaction();
|
void dispatchesExactGenericTransaction();
|
||||||
void rejectsInvalidSubmissionResponses();
|
void rejectsInvalidSubmissionResponses();
|
||||||
void exposesStableAccountModelRoles();
|
void exposesStableAccountModelRoles();
|
||||||
void fakeProviderImplementsConsumerContract();
|
void fakeProviderImplementsConsumerContract();
|
||||||
void controllerOwnsUiWalletFlow();
|
void controllerOwnsUiWalletFlow();
|
||||||
|
void controllerOpenDoesNotWaitForWalletSync();
|
||||||
void controllerStopsReachabilityChecksAfterDisconnect();
|
void controllerStopsReachabilityChecksAfterDisconnect();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -257,7 +259,7 @@ void LogosWalletProviderTest::preservesCreatedAccountWhenPublicReadFails()
|
|||||||
QCOMPARE(creation.snapshot.accounts.at(0).balance, QStringLiteral("7"));
|
QCOMPARE(creation.snapshot.accounts.at(0).balance, QStringLiteral("7"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogosWalletProviderTest::preservesCreatedAccountWhenSnapshotRefreshFails()
|
void LogosWalletProviderTest::createdAccountDoesNotRescanWallet()
|
||||||
{
|
{
|
||||||
LogosModules modules;
|
LogosModules modules;
|
||||||
modules.logos_execution_zone.sequencerAddress = QStringLiteral("http://sequencer");
|
modules.logos_execution_zone.sequencerAddress = QStringLiteral("http://sequencer");
|
||||||
@ -268,11 +270,13 @@ void LogosWalletProviderTest::preservesCreatedAccountWhenSnapshotRefreshFails()
|
|||||||
|
|
||||||
modules.logos_execution_zone.currentBlockHeight = 1;
|
modules.logos_execution_zone.currentBlockHeight = 1;
|
||||||
modules.logos_execution_zone.syncResult = 1;
|
modules.logos_execution_zone.syncResult = 1;
|
||||||
|
const int syncCalls = modules.logos_execution_zone.syncCalls;
|
||||||
const WalletAccountCreation creation = provider.createAccount(true);
|
const WalletAccountCreation creation = provider.createAccount(true);
|
||||||
|
|
||||||
QVERIFY(creation.ok());
|
QVERIFY(creation.ok());
|
||||||
QCOMPARE(creation.accountId, ACCOUNT_A);
|
QCOMPARE(creation.accountId, ACCOUNT_A);
|
||||||
QCOMPARE(creation.snapshot.failure, WalletFailure::ReadFailed);
|
QVERIFY(creation.snapshot.ok());
|
||||||
|
QCOMPARE(modules.logos_execution_zone.syncCalls, syncCalls);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogosWalletProviderTest::dispatchesExactGenericTransaction()
|
void LogosWalletProviderTest::dispatchesExactGenericTransaction()
|
||||||
@ -419,6 +423,33 @@ void LogosWalletProviderTest::controllerOwnsUiWalletFlow()
|
|||||||
settings.clear();
|
settings.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LogosWalletProviderTest::controllerOpenDoesNotWaitForWalletSync()
|
||||||
|
{
|
||||||
|
const QString settingsApplication = QStringLiteral("WalletAsyncOpenTest");
|
||||||
|
QSettings settings(QStringLiteral("Logos"), settingsApplication);
|
||||||
|
settings.clear();
|
||||||
|
|
||||||
|
FakeWalletProvider provider;
|
||||||
|
provider.deferAsync = true;
|
||||||
|
provider.connectResult.snapshot.accounts = {
|
||||||
|
{ ACCOUNT_A, QStringLiteral("5"), true },
|
||||||
|
};
|
||||||
|
WalletController controller(provider, settingsApplication);
|
||||||
|
|
||||||
|
QVERIFY(controller.open());
|
||||||
|
QCOMPARE(provider.connectCalls, 1);
|
||||||
|
QVERIFY(!controller.state().isWalletOpen);
|
||||||
|
QCOMPARE(controller.state().syncStatus, QStringLiteral("opening"));
|
||||||
|
QCOMPARE(controller.accountModel()->count(), 0);
|
||||||
|
|
||||||
|
provider.finishConnect();
|
||||||
|
QVERIFY(controller.state().isWalletOpen);
|
||||||
|
QVERIFY(controller.state().canSubmit());
|
||||||
|
QCOMPARE(controller.state().syncStatus, QStringLiteral("ready"));
|
||||||
|
QCOMPARE(controller.accountModel()->count(), 1);
|
||||||
|
settings.clear();
|
||||||
|
}
|
||||||
|
|
||||||
void LogosWalletProviderTest::controllerStopsReachabilityChecksAfterDisconnect()
|
void LogosWalletProviderTest::controllerStopsReachabilityChecksAfterDisconnect()
|
||||||
{
|
{
|
||||||
const QString settingsApplication = QStringLiteral("WalletReachabilityTest");
|
const QString settingsApplication = QStringLiteral("WalletReachabilityTest");
|
||||||
|
|||||||
@ -6,6 +6,8 @@
|
|||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <QVariantList>
|
#include <QVariantList>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
class LogosAPI;
|
class LogosAPI;
|
||||||
|
|
||||||
class FakeExecutionZone {
|
class FakeExecutionZone {
|
||||||
@ -48,6 +50,13 @@ public:
|
|||||||
return openResult;
|
return openResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void openAsync(const QString& config,
|
||||||
|
const QString& storage,
|
||||||
|
std::function<void(int)> callback)
|
||||||
|
{
|
||||||
|
callback(open(config, storage));
|
||||||
|
}
|
||||||
|
|
||||||
QString create_new(const QString& config,
|
QString create_new(const QString& config,
|
||||||
const QString& storage,
|
const QString& storage,
|
||||||
const QString& password)
|
const QString& password)
|
||||||
@ -64,36 +73,69 @@ public:
|
|||||||
return saveResult;
|
return saveResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void saveAsync(std::function<void(int)> callback) { callback(save()); }
|
||||||
|
|
||||||
QString create_account_public() { return publicAccountId; }
|
QString create_account_public() { return publicAccountId; }
|
||||||
QString create_account_private() { return privateAccountId; }
|
QString create_account_private() { return privateAccountId; }
|
||||||
|
|
||||||
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; }
|
||||||
|
void get_last_synced_blockAsync(std::function<void(int)> callback)
|
||||||
|
{
|
||||||
|
callback(get_last_synced_block());
|
||||||
|
}
|
||||||
|
void get_current_block_heightAsync(std::function<void(int)> callback)
|
||||||
|
{
|
||||||
|
callback(get_current_block_height());
|
||||||
|
}
|
||||||
|
|
||||||
int sync_to_block(quint64)
|
int sync_to_block(quint64)
|
||||||
{
|
{
|
||||||
++syncCalls;
|
++syncCalls;
|
||||||
return syncResult;
|
return syncResult;
|
||||||
}
|
}
|
||||||
|
void sync_to_blockAsync(int blockId, std::function<void(int)> callback)
|
||||||
|
{
|
||||||
|
callback(sync_to_block(static_cast<quint64>(blockId)));
|
||||||
|
}
|
||||||
|
|
||||||
QString get_sequencer_addr() const { return sequencerAddress; }
|
QString get_sequencer_addr() const { return sequencerAddress; }
|
||||||
|
void get_sequencer_addrAsync(std::function<void(QString)> callback)
|
||||||
|
{
|
||||||
|
callback(get_sequencer_addr());
|
||||||
|
}
|
||||||
|
|
||||||
QVariantList list_accounts()
|
QVariantList list_accounts()
|
||||||
{
|
{
|
||||||
++listCalls;
|
++listCalls;
|
||||||
return accounts;
|
return accounts;
|
||||||
}
|
}
|
||||||
|
void list_accountsAsync(std::function<void(QVariantList)> callback)
|
||||||
|
{
|
||||||
|
callback(list_accounts());
|
||||||
|
}
|
||||||
|
|
||||||
QString get_account_public(const QString& accountId)
|
QString get_account_public(const QString& accountId)
|
||||||
{
|
{
|
||||||
++publicReadCalls;
|
++publicReadCalls;
|
||||||
return publicAccounts.value(accountId);
|
return publicAccounts.value(accountId);
|
||||||
}
|
}
|
||||||
|
void get_account_publicAsync(const QString& accountId,
|
||||||
|
std::function<void(QString)> callback)
|
||||||
|
{
|
||||||
|
callback(get_account_public(accountId));
|
||||||
|
}
|
||||||
|
|
||||||
QString get_balance(const QString& accountId, bool) const
|
QString get_balance(const QString& accountId, bool) const
|
||||||
{
|
{
|
||||||
return balances.value(accountId);
|
return balances.value(accountId);
|
||||||
}
|
}
|
||||||
|
void get_balanceAsync(const QString& accountId,
|
||||||
|
bool isPublic,
|
||||||
|
std::function<void(QString)> callback)
|
||||||
|
{
|
||||||
|
callback(get_balance(accountId, isPublic));
|
||||||
|
}
|
||||||
|
|
||||||
QString send_generic_public_transaction(
|
QString send_generic_public_transaction(
|
||||||
const QStringList& accountIds,
|
const QStringList& accountIds,
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include "WalletProvider.h"
|
#include "WalletProvider.h"
|
||||||
|
|
||||||
class FakeWalletProvider final : public WalletProvider {
|
class FakeWalletProvider final : public WalletProvider {
|
||||||
@ -23,6 +25,9 @@ public:
|
|||||||
bool lastAccountWasPublic = false;
|
bool lastAccountWasPublic = false;
|
||||||
WalletPaths lastPaths;
|
WalletPaths lastPaths;
|
||||||
WalletTransaction lastTransaction;
|
WalletTransaction lastTransaction;
|
||||||
|
bool deferAsync = false;
|
||||||
|
SessionCallback pendingConnectCallback;
|
||||||
|
SnapshotCallback pendingSnapshotCallback;
|
||||||
|
|
||||||
WalletSession connect(const WalletPaths& paths) override
|
WalletSession connect(const WalletPaths& paths) override
|
||||||
{
|
{
|
||||||
@ -31,6 +36,16 @@ public:
|
|||||||
return connectResult;
|
return connectResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void connectAsync(const WalletPaths& paths, SessionCallback callback) override
|
||||||
|
{
|
||||||
|
++connectCalls;
|
||||||
|
lastPaths = paths;
|
||||||
|
if (deferAsync)
|
||||||
|
pendingConnectCallback = std::move(callback);
|
||||||
|
else
|
||||||
|
callback(connectResult);
|
||||||
|
}
|
||||||
|
|
||||||
WalletCreation createWallet(const WalletPaths& paths,
|
WalletCreation createWallet(const WalletPaths& paths,
|
||||||
const QString&) override
|
const QString&) override
|
||||||
{
|
{
|
||||||
@ -46,6 +61,16 @@ public:
|
|||||||
return snapshotResult;
|
return snapshotResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void snapshotAsync(bool forceRefresh, SnapshotCallback callback) override
|
||||||
|
{
|
||||||
|
++snapshotCalls;
|
||||||
|
lastForceRefresh = forceRefresh;
|
||||||
|
if (deferAsync)
|
||||||
|
pendingSnapshotCallback = std::move(callback);
|
||||||
|
else
|
||||||
|
callback(snapshotResult);
|
||||||
|
}
|
||||||
|
|
||||||
void clearSnapshot() override { ++clearCalls; }
|
void clearSnapshot() override { ++clearCalls; }
|
||||||
|
|
||||||
WalletAccountCreation createAccount(bool isPublic) override
|
WalletAccountCreation createAccount(bool isPublic) override
|
||||||
@ -72,4 +97,18 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void disconnect() override { ++disconnectCalls; }
|
void disconnect() override { ++disconnectCalls; }
|
||||||
|
|
||||||
|
void finishConnect()
|
||||||
|
{
|
||||||
|
SessionCallback callback = std::move(pendingConnectCallback);
|
||||||
|
if (callback)
|
||||||
|
callback(connectResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
void finishSnapshot()
|
||||||
|
{
|
||||||
|
SnapshotCallback callback = std::move(pendingSnapshotCallback);
|
||||||
|
if (callback)
|
||||||
|
callback(snapshotResult);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user