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;
std::map<std::string, std::string> value_map;
bool is_coinbase;
bool is_platform_transfer{false};
bool is_denominate;
};

View File

@ -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;

View File

@ -93,6 +93,10 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
{
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())
{
// Online transaction

View File

@ -39,7 +39,7 @@ QList<TransactionRecord> 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> TransactionRecord::decomposeTransaction(interfaces::Wal
// Generated
sub.type = TransactionRecord::Generated;
}
if (wtx.is_platform_transfer)
{
// Withdrawal from platform
sub.type = TransactionRecord::PlatformTransfer;
}
parts.append(sub);
}

View File

@ -96,7 +96,8 @@ public:
CoinJoinCollateralPayment,
CoinJoinMakeCollaterals,
CoinJoinCreateDenominations,
CoinJoinSend
CoinJoinSend,
PlatformTransfer,
};
/** 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");
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:

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("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());

View File

@ -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

View File

@ -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"},

View File

@ -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

View File

@ -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()