2021-02-04 20:17:53 +01:00
|
|
|
from fixtures import * # noqa: F401,F403
|
|
|
|
from fixtures import TEST_NETWORK
|
2021-02-10 22:17:02 +01:00
|
|
|
from pyln.client import RpcError
|
2021-02-04 20:17:53 +01:00
|
|
|
from utils import (
|
2021-04-26 21:58:58 +02:00
|
|
|
only_one, wait_for, sync_blockheight, first_channel_id
|
2021-02-04 20:17:53 +01:00
|
|
|
)
|
|
|
|
|
2021-02-10 22:17:02 +01:00
|
|
|
import pytest
|
2021-03-16 01:50:31 +01:00
|
|
|
import re
|
2021-02-04 20:17:53 +01:00
|
|
|
import unittest
|
|
|
|
|
|
|
|
|
|
|
|
def find_next_feerate(node, peer):
|
|
|
|
chan = only_one(only_one(node.rpc.listpeers(peer.info['id'])['peers'])['channels'])
|
|
|
|
return chan['next_feerate']
|
|
|
|
|
|
|
|
|
2021-03-12 01:19:40 +01:00
|
|
|
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
|
2021-04-26 21:58:58 +02:00
|
|
|
@pytest.mark.developer("uses dev-disconnect")
|
2021-04-26 21:23:40 +02:00
|
|
|
@pytest.mark.openchannel('v1') # Mixed v1 + v2, v2 manually turned on
|
2021-03-11 03:23:03 +01:00
|
|
|
def test_multifunding_v2_best_effort(node_factory, bitcoind):
|
|
|
|
'''
|
|
|
|
Check that best_effort flag works.
|
|
|
|
'''
|
|
|
|
disconnects = ["-WIRE_INIT",
|
|
|
|
"-WIRE_ACCEPT_CHANNEL",
|
|
|
|
"-WIRE_FUNDING_SIGNED"]
|
2021-03-12 01:19:40 +01:00
|
|
|
l1 = node_factory.get_node(options={'experimental-dual-fund': None},
|
2021-03-11 03:23:03 +01:00
|
|
|
allow_warning=True,
|
|
|
|
may_reconnect=True)
|
2021-03-12 01:19:40 +01:00
|
|
|
l2 = node_factory.get_node(options={'experimental-dual-fund': None},
|
2021-03-11 03:23:03 +01:00
|
|
|
allow_warning=True,
|
|
|
|
may_reconnect=True)
|
|
|
|
l3 = node_factory.get_node(disconnect=disconnects)
|
|
|
|
l4 = node_factory.get_node()
|
|
|
|
|
|
|
|
l1.fundwallet(2000000)
|
|
|
|
|
|
|
|
destinations = [{"id": '{}@localhost:{}'.format(l2.info['id'], l2.port),
|
|
|
|
"amount": 50000},
|
|
|
|
{"id": '{}@localhost:{}'.format(l3.info['id'], l3.port),
|
|
|
|
"amount": 50000},
|
|
|
|
{"id": '{}@localhost:{}'.format(l4.info['id'], l4.port),
|
|
|
|
"amount": 50000}]
|
|
|
|
|
|
|
|
for i, d in enumerate(disconnects):
|
|
|
|
failed_sign = d == "-WIRE_FUNDING_SIGNED"
|
|
|
|
# Should succeed due to best-effort flag.
|
|
|
|
min_channels = 1 if failed_sign else 2
|
|
|
|
l1.rpc.multifundchannel(destinations, minchannels=min_channels)
|
|
|
|
|
|
|
|
bitcoind.generate_block(6, wait_for_mempool=1)
|
|
|
|
|
|
|
|
# l3 should fail to have channels; l2 also fails on last attempt
|
|
|
|
node_list = [l1, l4] if failed_sign else [l1, l2, l4]
|
|
|
|
for node in node_list:
|
|
|
|
node.daemon.wait_for_log(r'to CHANNELD_NORMAL')
|
|
|
|
|
|
|
|
# There should be working channels to l2 and l4 for every run
|
|
|
|
# but the last
|
|
|
|
working_chans = [l4] if failed_sign else [l2, l4]
|
|
|
|
for ldest in working_chans:
|
|
|
|
inv = ldest.rpc.invoice(5000, 'i{}'.format(i), 'i{}'.format(i))['bolt11']
|
|
|
|
l1.rpc.pay(inv)
|
|
|
|
|
|
|
|
# Function to find the SCID of the channel that is
|
|
|
|
# currently open.
|
|
|
|
# Cannot use LightningNode.get_channel_scid since
|
|
|
|
# it assumes the *first* channel found is the one
|
|
|
|
# wanted, but in our case we close channels and
|
|
|
|
# open again, so multiple channels may remain
|
|
|
|
# listed.
|
|
|
|
def get_funded_channel_scid(n1, n2):
|
|
|
|
peers = n1.rpc.listpeers(n2.info['id'])['peers']
|
|
|
|
assert len(peers) == 1
|
|
|
|
peer = peers[0]
|
|
|
|
channels = peer['channels']
|
|
|
|
assert channels
|
|
|
|
for c in channels:
|
|
|
|
state = c['state']
|
|
|
|
if state in ('DUALOPEND_AWAITING_LOCKIN', 'CHANNELD_AWAITING_LOCKIN', 'CHANNELD_NORMAL'):
|
|
|
|
return c['short_channel_id']
|
|
|
|
assert False
|
|
|
|
|
|
|
|
# Now close channels to l2 and l4, for the next run.
|
|
|
|
if not failed_sign:
|
|
|
|
l1.rpc.close(get_funded_channel_scid(l1, l2))
|
|
|
|
l1.rpc.close(get_funded_channel_scid(l1, l4))
|
|
|
|
|
|
|
|
for node in node_list:
|
|
|
|
node.daemon.wait_for_log(r'to CLOSINGD_COMPLETE')
|
|
|
|
|
|
|
|
# With 2 down, it will fail to fund channel
|
|
|
|
l2.stop()
|
|
|
|
l3.stop()
|
|
|
|
with pytest.raises(RpcError, match=r'Connection refused'):
|
|
|
|
l1.rpc.multifundchannel(destinations, minchannels=2)
|
|
|
|
|
|
|
|
# This works though.
|
|
|
|
l1.rpc.multifundchannel(destinations, minchannels=1)
|
|
|
|
|
|
|
|
|
2021-03-16 01:50:31 +01:00
|
|
|
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
|
2021-04-26 21:58:58 +02:00
|
|
|
@pytest.mark.developer("uses dev-disconnect")
|
2021-04-26 21:23:40 +02:00
|
|
|
@pytest.mark.openchannel('v2')
|
2021-03-16 01:50:31 +01:00
|
|
|
def test_v2_open_sigs_restart(node_factory, bitcoind):
|
|
|
|
disconnects_1 = ['-WIRE_TX_SIGNATURES']
|
|
|
|
disconnects_2 = ['+WIRE_TX_SIGNATURES']
|
|
|
|
|
|
|
|
l1, l2 = node_factory.get_nodes(2,
|
2021-04-26 21:23:40 +02:00
|
|
|
opts=[{'disconnect': disconnects_1,
|
2021-03-16 01:50:31 +01:00
|
|
|
'may_reconnect': True},
|
2021-04-26 21:23:40 +02:00
|
|
|
{'disconnect': disconnects_2,
|
2021-03-16 01:50:31 +01:00
|
|
|
'may_reconnect': True}])
|
|
|
|
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
amount = 2**24
|
|
|
|
chan_amount = 100000
|
|
|
|
bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'], amount / 10**8 + 0.01)
|
|
|
|
bitcoind.generate_block(1)
|
|
|
|
# Wait for it to arrive.
|
|
|
|
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) > 0)
|
|
|
|
|
|
|
|
# Fund the channel, should appear to finish ok even though the
|
|
|
|
# peer fails
|
2021-03-18 22:22:03 +01:00
|
|
|
with pytest.raises(RpcError):
|
2021-03-16 01:50:31 +01:00
|
|
|
l1.rpc.fundchannel(l2.info['id'], chan_amount)
|
|
|
|
|
|
|
|
chan_id = first_channel_id(l1, l2)
|
2021-03-18 22:22:03 +01:00
|
|
|
log = l1.daemon.is_in_log('{} psbt'.format(chan_id))
|
2021-03-18 19:49:15 +01:00
|
|
|
assert log
|
2021-03-18 22:22:03 +01:00
|
|
|
psbt = re.search("psbt (.*)", log).group(1)
|
|
|
|
|
2021-03-16 01:50:31 +01:00
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
l1.daemon.wait_for_log('Peer has reconnected, state DUALOPEND_OPEN_INIT')
|
|
|
|
with pytest.raises(RpcError):
|
|
|
|
l1.rpc.openchannel_signed(chan_id, psbt)
|
|
|
|
|
|
|
|
l2.daemon.wait_for_log('Broadcasting funding tx')
|
2021-03-22 21:46:08 +01:00
|
|
|
txid = l2.rpc.listpeers(l1.info['id'])['peers'][0]['channels'][0]['funding_txid']
|
|
|
|
bitcoind.generate_block(6, wait_for_mempool=txid)
|
2021-03-16 01:50:31 +01:00
|
|
|
|
|
|
|
# Make sure we're ok.
|
|
|
|
l1.daemon.wait_for_log(r'to CHANNELD_NORMAL')
|
2021-03-18 19:49:15 +01:00
|
|
|
l2.daemon.wait_for_log(r'to CHANNELD_NORMAL')
|
2021-03-16 01:50:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
|
2021-04-26 21:58:58 +02:00
|
|
|
@pytest.mark.developer("uses dev-disconnect")
|
2021-04-26 21:23:40 +02:00
|
|
|
@pytest.mark.openchannel('v2')
|
2021-03-16 01:50:31 +01:00
|
|
|
def test_v2_open_sigs_restart_while_dead(node_factory, bitcoind):
|
|
|
|
# Same thing as above, except the transaction mines
|
|
|
|
# while we're asleep
|
|
|
|
disconnects_1 = ['-WIRE_TX_SIGNATURES']
|
|
|
|
disconnects_2 = ['+WIRE_TX_SIGNATURES']
|
|
|
|
|
|
|
|
l1, l2 = node_factory.get_nodes(2,
|
2021-04-26 21:23:40 +02:00
|
|
|
opts=[{'disconnect': disconnects_1,
|
2021-03-18 19:49:15 +01:00
|
|
|
'may_reconnect': True,
|
|
|
|
'may_fail': True},
|
2021-04-26 21:23:40 +02:00
|
|
|
{'disconnect': disconnects_2,
|
2021-03-18 19:49:15 +01:00
|
|
|
'may_reconnect': True,
|
|
|
|
'may_fail': True}])
|
2021-03-16 01:50:31 +01:00
|
|
|
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
amount = 2**24
|
|
|
|
chan_amount = 100000
|
|
|
|
bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'], amount / 10**8 + 0.01)
|
|
|
|
bitcoind.generate_block(1)
|
|
|
|
# Wait for it to arrive.
|
|
|
|
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) > 0)
|
|
|
|
|
|
|
|
# Fund the channel, should appear to finish ok even though the
|
|
|
|
# peer fails
|
2021-03-18 22:22:03 +01:00
|
|
|
with pytest.raises(RpcError):
|
2021-03-16 01:50:31 +01:00
|
|
|
l1.rpc.fundchannel(l2.info['id'], chan_amount)
|
|
|
|
|
|
|
|
chan_id = first_channel_id(l1, l2)
|
2021-03-18 22:22:03 +01:00
|
|
|
log = l1.daemon.is_in_log('{} psbt'.format(chan_id))
|
2021-03-18 19:49:15 +01:00
|
|
|
assert log
|
2021-03-18 22:22:03 +01:00
|
|
|
psbt = re.search("psbt (.*)", log).group(1)
|
|
|
|
|
2021-03-16 01:50:31 +01:00
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
l1.daemon.wait_for_log('Peer has reconnected, state DUALOPEND_OPEN_INIT')
|
|
|
|
with pytest.raises(RpcError):
|
|
|
|
l1.rpc.openchannel_signed(chan_id, psbt)
|
|
|
|
|
|
|
|
l2.daemon.wait_for_log('Broadcasting funding tx')
|
2021-04-08 02:58:50 +02:00
|
|
|
l2.daemon.wait_for_log('sendrawtx exit 0')
|
2021-03-16 01:50:31 +01:00
|
|
|
|
|
|
|
l1.stop()
|
|
|
|
l2.stop()
|
|
|
|
bitcoind.generate_block(6)
|
|
|
|
l1.restart()
|
|
|
|
l2.restart()
|
|
|
|
|
|
|
|
# Make sure we're ok.
|
|
|
|
l2.daemon.wait_for_log(r'to CHANNELD_NORMAL')
|
|
|
|
l1.daemon.wait_for_log(r'to CHANNELD_NORMAL')
|
|
|
|
|
|
|
|
|
2021-02-04 20:17:53 +01:00
|
|
|
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
|
2021-04-26 21:23:40 +02:00
|
|
|
@pytest.mark.openchannel('v2')
|
2021-02-04 20:17:53 +01:00
|
|
|
def test_v2_rbf(node_factory, bitcoind, chainparams):
|
2021-04-26 21:23:40 +02:00
|
|
|
l1, l2 = node_factory.get_nodes(2, opts={'wumbo': None})
|
2021-02-04 20:17:53 +01:00
|
|
|
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
amount = 2**24
|
|
|
|
chan_amount = 100000
|
|
|
|
bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'], amount / 10**8 + 0.01)
|
|
|
|
bitcoind.generate_block(1)
|
|
|
|
# Wait for it to arrive.
|
|
|
|
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) > 0)
|
|
|
|
|
|
|
|
res = l1.rpc.fundchannel(l2.info['id'], chan_amount)
|
|
|
|
chan_id = res['channel_id']
|
|
|
|
vins = bitcoind.rpc.decoderawtransaction(res['tx'])['vin']
|
|
|
|
assert(only_one(vins))
|
|
|
|
prev_utxos = ["{}:{}".format(vins[0]['txid'], vins[0]['vout'])]
|
|
|
|
|
|
|
|
# Check that we're waiting for lockin
|
|
|
|
l1.daemon.wait_for_log(' to DUALOPEND_AWAITING_LOCKIN')
|
|
|
|
|
|
|
|
next_feerate = find_next_feerate(l1, l2)
|
|
|
|
|
|
|
|
# Check that feerate info is correct
|
|
|
|
info_1 = only_one(only_one(l1.rpc.listpeers(l2.info['id'])['peers'])['channels'])
|
|
|
|
assert info_1['initial_feerate'] == info_1['last_feerate']
|
|
|
|
rate = int(info_1['last_feerate'][:-5])
|
|
|
|
assert int(info_1['next_feerate'][:-5]) == rate + rate // 4
|
|
|
|
assert info_1['next_fee_step'] == 1
|
|
|
|
|
|
|
|
# Initiate an RBF
|
|
|
|
startweight = 42 + 172 # base weight, funding output
|
|
|
|
initpsbt = l1.rpc.utxopsbt(chan_amount, next_feerate, startweight,
|
|
|
|
prev_utxos, reservedok=True,
|
|
|
|
min_witness_weight=110,
|
|
|
|
excess_as_change=True)
|
|
|
|
|
|
|
|
# Do the bump
|
|
|
|
bump = l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt'])
|
|
|
|
|
|
|
|
update = l1.rpc.openchannel_update(chan_id, bump['psbt'])
|
|
|
|
assert update['commitments_secured']
|
|
|
|
|
|
|
|
# Check that feerate info has incremented
|
|
|
|
info_2 = only_one(only_one(l1.rpc.listpeers(l2.info['id'])['peers'])['channels'])
|
|
|
|
assert info_1['initial_feerate'] == info_2['initial_feerate']
|
|
|
|
assert info_1['next_feerate'] == info_2['last_feerate']
|
|
|
|
|
|
|
|
rate = int(info_2['last_feerate'][:-5])
|
|
|
|
assert int(info_2['next_feerate'][:-5]) == rate + rate // 4
|
|
|
|
assert info_2['next_fee_step'] == 2
|
|
|
|
|
|
|
|
# Sign our inputs, and continue
|
|
|
|
signed_psbt = l1.rpc.signpsbt(update['psbt'])['signed_psbt']
|
|
|
|
l1.rpc.openchannel_signed(chan_id, signed_psbt)
|
|
|
|
|
|
|
|
bitcoind.generate_block(1)
|
|
|
|
sync_blockheight(bitcoind, [l1])
|
|
|
|
l1.daemon.wait_for_log(' to CHANNELD_NORMAL')
|
|
|
|
|
|
|
|
# Check that feerate info is gone
|
|
|
|
info_1 = only_one(only_one(l1.rpc.listpeers(l2.info['id'])['peers'])['channels'])
|
|
|
|
assert 'initial_feerate' not in info_1
|
|
|
|
assert 'last_feerate' not in info_1
|
|
|
|
assert 'next_feerate' not in info_1
|
|
|
|
assert 'next_fee_step' not in info_1
|
|
|
|
|
|
|
|
# Shut l2 down, force close the channel.
|
|
|
|
l2.stop()
|
|
|
|
resp = l1.rpc.close(l2.info['id'], unilateraltimeout=1)
|
|
|
|
assert resp['type'] == 'unilateral'
|
|
|
|
l1.daemon.wait_for_log(' to CHANNELD_SHUTTING_DOWN')
|
|
|
|
l1.daemon.wait_for_log('sendrawtx exit 0')
|
|
|
|
|
2021-02-10 22:17:02 +01:00
|
|
|
|
2021-02-12 00:01:07 +01:00
|
|
|
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
|
2021-04-26 21:23:40 +02:00
|
|
|
@pytest.mark.openchannel('v2')
|
2021-02-12 00:01:07 +01:00
|
|
|
def test_v2_rbf_multi(node_factory, bitcoind, chainparams):
|
|
|
|
l1, l2 = node_factory.get_nodes(2,
|
2021-04-26 21:23:40 +02:00
|
|
|
opts={'may_reconnect': True,
|
2021-03-09 22:14:08 +01:00
|
|
|
'allow_warning': True})
|
2021-02-12 00:01:07 +01:00
|
|
|
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
amount = 2**24
|
|
|
|
chan_amount = 100000
|
|
|
|
bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'], amount / 10**8 + 0.01)
|
|
|
|
bitcoind.generate_block(1)
|
|
|
|
# Wait for it to arrive.
|
|
|
|
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) > 0)
|
|
|
|
|
|
|
|
res = l1.rpc.fundchannel(l2.info['id'], chan_amount)
|
|
|
|
chan_id = res['channel_id']
|
|
|
|
vins = bitcoind.rpc.decoderawtransaction(res['tx'])['vin']
|
|
|
|
assert(only_one(vins))
|
|
|
|
prev_utxos = ["{}:{}".format(vins[0]['txid'], vins[0]['vout'])]
|
|
|
|
|
|
|
|
# Check that we're waiting for lockin
|
|
|
|
l1.daemon.wait_for_log(' to DUALOPEND_AWAITING_LOCKIN')
|
|
|
|
|
2021-03-09 22:14:08 +01:00
|
|
|
# Attempt to do abort, should fail since we've
|
|
|
|
# already gotten an inflight
|
|
|
|
with pytest.raises(RpcError):
|
|
|
|
l1.rpc.openchannel_abort(chan_id)
|
|
|
|
|
2021-02-12 00:01:07 +01:00
|
|
|
next_feerate = find_next_feerate(l1, l2)
|
|
|
|
|
|
|
|
# Initiate an RBF
|
|
|
|
startweight = 42 + 172 # base weight, funding output
|
|
|
|
initpsbt = l1.rpc.utxopsbt(chan_amount, next_feerate, startweight,
|
|
|
|
prev_utxos, reservedok=True,
|
|
|
|
min_witness_weight=110,
|
|
|
|
excess_as_change=True)
|
|
|
|
|
|
|
|
# Do the bump
|
|
|
|
bump = l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt'])
|
|
|
|
|
2021-03-09 22:14:08 +01:00
|
|
|
# Abort this open attempt! We will re-try
|
|
|
|
aborted = l1.rpc.openchannel_abort(chan_id)
|
|
|
|
assert not aborted['channel_canceled']
|
|
|
|
|
|
|
|
# Do the bump, again
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
bump = l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt'])
|
|
|
|
|
2021-02-12 00:01:07 +01:00
|
|
|
update = l1.rpc.openchannel_update(chan_id, bump['psbt'])
|
|
|
|
assert update['commitments_secured']
|
|
|
|
|
|
|
|
# Sign our inputs, and continue
|
|
|
|
signed_psbt = l1.rpc.signpsbt(update['psbt'])['signed_psbt']
|
|
|
|
l1.rpc.openchannel_signed(chan_id, signed_psbt)
|
|
|
|
|
|
|
|
next_feerate = find_next_feerate(l1, l2)
|
|
|
|
|
|
|
|
# Initiate an RBF, double the channel amount this time
|
|
|
|
startweight = 42 + 172 # base weight, funding output
|
|
|
|
initpsbt = l1.rpc.utxopsbt(chan_amount * 2, next_feerate, startweight,
|
|
|
|
prev_utxos, reservedok=True,
|
|
|
|
min_witness_weight=110,
|
|
|
|
excess_as_change=True)
|
|
|
|
|
|
|
|
# Do the bump
|
|
|
|
bump = l1.rpc.openchannel_bump(chan_id, chan_amount * 2, initpsbt['psbt'])
|
|
|
|
|
|
|
|
update = l1.rpc.openchannel_update(chan_id, bump['psbt'])
|
|
|
|
assert update['commitments_secured']
|
|
|
|
|
|
|
|
# Sign our inputs, and continue
|
|
|
|
signed_psbt = l1.rpc.signpsbt(update['psbt'])['signed_psbt']
|
|
|
|
l1.rpc.openchannel_signed(chan_id, signed_psbt)
|
|
|
|
|
|
|
|
bitcoind.generate_block(1)
|
|
|
|
sync_blockheight(bitcoind, [l1])
|
|
|
|
l1.daemon.wait_for_log(' to CHANNELD_NORMAL')
|
|
|
|
|
|
|
|
|
2021-02-10 22:17:02 +01:00
|
|
|
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
|
2021-04-26 21:58:58 +02:00
|
|
|
@pytest.mark.developer("uses dev-disconnect")
|
2021-04-26 21:23:40 +02:00
|
|
|
@pytest.mark.openchannel('v2')
|
2021-02-10 22:17:02 +01:00
|
|
|
def test_rbf_reconnect_init(node_factory, bitcoind, chainparams):
|
|
|
|
disconnects = ['-WIRE_INIT_RBF',
|
|
|
|
'@WIRE_INIT_RBF',
|
|
|
|
'+WIRE_INIT_RBF']
|
|
|
|
|
|
|
|
l1, l2 = node_factory.get_nodes(2,
|
2021-04-26 21:23:40 +02:00
|
|
|
opts=[{'disconnect': disconnects,
|
2021-02-10 22:17:02 +01:00
|
|
|
'may_reconnect': True},
|
2021-04-26 21:23:40 +02:00
|
|
|
{'may_reconnect': True}])
|
2021-02-10 22:17:02 +01:00
|
|
|
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
amount = 2**24
|
|
|
|
chan_amount = 100000
|
|
|
|
bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'], amount / 10**8 + 0.01)
|
|
|
|
bitcoind.generate_block(1)
|
|
|
|
# Wait for it to arrive.
|
|
|
|
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) > 0)
|
|
|
|
|
|
|
|
res = l1.rpc.fundchannel(l2.info['id'], chan_amount)
|
|
|
|
chan_id = res['channel_id']
|
|
|
|
vins = bitcoind.rpc.decoderawtransaction(res['tx'])['vin']
|
|
|
|
assert(only_one(vins))
|
|
|
|
prev_utxos = ["{}:{}".format(vins[0]['txid'], vins[0]['vout'])]
|
|
|
|
|
|
|
|
# Check that we're waiting for lockin
|
|
|
|
l1.daemon.wait_for_log(' to DUALOPEND_AWAITING_LOCKIN')
|
|
|
|
|
|
|
|
next_feerate = find_next_feerate(l1, l2)
|
|
|
|
|
|
|
|
# Initiate an RBF
|
|
|
|
startweight = 42 + 172 # base weight, funding output
|
|
|
|
initpsbt = l1.rpc.utxopsbt(chan_amount, next_feerate, startweight,
|
|
|
|
prev_utxos, reservedok=True,
|
|
|
|
min_witness_weight=110,
|
|
|
|
excess_as_change=True)
|
|
|
|
|
|
|
|
# Do the bump!?
|
|
|
|
for d in disconnects:
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
with pytest.raises(RpcError):
|
|
|
|
l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt'])
|
|
|
|
assert l1.rpc.getpeer(l2.info['id']) is not None
|
|
|
|
|
|
|
|
# This should succeed
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt'])
|
|
|
|
|
|
|
|
|
|
|
|
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
|
2021-04-26 21:58:58 +02:00
|
|
|
@pytest.mark.developer("uses dev-disconnect")
|
2021-04-26 21:23:40 +02:00
|
|
|
@pytest.mark.openchannel('v2')
|
2021-02-10 22:17:02 +01:00
|
|
|
def test_rbf_reconnect_ack(node_factory, bitcoind, chainparams):
|
|
|
|
disconnects = ['-WIRE_ACK_RBF',
|
|
|
|
'@WIRE_ACK_RBF',
|
|
|
|
'+WIRE_ACK_RBF']
|
|
|
|
|
|
|
|
l1, l2 = node_factory.get_nodes(2,
|
2021-04-26 21:23:40 +02:00
|
|
|
opts=[{'may_reconnect': True},
|
|
|
|
{'disconnect': disconnects,
|
2021-02-10 22:17:02 +01:00
|
|
|
'may_reconnect': True}])
|
|
|
|
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
amount = 2**24
|
|
|
|
chan_amount = 100000
|
|
|
|
bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'], amount / 10**8 + 0.01)
|
|
|
|
bitcoind.generate_block(1)
|
|
|
|
# Wait for it to arrive.
|
|
|
|
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) > 0)
|
|
|
|
|
|
|
|
res = l1.rpc.fundchannel(l2.info['id'], chan_amount)
|
|
|
|
chan_id = res['channel_id']
|
|
|
|
vins = bitcoind.rpc.decoderawtransaction(res['tx'])['vin']
|
|
|
|
assert(only_one(vins))
|
|
|
|
prev_utxos = ["{}:{}".format(vins[0]['txid'], vins[0]['vout'])]
|
|
|
|
|
|
|
|
# Check that we're waiting for lockin
|
|
|
|
l1.daemon.wait_for_log(' to DUALOPEND_AWAITING_LOCKIN')
|
|
|
|
|
|
|
|
next_feerate = find_next_feerate(l1, l2)
|
|
|
|
|
|
|
|
# Initiate an RBF
|
|
|
|
startweight = 42 + 172 # base weight, funding output
|
|
|
|
initpsbt = l1.rpc.utxopsbt(chan_amount, next_feerate, startweight,
|
|
|
|
prev_utxos, reservedok=True,
|
|
|
|
min_witness_weight=110,
|
|
|
|
excess_as_change=True)
|
|
|
|
|
|
|
|
# Do the bump!?
|
|
|
|
for d in disconnects:
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
with pytest.raises(RpcError):
|
|
|
|
l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt'])
|
|
|
|
assert l1.rpc.getpeer(l2.info['id']) is not None
|
|
|
|
|
|
|
|
# This should succeed
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt'])
|
2021-02-11 00:46:26 +01:00
|
|
|
|
|
|
|
|
|
|
|
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
|
2021-04-26 21:58:58 +02:00
|
|
|
@pytest.mark.developer("uses dev-disconnect")
|
2021-04-26 21:23:40 +02:00
|
|
|
@pytest.mark.openchannel('v2')
|
2021-02-11 00:46:26 +01:00
|
|
|
def test_rbf_reconnect_tx_construct(node_factory, bitcoind, chainparams):
|
|
|
|
disconnects = ['=WIRE_TX_ADD_INPUT', # Initial funding succeeds
|
|
|
|
'-WIRE_TX_ADD_INPUT',
|
|
|
|
'@WIRE_TX_ADD_INPUT',
|
|
|
|
'+WIRE_TX_ADD_INPUT',
|
|
|
|
'-WIRE_TX_ADD_OUTPUT',
|
|
|
|
'@WIRE_TX_ADD_OUTPUT',
|
|
|
|
'+WIRE_TX_ADD_OUTPUT',
|
|
|
|
'-WIRE_TX_COMPLETE',
|
|
|
|
'@WIRE_TX_COMPLETE',
|
|
|
|
'+WIRE_TX_COMPLETE']
|
|
|
|
|
|
|
|
l1, l2 = node_factory.get_nodes(2,
|
2021-04-26 21:23:40 +02:00
|
|
|
opts=[{'disconnect': disconnects,
|
2021-02-11 00:46:26 +01:00
|
|
|
'may_reconnect': True},
|
2021-04-26 21:23:40 +02:00
|
|
|
{'may_reconnect': True}])
|
2021-02-11 00:46:26 +01:00
|
|
|
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
amount = 2**24
|
|
|
|
chan_amount = 100000
|
|
|
|
bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'], amount / 10**8 + 0.01)
|
|
|
|
bitcoind.generate_block(1)
|
|
|
|
# Wait for it to arrive.
|
|
|
|
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) > 0)
|
|
|
|
|
|
|
|
res = l1.rpc.fundchannel(l2.info['id'], chan_amount)
|
|
|
|
chan_id = res['channel_id']
|
|
|
|
vins = bitcoind.rpc.decoderawtransaction(res['tx'])['vin']
|
|
|
|
assert(only_one(vins))
|
|
|
|
prev_utxos = ["{}:{}".format(vins[0]['txid'], vins[0]['vout'])]
|
|
|
|
|
|
|
|
# Check that we're waiting for lockin
|
|
|
|
l1.daemon.wait_for_log(' to DUALOPEND_AWAITING_LOCKIN')
|
|
|
|
|
|
|
|
next_feerate = find_next_feerate(l1, l2)
|
|
|
|
|
|
|
|
# Initiate an RBF
|
|
|
|
startweight = 42 + 172 # base weight, funding output
|
|
|
|
initpsbt = l1.rpc.utxopsbt(chan_amount, next_feerate, startweight,
|
|
|
|
prev_utxos, reservedok=True,
|
|
|
|
min_witness_weight=110,
|
|
|
|
excess_as_change=True)
|
|
|
|
|
|
|
|
# Run through TX_ADD wires
|
|
|
|
for d in disconnects[1:-3]:
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
with pytest.raises(RpcError):
|
|
|
|
l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt'])
|
|
|
|
assert l1.rpc.getpeer(l2.info['id']) is not None
|
|
|
|
|
|
|
|
# Now we finish off the completes failure check
|
|
|
|
for d in disconnects[-3:]:
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
bump = l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt'])
|
|
|
|
with pytest.raises(RpcError):
|
|
|
|
update = l1.rpc.openchannel_update(chan_id, bump['psbt'])
|
|
|
|
|
|
|
|
# Now we succeed
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
bump = l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt'])
|
|
|
|
update = l1.rpc.openchannel_update(chan_id, bump['psbt'])
|
|
|
|
assert update['commitments_secured']
|
2021-02-12 00:01:07 +01:00
|
|
|
|
|
|
|
|
|
|
|
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
|
2021-04-26 21:58:58 +02:00
|
|
|
@pytest.mark.developer("uses dev-disconnect")
|
2021-04-26 21:23:40 +02:00
|
|
|
@pytest.mark.openchannel('v2')
|
2021-02-12 00:01:07 +01:00
|
|
|
def test_rbf_reconnect_tx_sigs(node_factory, bitcoind, chainparams):
|
|
|
|
disconnects = ['=WIRE_TX_SIGNATURES', # Initial funding succeeds
|
|
|
|
'-WIRE_TX_SIGNATURES', # When we send tx-sigs, RBF
|
|
|
|
'=WIRE_TX_SIGNATURES', # When we reconnect
|
|
|
|
'@WIRE_TX_SIGNATURES', # When we RBF again
|
|
|
|
'=WIRE_TX_SIGNATURES', # When we reconnect
|
|
|
|
'+WIRE_TX_SIGNATURES'] # When we RBF again
|
|
|
|
|
|
|
|
l1, l2 = node_factory.get_nodes(2,
|
2021-04-26 21:23:40 +02:00
|
|
|
opts=[{'disconnect': disconnects,
|
2021-03-16 01:50:31 +01:00
|
|
|
'may_reconnect': True},
|
2021-04-26 21:23:40 +02:00
|
|
|
{'may_reconnect': True}])
|
2021-02-12 00:01:07 +01:00
|
|
|
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
amount = 2**24
|
|
|
|
chan_amount = 100000
|
|
|
|
bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'], amount / 10**8 + 0.01)
|
|
|
|
bitcoind.generate_block(1)
|
|
|
|
# Wait for it to arrive.
|
|
|
|
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) > 0)
|
|
|
|
|
|
|
|
res = l1.rpc.fundchannel(l2.info['id'], chan_amount)
|
|
|
|
chan_id = res['channel_id']
|
|
|
|
vins = bitcoind.rpc.decoderawtransaction(res['tx'])['vin']
|
|
|
|
assert(only_one(vins))
|
|
|
|
prev_utxos = ["{}:{}".format(vins[0]['txid'], vins[0]['vout'])]
|
|
|
|
|
|
|
|
# Check that we're waiting for lockin
|
|
|
|
l1.daemon.wait_for_log('Broadcasting funding tx')
|
|
|
|
l1.daemon.wait_for_log(' to DUALOPEND_AWAITING_LOCKIN')
|
|
|
|
|
|
|
|
next_feerate = find_next_feerate(l1, l2)
|
|
|
|
|
|
|
|
# Initiate an RBF
|
|
|
|
startweight = 42 + 172 # base weight, funding output
|
|
|
|
initpsbt = l1.rpc.utxopsbt(chan_amount, next_feerate, startweight,
|
|
|
|
prev_utxos, reservedok=True,
|
|
|
|
min_witness_weight=110,
|
|
|
|
excess_as_change=True)
|
|
|
|
|
|
|
|
bump = l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt'])
|
|
|
|
update = l1.rpc.openchannel_update(chan_id, bump['psbt'])
|
|
|
|
|
|
|
|
# Sign our inputs, and continue
|
|
|
|
signed_psbt = l1.rpc.signpsbt(update['psbt'])['signed_psbt']
|
|
|
|
|
|
|
|
# First time we error when we send our sigs
|
|
|
|
with pytest.raises(RpcError, match='Owning subdaemon dualopend died'):
|
|
|
|
l1.rpc.openchannel_signed(chan_id, signed_psbt)
|
|
|
|
|
|
|
|
# We reconnect and try again. feerate should have bumped
|
|
|
|
next_feerate = find_next_feerate(l1, l2)
|
|
|
|
|
|
|
|
# Initiate an RBF
|
|
|
|
startweight = 42 + 172 # base weight, funding output
|
|
|
|
initpsbt = l1.rpc.utxopsbt(chan_amount, next_feerate, startweight,
|
|
|
|
prev_utxos, reservedok=True,
|
|
|
|
min_witness_weight=110,
|
|
|
|
excess_as_change=True)
|
|
|
|
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
|
|
|
|
# l2 gets our sigs and broadcasts them
|
|
|
|
l2.daemon.wait_for_log('peer_in WIRE_CHANNEL_REESTABLISH')
|
|
|
|
l2.daemon.wait_for_log('peer_in WIRE_TX_SIGNATURES')
|
|
|
|
l2.daemon.wait_for_log('sendrawtx exit 0')
|
|
|
|
|
|
|
|
# Wait until we've done re-establish, if we try to
|
|
|
|
# RBF again too quickly, it'll fail since they haven't
|
|
|
|
# had time to process our sigs yet
|
|
|
|
l1.daemon.wait_for_log('peer_in WIRE_CHANNEL_REESTABLISH')
|
|
|
|
l1.daemon.wait_for_log('peer_in WIRE_TX_SIGNATURES')
|
|
|
|
|
|
|
|
# Now we initiate the RBF
|
|
|
|
bump = l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt'])
|
|
|
|
update = l1.rpc.openchannel_update(chan_id, bump['psbt'])
|
|
|
|
|
|
|
|
# Sign our inputs, and continue
|
|
|
|
signed_psbt = l1.rpc.signpsbt(update['psbt'])['signed_psbt']
|
|
|
|
|
|
|
|
# Second time we error after we send our sigs
|
|
|
|
with pytest.raises(RpcError, match='Owning subdaemon dualopend died'):
|
|
|
|
l1.rpc.openchannel_signed(chan_id, signed_psbt)
|
|
|
|
|
|
|
|
# We reconnect and try again. feerate should have bumped
|
|
|
|
next_feerate = find_next_feerate(l1, l2)
|
|
|
|
|
|
|
|
startweight = 42 + 172 # base weight, funding output
|
|
|
|
initpsbt = l1.rpc.utxopsbt(chan_amount, next_feerate, startweight,
|
|
|
|
prev_utxos, reservedok=True,
|
|
|
|
min_witness_weight=110,
|
|
|
|
excess_as_change=True)
|
|
|
|
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
|
|
|
|
# l2 gets our sigs and broadcasts them
|
|
|
|
l2.daemon.wait_for_log('peer_in WIRE_CHANNEL_REESTABLISH')
|
|
|
|
l2.daemon.wait_for_log('peer_in WIRE_TX_SIGNATURES')
|
|
|
|
l2.daemon.wait_for_log('sendrawtx exit 0')
|
|
|
|
|
|
|
|
# Wait until we've done re-establish, if we try to
|
|
|
|
# RBF again too quickly, it'll fail since they haven't
|
|
|
|
# had time to process our sigs yet
|
|
|
|
l1.daemon.wait_for_log('peer_in WIRE_CHANNEL_REESTABLISH')
|
|
|
|
l1.daemon.wait_for_log('peer_in WIRE_TX_SIGNATURES')
|
|
|
|
|
|
|
|
# 3rd RBF
|
|
|
|
bump = l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt'])
|
|
|
|
update = l1.rpc.openchannel_update(chan_id, bump['psbt'])
|
|
|
|
signed_psbt = l1.rpc.signpsbt(update['psbt'])['signed_psbt']
|
|
|
|
|
|
|
|
# Third time we error after we send our sigs
|
|
|
|
with pytest.raises(RpcError, match='Owning subdaemon dualopend died'):
|
|
|
|
l1.rpc.openchannel_signed(chan_id, signed_psbt)
|
|
|
|
|
|
|
|
# l2 gets our sigs
|
|
|
|
l2.daemon.wait_for_log('peer_in WIRE_TX_SIGNATURES')
|
|
|
|
l2.daemon.wait_for_log('sendrawtx exit 0')
|
|
|
|
|
|
|
|
# mine a block?
|
|
|
|
bitcoind.generate_block(1)
|
|
|
|
sync_blockheight(bitcoind, [l1])
|
|
|
|
l1.daemon.wait_for_log(' to CHANNELD_NORMAL')
|
|
|
|
|
|
|
|
# Check that they have matching funding txid
|
|
|
|
l1_funding_txid = only_one(only_one(l1.rpc.listpeers()['peers'])['channels'])['funding_txid']
|
|
|
|
l2_funding_txid = only_one(only_one(l2.rpc.listpeers()['peers'])['channels'])['funding_txid']
|
|
|
|
assert l1_funding_txid == l2_funding_txid
|
2021-02-12 00:14:26 +01:00
|
|
|
|
|
|
|
|
|
|
|
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
|
2021-04-26 21:23:40 +02:00
|
|
|
@pytest.mark.openchannel('v2')
|
2021-02-12 00:14:26 +01:00
|
|
|
def test_rbf_no_overlap(node_factory, bitcoind, chainparams):
|
|
|
|
l1, l2 = node_factory.get_nodes(2,
|
2021-04-26 21:23:40 +02:00
|
|
|
opts={'allow_warning': True})
|
2021-02-12 00:14:26 +01:00
|
|
|
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
amount = 2**24
|
|
|
|
chan_amount = 100000
|
|
|
|
bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'], amount / 10**8 + 0.01)
|
|
|
|
bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'], amount / 10**8 + 0.01)
|
|
|
|
bitcoind.generate_block(1)
|
|
|
|
# Wait for it to arrive.
|
|
|
|
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) > 0)
|
|
|
|
|
|
|
|
res = l1.rpc.fundchannel(l2.info['id'], chan_amount)
|
|
|
|
chan_id = res['channel_id']
|
|
|
|
|
|
|
|
# Check that we're waiting for lockin
|
|
|
|
l1.daemon.wait_for_log(' to DUALOPEND_AWAITING_LOCKIN')
|
|
|
|
|
|
|
|
next_feerate = find_next_feerate(l1, l2)
|
|
|
|
|
2021-05-07 21:50:17 +02:00
|
|
|
# Initiate an RBF (this grabs the non-reserved utxo, which isnt the
|
|
|
|
# one we started with)
|
2021-02-12 00:14:26 +01:00
|
|
|
startweight = 42 + 172 # base weight, funding output
|
|
|
|
initpsbt = l1.rpc.fundpsbt(chan_amount, next_feerate, startweight,
|
|
|
|
min_witness_weight=110,
|
|
|
|
excess_as_change=True)
|
|
|
|
|
|
|
|
# Do the bump
|
|
|
|
bump = l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt'])
|
|
|
|
|
|
|
|
with pytest.raises(RpcError, match='No overlapping input present.'):
|
|
|
|
l1.rpc.openchannel_update(chan_id, bump['psbt'])
|
2021-04-27 23:12:14 +02:00
|
|
|
|
|
|
|
|
2021-05-07 21:31:45 +02:00
|
|
|
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
|
|
|
|
@pytest.mark.openchannel('v2')
|
2021-05-20 02:16:49 +02:00
|
|
|
@pytest.mark.developer("uses dev-sign-last-tx")
|
2021-05-07 21:31:45 +02:00
|
|
|
def test_rbf_fails_to_broadcast(node_factory, bitcoind, chainparams):
|
|
|
|
l1, l2 = node_factory.get_nodes(2,
|
2021-05-19 23:51:56 +02:00
|
|
|
opts={'allow_warning': True,
|
|
|
|
'may_reconnect': True})
|
2021-05-07 21:31:45 +02:00
|
|
|
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
amount = 2**24
|
|
|
|
chan_amount = 100000
|
|
|
|
bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'], amount / 10**8 + 0.01)
|
|
|
|
bitcoind.generate_block(1)
|
|
|
|
# Wait for it to arrive.
|
|
|
|
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) > 0)
|
|
|
|
|
|
|
|
# Really low feerate means that the bump wont work the first time
|
|
|
|
res = l1.rpc.fundchannel(l2.info['id'], chan_amount, feerate='253perkw')
|
|
|
|
chan_id = res['channel_id']
|
|
|
|
vins = bitcoind.rpc.decoderawtransaction(res['tx'])['vin']
|
|
|
|
assert(only_one(vins))
|
|
|
|
prev_utxos = ["{}:{}".format(vins[0]['txid'], vins[0]['vout'])]
|
|
|
|
|
|
|
|
# Check that we're waiting for lockin
|
|
|
|
l1.daemon.wait_for_log(' to DUALOPEND_AWAITING_LOCKIN')
|
2021-05-19 23:51:56 +02:00
|
|
|
inflights = only_one(only_one(l1.rpc.listpeers()['peers'])['channels'])['inflight']
|
|
|
|
assert inflights[-1]['funding_txid'] in bitcoind.rpc.getrawmempool()
|
2021-05-07 21:31:45 +02:00
|
|
|
|
2021-05-19 23:51:56 +02:00
|
|
|
def run_retry():
|
|
|
|
startweight = 42 + 173
|
|
|
|
next_feerate = find_next_feerate(l1, l2)
|
|
|
|
initpsbt = l1.rpc.utxopsbt(chan_amount, next_feerate, startweight,
|
|
|
|
prev_utxos, reservedok=True,
|
|
|
|
min_witness_weight=110,
|
|
|
|
excess_as_change=True)
|
2021-05-07 21:31:45 +02:00
|
|
|
|
2021-05-19 23:51:56 +02:00
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
bump = l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt'])
|
2021-05-20 02:16:49 +02:00
|
|
|
# We should be able to call this with while an open is progress
|
|
|
|
# but not yet committed
|
|
|
|
l1.rpc.dev_sign_last_tx(l2.info['id'])
|
2021-05-19 23:51:56 +02:00
|
|
|
update = l1.rpc.openchannel_update(chan_id, bump['psbt'])
|
|
|
|
assert update['commitments_secured']
|
2021-05-07 21:31:45 +02:00
|
|
|
|
2021-05-19 23:51:56 +02:00
|
|
|
return l1.rpc.signpsbt(update['psbt'])['signed_psbt']
|
2021-05-07 21:31:45 +02:00
|
|
|
|
2021-05-19 23:51:56 +02:00
|
|
|
signed_psbt = run_retry()
|
2021-05-07 21:31:45 +02:00
|
|
|
with pytest.raises(RpcError, match=r'insufficient fee, rejecting replacement'):
|
|
|
|
l1.rpc.openchannel_signed(chan_id, signed_psbt)
|
2021-05-19 23:51:56 +02:00
|
|
|
inflights = only_one(only_one(l1.rpc.listpeers()['peers'])['channels'])['inflight']
|
|
|
|
assert inflights[-1]['funding_txid'] not in bitcoind.rpc.getrawmempool()
|
2021-05-07 21:31:45 +02:00
|
|
|
|
|
|
|
# If we restart and listpeers, it will crash
|
|
|
|
l1.restart()
|
|
|
|
|
|
|
|
l1.rpc.listpeers()
|
|
|
|
|
2021-05-19 23:51:56 +02:00
|
|
|
# We've restarted. Let's RBF until we successfully get a
|
|
|
|
# new funding transaction for this channel
|
|
|
|
# do it again
|
|
|
|
signed_psbt = run_retry()
|
|
|
|
with pytest.raises(RpcError, match=r'insufficient fee, rejecting replacement'):
|
|
|
|
l1.rpc.openchannel_signed(chan_id, signed_psbt)
|
|
|
|
inflights = only_one(only_one(l1.rpc.listpeers()['peers'])['channels'])['inflight']
|
|
|
|
assert inflights[-1]['funding_txid'] not in bitcoind.rpc.getrawmempool()
|
|
|
|
|
|
|
|
# and again
|
|
|
|
signed_psbt = run_retry()
|
|
|
|
with pytest.raises(RpcError, match=r'insufficient fee, rejecting replacement'):
|
|
|
|
l1.rpc.openchannel_signed(chan_id, signed_psbt)
|
|
|
|
inflights = only_one(only_one(l1.rpc.listpeers()['peers'])['channels'])['inflight']
|
|
|
|
assert inflights[-1]['funding_txid'] not in bitcoind.rpc.getrawmempool()
|
|
|
|
|
|
|
|
# now we should be ok
|
|
|
|
signed_psbt = run_retry()
|
|
|
|
l1.rpc.openchannel_signed(chan_id, signed_psbt)
|
|
|
|
inflights = only_one(only_one(l1.rpc.listpeers()['peers'])['channels'])['inflight']
|
|
|
|
assert len(inflights) == 5
|
|
|
|
assert inflights[-1]['funding_txid'] in bitcoind.rpc.getrawmempool()
|
|
|
|
|
|
|
|
l1.restart()
|
|
|
|
|
|
|
|
# Are inflights the same post restart
|
|
|
|
prev_inflights = inflights
|
|
|
|
inflights = only_one(only_one(l1.rpc.listpeers()['peers'])['channels'])['inflight']
|
|
|
|
assert prev_inflights == inflights
|
|
|
|
assert inflights[-1]['funding_txid'] in bitcoind.rpc.getrawmempool()
|
|
|
|
|
2021-05-20 02:16:49 +02:00
|
|
|
# Produce a signature for every inflight
|
|
|
|
last_txs = l1.rpc.dev_sign_last_tx(l2.info['id'])
|
|
|
|
assert len(last_txs['inflights']) == len(inflights)
|
|
|
|
for last_tx, inflight in zip(last_txs['inflights'], inflights):
|
|
|
|
assert last_tx['funding_txid'] == inflight['funding_txid']
|
|
|
|
assert last_txs['tx']
|
|
|
|
|
2021-05-07 21:31:45 +02:00
|
|
|
|
2021-05-20 02:18:39 +02:00
|
|
|
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
|
|
|
|
@pytest.mark.openchannel('v2')
|
|
|
|
def test_rbf_broadcast_close_inflights(node_factory, bitcoind, chainparams):
|
|
|
|
"""
|
|
|
|
Close a channel before it's mined, and the most recent transaction
|
|
|
|
hasn't made it to the mempool. Should publish all the commitment
|
|
|
|
transactions that we have.
|
|
|
|
"""
|
|
|
|
l1, l2 = node_factory.get_nodes(2,
|
|
|
|
opts={'allow_warning': True})
|
|
|
|
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
amount = 2**24
|
|
|
|
chan_amount = 100000
|
|
|
|
bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'], amount / 10**8 + 0.01)
|
|
|
|
bitcoind.generate_block(1)
|
|
|
|
# Wait for it to arrive.
|
|
|
|
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) > 0)
|
|
|
|
|
|
|
|
res = l1.rpc.fundchannel(l2.info['id'], chan_amount, feerate='7500perkw')
|
|
|
|
chan_id = res['channel_id']
|
|
|
|
vins = bitcoind.rpc.decoderawtransaction(res['tx'])['vin']
|
|
|
|
assert(only_one(vins))
|
|
|
|
prev_utxos = ["{}:{}".format(vins[0]['txid'], vins[0]['vout'])]
|
|
|
|
|
|
|
|
# Check that we're waiting for lockin
|
|
|
|
l1.daemon.wait_for_log(' to DUALOPEND_AWAITING_LOCKIN')
|
|
|
|
inflights = only_one(only_one(l1.rpc.listpeers()['peers'])['channels'])['inflight']
|
|
|
|
assert inflights[-1]['funding_txid'] in bitcoind.rpc.getrawmempool()
|
|
|
|
|
|
|
|
# Make it such that l1 and l2 cannot broadcast transactions
|
|
|
|
# (mimics failing to reach the miner with replacement)
|
|
|
|
def censoring_sendrawtx(r):
|
|
|
|
return {'id': r['id'], 'result': {}}
|
|
|
|
|
|
|
|
l1.daemon.rpcproxy.mock_rpc('sendrawtransaction', censoring_sendrawtx)
|
|
|
|
l2.daemon.rpcproxy.mock_rpc('sendrawtransaction', censoring_sendrawtx)
|
|
|
|
|
|
|
|
def run_retry():
|
|
|
|
startweight = 42 + 173
|
|
|
|
next_feerate = find_next_feerate(l1, l2)
|
|
|
|
initpsbt = l1.rpc.utxopsbt(chan_amount, next_feerate, startweight,
|
|
|
|
prev_utxos, reservedok=True,
|
|
|
|
min_witness_weight=110,
|
|
|
|
excess_as_change=True)
|
|
|
|
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
bump = l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt'])
|
|
|
|
update = l1.rpc.openchannel_update(chan_id, bump['psbt'])
|
|
|
|
assert update['commitments_secured']
|
|
|
|
|
|
|
|
return l1.rpc.signpsbt(update['psbt'])['signed_psbt']
|
|
|
|
|
|
|
|
signed_psbt = run_retry()
|
|
|
|
l1.rpc.openchannel_signed(chan_id, signed_psbt)
|
|
|
|
inflights = only_one(only_one(l1.rpc.listpeers()['peers'])['channels'])['inflight']
|
|
|
|
assert inflights[-1]['funding_txid'] not in bitcoind.rpc.getrawmempool()
|
|
|
|
|
|
|
|
cmtmt_txid = only_one(only_one(l1.rpc.listpeers()['peers'])['channels'])['scratch_txid']
|
|
|
|
assert cmtmt_txid == inflights[-1]['scratch_txid']
|
|
|
|
|
|
|
|
# l2 goes offline
|
|
|
|
l2.stop()
|
|
|
|
|
|
|
|
# l1 drops to chain.
|
|
|
|
l1.daemon.rpcproxy.mock_rpc('sendrawtransaction', None)
|
|
|
|
l1.rpc.close(chan_id, 1)
|
|
|
|
l1.daemon.wait_for_logs(['Broadcasting txid {}'.format(inflights[0]['scratch_txid']),
|
|
|
|
'Broadcasting txid {}'.format(inflights[1]['scratch_txid']),
|
|
|
|
'sendrawtx exit 0',
|
|
|
|
'sendrawtx exit 25'])
|
|
|
|
assert inflights[0]['scratch_txid'] in bitcoind.rpc.getrawmempool()
|
|
|
|
assert inflights[1]['scratch_txid'] not in bitcoind.rpc.getrawmempool()
|
|
|
|
|
|
|
|
|
2021-05-20 23:55:45 +02:00
|
|
|
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
|
|
|
|
@pytest.mark.openchannel('v2')
|
|
|
|
def test_rbf_non_last_mined(node_factory, bitcoind, chainparams):
|
|
|
|
"""
|
|
|
|
What happens if a 'non-tip' RBF transaction is mined?
|
|
|
|
"""
|
|
|
|
l1, l2 = node_factory.get_nodes(2,
|
|
|
|
opts={'allow_warning': True,
|
|
|
|
'may_reconnect': True})
|
|
|
|
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
amount = 2**24
|
|
|
|
chan_amount = 100000
|
|
|
|
bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'], amount / 10**8 + 0.01)
|
|
|
|
bitcoind.generate_block(1)
|
|
|
|
# Wait for it to arrive.
|
|
|
|
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) > 0)
|
|
|
|
|
|
|
|
res = l1.rpc.fundchannel(l2.info['id'], chan_amount, feerate='7500perkw')
|
|
|
|
chan_id = res['channel_id']
|
|
|
|
vins = bitcoind.rpc.decoderawtransaction(res['tx'])['vin']
|
|
|
|
assert(only_one(vins))
|
|
|
|
prev_utxos = ["{}:{}".format(vins[0]['txid'], vins[0]['vout'])]
|
|
|
|
|
|
|
|
# Check that we're waiting for lockin
|
|
|
|
l1.daemon.wait_for_log(' to DUALOPEND_AWAITING_LOCKIN')
|
|
|
|
inflights = only_one(only_one(l1.rpc.listpeers()['peers'])['channels'])['inflight']
|
|
|
|
assert inflights[-1]['funding_txid'] in bitcoind.rpc.getrawmempool()
|
|
|
|
|
|
|
|
def run_retry():
|
|
|
|
startweight = 42 + 173
|
|
|
|
next_feerate = find_next_feerate(l1, l2)
|
|
|
|
initpsbt = l1.rpc.utxopsbt(chan_amount, next_feerate, startweight,
|
|
|
|
prev_utxos, reservedok=True,
|
|
|
|
min_witness_weight=110,
|
|
|
|
excess_as_change=True)
|
|
|
|
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
bump = l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt['psbt'])
|
|
|
|
update = l1.rpc.openchannel_update(chan_id, bump['psbt'])
|
|
|
|
assert update['commitments_secured']
|
|
|
|
|
|
|
|
return l1.rpc.signpsbt(update['psbt'])['signed_psbt']
|
|
|
|
|
|
|
|
# Make a second inflight
|
|
|
|
signed_psbt = run_retry()
|
|
|
|
l1.rpc.openchannel_signed(chan_id, signed_psbt)
|
|
|
|
|
|
|
|
# Make it such that l1 and l2 cannot broadcast transactions
|
|
|
|
# (mimics failing to reach the miner with replacement)
|
|
|
|
def censoring_sendrawtx(r):
|
|
|
|
return {'id': r['id'], 'result': {}}
|
|
|
|
|
|
|
|
l1.daemon.rpcproxy.mock_rpc('sendrawtransaction', censoring_sendrawtx)
|
|
|
|
l2.daemon.rpcproxy.mock_rpc('sendrawtransaction', censoring_sendrawtx)
|
|
|
|
|
|
|
|
# Make a 3rd inflight that won't make it into the mempool
|
|
|
|
signed_psbt = run_retry()
|
|
|
|
l1.rpc.openchannel_signed(chan_id, signed_psbt)
|
|
|
|
|
|
|
|
l1.daemon.rpcproxy.mock_rpc('sendrawtransaction', None)
|
|
|
|
l2.daemon.rpcproxy.mock_rpc('sendrawtransaction', None)
|
|
|
|
|
|
|
|
# We fetch out our inflights list
|
|
|
|
inflights = only_one(only_one(l1.rpc.listpeers()['peers'])['channels'])['inflight']
|
|
|
|
|
|
|
|
# l2 goes offline
|
|
|
|
l2.stop()
|
|
|
|
|
|
|
|
# The funding transaction gets mined (should be the 2nd inflight)
|
|
|
|
bitcoind.generate_block(6, wait_for_mempool=1)
|
|
|
|
|
|
|
|
# l2 comes back up
|
|
|
|
l2.start()
|
|
|
|
|
|
|
|
# everybody's got the right things now
|
|
|
|
l1.daemon.wait_for_log(r'to CHANNELD_NORMAL')
|
|
|
|
l2.daemon.wait_for_log(r'to CHANNELD_NORMAL')
|
|
|
|
|
|
|
|
channel = only_one(only_one(l1.rpc.listpeers()['peers'])['channels'])
|
|
|
|
assert channel['funding_txid'] == inflights[1]['funding_txid']
|
|
|
|
assert channel['scratch_txid'] == inflights[1]['scratch_txid']
|
|
|
|
|
|
|
|
# We delete inflights when the channel is in normal ops
|
|
|
|
assert 'inflights' not in channel
|
|
|
|
|
|
|
|
# l2 stops, again
|
|
|
|
l2.stop()
|
|
|
|
|
|
|
|
# l1 drops to chain.
|
|
|
|
l1.rpc.close(chan_id, 1)
|
|
|
|
l1.daemon.wait_for_log('Broadcasting txid {}'.format(channel['scratch_txid']))
|
|
|
|
|
|
|
|
# The funding transaction gets mined (should be the 2nd inflight)
|
|
|
|
bitcoind.generate_block(1, wait_for_mempool=1)
|
|
|
|
l1.daemon.wait_for_log(r'to ONCHAIN')
|
|
|
|
|
|
|
|
|
2021-04-27 23:12:14 +02:00
|
|
|
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
|
2021-04-26 21:23:40 +02:00
|
|
|
@pytest.mark.openchannel('v2')
|
2021-04-27 23:12:14 +02:00
|
|
|
def test_funder_options(node_factory, bitcoind):
|
2021-04-26 21:23:40 +02:00
|
|
|
l1, l2, l3 = node_factory.get_nodes(3)
|
2021-04-27 23:12:14 +02:00
|
|
|
l1.fundwallet(10**7)
|
|
|
|
|
|
|
|
# Check the default options
|
|
|
|
funder_opts = l1.rpc.call('funderupdate')
|
|
|
|
|
|
|
|
assert funder_opts['policy'] == 'fixed'
|
|
|
|
assert funder_opts['policy_mod'] == 0
|
|
|
|
assert funder_opts['min_their_funding'] == '10000000msat'
|
|
|
|
assert funder_opts['max_their_funding'] == '4294967295000msat'
|
|
|
|
assert funder_opts['per_channel_min'] == '10000000msat'
|
|
|
|
assert funder_opts['per_channel_max'] == '4294967295000msat'
|
|
|
|
assert funder_opts['reserve_tank'] == '0msat'
|
|
|
|
assert funder_opts['fuzz_percent'] == 5
|
|
|
|
assert funder_opts['fund_probability'] == 100
|
|
|
|
|
|
|
|
# l2 funds a chanenl with us. We don't contribute
|
|
|
|
l2.rpc.connect(l1.info['id'], 'localhost', l1.port)
|
|
|
|
l2.fundchannel(l1, 10**6)
|
|
|
|
chan_info = only_one(only_one(l2.rpc.listpeers(l1.info['id'])['peers'])['channels'])
|
|
|
|
# l1 contributed nothing
|
|
|
|
assert chan_info['funding_msat'][l1.info['id']] == '0msat'
|
|
|
|
|
|
|
|
# Change all the options
|
|
|
|
funder_opts = l1.rpc.call('funderupdate',
|
|
|
|
{'policy': 'available',
|
|
|
|
'policy_mod': 100,
|
|
|
|
'min_their_funding': '100000msat',
|
|
|
|
'max_their_funding': '2000000000msat',
|
|
|
|
'per_channel_min': '8000000msat',
|
|
|
|
'per_channel_max': '10000000000msat',
|
|
|
|
'reserve_tank': '3000000msat',
|
|
|
|
'fund_probability': 99,
|
|
|
|
'fuzz_percent': 0})
|
|
|
|
|
|
|
|
assert funder_opts['policy'] == 'available'
|
|
|
|
assert funder_opts['policy_mod'] == 100
|
|
|
|
assert funder_opts['min_their_funding'] == '100000msat'
|
|
|
|
assert funder_opts['max_their_funding'] == '2000000000msat'
|
|
|
|
assert funder_opts['per_channel_min'] == '8000000msat'
|
|
|
|
assert funder_opts['per_channel_max'] == '10000000000msat'
|
|
|
|
assert funder_opts['reserve_tank'] == '3000000msat'
|
|
|
|
assert funder_opts['fuzz_percent'] == 0
|
|
|
|
assert funder_opts['fund_probability'] == 99
|
|
|
|
|
|
|
|
# Set the fund probability back up to 100.
|
|
|
|
funder_opts = l1.rpc.call('funderupdate',
|
|
|
|
{'fund_probability': 100})
|
|
|
|
l3.rpc.connect(l1.info['id'], 'localhost', l1.port)
|
|
|
|
l3.fundchannel(l1, 10**6)
|
|
|
|
chan_info = only_one(only_one(l3.rpc.listpeers(l1.info['id'])['peers'])['channels'])
|
|
|
|
# l1 contributed everything
|
|
|
|
assert chan_info['funding_msat'][l1.info['id']] != '0msat'
|
2021-05-05 01:53:24 +02:00
|
|
|
|
|
|
|
|
|
|
|
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
|
|
|
|
def test_funder_contribution_limits(node_factory, bitcoind):
|
|
|
|
opts = {'experimental-dual-fund': None,
|
|
|
|
'feerates': (5000, 5000, 5000, 5000)}
|
|
|
|
l1, l2, l3 = node_factory.get_nodes(3, opts=opts)
|
|
|
|
|
|
|
|
l1.fundwallet(10**8)
|
|
|
|
|
|
|
|
# Give l2 lots of utxos
|
|
|
|
l2.fundwallet(10**3) # this one is too small to add
|
|
|
|
l2.fundwallet(10**5)
|
|
|
|
l2.fundwallet(10**4)
|
|
|
|
l2.fundwallet(10**4)
|
|
|
|
l2.fundwallet(10**4)
|
|
|
|
l2.fundwallet(10**4)
|
|
|
|
l2.fundwallet(10**4)
|
|
|
|
|
|
|
|
# Give l3 lots of utxos
|
|
|
|
l3.fundwallet(10**3) # this one is too small to add
|
|
|
|
l3.fundwallet(10**4)
|
|
|
|
l3.fundwallet(10**4)
|
|
|
|
l3.fundwallet(10**4)
|
|
|
|
l3.fundwallet(10**4)
|
|
|
|
l3.fundwallet(10**4)
|
|
|
|
l3.fundwallet(10**4)
|
|
|
|
l3.fundwallet(10**4)
|
|
|
|
l3.fundwallet(10**4)
|
|
|
|
l3.fundwallet(10**4)
|
|
|
|
l3.fundwallet(10**4)
|
|
|
|
l3.fundwallet(10**4)
|
|
|
|
|
|
|
|
# Contribute 100% of available funds to l2, all 6 utxos (smallest utxo
|
|
|
|
# 10**3 is left out)
|
|
|
|
l2.rpc.call('funderupdate',
|
|
|
|
{'policy': 'available',
|
|
|
|
'policy_mod': 100,
|
|
|
|
'min_their_funding': '1000msat',
|
|
|
|
'per_channel_min': '1000000msat',
|
|
|
|
'fund_probability': 100,
|
|
|
|
'fuzz_percent': 0})
|
|
|
|
|
|
|
|
# Set our contribution to 50k sat, should only use 7 of 12 available utxos
|
|
|
|
l3.rpc.call('funderupdate',
|
|
|
|
{'policy': 'fixed',
|
|
|
|
'policy_mod': '50000sat',
|
|
|
|
'min_their_funding': '1000msat',
|
|
|
|
'per_channel_min': '1000sat',
|
|
|
|
'per_channel_max': '500000sat',
|
|
|
|
'fund_probability': 100,
|
|
|
|
'fuzz_percent': 0})
|
|
|
|
|
|
|
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
|
|
|
l1.fundchannel(l2, 10**7)
|
|
|
|
assert l2.daemon.is_in_log('Policy .* returned funding amount of 139020sat')
|
|
|
|
assert l2.daemon.is_in_log(r'calling `signpsbt` .* 6 inputs')
|
|
|
|
|
|
|
|
l1.rpc.connect(l3.info['id'], 'localhost', l3.port)
|
|
|
|
l1.fundchannel(l3, 10**7)
|
|
|
|
assert l3.daemon.is_in_log('Policy .* returned funding amount of 50000sat')
|
|
|
|
assert l3.daemon.is_in_log(r'calling `signpsbt` .* 7 inputs')
|