diff --git a/doc/PLUGINS.md b/doc/PLUGINS.md index 5f824552e..fc0102452 100644 --- a/doc/PLUGINS.md +++ b/doc/PLUGINS.md @@ -409,7 +409,7 @@ if the funding transaction has been included into a block. A notification to indicate that a channel open attempt has been unsuccessful. Useful for cleaning up state for a v2 channel open attempt. See -`tests/plugins/df_accepter.py` for an example of how to use this. +`plugins/funder.c` for an example of how to use this. ```json { @@ -1122,7 +1122,7 @@ Note that, like `openchannel_init` RPC call, the `our_funding_msat` amount must NOT be accounted for in any supplied output. Change, however, should be included and should use the `funding_feerate_per_kw` to calculate. -See `tests/plugins/df_accepter.py` for an example of how to use this hook +See `plugins/funder.c` for an example of how to use this hook to contribute funds to a channel open. e.g. @@ -1171,7 +1171,7 @@ negotation will end and commitment transactions will be exchanged. } ``` -See `tests/plugins/df_accepter.py` for an example of how to use this hook +See `plugins/funder.c` for an example of how to use this hook to continue a v2 channel open. @@ -1205,7 +1205,7 @@ broadcast. } ``` -See `tests/plugins/df_accepter.py` for an example of how to use this hook +See `plugins/funder.c` for an example of how to use this hook to sign a funding transaction. diff --git a/tests/plugins/df_accepter.py b/tests/plugins/df_accepter.py deleted file mode 100755 index 77844da21..000000000 --- a/tests/plugins/df_accepter.py +++ /dev/null @@ -1,158 +0,0 @@ -#!/usr/bin/env python3 -"""Test plugin for adding inputs/outputs to a dual-funded transaction -""" - -from pyln.client import Plugin, Millisatoshi -from wallycore import ( - psbt_find_input_unknown, - psbt_from_base64, - psbt_get_input_unknown, - psbt_get_num_inputs, -) - -plugin = Plugin() - - -def find_inputs(b64_psbt): - serial_id_key = bytes.fromhex('fc096c696768746e696e6701') - psbt = psbt_from_base64(b64_psbt) - input_idxs = [] - - for i in range(psbt_get_num_inputs(psbt)): - idx = psbt_find_input_unknown(psbt, i, serial_id_key) - if idx == 0: - continue - # returned index is off by one, so 0 can be 'not found' - serial_bytes = psbt_get_input_unknown(psbt, i, idx - 1) - serial_id = int.from_bytes(serial_bytes, byteorder='big', signed=False) - - # We're the accepter, so our inputs have odd serials - if serial_id % 2: - input_idxs.append(i) - - return input_idxs - - -@plugin.init() -def init(configuration, options, plugin): - # this is the max channel size, pre-wumbo - plugin.max_fund = Millisatoshi((2 ** 24 - 1) * 1000) - plugin.inflight = {} - plugin.log('max funding set to {}'.format(plugin.max_fund)) - - -@plugin.method("setacceptfundingmax") -def set_accept_funding_max(plugin, max_sats, **kwargs): - plugin.max_fund = Millisatoshi(max_sats) - - return {'accepter_max_funding': plugin.max_fund} - - -def add_inflight(plugin, peerid, chanid, psbt): - if peerid in plugin.inflight: - chans = plugin.inflight[peerid] - else: - chans = {} - plugin.inflight[peerid] = chans - - if chanid in chans: - raise ValueError("channel {} already in flight (peer {})".format(chanid, peerid)) - chans[chanid] = psbt - - -def cleanup_inflight(plugin, chanid): - for peer, chans in plugin.inflight.items(): - if chanid in chans: - psbt = chans[chanid] - del chans[chanid] - return psbt - return None - - -def cleanup_inflight_peer(plugin, peerid): - if peerid in plugin.inflight: - chans = plugin.inflight[peerid] - for chanid, psbt in chans.items(): - plugin.rpc.unreserveinputs(psbt) - del plugin.inflight[peerid] - - -@plugin.hook('openchannel2') -def on_openchannel(openchannel2, plugin, **kwargs): - # We mirror what the peer does, wrt to funding amount ... - amount = Millisatoshi(openchannel2['their_funding']) - locktime = openchannel2['locktime'] - - if amount > plugin.max_fund: - plugin.log("amount adjusted from {} to {}".format(amount, plugin.max_fund)) - amount = plugin.max_fund - - if amount == 0: - plugin.log("accepter_max_funding set to zero") - return {'result': 'continue'} - - # ...unless they send us totally unacceptable feerates. - proposed_feerate = openchannel2['funding_feerate_per_kw'] - our_min = openchannel2['feerate_our_min'] - our_max = openchannel2['feerate_our_max'] - - # Their feerate range is out of bounds, we're not going to - # participate. - if proposed_feerate > our_max or proposed_feerate < our_min: - plugin.log("Declining to fund, feerate unacceptable.") - return {'result': 'continue'} - - funding = plugin.rpc.fundpsbt(int(amount.to_satoshi()), - '{}perkw'.format(proposed_feerate), - 0, # because we're the accepter!! - reserve=True, - locktime=locktime, - minconf=0, - min_witness_weight=110, - excess_as_change=True) - add_inflight(plugin, openchannel2['id'], - openchannel2['channel_id'], funding['psbt']) - plugin.log("contributing {} at feerate {}".format(amount, proposed_feerate)) - - return {'result': 'continue', 'psbt': funding['psbt'], - 'our_funding_msat': amount} - - -@plugin.hook('openchannel2_changed') -def on_tx_changed(openchannel2_changed, plugin, **kwargs): - # In this example, we have nothing to add, so we - # pass back the same psbt that was forwarded in here - return {'result': 'continue', 'psbt': openchannel2_changed['psbt']} - - -@plugin.hook('openchannel2_sign') -def on_tx_sign(openchannel2_sign, plugin, **kwargs): - psbt = openchannel2_sign['psbt'] - - # We only sign the ones with our parity of a serial_id - input_idxs = find_inputs(psbt) - if len(input_idxs) > 0: - final_psbt = plugin.rpc.signpsbt(psbt, signonly=input_idxs)['signed_psbt'] - else: - final_psbt = psbt - - cleanup_inflight(plugin, openchannel2_sign['channel_id']) - return {'result': 'continue', 'psbt': final_psbt} - - -@plugin.subscribe("channel_open_failed") -def on_open_failed(channel_open_failed, plugin, **kwargs): - channel_id = channel_open_failed['channel_id'] - psbt = cleanup_inflight(plugin, channel_id) - if psbt: - plugin.log("failed to open channel {}, unreserving".format(channel_id)) - plugin.rpc.unreserveinputs(psbt) - - -@plugin.subscribe("disconnect") -def on_peer_disconnect(id, plugin, **kwargs): - plugin.log("peer {} disconnected, removing inflights".format(id)) - cleanup_inflight_peer(plugin, id) - - -plugin.run() diff --git a/tests/test_connection.py b/tests/test_connection.py index 6d9c48ad7..e9b1e13e8 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -356,11 +356,11 @@ def test_disconnect_fundee_v2(node_factory): '@WIRE_TX_COMPLETE', '+WIRE_TX_COMPLETE'] - accepter_plugin = os.path.join(os.path.dirname(__file__), - 'plugins/df_accepter.py') l1 = node_factory.get_node(options={'experimental-dual-fund': None}) l2 = node_factory.get_node(disconnect=disconnects, - options={'plugin': accepter_plugin, + options={'funder-policy': 'match', + 'funder-policy-mod': 100, + 'funder-fuzz-percent': 0, 'experimental-dual-fund': None}) l1.fundwallet(2000000) @@ -1399,16 +1399,18 @@ def test_funding_external_wallet(node_factory, bitcoind): @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') -def test_multifunding_v2_v1_mixed(node_factory, bitcoind): +def test_multifunding_v1_v2_mixed(node_factory, bitcoind): ''' Simple test for multifundchannel, using v1 + v2 ''' - accepter_plugin = os.path.join(os.path.dirname(__file__), - 'plugins/df_accepter.py') options = [{'experimental-dual-fund': None}, - {'plugin': accepter_plugin, + {'funder-policy': 'match', + 'funder-policy-mod': 100, + 'funder-fuzz-percent': 0, 'experimental-dual-fund': None}, - {'plugin': accepter_plugin, + {'funder-policy': 'match', + 'funder-policy-mod': 100, + 'funder-fuzz-percent': 0, 'experimental-dual-fund': None}, {}] @@ -1441,12 +1443,16 @@ def test_multifunding_v2_exclusive(node_factory, bitcoind): ''' Simple test for multifundchannel, using v2 ''' - accepter_plugin = os.path.join(os.path.dirname(__file__), - 'plugins/df_accepter.py') # Two of three will reply with inputs of their own options = [{'experimental-dual-fund': None}, - {'plugin': accepter_plugin, 'experimental-dual-fund': None}, - {'plugin': accepter_plugin, 'experimental-dual-fund': None}, + {'funder-policy': 'match', + 'funder-policy-mod': 100, + 'funder-fuzz-percent': 0, + 'experimental-dual-fund': None}, + {'funder-policy': 'match', + 'funder-policy-mod': 100, + 'funder-fuzz-percent': 0, + 'experimental-dual-fund': None}, {'experimental-dual-fund': None}] l1, l2, l3, l4 = node_factory.get_nodes(4, opts=options) diff --git a/tests/test_pay.py b/tests/test_pay.py index 0513a3f90..c46341a3f 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -3562,9 +3562,10 @@ def test_mpp_interference_2(node_factory, bitcoind, executor): if EXPERIMENTAL_DUAL_FUND: # fundbalancedchannel doesn't work for opt_dual_fund # because we've removed push_msat - accepter_plugin = os.path.join(os.path.dirname(__file__), - 'plugins/df_accepter.py') - opts['plugin'] = accepter_plugin + opts['experimental-dual-fund'] = None + opts['funder-policy'] = 'match' + opts['funder-policy-mod'] = 100 + opts['funder-fuzz-percent'] = 0 l1, l2, l3, l4, l5, l6, l7 = node_factory.get_nodes(7, opts=opts) @@ -3588,7 +3589,7 @@ def test_mpp_interference_2(node_factory, bitcoind, executor): # so that we can fund channels without making them balanced if EXPERIMENTAL_DUAL_FUND: for n in [l1, l2, l3, l4, l5, l6, l7]: - n.rpc.setacceptfundingmax('0msat') + n.rpc.call('funderupdate', {'fund_probability': 0}) # The order in which the routes are built should not matter so # shuffle them. @@ -3692,9 +3693,10 @@ def test_mpp_overload_payee(node_factory, bitcoind): if EXPERIMENTAL_DUAL_FUND: # fundbalancedchannel doesn't work for opt_dual_fund # because we've removed push_msat - accepter_plugin = os.path.join(os.path.dirname(__file__), - 'plugins/df_accepter.py') - opts['plugin'] = accepter_plugin + opts['experimental-dual-fund'] = None + opts['funder-policy'] = 'match' + opts['funder-policy-mod'] = 100 + opts['funder-fuzz-percent'] = 0 l1, l2, l3, l4, l5, l6 = node_factory.get_nodes(6, opts=opts)