mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-08-26 14:41:12 +00:00
feat(wallet): humanize shared wallet experience
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
#include <QTimer>
|
||||
#include <QVariantList>
|
||||
#include <QVariantMap>
|
||||
|
||||
@@ -73,6 +74,46 @@ WalletCreation failedCreation(WalletFailure failure)
|
||||
creation.snapshot.failure = failure;
|
||||
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;
|
||||
}
|
||||
|
||||
void applyPublicRead(WalletAccount& account, const WalletAccountRead& read)
|
||||
{
|
||||
account.readStatus = read.status;
|
||||
account.programOwner = read.programOwner;
|
||||
account.dataHex = read.dataHex;
|
||||
}
|
||||
}
|
||||
|
||||
struct LogosWalletProvider::Impl {
|
||||
@@ -130,6 +171,76 @@ WalletSession LogosWalletProvider::connect(const WalletPaths& paths)
|
||||
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,
|
||||
const QString& password)
|
||||
{
|
||||
@@ -180,6 +291,26 @@ WalletSnapshot LogosWalletProvider::snapshot(bool forceRefresh)
|
||||
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()
|
||||
{
|
||||
m_snapshot = {};
|
||||
@@ -208,45 +339,57 @@ WalletAccountCreation LogosWalletProvider::createAccount(bool isPublic)
|
||||
|
||||
if (isPublic)
|
||||
creation.publicAccount = readPublicAccount(creation.accountId);
|
||||
|
||||
clearSnapshot();
|
||||
creation.snapshot = snapshot(true);
|
||||
return creation;
|
||||
}
|
||||
|
||||
WalletAccountRead LogosWalletProvider::readPublicAccount(const QString& accountId) const
|
||||
{
|
||||
WalletAccountRead read;
|
||||
read.accountId = accountId;
|
||||
if (!m_impl->logos || !isHex(accountId, 64))
|
||||
return read;
|
||||
return WalletAccountRead { accountId };
|
||||
return parsePublicAccount(
|
||||
accountId,
|
||||
m_impl->logos->logos_execution_zone.get_account_public(accountId));
|
||||
}
|
||||
|
||||
QJsonParseError parseError;
|
||||
const QJsonDocument document = QJsonDocument::fromJson(
|
||||
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;
|
||||
void LogosWalletProvider::readPublicAccountsAsync(
|
||||
const QStringList& accountIds,
|
||||
AccountReadsCallback callback)
|
||||
{
|
||||
if (!m_impl->logos || accountIds.isEmpty()) {
|
||||
QTimer::singleShot(0,
|
||||
[callback = std::move(callback)]() mutable { callback({}); });
|
||||
return;
|
||||
}
|
||||
|
||||
read.status = QStringLiteral("ok");
|
||||
read.programOwner = owner;
|
||||
read.balanceHex = balance;
|
||||
read.nonceHex = nonce;
|
||||
read.dataHex = data;
|
||||
return read;
|
||||
struct BatchState {
|
||||
QVector<WalletAccountRead> reads;
|
||||
qsizetype remaining = 0;
|
||||
AccountReadsCallback callback;
|
||||
};
|
||||
const quint64 generation = m_generation;
|
||||
auto state = std::make_shared<BatchState>();
|
||||
state->reads.resize(accountIds.size());
|
||||
state->remaining = accountIds.size();
|
||||
state->callback = std::move(callback);
|
||||
for (qsizetype index = 0; index < accountIds.size(); ++index) {
|
||||
const QString accountId = accountIds.at(index);
|
||||
if (!isHex(accountId, 64)) {
|
||||
state->reads[index] = WalletAccountRead { accountId };
|
||||
if (--state->remaining == 0)
|
||||
state->callback(std::move(state->reads));
|
||||
continue;
|
||||
}
|
||||
m_impl->logos->logos_execution_zone.get_account_publicAsync(
|
||||
accountId,
|
||||
[this, generation, state, index, accountId](QString payload) mutable {
|
||||
if (generation != m_generation)
|
||||
return;
|
||||
state->reads[index] = parsePublicAccount(accountId, payload);
|
||||
if (--state->remaining == 0)
|
||||
state->callback(std::move(state->reads));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
WalletSubmission LogosWalletProvider::submitPublicTransaction(
|
||||
@@ -314,6 +457,7 @@ WalletSubmission LogosWalletProvider::submitPublicTransaction(
|
||||
|
||||
void LogosWalletProvider::disconnect()
|
||||
{
|
||||
++m_generation;
|
||||
if (m_connected)
|
||||
save();
|
||||
clearSnapshot();
|
||||
@@ -361,20 +505,181 @@ WalletSnapshot LogosWalletProvider::loadSnapshot()
|
||||
if (account.isPublic) {
|
||||
const WalletAccountRead read = readPublicAccount(address);
|
||||
result.publicAccountReads.append(read);
|
||||
applyPublicRead(account, read);
|
||||
account.balance = read.ok()
|
||||
? littleEndianU128ToDecimal(read.balanceHex)
|
||||
: m_impl->logos->logos_execution_zone.get_balance(address, true);
|
||||
} else {
|
||||
account.readStatus = QStringLiteral("private");
|
||||
account.balance = m_impl->logos->logos_execution_zone.get_balance(address, false);
|
||||
}
|
||||
result.accounts.append(account);
|
||||
}
|
||||
|
||||
if (!save())
|
||||
result.failure = WalletFailure::SaveFailed;
|
||||
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(),
|
||||
};
|
||||
if (!state->snapshot.accounts.at(index).isPublic) {
|
||||
state->snapshot.accounts[index].readStatus =
|
||||
QStringLiteral("private");
|
||||
}
|
||||
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;
|
||||
applyPublicRead(
|
||||
state->snapshot.accounts[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
|
||||
{
|
||||
return m_impl->logos
|
||||
|
||||
Reference in New Issue
Block a user