From 1fb67ece0e99baf668687538d9d22217bdc70e4f Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 17 Jul 2024 12:41:51 +0700 Subject: [PATCH 1/3] feat: make a support of Qt app to show Platform Transfer transaction as a new type of transaction --- src/interfaces/wallet.h | 1 + src/primitives/transaction.h | 5 +++++ src/qt/transactiondesc.cpp | 4 ++++ src/qt/transactionrecord.cpp | 7 ++++++- src/qt/transactionrecord.h | 3 ++- src/qt/transactiontablemodel.cpp | 1 + src/qt/transactionview.cpp | 1 + src/wallet/interfaces.cpp | 1 + src/wallet/rpcwallet.cpp | 15 ++++++++++++--- src/wallet/wallet.h | 1 + 10 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index 32fb6b28e89e0..cea629fead7ff 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -411,6 +411,7 @@ struct WalletTx int64_t time; std::map value_map; bool is_coinbase; + bool is_platform_transfer{false}; bool is_denominate; }; diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index dc87c07152e98..ba56ef366df8e 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -264,6 +264,11 @@ class CTransaction return nVersion >= SPECIAL_VERSION; } + bool IsPlatformTransfer() const noexcept + { + return IsSpecialTxVersion() && nType == TRANSACTION_ASSET_UNLOCK; + } + bool HasExtraPayloadField() const noexcept { return IsSpecialTxVersion() && nType != TRANSACTION_NORMAL; diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index 139db97a784fa..0e89ffe556485 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -93,6 +93,10 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall { strHTML += "" + tr("Source") + ": " + tr("Generated") + "
"; } + else if (wtx.is_platform_transfer) + { + strHTML += "" + tr("Source") + ": " + tr("Platform Transfer") + "
"; + } else if (wtx.value_map.count("from") && !wtx.value_map["from"].empty()) { // Online transaction diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 155c368c92d25..949a4ad19a8dd 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -39,7 +39,7 @@ QList TransactionRecord::decomposeTransaction(interfaces::Wal auto node = interfaces::MakeNode(); auto& coinJoinOptions = node->coinJoinOptions(); - if (nNet > 0 || wtx.is_coinbase) + if (nNet > 0 || wtx.is_coinbase || wtx.is_platform_transfer) { // // Credit @@ -74,6 +74,11 @@ QList TransactionRecord::decomposeTransaction(interfaces::Wal // Generated sub.type = TransactionRecord::Generated; } + if (wtx.is_platform_transfer) + { + // Withdrawal from platform + sub.type = TransactionRecord::PlatformTransfer; + } parts.append(sub); } diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h index 113ff35f21ee5..92a6086d65a8f 100644 --- a/src/qt/transactionrecord.h +++ b/src/qt/transactionrecord.h @@ -96,7 +96,8 @@ class TransactionRecord CoinJoinCollateralPayment, CoinJoinMakeCollaterals, CoinJoinCreateDenominations, - CoinJoinSend + CoinJoinSend, + PlatformTransfer, }; /** Number of confirmation recommended for accepting a transaction */ diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index c08c7db2f92ef..22be8c2de148e 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -530,6 +530,7 @@ QVariant TransactionTableModel::amountColor(const TransactionRecord *rec) const case TransactionRecord::RecvWithCoinJoin: case TransactionRecord::RecvWithAddress: case TransactionRecord::RecvFromOther: + case TransactionRecord::PlatformTransfer: return GUIUtil::getThemedQColor(GUIUtil::ThemedColor::GREEN); case TransactionRecord::CoinJoinSend: case TransactionRecord::SendToAddress: diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index b51361a64683e..6dd5d1a66498a 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -90,6 +90,7 @@ TransactionView::TransactionView(QWidget* parent) : typeWidget->addItem(tr("%1 Collateral Payment").arg(strCoinJoinName), TransactionFilterProxy::TYPE(TransactionRecord::CoinJoinCollateralPayment)); typeWidget->addItem(tr("To yourself"), TransactionFilterProxy::TYPE(TransactionRecord::SendToSelf)); typeWidget->addItem(tr("Mined"), TransactionFilterProxy::TYPE(TransactionRecord::Generated)); + typeWidget->addItem(tr("Platform Transfer"), TransactionFilterProxy::TYPE(TransactionRecord::PlatformTransfer)); typeWidget->addItem(tr("Other"), TransactionFilterProxy::TYPE(TransactionRecord::Other)); typeWidget->setCurrentIndex(settings.value("transactionType").toInt()); diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 50f446863ea2b..5174b0b44cb22 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -81,6 +81,7 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) result.time = wtx.GetTxTime(); result.value_map = wtx.mapValue; result.is_coinbase = wtx.IsCoinBase(); + result.is_platform_transfer = wtx.IsPlatformTransfer(); // The determination of is_denominate is based on simplified checks here because in this part of the code // we only want to know about mixing transactions belonging to this specific wallet. result.is_denominate = wtx.tx->vin.size() == wtx.tx->vout.size() && // Number of inputs is same as number of outputs diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 71d2c2c15cbfe..bde761482ae9d 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -167,6 +167,8 @@ static void WalletTxToJSON(interfaces::Chain& chain, const CWalletTx& wtx, UniVa entry.pushKV("chainlock", chainlock); if (wtx.IsCoinBase()) entry.pushKV("generated", true); + if (wtx.IsPlatformTransfer()) + entry.pushKV("platform-transfer", true); if (confirms > 0) { entry.pushKV("blockhash", wtx.m_confirm.hashBlock.GetHex()); @@ -1403,6 +1405,10 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM else entry.pushKV("category", "generate"); } + else if (wtx.IsPlatformTransfer()) + { + entry.pushKV("category", "platform-transfer"); + } else { entry.pushKV("category", "receive"); @@ -1467,7 +1473,8 @@ static RPCHelpMan listtransactions() "\"receive\" Non-coinbase transactions received.\n" "\"generate\" Coinbase transactions received with more than 100 confirmations.\n" "\"immature\" Coinbase transactions received with 100 or fewer confirmations.\n" - "\"orphan\" Orphaned coinbase transactions received.\n"}, + "\"orphan\" Orphaned coinbase transactions received.\n" + "\"platform-transfer\" Platform Transfer transactions received.\n"}, {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n" "for all other categories"}, {RPCResult::Type::STR, "label", "A comment for the address/transaction, if any"}, @@ -1582,7 +1589,8 @@ static RPCHelpMan listsinceblock() "\"receive\" Non-coinbase transactions received.\n" "\"generate\" Coinbase transactions received with more than 100 confirmations.\n" "\"immature\" Coinbase transactions received with 100 or fewer confirmations.\n" - "\"orphan\" Orphaned coinbase transactions received.\n"}, + "\"orphan\" Orphaned coinbase transactions received.\n" + "\"platform-transfer\" Platform Transfer transactions received.\n"}, {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n" "for all other categories"}, {RPCResult::Type::NUM, "vout", "the vout value"}, @@ -1723,7 +1731,8 @@ static RPCHelpMan gettransaction() "\"receive\" Non-coinbase transactions received.\n" "\"generate\" Coinbase transactions received with more than 100 confirmations.\n" "\"immature\" Coinbase transactions received with 100 or fewer confirmations.\n" - "\"orphan\" Orphaned coinbase transactions received.\n"}, + "\"orphan\" Orphaned coinbase transactions received.\n" + "\"platform-transfer\" Platform Transfer transactions received.\n"}, {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT}, {RPCResult::Type::STR, "label", "A comment for the address/transaction, if any"}, {RPCResult::Type::NUM, "vout", "the vout value"}, diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 35d00e4bae2c1..f8a0e71cd68bd 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -592,6 +592,7 @@ class CWalletTx void setConfirmed() { m_confirm.status = CWalletTx::CONFIRMED; } const uint256& GetHash() const { return tx->GetHash(); } bool IsCoinBase() const { return tx->IsCoinBase(); } + bool IsPlatformTransfer() const { return tx->IsPlatformTransfer(); } bool IsImmatureCoinBase() const; // Disable copying of CWalletTx objects to prevent bugs where instances get From c863473286e2a34bff6abf2fb58611ad239c65ab Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 19 Jul 2024 17:19:01 +0700 Subject: [PATCH 2/3] test: add spending asset unlock tx in functional tests --- test/functional/feature_asset_locks.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/test/functional/feature_asset_locks.py b/test/functional/feature_asset_locks.py index c4a1b030bedef..5cec8fee4daa5 100755 --- a/test/functional/feature_asset_locks.py +++ b/test/functional/feature_asset_locks.py @@ -44,6 +44,7 @@ get_bip9_details, hex_str_to_bytes, ) +from test_framework.wallet_util import bytes_to_wif llmq_type_test = 106 # LLMQType::LLMQ_TEST_PLATFORM tiny_amount = int(Decimal("0.0007") * COIN) @@ -260,6 +261,8 @@ def run_test(self): key = ECKey() key.generate() + privkey = bytes_to_wif(key.get_bytes()) + node_wallet.importprivkey(privkey) pubkey = key.get_pubkey().get_bytes() self.test_asset_locks(node_wallet, node, pubkey) @@ -477,15 +480,31 @@ def test_withdrawal_limits(self, node_wallet, node, pubkey): self.check_mempool_result(tx=asset_unlock_tx_full, result_expected={'allowed': True, 'fees': {'base': Decimal(str(tiny_amount / COIN))}}) txid_in_block = self.send_tx(asset_unlock_tx_full) + expected_balance = (Decimal(self.get_credit_pool_balance()) - Decimal(tiny_amount)) node.generate(1) self.sync_all() - self.log.info("Check txid_in_block was mined...") + self.log.info("Check txid_in_block was mined") block = node.getblock(node.getbestblockhash()) assert txid_in_block in block['tx'] self.validate_credit_pool_balance(0) + self.log.info(f"Check status of withdrawal and try to spend it") + withdrawal_status = node_wallet.gettransaction(txid_in_block) + assert_equal(withdrawal_status['amount'] * COIN, expected_balance) + assert_equal(withdrawal_status['details'][0]['category'], 'platform-transfer') + + spend_withdrawal_hex = node_wallet.createrawtransaction([{'txid': txid_in_block, 'vout' : 0}], { node_wallet.getnewaddress() : (expected_balance - Decimal(tiny_amount)) / COIN}) + spend_withdrawal_hex = node_wallet.signrawtransactionwithwallet(spend_withdrawal_hex)['hex'] + spend_withdrawal = tx_from_hex(spend_withdrawal_hex) + self.check_mempool_result(tx=spend_withdrawal, result_expected={'allowed': True, 'fees': {'base': Decimal(str(tiny_amount / COIN))}}) + spend_txid_in_block = self.send_tx(spend_withdrawal) + + node.generate(1) + block = node.getblock(node.getbestblockhash()) + assert spend_txid_in_block in block['tx'] + self.log.info("Fast forward to the next day to reset all current unlock limits...") - self.slowly_generate_batch(blocks_in_one_day + 1) + self.slowly_generate_batch(blocks_in_one_day) self.mine_quorum(llmq_type_name="llmq_test_platform", llmq_type=106) total = self.get_credit_pool_balance() From 21f174aff107f9aec9b758a00bde67b96ad3cd55 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 5 Aug 2024 17:15:17 +0700 Subject: [PATCH 3/3] feat: improve query categorisation in Qt App --- src/qt/transactiontablemodel.cpp | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 22be8c2de148e..5ba76de95752c 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -431,6 +431,8 @@ QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const return tr("Payment to yourself"); case TransactionRecord::Generated: return tr("Mined"); + case TransactionRecord::PlatformTransfer: + return tr("Platform Transfer"); case TransactionRecord::CoinJoinMixing: return tr("%1 Mixing").arg(QString::fromStdString(gCoinJoinName)); @@ -443,9 +445,10 @@ QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const case TransactionRecord::CoinJoinSend: return tr("%1 Send").arg(QString::fromStdString(gCoinJoinName)); - default: - return QString(); - } + case TransactionRecord::Other: + break; // use fail-over here + } // no default case, so the compiler can warn about missing cases + return QString(); } QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const @@ -473,14 +476,20 @@ QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, b case TransactionRecord::SendToAddress: case TransactionRecord::Generated: case TransactionRecord::CoinJoinSend: + case TransactionRecord::PlatformTransfer: return formatAddressLabel(wtx->strAddress, wtx->label, tooltip) + watchAddress; case TransactionRecord::SendToOther: return QString::fromStdString(wtx->strAddress) + watchAddress; case TransactionRecord::SendToSelf: return formatAddressLabel(wtx->strAddress, wtx->label, tooltip) + watchAddress; - default: - return tr("(n/a)") + watchAddress; - } + case TransactionRecord::CoinJoinMixing: + case TransactionRecord::CoinJoinCollateralPayment: + case TransactionRecord::CoinJoinMakeCollaterals: + case TransactionRecord::CoinJoinCreateDenominations: + case TransactionRecord::Other: + break; // use fail-over here + } // no default case, so the compiler can warn about missing cases + return tr("(n/a)") + watchAddress; } QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const @@ -491,6 +500,7 @@ QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const case TransactionRecord::RecvWithAddress: case TransactionRecord::SendToAddress: case TransactionRecord::Generated: + case TransactionRecord::PlatformTransfer: case TransactionRecord::CoinJoinSend: case TransactionRecord::RecvWithCoinJoin: { @@ -504,9 +514,11 @@ QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const case TransactionRecord::CoinJoinMakeCollaterals: case TransactionRecord::CoinJoinCollateralPayment: return GUIUtil::getThemedQColor(GUIUtil::ThemedColor::BAREADDRESS); - default: + case TransactionRecord::SendToOther: + case TransactionRecord::RecvFromOther: + case TransactionRecord::Other: break; - } + } // no default case, so the compiler can warn about missing cases return GUIUtil::getThemedQColor(GUIUtil::ThemedColor::DEFAULT); }