Browse Source

lnpeer+wallet: use channel type for channel open

* channel_type is put into storage, serialized as int and
  deserialized as ChannelType
* check for static_remotekey is done via channel type
patch-4
bitromortac 3 years ago
parent
commit
6915e3cb10
No known key found for this signature in database GPG Key ID: 1965063FC13BEBE2
  1. 2
      electrum/json_db.py
  2. 8
      electrum/lnchannel.py
  3. 85
      electrum/lnpeer.py
  4. 1
      electrum/lnutil.py
  5. 5
      electrum/lnworker.py
  6. 1
      electrum/tests/test_lnchannel.py
  7. 1
      electrum/tests/test_lnpeer.py
  8. 2
      electrum/wallet_db.py

2
electrum/json_db.py

@ -98,7 +98,7 @@ class StoredDict(dict):
if not self.db or self.db._should_convert_to_stored_dict(key):
v = StoredDict(v, self.db, self.path + [key])
# convert_value is called depth-first
if isinstance(v, dict) or isinstance(v, str):
if isinstance(v, dict) or isinstance(v, str) or isinstance(v, int):
if self.db:
v = self.db._convert_value(self.path, key, v)
# set parent of StoredObject

8
electrum/lnchannel.py

@ -52,7 +52,8 @@ from .lnutil import (Outpoint, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKey
ScriptHtlc, PaymentFailure, calc_fees_for_commitment_tx, RemoteMisbehaving, make_htlc_output_witness_script,
ShortChannelID, map_htlcs_to_ctx_output_idxs, LNPeerAddr,
fee_for_htlc_output, offered_htlc_trim_threshold_sat,
received_htlc_trim_threshold_sat, make_commitment_output_to_remote_address)
received_htlc_trim_threshold_sat, make_commitment_output_to_remote_address,
ChannelType)
from .lnsweep import create_sweeptxs_for_our_ctx, create_sweeptxs_for_their_ctx
from .lnsweep import create_sweeptx_for_their_revoked_htlc, SweepInfo
from .lnhtlc import HTLCManager
@ -709,7 +710,8 @@ class Channel(AbstractChannel):
return chan_ann
def is_static_remotekey_enabled(self) -> bool:
return bool(self.storage.get('static_remotekey_enabled'))
channel_type = ChannelType(self.storage.get('channel_type'))
return bool(channel_type & ChannelType.OPTION_STATIC_REMOTEKEY)
def get_wallet_addresses_channel_might_want_reserved(self) -> Sequence[str]:
ret = []
@ -925,6 +927,7 @@ class Channel(AbstractChannel):
Action must be initiated by LOCAL.
Finally, the next remote ctx becomes the latest remote ctx.
"""
# TODO: when more channel types are supported, this method should depend on channel type
next_remote_ctn = self.get_next_ctn(REMOTE)
self.logger.info(f"sign_next_commitment {next_remote_ctn}")
@ -966,6 +969,7 @@ class Channel(AbstractChannel):
If all checks pass, the next local ctx becomes the latest local ctx.
"""
# TODO in many failure cases below, we should "fail" the channel (force-close)
# TODO: when more channel types are supported, this method should depend on channel type
next_local_ctn = self.get_next_ctn(LOCAL)
self.logger.info(f"receive_new_commitment. ctn={next_local_ctn}, len(htlc_sigs)={len(htlc_sigs)}")

85
electrum/lnpeer.py

@ -42,7 +42,7 @@ from .lnutil import (Outpoint, LocalConfig, RECEIVED, UpdateAddHtlc, ChannelConf
LightningPeerConnectionClosed, HandshakeFailed,
RemoteMisbehaving, ShortChannelID,
IncompatibleLightningFeatures, derive_payment_secret_from_payment_preimage,
UpfrontShutdownScriptViolation)
UpfrontShutdownScriptViolation, ChannelType)
from .lnutil import FeeUpdate, channel_id_from_funding_tx
from .lntransport import LNTransport, LNTransportBase
from .lnmsg import encode_msg, decode_msg, UnknownOptionalMsgType
@ -508,6 +508,9 @@ class Peer(Logger):
def is_static_remotekey(self):
return self.features.supports(LnFeatures.OPTION_STATIC_REMOTEKEY_OPT)
def is_channel_type(self):
return self.features.supports(LnFeatures.OPTION_CHANNEL_TYPE_OPT)
def is_upfront_shutdown_script(self):
return self.features.supports(LnFeatures.OPTION_UPFRONT_SHUTDOWN_SCRIPT_OPT)
@ -525,16 +528,15 @@ class Peer(Logger):
self.logger.info(f"upfront shutdown script received: {upfront_shutdown_script}")
return upfront_shutdown_script
def make_local_config(self, funding_sat: int, push_msat: int, initiator: HTLCOwner) -> LocalConfig:
def make_local_config(self, funding_sat: int, push_msat: int, initiator: HTLCOwner, channel_type: ChannelType) -> LocalConfig:
channel_seed = os.urandom(32)
initial_msat = funding_sat * 1000 - push_msat if initiator == LOCAL else push_msat
static_remotekey = None
# sending empty bytes as the upfront_shutdown_script will give us the
# flexibility to decide an address at closing time
upfront_shutdown_script = b''
if self.is_static_remotekey():
if channel_type & channel_type.OPTION_STATIC_REMOTEKEY:
wallet = self.lnworker.wallet
assert wallet.txin_type == 'p2wpkh'
addr = wallet.get_new_sweep_address_for_channel()
@ -610,7 +612,24 @@ class Peer(Logger):
raise Exception('Not a trampoline node: ' + str(self.their_features))
feerate = self.lnworker.current_feerate_per_kw()
local_config = self.make_local_config(funding_sat, push_msat, LOCAL)
# we set a channel type for internal bookkeeping
open_channel_tlvs = {}
if self.their_features.supports(LnFeatures.OPTION_STATIC_REMOTEKEY_OPT):
our_channel_type = ChannelType(ChannelType.OPTION_STATIC_REMOTEKEY)
else:
our_channel_type = ChannelType(0)
# if option_channel_type is negotiated: MUST set channel_type
if self.is_channel_type():
# if it includes channel_type: MUST set it to a defined type representing the type it wants.
open_channel_tlvs['channel_type'] = {
'type': our_channel_type.to_bytes_minimal()
}
local_config = self.make_local_config(funding_sat, push_msat, LOCAL, our_channel_type)
# if it includes open_channel_tlvs: MUST include upfront_shutdown_script.
open_channel_tlvs['upfront_shutdown_script'] = {
'shutdown_scriptpubkey': local_config.upfront_shutdown_script
}
# for the first commitment transaction
per_commitment_secret_first = get_per_commitment_secret_from_seed(
@ -639,10 +658,7 @@ class Peer(Logger):
channel_flags=0x00, # not willing to announce channel
channel_reserve_satoshis=local_config.reserve_sat,
htlc_minimum_msat=local_config.htlc_minimum_msat,
open_channel_tlvs={
'upfront_shutdown_script':
{'shutdown_scriptpubkey': local_config.upfront_shutdown_script}
}
open_channel_tlvs=open_channel_tlvs,
)
# <- accept_channel
@ -657,6 +673,15 @@ class Peer(Logger):
upfront_shutdown_script = self.upfront_shutdown_script_from_payload(
payload, 'accept')
accept_channel_tlvs = payload.get('accept_channel_tlvs')
their_channel_type = accept_channel_tlvs.get('channel_type') if accept_channel_tlvs else None
if their_channel_type:
their_channel_type = ChannelType.from_bytes(their_channel_type['type'], byteorder='big').discard_unknown_and_check()
# if channel_type is set, and channel_type was set in open_channel,
# and they are not equal types: MUST reject the channel.
if open_channel_tlvs.get('channel_type') is not None and their_channel_type != our_channel_type:
raise Exception("Channel type is not the one that we sent.")
remote_config = RemoteConfig(
payment_basepoint=OnlyPubkeyKeypair(payload['payment_basepoint']),
multisig_key=OnlyPubkeyKeypair(payload["funding_pubkey"]),
@ -672,7 +697,7 @@ class Peer(Logger):
htlc_minimum_msat=payload['htlc_minimum_msat'],
next_per_commitment_point=remote_per_commitment_point,
current_per_commitment_point=None,
upfront_shutdown_script=upfront_shutdown_script
upfront_shutdown_script=upfront_shutdown_script,
)
ChannelConfig.cross_validate_params(
local_config=local_config,
@ -721,7 +746,7 @@ class Peer(Logger):
funding_txn_minimum_depth=funding_txn_minimum_depth
)
storage = self.create_channel_storage(
channel_id, outpoint, local_config, remote_config, constraints)
channel_id, outpoint, local_config, remote_config, constraints, our_channel_type)
chan = Channel(
storage,
sweep_address=self.lnworker.sweep_address,
@ -752,7 +777,7 @@ class Peer(Logger):
self.lnworker.add_new_channel(chan)
return chan, funding_tx
def create_channel_storage(self, channel_id, outpoint, local_config, remote_config, constraints):
def create_channel_storage(self, channel_id, outpoint, local_config, remote_config, constraints, channel_type):
chan_dict = {
"node_id": self.pubkey.hex(),
"channel_id": channel_id.hex(),
@ -769,7 +794,7 @@ class Peer(Logger):
"fail_htlc_reasons": {}, # htlc_id -> onion_packet
"unfulfilled_htlcs": {}, # htlc_id -> error_bytes, failure_message
"revocation_store": {},
"static_remotekey_enabled": self.is_static_remotekey(), # stored because it cannot be "downgraded", per BOLT2
"channel_type": channel_type,
}
return StoredDict(chan_dict, self.lnworker.db if self.lnworker else None, [])
@ -793,7 +818,21 @@ class Peer(Logger):
push_msat = payload['push_msat']
feerate = payload['feerate_per_kw'] # note: we are not validating this
temp_chan_id = payload['temporary_channel_id']
local_config = self.make_local_config(funding_sat, push_msat, REMOTE)
open_channel_tlvs = payload.get('open_channel_tlvs')
channel_type = open_channel_tlvs.get('channel_type') if open_channel_tlvs else None
# The receiving node MAY fail the channel if:
# option_channel_type was negotiated but the message doesn't include a channel_type
if self.is_channel_type() and channel_type is None:
raise Exception("sender has advertized option_channel_type, but hasn't sent the channel type")
# MUST fail the channel if it supports channel_type,
# channel_type was set, and the type is not suitable.
elif self.is_channel_type() and channel_type is not None:
channel_type = ChannelType.from_bytes(channel_type['type'], byteorder='big').discard_unknown_and_check()
if not channel_type.complies_with_features(self.features):
raise Exception("sender has sent a channel type we don't support")
local_config = self.make_local_config(funding_sat, push_msat, REMOTE, channel_type)
upfront_shutdown_script = self.upfront_shutdown_script_from_payload(
payload, 'open')
@ -836,6 +875,17 @@ class Peer(Logger):
per_commitment_point_first = secret_to_pubkey(
int.from_bytes(per_commitment_secret_first, 'big'))
min_depth = 3
accept_channel_tlvs = {
'upfront_shutdown_script': {
'shutdown_scriptpubkey': local_config.upfront_shutdown_script
},
}
# The sender: if it sets channel_type: MUST set it to the channel_type from open_channel
if self.is_channel_type():
accept_channel_tlvs['channel_type'] = {
'type': channel_type.to_bytes_minimal()
}
self.send_message(
'accept_channel',
temporary_channel_id=temp_chan_id,
@ -852,10 +902,7 @@ class Peer(Logger):
delayed_payment_basepoint=local_config.delayed_basepoint.pubkey,
htlc_basepoint=local_config.htlc_basepoint.pubkey,
first_per_commitment_point=per_commitment_point_first,
accept_channel_tlvs={
'upfront_shutdown_script':
{'shutdown_scriptpubkey': local_config.upfront_shutdown_script}
}
accept_channel_tlvs=accept_channel_tlvs,
)
# <- funding created
@ -872,7 +919,7 @@ class Peer(Logger):
)
outpoint = Outpoint(funding_txid, funding_idx)
chan_dict = self.create_channel_storage(
channel_id, outpoint, local_config, remote_config, constraints)
channel_id, outpoint, local_config, remote_config, constraints, channel_type)
chan = Channel(
chan_dict,
sweep_address=self.lnworker.sweep_address,

1
electrum/lnutil.py

@ -1169,6 +1169,7 @@ LN_FEATURES_IMPLEMENTED = (
| LnFeatures.BASIC_MPP_OPT | LnFeatures.BASIC_MPP_REQ
| LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT | LnFeatures.OPTION_TRAMPOLINE_ROUTING_REQ
| LnFeatures.OPTION_SHUTDOWN_ANYSEGWIT_OPT | LnFeatures.OPTION_SHUTDOWN_ANYSEGWIT_REQ
| LnFeatures.OPTION_CHANNEL_TYPE_OPT | LnFeatures.OPTION_CHANNEL_TYPE_REQ
)

5
electrum/lnworker.py

@ -167,7 +167,7 @@ BASE_FEATURES = LnFeatures(0)\
| LnFeatures.OPTION_STATIC_REMOTEKEY_OPT\
| LnFeatures.VAR_ONION_OPT\
| LnFeatures.PAYMENT_SECRET_OPT\
| LnFeatures.OPTION_UPFRONT_SHUTDOWN_SCRIPT_OPT
| LnFeatures.OPTION_UPFRONT_SHUTDOWN_SCRIPT_OPT\
# we do not want to receive unrequested gossip (see lnpeer.maybe_save_remote_update)
LNWALLET_FEATURES = BASE_FEATURES\
@ -177,10 +177,11 @@ LNWALLET_FEATURES = BASE_FEATURES\
| LnFeatures.BASIC_MPP_OPT\
| LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT\
| LnFeatures.OPTION_SHUTDOWN_ANYSEGWIT_OPT\
| LnFeatures.OPTION_CHANNEL_TYPE_OPT\
LNGOSSIP_FEATURES = BASE_FEATURES\
| LnFeatures.GOSSIP_QUERIES_OPT\
| LnFeatures.GOSSIP_QUERIES_REQ
| LnFeatures.GOSSIP_QUERIES_REQ\
class LNWorker(Logger, NetworkRetryManager[LNPeerAddr]):

1
electrum/tests/test_lnchannel.py

@ -107,6 +107,7 @@ def create_channel_state(funding_txid, funding_index, funding_sat, is_initiator,
'fail_htlc_reasons': {},
'unfulfilled_htlcs': {},
'revocation_store': {},
'channel_type': lnutil.ChannelType.OPTION_STATIC_REMOTEKEY
}
return StoredDict(state, None, [])

1
electrum/tests/test_lnpeer.py

@ -138,6 +138,7 @@ class MockLNWallet(Logger, NetworkRetryManager[LNPeerAddr]):
self.features |= LnFeatures.VAR_ONION_OPT
self.features |= LnFeatures.PAYMENT_SECRET_OPT
self.features |= LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT
self.features |= LnFeatures.OPTION_CHANNEL_TYPE_OPT
self.pending_payments = defaultdict(asyncio.Future)
for chan in chans:
chan.lnworker = self

2
electrum/wallet_db.py

@ -1377,6 +1377,8 @@ class WalletDB(JsonDB):
v = ChannelConstraints(**v)
elif key == 'funding_outpoint':
v = Outpoint(**v)
elif key == 'channel_type':
v = ChannelType(v)
return v
def _should_convert_to_stored_dict(self, key) -> bool:

Loading…
Cancel
Save