def test_open_channel_bad_type(runner: Runner, with_proposal: Any) -> None: """Tests for https://github.com/lightningnetwork/lightning-rfc/pull/880""" with_proposal(channel_type_csv) # This is not a feature bit, so use support_ to mark it. if runner.has_option('supports_open_accept_channel_types') is None: pytest.skip('Needs supports_open_accept_channel_types') local_funding_privkey = '20' local_keyset = KeySet(revocation_base_secret='21', payment_base_secret='22', htlc_base_secret='24', delayed_payment_base_secret='23', shachain_seed='00' * 32) test = [ Block(blockheight=102, txs=[tx_spendable]), Connect(connprivkey='02'), ExpectMsg('init'), TryAll( # BOLT-a12da24dd0102c170365124782b46d9710950ac1 #9: # | 20/21 | `option_anchor_outputs` | Anchor outputs Msg('init', globalfeatures='', features=bitfield(13, 21)), # BOLT #9: # | 12/13 | `option_static_remotekey` | Static key for remote output Msg('init', globalfeatures='', features=bitfield(13)), # And not. Msg('init', globalfeatures='', features='')), Msg( 'open_channel', chain_hash=regtest_hash, temporary_channel_id='00' * 32, funding_satoshis=funding_amount_for_utxo(0), push_msat=0, dust_limit_satoshis=546, max_htlc_value_in_flight_msat=4294967295, channel_reserve_satoshis=9998, htlc_minimum_msat=0, feerate_per_kw=253, # We use 5, because c-lightning runner uses 6, so this is different. to_self_delay=5, max_accepted_htlcs=483, funding_pubkey=pubkey_of(local_funding_privkey), revocation_basepoint=local_keyset.revocation_basepoint(), payment_basepoint=local_keyset.payment_basepoint(), delayed_payment_basepoint=local_keyset.delayed_payment_basepoint(), htlc_basepoint=local_keyset.htlc_basepoint(), first_per_commitment_point=local_keyset.per_commit_point(0), channel_flags=1, tlvs='{channel_types={types=[{features=' + bitfield(1) + '}]}}'), # BOLT #2 # The receiving node MUST fail the channel if: # - It supports `channel_types` and none of the `channel_types` # are suitable. ExpectError() ] runner.run(test)
def test_query_channel_range(runner: Runner) -> None: if runner.has_option('option_gossip_queries') is None: unittest.SkipTest('Needs option_gossip_queries') funding1, funding1_tx = Funding.from_utxo(*utxo(0), local_node_privkey='02', local_funding_privkey='10', remote_node_privkey='03', remote_funding_privkey='20') funding2, funding2_tx = Funding.from_utxo(*utxo(1), local_node_privkey='04', local_funding_privkey='30', remote_node_privkey='05', remote_funding_privkey='40') timestamp_103x1x0_LOCAL = int(time.time()) timestamp_109x1x0_LOCAL = timestamp_103x1x0_LOCAL - 1 timestamp_109x1x0_REMOTE = timestamp_109x1x0_LOCAL - 1 ts_103x1x0 = encode_timestamps( *funding1.node_id_sort(timestamp_103x1x0_LOCAL, 0)) ts_109x1x0 = encode_timestamps(*funding2.node_id_sort( timestamp_109x1x0_LOCAL, timestamp_109x1x0_REMOTE)) update_103x1x0_LOCAL = funding1.channel_update( side=Side.local, short_channel_id='103x1x0', disable=False, cltv_expiry_delta=144, htlc_minimum_msat=0, fee_base_msat=1000, fee_proportional_millionths=10, timestamp=timestamp_103x1x0_LOCAL, htlc_maximum_msat=None) update_109x1x0_LOCAL = funding2.channel_update( side=Side.local, short_channel_id='109x1x0', disable=False, cltv_expiry_delta=144, htlc_minimum_msat=0, fee_base_msat=1000, fee_proportional_millionths=10, timestamp=timestamp_109x1x0_LOCAL, htlc_maximum_msat=None) update_109x1x0_REMOTE = funding2.channel_update( side=Side.remote, short_channel_id='109x1x0', disable=False, cltv_expiry_delta=144, htlc_minimum_msat=0, fee_base_msat=1000, fee_proportional_millionths=10, timestamp=timestamp_109x1x0_REMOTE, htlc_maximum_msat=None) csums_103x1x0 = update_checksums( *funding1.node_id_sort(update_103x1x0_LOCAL, None)) csums_109x1x0 = update_checksums( *funding2.node_id_sort(update_109x1x0_LOCAL, update_109x1x0_REMOTE)) test = [ Block(blockheight=102, txs=[tx_spendable]), # Channel 103x1x0 (between 002 and 003) Block(blockheight=103, number=6, txs=[funding1_tx]), # Channel 109x1x0 (between 004 and 005) Block(blockheight=109, number=6, txs=[funding2_tx]), Connect(connprivkey='03'), ExpectMsg('init'), Msg('init', globalfeatures='', features=''), RawMsg(funding1.channel_announcement('103x1x0', '')), RawMsg(update_103x1x0_LOCAL), RawMsg(funding2.channel_announcement('109x1x0', '')), RawMsg(update_109x1x0_LOCAL), RawMsg(update_109x1x0_REMOTE), # c-lightning gets a race condition if we dont wait for # these updates to be added to the gossip store # FIXME: convert to explicit signal Wait(0.7), # New peer connects, with gossip_query option. Connect(connprivkey='05'), ExpectMsg('init'), # BOLT #9: # | 6/7 | `gossip_queries` | More sophisticated gossip control Msg('init', globalfeatures='', features=bitfield(7)), TryAll( # No queries? Must not get anything. [ MustNotMsg('channel_announcement'), MustNotMsg('channel_update'), MustNotMsg('node_announcement') ], # This should elicit an empty response [ Msg('query_channel_range', chain_hash=regtest_hash, first_blocknum=0, number_of_blocks=103), ExpectMsg('reply_channel_range', chain_hash=regtest_hash, first_blocknum=0, number_of_blocks=103), CheckEq(decode_scids, '') ], # This should get the first one, not the second. [ Msg('query_channel_range', chain_hash=regtest_hash, first_blocknum=103, number_of_blocks=1), ExpectMsg('reply_channel_range', chain_hash=regtest_hash, first_blocknum=103, number_of_blocks=1), CheckEq(decode_scids, '103x1x0') ], # This should get the second one, not the first. [ Msg('query_channel_range', chain_hash=regtest_hash, first_blocknum=109, number_of_blocks=4294967295), OneOf( ExpectMsg('reply_channel_range', chain_hash=regtest_hash, first_blocknum=109, number_of_blocks=4294967186), # Could truncate number_of_blocks. ExpectMsg('reply_channel_range', chain_hash=regtest_hash, first_blocknum=109, number_of_blocks=1)), CheckEq(decode_scids, '109x1x0') ], # This should get both. [ Msg('query_channel_range', chain_hash=regtest_hash, first_blocknum=103, number_of_blocks=7), ExpectMsg('reply_channel_range', chain_hash=regtest_hash, first_blocknum=103, number_of_blocks=7), CheckEq(decode_scids, '103x1x0,109x1x0') ], # This should get appended timestamp fields with option_gossip_queries_ex Sequence(enable=runner.has_option('option_gossip_queries_ex') is not None, events=[ Msg('query_channel_range', chain_hash=regtest_hash, first_blocknum=103, number_of_blocks=7, tlvs='{query_option={query_option_flags=1}}'), ExpectMsg('reply_channel_range', chain_hash=regtest_hash, first_blocknum=103, number_of_blocks=7), CheckEq(decode_timestamps, ts_103x1x0 + ts_109x1x0), CheckEq(decode_scids, '103x1x0,109x1x0') ]), # This should get appended checksum fields with option_gossip_queries_ex Sequence(enable=runner.has_option('option_gossip_queries_ex') is not None, events=[ Msg('query_channel_range', chain_hash=regtest_hash, first_blocknum=103, number_of_blocks=7, tlvs='{query_option={query_option_flags=2}}'), ExpectMsg('reply_channel_range', chain_hash=regtest_hash, first_blocknum=103, number_of_blocks=7, tlvs='{checksums_tlv={checksums=[' + csums_103x1x0 + ',' + csums_109x1x0 + ']}}'), CheckEq(decode_scids, '103x1x0,109x1x0') ]), # This should append timestamps and checksums with option_gossip_queries_ex Sequence(enable=runner.has_option('option_gossip_queries_ex') is not None, events=[ Msg('query_channel_range', chain_hash=regtest_hash, first_blocknum=103, number_of_blocks=7, tlvs='{query_option={query_option_flags=3}}'), ExpectMsg('reply_channel_range', chain_hash=regtest_hash, first_blocknum=103, number_of_blocks=7, tlvs='{checksums_tlv={checksums=[' + csums_103x1x0 + ',' + csums_109x1x0 + ']}}'), CheckEq(decode_timestamps, ts_103x1x0 + ts_109x1x0), CheckEq(decode_scids, '103x1x0,109x1x0') ])) ] runner.run(test)
def test_init(runner: Runner, namespaceoverride: Any) -> None: # We override default namespace since we only need BOLT1 namespaceoverride(pyln.spec.bolt1.namespace) test = [ Connect(connprivkey='03'), ExpectMsg('init'), Msg('init', globalfeatures='', features=''), # optionally disconnect that first one TryAll([], Disconnect()), Connect(connprivkey='02'), TryAll( # Even if we don't send anything, it should send init. [ExpectMsg('init')], # Minimal possible init message. # BOLT #1: # The sending node: # - MUST send `init` as the first Lightning message for any connection. [ExpectMsg('init'), Msg('init', globalfeatures='', features='')], # BOLT #1: # The sending node:... # - SHOULD NOT set features greater than 13 in `globalfeatures`. [ ExpectMsg('init', if_match=no_gf13), # BOLT #1: # The receiving node:... # - upon receiving unknown _odd_ feature bits that are non-zero: # - MUST ignore the bit. # init msg with unknown odd global bit (99): no error Msg('init', globalfeatures=bitfield(99), features='') ], # Sanity check that bits 34 and 35 are not used! [ ExpectMsg('init', if_match=functools.partial(no_feature, [34, 35])), # BOLT #1: # The receiving node:... # - upon receiving unknown _odd_ feature bits that are non-zero: # - MUST ignore the bit. # init msg with unknown odd local bit (99): no error Msg('init', globalfeatures='', features=bitfield(99)) ], # BOLT #1: # The receiving node: ... # - upon receiving unknown _even_ feature bits that are non-zero: # - MUST fail the connection. [ ExpectMsg('init'), Msg('init', globalfeatures='', features=bitfield(34)), ExpectError() ], # init msg with unknown even global bit (34): you will error [ ExpectMsg('init'), Msg('init', globalfeatures=bitfield(34), features=''), ExpectError() ], # If you don't support `option_data_loss_protect`, you will be ok if # we ask for it. Sequence([ ExpectMsg('init', if_match=functools.partial(no_feature, [0, 1])), Msg('init', globalfeatures='', features=bitfield(1)) ], enable=not runner.has_option('option_data_loss_protect')), # If you don't support `option_data_loss_protect`, you will error if # we require it. Sequence([ ExpectMsg('init', if_match=functools.partial(no_feature, [0, 1])), Msg('init', globalfeatures='', features=bitfield(0)), ExpectError() ], enable=not runner.has_option('option_data_loss_protect')), # If you support `option_data_loss_protect`, you will advertize it odd. Sequence( [ ExpectMsg('init', if_match=functools.partial(has_feature, [1])) ], enable=( runner.has_option('option_data_loss_protect') == 'odd')), # If you require `option_data_loss_protect`, you will advertize it even. Sequence( [ ExpectMsg('init', if_match=functools.partial(has_feature, [0])) ], enable=( runner.has_option('option_data_loss_protect') == 'even')), # If you don't support `option_anchor_outputs`, you will be ok if # we ask for it. Sequence([ ExpectMsg('init', if_match=functools.partial(no_feature, [20, 21])), Msg('init', globalfeatures='', features=bitfield(21)) ], enable=not runner.has_option('option_anchor_outputs')), # If you don't support `option_anchor_outputs`, you will error if # we require it. Sequence([ ExpectMsg('init', if_match=functools.partial(no_feature, [20, 21])), Msg('init', globalfeatures='', features=bitfield(20)), ExpectError() ], enable=not runner.has_option('option_anchor_outputs')), # If you support `option_anchor_outputs`, you will advertize it odd. Sequence( [ ExpectMsg('init', if_match=functools.partial(has_feature, [21])) ], enable=(runner.has_option('option_anchor_outputs') == 'odd')), # If you require `option_anchor_outputs`, you will advertize it even. Sequence( [ ExpectMsg('init', if_match=functools.partial(has_feature, [20])) ], enable=(runner.has_option('option_anchor_outputs') == 'even')), # BOLT-a12da24dd0102c170365124782b46d9710950ac1 #9: # | Bits | Name | ... | Dependencies # ... # | 12/13 | `option_static_remotekey` | # ... # | 20/21 | `option_anchor_outputs` | ... | `option_static_remotekey` | # If you support `option_anchor_outputs`, you will # advertize option_static_remotekey. Sequence([ ExpectMsg('init', if_match=functools.partial(has_one_feature, [12, 13])) ], enable=(runner.has_option('option_anchor_outputs') is not None)), # You should always handle us echoing your own features back! [ ExpectMsg('init'), Msg('init', globalfeatures=rcvd(), features=rcvd()) ], ) ] runner.run(test)
def test_gossip_timestamp_filter(runner: Runner) -> None: if runner.has_option('option_gossip_queries') is None: unittest.SkipTest('Needs option_gossip_queries') funding1, funding1_tx = Funding.from_utxo(*utxo(0), local_node_privkey='02', local_funding_privkey='10', remote_node_privkey='03', remote_funding_privkey='20') funding2, funding2_tx = Funding.from_utxo(*utxo(1), local_node_privkey='04', local_funding_privkey='30', remote_node_privkey='05', remote_funding_privkey='40') timestamp1 = int(time.time()) timestamp2 = timestamp1 + 1 test = [Block(blockheight=102, txs=[tx_spendable]), Connect(connprivkey='03'), ExpectMsg('init'), Msg('init', globalfeatures='', features=''), # txid 189c40b0728f382fe91c87270926584e48e0af3a6789f37454afee6c7560311d Block(blockheight=103, number=6, txs=[funding1_tx]), RawMsg(funding1.channel_announcement('103x1x0', '')), RawMsg(funding1.node_announcement(Side.local, '', (1, 2, 3), 'foobar', b'', timestamp1)), # New peer connects, asks for gossip_timestamp_filter=all. We *won't* relay channel_announcement, as there is no channel_update. Connect(connprivkey='05'), ExpectMsg('init'), # BOLT #9: # | 6/7 | `gossip_queries` | More sophisticated gossip control Msg('init', globalfeatures='', features=bitfield(6)), Msg('gossip_timestamp_filter', chain_hash='06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f', first_timestamp=0, timestamp_range=4294967295), MustNotMsg('channel_announcement'), MustNotMsg('channel_update'), MustNotMsg('node_announcement'), Disconnect(), # Now, with channel update RawMsg(funding1.channel_update(side=Side.local, short_channel_id='103x1x0', disable=False, cltv_expiry_delta=144, htlc_minimum_msat=0, fee_base_msat=1000, fee_proportional_millionths=10, timestamp=timestamp1, htlc_maximum_msat=None), connprivkey='03'), # New peer connects, asks for gossip_timestamp_filter=all. update and node announcement will be relayed. Connect(connprivkey='05'), ExpectMsg('init'), Msg('init', globalfeatures='', features=bitfield(6)), Msg('gossip_timestamp_filter', chain_hash='06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f', first_timestamp=0, timestamp_range=4294967295), ExpectMsg('channel_announcement', short_channel_id='103x1x0'), AnyOrder(ExpectMsg('channel_update', short_channel_id='103x1x0'), ExpectMsg('node_announcement')), Disconnect(), # BOLT #7: # The receiver: # - SHOULD send all gossip messages whose `timestamp` is greater or # equal to `first_timestamp`, and less than `first_timestamp` plus # `timestamp_range`. Connect(connprivkey='05'), ExpectMsg('init'), Msg('init', globalfeatures='', features=bitfield(6)), Msg('gossip_timestamp_filter', chain_hash='06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f', first_timestamp=1000, timestamp_range=timestamp1 - 1000), MustNotMsg('channel_announcement'), MustNotMsg('channel_update'), MustNotMsg('node_announcement'), Disconnect(), Connect(connprivkey='05'), ExpectMsg('init'), Msg('init', globalfeatures='', features=bitfield(6)), Msg('gossip_timestamp_filter', chain_hash='06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f', first_timestamp=timestamp1 + 1, timestamp_range=4294967295), MustNotMsg('channel_announcement'), MustNotMsg('channel_update'), MustNotMsg('node_announcement'), Disconnect(), # These two succeed in getting the gossip, then stay connected for next test. Connect(connprivkey='05'), ExpectMsg('init'), Msg('init', globalfeatures='', features=bitfield(6)), Msg('gossip_timestamp_filter', chain_hash='06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f', first_timestamp=timestamp1, timestamp_range=4294967295), ExpectMsg('channel_announcement', short_channel_id='103x1x0'), AnyOrder(ExpectMsg('channel_update', short_channel_id='103x1x0'), ExpectMsg('node_announcement')), Connect(connprivkey='06'), ExpectMsg('init'), Msg('init', globalfeatures='', features=bitfield(6)), Msg('gossip_timestamp_filter', chain_hash='06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f', first_timestamp=1000, timestamp_range=timestamp1 - 1000 + 1), ExpectMsg('channel_announcement', short_channel_id='103x1x0'), AnyOrder(ExpectMsg('channel_update', short_channel_id='103x1x0'), ExpectMsg('node_announcement')), # BOLT #7: # - SHOULD restrict future gossip messages to those whose `timestamp` # is greater or equal to `first_timestamp`, and less than # `first_timestamp` plus `timestamp_range`. Block(blockheight=109, number=6, txs=[funding2_tx]), RawMsg(funding2.channel_announcement('109x1x0', ''), connprivkey='03'), RawMsg(funding2.channel_update(side=Side.local, short_channel_id='109x1x0', disable=False, cltv_expiry_delta=144, htlc_minimum_msat=0, fee_base_msat=1000, fee_proportional_millionths=10, timestamp=timestamp2, htlc_maximum_msat=None)), RawMsg(funding2.channel_update(side=Side.remote, short_channel_id='109x1x0', disable=False, cltv_expiry_delta=144, htlc_minimum_msat=0, fee_base_msat=1000, fee_proportional_millionths=10, timestamp=timestamp2, htlc_maximum_msat=None)), RawMsg(funding2.node_announcement(Side.local, '', (1, 2, 3), 'foobar2', b'', timestamp2)), # 005's filter covers this, 006's doesn't. ExpectMsg('channel_announcement', short_channel_id='109x1x0', connprivkey='05'), AnyOrder(ExpectMsg('channel_update', short_channel_id='109x1x0', channel_flags=0), ExpectMsg('channel_update', short_channel_id='109x1x0', channel_flags=1), ExpectMsg('node_announcement')), MustNotMsg('channel_announcement', connprivkey='06'), MustNotMsg('channel_update'), MustNotMsg('node_announcement')] runner.run(test)