commit e8471e483b303e6b65f9a2979b9eb9ea530b0490
parent 1d4c113a3524cf012d5a7c40acdc7a61e16b19ed
Author: Janus <ysangkok@gmail.com>
Date: Wed, 10 Oct 2018 22:54:30 +0200
lnhtlc: merge config and state, remove unnecessary properties
Diffstat:
5 files changed, 292 insertions(+), 304 deletions(-)
diff --git a/electrum/lnbase.py b/electrum/lnbase.py
@@ -27,9 +27,9 @@ from .util import PrintError, bh2u, print_error, bfh, log_exceptions
from .transaction import Transaction, TxOutput
from .lnonion import new_onion_packet, OnionHopsDataSingle, OnionPerHop, decode_onion_error, OnionFailureCode
from .lnaddr import lndecode
-from .lnhtlc import HTLCStateMachine, RevokeAndAck
-from .lnutil import (Outpoint, ChannelConfig, LocalState,
- RemoteState, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore,
+from .lnhtlc import HTLCStateMachine, RevokeAndAck, htlcsum
+from .lnutil import (Outpoint, LocalConfig, ChannelConfig,
+ RemoteConfig, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore,
funding_output_script, get_ecdh, get_per_commitment_secret_from_seed,
secret_to_pubkey, LNPeerAddr, PaymentFailure, LnLocalFeatures,
LOCAL, REMOTE, HTLCOwner, generate_keypair, LnKeyFamily,
@@ -480,7 +480,7 @@ class Peer(PrintError):
def on_announcement_signatures(self, payload):
channel_id = payload['channel_id']
chan = self.channels[payload['channel_id']]
- if chan.local_state.was_announced:
+ if chan.config[LOCAL].was_announced:
h, local_node_sig, local_bitcoin_sig = self.send_announcement_signatures(chan)
else:
self.announcement_signatures[channel_id].put_nowait(payload)
@@ -530,7 +530,7 @@ class Peer(PrintError):
chan.set_state('DISCONNECTED')
self.network.trigger_callback('channel', chan)
- def make_local_config(self, funding_sat, push_msat, initiator: HTLCOwner):
+ def make_local_config(self, funding_sat, push_msat, initiator: HTLCOwner, feerate):
# key derivation
channel_counter = self.lnworker.get_and_inc_counter_for_channel_keys()
keypair_generator = lambda family: generate_keypair(self.lnworker.ln_keystore, family, channel_counter)
@@ -549,6 +549,10 @@ class Peer(PrintError):
max_htlc_value_in_flight_msat=0xffffffffffffffff,
max_accepted_htlcs=5,
initial_msat=initial_msat,
+ ctn=-1,
+ next_htlc_id=0,
+ amount_msat=initial_msat,
+ feerate=feerate,
)
per_commitment_secret_seed = keypair_generator(LnKeyFamily.REVOCATION_ROOT).privkey
return local_config, per_commitment_secret_seed
@@ -556,9 +560,8 @@ class Peer(PrintError):
@log_exceptions
async def channel_establishment_flow(self, password, funding_sat, push_msat, temp_channel_id):
await self.initialized
- local_config, per_commitment_secret_seed = self.make_local_config(funding_sat, push_msat, LOCAL)
- # amounts
- local_feerate = self.current_feerate_per_kw()
+ feerate = self.current_feerate_per_kw()
+ local_config, per_commitment_secret_seed = self.make_local_config(funding_sat, push_msat, LOCAL, feerate)
# for the first commitment transaction
per_commitment_secret_first = get_per_commitment_secret_from_seed(per_commitment_secret_seed, RevocationStore.START_INDEX)
per_commitment_point_first = secret_to_pubkey(int.from_bytes(per_commitment_secret_first, 'big'))
@@ -569,7 +572,7 @@ class Peer(PrintError):
funding_satoshis=funding_sat,
push_msat=push_msat,
dust_limit_satoshis=local_config.dust_limit_sat,
- feerate_per_kw=local_feerate,
+ feerate_per_kw=feerate,
max_accepted_htlcs=local_config.max_accepted_htlcs,
funding_pubkey=local_config.multisig_key.pubkey,
revocation_basepoint=local_config.revocation_basepoint.pubkey,
@@ -587,24 +590,33 @@ class Peer(PrintError):
if payload.get('error'):
raise Exception(payload.get('error'))
remote_per_commitment_point = payload['first_per_commitment_point']
- remote_config=ChannelConfig(
+ funding_txn_minimum_depth = int.from_bytes(payload['minimum_depth'], 'big')
+ remote_dust_limit_sat = int.from_bytes(payload['dust_limit_satoshis'], byteorder='big')
+ assert remote_dust_limit_sat < 600, remote_dust_limit_sat
+ assert int.from_bytes(payload['htlc_minimum_msat'], 'big') < 600 * 1000
+ remote_max = int.from_bytes(payload['max_htlc_value_in_flight_msat'], 'big')
+ assert remote_max >= 198 * 1000 * 1000, remote_max
+ their_revocation_store = RevocationStore()
+ remote_config = RemoteConfig(
payment_basepoint=OnlyPubkeyKeypair(payload['payment_basepoint']),
multisig_key=OnlyPubkeyKeypair(payload["funding_pubkey"]),
htlc_basepoint=OnlyPubkeyKeypair(payload['htlc_basepoint']),
delayed_basepoint=OnlyPubkeyKeypair(payload['delayed_payment_basepoint']),
revocation_basepoint=OnlyPubkeyKeypair(payload['revocation_basepoint']),
to_self_delay=int.from_bytes(payload['to_self_delay'], byteorder='big'),
- dust_limit_sat=int.from_bytes(payload['dust_limit_satoshis'], byteorder='big'),
- max_htlc_value_in_flight_msat=int.from_bytes(payload['max_htlc_value_in_flight_msat'], 'big'),
+ dust_limit_sat=remote_dust_limit_sat,
+ max_htlc_value_in_flight_msat=remote_max,
max_accepted_htlcs=int.from_bytes(payload["max_accepted_htlcs"], 'big'),
- initial_msat=push_msat
+ initial_msat=push_msat,
+ ctn = -1,
+ amount_msat=push_msat,
+ next_htlc_id = 0,
+ feerate=feerate,
+
+ next_per_commitment_point=remote_per_commitment_point,
+ current_per_commitment_point=None,
+ revocation_store=their_revocation_store,
)
- funding_txn_minimum_depth = int.from_bytes(payload['minimum_depth'], 'big')
- assert remote_config.dust_limit_sat < 600
- assert int.from_bytes(payload['htlc_minimum_msat'], 'big') < 600 * 1000
- assert remote_config.max_htlc_value_in_flight_msat >= 198 * 1000 * 1000, remote_config.max_htlc_value_in_flight_msat
- self.print_error('remote delay', remote_config.to_self_delay)
- self.print_error('funding_txn_minimum_depth', funding_txn_minimum_depth)
# create funding tx
redeem_script = funding_output_script(local_config, remote_config)
funding_address = bitcoin.redeem_script_to_address('p2wsh', redeem_script)
@@ -612,38 +624,21 @@ class Peer(PrintError):
funding_tx = self.lnworker.wallet.mktx([funding_output], password, self.lnworker.config, 1000)
funding_txid = funding_tx.txid()
funding_index = funding_tx.outputs().index(funding_output)
- # compute amounts
- local_amount = funding_sat*1000 - push_msat
- remote_amount = push_msat
# remote commitment transaction
channel_id, funding_txid_bytes = channel_id_from_funding_tx(funding_txid, funding_index)
- their_revocation_store = RevocationStore()
chan = {
"node_id": self.pubkey,
"channel_id": channel_id,
"short_channel_id": None,
"funding_outpoint": Outpoint(funding_txid, funding_index),
- "local_config": local_config,
"remote_config": remote_config,
- "remote_state": RemoteState(
- ctn = -1,
- next_per_commitment_point=remote_per_commitment_point,
- current_per_commitment_point=None,
- amount_msat=remote_amount,
- revocation_store=their_revocation_store,
- next_htlc_id = 0,
- feerate=local_feerate
- ),
- "local_state": LocalState(
- ctn = -1,
+ "local_config": LocalConfig(
+ **local_config._asdict(),
per_commitment_secret_seed=per_commitment_secret_seed,
- amount_msat=local_amount,
- next_htlc_id = 0,
funding_locked_received = False,
was_announced = False,
current_commitment_signature = None,
current_htlc_signatures = None,
- feerate=local_feerate
),
"constraints": ChannelConstraints(capacity=funding_sat, is_initiator=True, funding_txn_minimum_depth=funding_txn_minimum_depth),
"remote_commitment_to_be_revoked": None,
@@ -665,8 +660,8 @@ class Peer(PrintError):
success, _txid = await self.network.broadcast_transaction(funding_tx)
assert success, success
m.remote_commitment_to_be_revoked = m.pending_remote_commitment
- m.remote_state = m.remote_state._replace(ctn=0)
- m.local_state = m.local_state._replace(ctn=0, current_commitment_signature=remote_sig)
+ m.config[REMOTE] = m.config[REMOTE]._replace(ctn=0)
+ m.config[LOCAL] = m.config[LOCAL]._replace(ctn=0, current_commitment_signature=remote_sig)
m.set_state('OPENING')
return m
@@ -677,21 +672,10 @@ class Peer(PrintError):
raise Exception('wrong chain_hash')
funding_sat = int.from_bytes(payload['funding_satoshis'], 'big')
push_msat = int.from_bytes(payload['push_msat'], 'big')
+ feerate = int.from_bytes(payload['feerate_per_kw'], 'big')
- remote_config = ChannelConfig(
- payment_basepoint=OnlyPubkeyKeypair(payload['payment_basepoint']),
- multisig_key=OnlyPubkeyKeypair(payload['funding_pubkey']),
- htlc_basepoint=OnlyPubkeyKeypair(payload['htlc_basepoint']),
- delayed_basepoint=OnlyPubkeyKeypair(payload['delayed_payment_basepoint']),
- revocation_basepoint=OnlyPubkeyKeypair(payload['revocation_basepoint']),
- to_self_delay=int.from_bytes(payload['to_self_delay'], 'big'),
- dust_limit_sat=int.from_bytes(payload['dust_limit_satoshis'], 'big'),
- max_htlc_value_in_flight_msat=int.from_bytes(payload['max_htlc_value_in_flight_msat'], 'big'),
- max_accepted_htlcs=int.from_bytes(payload['max_accepted_htlcs'], 'big'),
- initial_msat=funding_sat * 1000 - push_msat,
- )
temp_chan_id = payload['temporary_channel_id']
- local_config, per_commitment_secret_seed = self.make_local_config(funding_sat * 1000, push_msat, REMOTE)
+ local_config, per_commitment_secret_seed = self.make_local_config(funding_sat * 1000, push_msat, REMOTE, feerate)
# for the first commitment transaction
per_commitment_secret_first = get_per_commitment_secret_from_seed(per_commitment_secret_seed, RevocationStore.START_INDEX)
@@ -719,33 +703,39 @@ class Peer(PrintError):
funding_txid = bh2u(funding_created['funding_txid'][::-1])
channel_id, funding_txid_bytes = channel_id_from_funding_tx(funding_txid, funding_idx)
their_revocation_store = RevocationStore()
- local_feerate = int.from_bytes(payload['feerate_per_kw'], 'big')
+ remote_balance_sat = funding_sat * 1000 - push_msat
chan = {
"node_id": self.pubkey,
"channel_id": channel_id,
"short_channel_id": None,
"funding_outpoint": Outpoint(funding_txid, funding_idx),
- "local_config": local_config,
- "remote_config": remote_config,
- "remote_state": RemoteState(
+ "remote_config": RemoteConfig(
+ payment_basepoint=OnlyPubkeyKeypair(payload['payment_basepoint']),
+ multisig_key=OnlyPubkeyKeypair(payload['funding_pubkey']),
+ htlc_basepoint=OnlyPubkeyKeypair(payload['htlc_basepoint']),
+ delayed_basepoint=OnlyPubkeyKeypair(payload['delayed_payment_basepoint']),
+ revocation_basepoint=OnlyPubkeyKeypair(payload['revocation_basepoint']),
+ to_self_delay=int.from_bytes(payload['to_self_delay'], 'big'),
+ dust_limit_sat=int.from_bytes(payload['dust_limit_satoshis'], 'big'),
+ max_htlc_value_in_flight_msat=int.from_bytes(payload['max_htlc_value_in_flight_msat'], 'big'),
+ max_accepted_htlcs=int.from_bytes(payload['max_accepted_htlcs'], 'big'),
+ initial_msat=remote_balance_sat,
ctn = -1,
+ amount_msat=remote_balance_sat,
+ next_htlc_id = 0,
+ feerate=feerate,
+
next_per_commitment_point=payload['first_per_commitment_point'],
current_per_commitment_point=None,
- amount_msat=remote_config.initial_msat,
revocation_store=their_revocation_store,
- next_htlc_id = 0,
- feerate=local_feerate
),
- "local_state": LocalState(
- ctn = -1,
+ "local_config": LocalConfig(
+ **local_config._asdict(),
per_commitment_secret_seed=per_commitment_secret_seed,
- amount_msat=local_config.initial_msat,
- next_htlc_id = 0,
funding_locked_received = False,
was_announced = False,
current_commitment_signature = None,
current_htlc_signatures = None,
- feerate=local_feerate
),
"constraints": ChannelConstraints(capacity=funding_sat, is_initiator=False, funding_txn_minimum_depth=min_depth),
"remote_commitment_to_be_revoked": None,
@@ -762,8 +752,8 @@ class Peer(PrintError):
))
m.set_state('OPENING')
m.remote_commitment_to_be_revoked = m.pending_remote_commitment
- m.remote_state = m.remote_state._replace(ctn=0)
- m.local_state = m.local_state._replace(ctn=0, current_commitment_signature=remote_sig)
+ m.config[REMOTE] = m.config[REMOTE]._replace(ctn=0)
+ m.config[LOCAL] = m.config[LOCAL]._replace(ctn=0, current_commitment_signature=remote_sig)
self.lnworker.save_channel(m)
self.lnwatcher.watch_channel(m.get_funding_address(), m.funding_outpoint.to_str())
self.lnworker.on_channels_updated()
@@ -776,7 +766,7 @@ class Peer(PrintError):
else:
break
outp = funding_tx.outputs()[funding_idx]
- redeem_script = funding_output_script(remote_config, local_config)
+ redeem_script = funding_output_script(m.config[REMOTE], m.config[LOCAL])
funding_address = bitcoin.redeem_script_to_address('p2wsh', redeem_script)
if outp != TxOutput(bitcoin.TYPE_ADDRESS, funding_address, funding_sat):
m.set_state('DISCONNECTED')
@@ -794,12 +784,12 @@ class Peer(PrintError):
self.network.trigger_callback('channel', chan)
self.send_message(gen_msg("channel_reestablish",
channel_id=chan_id,
- next_local_commitment_number=chan.local_state.ctn+1,
- next_remote_revocation_number=chan.remote_state.ctn
+ next_local_commitment_number=chan.config[LOCAL].ctn+1,
+ next_remote_revocation_number=chan.config[REMOTE].ctn
))
await self.channel_reestablished[chan_id]
chan.set_state('OPENING')
- if chan.local_state.funding_locked_received and chan.short_channel_id:
+ if chan.config[LOCAL].funding_locked_received and chan.short_channel_id:
self.mark_open(chan)
self.network.trigger_callback('channel', chan)
@@ -822,24 +812,24 @@ class Peer(PrintError):
channel_reestablish_msg = payload
# compare remote ctns
remote_ctn = int.from_bytes(channel_reestablish_msg["next_local_commitment_number"], 'big')
- if remote_ctn != chan.remote_state.ctn + 1:
- self.print_error("expected remote ctn {}, got {}".format(chan.remote_state.ctn + 1, remote_ctn))
+ if remote_ctn != chan.config[REMOTE].ctn + 1:
+ self.print_error("expected remote ctn {}, got {}".format(chan.config[REMOTE].ctn + 1, remote_ctn))
# TODO iff their ctn is lower than ours, we should force close instead
try_to_get_remote_to_force_close_with_their_latest()
return
# compare local ctns
local_ctn = int.from_bytes(channel_reestablish_msg["next_remote_revocation_number"], 'big')
- if local_ctn != chan.local_state.ctn:
- self.print_error("expected local ctn {}, got {}".format(chan.local_state.ctn, local_ctn))
+ if local_ctn != chan.config[LOCAL].ctn:
+ self.print_error("expected local ctn {}, got {}".format(chan.config[LOCAL].ctn, local_ctn))
# TODO iff their ctn is lower than ours, we should force close instead
try_to_get_remote_to_force_close_with_their_latest()
return
# compare per commitment points (needs data_protect option)
their_pcp = channel_reestablish_msg.get("my_current_per_commitment_point", None)
if their_pcp is not None:
- our_pcp = chan.remote_state.current_per_commitment_point
+ our_pcp = chan.config[REMOTE].current_per_commitment_point
if our_pcp is None:
- our_pcp = chan.remote_state.next_per_commitment_point
+ our_pcp = chan.config[REMOTE].next_per_commitment_point
if our_pcp != their_pcp:
self.print_error("Remote PCP mismatch: {} {}".format(bh2u(our_pcp), bh2u(their_pcp)))
# FIXME ...what now?
@@ -852,10 +842,10 @@ class Peer(PrintError):
channel_id = chan.channel_id
per_commitment_secret_index = RevocationStore.START_INDEX - 1
per_commitment_point_second = secret_to_pubkey(int.from_bytes(
- get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, per_commitment_secret_index), 'big'))
+ get_per_commitment_secret_from_seed(chan.config[LOCAL].per_commitment_secret_seed, per_commitment_secret_index), 'big'))
# note: if funding_locked was not yet received, we might send it multiple times
self.send_message(gen_msg("funding_locked", channel_id=channel_id, next_per_commitment_point=per_commitment_point_second))
- if chan.local_state.funding_locked_received:
+ if chan.config[LOCAL].funding_locked_received:
self.mark_open(chan)
def on_funding_locked(self, payload):
@@ -864,13 +854,13 @@ class Peer(PrintError):
if not chan:
print(self.channels)
raise Exception("Got unknown funding_locked", channel_id)
- if not chan.local_state.funding_locked_received:
- our_next_point = chan.remote_state.next_per_commitment_point
+ if not chan.config[LOCAL].funding_locked_received:
+ our_next_point = chan.config[REMOTE].next_per_commitment_point
their_next_point = payload["next_per_commitment_point"]
- new_remote_state = chan.remote_state._replace(next_per_commitment_point=their_next_point, current_per_commitment_point=our_next_point)
- new_local_state = chan.local_state._replace(funding_locked_received = True)
- chan.remote_state=new_remote_state
- chan.local_state=new_local_state
+ new_remote_state = chan.config[REMOTE]._replace(next_per_commitment_point=their_next_point, current_per_commitment_point=our_next_point)
+ new_local_state = chan.config[LOCAL]._replace(funding_locked_received = True)
+ chan.config[REMOTE]=new_remote_state
+ chan.config[LOCAL]=new_local_state
self.lnworker.save_channel(chan)
if chan.short_channel_id:
self.mark_open(chan)
@@ -881,11 +871,11 @@ class Peer(PrintError):
Runs on the Network thread.
"""
- if not chan.local_state.was_announced and funding_tx_depth >= 6:
+ if not chan.config[LOCAL].was_announced and funding_tx_depth >= 6:
# don't announce our channels
# FIXME should this be a field in chan.local_state maybe?
return
- chan.local_state=chan.local_state._replace(was_announced=True)
+ chan.config[LOCAL]=chan.config[LOCAL]._replace(was_announced=True)
coro = self.handle_announcements(chan)
self.lnworker.save_channel(chan)
asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop)
@@ -896,7 +886,7 @@ class Peer(PrintError):
announcement_signatures_msg = await self.announcement_signatures[chan.channel_id].get()
remote_node_sig = announcement_signatures_msg["node_signature"]
remote_bitcoin_sig = announcement_signatures_msg["bitcoin_signature"]
- if not ecc.verify_signature(chan.remote_config.multisig_key.pubkey, remote_bitcoin_sig, h):
+ if not ecc.verify_signature(chan.config[REMOTE].multisig_key.pubkey, remote_bitcoin_sig, h):
raise Exception("bitcoin_sig invalid in announcement_signatures")
if not ecc.verify_signature(self.pubkey, remote_node_sig, h):
raise Exception("node_sig invalid in announcement_signatures")
@@ -904,7 +894,7 @@ class Peer(PrintError):
node_sigs = [local_node_sig, remote_node_sig]
bitcoin_sigs = [local_bitcoin_sig, remote_bitcoin_sig]
node_ids = [privkey_to_pubkey(self.privkey), self.pubkey]
- bitcoin_keys = [chan.local_config.multisig_key.pubkey, chan.remote_config.multisig_key.pubkey]
+ bitcoin_keys = [chan.config[LOCAL].multisig_key.pubkey, chan.config[REMOTE].multisig_key.pubkey]
if node_ids[0] > node_ids[1]:
node_sigs.reverse()
@@ -935,14 +925,14 @@ class Peer(PrintError):
if chan.get_state() == "OPEN":
return
# NOTE: even closed channels will be temporarily marked "OPEN"
- assert chan.local_state.funding_locked_received
+ assert chan.config[LOCAL].funding_locked_received
chan.set_state("OPEN")
self.network.trigger_callback('channel', chan)
# add channel to database
pubkey_ours = self.lnworker.node_keypair.pubkey
pubkey_theirs = self.pubkey
node_ids = [pubkey_theirs, pubkey_ours]
- bitcoin_keys = [chan.local_config.multisig_key.pubkey, chan.remote_config.multisig_key.pubkey]
+ bitcoin_keys = [chan.config[LOCAL].multisig_key.pubkey, chan.config[REMOTE].multisig_key.pubkey]
sorted_node_ids = list(sorted(node_ids))
if sorted_node_ids != node_ids:
node_ids = sorted_node_ids
@@ -977,8 +967,8 @@ class Peer(PrintError):
def send_announcement_signatures(self, chan):
- bitcoin_keys = [chan.local_config.multisig_key.pubkey,
- chan.remote_config.multisig_key.pubkey]
+ bitcoin_keys = [chan.config[LOCAL].multisig_key.pubkey,
+ chan.config[REMOTE].multisig_key.pubkey]
node_ids = [privkey_to_pubkey(self.privkey),
self.pubkey]
@@ -1000,7 +990,7 @@ class Peer(PrintError):
)
to_hash = chan_ann[256+2:]
h = bitcoin.Hash(to_hash)
- bitcoin_signature = ecc.ECPrivkey(chan.local_config.multisig_key.privkey).sign(h, sig_string_from_r_and_s, get_r_and_s_from_sig_string)
+ bitcoin_signature = ecc.ECPrivkey(chan.config[LOCAL].multisig_key.privkey).sign(h, sig_string_from_r_and_s, get_r_and_s_from_sig_string)
node_signature = ecc.ECPrivkey(self.privkey).sign(h, sig_string_from_r_and_s, get_r_and_s_from_sig_string)
self.send_message(gen_msg("announcement_signatures",
channel_id=chan.channel_id,
@@ -1105,10 +1095,10 @@ class Peer(PrintError):
# then no other payment can use this channel either.
# we need finer blacklisting -- e.g. a blacklist for just this "payment session"?
# or blacklist entries could store an msat value and also expire
- if len(chan.htlcs_in_local) + 1 > chan.remote_config.max_accepted_htlcs:
+ if len(chan.htlcs(LOCAL, only_pending=True)) + 1 > chan.config[REMOTE].max_accepted_htlcs:
raise PaymentFailure('too many HTLCs already in channel')
- if chan.htlcsum(chan.htlcs_in_local) + amount_msat > chan.remote_config.max_htlc_value_in_flight_msat:
- raise PaymentFailure('HTLC value sum would exceed max allowed: {} msat'.format(chan.remote_config.max_htlc_value_in_flight_msat))
+ if htlcsum(chan.htlcs(LOCAL, only_pending=True)) + amount_msat > chan.config[REMOTE].max_htlc_value_in_flight_msat:
+ raise PaymentFailure('HTLC value sum would exceed max allowed: {} msat'.format(chan.config[REMOTE].max_htlc_value_in_flight_msat))
if msat_local < 0:
# FIXME what about channel_reserve_satoshis? will the remote fail the channel if we go below? test.
raise PaymentFailure('not enough local balance')
@@ -1144,7 +1134,7 @@ class Peer(PrintError):
channel_id = chan.channel_id
expected_received_msat = int(decoded.amount * bitcoin.COIN * 1000)
htlc_id = int.from_bytes(htlc["id"], 'big')
- assert htlc_id == chan.remote_state.next_htlc_id, (htlc_id, chan.remote_state.next_htlc_id)
+ assert htlc_id == chan.config[REMOTE].next_htlc_id, (htlc_id, chan.config[REMOTE].next_htlc_id)
assert chan.get_state() == "OPEN"
cltv_expiry = int.from_bytes(htlc["cltv_expiry"], 'big')
# TODO verify sanity of their cltv expiry
@@ -1166,7 +1156,7 @@ class Peer(PrintError):
self.print_error("commitment_signed", payload)
channel_id = payload['channel_id']
chan = self.channels[channel_id]
- chan.local_state=chan.local_state._replace(
+ chan.config[LOCAL]=chan.config[LOCAL]._replace(
current_commitment_signature=payload['signature'],
current_htlc_signatures=payload['htlc_signature'])
self.lnworker.save_channel(chan)
diff --git a/electrum/lnhtlc.py b/electrum/lnhtlc.py
@@ -10,7 +10,7 @@ from .bitcoin import Hash, TYPE_SCRIPT, TYPE_ADDRESS
from .bitcoin import redeem_script_to_address
from .crypto import sha256
from . import ecc
-from .lnutil import Outpoint, ChannelConfig, LocalState, RemoteState, Keypair, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore, EncumberedTransaction
+from .lnutil import Outpoint, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore, EncumberedTransaction
from .lnutil import get_per_commitment_secret_from_seed
from .lnutil import make_commitment_output_to_remote_address, make_commitment_output_to_local_witness_script
from .lnutil import secret_to_pubkey, derive_privkey, derive_pubkey, derive_blinded_pubkey, derive_blinded_privkey
@@ -75,59 +75,50 @@ class UpdateAddHtlc(namedtuple('UpdateAddHtlc', ['amount_msat', 'payment_hash',
if 'locked_in' not in kwargs:
kwargs['locked_in'] = {LOCAL: None, REMOTE: None}
else:
- kwargs['locked_in'] = {HTLCOwner(int(x)): y for x,y in kwargs['locked_in']}
+ kwargs['locked_in'] = {HTLCOwner(int(x)): y for x,y in kwargs['locked_in'].items()}
return super().__new__(cls, **kwargs)
-is_key = lambda k: k.endswith("_basepoint") or k.endswith("_key")
-
-def maybeDecode(k, v):
- assert type(v) is not list
- if k in ["node_id", "channel_id", "short_channel_id", "pubkey", "privkey", "current_per_commitment_point", "next_per_commitment_point", "per_commitment_secret_seed", "current_commitment_signature", "current_htlc_signatures"] and v is not None:
- return binascii.unhexlify(v)
- return v
-
-def decodeAll(v):
- return {i: maybeDecode(i, j) for i, j in v.items()} if isinstance(v, dict) else v
-
-def typeWrap(k, v, local):
- if is_key(k):
- if local:
- return Keypair(**v)
+def decodeAll(d, local):
+ for k, v in d.items():
+ if k == 'revocation_store':
+ yield (k, RevocationStore.from_json_obj(v))
+ elif k.endswith("_basepoint") or k.endswith("_key"):
+ if local:
+ yield (k, Keypair(**dict(decodeAll(v, local))))
+ else:
+ yield (k, OnlyPubkeyKeypair(**dict(decodeAll(v, local))))
+ elif k in ["node_id", "channel_id", "short_channel_id", "pubkey", "privkey", "current_per_commitment_point", "next_per_commitment_point", "per_commitment_secret_seed", "current_commitment_signature", "current_htlc_signatures"] and v is not None:
+ yield (k, binascii.unhexlify(v))
else:
- return OnlyPubkeyKeypair(**v)
- return v
+ yield (k, v)
+
+def htlcsum(htlcs):
+ return sum([x.amount_msat for x in htlcs])
class HTLCStateMachine(PrintError):
def diagnostic_name(self):
return str(self.name)
def __init__(self, state, name = None):
- self.local_config = state["local_config"]
- if type(self.local_config) is not ChannelConfig:
- new_local_config = {k: typeWrap(k, decodeAll(v), True) for k, v in self.local_config.items()}
- self.local_config = ChannelConfig(**new_local_config)
-
- self.remote_config = state["remote_config"]
- if type(self.remote_config) is not ChannelConfig:
- new_remote_config = {k: typeWrap(k, decodeAll(v), False) for k, v in self.remote_config.items()}
- self.remote_config = ChannelConfig(**new_remote_config)
-
- self.local_state = state["local_state"]
- if type(self.local_state) is not LocalState:
- self.local_state = LocalState(**decodeAll(self.local_state))
-
- self.remote_state = state["remote_state"]
- if type(self.remote_state) is not RemoteState:
- self.remote_state = RemoteState(**decodeAll(self.remote_state))
-
- if type(self.remote_state.revocation_store) is not RevocationStore:
- self.remote_state = self.remote_state._replace(revocation_store = RevocationStore.from_json_obj(self.remote_state.revocation_store))
-
- self.channel_id = maybeDecode("channel_id", state["channel_id"]) if type(state["channel_id"]) is not bytes else state["channel_id"]
- self.constraints = ChannelConstraints(**decodeAll(state["constraints"])) if type(state["constraints"]) is not ChannelConstraints else state["constraints"]
- self.funding_outpoint = Outpoint(**decodeAll(state["funding_outpoint"])) if type(state["funding_outpoint"]) is not Outpoint else state["funding_outpoint"]
- self.node_id = maybeDecode("node_id", state["node_id"]) if type(state["node_id"]) is not bytes else state["node_id"]
- self.short_channel_id = maybeDecode("short_channel_id", state["short_channel_id"]) if type(state["short_channel_id"]) is not bytes else state["short_channel_id"]
+ assert 'local_state' not in state
+ self.config = {}
+ self.config[LOCAL] = state["local_config"]
+ if type(self.config[LOCAL]) is not LocalConfig:
+ conf = dict(decodeAll(self.config[LOCAL], True))
+ self.config[LOCAL] = LocalConfig(**conf)
+ assert type(self.config[LOCAL].htlc_basepoint.privkey) is bytes
+
+ self.config[REMOTE] = state["remote_config"]
+ if type(self.config[REMOTE]) is not RemoteConfig:
+ conf = dict(decodeAll(self.config[REMOTE], False))
+ self.config[REMOTE] = RemoteConfig(**conf)
+ assert type(self.config[REMOTE].htlc_basepoint.pubkey) is bytes
+
+ self.channel_id = bfh(state["channel_id"]) if type(state["channel_id"]) not in (bytes, type(None)) else state["channel_id"]
+ self.constraints = ChannelConstraints(**state["constraints"]) if type(state["constraints"]) is not ChannelConstraints else state["constraints"]
+ self.funding_outpoint = Outpoint(**dict(decodeAll(state["funding_outpoint"], False))) if type(state["funding_outpoint"]) is not Outpoint else state["funding_outpoint"]
+ self.node_id = bfh(state["node_id"]) if type(state["node_id"]) not in (bytes, type(None)) else state["node_id"]
+ self.short_channel_id = bfh(state["short_channel_id"]) if type(state["short_channel_id"]) not in (bytes, type(None)) else state["short_channel_id"]
self.short_channel_id_predicted = self.short_channel_id
self.onion_keys = {int(k): bfh(v) for k,v in state['onion_keys'].items()} if 'onion_keys' in state else {}
@@ -141,7 +132,7 @@ class HTLCStateMachine(PrintError):
for strname, subject in [('remote_log', REMOTE), ('local_log', LOCAL)]:
if strname not in state: continue
for y in state[strname]:
- htlc = UpdateAddHtlc(*decodeAll(y))
+ htlc = UpdateAddHtlc(**y)
self.log[subject]['adds'][htlc.htlc_id] = htlc
self.name = name
@@ -172,7 +163,7 @@ class HTLCStateMachine(PrintError):
return self._is_funding_txo_spent is False and self._state == 'DISCONNECTED'
def get_funding_address(self):
- script = funding_output_script(self.local_config, self.remote_config)
+ script = funding_output_script(self.config[LOCAL], self.config[REMOTE])
return redeem_script_to_address('p2wsh', script)
def add_htlc(self, htlc):
@@ -181,10 +172,10 @@ class HTLCStateMachine(PrintError):
should be called when preparing to send an outgoing HTLC.
"""
assert type(htlc) is dict
- htlc = UpdateAddHtlc(**htlc, htlc_id=self.local_state.next_htlc_id)
+ htlc = UpdateAddHtlc(**htlc, htlc_id=self.config[LOCAL].next_htlc_id)
self.log[LOCAL]['adds'][htlc.htlc_id] = htlc
self.print_error("add_htlc")
- self.local_state=self.local_state._replace(next_htlc_id=htlc.htlc_id + 1)
+ self.config[LOCAL]=self.config[LOCAL]._replace(next_htlc_id=htlc.htlc_id + 1)
return htlc.htlc_id
def receive_htlc(self, htlc):
@@ -194,10 +185,10 @@ class HTLCStateMachine(PrintError):
party.
"""
assert type(htlc) is dict
- htlc = UpdateAddHtlc(**htlc, htlc_id = self.remote_state.next_htlc_id)
+ htlc = UpdateAddHtlc(**htlc, htlc_id = self.config[REMOTE].next_htlc_id)
self.log[REMOTE]['adds'][htlc.htlc_id] = htlc
self.print_error("receive_htlc")
- self.remote_state=self.remote_state._replace(next_htlc_id=htlc.htlc_id + 1)
+ self.config[REMOTE]=self.config[REMOTE]._replace(next_htlc_id=htlc.htlc_id + 1)
return htlc.htlc_id
def sign_next_commitment(self):
@@ -215,15 +206,15 @@ class HTLCStateMachine(PrintError):
"""
for htlc in self.log[LOCAL]['adds'].values():
if htlc.locked_in[LOCAL] is None:
- htlc.locked_in[LOCAL] = self.local_state.ctn
+ htlc.locked_in[LOCAL] = self.config[LOCAL].ctn
self.print_error("sign_next_commitment")
pending_remote_commitment = self.pending_remote_commitment
- sig_64 = sign_and_get_sig_string(pending_remote_commitment, self.local_config, self.remote_config)
+ sig_64 = sign_and_get_sig_string(pending_remote_commitment, self.config[LOCAL], self.config[REMOTE])
their_remote_htlc_privkey_number = derive_privkey(
- int.from_bytes(self.local_config.htlc_basepoint.privkey, 'big'),
- self.remote_state.next_per_commitment_point)
+ int.from_bytes(self.config[LOCAL].htlc_basepoint.privkey, 'big'),
+ self.config[REMOTE].next_per_commitment_point)
their_remote_htlc_privkey = their_remote_htlc_privkey_number.to_bytes(32, 'big')
for_us = False
@@ -231,7 +222,7 @@ class HTLCStateMachine(PrintError):
htlcsigs = []
for we_receive, htlcs in zip([True, False], [self.included_htlcs(REMOTE, REMOTE), self.included_htlcs(REMOTE, LOCAL)]):
for htlc in htlcs:
- args = [self.remote_state.next_per_commitment_point, for_us, we_receive, pending_remote_commitment, htlc]
+ args = [self.config[REMOTE].next_per_commitment_point, for_us, we_receive, pending_remote_commitment, htlc]
htlc_tx = make_htlc_tx_with_open_channel(self, *args)
sig = bfh(htlc_tx.sign_txin(0, their_remote_htlc_privkey))
htlc_sig = ecc.sig_string_from_der_sig(sig[:-1])
@@ -265,13 +256,13 @@ class HTLCStateMachine(PrintError):
self.print_error("receive_new_commitment")
for htlc in self.log[REMOTE]['adds'].values():
if htlc.locked_in[REMOTE] is None:
- htlc.locked_in[REMOTE] = self.remote_state.ctn
+ htlc.locked_in[REMOTE] = self.config[REMOTE].ctn
assert len(htlc_sigs) == 0 or type(htlc_sigs[0]) is bytes
pending_local_commitment = self.pending_local_commitment
preimage_hex = pending_local_commitment.serialize_preimage(0)
pre_hash = Hash(bfh(preimage_hex))
- if not ecc.verify_signature(self.remote_config.multisig_key.pubkey, sig, pre_hash):
+ if not ecc.verify_signature(self.config[REMOTE].multisig_key.pubkey, sig, pre_hash):
raise Exception('failed verifying signature of our updated commitment transaction: ' + bh2u(sig) + ' preimage is ' + preimage_hex)
_, this_point, _ = self.points
@@ -280,7 +271,7 @@ class HTLCStateMachine(PrintError):
for htlc in htlcs:
htlc_tx = make_htlc_tx_with_open_channel(self, this_point, True, we_receive, pending_local_commitment, htlc)
pre_hash = Hash(bfh(htlc_tx.serialize_preimage(0)))
- remote_htlc_pubkey = derive_pubkey(self.remote_config.htlc_basepoint.pubkey, this_point)
+ remote_htlc_pubkey = derive_pubkey(self.config[REMOTE].htlc_basepoint.pubkey, this_point)
for idx, sig in enumerate(htlc_sigs):
if ecc.verify_signature(remote_htlc_pubkey, sig, pre_hash):
del htlc_sigs[idx]
@@ -314,8 +305,8 @@ class HTLCStateMachine(PrintError):
last_secret, this_point, next_point = self.points
- new_local_feerate = self.local_state.feerate
- new_remote_feerate = self.remote_state.feerate
+ new_local_feerate = self.config[LOCAL].feerate
+ new_remote_feerate = self.config[REMOTE].feerate
for pending_fee in self.fee_mgr[:]:
if not self.constraints.is_initiator and pending_fee.had(FUNDEE_SIGNED):
@@ -327,11 +318,11 @@ class HTLCStateMachine(PrintError):
self.fee_mgr.remove(pending_fee)
print("FEERATE CHANGE COMPLETE (initiator)")
- self.local_state=self.local_state._replace(
- ctn=self.local_state.ctn + 1,
+ self.config[LOCAL]=self.config[LOCAL]._replace(
+ ctn=self.config[LOCAL].ctn + 1,
feerate=new_local_feerate
)
- self.remote_state=self.remote_state._replace(
+ self.config[REMOTE]=self.config[REMOTE]._replace(
feerate=new_remote_feerate
)
@@ -341,13 +332,13 @@ class HTLCStateMachine(PrintError):
@property
def points(self):
- last_small_num = self.local_state.ctn
+ last_small_num = self.config[LOCAL].ctn
this_small_num = last_small_num + 1
next_small_num = last_small_num + 2
- last_secret = get_per_commitment_secret_from_seed(self.local_state.per_commitment_secret_seed, RevocationStore.START_INDEX - last_small_num)
- this_secret = get_per_commitment_secret_from_seed(self.local_state.per_commitment_secret_seed, RevocationStore.START_INDEX - this_small_num)
+ last_secret = get_per_commitment_secret_from_seed(self.config[LOCAL].per_commitment_secret_seed, RevocationStore.START_INDEX - last_small_num)
+ this_secret = get_per_commitment_secret_from_seed(self.config[LOCAL].per_commitment_secret_seed, RevocationStore.START_INDEX - this_small_num)
this_point = secret_to_pubkey(int.from_bytes(this_secret, 'big'))
- next_secret = get_per_commitment_secret_from_seed(self.local_state.per_commitment_secret_seed, RevocationStore.START_INDEX - next_small_num)
+ next_secret = get_per_commitment_secret_from_seed(self.config[LOCAL].per_commitment_secret_seed, RevocationStore.START_INDEX - next_small_num)
next_point = secret_to_pubkey(int.from_bytes(next_secret, 'big'))
return last_secret, this_point, next_point
@@ -358,13 +349,13 @@ class HTLCStateMachine(PrintError):
return
outpoint = self.funding_outpoint.to_str()
if ours:
- ctn = self.local_state.ctn + 1
+ ctn = self.config[LOCAL].ctn + 1
our_per_commitment_secret = get_per_commitment_secret_from_seed(
- self.local_state.per_commitment_secret_seed, RevocationStore.START_INDEX - ctn)
+ self.config[LOCAL].per_commitment_secret_seed, RevocationStore.START_INDEX - ctn)
our_cur_pcp = ecc.ECPrivkey(our_per_commitment_secret).get_public_key_bytes(compressed=True)
encumbered_sweeptx = maybe_create_sweeptx_for_our_ctx_to_local(self, ctx, our_cur_pcp, self.sweep_address)
else:
- their_cur_pcp = self.remote_state.next_per_commitment_point
+ their_cur_pcp = self.config[REMOTE].next_per_commitment_point
encumbered_sweeptx = maybe_create_sweeptx_for_their_ctx_to_remote(self, ctx, their_cur_pcp, self.sweep_address)
self.lnwatcher.add_sweep_tx(outpoint, ctx.txid(), encumbered_sweeptx)
@@ -390,7 +381,7 @@ class HTLCStateMachine(PrintError):
"""
self.print_error("receive_revocation")
- cur_point = self.remote_state.current_per_commitment_point
+ cur_point = self.config[REMOTE].current_per_commitment_point
derived_point = ecc.ECPrivkey(revocation.per_commitment_secret).get_public_key_bytes(compressed=True)
if cur_point != derived_point:
raise Exception('revoked secret not for current point')
@@ -400,14 +391,14 @@ class HTLCStateMachine(PrintError):
# this might break
prev_remote_commitment = self.pending_remote_commitment
- self.remote_state.revocation_store.add_next_entry(revocation.per_commitment_secret)
+ self.config[REMOTE].revocation_store.add_next_entry(revocation.per_commitment_secret)
self.process_new_revocation_secret(revocation.per_commitment_secret)
def mark_settled(subject):
"""
find pending settlements for subject (LOCAL or REMOTE) and mark them settled, return value of settled htlcs
"""
- old_amount = self.htlcsum(self.gen_htlc_indices(subject, False))
+ old_amount = htlcsum(self.htlcs(subject, False))
for htlc_id in self.log[-subject]['settles']:
adds = self.log[subject]['adds']
@@ -415,23 +406,23 @@ class HTLCStateMachine(PrintError):
self.settled[subject].append(htlc.amount_msat)
self.log[-subject]['settles'].clear()
- return old_amount - self.htlcsum(self.gen_htlc_indices(subject, False))
+ return old_amount - htlcsum(self.htlcs(subject, False))
sent_this_batch = mark_settled(LOCAL)
received_this_batch = mark_settled(REMOTE)
- next_point = self.remote_state.next_per_commitment_point
+ next_point = self.config[REMOTE].next_per_commitment_point
print("RECEIVED", received_this_batch)
print("SENT", sent_this_batch)
- self.remote_state=self.remote_state._replace(
- ctn=self.remote_state.ctn + 1,
+ self.config[REMOTE]=self.config[REMOTE]._replace(
+ ctn=self.config[REMOTE].ctn + 1,
current_per_commitment_point=next_point,
next_per_commitment_point=revocation.next_per_commitment_point,
- amount_msat=self.remote_state.amount_msat + (sent_this_batch - received_this_batch)
+ amount_msat=self.config[REMOTE].amount_msat + (sent_this_batch - received_this_batch)
)
- self.local_state=self.local_state._replace(
- amount_msat = self.local_state.amount_msat + (received_this_batch - sent_this_batch)
+ self.config[LOCAL]=self.config[LOCAL]._replace(
+ amount_msat = self.config[LOCAL].amount_msat + (received_this_batch - sent_this_batch)
)
for pending_fee in self.fee_mgr:
@@ -444,43 +435,39 @@ class HTLCStateMachine(PrintError):
return received_this_batch, sent_this_batch
def balance(self, subject):
- initial = self.local_config.initial_msat if subject == LOCAL else self.remote_config.initial_msat
+ initial = self.config[subject].initial_msat
initial -= sum(self.settled[subject])
initial += sum(self.settled[-subject])
- assert initial == (self.local_state.amount_msat if subject == LOCAL else self.remote_state.amount_msat)
+ assert initial == self.config[subject].amount_msat
return initial
- @staticmethod
- def htlcsum(htlcs):
- amount_unsettled = 0
- for x in htlcs:
- amount_unsettled += x.amount_msat
- return amount_unsettled
-
def amounts(self):
- remote_settled= self.htlcsum(self.gen_htlc_indices(REMOTE, False))
- local_settled= self.htlcsum(self.gen_htlc_indices(LOCAL, False))
- unsettled_local = self.htlcsum(self.gen_htlc_indices(LOCAL, True))
- unsettled_remote = self.htlcsum(self.gen_htlc_indices(REMOTE, True))
- remote_msat = self.remote_state.amount_msat -\
+ remote_settled= htlcsum(self.htlcs(REMOTE, False))
+ local_settled= htlcsum(self.htlcs(LOCAL, False))
+ unsettled_local = htlcsum(self.htlcs(LOCAL, True))
+ unsettled_remote = htlcsum(self.htlcs(REMOTE, True))
+ remote_msat = self.config[REMOTE].amount_msat -\
unsettled_remote + local_settled - remote_settled
- local_msat = self.local_state.amount_msat -\
+ local_msat = self.config[LOCAL].amount_msat -\
unsettled_local + remote_settled - local_settled
return remote_msat, local_msat
def included_htlcs(self, subject, htlc_initiator):
+ """
+ return filter of non-dust htlcs for subjects commitment transaction, initiated by given party
+ """
feerate = self.pending_feerate(subject)
- conf = self.remote_config if subject == REMOTE else self.local_config
+ conf = self.config[subject]
weight = HTLC_SUCCESS_WEIGHT if subject != htlc_initiator else HTLC_TIMEOUT_WEIGHT
- htlcs = self.htlcs_in_local if htlc_initiator == LOCAL else self.htlcs_in_remote
+ htlcs = self.htlcs(htlc_initiator, only_pending=True)
fee_for_htlc = lambda htlc: htlc.amount_msat // 1000 - (weight * feerate // 1000)
return filter(lambda htlc: fee_for_htlc(htlc) >= conf.dust_limit_sat, htlcs)
@property
def pending_remote_commitment(self):
- this_point = self.remote_state.next_per_commitment_point
+ this_point = self.config[REMOTE].next_per_commitment_point
return self.make_commitment(REMOTE, this_point)
def pending_feerate(self, subject):
@@ -495,7 +482,7 @@ class HTLCStateMachine(PrintError):
@property
def _committed_feerate(self):
- return {LOCAL: self.local_state.feerate, REMOTE: self.remote_state.feerate}
+ return {LOCAL: self.config[LOCAL].feerate, REMOTE: self.config[REMOTE].feerate}
@property
def pending_local_commitment(self):
@@ -505,10 +492,9 @@ class HTLCStateMachine(PrintError):
def total_msat(self, sub):
return sum(self.settled[sub])
- def gen_htlc_indices(self, subject, only_pending):
+ def htlcs(self, subject, only_pending):
"""
only_pending: require the htlc's settlement to be pending (needs additional signatures/acks)
- include_settled: include settled (totally done with) htlcs
"""
update_log = self.log[subject]
other_log = self.log[-subject]
@@ -521,16 +507,6 @@ class HTLCStateMachine(PrintError):
res.append(htlc)
return res
- @property
- def htlcs_in_local(self):
- """in the local log. 'offered by us'"""
- return self.gen_htlc_indices(LOCAL, True)
-
- @property
- def htlcs_in_remote(self):
- """in the remote log. 'offered by them'"""
- return self.gen_htlc_indices(REMOTE, True)
-
def settle_htlc(self, preimage, htlc_id):
"""
SettleHTLC attempts to settle an existing outstanding received HTLC.
@@ -552,7 +528,7 @@ class HTLCStateMachine(PrintError):
@property
def current_height(self):
- return {LOCAL: self.local_state.ctn, REMOTE: self.remote_state.ctn}
+ return {LOCAL: self.config[LOCAL].ctn, REMOTE: self.config[REMOTE].ctn}
@property
def pending_local_fee(self):
@@ -581,7 +557,7 @@ class HTLCStateMachine(PrintError):
for i in self.log[subject]['adds'].values():
locked_in = i.locked_in[LOCAL] is not None or i.locked_in[REMOTE] is not None
if locked_in:
- htlcs.append(i)
+ htlcs.append(i._asdict())
else:
removed.append(i.htlc_id)
return htlcs, removed
@@ -593,10 +569,8 @@ class HTLCStateMachine(PrintError):
remote_filtered, remote_removed = self.remove_uncommitted_htlcs_from_log(REMOTE)
local_filtered, local_removed = self.remove_uncommitted_htlcs_from_log(LOCAL)
to_save = {
- "local_config": self.local_config,
- "remote_config": self.remote_config,
- "local_state": self.local_state,
- "remote_state": self.remote_state,
+ "local_config": self.config[LOCAL],
+ "remote_config": self.config[REMOTE],
"channel_id": self.channel_id,
"short_channel_id": self.short_channel_id,
"constraints": self.constraints,
@@ -613,12 +587,12 @@ class HTLCStateMachine(PrintError):
# htlcs number must be monotonically increasing,
# so we have to decrease the counter
if len(remote_removed) != 0:
- assert min(remote_removed) < to_save['remote_state'].next_htlc_id
- to_save['remote_state'] = to_save['remote_state']._replace(next_htlc_id = min(remote_removed))
+ assert min(remote_removed) < to_save['remote_config'].next_htlc_id
+ to_save['remote_config'] = to_save['remote_config']._replace(next_htlc_id = min(remote_removed))
if len(local_removed) != 0:
- assert min(local_removed) < to_save['local_state'].next_htlc_id
- to_save['local_state'] = to_save['local_state']._replace(next_htlc_id = min(local_removed))
+ assert min(local_removed) < to_save['local_config'].next_htlc_id
+ to_save['local_config'] = to_save['local_config']._replace(next_htlc_id = min(local_removed))
return to_save
@@ -652,8 +626,8 @@ class HTLCStateMachine(PrintError):
remote_msat, local_msat = self.amounts()
assert local_msat >= 0
assert remote_msat >= 0
- this_config = self.remote_config if subject != LOCAL else self.local_config
- other_config = self.remote_config if subject == LOCAL else self.local_config
+ this_config = self.config[subject]
+ other_config = self.config[-subject]
other_htlc_pubkey = derive_pubkey(other_config.htlc_basepoint.pubkey, this_point)
this_htlc_pubkey = derive_pubkey(this_config.htlc_basepoint.pubkey, this_point)
other_revocation_pubkey = derive_blinded_pubkey(other_config.revocation_basepoint.pubkey, this_point)
@@ -676,12 +650,12 @@ class HTLCStateMachine(PrintError):
remote_msat, local_msat = local_msat, remote_msat
payment_pubkey = derive_pubkey(other_config.payment_basepoint.pubkey, this_point)
return make_commitment(
- (self.local_state.ctn if subject == LOCAL else self.remote_state.ctn) + 1,
+ self.config[subject].ctn + 1,
this_config.multisig_key.pubkey,
other_config.multisig_key.pubkey,
payment_pubkey,
- self.local_config.payment_basepoint.pubkey,
- self.remote_config.payment_basepoint.pubkey,
+ self.config[LOCAL].payment_basepoint.pubkey,
+ self.config[REMOTE].payment_basepoint.pubkey,
other_revocation_pubkey,
derive_pubkey(this_config.delayed_basepoint.pubkey, this_point),
other_config.to_self_delay,
@@ -700,28 +674,28 @@ class HTLCStateMachine(PrintError):
fee_sat = self.pending_local_fee
_, outputs = make_outputs(fee_sat * 1000, True,
- self.local_state.amount_msat,
- self.remote_state.amount_msat,
+ self.config[LOCAL].amount_msat,
+ self.config[REMOTE].amount_msat,
(TYPE_SCRIPT, bh2u(local_script)),
(TYPE_SCRIPT, bh2u(remote_script)),
- [], self.local_config.dust_limit_sat)
+ [], self.config[LOCAL].dust_limit_sat)
- closing_tx = make_closing_tx(self.local_config.multisig_key.pubkey,
- self.remote_config.multisig_key.pubkey,
- self.local_config.payment_basepoint.pubkey,
- self.remote_config.payment_basepoint.pubkey,
+ closing_tx = make_closing_tx(self.config[LOCAL].multisig_key.pubkey,
+ self.config[REMOTE].multisig_key.pubkey,
+ self.config[LOCAL].payment_basepoint.pubkey,
+ self.config[REMOTE].payment_basepoint.pubkey,
# TODO hardcoded we_are_initiator:
True, *self.funding_outpoint, self.constraints.capacity,
outputs)
- der_sig = bfh(closing_tx.sign_txin(0, self.local_config.multisig_key.privkey))
+ der_sig = bfh(closing_tx.sign_txin(0, self.config[LOCAL].multisig_key.privkey))
sig = ecc.sig_string_from_der_sig(der_sig[:-1])
return sig, fee_sat
def maybe_create_sweeptx_for_their_ctx_to_remote(chan, ctx, their_pcp: bytes,
sweep_address) -> Optional[EncumberedTransaction]:
assert isinstance(their_pcp, bytes)
- payment_bp_privkey = ecc.ECPrivkey(chan.local_config.payment_basepoint.privkey)
+ payment_bp_privkey = ecc.ECPrivkey(chan.config[LOCAL].payment_basepoint.privkey)
our_payment_privkey = derive_privkey(payment_bp_privkey.secret_scalar, their_pcp)
our_payment_privkey = ecc.ECPrivkey.from_secret_scalar(our_payment_privkey)
our_payment_pubkey = our_payment_privkey.get_public_key_bytes(compressed=True)
@@ -742,11 +716,11 @@ def maybe_create_sweeptx_for_their_ctx_to_local(chan, ctx, per_commitment_secret
sweep_address) -> Optional[EncumberedTransaction]:
assert isinstance(per_commitment_secret, bytes)
per_commitment_point = ecc.ECPrivkey(per_commitment_secret).get_public_key_bytes(compressed=True)
- revocation_privkey = derive_blinded_privkey(chan.local_config.revocation_basepoint.privkey,
+ revocation_privkey = derive_blinded_privkey(chan.config[LOCAL].revocation_basepoint.privkey,
per_commitment_secret)
revocation_pubkey = ecc.ECPrivkey(revocation_privkey).get_public_key_bytes(compressed=True)
- to_self_delay = chan.local_config.to_self_delay
- delayed_pubkey = derive_pubkey(chan.remote_config.delayed_basepoint.pubkey,
+ to_self_delay = chan.config[LOCAL].to_self_delay
+ delayed_pubkey = derive_pubkey(chan.config[REMOTE].delayed_basepoint.pubkey,
per_commitment_point)
witness_script = bh2u(make_commitment_output_to_local_witness_script(
revocation_pubkey, to_self_delay, delayed_pubkey))
@@ -768,13 +742,13 @@ def maybe_create_sweeptx_for_their_ctx_to_local(chan, ctx, per_commitment_secret
def maybe_create_sweeptx_for_our_ctx_to_local(chan, ctx, our_pcp: bytes,
sweep_address) -> Optional[EncumberedTransaction]:
assert isinstance(our_pcp, bytes)
- delayed_bp_privkey = ecc.ECPrivkey(chan.local_config.delayed_basepoint.privkey)
+ delayed_bp_privkey = ecc.ECPrivkey(chan.config[LOCAL].delayed_basepoint.privkey)
our_localdelayed_privkey = derive_privkey(delayed_bp_privkey.secret_scalar, our_pcp)
our_localdelayed_privkey = ecc.ECPrivkey.from_secret_scalar(our_localdelayed_privkey)
our_localdelayed_pubkey = our_localdelayed_privkey.get_public_key_bytes(compressed=True)
- revocation_pubkey = derive_blinded_pubkey(chan.remote_config.revocation_basepoint.pubkey,
+ revocation_pubkey = derive_blinded_pubkey(chan.config[REMOTE].revocation_basepoint.pubkey,
our_pcp)
- to_self_delay = chan.remote_config.to_self_delay
+ to_self_delay = chan.config[REMOTE].to_self_delay
witness_script = bh2u(make_commitment_output_to_local_witness_script(
revocation_pubkey, to_self_delay, our_localdelayed_pubkey))
to_local_address = redeem_script_to_address('p2wsh', witness_script)
diff --git a/electrum/lnutil.py b/electrum/lnutil.py
@@ -20,14 +20,36 @@ HTLC_TIMEOUT_WEIGHT = 663
HTLC_SUCCESS_WEIGHT = 703
Keypair = namedtuple("Keypair", ["pubkey", "privkey"])
-ChannelConfig = namedtuple("ChannelConfig", [
- "payment_basepoint", "multisig_key", "htlc_basepoint", "delayed_basepoint", "revocation_basepoint",
- "to_self_delay", "dust_limit_sat", "max_htlc_value_in_flight_msat", "max_accepted_htlcs", "initial_msat"])
OnlyPubkeyKeypair = namedtuple("OnlyPubkeyKeypair", ["pubkey"])
-RemoteState = namedtuple("RemoteState", ["ctn", "next_per_commitment_point", "amount_msat", "revocation_store", "current_per_commitment_point", "next_htlc_id", "feerate"])
-LocalState = namedtuple("LocalState", ["ctn", "per_commitment_secret_seed", "amount_msat", "next_htlc_id", "funding_locked_received", "was_announced", "current_commitment_signature", "current_htlc_signatures", "feerate"])
+
+common = [
+ ('ctn' , int),
+ ('amount_msat' , int),
+ ('next_htlc_id' , int),
+ ('feerate' , int),
+ ('payment_basepoint' , Keypair),
+ ('multisig_key' , Keypair),
+ ('htlc_basepoint' , Keypair),
+ ('delayed_basepoint' , Keypair),
+ ('revocation_basepoint' , Keypair),
+ ('to_self_delay' , int),
+ ('dust_limit_sat' , int),
+ ('max_htlc_value_in_flight_msat' , int),
+ ('max_accepted_htlcs' , int),
+ ('initial_msat' , int),
+]
+
+ChannelConfig = NamedTuple('ChannelConfig', common)
+
+LocalConfig = NamedTuple('LocalConfig', common + [
+ ('per_commitment_secret_seed', bytes),
+ ('funding_locked_received', bool),
+ ('was_announced', bool),
+ ('current_commitment_signature', bytes),
+ ('current_htlc_signatures', List[bytes]),
+])
+
ChannelConstraints = namedtuple("ChannelConstraints", ["capacity", "is_initiator", "funding_txn_minimum_depth"])
-#OpenChannel = namedtuple("OpenChannel", ["channel_id", "short_channel_id", "funding_outpoint", "local_config", "remote_config", "remote_state", "local_state", "constraints", "node_id"])
ScriptHtlc = namedtuple('ScriptHtlc', ['redeem_script', 'htlc'])
@@ -88,6 +110,12 @@ class RevocationStore:
def __hash__(self):
return hash(json.dumps(self.serialize(), sort_keys=True))
+RemoteConfig = NamedTuple('RemoteConfig', common + [
+ ('next_per_commitment_point' , bytes),
+ ('revocation_store' , RevocationStore),
+ ('current_per_commitment_point' , bytes),
+])
+
def count_trailing_zeros(index):
""" BOLT-03 (where_to_put_secret) """
try:
@@ -243,8 +271,8 @@ def make_received_htlc(revocation_pubkey, remote_htlcpubkey, local_htlcpubkey, p
def make_htlc_tx_with_open_channel(chan, pcp, for_us, we_receive, commit, htlc):
amount_msat, cltv_expiry, payment_hash = htlc.amount_msat, htlc.cltv_expiry, htlc.payment_hash
- conf = chan.local_config if for_us else chan.remote_config
- other_conf = chan.local_config if not for_us else chan.remote_config
+ conf = chan.config[LOCAL] if for_us else chan.config[REMOTE]
+ other_conf = chan.config[LOCAL] if not for_us else chan.config[REMOTE]
revocation_pubkey = derive_blinded_pubkey(other_conf.revocation_basepoint.pubkey, pcp)
delayedpubkey = derive_pubkey(conf.delayed_basepoint.pubkey, pcp)
@@ -412,8 +440,8 @@ def extract_ctn_from_tx(tx, txin_index: int, funder_payment_basepoint: bytes,
return get_obscured_ctn(obs, funder_payment_basepoint, fundee_payment_basepoint)
def extract_ctn_from_tx_and_chan(tx, chan) -> int:
- funder_conf = chan.local_config if chan.constraints.is_initiator else chan.remote_config
- fundee_conf = chan.local_config if not chan.constraints.is_initiator else chan.remote_config
+ funder_conf = chan.config[LOCAL] if chan.constraints.is_initiator else chan.config[REMOTE]
+ fundee_conf = chan.config[LOCAL] if not chan.constraints.is_initiator else chan.config[REMOTE]
return extract_ctn_from_tx(tx, txin_index=0,
funder_payment_basepoint=funder_conf.payment_basepoint.pubkey,
fundee_payment_basepoint=fundee_conf.payment_basepoint.pubkey)
diff --git a/electrum/lnworker.py b/electrum/lnworker.py
@@ -116,7 +116,7 @@ class LNWorker(PrintError):
def save_channel(self, openchannel):
assert type(openchannel) is HTLCStateMachine
- if openchannel.remote_state.next_per_commitment_point == openchannel.remote_state.current_per_commitment_point:
+ if openchannel.config[REMOTE].next_per_commitment_point == openchannel.config[REMOTE].current_per_commitment_point:
raise Exception("Tried to save channel with next_point == current_point, this should not happen")
with self.lock:
self.channels[openchannel.channel_id] = openchannel
@@ -350,12 +350,12 @@ class LNWorker(PrintError):
chan = self.channels[chan_id]
# local_commitment always gives back the next expected local_commitment,
# but in this case, we want the current one. So substract one ctn number
- old_local_state = chan.local_state
- chan.local_state=chan.local_state._replace(ctn=chan.local_state.ctn - 1)
+ old_local_state = chan.config[LOCAL]
+ chan.config[LOCAL]=chan.config[LOCAL]._replace(ctn=chan.config[LOCAL].ctn - 1)
tx = chan.pending_local_commitment
- chan.local_state = old_local_state
- tx.sign({bh2u(chan.local_config.multisig_key.pubkey): (chan.local_config.multisig_key.privkey, True)})
- remote_sig = chan.local_state.current_commitment_signature
+ chan.config[LOCAL] = old_local_state
+ tx.sign({bh2u(chan.config[LOCAL].multisig_key.pubkey): (chan.config[LOCAL].multisig_key.privkey, True)})
+ remote_sig = chan.config[LOCAL].current_commitment_signature
remote_sig = der_sig_from_sig_string(remote_sig) + b"\x01"
none_idx = tx._inputs[0]["signatures"].index(None)
tx.add_signature_to_txin(0, none_idx, bh2u(remote_sig))
diff --git a/electrum/tests/test_lnhtlc.py b/electrum/tests/test_lnhtlc.py
@@ -16,56 +16,52 @@ def create_channel_state(funding_txid, funding_index, funding_sat, local_feerate
assert remote_amount > 0
channel_id, _ = lnbase.channel_id_from_funding_tx(funding_txid, funding_index)
their_revocation_store = lnbase.RevocationStore()
- local_config=lnbase.ChannelConfig(
- payment_basepoint=privkeys[0],
- multisig_key=privkeys[1],
- htlc_basepoint=privkeys[2],
- delayed_basepoint=privkeys[3],
- revocation_basepoint=privkeys[4],
- to_self_delay=l_csv,
- dust_limit_sat=l_dust,
- max_htlc_value_in_flight_msat=500000 * 1000,
- max_accepted_htlcs=5,
- initial_msat=local_amount,
- )
- remote_config=lnbase.ChannelConfig(
- payment_basepoint=other_pubkeys[0],
- multisig_key=other_pubkeys[1],
- htlc_basepoint=other_pubkeys[2],
- delayed_basepoint=other_pubkeys[3],
- revocation_basepoint=other_pubkeys[4],
- to_self_delay=r_csv,
- dust_limit_sat=r_dust,
- max_htlc_value_in_flight_msat=500000 * 1000,
- max_accepted_htlcs=5,
- initial_msat=remote_amount,
- )
return {
"channel_id":channel_id,
"short_channel_id":channel_id[:8],
"funding_outpoint":lnbase.Outpoint(funding_txid, funding_index),
- "local_config":local_config,
- "remote_config":remote_config,
- "remote_state":lnbase.RemoteState(
+ "remote_config":lnbase.RemoteConfig(
+ payment_basepoint=other_pubkeys[0],
+ multisig_key=other_pubkeys[1],
+ htlc_basepoint=other_pubkeys[2],
+ delayed_basepoint=other_pubkeys[3],
+ revocation_basepoint=other_pubkeys[4],
+ to_self_delay=r_csv,
+ dust_limit_sat=r_dust,
+ max_htlc_value_in_flight_msat=500000 * 1000,
+ max_accepted_htlcs=5,
+ initial_msat=remote_amount,
ctn = 0,
+ next_htlc_id = 0,
+ feerate=local_feerate,
+ amount_msat=remote_amount,
+
next_per_commitment_point=nex,
current_per_commitment_point=cur,
- amount_msat=remote_amount,
revocation_store=their_revocation_store,
- next_htlc_id = 0,
- feerate=local_feerate
),
- "local_state":lnbase.LocalState(
+ "local_config":lnbase.LocalConfig(
+ payment_basepoint=privkeys[0],
+ multisig_key=privkeys[1],
+ htlc_basepoint=privkeys[2],
+ delayed_basepoint=privkeys[3],
+ revocation_basepoint=privkeys[4],
+ to_self_delay=l_csv,
+ dust_limit_sat=l_dust,
+ max_htlc_value_in_flight_msat=500000 * 1000,
+ max_accepted_htlcs=5,
+ initial_msat=local_amount,
ctn = 0,
- per_commitment_secret_seed=seed,
- amount_msat=local_amount,
next_htlc_id = 0,
+ feerate=local_feerate,
+ amount_msat=local_amount,
+
+ per_commitment_secret_seed=seed,
funding_locked_received=True,
was_announced=False,
current_commitment_signature=None,
current_htlc_signatures=None,
- feerate=local_feerate
),
"constraints":lnbase.ChannelConstraints(capacity=funding_sat, is_initiator=is_initiator, funding_txn_minimum_depth=3),
"node_id":other_node_id,
@@ -205,8 +201,8 @@ class TestLNBaseHTLCStateMachine(unittest.TestCase):
self.assertEqual(alice_channel.total_msat(RECEIVED), bobSent, "alice has incorrect milli-satoshis received")
self.assertEqual(bob_channel.total_msat(SENT), bobSent, "bob has incorrect milli-satoshis sent")
self.assertEqual(bob_channel.total_msat(RECEIVED), aliceSent, "bob has incorrect milli-satoshis received")
- self.assertEqual(bob_channel.local_state.ctn, 1, "bob has incorrect commitment height")
- self.assertEqual(alice_channel.local_state.ctn, 1, "alice has incorrect commitment height")
+ self.assertEqual(bob_channel.config[LOCAL].ctn, 1, "bob has incorrect commitment height")
+ self.assertEqual(alice_channel.config[LOCAL].ctn, 1, "alice has incorrect commitment height")
# Both commitment transactions should have three outputs, and one of
# them should be exactly the amount of the HTLC.
@@ -275,20 +271,20 @@ class TestLNBaseHTLCStateMachine(unittest.TestCase):
bob_channel.receive_new_commitment(alice_sig, alice_htlc_sigs)
- self.assertNotEqual(fee, bob_channel.local_state.feerate)
+ self.assertNotEqual(fee, bob_channel.config[LOCAL].feerate)
rev, _ = bob_channel.revoke_current_commitment()
- self.assertEqual(fee, bob_channel.local_state.feerate)
+ self.assertEqual(fee, bob_channel.config[LOCAL].feerate)
bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment()
alice_channel.receive_revocation(rev)
alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs)
- self.assertNotEqual(fee, alice_channel.local_state.feerate)
+ self.assertNotEqual(fee, alice_channel.config[LOCAL].feerate)
rev, _ = alice_channel.revoke_current_commitment()
- self.assertEqual(fee, alice_channel.local_state.feerate)
+ self.assertEqual(fee, alice_channel.config[LOCAL].feerate)
bob_channel.receive_revocation(rev)
- self.assertEqual(fee, bob_channel.remote_state.feerate)
+ self.assertEqual(fee, bob_channel.config[REMOTE].feerate)
def test_UpdateFeeReceiverCommits(self):
@@ -304,20 +300,20 @@ class TestLNBaseHTLCStateMachine(unittest.TestCase):
alice_sig, alice_htlc_sigs = alice_channel.sign_next_commitment()
bob_channel.receive_new_commitment(alice_sig, alice_htlc_sigs)
- self.assertNotEqual(fee, bob_channel.local_state.feerate)
+ self.assertNotEqual(fee, bob_channel.config[LOCAL].feerate)
bob_revocation, _ = bob_channel.revoke_current_commitment()
- self.assertEqual(fee, bob_channel.local_state.feerate)
+ self.assertEqual(fee, bob_channel.config[LOCAL].feerate)
bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment()
alice_channel.receive_revocation(bob_revocation)
alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs)
- self.assertNotEqual(fee, alice_channel.local_state.feerate)
+ self.assertNotEqual(fee, alice_channel.config[LOCAL].feerate)
alice_revocation, _ = alice_channel.revoke_current_commitment()
- self.assertEqual(fee, alice_channel.local_state.feerate)
+ self.assertEqual(fee, alice_channel.config[LOCAL].feerate)
bob_channel.receive_revocation(alice_revocation)
- self.assertEqual(fee, bob_channel.remote_state.feerate)
+ self.assertEqual(fee, bob_channel.config[REMOTE].feerate)
@@ -327,7 +323,7 @@ class TestLNHTLCDust(unittest.TestCase):
paymentPreimage = b"\x01" * 32
paymentHash = bitcoin.sha256(paymentPreimage)
- fee_per_kw = alice_channel.local_state.feerate
+ fee_per_kw = alice_channel.config[LOCAL].feerate
self.assertEqual(fee_per_kw, 6000)
htlcAmt = 500 + lnutil.HTLC_TIMEOUT_WEIGHT * (fee_per_kw // 1000)
self.assertEqual(htlcAmt, 4478)