|
|
@ -29,6 +29,7 @@ import json |
|
|
|
from enum import IntEnum |
|
|
|
from typing import Optional, Dict, List, Tuple, NamedTuple, Set, Callable, Iterable, Sequence, TYPE_CHECKING |
|
|
|
import time |
|
|
|
import threading |
|
|
|
|
|
|
|
from . import ecc |
|
|
|
from .util import bfh, bh2u |
|
|
@ -99,6 +100,8 @@ class ChannelJsonEncoder(json.JSONEncoder): |
|
|
|
return o.serialize() |
|
|
|
if isinstance(o, set): |
|
|
|
return list(o) |
|
|
|
if hasattr(o, 'to_json') and callable(o.to_json): |
|
|
|
return o.to_json() |
|
|
|
return super().default(o) |
|
|
|
|
|
|
|
RevokeAndAck = namedtuple("RevokeAndAck", ["per_commitment_secret", "next_per_commitment_point"]) |
|
|
@ -110,7 +113,7 @@ class RemoteCtnTooFarInFuture(Exception): pass |
|
|
|
def decodeAll(d, local): |
|
|
|
for k, v in d.items(): |
|
|
|
if k == 'revocation_store': |
|
|
|
yield (k, RevocationStore.from_json_obj(v)) |
|
|
|
yield (k, RevocationStore(v)) |
|
|
|
elif k.endswith("_basepoint") or k.endswith("_key"): |
|
|
|
if local: |
|
|
|
yield (k, Keypair(**dict(decodeAll(v, local)))) |
|
|
@ -152,6 +155,7 @@ class Channel(Logger): |
|
|
|
self.lnworker = lnworker # type: Optional[LNWallet] |
|
|
|
self.sweep_address = sweep_address |
|
|
|
assert 'local_state' not in state |
|
|
|
self.db_lock = self.lnworker.wallet.storage.db.lock if self.lnworker else threading.RLock() |
|
|
|
self.config = {} |
|
|
|
self.config[LOCAL] = state["local_config"] |
|
|
|
if type(self.config[LOCAL]) is not LocalConfig: |
|
|
@ -181,6 +185,7 @@ class Channel(Logger): |
|
|
|
self.peer_state = peer_states.DISCONNECTED |
|
|
|
self.sweep_info = {} # type: Dict[str, Dict[str, SweepInfo]] |
|
|
|
self._outgoing_channel_update = None # type: Optional[bytes] |
|
|
|
self.revocation_store = RevocationStore(state["revocation_store"]) |
|
|
|
|
|
|
|
def get_feerate(self, subject, ctn): |
|
|
|
return self.hm.get_feerate(subject, ctn) |
|
|
@ -211,12 +216,13 @@ class Channel(Logger): |
|
|
|
return out |
|
|
|
|
|
|
|
def open_with_first_pcp(self, remote_pcp, remote_sig): |
|
|
|
self.config[REMOTE] = self.config[REMOTE]._replace(current_per_commitment_point=remote_pcp, |
|
|
|
next_per_commitment_point=None) |
|
|
|
self.config[LOCAL] = self.config[LOCAL]._replace(current_commitment_signature=remote_sig) |
|
|
|
self.hm.channel_open_finished() |
|
|
|
self.peer_state = peer_states.GOOD |
|
|
|
self.set_state(channel_states.OPENING) |
|
|
|
with self.db_lock: |
|
|
|
self.config[REMOTE].current_per_commitment_point=remote_pcp |
|
|
|
self.config[REMOTE].next_per_commitment_point=None |
|
|
|
self.config[LOCAL].current_commitment_signature=remote_sig |
|
|
|
self.hm.channel_open_finished() |
|
|
|
self.peer_state = peer_states.GOOD |
|
|
|
self.set_state(channel_states.OPENING) |
|
|
|
|
|
|
|
def set_state(self, state): |
|
|
|
""" set on-chain state """ |
|
|
@ -279,7 +285,8 @@ class Channel(Logger): |
|
|
|
self._check_can_pay(htlc.amount_msat) |
|
|
|
if htlc.htlc_id is None: |
|
|
|
htlc = htlc._replace(htlc_id=self.hm.get_next_htlc_id(LOCAL)) |
|
|
|
self.hm.send_htlc(htlc) |
|
|
|
with self.db_lock: |
|
|
|
self.hm.send_htlc(htlc) |
|
|
|
self.logger.info("add_htlc") |
|
|
|
return htlc |
|
|
|
|
|
|
@ -300,7 +307,8 @@ class Channel(Logger): |
|
|
|
raise RemoteMisbehaving('Remote dipped below channel reserve.' +\ |
|
|
|
f' Available at remote: {self.available_to_spend(REMOTE)},' +\ |
|
|
|
f' HTLC amount: {htlc.amount_msat}') |
|
|
|
self.hm.recv_htlc(htlc) |
|
|
|
with self.db_lock: |
|
|
|
self.hm.recv_htlc(htlc) |
|
|
|
self.logger.info("receive_htlc") |
|
|
|
return htlc |
|
|
|
|
|
|
@ -346,9 +354,8 @@ class Channel(Logger): |
|
|
|
htlcsigs.append((ctx_output_idx, htlc_sig)) |
|
|
|
htlcsigs.sort() |
|
|
|
htlcsigs = [x[1] for x in htlcsigs] |
|
|
|
|
|
|
|
self.hm.send_ctx() |
|
|
|
|
|
|
|
with self.db_lock: |
|
|
|
self.hm.send_ctx() |
|
|
|
return sig_64, htlcsigs |
|
|
|
|
|
|
|
def receive_new_commitment(self, sig, htlc_sigs): |
|
|
@ -395,11 +402,10 @@ class Channel(Logger): |
|
|
|
pcp=pcp, |
|
|
|
ctx=pending_local_commitment, |
|
|
|
ctx_output_idx=ctx_output_idx) |
|
|
|
|
|
|
|
self.hm.recv_ctx() |
|
|
|
self.config[LOCAL]=self.config[LOCAL]._replace( |
|
|
|
current_commitment_signature=sig, |
|
|
|
current_htlc_signatures=htlc_sigs_string) |
|
|
|
with self.db_lock: |
|
|
|
self.hm.recv_ctx() |
|
|
|
self.config[LOCAL].current_commitment_signature=sig |
|
|
|
self.config[LOCAL].current_htlc_signatures=htlc_sigs_string |
|
|
|
|
|
|
|
def verify_htlc(self, *, htlc: UpdateAddHtlc, htlc_sig: bytes, htlc_direction: Direction, |
|
|
|
pcp: bytes, ctx: Transaction, ctx_output_idx: int) -> None: |
|
|
@ -429,7 +435,8 @@ class Channel(Logger): |
|
|
|
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() |
|
|
|
with self.db_lock: |
|
|
|
self.hm.send_rev() |
|
|
|
received = self.hm.received_in_ctn(new_ctn) |
|
|
|
sent = self.hm.sent_in_ctn(new_ctn) |
|
|
|
if self.lnworker: |
|
|
@ -451,13 +458,12 @@ class Channel(Logger): |
|
|
|
if cur_point != derived_point: |
|
|
|
raise Exception('revoked secret not for current point') |
|
|
|
|
|
|
|
self.config[REMOTE].revocation_store.add_next_entry(revocation.per_commitment_secret) |
|
|
|
##### start applying fee/htlc changes |
|
|
|
self.hm.recv_rev() |
|
|
|
self.config[REMOTE]=self.config[REMOTE]._replace( |
|
|
|
current_per_commitment_point=self.config[REMOTE].next_per_commitment_point, |
|
|
|
next_per_commitment_point=revocation.next_per_commitment_point, |
|
|
|
) |
|
|
|
with self.db_lock: |
|
|
|
self.revocation_store.add_next_entry(revocation.per_commitment_secret) |
|
|
|
##### start applying fee/htlc changes |
|
|
|
self.hm.recv_rev() |
|
|
|
self.config[REMOTE].current_per_commitment_point=self.config[REMOTE].next_per_commitment_point |
|
|
|
self.config[REMOTE].next_per_commitment_point=revocation.next_per_commitment_point |
|
|
|
|
|
|
|
def balance(self, whose, *, ctx_owner=HTLCOwner.LOCAL, ctn=None): |
|
|
|
""" |
|
|
@ -548,7 +554,7 @@ class Channel(Logger): |
|
|
|
secret = None |
|
|
|
point = conf.current_per_commitment_point |
|
|
|
else: |
|
|
|
secret = conf.revocation_store.retrieve_secret(RevocationStore.START_INDEX - ctn) |
|
|
|
secret = self.revocation_store.retrieve_secret(RevocationStore.START_INDEX - ctn) |
|
|
|
point = secret_to_pubkey(int.from_bytes(secret, 'big')) |
|
|
|
else: |
|
|
|
secret = get_per_commitment_secret_from_seed(self.config[LOCAL].per_commitment_secret_seed, RevocationStore.START_INDEX - ctn) |
|
|
@ -624,15 +630,18 @@ class Channel(Logger): |
|
|
|
htlc = log['adds'][htlc_id] |
|
|
|
assert htlc.payment_hash == sha256(preimage) |
|
|
|
assert htlc_id not in log['settles'] |
|
|
|
self.hm.recv_settle(htlc_id) |
|
|
|
with self.db_lock: |
|
|
|
self.hm.recv_settle(htlc_id) |
|
|
|
|
|
|
|
def fail_htlc(self, htlc_id): |
|
|
|
self.logger.info("fail_htlc") |
|
|
|
self.hm.send_fail(htlc_id) |
|
|
|
with self.db_lock: |
|
|
|
self.hm.send_fail(htlc_id) |
|
|
|
|
|
|
|
def receive_fail_htlc(self, htlc_id): |
|
|
|
self.logger.info("receive_fail_htlc") |
|
|
|
self.hm.recv_fail(htlc_id) |
|
|
|
with self.db_lock: |
|
|
|
self.hm.recv_fail(htlc_id) |
|
|
|
|
|
|
|
def pending_local_fee(self): |
|
|
|
return self.constraints.capacity - sum(x.value for x in self.get_next_commitment(LOCAL).outputs()) |
|
|
@ -641,10 +650,11 @@ class Channel(Logger): |
|
|
|
# feerate uses sat/kw |
|
|
|
if self.constraints.is_initiator != from_us: |
|
|
|
raise Exception(f"Cannot update_fee: wrong initiator. us: {from_us}") |
|
|
|
if from_us: |
|
|
|
self.hm.send_update_fee(feerate) |
|
|
|
else: |
|
|
|
self.hm.recv_update_fee(feerate) |
|
|
|
with self.db_lock: |
|
|
|
if from_us: |
|
|
|
self.hm.send_update_fee(feerate) |
|
|
|
else: |
|
|
|
self.hm.recv_update_fee(feerate) |
|
|
|
|
|
|
|
def to_save(self): |
|
|
|
to_save = { |
|
|
@ -656,6 +666,7 @@ class Channel(Logger): |
|
|
|
"funding_outpoint": self.funding_outpoint, |
|
|
|
"node_id": self.node_id, |
|
|
|
"log": self.hm.to_save(), |
|
|
|
"revocation_store": self.revocation_store, |
|
|
|
"onion_keys": str_bytes_dict_to_save(self.onion_keys), |
|
|
|
"state": self._state.name, |
|
|
|
"data_loss_protect_remote_pcp": str_bytes_dict_to_save(self.data_loss_protect_remote_pcp), |
|
|
|