Merge #6131: feat: make a support of Qt app to show Platform transfer Tx

21f174aff1 feat: improve query categorisation in Qt App (Konstantin Akimov)
c863473286 test: add spending asset unlock tx in functional tests (Konstantin Akimov)
1fb67ece0e 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
This commit is contained in:
pasta 2024-08-07 08:29:35 +07:00
parent 80ed27914e
commit 8e70262db4
No known key found for this signature in database
GPG Key ID: E2F3D7916E722D38
11 changed files with 75 additions and 15 deletions

View File

@ -408,6 +408,7 @@ struct WalletTx
int64_t time; int64_t time;
std::map<std::string, std::string> value_map; std::map<std::string, std::string> value_map;
bool is_coinbase; bool is_coinbase;
bool is_platform_transfer{false};
bool is_denominate; bool is_denominate;
}; };

View File

@ -264,6 +264,11 @@ public:
return nVersion >= SPECIAL_VERSION; return nVersion >= SPECIAL_VERSION;
} }
bool IsPlatformTransfer() const noexcept
{
return IsSpecialTxVersion() && nType == TRANSACTION_ASSET_UNLOCK;
}
bool HasExtraPayloadField() const noexcept bool HasExtraPayloadField() const noexcept
{ {
return IsSpecialTxVersion() && nType != TRANSACTION_NORMAL; return IsSpecialTxVersion() && nType != TRANSACTION_NORMAL;

View File

@ -93,6 +93,10 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
{ {
strHTML += "<b>" + tr("Source") + ":</b> " + tr("Generated") + "<br>"; strHTML += "<b>" + tr("Source") + ":</b> " + tr("Generated") + "<br>";
} }
else if (wtx.is_platform_transfer)
{
strHTML += "<b>" + tr("Source") + ":</b> " + tr("Platform Transfer") + "<br>";
}
else if (wtx.value_map.count("from") && !wtx.value_map["from"].empty()) else if (wtx.value_map.count("from") && !wtx.value_map["from"].empty())
{ {
// Online transaction // Online transaction

View File

@ -39,7 +39,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(interfaces::Wal
auto node = interfaces::MakeNode(); auto node = interfaces::MakeNode();
auto& coinJoinOptions = node->coinJoinOptions(); auto& coinJoinOptions = node->coinJoinOptions();
if (nNet > 0 || wtx.is_coinbase) if (nNet > 0 || wtx.is_coinbase || wtx.is_platform_transfer)
{ {
// //
// Credit // Credit
@ -74,6 +74,11 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(interfaces::Wal
// Generated // Generated
sub.type = TransactionRecord::Generated; sub.type = TransactionRecord::Generated;
} }
if (wtx.is_platform_transfer)
{
// Withdrawal from platform
sub.type = TransactionRecord::PlatformTransfer;
}
parts.append(sub); parts.append(sub);
} }

View File

@ -96,7 +96,8 @@ public:
CoinJoinCollateralPayment, CoinJoinCollateralPayment,
CoinJoinMakeCollaterals, CoinJoinMakeCollaterals,
CoinJoinCreateDenominations, CoinJoinCreateDenominations,
CoinJoinSend CoinJoinSend,
PlatformTransfer,
}; };
/** Number of confirmation recommended for accepting a transaction */ /** Number of confirmation recommended for accepting a transaction */

View File

@ -431,6 +431,8 @@ QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
return tr("Payment to yourself"); return tr("Payment to yourself");
case TransactionRecord::Generated: case TransactionRecord::Generated:
return tr("Mined"); return tr("Mined");
case TransactionRecord::PlatformTransfer:
return tr("Platform Transfer");
case TransactionRecord::CoinJoinMixing: case TransactionRecord::CoinJoinMixing:
return tr("%1 Mixing").arg(QString::fromStdString(gCoinJoinName)); return tr("%1 Mixing").arg(QString::fromStdString(gCoinJoinName));
@ -443,9 +445,10 @@ QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
case TransactionRecord::CoinJoinSend: case TransactionRecord::CoinJoinSend:
return tr("%1 Send").arg(QString::fromStdString(gCoinJoinName)); return tr("%1 Send").arg(QString::fromStdString(gCoinJoinName));
default: case TransactionRecord::Other:
return QString(); 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 QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const
@ -473,14 +476,20 @@ QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, b
case TransactionRecord::SendToAddress: case TransactionRecord::SendToAddress:
case TransactionRecord::Generated: case TransactionRecord::Generated:
case TransactionRecord::CoinJoinSend: case TransactionRecord::CoinJoinSend:
case TransactionRecord::PlatformTransfer:
return formatAddressLabel(wtx->strAddress, wtx->label, tooltip) + watchAddress; return formatAddressLabel(wtx->strAddress, wtx->label, tooltip) + watchAddress;
case TransactionRecord::SendToOther: case TransactionRecord::SendToOther:
return QString::fromStdString(wtx->strAddress) + watchAddress; return QString::fromStdString(wtx->strAddress) + watchAddress;
case TransactionRecord::SendToSelf: case TransactionRecord::SendToSelf:
return formatAddressLabel(wtx->strAddress, wtx->label, tooltip) + watchAddress; return formatAddressLabel(wtx->strAddress, wtx->label, tooltip) + watchAddress;
default: case TransactionRecord::CoinJoinMixing:
return tr("(n/a)") + watchAddress; 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 QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
@ -491,6 +500,7 @@ QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
case TransactionRecord::RecvWithAddress: case TransactionRecord::RecvWithAddress:
case TransactionRecord::SendToAddress: case TransactionRecord::SendToAddress:
case TransactionRecord::Generated: case TransactionRecord::Generated:
case TransactionRecord::PlatformTransfer:
case TransactionRecord::CoinJoinSend: case TransactionRecord::CoinJoinSend:
case TransactionRecord::RecvWithCoinJoin: case TransactionRecord::RecvWithCoinJoin:
{ {
@ -504,9 +514,11 @@ QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
case TransactionRecord::CoinJoinMakeCollaterals: case TransactionRecord::CoinJoinMakeCollaterals:
case TransactionRecord::CoinJoinCollateralPayment: case TransactionRecord::CoinJoinCollateralPayment:
return GUIUtil::getThemedQColor(GUIUtil::ThemedColor::BAREADDRESS); return GUIUtil::getThemedQColor(GUIUtil::ThemedColor::BAREADDRESS);
default: case TransactionRecord::SendToOther:
case TransactionRecord::RecvFromOther:
case TransactionRecord::Other:
break; break;
} } // no default case, so the compiler can warn about missing cases
return GUIUtil::getThemedQColor(GUIUtil::ThemedColor::DEFAULT); return GUIUtil::getThemedQColor(GUIUtil::ThemedColor::DEFAULT);
} }
@ -530,6 +542,7 @@ QVariant TransactionTableModel::amountColor(const TransactionRecord *rec) const
case TransactionRecord::RecvWithCoinJoin: case TransactionRecord::RecvWithCoinJoin:
case TransactionRecord::RecvWithAddress: case TransactionRecord::RecvWithAddress:
case TransactionRecord::RecvFromOther: case TransactionRecord::RecvFromOther:
case TransactionRecord::PlatformTransfer:
return GUIUtil::getThemedQColor(GUIUtil::ThemedColor::GREEN); return GUIUtil::getThemedQColor(GUIUtil::ThemedColor::GREEN);
case TransactionRecord::CoinJoinSend: case TransactionRecord::CoinJoinSend:
case TransactionRecord::SendToAddress: case TransactionRecord::SendToAddress:

View File

@ -90,6 +90,7 @@ TransactionView::TransactionView(QWidget* parent) :
typeWidget->addItem(tr("%1 Collateral Payment").arg(strCoinJoinName), TransactionFilterProxy::TYPE(TransactionRecord::CoinJoinCollateralPayment)); typeWidget->addItem(tr("%1 Collateral Payment").arg(strCoinJoinName), TransactionFilterProxy::TYPE(TransactionRecord::CoinJoinCollateralPayment));
typeWidget->addItem(tr("To yourself"), TransactionFilterProxy::TYPE(TransactionRecord::SendToSelf)); typeWidget->addItem(tr("To yourself"), TransactionFilterProxy::TYPE(TransactionRecord::SendToSelf));
typeWidget->addItem(tr("Mined"), TransactionFilterProxy::TYPE(TransactionRecord::Generated)); 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->addItem(tr("Other"), TransactionFilterProxy::TYPE(TransactionRecord::Other));
typeWidget->setCurrentIndex(settings.value("transactionType").toInt()); typeWidget->setCurrentIndex(settings.value("transactionType").toInt());

View File

@ -81,6 +81,7 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx)
result.time = wtx.GetTxTime(); result.time = wtx.GetTxTime();
result.value_map = wtx.mapValue; result.value_map = wtx.mapValue;
result.is_coinbase = wtx.IsCoinBase(); 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 // 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. // 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 result.is_denominate = wtx.tx->vin.size() == wtx.tx->vout.size() && // Number of inputs is same as number of outputs

View File

@ -167,6 +167,8 @@ static void WalletTxToJSON(interfaces::Chain& chain, const CWalletTx& wtx, UniVa
entry.pushKV("chainlock", chainlock); entry.pushKV("chainlock", chainlock);
if (wtx.IsCoinBase()) if (wtx.IsCoinBase())
entry.pushKV("generated", true); entry.pushKV("generated", true);
if (wtx.IsPlatformTransfer())
entry.pushKV("platform-transfer", true);
if (confirms > 0) if (confirms > 0)
{ {
entry.pushKV("blockhash", wtx.m_confirm.hashBlock.GetHex()); entry.pushKV("blockhash", wtx.m_confirm.hashBlock.GetHex());
@ -1419,6 +1421,10 @@ static void ListTransactions(const CWallet* const pwallet, const CWalletTx& wtx,
else else
entry.pushKV("category", "generate"); entry.pushKV("category", "generate");
} }
else if (wtx.IsPlatformTransfer())
{
entry.pushKV("category", "platform-transfer");
}
else else
{ {
entry.pushKV("category", "receive"); entry.pushKV("category", "receive");
@ -1483,7 +1489,8 @@ static RPCHelpMan listtransactions()
"\"receive\" Non-coinbase transactions received.\n" "\"receive\" Non-coinbase transactions received.\n"
"\"generate\" Coinbase transactions received with more than 100 confirmations.\n" "\"generate\" Coinbase transactions received with more than 100 confirmations.\n"
"\"immature\" Coinbase transactions received with 100 or fewer 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" {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"}, "for all other categories"},
{RPCResult::Type::STR, "label", "A comment for the address/transaction, if any"}, {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" "\"receive\" Non-coinbase transactions received.\n"
"\"generate\" Coinbase transactions received with more than 100 confirmations.\n" "\"generate\" Coinbase transactions received with more than 100 confirmations.\n"
"\"immature\" Coinbase transactions received with 100 or fewer 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" {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"}, "for all other categories"},
{RPCResult::Type::NUM, "vout", "the vout value"}, {RPCResult::Type::NUM, "vout", "the vout value"},
@ -1740,7 +1748,8 @@ static RPCHelpMan gettransaction()
"\"receive\" Non-coinbase transactions received.\n" "\"receive\" Non-coinbase transactions received.\n"
"\"generate\" Coinbase transactions received with more than 100 confirmations.\n" "\"generate\" Coinbase transactions received with more than 100 confirmations.\n"
"\"immature\" Coinbase transactions received with 100 or fewer 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_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT},
{RPCResult::Type::STR, "label", "A comment for the address/transaction, if any"}, {RPCResult::Type::STR, "label", "A comment for the address/transaction, if any"},
{RPCResult::Type::NUM, "vout", "the vout value"}, {RPCResult::Type::NUM, "vout", "the vout value"},

View File

@ -591,6 +591,7 @@ public:
void setConfirmed() { m_confirm.status = CWalletTx::CONFIRMED; } void setConfirmed() { m_confirm.status = CWalletTx::CONFIRMED; }
const uint256& GetHash() const { return tx->GetHash(); } const uint256& GetHash() const { return tx->GetHash(); }
bool IsCoinBase() const { return tx->IsCoinBase(); } bool IsCoinBase() const { return tx->IsCoinBase(); }
bool IsPlatformTransfer() const { return tx->IsPlatformTransfer(); }
bool IsImmatureCoinBase() const; bool IsImmatureCoinBase() const;
// Disable copying of CWalletTx objects to prevent bugs where instances get // Disable copying of CWalletTx objects to prevent bugs where instances get

View File

@ -41,6 +41,7 @@ from test_framework.util import (
get_bip9_details, get_bip9_details,
hex_str_to_bytes, hex_str_to_bytes,
) )
from test_framework.wallet_util import bytes_to_wif
llmq_type_test = 106 # LLMQType::LLMQ_TEST_PLATFORM llmq_type_test = 106 # LLMQType::LLMQ_TEST_PLATFORM
tiny_amount = int(Decimal("0.0007") * COIN) tiny_amount = int(Decimal("0.0007") * COIN)
@ -257,6 +258,8 @@ class AssetLocksTest(DashTestFramework):
key = ECKey() key = ECKey()
key.generate() key.generate()
privkey = bytes_to_wif(key.get_bytes())
node_wallet.importprivkey(privkey)
pubkey = key.get_pubkey().get_bytes() pubkey = key.get_pubkey().get_bytes()
self.test_asset_locks(node_wallet, node, pubkey) 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))}}) 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) txid_in_block = self.send_tx(asset_unlock_tx_full)
expected_balance = (Decimal(self.get_credit_pool_balance()) - Decimal(tiny_amount))
node.generate(1) node.generate(1)
self.sync_all() 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()) block = node.getblock(node.getbestblockhash())
assert txid_in_block in block['tx'] assert txid_in_block in block['tx']
self.validate_credit_pool_balance(0) 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.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) self.mine_quorum(llmq_type_name="llmq_test_platform", llmq_type=106)
total = self.get_credit_pool_balance() total = self.get_credit_pool_balance()