Merge #965: poller: unspend expired before new spend
cc1de1d6d6710f1426a957806661e7f3461a7cb5 poller: unspend coins before spending new (jp1ac4)
1e7653e08a3778446ff677bb147df68b734a31fd 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 cc1de1d6d6710f1426a957806661e7f3461a7cb5
Tree-SHA512: e3f00804a63b0e94bc1b2cbee03cac63dd6e2555ca6d301589b356b2baf8e0cf27362e1dda44018d1d8282e300b187079fcf61f5d2754263b9e8b08cd34be06e
This commit is contained in:
commit
8d33f49935
@ -245,8 +245,8 @@ fn updates(
|
|||||||
db_conn.new_unspent_coins(&updated_coins.received);
|
db_conn.new_unspent_coins(&updated_coins.received);
|
||||||
db_conn.remove_coins(&updated_coins.expired);
|
db_conn.remove_coins(&updated_coins.expired);
|
||||||
db_conn.confirm_coins(&updated_coins.confirmed);
|
db_conn.confirm_coins(&updated_coins.confirmed);
|
||||||
db_conn.spend_coins(&updated_coins.spending);
|
|
||||||
db_conn.unspend_coins(&updated_coins.expired_spending);
|
db_conn.unspend_coins(&updated_coins.expired_spending);
|
||||||
|
db_conn.spend_coins(&updated_coins.spending);
|
||||||
db_conn.confirm_spend(&updated_coins.spent);
|
db_conn.confirm_spend(&updated_coins.spent);
|
||||||
if latest_tip != current_tip {
|
if latest_tip != current_tip {
|
||||||
db_conn.update_tip(&latest_tip);
|
db_conn.update_tip(&latest_tip);
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import copy
|
|||||||
from fixtures import *
|
from fixtures import *
|
||||||
from test_framework.utils import (
|
from test_framework.utils import (
|
||||||
wait_for,
|
wait_for,
|
||||||
|
wait_for_while_condition_holds,
|
||||||
get_txid,
|
get_txid,
|
||||||
spend_coins,
|
spend_coins,
|
||||||
RpcError,
|
RpcError,
|
||||||
@ -407,7 +408,13 @@ def test_conflicting_unconfirmed_spend_txs(lianad, bitcoind):
|
|||||||
return False
|
return False
|
||||||
return coin["spend_info"]["txid"] == txid.hex()
|
return coin["spend_info"]["txid"] == txid.hex()
|
||||||
|
|
||||||
wait_for(lambda: is_spent_by(lianad, spent_coin["outpoint"], txid_b))
|
wait_for_while_condition_holds(
|
||||||
|
lambda: is_spent_by(lianad, spent_coin["outpoint"], txid_b),
|
||||||
|
lambda: lianad.rpc.listcoins([], [spent_coin["outpoint"]])["coins"][0][
|
||||||
|
"spend_info"
|
||||||
|
]
|
||||||
|
is not None, # The spend txid changes directly from txid_a to txid_b
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_spend_replacement(lianad, bitcoind):
|
def test_spend_replacement(lianad, bitcoind):
|
||||||
@ -454,11 +461,15 @@ def test_spend_replacement(lianad, bitcoind):
|
|||||||
# newly marked as spending, the second one's spend_txid should be updated and
|
# newly marked as spending, the second one's spend_txid should be updated and
|
||||||
# the first one's spend txid should be dropped.
|
# the first one's spend txid should be dropped.
|
||||||
second_txid = sign_and_broadcast_psbt(lianad, second_psbt)
|
second_txid = sign_and_broadcast_psbt(lianad, second_psbt)
|
||||||
wait_for(
|
wait_for_while_condition_holds(
|
||||||
lambda: all(
|
lambda: all(
|
||||||
c["spend_info"] is not None and c["spend_info"]["txid"] == second_txid
|
c["spend_info"] is not None and c["spend_info"]["txid"] == second_txid
|
||||||
for c in lianad.rpc.listcoins([], second_outpoints)["coins"]
|
for c in lianad.rpc.listcoins([], second_outpoints)["coins"]
|
||||||
)
|
),
|
||||||
|
lambda: lianad.rpc.listcoins([], [coins[1]["outpoint"]])["coins"][0][
|
||||||
|
"spend_info"
|
||||||
|
]
|
||||||
|
is not None, # The spend txid of coin from first spend is updated directly
|
||||||
)
|
)
|
||||||
wait_for(
|
wait_for(
|
||||||
lambda: lianad.rpc.listcoins([], [first_outpoints[0]])["coins"][0]["spend_info"]
|
lambda: lianad.rpc.listcoins([], [first_outpoints[0]])["coins"][0]["spend_info"]
|
||||||
@ -467,11 +478,15 @@ def test_spend_replacement(lianad, bitcoind):
|
|||||||
|
|
||||||
# Now RBF the second transaction with a send-to-self, just because.
|
# Now RBF the second transaction with a send-to-self, just because.
|
||||||
third_txid = sign_and_broadcast_psbt(lianad, third_psbt)
|
third_txid = sign_and_broadcast_psbt(lianad, third_psbt)
|
||||||
wait_for(
|
wait_for_while_condition_holds(
|
||||||
lambda: all(
|
lambda: all(
|
||||||
c["spend_info"] is not None and c["spend_info"]["txid"] == third_txid
|
c["spend_info"] is not None and c["spend_info"]["txid"] == third_txid
|
||||||
for c in lianad.rpc.listcoins([], second_outpoints)["coins"]
|
for c in lianad.rpc.listcoins([], second_outpoints)["coins"]
|
||||||
)
|
),
|
||||||
|
lambda: all(
|
||||||
|
c["spend_info"] is not None
|
||||||
|
for c in lianad.rpc.listcoins([], second_outpoints)["coins"]
|
||||||
|
), # The spend txid of all coins are updated directly
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
lianad.rpc.listcoins([], [first_outpoints[0]])["coins"][0]["spend_info"] is None
|
lianad.rpc.listcoins([], [first_outpoints[0]])["coins"][0]["spend_info"] is None
|
||||||
|
|||||||
@ -35,17 +35,33 @@ def wait_for(success, timeout=TIMEOUT, debug_fn=None):
|
|||||||
debug_fn is logged at each call to success, it can be useful for debugging
|
debug_fn is logged at each call to success, it can be useful for debugging
|
||||||
when tests fail.
|
when tests fail.
|
||||||
"""
|
"""
|
||||||
|
wait_for_while_condition_holds(success, lambda: True, timeout, debug_fn)
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_while_condition_holds(success, condition, timeout=TIMEOUT, debug_fn=None):
|
||||||
|
"""
|
||||||
|
Run success() either until it returns True, or until the timeout is reached,
|
||||||
|
as long as condition() holds.
|
||||||
|
debug_fn is logged at each call to success, it can be useful for debugging
|
||||||
|
when tests fail.
|
||||||
|
"""
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
interval = 0.25
|
interval = 0.25
|
||||||
while not success() and time.time() < start_time + timeout:
|
while True:
|
||||||
|
if time.time() >= start_time + timeout:
|
||||||
|
raise ValueError("Error waiting for {}", success)
|
||||||
|
if not condition():
|
||||||
|
raise ValueError(
|
||||||
|
"Condition {} not met while waiting for {}", condition, success
|
||||||
|
)
|
||||||
|
if success():
|
||||||
|
return
|
||||||
if debug_fn is not None:
|
if debug_fn is not None:
|
||||||
logging.info(debug_fn())
|
logging.info(debug_fn())
|
||||||
time.sleep(interval)
|
time.sleep(interval)
|
||||||
interval *= 2
|
interval *= 2
|
||||||
if interval > 5:
|
if interval > 5:
|
||||||
interval = 5
|
interval = 5
|
||||||
if time.time() > start_time + timeout:
|
|
||||||
raise ValueError("Error waiting for {}", success)
|
|
||||||
|
|
||||||
|
|
||||||
def get_txid(hex_tx):
|
def get_txid(hex_tx):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user