Skip to content

Commit 8d33f49

Browse files
committed
Merge #965: poller: unspend expired before new spend
cc1de1d poller: unspend coins before spending new (jp1ac4) 1e7653e tests: add function to wait while condition holds (jp1ac4) Pull request description: This change ensures that the spend txid of a coin is updated directly to another value in case a conflicting spend is detected. The previous order caused the spend txid to first be cleared, which would misleadingly make the coin appear as confirmed rather than spending. I've added a new utils function for the functional tests that is a slight generalisation of `wait_for` with an additional condition that must always be met while waiting. `wait_for` now calls this new function with the condition being one that is always true. ACKs for top commit: darosior: ACK cc1de1d Tree-SHA512: e3f00804a63b0e94bc1b2cbee03cac63dd6e2555ca6d301589b356b2baf8e0cf27362e1dda44018d1d8282e300b187079fcf61f5d2754263b9e8b08cd34be06e
2 parents 87712ab + cc1de1d commit 8d33f49

File tree

3 files changed

+40
-9
lines changed

3 files changed

+40
-9
lines changed

src/bitcoin/poller/looper.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,8 @@ fn updates(
245245
db_conn.new_unspent_coins(&updated_coins.received);
246246
db_conn.remove_coins(&updated_coins.expired);
247247
db_conn.confirm_coins(&updated_coins.confirmed);
248-
db_conn.spend_coins(&updated_coins.spending);
249248
db_conn.unspend_coins(&updated_coins.expired_spending);
249+
db_conn.spend_coins(&updated_coins.spending);
250250
db_conn.confirm_spend(&updated_coins.spent);
251251
if latest_tip != current_tip {
252252
db_conn.update_tip(&latest_tip);

tests/test_chain.py

+20-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from fixtures import *
44
from test_framework.utils import (
55
wait_for,
6+
wait_for_while_condition_holds,
67
get_txid,
78
spend_coins,
89
RpcError,
@@ -407,7 +408,13 @@ def is_spent_by(lianad, outpoint, txid):
407408
return False
408409
return coin["spend_info"]["txid"] == txid.hex()
409410

410-
wait_for(lambda: is_spent_by(lianad, spent_coin["outpoint"], txid_b))
411+
wait_for_while_condition_holds(
412+
lambda: is_spent_by(lianad, spent_coin["outpoint"], txid_b),
413+
lambda: lianad.rpc.listcoins([], [spent_coin["outpoint"]])["coins"][0][
414+
"spend_info"
415+
]
416+
is not None, # The spend txid changes directly from txid_a to txid_b
417+
)
411418

412419

413420
def test_spend_replacement(lianad, bitcoind):
@@ -454,11 +461,15 @@ def test_spend_replacement(lianad, bitcoind):
454461
# newly marked as spending, the second one's spend_txid should be updated and
455462
# the first one's spend txid should be dropped.
456463
second_txid = sign_and_broadcast_psbt(lianad, second_psbt)
457-
wait_for(
464+
wait_for_while_condition_holds(
458465
lambda: all(
459466
c["spend_info"] is not None and c["spend_info"]["txid"] == second_txid
460467
for c in lianad.rpc.listcoins([], second_outpoints)["coins"]
461-
)
468+
),
469+
lambda: lianad.rpc.listcoins([], [coins[1]["outpoint"]])["coins"][0][
470+
"spend_info"
471+
]
472+
is not None, # The spend txid of coin from first spend is updated directly
462473
)
463474
wait_for(
464475
lambda: lianad.rpc.listcoins([], [first_outpoints[0]])["coins"][0]["spend_info"]
@@ -467,11 +478,15 @@ def test_spend_replacement(lianad, bitcoind):
467478

468479
# Now RBF the second transaction with a send-to-self, just because.
469480
third_txid = sign_and_broadcast_psbt(lianad, third_psbt)
470-
wait_for(
481+
wait_for_while_condition_holds(
471482
lambda: all(
472483
c["spend_info"] is not None and c["spend_info"]["txid"] == third_txid
473484
for c in lianad.rpc.listcoins([], second_outpoints)["coins"]
474-
)
485+
),
486+
lambda: all(
487+
c["spend_info"] is not None
488+
for c in lianad.rpc.listcoins([], second_outpoints)["coins"]
489+
), # The spend txid of all coins are updated directly
475490
)
476491
assert (
477492
lianad.rpc.listcoins([], [first_outpoints[0]])["coins"][0]["spend_info"] is None

tests/test_framework/utils.py

+19-3
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,33 @@ def wait_for(success, timeout=TIMEOUT, debug_fn=None):
3535
debug_fn is logged at each call to success, it can be useful for debugging
3636
when tests fail.
3737
"""
38+
wait_for_while_condition_holds(success, lambda: True, timeout, debug_fn)
39+
40+
41+
def wait_for_while_condition_holds(success, condition, timeout=TIMEOUT, debug_fn=None):
42+
"""
43+
Run success() either until it returns True, or until the timeout is reached,
44+
as long as condition() holds.
45+
debug_fn is logged at each call to success, it can be useful for debugging
46+
when tests fail.
47+
"""
3848
start_time = time.time()
3949
interval = 0.25
40-
while not success() and time.time() < start_time + timeout:
50+
while True:
51+
if time.time() >= start_time + timeout:
52+
raise ValueError("Error waiting for {}", success)
53+
if not condition():
54+
raise ValueError(
55+
"Condition {} not met while waiting for {}", condition, success
56+
)
57+
if success():
58+
return
4159
if debug_fn is not None:
4260
logging.info(debug_fn())
4361
time.sleep(interval)
4462
interval *= 2
4563
if interval > 5:
4664
interval = 5
47-
if time.time() > start_time + timeout:
48-
raise ValueError("Error waiting for {}", success)
4965

5066

5167
def get_txid(hex_tx):

0 commit comments

Comments
 (0)