From 8e70262db42bc0f583802dd490dcc1f477ff9a02 Mon Sep 17 00:00:00 2001 From: pasta Date: Wed, 7 Aug 2024 08:29:35 +0700 Subject: [PATCH] Merge #6131: feat: make a support of Qt app to show Platform transfer Tx 21f174aff107f9aec9b758a00bde67b96ad3cd55 feat: improve query categorisation in Qt App (Konstantin Akimov) c863473286e2a34bff6abf2fb58611ad239c65ab test: add spending asset unlock tx in functional tests (Konstantin Akimov) 1fb67ece0e99baf668687538d9d22217bdc70e4f feat: make a support of Qt app to show Platform Transfer transaction as a new type of transaction (Konstantin Akimov) Pull request description: ## Issue being fixed or feature implemented Transfers from platform have incorrectly shown amount in Dash Core wallet app. They also shown in Qt app as self-send that is not completely true. ## What was done? Added new type of transaction to Qt App, added a filter for its type, fixed calculation of output for tx records. As well added a new type of transaction `platform-transfer` in rpc output of `gettransaction` RPC ## How Has This Been Tested? Make a Platform Transfer transaction on RegTest and check it in Dash Core ![image](https://github.com/user-attachments/assets/16c83f09-724f-4b8b-99c8-9bb0df1428da) Helper to see it: export dpath=/tmp/dash_func_test_PATHPATH/ ; src/qt/dash-qt -regtest -conf=$dpath/node0/dash.conf -datadir=$dpath/node0/ -debug=0 -debuglogfile=/dev/stdout ## Breaking Changes There's new type of transaction "platform-transfer" in rpc output of `gettransaction`. **This PR DOES NOT change any consensus rules.** Breaking changes that makes withdrawal transaction immature is moved to https://github.com/dashpay/dash/pull/6128 ## Checklist: - [x] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [x] I have assigned this pull request to a milestone Top commit has no ACKs. Tree-SHA512: ec2a54a910f121ad30ff8e94cf17080b5b3c651872e9bc3de9ec0924ca7f7a0e526b74b05cde26aaf860e3809e67f66142112319a69c216527e5bcb1b8a2b8f6 --- 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 | 29 +++++++++++++++++++------- src/qt/transactionview.cpp | 1 + src/wallet/interfaces.cpp | 1 + src/wallet/rpcwallet.cpp | 15 ++++++++++--- src/wallet/wallet.h | 1 + test/functional/feature_asset_locks.py | 23 ++++++++++++++++++-- 11 files changed, 75 insertions(+), 15 deletions(-) diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index 3b068f16be..e2365c4c89 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -408,6 +408,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 dc87c07152..ba56ef366d 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -264,6 +264,11 @@ public: 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 139db97a78..0e89ffe556 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 155c368c92..949a4ad19a 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 113ff35f21..92a6086d65 100644 --- a/src/qt/transactionrecord.h +++ b/src/qt/transactionrecord.h @@ -96,7 +96,8 @@ public: 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 c08c7db2f9..5ba76de957 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); } @@ -530,6 +542,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 573d91d64f..a711082dbb 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 9f1ebb49fd..04afc44e24 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 2107cec953..984e701da1 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()); @@ -1419,6 +1421,10 @@ static void ListTransactions(const CWallet* const pwallet, const CWalletTx& wtx, else entry.pushKV("category", "generate"); } + else if (wtx.IsPlatformTransfer()) + { + entry.pushKV("category", "platform-transfer"); + } else { entry.pushKV("category", "receive"); @@ -1483,7 +1489,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"}, @@ -1599,7 +1606,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"}, @@ -1740,7 +1748,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 0ac9666989..b29a010dbe 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -591,6 +591,7 @@ public: 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 diff --git a/test/functional/feature_asset_locks.py b/test/functional/feature_asset_locks.py index 5c27945965..d9d535a06a 100755 --- a/test/functional/feature_asset_locks.py +++ b/test/functional/feature_asset_locks.py @@ -41,6 +41,7 @@ from test_framework.util import ( 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) @@ -257,6 +258,8 @@ class AssetLocksTest(DashTestFramework): 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) @@ -478,15 +481,31 @@ class AssetLocksTest(DashTestFramework): 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()