Skip to content

Commit 918cc22

Browse files
achow101vijaydasmp
authored andcommitted
Merge bitcoin#24067: wallet: Actually treat (un)confirmed txs as (un)confirmed
fac8165 Remove unused checkFinalTx (MarcoFalke) fa272ea wallet: Avoid dropping confirmed coins (MarcoFalke) 888841e interfaces: Remove unused is_final (MarcoFalke) dddd05e qt: Treat unconfirmed txs as unconfirmed (MarcoFalke) Pull request description: The wallet has several issues: ## Unconfirmed txs in the GUI The GUI clumsily attempts to guess if unconfirmed txs are locked until a future time. This is currently based on the locktime only, not nSequence, thus wrong. Fix this by removing the clumsy code and treat all unconfirmed txs as unconfirmed. The GUI already prints whether a tx is in the mempool, in which case the user knows that the tx wasn't locked until a future time. If the tx is not in the mempool, it might be better to report the exact reject reason from the mempool instead of using incorrect heuristics. ## Confirmed txs in the wallet The wallet drops coins that it incorrectly assumes to be locked until a future time, even if they are already confirmed in the chain. This is because the wallet is using the wrong time (adjusted network time) instead of MTP, due to the `-1` default argument of `CheckFinalTx`. The issues are fixed in separate commits and there is even a test. ACKs for top commit: achow101: ACK fac8165 prayank23: reACK bitcoin@fac8165 glozow: code review ACK fac8165, I understand now how this fixes both issues. Tree-SHA512: 210afb855f4c6d903fee49eba6b1a9735d699cf0168b669eabb38178e53b3a522258b7cc669f52489c6cd3e38bf358afde12eef3ba2e2f2ffaeb06b8f652ccd0
1 parent 1549daa commit 918cc22

12 files changed

+52
-48
lines changed

src/interfaces/chain.h

-3
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,6 @@ class Chain
133133
//! or one of its ancestors.
134134
virtual std::optional<int> findLocatorFork(const CBlockLocator& locator) = 0;
135135

136-
//! Check if transaction will be final given chain height current time.
137-
virtual bool checkFinalTx(const CTransaction& tx) = 0;
138-
139136
//! Check if transaction is locked by InstantSendManager
140137
virtual bool isInstantSendLockedTx(const uint256& hash) = 0;
141138

src/interfaces/wallet.h

-1
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,6 @@ struct WalletTxStatus
433433
int depth_in_main_chain;
434434
unsigned int time_received;
435435
uint32_t lock_time;
436-
bool is_final;
437436
bool is_trusted;
438437
bool is_abandoned;
439438
bool is_coinbase;

src/node/interfaces.cpp

-5
Original file line numberDiff line numberDiff line change
@@ -771,11 +771,6 @@ class ChainImpl : public Chain
771771
}
772772
return std::nullopt;
773773
}
774-
bool checkFinalTx(const CTransaction& tx) override
775-
{
776-
LOCK(cs_main);
777-
return CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), tx);
778-
}
779774
bool isInstantSendLockedTx(const uint256& hash) override
780775
{
781776
if (m_node.llmq_ctx == nullptr || m_node.llmq_ctx->isman == nullptr) return false;

src/qt/transactiondesc.cpp

-9
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
#include <key_io.h>
1919
#include <interfaces/node.h>
2020
#include <interfaces/wallet.h>
21-
#include <script/script.h>
2221
#include <util/system.h>
2322
#include <validation.h>
2423
#include <wallet/ismine.h>
@@ -30,14 +29,6 @@
3029

3130
QString TransactionDesc::FormatTxStatus(const interfaces::WalletTx& wtx, const interfaces::WalletTxStatus& status, bool inMempool, int numBlocks)
3231
{
33-
if (!status.is_final)
34-
{
35-
if (wtx.tx->nLockTime < LOCKTIME_THRESHOLD)
36-
return tr("Open for %n more block(s)", "", wtx.tx->nLockTime - numBlocks);
37-
else
38-
return tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx.tx->nLockTime));
39-
}
40-
else
4132
{
4233
int nDepth = status.depth_in_main_chain;
4334
if (nDepth < 0) return tr("conflicted");

src/qt/transactionrecord.cpp

+1-14
Original file line numberDiff line numberDiff line change
@@ -278,21 +278,8 @@ void TransactionRecord::updateStatus(const interfaces::WalletTxStatus& wtx, cons
278278
status.lockedByChainLocks = wtx.is_chainlocked;
279279
status.lockedByInstantSend = wtx.is_islocked;
280280

281-
const bool up_to_date = ((int64_t)QDateTime::currentMSecsSinceEpoch() / 1000 - block_time < MAX_BLOCK_TIME_GAP);
282-
if (up_to_date && !wtx.is_final) {
283-
if (wtx.lock_time < LOCKTIME_THRESHOLD) {
284-
status.status = TransactionStatus::OpenUntilBlock;
285-
status.open_for = wtx.lock_time - numBlocks;
286-
}
287-
else
288-
{
289-
status.status = TransactionStatus::OpenUntilDate;
290-
status.open_for = wtx.lock_time;
291-
}
292-
}
293281
// For generated transactions, determine maturity
294-
else if(type == TransactionRecord::Generated)
295-
{
282+
if (type == TransactionRecord::Generated) {
296283
if (wtx.blocks_to_maturity > 0)
297284
{
298285
status.status = TransactionStatus::Immature;

src/qt/transactionrecord.h

-2
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ class TransactionStatus
3333
enum Status {
3434
Confirmed, /**< Have 6 or more confirmations (normal tx) or fully mature (mined tx) **/
3535
/// Normal (sent/received) transactions
36-
OpenUntilDate, /**< Transaction not yet final, waiting for date */
37-
OpenUntilBlock, /**< Transaction not yet final, waiting for block */
3836
Unconfirmed, /**< Not yet mined into a block **/
3937
Confirming, /**< Confirmed, but waiting for the recommended number of confirmations **/
4038
Conflicted, /**< Conflicts with other transaction or mempool **/

src/qt/transactiontablemodel.cpp

-9
Original file line numberDiff line numberDiff line change
@@ -350,12 +350,6 @@ QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) cons
350350

351351
switch(wtx->status.status)
352352
{
353-
case TransactionStatus::OpenUntilBlock:
354-
status = tr("Open for %n more block(s)","",wtx->status.open_for);
355-
break;
356-
case TransactionStatus::OpenUntilDate:
357-
status = tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx->status.open_for));
358-
break;
359353
case TransactionStatus::Unconfirmed:
360354
status = tr("Unconfirmed");
361355
break;
@@ -564,9 +558,6 @@ QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx)
564558
{
565559
switch(wtx->status.status)
566560
{
567-
case TransactionStatus::OpenUntilBlock:
568-
case TransactionStatus::OpenUntilDate:
569-
return GUIUtil::getThemedQColor(GUIUtil::ThemedColor::TX_STATUS_OPENUNTILDATE);
570561
case TransactionStatus::Unconfirmed:
571562
return GUIUtil::getIcon("transaction_0");
572563
case TransactionStatus::Abandoned:

src/wallet/interfaces.cpp

-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ WalletTxStatus MakeWalletTxStatus(const CWallet& wallet, const CWalletTx& wtx)
100100
result.depth_in_main_chain = wtx.GetDepthInMainChain();
101101
result.time_received = wtx.nTimeReceived;
102102
result.lock_time = wtx.tx->nLockTime;
103-
result.is_final = wallet.chain().checkFinalTx(*wtx.tx);
104103
result.is_trusted = wtx.IsTrusted();
105104
result.is_abandoned = wtx.isAbandoned();
106105
result.is_coinbase = wtx.IsCoinBase();

src/wallet/receive.cpp

-1
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,6 @@ bool CWallet::IsTrusted(const CWalletTx& wtx, std::set<uint256>& trusted_parents
277277
{
278278
AssertLockHeld(cs_wallet);
279279
// Quick answer in most cases
280-
if (!chain().checkFinalTx(*wtx.tx)) return false;
281280
int nDepth = wtx.GetDepthInMainChain();
282281
if (nDepth >= 1) return true;
283282
if (nDepth < 0) return false;

src/wallet/spend.cpp

-3
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,6 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, const CCoinControl* c
8484

8585
const uint256& wtxid = wtx.GetHash();
8686

87-
if (!chain().checkFinalTx(*wtx.tx))
88-
continue;
89-
9087
if (wtx.IsImmatureCoinBase())
9188
continue;
9289

test/functional/test_runner.py

+1
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@
370370
'feature_coinstatsindex.py',
371371
'wallet_orphanedreward.py',
372372
'p2p_node_network_limited.py --v1transport',
373+
'wallet_timelock.py',
373374
'p2p_node_network_limited.py --v2transport',
374375
'p2p_permissions.py',
375376
'feature_blocksdir.py',

test/functional/wallet_timelock.py

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2022 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
6+
from test_framework.test_framework import BitcoinTestFramework
7+
from test_framework.util import assert_equal
8+
9+
10+
class WalletLocktimeTest(BitcoinTestFramework):
11+
def set_test_params(self):
12+
self.num_nodes = 1
13+
14+
def skip_test_if_missing_module(self):
15+
self.skip_if_no_wallet()
16+
17+
def run_test(self):
18+
node = self.nodes[0]
19+
20+
mtp_tip = node.getblockheader(node.getbestblockhash())["mediantime"]
21+
22+
self.log.info("Get new address with label")
23+
label = "timelock⌛🔓"
24+
address = node.getnewaddress(label=label)
25+
26+
self.log.info("Send to new address with locktime")
27+
node.send(
28+
outputs={address: 5},
29+
options={"locktime": mtp_tip - 1},
30+
)
31+
self.generate(node, 1)
32+
33+
self.log.info("Check that clock can not change finality of confirmed txs")
34+
amount_before_ad = node.getreceivedbyaddress(address)
35+
amount_before_lb = node.getreceivedbylabel(label)
36+
list_before_ad = node.listreceivedbyaddress(address_filter=address)
37+
list_before_lb = node.listreceivedbylabel(include_empty=False)
38+
balance_before = node.getbalances()["mine"]["trusted"]
39+
coin_before = node.listunspent(maxconf=1)
40+
node.setmocktime(mtp_tip - 1)
41+
assert_equal(node.getreceivedbyaddress(address), amount_before_ad)
42+
assert_equal(node.getreceivedbylabel(label), amount_before_lb)
43+
assert_equal(node.listreceivedbyaddress(address_filter=address), list_before_ad)
44+
assert_equal(node.listreceivedbylabel(include_empty=False), list_before_lb)
45+
assert_equal(node.getbalances()["mine"]["trusted"], balance_before)
46+
assert_equal(node.listunspent(maxconf=1), coin_before)
47+
48+
49+
if __name__ == "__main__":
50+
WalletLocktimeTest().main()

0 commit comments

Comments
 (0)