mirror of
https://github.com/aljazceru/lightning.git
synced 2026-01-06 15:44:21 +01:00
tests/test_closing.py: Test new onchaind earth-scorching behavior.
This commit is contained in:
committed by
Rusty Russell
parent
06b44f00a3
commit
5bac63c6a3
@@ -996,6 +996,216 @@ def test_penalty_htlc_tx_timeout(node_factory, bitcoind, chainparams):
|
||||
assert account_balance(l2, channel_id) == 0
|
||||
|
||||
|
||||
@unittest.skipIf(not DEVELOPER, "uses dev_sign_last_tx")
|
||||
def test_penalty_rbf_normal(node_factory, bitcoind, executor, chainparams):
|
||||
'''
|
||||
Test that penalty transactions are RBFed.
|
||||
'''
|
||||
to_self_delay = 10
|
||||
# l1 is the thief, which causes our honest upstanding lightningd
|
||||
# code to break, so l1 can fail.
|
||||
# Initially, disconnect before the HTLC can be resolved.
|
||||
l1 = node_factory.get_node(disconnect=['=WIRE_COMMITMENT_SIGNED-nocommit'],
|
||||
may_fail=True, allow_broken_log=True)
|
||||
l2 = node_factory.get_node(disconnect=['=WIRE_COMMITMENT_SIGNED-nocommit'],
|
||||
options={'watchtime-blocks': to_self_delay})
|
||||
|
||||
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
||||
l1.fund_channel(l2, 10**7)
|
||||
|
||||
# Trigger an HTLC being added.
|
||||
t = executor.submit(l1.pay, l2, 1000000 * 1000)
|
||||
|
||||
# Make sure the channel is still alive.
|
||||
assert len(l1.getactivechannels()) == 2
|
||||
assert len(l2.getactivechannels()) == 2
|
||||
|
||||
# Wait for the disconnection.
|
||||
l1.daemon.wait_for_log('=WIRE_COMMITMENT_SIGNED-nocommit')
|
||||
l2.daemon.wait_for_log('=WIRE_COMMITMENT_SIGNED-nocommit')
|
||||
# Make sure l1 gets the new HTLC.
|
||||
l1.daemon.wait_for_log('got commitsig')
|
||||
|
||||
# l1 prepares a theft commitment transaction
|
||||
theft_tx = l1.rpc.dev_sign_last_tx(l2.info['id'])['tx']
|
||||
|
||||
# Now continue processing until fulfilment.
|
||||
l1.rpc.dev_reenable_commit(l2.info['id'])
|
||||
l2.rpc.dev_reenable_commit(l1.info['id'])
|
||||
|
||||
# Wait for the fulfilment.
|
||||
l1.daemon.wait_for_log('peer_in WIRE_UPDATE_FULFILL_HTLC')
|
||||
l1.daemon.wait_for_log('peer_out WIRE_REVOKE_AND_ACK')
|
||||
l2.daemon.wait_for_log('peer_out WIRE_UPDATE_FULFILL_HTLC')
|
||||
l1.daemon.wait_for_log('peer_in WIRE_REVOKE_AND_ACK')
|
||||
|
||||
# Now payment should complete.
|
||||
t.result(timeout=10)
|
||||
|
||||
# l1 goes offline and bribes the miners to censor transactions from l2.
|
||||
l1.rpc.stop()
|
||||
|
||||
def censoring_sendrawtx(r):
|
||||
return {'id': r['id'], 'result': {}}
|
||||
|
||||
l2.daemon.rpcproxy.mock_rpc('sendrawtransaction', censoring_sendrawtx)
|
||||
|
||||
# l1 now performs the theft attack!
|
||||
bitcoind.rpc.sendrawtransaction(theft_tx)
|
||||
bitcoind.generate_block(1)
|
||||
|
||||
# l2 notices.
|
||||
l2.daemon.wait_for_log(' to ONCHAIN')
|
||||
|
||||
def get_rbf_tx(self, depth, name, resolve):
|
||||
r = self.daemon.wait_for_log('Broadcasting RBF {} .* to resolve {} depth={}'
|
||||
.format(name, resolve, depth))
|
||||
return re.search(r'.* \(([0-9a-fA-F]*)\)', r).group(1)
|
||||
|
||||
rbf_txes = []
|
||||
# Now the censoring miners generate some blocks.
|
||||
for depth in range(2, 8):
|
||||
bitcoind.generate_block(1)
|
||||
sync_blockheight(bitcoind, [l2])
|
||||
# l2 should RBF, twice even, one for the l1 main output,
|
||||
# one for the l1 HTLC output.
|
||||
rbf_txes.append(get_rbf_tx(l2, depth,
|
||||
'OUR_PENALTY_TX',
|
||||
'THEIR_REVOKED_UNILATERAL/THEIR_HTLC'))
|
||||
rbf_txes.append(get_rbf_tx(l2, depth,
|
||||
'OUR_PENALTY_TX',
|
||||
'THEIR_REVOKED_UNILATERAL/DELAYED_CHEAT_OUTPUT_TO_THEM'))
|
||||
|
||||
# Now that the transactions have high fees, independent miners
|
||||
# realize they can earn potentially more money by grabbing the
|
||||
# high-fee censored transactions, and fresh, non-censoring
|
||||
# hashpower arises, evicting the censor.
|
||||
l2.daemon.rpcproxy.mock_rpc('sendrawtransaction', None)
|
||||
|
||||
# Check that the order in which l2 generated RBF transactions
|
||||
# would be acceptable to Bitcoin.
|
||||
for tx in rbf_txes:
|
||||
# Use the bcli interface as well, so that we also check the
|
||||
# bcli interface.
|
||||
l2.rpc.call('sendrawtransaction', [tx, True])
|
||||
|
||||
# Now the non-censoring miners overpower the censoring miners.
|
||||
bitcoind.generate_block(1)
|
||||
sync_blockheight(bitcoind, [l2])
|
||||
|
||||
# And l2 should consider it resolved now.
|
||||
l2.daemon.wait_for_log('Resolved THEIR_REVOKED_UNILATERAL/DELAYED_CHEAT_OUTPUT_TO_THEM by our proposal OUR_PENALTY_TX')
|
||||
l2.daemon.wait_for_log('Resolved THEIR_REVOKED_UNILATERAL/THEIR_HTLC by our proposal OUR_PENALTY_TX')
|
||||
|
||||
# And l2 should consider it in its listfunds.
|
||||
assert(len(l2.rpc.listfunds()['outputs']) >= 1)
|
||||
|
||||
|
||||
@unittest.skipIf(not DEVELOPER, "uses dev_sign_last_tx")
|
||||
def test_penalty_rbf_burn(node_factory, bitcoind, executor, chainparams):
|
||||
'''
|
||||
Test that penalty transactions are RBFed and we are willing to burn
|
||||
it all up to spite the thief.
|
||||
'''
|
||||
to_self_delay = 10
|
||||
# l1 is the thief, which causes our honest upstanding lightningd
|
||||
# code to break, so l1 can fail.
|
||||
# Initially, disconnect before the HTLC can be resolved.
|
||||
l1 = node_factory.get_node(disconnect=['=WIRE_COMMITMENT_SIGNED-nocommit'],
|
||||
may_fail=True, allow_broken_log=True)
|
||||
l2 = node_factory.get_node(disconnect=['=WIRE_COMMITMENT_SIGNED-nocommit'],
|
||||
options={'watchtime-blocks': to_self_delay})
|
||||
|
||||
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
||||
l1.fund_channel(l2, 10**7)
|
||||
|
||||
# Trigger an HTLC being added.
|
||||
t = executor.submit(l1.pay, l2, 1000000 * 1000)
|
||||
|
||||
# Make sure the channel is still alive.
|
||||
assert len(l1.getactivechannels()) == 2
|
||||
assert len(l2.getactivechannels()) == 2
|
||||
|
||||
# Wait for the disconnection.
|
||||
l1.daemon.wait_for_log('=WIRE_COMMITMENT_SIGNED-nocommit')
|
||||
l2.daemon.wait_for_log('=WIRE_COMMITMENT_SIGNED-nocommit')
|
||||
# Make sure l1 gets the new HTLC.
|
||||
l1.daemon.wait_for_log('got commitsig')
|
||||
|
||||
# l1 prepares a theft commitment transaction
|
||||
theft_tx = l1.rpc.dev_sign_last_tx(l2.info['id'])['tx']
|
||||
|
||||
# Now continue processing until fulfilment.
|
||||
l1.rpc.dev_reenable_commit(l2.info['id'])
|
||||
l2.rpc.dev_reenable_commit(l1.info['id'])
|
||||
|
||||
# Wait for the fulfilment.
|
||||
l1.daemon.wait_for_log('peer_in WIRE_UPDATE_FULFILL_HTLC')
|
||||
l1.daemon.wait_for_log('peer_out WIRE_REVOKE_AND_ACK')
|
||||
l2.daemon.wait_for_log('peer_out WIRE_UPDATE_FULFILL_HTLC')
|
||||
l1.daemon.wait_for_log('peer_in WIRE_REVOKE_AND_ACK')
|
||||
|
||||
# Now payment should complete.
|
||||
t.result(timeout=10)
|
||||
|
||||
# l1 goes offline and bribes the miners to censor transactions from l2.
|
||||
l1.rpc.stop()
|
||||
|
||||
def censoring_sendrawtx(r):
|
||||
return {'id': r['id'], 'result': {}}
|
||||
|
||||
l2.daemon.rpcproxy.mock_rpc('sendrawtransaction', censoring_sendrawtx)
|
||||
|
||||
# l1 now performs the theft attack!
|
||||
bitcoind.rpc.sendrawtransaction(theft_tx)
|
||||
bitcoind.generate_block(1)
|
||||
|
||||
# l2 notices.
|
||||
l2.daemon.wait_for_log(' to ONCHAIN')
|
||||
|
||||
def get_rbf_tx(self, depth, name, resolve):
|
||||
r = self.daemon.wait_for_log('Broadcasting RBF {} .* to resolve {} depth={}'
|
||||
.format(name, resolve, depth))
|
||||
return re.search(r'.* \(([0-9a-fA-F]*)\)', r).group(1)
|
||||
|
||||
rbf_txes = []
|
||||
# Now the censoring miners generate some blocks.
|
||||
for depth in range(2, 10):
|
||||
bitcoind.generate_block(1)
|
||||
sync_blockheight(bitcoind, [l2])
|
||||
# l2 should RBF, twice even, one for the l1 main output,
|
||||
# one for the l1 HTLC output.
|
||||
rbf_txes.append(get_rbf_tx(l2, depth,
|
||||
'OUR_PENALTY_TX',
|
||||
'THEIR_REVOKED_UNILATERAL/THEIR_HTLC'))
|
||||
rbf_txes.append(get_rbf_tx(l2, depth,
|
||||
'OUR_PENALTY_TX',
|
||||
'THEIR_REVOKED_UNILATERAL/DELAYED_CHEAT_OUTPUT_TO_THEM'))
|
||||
|
||||
# Now that the transactions have high fees, independent miners
|
||||
# realize they can earn potentially more money by grabbing the
|
||||
# high-fee censored transactions, and fresh, non-censoring
|
||||
# hashpower arises, evicting the censor.
|
||||
l2.daemon.rpcproxy.mock_rpc('sendrawtransaction', None)
|
||||
|
||||
# Check that the last two txes can be broadcast.
|
||||
# These should donate the total amount to miners.
|
||||
rbf_txes = rbf_txes[-2:]
|
||||
for tx in rbf_txes:
|
||||
l2.rpc.call('sendrawtransaction', [tx, True])
|
||||
|
||||
# Now the non-censoring miners overpower the censoring miners.
|
||||
bitcoind.generate_block(1)
|
||||
sync_blockheight(bitcoind, [l2])
|
||||
|
||||
# And l2 should consider it resolved now.
|
||||
l2.daemon.wait_for_log('Resolved THEIR_REVOKED_UNILATERAL/DELAYED_CHEAT_OUTPUT_TO_THEM by our proposal OUR_PENALTY_TX')
|
||||
l2.daemon.wait_for_log('Resolved THEIR_REVOKED_UNILATERAL/THEIR_HTLC by our proposal OUR_PENALTY_TX')
|
||||
|
||||
# l2 donated it to the miners, so it owns nothing
|
||||
assert(len(l2.rpc.listfunds()['outputs']) == 0)
|
||||
|
||||
|
||||
@unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1")
|
||||
def test_onchain_first_commit(node_factory, bitcoind):
|
||||
"""Onchain handling where opener immediately drops to chain"""
|
||||
|
||||
Reference in New Issue
Block a user