Browse Source

lnchannel: start using "latest" and "next" instead of "current" and "pending"

"current" used to be "oldest_unrevoked"; and pending was "oldest_unrevoked + 1"
but this was very confusing...
so now we have "oldest_unrevoked", "latest", and "next"
where "next" is "latest + 1"
"oldest_unrevoked" and "latest" are either the same or are offset by 1
(but caller should know which one they need)

rm "got_sig_for_next" - it was a redundant sanity check, that really
just complicated things

rm "local_commitment", "remote_commitment", "set_local_commitment",
"set_remote_commitment" - just use "get_latest_commitment" instead
dependabot/pip/contrib/deterministic-build/ecdsa-0.13.3
SomberNight 5 years ago
committed by ThomasV
parent
commit
b1f606eaed
  1. 78
      electrum/lnchannel.py
  2. 41
      electrum/lnpeer.py
  3. 11
      electrum/lnsweep.py
  4. 1
      electrum/lnutil.py
  5. 6
      electrum/lnworker.py
  6. 143
      electrum/tests/test_lnchannel.py

78
electrum/lnchannel.py

@ -146,8 +146,6 @@ class Channel(Logger):
self._is_funding_txo_spent = None # "don't know"
self._state = None
self.set_state('DISCONNECTED')
self.local_commitment = None
self.remote_commitment = None
self.sweep_info = {}
def get_feerate(self, subject, ctn):
@ -175,14 +173,6 @@ class Channel(Logger):
out[rhash] = (self.channel_id, htlc, direction, status)
return out
def set_local_commitment(self, ctx):
ctn = extract_ctn_from_tx_and_chan(ctx, self)
assert self.signature_fits(ctx), (self.hm.log[LOCAL])
self.local_commitment = ctx
def set_remote_commitment(self):
self.remote_commitment = self.current_commitment(REMOTE)
def open_with_first_pcp(self, remote_pcp, remote_sig):
self.config[REMOTE] = self.config[REMOTE]._replace(ctn=0, current_per_commitment_point=remote_pcp, next_per_commitment_point=None)
self.config[LOCAL] = self.config[LOCAL]._replace(ctn=0, current_commitment_signature=remote_sig)
@ -285,10 +275,10 @@ class Channel(Logger):
This docstring was adapted from LND.
"""
next_remote_ctn = self.get_current_ctn(REMOTE) + 1
next_remote_ctn = self.get_next_ctn(REMOTE)
self.logger.info(f"sign_next_commitment {next_remote_ctn}")
self.hm.send_ctx()
pending_remote_commitment = self.pending_commitment(REMOTE)
pending_remote_commitment = self.get_next_commitment(REMOTE)
sig_64 = sign_and_get_sig_string(pending_remote_commitment, self.config[LOCAL], self.config[REMOTE])
their_remote_htlc_privkey_number = derive_privkey(
@ -317,8 +307,7 @@ class Channel(Logger):
htlcsigs.sort()
htlcsigs = [x[1] for x in htlcsigs]
# TODO should add remote_commitment here and handle
# both valid ctx'es in lnwatcher at the same time...
self.hm.send_ctx()
return sig_64, htlcsigs
@ -335,22 +324,20 @@ class Channel(Logger):
This docstring is from LND.
"""
next_local_ctn = self.get_next_ctn(LOCAL)
self.logger.info("receive_new_commitment")
self.hm.recv_ctx()
assert len(htlc_sigs) == 0 or type(htlc_sigs[0]) is bytes
pending_local_commitment = self.pending_commitment(LOCAL)
pending_local_commitment = self.get_next_commitment(LOCAL)
preimage_hex = pending_local_commitment.serialize_preimage(0)
pre_hash = sha256d(bfh(preimage_hex))
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)
raise Exception(f'failed verifying signature of our updated commitment transaction: {bh2u(sig)} preimage is {preimage_hex}')
htlc_sigs_string = b''.join(htlc_sigs)
htlc_sigs = htlc_sigs[:] # copy cause we will delete now
next_local_ctn = self.get_current_ctn(LOCAL) + 1
for htlcs, we_receive in [(self.included_htlcs(LOCAL, SENT, ctn=next_local_ctn), False),
(self.included_htlcs(LOCAL, RECEIVED, ctn=next_local_ctn), True)]:
for htlc in htlcs:
@ -359,12 +346,10 @@ class Channel(Logger):
if len(htlc_sigs) != 0: # all sigs should have been popped above
raise Exception('failed verifying HTLC signatures: invalid amount of correct signatures')
self.hm.recv_ctx()
self.config[LOCAL]=self.config[LOCAL]._replace(
current_commitment_signature=sig,
current_htlc_signatures=htlc_sigs_string,
got_sig_for_next=True)
self.set_local_commitment(pending_local_commitment)
current_htlc_signatures=htlc_sigs_string)
def verify_htlc(self, htlc: UpdateAddHtlc, htlc_sigs: Sequence[bytes], we_receive: bool, ctx) -> int:
ctn = extract_ctn_from_tx_and_chan(ctx, self)
@ -394,15 +379,14 @@ class Channel(Logger):
def revoke_current_commitment(self):
self.logger.info("revoke_current_commitment")
assert self.config[LOCAL].got_sig_for_next
new_ctn = self.config[LOCAL].ctn + 1
new_ctx = self.pending_commitment(LOCAL)
assert self.signature_fits(new_ctx)
self.set_local_commitment(new_ctx)
new_ctx = self.get_latest_commitment(LOCAL)
if not self.signature_fits(new_ctx):
# this should never fail; as receive_new_commitment already did this test
raise Exception("refusing to revoke as remote sig does not fit")
self.hm.send_rev()
self.config[LOCAL]=self.config[LOCAL]._replace(
ctn=new_ctn,
got_sig_for_next=False,
)
received = self.hm.received_in_ctn(new_ctn)
sent = self.hm.sent_in_ctn(new_ctn)
@ -427,14 +411,12 @@ class Channel(Logger):
self.config[REMOTE].revocation_store.add_next_entry(revocation.per_commitment_secret)
##### start applying fee/htlc changes
next_point = self.config[REMOTE].next_per_commitment_point
self.hm.recv_rev()
self.config[REMOTE]=self.config[REMOTE]._replace(
ctn=self.config[REMOTE].ctn + 1,
current_per_commitment_point=next_point,
current_per_commitment_point=self.config[REMOTE].next_per_commitment_point,
next_per_commitment_point=revocation.next_per_commitment_point,
)
self.set_remote_commitment()
def balance(self, whose, *, ctx_owner=HTLCOwner.LOCAL, ctn=None):
"""
@ -512,7 +494,7 @@ class Channel(Logger):
def get_secret_and_point(self, subject, ctn) -> Tuple[Optional[bytes], bytes]:
assert type(subject) is HTLCOwner
offset = ctn - self.get_current_ctn(subject)
offset = ctn - self.get_oldest_unrevoked_ctn(subject)
if subject == REMOTE:
if offset > 1:
raise RemoteCtnTooFarInFuture(f"offset: {offset}")
@ -540,12 +522,16 @@ class Channel(Logger):
secret, ctx = self.get_secret_and_commitment(subject, ctn)
return ctx
def pending_commitment(self, subject):
ctn = self.get_current_ctn(subject)
return self.get_commitment(subject, ctn + 1)
def get_next_commitment(self, subject: HTLCOwner) -> Transaction:
ctn = self.get_next_ctn(subject)
return self.get_commitment(subject, ctn)
def get_latest_commitment(self, subject: HTLCOwner) -> Transaction:
ctn = self.get_latest_ctn(subject)
return self.get_commitment(subject, ctn)
def current_commitment(self, subject):
ctn = self.get_current_ctn(subject)
def get_oldest_unrevoked_commitment(self, subject: HTLCOwner) -> Transaction:
ctn = self.get_oldest_unrevoked_ctn(subject)
return self.get_commitment(subject, ctn)
def create_sweeptxs(self, ctn):
@ -553,9 +539,15 @@ class Channel(Logger):
secret, ctx = self.get_secret_and_commitment(REMOTE, ctn)
return create_sweeptxs_for_watchtower(self, ctx, secret, self.sweep_address)
def get_current_ctn(self, subject):
def get_oldest_unrevoked_ctn(self, subject: HTLCOwner) -> int:
return self.config[subject].ctn
def get_latest_ctn(self, subject: HTLCOwner) -> int:
return self.hm.ctn_latest(subject)
def get_next_ctn(self, subject: HTLCOwner) -> int:
return self.hm.ctn_latest(subject) + 1
def total_msat(self, direction):
"""Return the cumulative total msat amount received/sent so far."""
assert type(direction) is Direction
@ -597,12 +589,8 @@ class Channel(Logger):
self.logger.info("receive_fail_htlc")
self.hm.recv_fail(htlc_id)
@property
def current_height(self):
return {LOCAL: self.config[LOCAL].ctn, REMOTE: self.config[REMOTE].ctn}
def pending_local_fee(self):
return self.constraints.capacity - sum(x[2] for x in self.pending_commitment(LOCAL).outputs())
return self.constraints.capacity - sum(x[2] for x in self.get_next_commitment(LOCAL).outputs())
def update_fee(self, feerate: int, from_us: bool):
# feerate uses sat/kw
@ -751,7 +739,7 @@ class Channel(Logger):
return res
def force_close_tx(self):
tx = self.local_commitment
tx = self.get_latest_commitment(LOCAL)
assert self.signature_fits(tx)
tx = Transaction(str(tx))
tx.deserialize(True)

41
electrum/lnpeer.py

@ -458,7 +458,6 @@ class Peer(Logger):
was_announced=False,
current_commitment_signature=None,
current_htlc_signatures=[],
got_sig_for_next=False,
)
return local_config
@ -577,8 +576,6 @@ class Peer(Logger):
# broadcast funding tx
await asyncio.wait_for(self.network.broadcast_transaction(funding_tx), 5)
chan.open_with_first_pcp(remote_per_commitment_point, remote_sig)
chan.set_remote_commitment()
chan.set_local_commitment(chan.current_commitment(LOCAL))
return chan
async def on_open_channel(self, payload):
@ -713,12 +710,12 @@ class Peer(Logger):
# BOLT-02: "A node [...] upon disconnection [...] MUST reverse any uncommitted updates sent by the other side"
chan.hm.discard_unsigned_remote_updates()
# ctns
oldest_unrevoked_local_ctn = chan.config[LOCAL].ctn
latest_local_ctn = chan.hm.ctn_latest(LOCAL)
next_local_ctn = latest_local_ctn + 1
oldest_unrevoked_remote_ctn = chan.config[REMOTE].ctn
latest_remote_ctn = chan.hm.ctn_latest(REMOTE)
next_remote_ctn = latest_remote_ctn + 1
oldest_unrevoked_local_ctn = chan.get_oldest_unrevoked_ctn(LOCAL)
latest_local_ctn = chan.get_latest_ctn(LOCAL)
next_local_ctn = chan.get_next_ctn(LOCAL)
oldest_unrevoked_remote_ctn = chan.get_oldest_unrevoked_ctn(REMOTE)
latest_remote_ctn = chan.get_latest_ctn(REMOTE)
next_remote_ctn = chan.get_next_ctn(REMOTE)
# send message
dlp_enabled = self.localfeatures & LnLocalFeatures.OPTION_DATA_LOSS_PROTECT_OPT
if dlp_enabled:
@ -1016,7 +1013,7 @@ class Peer(Logger):
htlc_id = int.from_bytes(payload["id"], "big")
chan = self.channels[channel_id]
chan.receive_fail_htlc(htlc_id)
local_ctn = chan.get_current_ctn(LOCAL)
local_ctn = chan.get_latest_ctn(LOCAL)
asyncio.ensure_future(self._handle_error_code_from_failed_htlc(payload, channel_id, htlc_id))
asyncio.ensure_future(self._on_update_fail_htlc(channel_id, htlc_id, local_ctn))
@ -1087,7 +1084,7 @@ class Peer(Logger):
self.network.path_finder.add_to_blacklist(short_chan_id)
def maybe_send_commitment(self, chan: Channel):
ctn_to_sign = chan.get_current_ctn(REMOTE) + 1
ctn_to_sign = chan.get_next_ctn(REMOTE)
# if there are no changes, we will not (and must not) send a new commitment
next_htlcs, latest_htlcs = chan.hm.get_htlcs_in_next_ctx(REMOTE), chan.hm.get_htlcs_in_latest_ctx(REMOTE)
if (next_htlcs == latest_htlcs
@ -1101,12 +1098,12 @@ class Peer(Logger):
async def await_remote(self, chan: Channel, ctn: int):
self.maybe_send_commitment(chan)
while chan.get_current_ctn(REMOTE) <= ctn:
while chan.get_latest_ctn(REMOTE) <= ctn:
await self._remote_changed_events[chan.channel_id].wait()
async def await_local(self, chan: Channel, ctn: int):
self.maybe_send_commitment(chan)
while chan.get_current_ctn(LOCAL) <= ctn:
while chan.get_latest_ctn(LOCAL) <= ctn:
await self._local_changed_events[chan.channel_id].wait()
async def pay(self, route: List['RouteEdge'], chan: Channel, amount_msat: int,
@ -1122,7 +1119,7 @@ class Peer(Logger):
# create htlc
htlc = UpdateAddHtlc(amount_msat=amount_msat, payment_hash=payment_hash, cltv_expiry=cltv, timestamp=int(time.time()))
htlc = chan.add_htlc(htlc)
remote_ctn = chan.get_current_ctn(REMOTE)
remote_ctn = chan.get_latest_ctn(REMOTE)
chan.onion_keys[htlc.htlc_id] = secret_key
self.attempted_route[(chan.channel_id, htlc.htlc_id)] = route
self.logger.info(f"starting payment. route: {route}. htlc: {htlc}")
@ -1156,7 +1153,7 @@ class Peer(Logger):
and chan.get_next_feerate(LOCAL) == chan.get_latest_feerate(LOCAL)):
raise RemoteMisbehaving('received commitment_signed without pending changes')
# make sure ctn is new
ctn_to_recv = chan.get_current_ctn(LOCAL) + 1
ctn_to_recv = chan.get_next_ctn(LOCAL)
if ctn_to_recv == self.recv_commitment_for_ctn_last[chan]:
raise RemoteMisbehaving('received commitment_signed with same ctn')
self.recv_commitment_for_ctn_last[chan] = ctn_to_recv
@ -1172,7 +1169,7 @@ class Peer(Logger):
preimage = update_fulfill_htlc_msg["payment_preimage"]
htlc_id = int.from_bytes(update_fulfill_htlc_msg["id"], "big")
chan.receive_htlc_settle(preimage, htlc_id)
local_ctn = chan.get_current_ctn(LOCAL)
local_ctn = chan.get_latest_ctn(LOCAL)
asyncio.ensure_future(self._on_update_fulfill_htlc(chan, htlc_id, preimage, local_ctn))
@log_exceptions
@ -1206,8 +1203,8 @@ class Peer(Logger):
timestamp=int(time.time()),
htlc_id=htlc_id)
htlc = chan.receive_htlc(htlc)
local_ctn = chan.get_current_ctn(LOCAL)
remote_ctn = chan.get_current_ctn(REMOTE)
local_ctn = chan.get_latest_ctn(LOCAL)
remote_ctn = chan.get_latest_ctn(REMOTE)
if processed_onion.are_we_final:
asyncio.ensure_future(self._maybe_fulfill_htlc(chan=chan,
htlc=htlc,
@ -1243,7 +1240,7 @@ class Peer(Logger):
next_amount_msat_htlc = int.from_bytes(dph.amt_to_forward, 'big')
next_htlc = UpdateAddHtlc(amount_msat=next_amount_msat_htlc, payment_hash=htlc.payment_hash, cltv_expiry=next_cltv_expiry, timestamp=int(time.time()))
next_htlc = next_chan.add_htlc(next_htlc)
next_remote_ctn = next_chan.get_current_ctn(REMOTE)
next_remote_ctn = next_chan.get_latest_ctn(REMOTE)
next_peer.send_message(
"update_add_htlc",
channel_id=next_chan.channel_id,
@ -1301,7 +1298,7 @@ class Peer(Logger):
async def _fulfill_htlc(self, chan: Channel, htlc_id: int, preimage: bytes):
chan.settle_htlc(preimage, htlc_id)
remote_ctn = chan.get_current_ctn(REMOTE)
remote_ctn = chan.get_latest_ctn(REMOTE)
self.send_message("update_fulfill_htlc",
channel_id=chan.channel_id,
id=htlc_id,
@ -1313,7 +1310,7 @@ class Peer(Logger):
reason: OnionRoutingFailureMessage):
self.logger.info(f"failing received htlc {(bh2u(chan.channel_id), htlc_id)}. reason: {reason}")
chan.fail_htlc(htlc_id)
remote_ctn = chan.get_current_ctn(REMOTE)
remote_ctn = chan.get_latest_ctn(REMOTE)
error_packet = construct_onion_error(reason, onion_packet, our_onion_private_key=self.privkey)
self.send_message("update_fail_htlc",
channel_id=chan.channel_id,
@ -1357,7 +1354,7 @@ class Peer(Logger):
else:
return
chan.update_fee(feerate_per_kw, True)
remote_ctn = chan.get_current_ctn(REMOTE)
remote_ctn = chan.get_latest_ctn(REMOTE)
self.send_message("update_fee",
channel_id=chan.channel_id,
feerate_per_kw=feerate_per_kw)

11
electrum/lnsweep.py

@ -188,7 +188,7 @@ def create_sweeptxs_for_our_ctx(chan: 'Channel', ctx: Transaction, ctn: int,
# other outputs are htlcs
# if they are spent, we need to generate the script
# so, second-stage htlc sweep should not be returned here
if ctn != our_conf.ctn:
if ctn < chan.get_oldest_unrevoked_ctn(LOCAL):
_logger.info("we breached.")
return {}
txs = {}
@ -247,17 +247,18 @@ def create_sweeptxs_for_our_ctx(chan: 'Channel', ctx: Transaction, ctn: int,
def analyze_ctx(chan: 'Channel', ctx: Transaction):
# note: the remote sometimes has two valid non-revoked commitment transactions,
# either of which could be broadcast (their_conf.ctn, their_conf.ctn+1)
# either of which could be broadcast
our_conf, their_conf = get_ordered_channel_configs(chan=chan, for_us=True)
ctn = extract_ctn_from_tx_and_chan(ctx, chan)
per_commitment_secret = None
if ctn == their_conf.ctn:
oldest_unrevoked_remote_ctn = chan.get_oldest_unrevoked_ctn(REMOTE)
if ctn == oldest_unrevoked_remote_ctn:
their_pcp = their_conf.current_per_commitment_point
is_revocation = False
elif ctn == their_conf.ctn + 1:
elif ctn == oldest_unrevoked_remote_ctn + 1:
their_pcp = their_conf.next_per_commitment_point
is_revocation = False
elif ctn < their_conf.ctn: # breach
elif ctn < oldest_unrevoked_remote_ctn: # breach
try:
per_commitment_secret = their_conf.revocation_store.retrieve_secret(RevocationStore.START_INDEX - ctn)
except UnableToDeriveSecret:

1
electrum/lnutil.py

@ -52,7 +52,6 @@ class LocalConfig(NamedTuple):
was_announced: bool
current_commitment_signature: Optional[bytes]
current_htlc_signatures: List[bytes]
got_sig_for_next: bool
class RemoteConfig(NamedTuple):

6
electrum/lnworker.py

@ -311,8 +311,6 @@ class LNWallet(LNWorker):
for x in wallet.storage.get("channels", []):
c = Channel(x, sweep_address=self.sweep_address, lnworker=self)
self.channels[c.channel_id] = c
c.set_remote_commitment()
c.set_local_commitment(c.current_commitment(LOCAL))
# timestamps of opening and closing transactions
self.channel_timestamps = self.storage.get('lightning_channel_timestamps', {})
self.pending_payments = defaultdict(asyncio.Future)
@ -348,10 +346,10 @@ class LNWallet(LNWorker):
self.logger.info(f'could not contact remote watchtower {watchtower_url}')
await asyncio.sleep(5)
async def sync_channel_with_watchtower(self, chan, watchtower):
async def sync_channel_with_watchtower(self, chan: Channel, watchtower):
outpoint = chan.funding_outpoint.to_str()
addr = chan.get_funding_address()
current_ctn = chan.get_current_ctn(REMOTE)
current_ctn = chan.get_oldest_unrevoked_ctn(REMOTE)
watchtower_ctn = await watchtower.get_ctn(outpoint, addr)
for ctn in range(watchtower_ctn + 1, current_ctn):
sweeptxs = chan.create_sweeptxs(ctn)

143
electrum/tests/test_lnchannel.py

@ -85,7 +85,6 @@ def create_channel_state(funding_txid, funding_index, funding_sat, is_initiator,
was_announced=False,
current_commitment_signature=None,
current_htlc_signatures=None,
got_sig_for_next=False,
),
"constraints":lnpeer.ChannelConstraints(
capacity=funding_sat,
@ -93,7 +92,6 @@ def create_channel_state(funding_txid, funding_index, funding_sat, is_initiator,
funding_txn_minimum_depth=3,
),
"node_id":other_node_id,
"remote_commitment_to_be_revoked": None,
'onion_keys': {},
}
@ -137,8 +135,8 @@ def create_test_channels(feerate=6000, local=None, remote=None):
alice.set_state('OPEN')
bob.set_state('OPEN')
a_out = alice.current_commitment(LOCAL).outputs()
b_out = bob.pending_commitment(REMOTE).outputs()
a_out = alice.get_latest_commitment(LOCAL).outputs()
b_out = bob.get_next_commitment(REMOTE).outputs()
assert a_out == b_out, "\n" + pformat((a_out, b_out))
sig_from_bob, a_htlc_sigs = bob.sign_next_commitment()
@ -150,21 +148,12 @@ def create_test_channels(feerate=6000, local=None, remote=None):
alice.config[LOCAL] = alice.config[LOCAL]._replace(current_commitment_signature=sig_from_bob)
bob.config[LOCAL] = bob.config[LOCAL]._replace(current_commitment_signature=sig_from_alice)
alice.set_local_commitment(alice.current_commitment(LOCAL))
bob.set_local_commitment(bob.current_commitment(LOCAL))
alice_second = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(alice_seed, lnutil.RevocationStore.START_INDEX - 1), "big"))
bob_second = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(bob_seed, lnutil.RevocationStore.START_INDEX - 1), "big"))
alice.config[REMOTE] = alice.config[REMOTE]._replace(next_per_commitment_point=bob_second, current_per_commitment_point=bob_first)
bob.config[REMOTE] = bob.config[REMOTE]._replace(next_per_commitment_point=alice_second, current_per_commitment_point=alice_first)
alice.set_remote_commitment()
bob.set_remote_commitment()
alice.remote_commitment_to_be_revoked = alice.remote_commitment
bob.remote_commitment_to_be_revoked = bob.remote_commitment
alice.config[REMOTE] = alice.config[REMOTE]._replace(ctn=0)
bob.config[REMOTE] = bob.config[REMOTE]._replace(ctn=0)
alice.hm.channel_open_finished()
@ -179,7 +168,7 @@ class TestFee(unittest.TestCase):
"""
def test_fee(self):
alice_channel, bob_channel = create_test_channels(253, 10000000000, 5000000000)
self.assertIn(9999817, [x[2] for x in alice_channel.local_commitment.outputs()])
self.assertIn(9999817, [x[2] for x in alice_channel.get_latest_commitment(LOCAL).outputs()])
class TestChannel(unittest.TestCase):
maxDiff = 999
@ -228,31 +217,43 @@ class TestChannel(unittest.TestCase):
self.htlc_dict['amount_msat'] += 1000
self.bob_channel.add_htlc(self.htlc_dict)
self.alice_channel.receive_htlc(self.htlc_dict)
self.assertEqual(len(self.alice_channel.get_latest_commitment(LOCAL).outputs()), 2)
self.assertEqual(len(self.alice_channel.get_next_commitment(LOCAL).outputs()), 3)
self.assertEqual(len(self.alice_channel.get_latest_commitment(REMOTE).outputs()), 2)
self.assertEqual(len(self.alice_channel.get_next_commitment(REMOTE).outputs()), 3)
self.alice_channel.receive_new_commitment(*self.bob_channel.sign_next_commitment())
self.assertEqual(len(self.alice_channel.pending_commitment(REMOTE).outputs()), 3)
self.assertEqual(len(self.alice_channel.get_latest_commitment(LOCAL).outputs()), 3)
self.assertEqual(len(self.alice_channel.get_next_commitment(LOCAL).outputs()), 3)
self.assertEqual(len(self.alice_channel.get_latest_commitment(REMOTE).outputs()), 2)
self.assertEqual(len(self.alice_channel.get_next_commitment(REMOTE).outputs()), 3)
self.alice_channel.revoke_current_commitment()
self.assertEqual(len(self.alice_channel.pending_commitment(REMOTE).outputs()), 4)
self.assertEqual(len(self.alice_channel.get_latest_commitment(LOCAL).outputs()), 3)
self.assertEqual(len(self.alice_channel.get_next_commitment(LOCAL).outputs()), 3)
self.assertEqual(len(self.alice_channel.get_latest_commitment(REMOTE).outputs()), 2)
self.assertEqual(len(self.alice_channel.get_next_commitment(REMOTE).outputs()), 4)
def test_SimpleAddSettleWorkflow(self):
alice_channel, bob_channel = self.alice_channel, self.bob_channel
htlc = self.htlc
alice_out = alice_channel.current_commitment(LOCAL).outputs()
alice_out = alice_channel.get_latest_commitment(LOCAL).outputs()
short_idx, = [idx for idx, x in enumerate(alice_out) if len(x.address) == 42]
long_idx, = [idx for idx, x in enumerate(alice_out) if len(x.address) == 62]
self.assertLess(alice_out[long_idx].value, 5 * 10**8, alice_out)
self.assertEqual(alice_out[short_idx].value, 5 * 10**8, alice_out)
alice_out = alice_channel.current_commitment(REMOTE).outputs()
alice_out = alice_channel.get_latest_commitment(REMOTE).outputs()
short_idx, = [idx for idx, x in enumerate(alice_out) if len(x.address) == 42]
long_idx, = [idx for idx, x in enumerate(alice_out) if len(x.address) == 62]
self.assertLess(alice_out[short_idx].value, 5 * 10**8)
self.assertEqual(alice_out[long_idx].value, 5 * 10**8)
def com():
return alice_channel.local_commitment
self.assertTrue(alice_channel.signature_fits(com()))
self.assertTrue(alice_channel.signature_fits(alice_channel.get_latest_commitment(LOCAL)))
self.assertNotEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, 1), [])
@ -270,9 +271,9 @@ class TestChannel(unittest.TestCase):
from electrum.lnutil import extract_ctn_from_tx_and_chan
tx0 = str(alice_channel.force_close_tx())
self.assertEqual(alice_channel.config[LOCAL].ctn, 0)
self.assertEqual(alice_channel.get_oldest_unrevoked_ctn(LOCAL), 0)
self.assertEqual(extract_ctn_from_tx_and_chan(alice_channel.force_close_tx(), alice_channel), 0)
self.assertTrue(alice_channel.signature_fits(alice_channel.current_commitment(LOCAL)))
self.assertTrue(alice_channel.signature_fits(alice_channel.get_latest_commitment(LOCAL)))
# Next alice commits this change by sending a signature message. Since
# we expect the messages to be ordered, Bob will receive the HTLC we
@ -281,21 +282,20 @@ class TestChannel(unittest.TestCase):
aliceSig, aliceHtlcSigs = alice_channel.sign_next_commitment()
self.assertEqual(len(aliceHtlcSigs), 1, "alice should generate one htlc signature")
self.assertTrue(alice_channel.signature_fits(com()))
self.assertEqual(str(alice_channel.current_commitment(LOCAL)), str(com()))
self.assertTrue(alice_channel.signature_fits(alice_channel.get_latest_commitment(LOCAL)))
self.assertEqual(next(iter(alice_channel.hm.get_htlcs_in_next_ctx(REMOTE)))[0], RECEIVED)
self.assertEqual(alice_channel.hm.get_htlcs_in_next_ctx(REMOTE), bob_channel.hm.get_htlcs_in_next_ctx(LOCAL))
self.assertEqual(alice_channel.pending_commitment(REMOTE).outputs(), bob_channel.pending_commitment(LOCAL).outputs())
self.assertEqual(alice_channel.get_latest_commitment(REMOTE).outputs(), bob_channel.get_next_commitment(LOCAL).outputs())
# Bob receives this signature message, and checks that this covers the
# state he has in his remote log. This includes the HTLC just sent
# from Alice.
self.assertTrue(bob_channel.signature_fits(bob_channel.current_commitment(LOCAL)))
self.assertTrue(bob_channel.signature_fits(bob_channel.get_latest_commitment(LOCAL)))
bob_channel.receive_new_commitment(aliceSig, aliceHtlcSigs)
self.assertTrue(bob_channel.signature_fits(bob_channel.pending_commitment(LOCAL)))
self.assertTrue(bob_channel.signature_fits(bob_channel.get_latest_commitment(LOCAL)))
self.assertEqual(bob_channel.config[REMOTE].ctn, 0)
self.assertEqual(bob_channel.get_oldest_unrevoked_ctn(REMOTE), 0)
self.assertEqual(bob_channel.included_htlcs(LOCAL, RECEIVED, 1), [htlc])#
self.assertEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, 0), [])
@ -311,57 +311,50 @@ class TestChannel(unittest.TestCase):
# has a valid signature for a newer commitment.
bobRevocation, _ = bob_channel.revoke_current_commitment()
bob_channel.serialize()
self.assertTrue(bob_channel.signature_fits(bob_channel.current_commitment(LOCAL)))
self.assertTrue(bob_channel.signature_fits(bob_channel.get_latest_commitment(LOCAL)))
# Bob finally sends a signature for Alice's commitment transaction.
# This signature will cover the HTLC, since Bob will first send the
# revocation just created. The revocation also acks every received
# HTLC up to the point where Alice sent her signature.
bobSig, bobHtlcSigs = bob_channel.sign_next_commitment()
self.assertTrue(bob_channel.signature_fits(bob_channel.current_commitment(LOCAL)))
self.assertTrue(bob_channel.signature_fits(bob_channel.get_latest_commitment(LOCAL)))
self.assertEqual(len(bobHtlcSigs), 1)
self.assertTrue(alice_channel.signature_fits(com()))
self.assertEqual(str(alice_channel.current_commitment(LOCAL)), str(com()))
self.assertTrue(alice_channel.signature_fits(alice_channel.get_latest_commitment(LOCAL)))
# so far: Alice added htlc, Alice signed.
self.assertEqual(len(alice_channel.current_commitment(LOCAL).outputs()), 2)
self.assertEqual(len(alice_channel.pending_commitment(LOCAL).outputs()), 2)
self.assertEqual(len(alice_channel.current_commitment(REMOTE).outputs()), 2) # oldest unrevoked
self.assertEqual(len(alice_channel.pending_commitment(REMOTE).outputs()), 3) # latest
self.assertEqual(len(alice_channel.get_latest_commitment(LOCAL).outputs()), 2)
self.assertEqual(len(alice_channel.get_next_commitment(LOCAL).outputs()), 2)
self.assertEqual(len(alice_channel.get_oldest_unrevoked_commitment(REMOTE).outputs()), 2)
self.assertEqual(len(alice_channel.get_latest_commitment(REMOTE).outputs()), 3)
# Alice then processes this revocation, sending her own revocation for
# her prior commitment transaction. Alice shouldn't have any HTLCs to
# forward since she's sending an outgoing HTLC.
alice_channel.receive_revocation(bobRevocation)
alice_channel.serialize()
self.assertEqual(alice_channel.remote_commitment.outputs(), alice_channel.current_commitment(REMOTE).outputs())
self.assertTrue(alice_channel.signature_fits(com()))
self.assertTrue(alice_channel.signature_fits(alice_channel.current_commitment(LOCAL)))
self.assertTrue(alice_channel.signature_fits(alice_channel.get_latest_commitment(LOCAL)))
alice_channel.serialize()
self.assertEqual(str(alice_channel.current_commitment(LOCAL)), str(com()))
self.assertEqual(len(alice_channel.current_commitment(LOCAL).outputs()), 2)
self.assertEqual(len(alice_channel.current_commitment(REMOTE).outputs()), 3)
self.assertEqual(len(com().outputs()), 2)
self.assertEqual(len(alice_channel.get_latest_commitment(LOCAL).outputs()), 2)
self.assertEqual(len(alice_channel.get_latest_commitment(REMOTE).outputs()), 3)
self.assertEqual(len(alice_channel.force_close_tx().outputs()), 2)
self.assertEqual(len(alice_channel.hm.log[LOCAL]['adds']), 1)
alice_channel.serialize()
self.assertEqual(alice_channel.pending_commitment(LOCAL).outputs(),
bob_channel.pending_commitment(REMOTE).outputs())
self.assertEqual(alice_channel.get_next_commitment(LOCAL).outputs(),
bob_channel.get_latest_commitment(REMOTE).outputs())
# Alice then processes bob's signature, and since she just received
# the revocation, she expect this signature to cover everything up to
# the point where she sent her signature, including the HTLC.
alice_channel.receive_new_commitment(bobSig, bobHtlcSigs)
self.assertEqual(alice_channel.remote_commitment.outputs(), alice_channel.current_commitment(REMOTE).outputs())
self.assertEqual(len(alice_channel.current_commitment(REMOTE).outputs()), 3)
self.assertEqual(len(com().outputs()), 3)
self.assertEqual(len(alice_channel.get_latest_commitment(REMOTE).outputs()), 3)
self.assertEqual(len(alice_channel.force_close_tx().outputs()), 3)
self.assertEqual(len(alice_channel.hm.log[LOCAL]['adds']), 1)
@ -371,10 +364,8 @@ class TestChannel(unittest.TestCase):
self.assertNotEqual(tx0, tx1)
# Alice then generates a revocation for bob.
self.assertEqual(alice_channel.remote_commitment.outputs(), alice_channel.current_commitment(REMOTE).outputs())
aliceRevocation, _ = alice_channel.revoke_current_commitment()
alice_channel.serialize()
#self.assertEqual(alice_channel.remote_commitment.outputs(), alice_channel.current_commitment(REMOTE).outputs())
tx2 = str(alice_channel.force_close_tx())
# since alice already has the signature for the next one, it doesn't change her force close tx (it was already the newer one)
@ -384,7 +375,7 @@ class TestChannel(unittest.TestCase):
# is fully locked in within both commitment transactions. Bob should
# also be able to forward an HTLC now that the HTLC has been locked
# into both commitment transactions.
self.assertTrue(bob_channel.signature_fits(bob_channel.current_commitment(LOCAL)))
self.assertTrue(bob_channel.signature_fits(bob_channel.get_latest_commitment(LOCAL)))
bob_channel.receive_revocation(aliceRevocation)
bob_channel.serialize()
@ -398,13 +389,13 @@ class TestChannel(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.config[LOCAL].ctn, 1, "bob has incorrect commitment height")
self.assertEqual(alice_channel.config[LOCAL].ctn, 1, "alice has incorrect commitment height")
self.assertEqual(bob_channel.get_oldest_unrevoked_ctn(LOCAL), 1, "bob has incorrect commitment height")
self.assertEqual(alice_channel.get_oldest_unrevoked_ctn(LOCAL), 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.
alice_ctx = alice_channel.pending_commitment(LOCAL)
bob_ctx = bob_channel.pending_commitment(LOCAL)
alice_ctx = alice_channel.get_next_commitment(LOCAL)
bob_ctx = bob_channel.get_next_commitment(LOCAL)
self.assertEqual(len(alice_ctx.outputs()), 3, "alice should have three commitment outputs, instead have %s"% len(alice_ctx.outputs()))
self.assertEqual(len(bob_ctx.outputs()), 3, "bob should have three commitment outputs, instead have %s"% len(bob_ctx.outputs()))
self.assertOutputExistsByValue(alice_ctx, htlc.amount_msat // 1000)
@ -415,7 +406,6 @@ class TestChannel(unittest.TestCase):
preimage = self.paymentPreimage
bob_channel.settle_htlc(preimage, self.bobHtlcIndex)
#self.assertEqual(alice_channel.remote_commitment.outputs(), alice_channel.current_commitment(REMOTE).outputs())
alice_channel.receive_htlc_settle(preimage, self.aliceHtlcIndex)
tx3 = str(alice_channel.force_close_tx())
@ -426,7 +416,7 @@ class TestChannel(unittest.TestCase):
self.assertEqual(len(bobHtlcSigs2), 0)
self.assertEqual(alice_channel.hm.htlcs_by_direction(REMOTE, RECEIVED), [htlc])
self.assertEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, alice_channel.config[REMOTE].ctn), [htlc])
self.assertEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, alice_channel.get_oldest_unrevoked_ctn(REMOTE)), [htlc])
self.assertEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, 1), [htlc])
self.assertEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, 2), [htlc])
@ -440,8 +430,8 @@ class TestChannel(unittest.TestCase):
self.assertEqual(bob_channel.included_htlcs(REMOTE, RECEIVED, 1), [])
self.assertEqual(bob_channel.included_htlcs(REMOTE, RECEIVED, 2), [])
alice_ctx_bob_version = bob_channel.pending_commitment(REMOTE).outputs()
alice_ctx_alice_version = alice_channel.pending_commitment(LOCAL).outputs()
alice_ctx_bob_version = bob_channel.get_latest_commitment(REMOTE).outputs()
alice_ctx_alice_version = alice_channel.get_next_commitment(LOCAL).outputs()
self.assertEqual(alice_ctx_alice_version, alice_ctx_bob_version)
alice_channel.receive_new_commitment(bobSig2, bobHtlcSigs2)
@ -450,14 +440,13 @@ class TestChannel(unittest.TestCase):
self.assertNotEqual(tx3, tx4)
self.assertEqual(alice_channel.balance(LOCAL), 500000000000)
self.assertEqual(1, alice_channel.config[LOCAL].ctn)
self.assertEqual(1, alice_channel.get_oldest_unrevoked_ctn(LOCAL))
self.assertEqual(len(alice_channel.included_htlcs(LOCAL, RECEIVED, ctn=2)), 0)
aliceRevocation2, _ = alice_channel.revoke_current_commitment()
alice_channel.serialize()
aliceSig2, aliceHtlcSigs2 = alice_channel.sign_next_commitment()
self.assertEqual(aliceHtlcSigs2, [], "alice should generate no htlc signatures")
self.assertEqual(len(bob_channel.current_commitment(LOCAL).outputs()), 3)
#self.assertEqual(len(bob_channel.pending_commitment(LOCAL).outputs()), 3)
self.assertEqual(len(bob_channel.get_latest_commitment(LOCAL).outputs()), 3)
bob_channel.receive_revocation(aliceRevocation2)
bob_channel.serialize()
@ -478,14 +467,14 @@ class TestChannel(unittest.TestCase):
self.assertEqual(alice_channel.total_msat(RECEIVED), 0, "alice satoshis received incorrect")
self.assertEqual(bob_channel.total_msat(RECEIVED), mSatTransferred, "bob satoshis received incorrect")
self.assertEqual(bob_channel.total_msat(SENT), 0, "bob satoshis sent incorrect")
self.assertEqual(bob_channel.current_height[LOCAL], 2, "bob has incorrect commitment height")
self.assertEqual(alice_channel.current_height[LOCAL], 2, "alice has incorrect commitment height")
self.assertEqual(bob_channel.get_latest_ctn(LOCAL), 2, "bob has incorrect commitment height")
self.assertEqual(alice_channel.get_latest_ctn(LOCAL), 2, "alice has incorrect commitment height")
alice_channel.update_fee(100000, True)
alice_outputs = alice_channel.pending_commitment(REMOTE).outputs()
old_outputs = bob_channel.pending_commitment(LOCAL).outputs()
alice_outputs = alice_channel.get_next_commitment(REMOTE).outputs()
old_outputs = bob_channel.get_next_commitment(LOCAL).outputs()
bob_channel.update_fee(100000, False)
new_outputs = bob_channel.pending_commitment(LOCAL).outputs()
new_outputs = bob_channel.get_next_commitment(LOCAL).outputs()
self.assertNotEqual(old_outputs, new_outputs)
self.assertEqual(alice_outputs, new_outputs)
@ -517,13 +506,13 @@ class TestChannel(unittest.TestCase):
def alice_to_bob_fee_update(self, fee=111):
aoldctx = self.alice_channel.pending_commitment(REMOTE).outputs()
aoldctx = self.alice_channel.get_next_commitment(REMOTE).outputs()
self.alice_channel.update_fee(fee, True)
anewctx = self.alice_channel.pending_commitment(REMOTE).outputs()
anewctx = self.alice_channel.get_next_commitment(REMOTE).outputs()
self.assertNotEqual(aoldctx, anewctx)
boldctx = self.bob_channel.pending_commitment(LOCAL).outputs()
boldctx = self.bob_channel.get_next_commitment(LOCAL).outputs()
self.bob_channel.update_fee(fee, False)
bnewctx = self.bob_channel.pending_commitment(LOCAL).outputs()
bnewctx = self.bob_channel.get_next_commitment(LOCAL).outputs()
self.assertNotEqual(boldctx, bnewctx)
self.assertEqual(anewctx, bnewctx)
return fee
@ -805,12 +794,12 @@ class TestDust(unittest.TestCase):
'timestamp' : 0,
}
old_values = [x.value for x in bob_channel.current_commitment(LOCAL).outputs() ]
old_values = [x.value for x in bob_channel.get_latest_commitment(LOCAL).outputs() ]
aliceHtlcIndex = alice_channel.add_htlc(htlc).htlc_id
bobHtlcIndex = bob_channel.receive_htlc(htlc).htlc_id
force_state_transition(alice_channel, bob_channel)
alice_ctx = alice_channel.current_commitment(LOCAL)
bob_ctx = bob_channel.current_commitment(LOCAL)
alice_ctx = alice_channel.get_latest_commitment(LOCAL)
bob_ctx = bob_channel.get_latest_commitment(LOCAL)
new_values = [x.value for x in bob_ctx.outputs() ]
self.assertNotEqual(old_values, new_values)
self.assertEqual(len(alice_ctx.outputs()), 3)
@ -820,7 +809,7 @@ class TestDust(unittest.TestCase):
bob_channel.settle_htlc(paymentPreimage, bobHtlcIndex)
alice_channel.receive_htlc_settle(paymentPreimage, aliceHtlcIndex)
force_state_transition(bob_channel, alice_channel)
self.assertEqual(len(alice_channel.pending_commitment(LOCAL).outputs()), 2)
self.assertEqual(len(alice_channel.get_next_commitment(LOCAL).outputs()), 2)
self.assertEqual(alice_channel.total_msat(SENT) // 1000, htlcAmt)
def force_state_transition(chanA, chanB):

Loading…
Cancel
Save