mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-07-25 00:03:11 +00:00
fix(amm): reuse pending LP accounts
This commit is contained in:
parent
f704ab6a45
commit
d51856a34f
@ -19,6 +19,7 @@
|
|||||||
namespace {
|
namespace {
|
||||||
const char SCHEMA[] = "new-position.v2";
|
const char SCHEMA[] = "new-position.v2";
|
||||||
constexpr qsizetype HASH_BYTES = 32;
|
constexpr qsizetype HASH_BYTES = 32;
|
||||||
|
constexpr qsizetype ACCOUNT_ID_HEX_SIZE = HASH_BYTES * 2;
|
||||||
constexpr std::size_t BASE58_BUFFER_SIZE = 45;
|
constexpr std::size_t BASE58_BUFFER_SIZE = 45;
|
||||||
|
|
||||||
QString base58TransactionId(const QString& transactionHash)
|
QString base58TransactionId(const QString& transactionHash)
|
||||||
@ -53,6 +54,32 @@ namespace {
|
|||||||
static_cast<qsizetype>(bytes.size())).toHex());
|
static_cast<qsizetype>(bytes.size())).toHex());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isLowerHex(const QString& value, qsizetype size)
|
||||||
|
{
|
||||||
|
if (value.size() != size)
|
||||||
|
return false;
|
||||||
|
for (qsizetype index = 0; index < value.size(); ++index) {
|
||||||
|
const char16_t character = value.at(index).unicode();
|
||||||
|
if (!((character >= u'0' && character <= u'9')
|
||||||
|
|| (character >= u'a' && character <= u'f'))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isDefaultAccountRead(const WalletAccountRead& read,
|
||||||
|
const QString& expectedAccountId)
|
||||||
|
{
|
||||||
|
return read.ok()
|
||||||
|
&& read.accountId == expectedAccountId
|
||||||
|
&& isLowerHex(read.accountId, ACCOUNT_ID_HEX_SIZE)
|
||||||
|
&& read.programOwner == QString(ACCOUNT_ID_HEX_SIZE, QLatin1Char('0'))
|
||||||
|
&& read.balanceHex == QString(32, QLatin1Char('0'))
|
||||||
|
&& read.nonceHex == QString(32, QLatin1Char('0'))
|
||||||
|
&& read.dataHex.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
QJsonObject issue(const QString& code,
|
QJsonObject issue(const QString& code,
|
||||||
const QJsonArray& blockingFields = {})
|
const QJsonArray& blockingFields = {})
|
||||||
{
|
{
|
||||||
@ -172,9 +199,10 @@ NewPositionRuntime::NewPositionRuntime(WalletProvider* wallet,
|
|||||||
void NewPositionRuntime::clearWalletAccounts()
|
void NewPositionRuntime::clearWalletAccounts()
|
||||||
{
|
{
|
||||||
++m_walletGeneration;
|
++m_walletGeneration;
|
||||||
cancelSubmit();
|
|
||||||
m_walletPublicAccountIds.clear();
|
m_walletPublicAccountIds.clear();
|
||||||
|
clearPendingFreshLp();
|
||||||
m_wallet->clearSnapshot();
|
m_wallet->clearSnapshot();
|
||||||
|
cancelSubmit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NewPositionRuntime::setWalletAccounts(const QVector<WalletAccount>& accounts)
|
void NewPositionRuntime::setWalletAccounts(const QVector<WalletAccount>& accounts)
|
||||||
@ -184,11 +212,33 @@ void NewPositionRuntime::setWalletAccounts(const QVector<WalletAccount>& account
|
|||||||
if (account.isPublic)
|
if (account.isPublic)
|
||||||
publicAccountIds.append(account.address);
|
publicAccountIds.append(account.address);
|
||||||
}
|
}
|
||||||
|
publicAccountIds.sort(Qt::CaseSensitive);
|
||||||
if (publicAccountIds != m_walletPublicAccountIds) {
|
if (publicAccountIds != m_walletPublicAccountIds) {
|
||||||
++m_walletGeneration;
|
if (!m_pendingFreshLpAccountId.isEmpty()
|
||||||
|
&& !publicAccountIds.contains(m_pendingFreshLpAccountId)
|
||||||
|
&& m_freshLpState == FreshLpState::Ready) {
|
||||||
|
clearPendingFreshLp();
|
||||||
|
}
|
||||||
|
m_walletPublicAccountIds = std::move(publicAccountIds);
|
||||||
cancelSubmit();
|
cancelSubmit();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
m_walletPublicAccountIds = std::move(publicAccountIds);
|
}
|
||||||
|
|
||||||
|
void NewPositionRuntime::rememberPendingFreshLp(const QString& accountId)
|
||||||
|
{
|
||||||
|
m_pendingFreshLpAccountId = accountId;
|
||||||
|
m_freshLpState = FreshLpState::Ready;
|
||||||
|
if (!m_walletPublicAccountIds.contains(accountId)) {
|
||||||
|
m_walletPublicAccountIds.append(accountId);
|
||||||
|
m_walletPublicAccountIds.sort(Qt::CaseSensitive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NewPositionRuntime::clearPendingFreshLp()
|
||||||
|
{
|
||||||
|
m_pendingFreshLpAccountId.clear();
|
||||||
|
m_freshLpState = FreshLpState::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NewPositionRuntime::cancelSubmit()
|
void NewPositionRuntime::cancelSubmit()
|
||||||
@ -558,6 +608,11 @@ void NewPositionRuntime::submitAsync(const QVariantMap& request,
|
|||||||
callback(publicError(QStringLiteral("wallet_syncing")).toVariantMap());
|
callback(publicError(QStringLiteral("wallet_syncing")).toVariantMap());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (m_freshLpState == FreshLpState::Creating
|
||||||
|
|| m_freshLpState == FreshLpState::Submitting) {
|
||||||
|
callback(publicError(QStringLiteral("submit_in_progress")).toVariantMap());
|
||||||
|
return;
|
||||||
|
}
|
||||||
m_submitInFlight = true;
|
m_submitInFlight = true;
|
||||||
const quint64 submitGeneration = ++m_submitGeneration;
|
const quint64 submitGeneration = ++m_submitGeneration;
|
||||||
const quint64 walletGeneration = m_walletGeneration;
|
const quint64 walletGeneration = m_walletGeneration;
|
||||||
@ -595,35 +650,132 @@ void NewPositionRuntime::submitAsync(const QVariantMap& request,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (quote.value(QStringLiteral("requiresFreshLp")).toBool(false)) {
|
if (quote.value(QStringLiteral("requiresFreshLp")).toBool(false)) {
|
||||||
guard->m_wallet->createAccountAsync(
|
guard->prepareFreshLpAsync(
|
||||||
true,
|
std::move(input), quoteHash,
|
||||||
[guard, input = std::move(input), quoteHash,
|
submitGeneration, walletGeneration);
|
||||||
submitGeneration, walletGeneration](
|
|
||||||
WalletAccountCreation creation) mutable {
|
|
||||||
if (!guard
|
|
||||||
|| !guard->submitIsCurrent(
|
|
||||||
submitGeneration, walletGeneration))
|
|
||||||
return;
|
|
||||||
if (!creation.ok() || !creation.publicAccount.ok()) {
|
|
||||||
const QString code = creation.failure
|
|
||||||
== 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;
|
return;
|
||||||
}
|
}
|
||||||
guard->submitPlanAsync(
|
guard->submitPlanAsync(
|
||||||
std::move(input), quoteHash, {},
|
std::move(input), quoteHash, {}, {},
|
||||||
|
submitGeneration, walletGeneration);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void NewPositionRuntime::prepareFreshLpAsync(QJsonObject input,
|
||||||
|
const QString& quoteHash,
|
||||||
|
quint64 submitGeneration,
|
||||||
|
quint64 walletGeneration)
|
||||||
|
{
|
||||||
|
if (!submitIsCurrent(submitGeneration, walletGeneration))
|
||||||
|
return;
|
||||||
|
if (m_freshLpState == FreshLpState::Ready
|
||||||
|
&& !m_pendingFreshLpAccountId.isEmpty()) {
|
||||||
|
validatePendingFreshLpAsync(
|
||||||
|
std::move(input), quoteHash,
|
||||||
|
submitGeneration, walletGeneration);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_freshLpState != FreshLpState::None) {
|
||||||
|
finishSubmit(
|
||||||
|
submitGeneration,
|
||||||
|
publicError(QStringLiteral("submit_in_progress")).toVariantMap());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_freshLpState = FreshLpState::Creating;
|
||||||
|
QPointer<NewPositionRuntime> guard(this);
|
||||||
|
m_wallet->createAccountAsync(
|
||||||
|
true,
|
||||||
|
[guard, input = std::move(input), quoteHash,
|
||||||
|
submitGeneration, walletGeneration](
|
||||||
|
WalletAccountCreation creation) mutable {
|
||||||
|
if (!guard || walletGeneration != guard->m_walletGeneration)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const bool reusable = creation.ok()
|
||||||
|
&& isLowerHex(creation.accountId, ACCOUNT_ID_HEX_SIZE);
|
||||||
|
if (reusable)
|
||||||
|
guard->rememberPendingFreshLp(creation.accountId);
|
||||||
|
else
|
||||||
|
guard->clearPendingFreshLp();
|
||||||
|
if (!guard->submitIsCurrent(submitGeneration, walletGeneration))
|
||||||
|
return;
|
||||||
|
if (!reusable) {
|
||||||
|
const QString code = creation.failure
|
||||||
|
== WalletFailure::WalletUnavailable
|
||||||
|
? QStringLiteral("wallet_unavailable")
|
||||||
|
: QStringLiteral("wallet_submission_failed");
|
||||||
|
guard->finishSubmit(
|
||||||
|
submitGeneration, publicError(code).toVariantMap());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WalletAccountRead read = std::move(creation.publicAccount);
|
||||||
|
read.accountId = creation.accountId;
|
||||||
|
if (isDefaultAccountRead(read, creation.accountId)) {
|
||||||
|
guard->submitPlanAsync(
|
||||||
|
std::move(input), quoteHash, accountReadJson(read),
|
||||||
|
creation.accountId, submitGeneration, walletGeneration);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
guard->validatePendingFreshLpAsync(
|
||||||
|
std::move(input), quoteHash,
|
||||||
|
submitGeneration, walletGeneration);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void NewPositionRuntime::validatePendingFreshLpAsync(
|
||||||
|
QJsonObject input,
|
||||||
|
const QString& quoteHash,
|
||||||
|
quint64 submitGeneration,
|
||||||
|
quint64 walletGeneration)
|
||||||
|
{
|
||||||
|
if (!submitIsCurrent(submitGeneration, walletGeneration))
|
||||||
|
return;
|
||||||
|
const QString accountId = m_pendingFreshLpAccountId;
|
||||||
|
if (m_freshLpState != FreshLpState::Ready
|
||||||
|
|| accountId.isEmpty()
|
||||||
|
|| !m_sequencer
|
||||||
|
|| !m_sequencer->isConfigured()) {
|
||||||
|
finishSubmit(
|
||||||
|
submitGeneration,
|
||||||
|
publicError(QStringLiteral("account_read_failed")).toVariantMap());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPointer<NewPositionRuntime> guard(this);
|
||||||
|
m_sequencer->readAccounts(
|
||||||
|
{ accountId }, true,
|
||||||
|
[guard, input = std::move(input), quoteHash, accountId,
|
||||||
|
submitGeneration, walletGeneration](
|
||||||
|
QVector<WalletAccountRead> reads) mutable {
|
||||||
|
if (!guard
|
||||||
|
|| !guard->submitIsCurrent(
|
||||||
|
submitGeneration, walletGeneration)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (guard->m_pendingFreshLpAccountId != accountId) {
|
||||||
|
guard->finishSubmit(
|
||||||
|
submitGeneration,
|
||||||
|
publicError(QStringLiteral("wallet_unavailable")).toVariantMap());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const WalletAccountRead read = reads.value(0);
|
||||||
|
if (!read.ok()) {
|
||||||
|
guard->finishSubmit(
|
||||||
|
submitGeneration,
|
||||||
|
publicError(QStringLiteral("account_read_failed")).toVariantMap());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isDefaultAccountRead(read, accountId)) {
|
||||||
|
guard->clearPendingFreshLp();
|
||||||
|
guard->finishSubmit(
|
||||||
|
submitGeneration,
|
||||||
|
publicError(QStringLiteral("submission_status_unknown")).toVariantMap());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
guard->submitPlanAsync(
|
||||||
|
std::move(input), quoteHash, accountReadJson(read), accountId,
|
||||||
submitGeneration, walletGeneration);
|
submitGeneration, walletGeneration);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -631,6 +783,7 @@ void NewPositionRuntime::submitAsync(const QVariantMap& request,
|
|||||||
void NewPositionRuntime::submitPlanAsync(QJsonObject input,
|
void NewPositionRuntime::submitPlanAsync(QJsonObject input,
|
||||||
const QString& quoteHash,
|
const QString& quoteHash,
|
||||||
QJsonValue freshLp,
|
QJsonValue freshLp,
|
||||||
|
QString freshLpAccountId,
|
||||||
quint64 submitGeneration,
|
quint64 submitGeneration,
|
||||||
quint64 walletGeneration)
|
quint64 walletGeneration)
|
||||||
{
|
{
|
||||||
@ -678,18 +831,32 @@ void NewPositionRuntime::submitPlanAsync(QJsonObject input,
|
|||||||
jsonBoolList(plan.value(QStringLiteral("signingRequirements")).toArray()),
|
jsonBoolList(plan.value(QStringLiteral("signingRequirements")).toArray()),
|
||||||
jsonUIntList(plan.value(QStringLiteral("instruction")).toArray()),
|
jsonUIntList(plan.value(QStringLiteral("instruction")).toArray()),
|
||||||
};
|
};
|
||||||
|
if (!freshLpAccountId.isEmpty()
|
||||||
|
&& freshLpAccountId == m_pendingFreshLpAccountId) {
|
||||||
|
m_freshLpState = FreshLpState::Submitting;
|
||||||
|
}
|
||||||
QPointer<NewPositionRuntime> guard(this);
|
QPointer<NewPositionRuntime> guard(this);
|
||||||
m_wallet->submitPublicTransactionAsync(
|
m_wallet->submitPublicTransactionAsync(
|
||||||
transaction,
|
transaction,
|
||||||
[guard, submitGeneration, walletGeneration, deadlineMs =
|
[guard, submitGeneration, walletGeneration,
|
||||||
|
freshLpAccountId = std::move(freshLpAccountId), deadlineMs =
|
||||||
plan.value(QStringLiteral("deadlineMs")), affectedAccountIds =
|
plan.value(QStringLiteral("deadlineMs")), affectedAccountIds =
|
||||||
plan.value(QStringLiteral("affectedAccountIds"))](
|
plan.value(QStringLiteral("affectedAccountIds"))](
|
||||||
WalletSubmission submission) mutable {
|
WalletSubmission submission) mutable {
|
||||||
if (!guard
|
if (!guard)
|
||||||
|| !guard->submitIsCurrent(submitGeneration, walletGeneration))
|
|
||||||
return;
|
return;
|
||||||
const QString transactionId = submission.accepted()
|
const QString transactionId = submission.accepted()
|
||||||
? base58TransactionId(submission.nativeHash) : QString();
|
? base58TransactionId(submission.nativeHash) : QString();
|
||||||
|
if (walletGeneration == guard->m_walletGeneration
|
||||||
|
&& !freshLpAccountId.isEmpty()
|
||||||
|
&& freshLpAccountId == guard->m_pendingFreshLpAccountId) {
|
||||||
|
if (submission.accepted())
|
||||||
|
guard->clearPendingFreshLp();
|
||||||
|
else
|
||||||
|
guard->m_freshLpState = FreshLpState::Ready;
|
||||||
|
}
|
||||||
|
if (!guard->submitIsCurrent(submitGeneration, walletGeneration))
|
||||||
|
return;
|
||||||
if (transactionId.isEmpty()) {
|
if (transactionId.isEmpty()) {
|
||||||
const QString code = submission.failure
|
const QString code = submission.failure
|
||||||
== WalletFailure::WalletUnavailable
|
== WalletFailure::WalletUnavailable
|
||||||
@ -803,6 +970,10 @@ QVariantMap NewPositionRuntime::submit(const QVariantMap& request,
|
|||||||
return publicError(QStringLiteral("submit_in_progress")).toVariantMap();
|
return publicError(QStringLiteral("submit_in_progress")).toVariantMap();
|
||||||
if (!walletOpen)
|
if (!walletOpen)
|
||||||
return publicError(QStringLiteral("wallet_unavailable")).toVariantMap();
|
return publicError(QStringLiteral("wallet_unavailable")).toVariantMap();
|
||||||
|
if (m_freshLpState == FreshLpState::Creating
|
||||||
|
|| m_freshLpState == FreshLpState::Submitting) {
|
||||||
|
return publicError(QStringLiteral("submit_in_progress")).toVariantMap();
|
||||||
|
}
|
||||||
QScopedValueRollback<bool> submitGuard(m_submitInFlight, true);
|
QScopedValueRollback<bool> submitGuard(m_submitInFlight, true);
|
||||||
|
|
||||||
QJsonObject error;
|
QJsonObject error;
|
||||||
@ -826,11 +997,34 @@ QVariantMap NewPositionRuntime::submit(const QVariantMap& request,
|
|||||||
}
|
}
|
||||||
|
|
||||||
QJsonValue freshLp;
|
QJsonValue freshLp;
|
||||||
|
QString freshLpAccountId;
|
||||||
if (quote.value(QStringLiteral("requiresFreshLp")).toBool(false)) {
|
if (quote.value(QStringLiteral("requiresFreshLp")).toBool(false)) {
|
||||||
const WalletAccountCreation creation = m_wallet->createAccount(true);
|
WalletAccountRead read;
|
||||||
if (!creation.ok() || !creation.publicAccount.ok())
|
if (m_freshLpState == FreshLpState::None) {
|
||||||
return publicError(QStringLiteral("wallet_submission_failed")).toVariantMap();
|
m_freshLpState = FreshLpState::Creating;
|
||||||
freshLp = accountReadJson(creation.publicAccount);
|
const WalletAccountCreation creation = m_wallet->createAccount(true);
|
||||||
|
if (!creation.ok()
|
||||||
|
|| !isLowerHex(creation.accountId, ACCOUNT_ID_HEX_SIZE)) {
|
||||||
|
clearPendingFreshLp();
|
||||||
|
return publicError(QStringLiteral("wallet_submission_failed")).toVariantMap();
|
||||||
|
}
|
||||||
|
rememberPendingFreshLp(creation.accountId);
|
||||||
|
read = creation.publicAccount;
|
||||||
|
read.accountId = creation.accountId;
|
||||||
|
} else if (m_freshLpState == FreshLpState::Ready
|
||||||
|
&& !m_pendingFreshLpAccountId.isEmpty()) {
|
||||||
|
read = m_wallet->readPublicAccount(m_pendingFreshLpAccountId);
|
||||||
|
} else {
|
||||||
|
return publicError(QStringLiteral("submit_in_progress")).toVariantMap();
|
||||||
|
}
|
||||||
|
freshLpAccountId = m_pendingFreshLpAccountId;
|
||||||
|
if (!read.ok())
|
||||||
|
return publicError(QStringLiteral("account_read_failed")).toVariantMap();
|
||||||
|
if (!isDefaultAccountRead(read, freshLpAccountId)) {
|
||||||
|
clearPendingFreshLp();
|
||||||
|
return publicError(QStringLiteral("submission_status_unknown")).toVariantMap();
|
||||||
|
}
|
||||||
|
freshLp = accountReadJson(read);
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject planInput = input;
|
QJsonObject planInput = input;
|
||||||
@ -859,14 +1053,27 @@ QVariantMap NewPositionRuntime::submit(const QVariantMap& request,
|
|||||||
|| static_cast<qulonglong>(QDateTime::currentMSecsSinceEpoch()) >= deadline) {
|
|| static_cast<qulonglong>(QDateTime::currentMSecsSinceEpoch()) >= deadline) {
|
||||||
return publicError(QStringLiteral("transaction_deadline_expired")).toVariantMap();
|
return publicError(QStringLiteral("transaction_deadline_expired")).toVariantMap();
|
||||||
}
|
}
|
||||||
|
if (!freshLpAccountId.isEmpty()
|
||||||
|
&& freshLpAccountId == m_pendingFreshLpAccountId) {
|
||||||
|
m_freshLpState = FreshLpState::Submitting;
|
||||||
|
}
|
||||||
const WalletSubmission submission = m_wallet->submitPublicTransaction({
|
const WalletSubmission submission = m_wallet->submitPublicTransaction({
|
||||||
programId,
|
programId,
|
||||||
accountIds,
|
accountIds,
|
||||||
signingRequirements,
|
signingRequirements,
|
||||||
instruction,
|
instruction,
|
||||||
});
|
});
|
||||||
if (!submission.accepted())
|
if (!submission.accepted()) {
|
||||||
|
if (!freshLpAccountId.isEmpty()
|
||||||
|
&& freshLpAccountId == m_pendingFreshLpAccountId) {
|
||||||
|
m_freshLpState = FreshLpState::Ready;
|
||||||
|
}
|
||||||
return publicError(QStringLiteral("wallet_submission_failed")).toVariantMap();
|
return publicError(QStringLiteral("wallet_submission_failed")).toVariantMap();
|
||||||
|
}
|
||||||
|
if (!freshLpAccountId.isEmpty()
|
||||||
|
&& freshLpAccountId == m_pendingFreshLpAccountId) {
|
||||||
|
clearPendingFreshLp();
|
||||||
|
}
|
||||||
const QString transactionId = base58TransactionId(submission.nativeHash);
|
const QString transactionId = base58TransactionId(submission.nativeHash);
|
||||||
if (transactionId.isEmpty())
|
if (transactionId.isEmpty())
|
||||||
return publicError(QStringLiteral("wallet_submission_failed")).toVariantMap();
|
return publicError(QStringLiteral("wallet_submission_failed")).toVariantMap();
|
||||||
|
|||||||
@ -57,6 +57,13 @@ public:
|
|||||||
bool walletOpen);
|
bool walletOpen);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
enum class FreshLpState {
|
||||||
|
None,
|
||||||
|
Creating,
|
||||||
|
Ready,
|
||||||
|
Submitting,
|
||||||
|
};
|
||||||
|
|
||||||
QJsonArray walletAccountReads(bool walletOpen, bool refresh) const;
|
QJsonArray walletAccountReads(bool walletOpen, bool refresh) const;
|
||||||
QJsonObject buildQuoteInput(const QVariantMap& request,
|
QJsonObject buildQuoteInput(const QVariantMap& request,
|
||||||
const ActiveNetworkSnapshot& network,
|
const ActiveNetworkSnapshot& network,
|
||||||
@ -71,8 +78,19 @@ private:
|
|||||||
void submitPlanAsync(QJsonObject input,
|
void submitPlanAsync(QJsonObject input,
|
||||||
const QString& quoteHash,
|
const QString& quoteHash,
|
||||||
QJsonValue freshLp,
|
QJsonValue freshLp,
|
||||||
|
QString freshLpAccountId,
|
||||||
quint64 submitGeneration,
|
quint64 submitGeneration,
|
||||||
quint64 walletGeneration);
|
quint64 walletGeneration);
|
||||||
|
void prepareFreshLpAsync(QJsonObject input,
|
||||||
|
const QString& quoteHash,
|
||||||
|
quint64 submitGeneration,
|
||||||
|
quint64 walletGeneration);
|
||||||
|
void validatePendingFreshLpAsync(QJsonObject input,
|
||||||
|
const QString& quoteHash,
|
||||||
|
quint64 submitGeneration,
|
||||||
|
quint64 walletGeneration);
|
||||||
|
void rememberPendingFreshLp(const QString& accountId);
|
||||||
|
void clearPendingFreshLp();
|
||||||
void finishSubmit(quint64 submitGeneration, QVariantMap result);
|
void finishSubmit(quint64 submitGeneration, QVariantMap result);
|
||||||
bool submitIsCurrent(quint64 submitGeneration,
|
bool submitIsCurrent(quint64 submitGeneration,
|
||||||
quint64 walletGeneration) const;
|
quint64 walletGeneration) const;
|
||||||
@ -81,6 +99,8 @@ private:
|
|||||||
AmmClient* m_client;
|
AmmClient* m_client;
|
||||||
SequencerClient* m_sequencer;
|
SequencerClient* m_sequencer;
|
||||||
QStringList m_walletPublicAccountIds;
|
QStringList m_walletPublicAccountIds;
|
||||||
|
QString m_pendingFreshLpAccountId;
|
||||||
|
FreshLpState m_freshLpState = FreshLpState::None;
|
||||||
bool m_submitInFlight = false;
|
bool m_submitInFlight = false;
|
||||||
quint64 m_walletGeneration = 0;
|
quint64 m_walletGeneration = 0;
|
||||||
quint64 m_submitGeneration = 0;
|
quint64 m_submitGeneration = 0;
|
||||||
|
|||||||
@ -193,9 +193,13 @@ namespace {
|
|||||||
{
|
{
|
||||||
++createdAccounts;
|
++createdAccounts;
|
||||||
WalletAccountCreation creation;
|
WalletAccountCreation creation;
|
||||||
creation.accountId = QStringLiteral("fresh-lp");
|
const QLatin1Char accountDigit(
|
||||||
if (isPublic)
|
"abcdef"[(createdAccounts - 1) % 6]);
|
||||||
|
creation.accountId = QString(64, accountDigit);
|
||||||
|
if (isPublic && !failCreatedPublicRead)
|
||||||
creation.publicAccount = readPublicAccount(creation.accountId);
|
creation.publicAccount = readPublicAccount(creation.accountId);
|
||||||
|
else
|
||||||
|
creation.publicAccount.accountId = creation.accountId;
|
||||||
return creation;
|
return creation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,6 +219,9 @@ namespace {
|
|||||||
WalletAccountRead read;
|
WalletAccountRead read;
|
||||||
read.accountId = accountId;
|
read.accountId = accountId;
|
||||||
read.status = QStringLiteral("ok");
|
read.status = QStringLiteral("ok");
|
||||||
|
read.programOwner = QString(64, QLatin1Char('0'));
|
||||||
|
read.balanceHex = readBalanceHex;
|
||||||
|
read.nonceHex = QString(32, QLatin1Char('0'));
|
||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,6 +229,10 @@ namespace {
|
|||||||
{
|
{
|
||||||
++submissions;
|
++submissions;
|
||||||
submitted = transaction;
|
submitted = transaction;
|
||||||
|
if (submissionFailuresRemaining > 0) {
|
||||||
|
--submissionFailuresRemaining;
|
||||||
|
return { WalletFailure::SubmissionFailed, {} };
|
||||||
|
}
|
||||||
return { WalletFailure::None, transactionHash };
|
return { WalletFailure::None, transactionHash };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,6 +272,9 @@ namespace {
|
|||||||
WalletTransaction submitted;
|
WalletTransaction submitted;
|
||||||
bool deferAccountCreation = false;
|
bool deferAccountCreation = false;
|
||||||
bool deferSubmission = false;
|
bool deferSubmission = false;
|
||||||
|
bool failCreatedPublicRead = false;
|
||||||
|
int submissionFailuresRemaining = 0;
|
||||||
|
QString readBalanceHex = QString(32, QLatin1Char('0'));
|
||||||
AccountCreationCallback pendingAccountCreation;
|
AccountCreationCallback pendingAccountCreation;
|
||||||
SubmissionCallback pendingSubmissionCallback;
|
SubmissionCallback pendingSubmissionCallback;
|
||||||
WalletAccountCreation pendingCreation;
|
WalletAccountCreation pendingCreation;
|
||||||
@ -316,6 +330,16 @@ namespace {
|
|||||||
AmmClientResult plan(const QJsonObject& request) const override
|
AmmClientResult plan(const QJsonObject& request) const override
|
||||||
{
|
{
|
||||||
sawFreshLp = request.contains(QStringLiteral("freshLp"));
|
sawFreshLp = request.contains(QStringLiteral("freshLp"));
|
||||||
|
if (sawFreshLp) {
|
||||||
|
freshLpAccountIds.append(
|
||||||
|
request.value(QStringLiteral("freshLp")).toObject()
|
||||||
|
.value(QStringLiteral("id")).toString());
|
||||||
|
}
|
||||||
|
++planCalls;
|
||||||
|
if (planFailuresRemaining > 0) {
|
||||||
|
--planFailuresRemaining;
|
||||||
|
return { false, {} };
|
||||||
|
}
|
||||||
return success({
|
return success({
|
||||||
{ QStringLiteral("status"), QStringLiteral("ready") },
|
{ QStringLiteral("status"), QStringLiteral("ready") },
|
||||||
{ QStringLiteral("accountIds"), QJsonArray { QStringLiteral("account") } },
|
{ QStringLiteral("accountIds"), QJsonArray { QStringLiteral("account") } },
|
||||||
@ -331,11 +355,21 @@ namespace {
|
|||||||
|
|
||||||
AmmClientResult normalizeAccountRpc(const QJsonObject& request) const override
|
AmmClientResult normalizeAccountRpc(const QJsonObject& request) const override
|
||||||
{
|
{
|
||||||
|
normalizedAccountIds.append(
|
||||||
|
request.value(QStringLiteral("accountId")).toString());
|
||||||
return success({
|
return success({
|
||||||
{ QStringLiteral("id"),
|
{ QStringLiteral("id"),
|
||||||
request.value(QStringLiteral("accountId")).toString() },
|
request.value(QStringLiteral("accountId")).toString() },
|
||||||
{ QStringLiteral("status"), QStringLiteral("ok") },
|
{ QStringLiteral("status"), QStringLiteral("ok") },
|
||||||
{ QStringLiteral("account"), QJsonObject() },
|
{ QStringLiteral("account"), QJsonObject {
|
||||||
|
{ QStringLiteral("program_owner"),
|
||||||
|
QString(64, QLatin1Char('0')) },
|
||||||
|
{ QStringLiteral("balance"),
|
||||||
|
normalizedBalanceHex },
|
||||||
|
{ QStringLiteral("nonce"),
|
||||||
|
QString(32, QLatin1Char('0')) },
|
||||||
|
{ QStringLiteral("data"), QString() },
|
||||||
|
} },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,6 +381,11 @@ namespace {
|
|||||||
QString quoteHash = QStringLiteral("sha256:expected");
|
QString quoteHash = QStringLiteral("sha256:expected");
|
||||||
bool requiresFreshLp = true;
|
bool requiresFreshLp = true;
|
||||||
mutable bool sawFreshLp = false;
|
mutable bool sawFreshLp = false;
|
||||||
|
mutable int planCalls = 0;
|
||||||
|
mutable int planFailuresRemaining = 0;
|
||||||
|
mutable QStringList freshLpAccountIds;
|
||||||
|
mutable QStringList normalizedAccountIds;
|
||||||
|
QString normalizedBalanceHex = QString(32, QLatin1Char('0'));
|
||||||
};
|
};
|
||||||
|
|
||||||
ActiveNetworkSnapshot readyNetwork()
|
ActiveNetworkSnapshot readyNetwork()
|
||||||
@ -442,6 +481,125 @@ int main(int argc, char** argv)
|
|||||||
"runtime should dispatch the unchanged plan once"))
|
"runtime should dispatch the unchanged plan once"))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
|
FakeWallet planRetryWallet;
|
||||||
|
FakeAmmClient planRetryClient;
|
||||||
|
planRetryClient.planFailuresRemaining = 1;
|
||||||
|
NewPositionRuntime planRetryRuntime(&planRetryWallet, &planRetryClient);
|
||||||
|
const QVariantMap failedPlan = planRetryRuntime.submit(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true);
|
||||||
|
const QVariantMap retriedPlan = planRetryRuntime.submit(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true);
|
||||||
|
if (!expect(failedPlan.value(QStringLiteral("code")).toString()
|
||||||
|
== QStringLiteral("backend_error")
|
||||||
|
&& retriedPlan.value(QStringLiteral("status")).toString()
|
||||||
|
== QStringLiteral("submitted"),
|
||||||
|
"plan failure should remain retryable"))
|
||||||
|
return 1;
|
||||||
|
if (!expect(planRetryWallet.createdAccounts == 1
|
||||||
|
&& planRetryClient.freshLpAccountIds.size() == 2
|
||||||
|
&& planRetryClient.freshLpAccountIds.constFirst()
|
||||||
|
== planRetryClient.freshLpAccountIds.constLast(),
|
||||||
|
"plan retry should reuse the pending LP account"))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
FakeWallet submissionRetryWallet;
|
||||||
|
submissionRetryWallet.submissionFailuresRemaining = 1;
|
||||||
|
FakeAmmClient submissionRetryClient;
|
||||||
|
NewPositionRuntime submissionRetryRuntime(
|
||||||
|
&submissionRetryWallet, &submissionRetryClient);
|
||||||
|
const QVariantMap failedSubmission = submissionRetryRuntime.submit(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true);
|
||||||
|
const QVariantMap retriedSubmission = submissionRetryRuntime.submit(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true);
|
||||||
|
if (!expect(failedSubmission.value(QStringLiteral("code")).toString()
|
||||||
|
== QStringLiteral("wallet_submission_failed")
|
||||||
|
&& retriedSubmission.value(QStringLiteral("status")).toString()
|
||||||
|
== QStringLiteral("submitted"),
|
||||||
|
"wallet rejection should remain retryable"))
|
||||||
|
return 1;
|
||||||
|
if (!expect(submissionRetryWallet.createdAccounts == 1
|
||||||
|
&& submissionRetryWallet.submissions == 2
|
||||||
|
&& submissionRetryClient.freshLpAccountIds.size() == 2
|
||||||
|
&& submissionRetryClient.freshLpAccountIds.constFirst()
|
||||||
|
== submissionRetryClient.freshLpAccountIds.constLast(),
|
||||||
|
"wallet retry should reuse the pending LP account"))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
FakeWallet consumedWallet;
|
||||||
|
FakeAmmClient consumedClient;
|
||||||
|
NewPositionRuntime consumedRuntime(&consumedWallet, &consumedClient);
|
||||||
|
const QVariantMap firstConsumed = consumedRuntime.submit(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true);
|
||||||
|
const QVariantMap secondConsumed = consumedRuntime.submit(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true);
|
||||||
|
if (!expect(firstConsumed.value(QStringLiteral("status")).toString()
|
||||||
|
== QStringLiteral("submitted")
|
||||||
|
&& secondConsumed.value(QStringLiteral("status")).toString()
|
||||||
|
== QStringLiteral("submitted")
|
||||||
|
&& consumedWallet.createdAccounts == 2
|
||||||
|
&& consumedClient.freshLpAccountIds.size() == 2
|
||||||
|
&& consumedClient.freshLpAccountIds.constFirst()
|
||||||
|
!= consumedClient.freshLpAccountIds.constLast(),
|
||||||
|
"accepted submission should consume the LP account reservation"))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
FakeWallet changedWallet;
|
||||||
|
FakeAmmClient changedClient;
|
||||||
|
changedClient.planFailuresRemaining = 1;
|
||||||
|
NewPositionRuntime changedRuntime(&changedWallet, &changedClient);
|
||||||
|
const QVariantMap beforeWalletChange = changedRuntime.submit(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true);
|
||||||
|
changedRuntime.clearWalletAccounts();
|
||||||
|
const QVariantMap afterWalletChange = changedRuntime.submit(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true);
|
||||||
|
if (!expect(beforeWalletChange.value(QStringLiteral("code")).toString()
|
||||||
|
== QStringLiteral("backend_error")
|
||||||
|
&& afterWalletChange.value(QStringLiteral("status")).toString()
|
||||||
|
== QStringLiteral("submitted")
|
||||||
|
&& changedWallet.createdAccounts == 2,
|
||||||
|
"wallet change should discard the previous LP reservation"))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
FakeWallet readRetryWallet;
|
||||||
|
readRetryWallet.failCreatedPublicRead = true;
|
||||||
|
FakeAmmClient readRetryClient;
|
||||||
|
NewPositionRuntime readRetryRuntime(&readRetryWallet, &readRetryClient);
|
||||||
|
const QVariantMap failedCreatedRead = readRetryRuntime.submit(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true);
|
||||||
|
readRetryWallet.failCreatedPublicRead = false;
|
||||||
|
const QVariantMap retriedCreatedRead = readRetryRuntime.submit(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true);
|
||||||
|
if (!expect(failedCreatedRead.value(QStringLiteral("code")).toString()
|
||||||
|
== QStringLiteral("account_read_failed")
|
||||||
|
&& retriedCreatedRead.value(QStringLiteral("status")).toString()
|
||||||
|
== QStringLiteral("submitted")
|
||||||
|
&& readRetryWallet.createdAccounts == 1,
|
||||||
|
"created-account read failure should preserve the LP reservation"))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
FakeWallet nonDefaultWallet;
|
||||||
|
FakeAmmClient nonDefaultClient;
|
||||||
|
nonDefaultClient.planFailuresRemaining = 1;
|
||||||
|
NewPositionRuntime nonDefaultRuntime(&nonDefaultWallet, &nonDefaultClient);
|
||||||
|
const QVariantMap reservedAccount = nonDefaultRuntime.submit(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true);
|
||||||
|
nonDefaultWallet.readBalanceHex = QString(31, QLatin1Char('0'))
|
||||||
|
+ QLatin1Char('1');
|
||||||
|
const QVariantMap nonDefaultAccount = nonDefaultRuntime.submit(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true);
|
||||||
|
nonDefaultWallet.readBalanceHex = QString(32, QLatin1Char('0'));
|
||||||
|
const QVariantMap replacementAccount = nonDefaultRuntime.submit(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true);
|
||||||
|
if (!expect(reservedAccount.value(QStringLiteral("code")).toString()
|
||||||
|
== QStringLiteral("backend_error")
|
||||||
|
&& nonDefaultAccount.value(QStringLiteral("code")).toString()
|
||||||
|
== QStringLiteral("submission_status_unknown")
|
||||||
|
&& replacementAccount.value(QStringLiteral("status")).toString()
|
||||||
|
== QStringLiteral("submitted")
|
||||||
|
&& nonDefaultWallet.createdAccounts == 2,
|
||||||
|
"non-default LP reservation should be replaced"))
|
||||||
|
return 1;
|
||||||
|
|
||||||
FakeWallet orderedBytesWallet;
|
FakeWallet orderedBytesWallet;
|
||||||
orderedBytesWallet.transactionHash = QStringLiteral(
|
orderedBytesWallet.transactionHash = QStringLiteral(
|
||||||
"0102030405060708090a0b0c0d0e0f10"
|
"0102030405060708090a0b0c0d0e0f10"
|
||||||
@ -800,6 +958,234 @@ int main(int argc, char** argv)
|
|||||||
"async wallet completion should finish exactly once"))
|
"async wallet completion should finish exactly once"))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
|
FakeWallet cancelledCreationWallet;
|
||||||
|
cancelledCreationWallet.deferAccountCreation = true;
|
||||||
|
FakeAmmClient cancelledCreationClient;
|
||||||
|
NewPositionRuntime cancelledCreationRuntime(
|
||||||
|
&cancelledCreationWallet, &cancelledCreationClient, &sequencer);
|
||||||
|
QVariantMap cancelledCreationResult;
|
||||||
|
cancelledCreationRuntime.submitAsync(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true,
|
||||||
|
[&](QVariantMap result) {
|
||||||
|
cancelledCreationResult = std::move(result);
|
||||||
|
});
|
||||||
|
if (!expect(waitForCondition(
|
||||||
|
[&]() { return cancelledCreationWallet.createdAccounts == 1; }),
|
||||||
|
"cancelled creation should reach the wallet"))
|
||||||
|
return 1;
|
||||||
|
WalletAccount creationDiscoveredAccount;
|
||||||
|
creationDiscoveredAccount.address = QString(64, QLatin1Char('8'));
|
||||||
|
creationDiscoveredAccount.isPublic = true;
|
||||||
|
cancelledCreationRuntime.setWalletAccounts({ creationDiscoveredAccount });
|
||||||
|
QVariantMap blockedCreationRetry;
|
||||||
|
cancelledCreationRuntime.submitAsync(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true,
|
||||||
|
[&](QVariantMap result) {
|
||||||
|
blockedCreationRetry = std::move(result);
|
||||||
|
});
|
||||||
|
if (!expect(cancelledCreationResult.value(QStringLiteral("code")).toString()
|
||||||
|
== QStringLiteral("wallet_unavailable")
|
||||||
|
&& blockedCreationRetry.value(QStringLiteral("code")).toString()
|
||||||
|
== QStringLiteral("submit_in_progress"),
|
||||||
|
"account refresh should wait for active creation to settle"))
|
||||||
|
return 1;
|
||||||
|
cancelledCreationWallet.finishAccountCreation();
|
||||||
|
const int creationReadsBeforeRetry = refreshClient.normalizedAccountIds.count(
|
||||||
|
QString(64, QLatin1Char('a')));
|
||||||
|
QVariantMap creationRetryResult;
|
||||||
|
cancelledCreationRuntime.submitAsync(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true,
|
||||||
|
[&](QVariantMap result) {
|
||||||
|
creationRetryResult = std::move(result);
|
||||||
|
});
|
||||||
|
if (!expect(waitForCondition([&]() {
|
||||||
|
return !creationRetryResult.isEmpty();
|
||||||
|
}),
|
||||||
|
"creation retry should complete"))
|
||||||
|
return 1;
|
||||||
|
if (!expect(creationRetryResult.value(QStringLiteral("status")).toString()
|
||||||
|
== QStringLiteral("submitted")
|
||||||
|
&& cancelledCreationWallet.createdAccounts == 1
|
||||||
|
&& cancelledCreationClient.freshLpAccountIds.size() == 1
|
||||||
|
&& refreshClient.normalizedAccountIds.count(
|
||||||
|
QString(64, QLatin1Char('a')))
|
||||||
|
> creationReadsBeforeRetry,
|
||||||
|
"late creation should be reserved for the retry"))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
FakeWallet failedReadWallet;
|
||||||
|
failedReadWallet.failCreatedPublicRead = true;
|
||||||
|
FakeAmmClient failedReadClient;
|
||||||
|
NewPositionRuntime failedReadRuntime(
|
||||||
|
&failedReadWallet, &failedReadClient, &sequencer);
|
||||||
|
const int failedReadNormalizations = refreshClient.normalizedAccountIds.count(
|
||||||
|
QString(64, QLatin1Char('a')));
|
||||||
|
QVariantMap failedReadResult;
|
||||||
|
failedReadRuntime.submitAsync(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true,
|
||||||
|
[&](QVariantMap result) {
|
||||||
|
failedReadResult = std::move(result);
|
||||||
|
});
|
||||||
|
if (!expect(waitForCondition([&]() { return !failedReadResult.isEmpty(); }),
|
||||||
|
"sequencer fallback should complete the failed wallet read"))
|
||||||
|
return 1;
|
||||||
|
if (!expect(failedReadResult.value(QStringLiteral("status")).toString()
|
||||||
|
== QStringLiteral("submitted")
|
||||||
|
&& failedReadWallet.createdAccounts == 1
|
||||||
|
&& failedReadWallet.submissions == 1
|
||||||
|
&& refreshClient.normalizedAccountIds.count(
|
||||||
|
QString(64, QLatin1Char('a')))
|
||||||
|
> failedReadNormalizations,
|
||||||
|
"sequencer should validate a newly created LP account"))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
FakeWallet cancelledSubmissionWallet;
|
||||||
|
cancelledSubmissionWallet.deferSubmission = true;
|
||||||
|
cancelledSubmissionWallet.submissionFailuresRemaining = 1;
|
||||||
|
FakeAmmClient cancelledSubmissionClient;
|
||||||
|
NewPositionRuntime cancelledSubmissionRuntime(
|
||||||
|
&cancelledSubmissionWallet, &cancelledSubmissionClient, &sequencer);
|
||||||
|
QVariantMap cancelledSubmissionResult;
|
||||||
|
cancelledSubmissionRuntime.submitAsync(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true,
|
||||||
|
[&](QVariantMap result) {
|
||||||
|
cancelledSubmissionResult = std::move(result);
|
||||||
|
});
|
||||||
|
if (!expect(waitForCondition(
|
||||||
|
[&]() { return cancelledSubmissionWallet.submissions == 1; }),
|
||||||
|
"cancelled submission should reach the wallet"))
|
||||||
|
return 1;
|
||||||
|
WalletAccount pendingFreshLp;
|
||||||
|
pendingFreshLp.address = QString(64, QLatin1Char('a'));
|
||||||
|
pendingFreshLp.isPublic = true;
|
||||||
|
WalletAccount discoveredAccount;
|
||||||
|
discoveredAccount.address = QString(64, QLatin1Char('9'));
|
||||||
|
discoveredAccount.isPublic = true;
|
||||||
|
cancelledSubmissionRuntime.setWalletAccounts(
|
||||||
|
{ pendingFreshLp, discoveredAccount });
|
||||||
|
QVariantMap blockedSubmissionRetry;
|
||||||
|
cancelledSubmissionRuntime.submitAsync(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true,
|
||||||
|
[&](QVariantMap result) {
|
||||||
|
blockedSubmissionRetry = std::move(result);
|
||||||
|
});
|
||||||
|
if (!expect(cancelledSubmissionResult.value(QStringLiteral("code")).toString()
|
||||||
|
== QStringLiteral("wallet_unavailable")
|
||||||
|
&& blockedSubmissionRetry.value(QStringLiteral("code")).toString()
|
||||||
|
== QStringLiteral("submit_in_progress"),
|
||||||
|
"retry should wait for cancelled wallet submission to settle"))
|
||||||
|
return 1;
|
||||||
|
cancelledSubmissionWallet.finishSubmission();
|
||||||
|
cancelledSubmissionWallet.deferSubmission = false;
|
||||||
|
QVariantMap settledSubmissionRetry;
|
||||||
|
cancelledSubmissionRuntime.submitAsync(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true,
|
||||||
|
[&](QVariantMap result) {
|
||||||
|
settledSubmissionRetry = std::move(result);
|
||||||
|
});
|
||||||
|
if (!expect(waitForCondition([&]() {
|
||||||
|
return !settledSubmissionRetry.isEmpty();
|
||||||
|
}),
|
||||||
|
"settled submission retry should complete"))
|
||||||
|
return 1;
|
||||||
|
if (!expect(settledSubmissionRetry.value(QStringLiteral("status")).toString()
|
||||||
|
== QStringLiteral("submitted")
|
||||||
|
&& cancelledSubmissionWallet.createdAccounts == 1
|
||||||
|
&& cancelledSubmissionWallet.submissions == 2
|
||||||
|
&& cancelledSubmissionClient.freshLpAccountIds.size() == 2
|
||||||
|
&& cancelledSubmissionClient.freshLpAccountIds.constFirst()
|
||||||
|
== cancelledSubmissionClient.freshLpAccountIds.constLast(),
|
||||||
|
"rejected late submission should restore the LP reservation"))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
FakeWallet acceptedLateWallet;
|
||||||
|
acceptedLateWallet.deferSubmission = true;
|
||||||
|
FakeAmmClient acceptedLateClient;
|
||||||
|
NewPositionRuntime acceptedLateRuntime(
|
||||||
|
&acceptedLateWallet, &acceptedLateClient, &sequencer);
|
||||||
|
QVariantMap acceptedLateResult;
|
||||||
|
acceptedLateRuntime.submitAsync(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true,
|
||||||
|
[&](QVariantMap result) {
|
||||||
|
acceptedLateResult = std::move(result);
|
||||||
|
});
|
||||||
|
if (!expect(waitForCondition(
|
||||||
|
[&]() { return acceptedLateWallet.submissions == 1; }),
|
||||||
|
"late accepted submission should reach the wallet"))
|
||||||
|
return 1;
|
||||||
|
acceptedLateRuntime.cancelSubmit();
|
||||||
|
QVariantMap acceptedLateBlockedRetry;
|
||||||
|
acceptedLateRuntime.submitAsync(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true,
|
||||||
|
[&](QVariantMap result) {
|
||||||
|
acceptedLateBlockedRetry = std::move(result);
|
||||||
|
});
|
||||||
|
if (!expect(acceptedLateResult.value(QStringLiteral("code")).toString()
|
||||||
|
== QStringLiteral("wallet_unavailable")
|
||||||
|
&& acceptedLateBlockedRetry.value(QStringLiteral("code")).toString()
|
||||||
|
== QStringLiteral("submit_in_progress"),
|
||||||
|
"accepted late submission should block retry until settled"))
|
||||||
|
return 1;
|
||||||
|
acceptedLateWallet.finishSubmission();
|
||||||
|
acceptedLateWallet.deferSubmission = false;
|
||||||
|
QVariantMap acceptedLateRetry;
|
||||||
|
acceptedLateRuntime.submitAsync(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true,
|
||||||
|
[&](QVariantMap result) {
|
||||||
|
acceptedLateRetry = std::move(result);
|
||||||
|
});
|
||||||
|
if (!expect(waitForCondition([&]() { return !acceptedLateRetry.isEmpty(); }),
|
||||||
|
"accepted late retry should complete"))
|
||||||
|
return 1;
|
||||||
|
if (!expect(acceptedLateRetry.value(QStringLiteral("status")).toString()
|
||||||
|
== QStringLiteral("submitted")
|
||||||
|
&& acceptedLateWallet.createdAccounts == 2
|
||||||
|
&& acceptedLateWallet.submissions == 2
|
||||||
|
&& acceptedLateClient.freshLpAccountIds.size() == 2
|
||||||
|
&& acceptedLateClient.freshLpAccountIds.constFirst()
|
||||||
|
!= acceptedLateClient.freshLpAccountIds.constLast(),
|
||||||
|
"accepted late submission should consume its LP reservation"))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
FakeWallet changedDuringSubmissionWallet;
|
||||||
|
changedDuringSubmissionWallet.deferSubmission = true;
|
||||||
|
FakeAmmClient changedDuringSubmissionClient;
|
||||||
|
NewPositionRuntime changedDuringSubmissionRuntime(
|
||||||
|
&changedDuringSubmissionWallet, &changedDuringSubmissionClient, &sequencer);
|
||||||
|
QVariantMap changedDuringSubmissionResult;
|
||||||
|
changedDuringSubmissionRuntime.submitAsync(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true,
|
||||||
|
[&](QVariantMap result) {
|
||||||
|
changedDuringSubmissionResult = std::move(result);
|
||||||
|
});
|
||||||
|
if (!expect(waitForCondition([&]() {
|
||||||
|
return changedDuringSubmissionWallet.submissions == 1;
|
||||||
|
}),
|
||||||
|
"wallet-change submission should reach the wallet"))
|
||||||
|
return 1;
|
||||||
|
changedDuringSubmissionRuntime.clearWalletAccounts();
|
||||||
|
if (!expect(changedDuringSubmissionResult.value(
|
||||||
|
QStringLiteral("code")).toString()
|
||||||
|
== QStringLiteral("wallet_unavailable"),
|
||||||
|
"wallet change should cancel deferred submission"))
|
||||||
|
return 1;
|
||||||
|
changedDuringSubmissionWallet.finishSubmission();
|
||||||
|
changedDuringSubmissionWallet.deferSubmission = false;
|
||||||
|
QVariantMap changedWalletRetry;
|
||||||
|
changedDuringSubmissionRuntime.submitAsync(
|
||||||
|
request, QStringLiteral("sha256:expected"), readyNetwork(), true,
|
||||||
|
[&](QVariantMap result) {
|
||||||
|
changedWalletRetry = std::move(result);
|
||||||
|
});
|
||||||
|
if (!expect(waitForCondition([&]() { return !changedWalletRetry.isEmpty(); }),
|
||||||
|
"new wallet submission should complete"))
|
||||||
|
return 1;
|
||||||
|
if (!expect(changedWalletRetry.value(QStringLiteral("status")).toString()
|
||||||
|
== QStringLiteral("submitted")
|
||||||
|
&& changedDuringSubmissionWallet.createdAccounts == 2,
|
||||||
|
"old wallet callback should not mutate new reservation state"))
|
||||||
|
return 1;
|
||||||
|
|
||||||
FakeWallet cancelledMutationWallet;
|
FakeWallet cancelledMutationWallet;
|
||||||
cancelledMutationWallet.deferAccountCreation = true;
|
cancelledMutationWallet.deferAccountCreation = true;
|
||||||
FakeAmmClient cancelledMutationClient;
|
FakeAmmClient cancelledMutationClient;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user