2018-10-25 19:16:55 +02:00
# Copyright (C) 2018 The Electrum developers
# Copyright (C) 2015-2018 The Lightning Network Developers
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# API (method signatures) copied from lnd
# 42de4400bff5105352d0552155f73589166d162b
2018-10-11 17:06:37 +02:00
from collections import namedtuple , defaultdict
2018-06-27 20:23:03 +02:00
import binascii
import json
2018-09-13 19:59:12 +02:00
from enum import Enum , auto
2018-10-22 18:57:51 +02:00
from typing import Optional , Dict , List , Tuple
2018-09-12 16:17:10 +02:00
2018-07-20 16:17:18 +02:00
from . util import bfh , PrintError , bh2u
2018-10-05 19:37:55 +02:00
from . bitcoin import Hash , TYPE_SCRIPT , TYPE_ADDRESS
2018-07-19 10:39:29 +02:00
from . bitcoin import redeem_script_to_address
2018-06-11 00:14:38 +02:00
from . crypto import sha256
2018-06-11 19:53:15 +02:00
from . import ecc
2018-10-10 22:54:30 +02:00
from . lnutil import Outpoint , LocalConfig , RemoteConfig , Keypair , OnlyPubkeyKeypair , ChannelConstraints , RevocationStore , EncumberedTransaction
2018-06-28 15:50:45 +02:00
from . lnutil import get_per_commitment_secret_from_seed
2018-10-05 19:37:55 +02:00
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
2018-10-22 18:57:51 +02:00
from . lnutil import sign_and_get_sig_string , privkey_to_pubkey , make_htlc_tx_witness
2018-06-28 15:50:45 +02:00
from . lnutil import make_htlc_tx_with_open_channel , make_commitment , make_received_htlc , make_offered_htlc
from . lnutil import HTLC_TIMEOUT_WEIGHT , HTLC_SUCCESS_WEIGHT
2018-10-22 21:50:13 +02:00
from . lnutil import funding_output_script , LOCAL , REMOTE , HTLCOwner , make_closing_tx , make_commitment_outputs
2018-10-23 20:32:18 +02:00
from . lnutil import ScriptHtlc , SENT , RECEIVED , PaymentFailure , calc_onchain_fees , RemoteMisbehaving
2018-10-05 19:37:55 +02:00
from . transaction import Transaction , TxOutput , construct_witness
from . simple_config import SimpleConfig , FEERATE_FALLBACK_STATIC_FEE
2018-09-12 16:17:10 +02:00
2018-10-25 00:22:42 +02:00
class ChannelJsonEncoder ( json . JSONEncoder ) :
def default ( self , o ) :
if isinstance ( o , bytes ) :
return binascii . hexlify ( o ) . decode ( " ascii " )
if isinstance ( o , RevocationStore ) :
return o . serialize ( )
return super ( ChannelJsonEncoder , self )
2018-05-29 11:51:48 +02:00
2018-06-14 15:34:51 +02:00
RevokeAndAck = namedtuple ( " RevokeAndAck " , [ " per_commitment_secret " , " next_per_commitment_point " ] )
2018-05-29 11:51:48 +02:00
2018-09-13 19:59:12 +02:00
class FeeUpdateProgress ( Enum ) :
FUNDEE_SIGNED = auto ( )
FUNDEE_ACKED = auto ( )
FUNDER_SIGNED = auto ( )
2018-09-12 23:37:45 +02:00
2018-09-13 19:59:12 +02:00
FUNDEE_SIGNED = FeeUpdateProgress . FUNDEE_SIGNED
FUNDEE_ACKED = FeeUpdateProgress . FUNDEE_ACKED
FUNDER_SIGNED = FeeUpdateProgress . FUNDER_SIGNED
2018-09-12 23:37:45 +02:00
2018-10-11 17:06:37 +02:00
class FeeUpdate ( defaultdict ) :
2018-10-04 16:05:23 +02:00
def __init__ ( self , chan , rate ) :
2018-10-11 17:06:37 +02:00
super ( ) . __init__ ( lambda : False )
2018-10-04 16:05:23 +02:00
self . rate = rate
2018-09-13 19:59:12 +02:00
self . chan = chan
2018-07-20 16:44:03 +02:00
2018-09-13 19:59:12 +02:00
def pending_feerate ( self , subject ) :
2018-10-11 17:06:37 +02:00
if self [ FUNDEE_ACKED ] :
2018-09-13 19:59:12 +02:00
return self . rate
if subject == REMOTE and self . chan . constraints . is_initiator :
return self . rate
if subject == LOCAL and not self . chan . constraints . is_initiator :
return self . rate
2018-09-26 15:08:57 +02:00
# implicit return None
2018-07-02 17:51:57 +02:00
2018-10-10 16:58:31 +02:00
class UpdateAddHtlc ( namedtuple ( ' UpdateAddHtlc ' , [ ' amount_msat ' , ' payment_hash ' , ' cltv_expiry ' , ' locked_in ' , ' htlc_id ' ] ) ) :
2018-09-18 18:38:57 +02:00
__slots__ = ( )
def __new__ ( cls , * args , * * kwargs ) :
if len ( args ) > 0 :
args = list ( args )
if type ( args [ 1 ] ) is str :
args [ 1 ] = bfh ( args [ 1 ] )
args [ 3 ] = { HTLCOwner ( int ( x ) ) : y for x , y in args [ 3 ] . items ( ) }
return super ( ) . __new__ ( cls , * args )
if type ( kwargs [ ' payment_hash ' ] ) is str :
kwargs [ ' payment_hash ' ] = bfh ( kwargs [ ' payment_hash ' ] )
if ' locked_in ' not in kwargs :
kwargs [ ' locked_in ' ] = { LOCAL : None , REMOTE : None }
else :
2018-10-10 22:54:30 +02:00
kwargs [ ' locked_in ' ] = { HTLCOwner ( int ( x ) ) : y for x , y in kwargs [ ' locked_in ' ] . items ( ) }
2018-09-18 18:38:57 +02:00
return super ( ) . __new__ ( cls , * * kwargs )
2018-05-29 11:51:48 +02:00
2018-10-10 22:54:30 +02:00
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 ) )
2018-06-27 20:23:03 +02:00
else :
2018-10-10 22:54:30 +02:00
yield ( k , v )
def htlcsum ( htlcs ) :
return sum ( [ x . amount_msat for x in htlcs ] )
2018-05-29 11:51:48 +02:00
2018-10-17 19:35:15 +02:00
# following two functions are used because json
# doesn't store int keys and byte string values
def str_bytes_dict_from_save ( x ) :
return { int ( k ) : bfh ( v ) for k , v in x . items ( ) }
def str_bytes_dict_to_save ( x ) :
return { str ( k ) : bh2u ( v ) for k , v in x . items ( ) }
2018-10-11 17:15:25 +02:00
class Channel ( PrintError ) :
2018-05-29 11:51:48 +02:00
def diagnostic_name ( self ) :
2018-10-22 15:05:58 +02:00
if self . name :
return str ( self . name )
try :
return f " lnchan_ { bh2u ( self . channel_id [ - 4 : ] ) } "
except :
return super ( ) . diagnostic_name ( )
2018-05-29 11:51:48 +02:00
2018-06-11 00:14:38 +02:00
def __init__ ( self , state , name = None ) :
2018-10-10 22:54:30 +02:00
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 " ]
2018-10-08 20:31:15 +02:00
self . short_channel_id_predicted = self . short_channel_id
2018-10-17 19:35:15 +02:00
self . onion_keys = str_bytes_dict_from_save ( state . get ( ' onion_keys ' , { } ) )
2018-06-27 20:23:03 +02:00
2018-09-12 16:17:10 +02:00
# FIXME this is a tx serialised in the custom electrum partial tx format.
# we should not persist txns in this format. we should persist htlcs, and be able to derive
# any past commitment transaction and use that instead; until then...
self . remote_commitment_to_be_revoked = Transaction ( state [ " remote_commitment_to_be_revoked " ] )
2018-10-17 19:35:15 +02:00
template = lambda : {
2018-10-22 18:57:51 +02:00
' adds ' : { } , # Dict[HTLC_ID, UpdateAddHtlc]
' settles ' : [ ] , # List[HTLC_ID]
2018-10-23 20:32:18 +02:00
' fails ' : [ ] , # List[HTLC_ID]
2018-10-17 19:35:15 +02:00
}
2018-10-10 19:52:46 +02:00
self . log = { LOCAL : template ( ) , REMOTE : template ( ) }
2018-09-18 18:38:57 +02:00
for strname , subject in [ ( ' remote_log ' , REMOTE ) , ( ' local_log ' , LOCAL ) ] :
if strname not in state : continue
2018-10-10 19:52:46 +02:00
for y in state [ strname ] :
2018-10-10 22:54:30 +02:00
htlc = UpdateAddHtlc ( * * y )
2018-10-10 19:52:46 +02:00
self . log [ subject ] [ ' adds ' ] [ htlc . htlc_id ] = htlc
2018-05-29 11:51:48 +02:00
self . name = name
2018-09-13 19:59:12 +02:00
self . fee_mgr = [ ]
2018-07-02 17:51:57 +02:00
self . local_commitment = self . pending_local_commitment
self . remote_commitment = self . pending_remote_commitment
2018-05-29 11:51:48 +02:00
2018-07-30 13:51:03 +02:00
self . _is_funding_txo_spent = None # "don't know"
self . set_state ( ' DISCONNECTED ' )
2018-09-12 16:17:10 +02:00
self . lnwatcher = None
2018-10-10 16:58:31 +02:00
self . settled = { LOCAL : state . get ( ' settled_local ' , [ ] ) , REMOTE : state . get ( ' settled_remote ' , [ ] ) }
2018-07-30 13:51:03 +02:00
def set_state ( self , state : str ) :
self . _state = state
def get_state ( self ) :
return self . _state
2018-10-23 20:32:18 +02:00
def _check_can_pay ( self , amount_msat : int ) - > None :
2018-10-15 12:45:07 +02:00
if self . get_state ( ) != ' OPEN ' :
raise PaymentFailure ( ' Channel not open ' )
2018-10-17 20:01:45 +02:00
if self . available_to_spend ( LOCAL ) < amount_msat :
2018-10-24 20:39:07 +02:00
raise PaymentFailure ( f ' Not enough local balance. Have: { self . available_to_spend ( LOCAL ) } , Need: { amount_msat } ' )
2018-10-15 12:45:07 +02:00
if len ( self . htlcs ( LOCAL , only_pending = True ) ) + 1 > self . config [ REMOTE ] . max_accepted_htlcs :
raise PaymentFailure ( ' Too many HTLCs already in channel ' )
2018-10-23 20:32:18 +02:00
current_htlc_sum = htlcsum ( self . htlcs ( LOCAL , only_pending = True ) )
if current_htlc_sum + amount_msat > self . config [ REMOTE ] . max_htlc_value_in_flight_msat :
raise PaymentFailure ( f ' HTLC value sum (sum of pending htlcs: { current_htlc_sum / 1000 } sat plus new htlc: { amount_msat / 1000 } sat) would exceed max allowed: { self . config [ REMOTE ] . max_htlc_value_in_flight_msat / 1000 } sat ' )
2018-10-22 20:39:44 +02:00
if amount_msat < = 0 : # FIXME htlc_minimum_msat
raise PaymentFailure ( f ' HTLC value too small: { amount_msat } msat ' )
2018-10-15 12:45:07 +02:00
def can_pay ( self , amount_msat ) :
try :
2018-10-23 20:32:18 +02:00
self . _check_can_pay ( amount_msat )
except PaymentFailure :
2018-10-15 12:45:07 +02:00
return False
return True
2018-07-30 13:51:03 +02:00
def set_funding_txo_spentness ( self , is_spent : bool ) :
assert isinstance ( is_spent , bool )
self . _is_funding_txo_spent = is_spent
def should_try_to_reestablish_peer ( self ) - > bool :
return self . _is_funding_txo_spent is False and self . _state == ' DISCONNECTED '
2018-07-16 16:51:32 +02:00
2018-07-19 13:10:41 +02:00
def get_funding_address ( self ) :
2018-10-10 22:54:30 +02:00
script = funding_output_script ( self . config [ LOCAL ] , self . config [ REMOTE ] )
2018-07-19 10:39:29 +02:00
return redeem_script_to_address ( ' p2wsh ' , script )
2018-05-29 11:51:48 +02:00
def add_htlc ( self , htlc ) :
"""
AddHTLC adds an HTLC to the state machine ' s local update log. This method
should be called when preparing to send an outgoing HTLC .
"""
2018-09-18 18:38:57 +02:00
assert type ( htlc ) is dict
2018-10-23 20:32:18 +02:00
self . _check_can_pay ( htlc [ ' amount_msat ' ] )
2018-10-10 22:54:30 +02:00
htlc = UpdateAddHtlc ( * * htlc , htlc_id = self . config [ LOCAL ] . next_htlc_id )
2018-10-10 19:52:46 +02:00
self . log [ LOCAL ] [ ' adds ' ] [ htlc . htlc_id ] = htlc
2018-05-29 11:51:48 +02:00
self . print_error ( " add_htlc " )
2018-10-10 22:54:30 +02:00
self . config [ LOCAL ] = self . config [ LOCAL ] . _replace ( next_htlc_id = htlc . htlc_id + 1 )
2018-09-18 18:38:57 +02:00
return htlc . htlc_id
2018-05-29 11:51:48 +02:00
def receive_htlc ( self , htlc ) :
"""
ReceiveHTLC adds an HTLC to the state machine ' s remote update log. This
method should be called in response to receiving a new HTLC from the remote
party .
"""
2018-09-18 18:38:57 +02:00
assert type ( htlc ) is dict
2018-10-10 22:54:30 +02:00
htlc = UpdateAddHtlc ( * * htlc , htlc_id = self . config [ REMOTE ] . next_htlc_id )
2018-10-23 20:32:18 +02:00
if self . available_to_spend ( REMOTE ) < htlc . amount_msat :
2018-10-24 20:39:07 +02:00
raise RemoteMisbehaving ( ' Remote dipped below channel reserve. ' + \
f ' Available at remote: { self . available_to_spend ( REMOTE ) } , ' + \
f ' HTLC amount: { htlc . amount_msat } ' )
2018-10-23 20:32:18 +02:00
adds = self . log [ REMOTE ] [ ' adds ' ]
adds [ htlc . htlc_id ] = htlc
2018-09-18 18:38:57 +02:00
self . print_error ( " receive_htlc " )
2018-10-10 22:54:30 +02:00
self . config [ REMOTE ] = self . config [ REMOTE ] . _replace ( next_htlc_id = htlc . htlc_id + 1 )
2018-09-18 18:38:57 +02:00
return htlc . htlc_id
2018-05-29 11:51:48 +02:00
def sign_next_commitment ( self ) :
"""
SignNextCommitment signs a new commitment which includes any previous
unsettled HTLCs , any new HTLCs , and any modifications to prior HTLCs
committed in previous commitment updates . Signing a new commitment
decrements the available revocation window by 1. After a successful method
call , the remote party ' s commitment chain is extended by a new commitment
which includes all updates to the HTLC log prior to this method invocation .
The first return parameter is the signature for the commitment transaction
itself , while the second parameter is a slice of all HTLC signatures ( if
any ) . The HTLC signatures are sorted according to the BIP 69 order of the
HTLC ' s on the commitment transaction.
"""
self . print_error ( " sign_next_commitment " )
2018-10-23 20:32:18 +02:00
self . lock_in_htlc_changes ( LOCAL )
2018-05-29 11:51:48 +02:00
2018-09-12 16:17:10 +02:00
pending_remote_commitment = self . pending_remote_commitment
2018-10-10 22:54:30 +02:00
sig_64 = sign_and_get_sig_string ( pending_remote_commitment , self . config [ LOCAL ] , self . config [ REMOTE ] )
2018-07-10 19:26:54 +02:00
their_remote_htlc_privkey_number = derive_privkey (
2018-10-10 22:54:30 +02:00
int . from_bytes ( self . config [ LOCAL ] . htlc_basepoint . privkey , ' big ' ) ,
self . config [ REMOTE ] . next_per_commitment_point )
2018-07-10 19:26:54 +02:00
their_remote_htlc_privkey = their_remote_htlc_privkey_number . to_bytes ( 32 , ' big ' )
for_us = False
htlcsigs = [ ]
2018-09-25 17:08:46 +02:00
for we_receive , htlcs in zip ( [ True , False ] , [ self . included_htlcs ( REMOTE , REMOTE ) , self . included_htlcs ( REMOTE , LOCAL ) ] ) :
2018-07-10 19:26:54 +02:00
for htlc in htlcs :
2018-10-10 22:54:30 +02:00
args = [ self . config [ REMOTE ] . next_per_commitment_point , for_us , we_receive , pending_remote_commitment , htlc ]
2018-10-22 18:57:51 +02:00
_script , htlc_tx = make_htlc_tx_with_open_channel ( self , * args )
2018-07-10 19:26:54 +02:00
sig = bfh ( htlc_tx . sign_txin ( 0 , their_remote_htlc_privkey ) )
htlc_sig = ecc . sig_string_from_der_sig ( sig [ : - 1 ] )
2018-09-25 18:04:00 +02:00
htlcsigs . append ( ( pending_remote_commitment . htlc_output_indices [ htlc . payment_hash ] , htlc_sig ) )
2018-07-10 19:26:54 +02:00
2018-09-13 19:59:12 +02:00
for pending_fee in self . fee_mgr :
2018-10-04 16:05:23 +02:00
if not self . constraints . is_initiator :
2018-10-11 17:06:37 +02:00
pending_fee [ FUNDEE_SIGNED ] = True
if self . constraints . is_initiator and pending_fee [ FUNDEE_ACKED ] :
pending_fee [ FUNDER_SIGNED ] = True
2018-07-20 16:44:03 +02:00
2018-10-05 19:37:55 +02:00
self . process_new_offchain_ctx ( pending_remote_commitment , ours = False )
2018-09-12 16:17:10 +02:00
2018-09-25 17:08:46 +02:00
htlcsigs . sort ( )
2018-09-25 18:04:00 +02:00
htlcsigs = [ x [ 1 ] for x in htlcsigs ]
2018-09-25 17:08:46 +02:00
2018-07-10 19:26:54 +02:00
return sig_64 , htlcsigs
2018-05-29 11:51:48 +02:00
2018-10-23 20:32:18 +02:00
def lock_in_htlc_changes ( self , subject ) :
for sub in ( LOCAL , REMOTE ) :
for htlc_id in self . log [ - sub ] [ ' fails ' ] :
adds = self . log [ sub ] [ ' adds ' ]
htlc = adds . pop ( htlc_id )
self . log [ - sub ] [ ' fails ' ] . clear ( )
for htlc in self . log [ subject ] [ ' adds ' ] . values ( ) :
if htlc . locked_in [ subject ] is None :
htlc . locked_in [ subject ] = self . config [ subject ] . ctn
2018-05-29 11:51:48 +02:00
def receive_new_commitment ( self , sig , htlc_sigs ) :
"""
ReceiveNewCommitment process a signature for a new commitment state sent by
the remote party . This method should be called in response to the
remote party initiating a new change , or when the remote party sends a
signature fully accepting a new state we ' ve initiated. If we are able to
successfully validate the signature , then the generated commitment is added
to our local commitment chain . Once we send a revocation for our prior
state , then this newly added commitment becomes our current accepted channel
state .
"""
self . print_error ( " receive_new_commitment " )
2018-10-23 20:32:18 +02:00
self . lock_in_htlc_changes ( REMOTE )
2018-06-11 19:53:15 +02:00
assert len ( htlc_sigs ) == 0 or type ( htlc_sigs [ 0 ] ) is bytes
2018-09-12 16:17:10 +02:00
pending_local_commitment = self . pending_local_commitment
preimage_hex = pending_local_commitment . serialize_preimage ( 0 )
2018-07-10 19:26:54 +02:00
pre_hash = Hash ( bfh ( preimage_hex ) )
2018-10-10 22:54:30 +02:00
if not ecc . verify_signature ( self . config [ REMOTE ] . multisig_key . pubkey , sig , pre_hash ) :
2018-07-20 16:17:18 +02:00
raise Exception ( ' failed verifying signature of our updated commitment transaction: ' + bh2u ( sig ) + ' preimage is ' + preimage_hex )
2018-07-10 19:26:54 +02:00
2018-10-22 18:57:51 +02:00
htlc_sigs_string = b ' ' . join ( htlc_sigs )
2018-07-10 19:26:54 +02:00
2018-10-22 18:57:51 +02:00
htlc_sigs = htlc_sigs [ : ] # copy cause we will delete now
2018-09-25 17:08:46 +02:00
for htlcs , we_receive in [ ( self . included_htlcs ( LOCAL , REMOTE ) , True ) , ( self . included_htlcs ( LOCAL , LOCAL ) , False ) ] :
for htlc in htlcs :
2018-10-22 18:57:51 +02:00
idx = self . verify_htlc ( htlc , htlc_sigs , we_receive )
del htlc_sigs [ idx ]
2018-09-25 17:08:46 +02:00
if len ( htlc_sigs ) != 0 : # all sigs should have been popped above
raise Exception ( ' failed verifying HTLC signatures: invalid amount of correct signatures ' )
2018-06-14 15:34:51 +02:00
2018-10-22 18:57:51 +02:00
self . config [ LOCAL ] = self . config [ LOCAL ] . _replace (
current_commitment_signature = sig ,
current_htlc_signatures = htlc_sigs_string )
2018-09-13 19:59:12 +02:00
for pending_fee in self . fee_mgr :
2018-10-04 16:05:23 +02:00
if not self . constraints . is_initiator :
2018-10-11 17:06:37 +02:00
pending_fee [ FUNDEE_SIGNED ] = True
if self . constraints . is_initiator and pending_fee [ FUNDEE_ACKED ] :
pending_fee [ FUNDER_SIGNED ] = True
2018-07-20 16:44:03 +02:00
2018-10-05 19:37:55 +02:00
self . process_new_offchain_ctx ( pending_local_commitment , ours = True )
2018-09-12 16:17:10 +02:00
2018-10-22 18:57:51 +02:00
def verify_htlc ( self , htlc , htlc_sigs , we_receive ) :
_ , this_point , _ = self . points
_script , htlc_tx = make_htlc_tx_with_open_channel ( self , this_point , True , we_receive , self . pending_local_commitment , htlc )
pre_hash = Hash ( bfh ( htlc_tx . serialize_preimage ( 0 ) ) )
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 ) :
return idx
else :
raise Exception ( f ' failed verifying HTLC signatures: { htlc } ' )
2018-07-20 16:44:03 +02:00
2018-05-29 11:51:48 +02:00
def revoke_current_commitment ( self ) :
"""
RevokeCurrentCommitment revokes the next lowest unrevoked commitment
transaction in the local commitment chain . As a result the edge of our
revocation window is extended by one , and the tail of our local commitment
chain is advanced by a single commitment . This now lowest unrevoked
commitment becomes our currently accepted state within the channel . This
method also returns the set of HTLC ' s currently active within the commitment
transaction . This return value allows callers to act once an HTLC has been
locked into our commitment transaction .
"""
self . print_error ( " revoke_current_commitment " )
2018-06-11 19:53:15 +02:00
last_secret , this_point , next_point = self . points
2018-10-11 17:06:37 +02:00
new_feerate = self . constraints . feerate
2018-07-20 16:44:03 +02:00
2018-10-04 16:05:23 +02:00
for pending_fee in self . fee_mgr [ : ] :
2018-10-11 17:06:37 +02:00
if not self . constraints . is_initiator and pending_fee [ FUNDEE_SIGNED ] :
new_feerate = pending_fee . rate
2018-10-04 16:05:23 +02:00
self . fee_mgr . remove ( pending_fee )
2018-07-20 16:44:03 +02:00
print ( " FEERATE CHANGE COMPLETE (non-initiator) " )
2018-10-11 17:06:37 +02:00
if self . constraints . is_initiator and pending_fee [ FUNDER_SIGNED ] :
new_feerate = pending_fee . rate
2018-10-04 16:05:23 +02:00
self . fee_mgr . remove ( pending_fee )
2018-07-20 16:44:03 +02:00
print ( " FEERATE CHANGE COMPLETE (initiator) " )
2018-07-02 17:51:57 +02:00
2018-10-10 22:54:30 +02:00
self . config [ LOCAL ] = self . config [ LOCAL ] . _replace (
ctn = self . config [ LOCAL ] . ctn + 1 ,
2018-06-11 19:53:15 +02:00
)
2018-10-11 17:06:37 +02:00
self . constraints = self . constraints . _replace (
feerate = new_feerate
2018-07-23 20:16:03 +02:00
)
2018-06-11 19:53:15 +02:00
2018-07-02 17:51:57 +02:00
self . local_commitment = self . pending_local_commitment
2018-06-14 15:34:51 +02:00
return RevokeAndAck ( last_secret , next_point ) , " current htlcs "
2018-06-11 19:53:15 +02:00
@property
def points ( self ) :
2018-10-10 22:54:30 +02:00
last_small_num = self . config [ LOCAL ] . ctn
2018-05-29 11:51:48 +02:00
this_small_num = last_small_num + 1
2018-07-09 00:15:55 +02:00
next_small_num = last_small_num + 2
2018-10-10 22:54:30 +02:00
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 )
2018-05-29 11:51:48 +02:00
this_point = secret_to_pubkey ( int . from_bytes ( this_secret , ' big ' ) )
2018-10-10 22:54:30 +02:00
next_secret = get_per_commitment_secret_from_seed ( self . config [ LOCAL ] . per_commitment_secret_seed , RevocationStore . START_INDEX - next_small_num )
2018-05-29 11:51:48 +02:00
next_point = secret_to_pubkey ( int . from_bytes ( next_secret , ' big ' ) )
2018-06-11 19:53:15 +02:00
return last_secret , this_point , next_point
2018-05-29 11:51:48 +02:00
2018-10-05 19:37:55 +02:00
# TODO batch sweeps
# TODO sweep HTLC outputs
def process_new_offchain_ctx ( self , ctx , ours : bool ) :
2018-10-08 17:20:31 +02:00
if not self . lnwatcher :
return
2018-10-05 19:37:55 +02:00
outpoint = self . funding_outpoint . to_str ( )
if ours :
2018-10-10 22:54:30 +02:00
ctn = self . config [ LOCAL ] . ctn + 1
2018-10-05 19:37:55 +02:00
our_per_commitment_secret = get_per_commitment_secret_from_seed (
2018-10-10 22:54:30 +02:00
self . config [ LOCAL ] . per_commitment_secret_seed , RevocationStore . START_INDEX - ctn )
2018-10-05 19:37:55 +02:00
our_cur_pcp = ecc . ECPrivkey ( our_per_commitment_secret ) . get_public_key_bytes ( compressed = True )
2018-10-22 18:57:51 +02:00
encumbered_sweeptxs = create_sweeptxs_for_our_ctx ( self , ctx , our_cur_pcp , self . sweep_address )
2018-10-05 19:37:55 +02:00
else :
2018-10-10 22:54:30 +02:00
their_cur_pcp = self . config [ REMOTE ] . next_per_commitment_point
2018-10-22 18:57:51 +02:00
encumbered_sweeptxs = [ ( None , maybe_create_sweeptx_for_their_ctx_to_remote ( self , ctx , their_cur_pcp , self . sweep_address ) ) ]
for prev_txid , encumbered_tx in encumbered_sweeptxs :
if prev_txid is None :
prev_txid = ctx . txid ( )
2018-10-22 21:50:13 +02:00
if encumbered_tx is not None :
self . lnwatcher . add_sweep_tx ( outpoint , prev_txid , encumbered_tx . to_json ( ) )
2018-10-05 19:37:55 +02:00
def process_new_revocation_secret ( self , per_commitment_secret : bytes ) :
2018-10-08 17:20:31 +02:00
if not self . lnwatcher :
return
2018-10-05 19:37:55 +02:00
outpoint = self . funding_outpoint . to_str ( )
ctx = self . remote_commitment_to_be_revoked
encumbered_sweeptx = maybe_create_sweeptx_for_their_ctx_to_local ( self , ctx , per_commitment_secret , self . sweep_address )
2018-10-13 14:35:56 +02:00
if encumbered_sweeptx :
self . lnwatcher . add_sweep_tx ( outpoint , ctx . txid ( ) , encumbered_sweeptx . to_json ( ) )
2018-10-05 19:37:55 +02:00
2018-05-29 11:51:48 +02:00
def receive_revocation ( self , revocation ) :
"""
ReceiveRevocation processes a revocation sent by the remote party for the
lowest unrevoked commitment within their commitment chain . We receive a
revocation either during the initial session negotiation wherein revocation
windows are extended , or in response to a state update that we initiate . If
successful , then the remote commitment chain is advanced by a single
commitment , and a log compaction is attempted .
Returns the forwarding package corresponding to the remote commitment height
that was revoked .
"""
self . print_error ( " receive_revocation " )
2018-10-10 22:54:30 +02:00
cur_point = self . config [ REMOTE ] . current_per_commitment_point
2018-09-12 16:17:10 +02:00
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 ' )
# FIXME not sure this is correct... but it seems to work
# if there are update_add_htlc msgs between commitment_signed and rev_ack,
# this might break
prev_remote_commitment = self . pending_remote_commitment
2018-10-10 22:54:30 +02:00
self . config [ REMOTE ] . revocation_store . add_next_entry ( revocation . per_commitment_secret )
2018-10-05 19:37:55 +02:00
self . process_new_revocation_secret ( revocation . per_commitment_secret )
2018-09-12 16:17:10 +02:00
2018-09-12 23:37:45 +02:00
def mark_settled ( subject ) :
"""
2018-10-10 19:52:46 +02:00
find pending settlements for subject ( LOCAL or REMOTE ) and mark them settled , return value of settled htlcs
2018-09-12 23:37:45 +02:00
"""
2018-10-10 22:54:30 +02:00
old_amount = htlcsum ( self . htlcs ( subject , False ) )
2018-06-15 16:35:29 +02:00
2018-10-10 19:52:46 +02:00
for htlc_id in self . log [ - subject ] [ ' settles ' ] :
adds = self . log [ subject ] [ ' adds ' ]
htlc = adds . pop ( htlc_id )
2018-10-10 16:58:31 +02:00
self . settled [ subject ] . append ( htlc . amount_msat )
2018-10-10 19:52:46 +02:00
self . log [ - subject ] [ ' settles ' ] . clear ( )
2018-06-15 16:35:29 +02:00
2018-10-10 22:54:30 +02:00
return old_amount - htlcsum ( self . htlcs ( subject , False ) )
2018-05-29 11:51:48 +02:00
2018-09-12 23:37:45 +02:00
sent_this_batch = mark_settled ( LOCAL )
received_this_batch = mark_settled ( REMOTE )
2018-06-25 21:06:50 +02:00
2018-10-10 22:54:30 +02:00
next_point = self . config [ REMOTE ] . next_per_commitment_point
2018-05-29 11:51:48 +02:00
2018-06-25 21:06:50 +02:00
print ( " RECEIVED " , received_this_batch )
print ( " SENT " , sent_this_batch )
2018-10-10 22:54:30 +02:00
self . config [ REMOTE ] = self . config [ REMOTE ] . _replace (
ctn = self . config [ REMOTE ] . ctn + 1 ,
2018-06-27 20:23:03 +02:00
current_per_commitment_point = next_point ,
next_per_commitment_point = revocation . next_per_commitment_point ,
2018-10-10 22:54:30 +02:00
amount_msat = self . config [ REMOTE ] . amount_msat + ( sent_this_batch - received_this_batch )
2018-06-27 20:23:03 +02:00
)
2018-10-10 22:54:30 +02:00
self . config [ LOCAL ] = self . config [ LOCAL ] . _replace (
amount_msat = self . config [ LOCAL ] . amount_msat + ( received_this_batch - sent_this_batch )
2018-05-29 11:51:48 +02:00
)
2018-07-20 16:44:03 +02:00
2018-09-13 19:59:12 +02:00
for pending_fee in self . fee_mgr :
2018-10-04 16:05:23 +02:00
if self . constraints . is_initiator :
2018-10-11 17:06:37 +02:00
pending_fee [ FUNDEE_ACKED ] = True
2018-07-20 16:44:03 +02:00
2018-07-02 17:51:57 +02:00
self . local_commitment = self . pending_local_commitment
self . remote_commitment = self . pending_remote_commitment
2018-09-12 16:17:10 +02:00
self . remote_commitment_to_be_revoked = prev_remote_commitment
2018-09-12 21:01:00 +02:00
return received_this_batch , sent_this_batch
2018-05-29 11:51:48 +02:00
2018-09-18 18:38:57 +02:00
def balance ( self , subject ) :
2018-10-24 20:39:07 +02:00
"""
This balance in mSAT is not including reserve and fees .
So a node cannot actually use it ' s whole balance.
But this number is simple , since it is derived simply
from the initial balance , and the value of settled HTLCs .
Note that it does not decrease once an HTLC is added ,
failed or fulfilled , since the balance change is only
commited to later when the respective commitment
transaction as been revoked .
"""
2018-10-10 22:54:30 +02:00
initial = self . config [ subject ] . initial_msat
2018-09-18 18:38:57 +02:00
2018-10-10 16:58:31 +02:00
initial - = sum ( self . settled [ subject ] )
initial + = sum ( self . settled [ - subject ] )
2018-09-18 18:38:57 +02:00
2018-10-10 22:54:30 +02:00
assert initial == self . config [ subject ] . amount_msat
2018-09-18 18:38:57 +02:00
return initial
2018-10-24 20:39:07 +02:00
def balance_minus_outgoing_htlcs ( self , subject ) :
"""
This balance in mSAT , which includes the value of
pending outgoing HTLCs , is used in the UI .
"""
2018-10-23 20:32:18 +02:00
return self . balance ( subject ) \
2018-10-24 20:39:07 +02:00
- htlcsum ( self . log [ subject ] [ ' adds ' ] . values ( ) )
def available_to_spend ( self , subject ) :
"""
This balance in mSAT , while technically correct , can
not be used in the UI cause it fluctuates ( commit fee )
"""
return self . balance_minus_outgoing_htlcs ( subject ) \
2018-10-23 20:32:18 +02:00
- htlcsum ( self . log [ subject ] [ ' adds ' ] . values ( ) ) \
2018-10-24 20:39:07 +02:00
- self . config [ - subject ] . reserve_sat * 1000 \
2018-10-23 20:32:18 +02:00
- calc_onchain_fees (
# TODO should we include a potential new htlc, when we are called from receive_htlc?
len ( list ( self . included_htlcs ( subject , LOCAL ) ) + list ( self . included_htlcs ( subject , REMOTE ) ) ) ,
self . pending_feerate ( subject ) ,
2018-10-24 20:39:07 +02:00
True , # for_us
2018-10-23 20:32:18 +02:00
self . constraints . is_initiator ,
) [ subject ]
2018-10-17 20:01:45 +02:00
2018-06-25 21:06:50 +02:00
def amounts ( self ) :
2018-10-10 22:54:30 +02:00
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 - \
2018-09-12 23:37:45 +02:00
unsettled_remote + local_settled - remote_settled
2018-10-10 22:54:30 +02:00
local_msat = self . config [ LOCAL ] . amount_msat - \
2018-09-12 23:37:45 +02:00
unsettled_local + remote_settled - local_settled
2018-07-25 17:10:25 +02:00
return remote_msat , local_msat
2018-06-25 21:06:50 +02:00
2018-09-25 17:08:46 +02:00
def included_htlcs ( self , subject , htlc_initiator ) :
2018-10-10 22:54:30 +02:00
"""
return filter of non - dust htlcs for subjects commitment transaction , initiated by given party
"""
2018-09-25 17:08:46 +02:00
feerate = self . pending_feerate ( subject )
2018-10-10 22:54:30 +02:00
conf = self . config [ subject ]
2018-09-25 17:08:46 +02:00
weight = HTLC_SUCCESS_WEIGHT if subject != htlc_initiator else HTLC_TIMEOUT_WEIGHT
2018-10-10 22:54:30 +02:00
htlcs = self . htlcs ( htlc_initiator , only_pending = True )
lnhtlc: multiply weight by feerate before rounding
This resolves the error formerly manifested as:
Traceback (most recent call last):
File "/home/janus/Skrivebord/lightning-rfc/tools/electrum/packages/jsonrpclib/SimpleJSONRPCServer.py", line 376, in _dispatch
return func(*params)
File "/home/janus/Skrivebord/lightning-rfc/tools/electrum/electrum/daemon.py", line 292, in run_cmdline
result = func(*args, **kwargs)
File "/home/janus/Skrivebord/lightning-rfc/tools/electrum/electrum/commands.py", line 87, in func_wrapper
return func(*args, **kwargs)
File "/home/janus/Skrivebord/lightning-rfc/tools/electrum/electrum/commands.py", line 697, in lnpay
return f.result()
File "/usr/lib/python3.6/concurrent/futures/_base.py", line 432, in result
return self.__get_result()
File "/usr/lib/python3.6/concurrent/futures/_base.py", line 384, in __get_result
raise self._exception
File "/home/janus/Skrivebord/lightning-rfc/tools/electrum/electrum/lnbase.py", line 887, in pay
sig_64, htlc_sigs = chan.sign_next_commitment()
File "/home/janus/Skrivebord/lightning-rfc/tools/electrum/electrum/lnhtlc.py", line 281, in sign_next_commitment
htlc_tx = make_htlc_tx_with_open_channel(self, *args)
File "/home/janus/Skrivebord/lightning-rfc/tools/electrum/electrum/lnutil.py", line 262, in make_htlc_tx_with_open_channel
commit.txid(), commit.htlc_output_indices[original_htlc_output_index],
KeyError: 0
2018-10-02 17:13:45 +02:00
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 )
2018-09-25 17:08:46 +02:00
2018-06-25 21:06:50 +02:00
@property
2018-07-02 17:51:57 +02:00
def pending_remote_commitment ( self ) :
2018-10-10 22:54:30 +02:00
this_point = self . config [ REMOTE ] . next_per_commitment_point
2018-09-28 16:40:32 +02:00
return self . make_commitment ( REMOTE , this_point )
2018-05-29 11:51:48 +02:00
2018-09-13 19:59:12 +02:00
def pending_feerate ( self , subject ) :
2018-10-11 17:06:37 +02:00
candidate = self . constraints . feerate
2018-09-13 19:59:12 +02:00
for pending_fee in self . fee_mgr :
x = pending_fee . pending_feerate ( subject )
if x is not None :
candidate = x
2018-10-11 17:06:37 +02:00
return candidate
2018-09-13 19:59:12 +02:00
2018-05-29 11:51:48 +02:00
@property
2018-07-02 17:51:57 +02:00
def pending_local_commitment ( self ) :
2018-06-11 19:53:15 +02:00
_ , this_point , _ = self . points
2018-09-28 16:40:32 +02:00
return self . make_commitment ( LOCAL , this_point )
2018-05-29 11:51:48 +02:00
2018-10-10 16:58:31 +02:00
def total_msat ( self , sub ) :
return sum ( self . settled [ sub ] )
2018-09-12 23:37:45 +02:00
2018-10-10 22:54:30 +02:00
def htlcs ( self , subject , only_pending ) :
2018-09-12 23:37:45 +02:00
"""
only_pending : require the htlc ' s settlement to be pending (needs additional signatures/acks)
"""
update_log = self . log [ subject ]
other_log = self . log [ - subject ]
2018-06-11 00:14:38 +02:00
res = [ ]
2018-10-10 19:52:46 +02:00
for htlc in update_log [ ' adds ' ] . values ( ) :
2018-09-12 23:37:45 +02:00
locked_in = htlc . locked_in [ subject ]
2018-10-23 20:32:18 +02:00
settled = htlc . htlc_id in other_log [ ' settles ' ]
failed = htlc . htlc_id in other_log [ ' fails ' ]
if locked_in is None :
continue
if only_pending == ( settled or failed ) :
2018-05-29 11:51:48 +02:00
continue
2018-06-11 00:14:38 +02:00
res . append ( htlc )
return res
2018-05-29 11:51:48 +02:00
2018-06-15 16:35:29 +02:00
def settle_htlc ( self , preimage , htlc_id ) :
2018-05-29 11:51:48 +02:00
"""
SettleHTLC attempts to settle an existing outstanding received HTLC .
"""
2018-06-25 21:06:50 +02:00
self . print_error ( " settle_htlc " )
2018-10-10 19:52:46 +02:00
htlc = self . log [ REMOTE ] [ ' adds ' ] [ htlc_id ]
2018-05-29 11:51:48 +02:00
assert htlc . payment_hash == sha256 ( preimage )
2018-10-10 19:52:46 +02:00
self . log [ LOCAL ] [ ' settles ' ] . append ( htlc_id )
2018-10-17 19:35:15 +02:00
# not saving preimage because it's already saved in LNWorker.invoices
2018-05-29 11:51:48 +02:00
2018-10-18 22:56:40 +02:00
def receive_htlc_settle ( self , preimage , htlc_id ) :
2018-06-25 21:06:50 +02:00
self . print_error ( " receive_htlc_settle " )
2018-10-18 22:56:40 +02:00
htlc = self . log [ LOCAL ] [ ' adds ' ] [ htlc_id ]
2018-05-29 11:51:48 +02:00
assert htlc . payment_hash == sha256 ( preimage )
2018-10-18 22:56:40 +02:00
self . log [ REMOTE ] [ ' settles ' ] . append ( htlc_id )
2018-10-17 19:35:15 +02:00
# we don't save the preimage because we don't need to forward it anyway
2018-06-18 19:46:25 +02:00
2018-10-18 22:56:40 +02:00
def fail_htlc ( self , htlc_id ) :
self . print_error ( " fail_htlc " )
2018-10-23 20:32:18 +02:00
self . log [ LOCAL ] [ ' fails ' ] . append ( htlc_id )
2018-10-18 22:56:40 +02:00
2018-09-24 18:10:14 +02:00
def receive_fail_htlc ( self , htlc_id ) :
self . print_error ( " receive_fail_htlc " )
2018-10-23 20:32:18 +02:00
self . log [ REMOTE ] [ ' fails ' ] . append ( htlc_id )
2018-06-25 21:06:50 +02:00
@property
2018-09-12 23:37:45 +02:00
def current_height ( self ) :
2018-10-10 22:54:30 +02:00
return { LOCAL : self . config [ LOCAL ] . ctn , REMOTE : self . config [ REMOTE ] . ctn }
2018-06-26 19:18:56 +02:00
@property
2018-07-20 16:44:03 +02:00
def pending_local_fee ( self ) :
return self . constraints . capacity - sum ( x [ 2 ] for x in self . pending_local_commitment . outputs ( ) )
2018-06-27 18:33:55 +02:00
2018-09-13 19:59:12 +02:00
def update_fee ( self , feerate ) :
2018-07-02 17:51:57 +02:00
if not self . constraints . is_initiator :
raise Exception ( " only initiator can update_fee, this counterparty is not initiator " )
2018-09-18 18:38:57 +02:00
pending_fee = FeeUpdate ( self , rate = feerate )
2018-09-13 19:59:12 +02:00
self . fee_mgr . append ( pending_fee )
2018-06-27 18:33:55 +02:00
2018-09-13 19:59:12 +02:00
def receive_update_fee ( self , feerate ) :
2018-07-02 17:51:57 +02:00
if self . constraints . is_initiator :
raise Exception ( " only the non-initiator can receive_update_fee, this counterparty is initiator " )
2018-09-18 18:38:57 +02:00
pending_fee = FeeUpdate ( self , rate = feerate )
2018-09-13 19:59:12 +02:00
self . fee_mgr . append ( pending_fee )
2018-06-27 18:33:55 +02:00
2018-09-25 21:28:06 +02:00
def remove_uncommitted_htlcs_from_log ( self , subject ) :
"""
returns
- the htlcs with uncommited ( not locked in ) htlcs removed
- a list of htlc_ids that were removed
"""
removed = [ ]
htlcs = [ ]
2018-10-10 19:52:46 +02:00
for i in self . log [ subject ] [ ' adds ' ] . values ( ) :
2018-09-27 18:59:06 +02:00
locked_in = i . locked_in [ LOCAL ] is not None or i . locked_in [ REMOTE ] is not None
2018-10-10 19:52:46 +02:00
if locked_in :
2018-10-10 22:54:30 +02:00
htlcs . append ( i . _asdict ( ) )
2018-09-25 21:28:06 +02:00
else :
removed . append ( i . htlc_id )
return htlcs , removed
2018-06-27 20:23:03 +02:00
def to_save ( self ) :
2018-09-25 21:28:06 +02:00
# need to forget about uncommited htlcs
# since we must assume they don't know about it,
# if it was not acked
remote_filtered , remote_removed = self . remove_uncommitted_htlcs_from_log ( REMOTE )
local_filtered , local_removed = self . remove_uncommitted_htlcs_from_log ( LOCAL )
to_save = {
2018-10-10 22:54:30 +02:00
" local_config " : self . config [ LOCAL ] ,
" remote_config " : self . config [ REMOTE ] ,
2018-06-27 20:23:03 +02:00
" channel_id " : self . channel_id ,
" short_channel_id " : self . short_channel_id ,
" constraints " : self . constraints ,
" funding_outpoint " : self . funding_outpoint ,
" node_id " : self . node_id ,
2018-09-12 16:17:10 +02:00
" remote_commitment_to_be_revoked " : str ( self . remote_commitment_to_be_revoked ) ,
2018-10-10 19:52:46 +02:00
" remote_log " : remote_filtered ,
" local_log " : local_filtered ,
2018-10-17 19:35:15 +02:00
" onion_keys " : str_bytes_dict_to_save ( self . onion_keys ) ,
2018-10-10 16:58:31 +02:00
" settled_local " : self . settled [ LOCAL ] ,
" settled_remote " : self . settled [ REMOTE ] ,
2018-06-27 20:23:03 +02:00
}
2018-09-25 21:28:06 +02:00
# htlcs number must be monotonically increasing,
# so we have to decrease the counter
if len ( remote_removed ) != 0 :
2018-10-10 22:54:30 +02:00
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 ) )
2018-09-25 21:28:06 +02:00
if len ( local_removed ) != 0 :
2018-10-10 22:54:30 +02:00
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 ) )
2018-09-25 21:28:06 +02:00
return to_save
2018-06-27 20:23:03 +02:00
def serialize ( self ) :
namedtuples_to_dict = lambda v : { i : j . _asdict ( ) if isinstance ( j , tuple ) else j for i , j in v . _asdict ( ) . items ( ) }
serialized_channel = { k : namedtuples_to_dict ( v ) if isinstance ( v , tuple ) else v for k , v in self . to_save ( ) . items ( ) }
2018-10-25 00:22:42 +02:00
dumped = ChannelJsonEncoder ( ) . encode ( serialized_channel )
2018-06-27 20:23:03 +02:00
roundtripped = json . loads ( dumped )
2018-10-11 17:15:25 +02:00
reconstructed = Channel ( roundtripped )
2018-06-27 20:23:03 +02:00
if reconstructed . to_save ( ) != self . to_save ( ) :
2018-09-25 21:28:06 +02:00
from pprint import pformat
try :
from deepdiff import DeepDiff
except ImportError :
raise Exception ( " Channels did not roundtrip serialization without changes: \n " + pformat ( reconstructed . to_save ( ) ) + " \n " + pformat ( self . to_save ( ) ) )
else :
raise Exception ( " Channels did not roundtrip serialization without changes: \n " + pformat ( DeepDiff ( reconstructed . to_save ( ) , self . to_save ( ) ) ) )
2018-06-27 20:23:03 +02:00
return roundtripped
def __str__ ( self ) :
2018-10-12 16:27:12 +02:00
return str ( self . serialize ( ) )
2018-06-28 15:50:45 +02:00
2018-09-28 16:40:32 +02:00
def make_commitment ( self , subject , this_point ) - > Transaction :
remote_msat , local_msat = self . amounts ( )
2018-10-23 20:32:18 +02:00
assert local_msat > = 0 , local_msat
assert remote_msat > = 0 , remote_msat
2018-10-10 22:54:30 +02:00
this_config = self . config [ subject ]
other_config = self . config [ - subject ]
2018-09-28 16:40:32 +02:00
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 )
htlcs = [ ]
for htlc in self . included_htlcs ( subject , - subject ) :
htlcs . append ( ScriptHtlc ( make_received_htlc (
other_revocation_pubkey ,
other_htlc_pubkey ,
this_htlc_pubkey ,
htlc . payment_hash ,
htlc . cltv_expiry ) , htlc ) )
for htlc in self . included_htlcs ( subject , subject ) :
htlcs . append (
ScriptHtlc ( make_offered_htlc (
other_revocation_pubkey ,
other_htlc_pubkey ,
this_htlc_pubkey ,
htlc . payment_hash ) , htlc ) )
if subject != LOCAL :
remote_msat , local_msat = local_msat , remote_msat
payment_pubkey = derive_pubkey ( other_config . payment_basepoint . pubkey , this_point )
2018-06-28 15:50:45 +02:00
return make_commitment (
2018-10-10 22:54:30 +02:00
self . config [ subject ] . ctn + 1 ,
2018-09-28 16:40:32 +02:00
this_config . multisig_key . pubkey ,
other_config . multisig_key . pubkey ,
2018-06-28 15:50:45 +02:00
payment_pubkey ,
2018-10-15 18:36:13 +02:00
self . config [ LOCAL if self . constraints . is_initiator else REMOTE ] . payment_basepoint . pubkey ,
self . config [ LOCAL if not self . constraints . is_initiator else REMOTE ] . payment_basepoint . pubkey ,
2018-09-28 16:40:32 +02:00
other_revocation_pubkey ,
derive_pubkey ( this_config . delayed_basepoint . pubkey , this_point ) ,
other_config . to_self_delay ,
* self . funding_outpoint ,
self . constraints . capacity ,
2018-06-28 15:50:45 +02:00
local_msat ,
remote_msat ,
2018-09-28 16:40:32 +02:00
this_config . dust_limit_sat ,
2018-10-15 18:36:13 +02:00
calc_onchain_fees (
len ( htlcs ) ,
self . pending_feerate ( subject ) ,
subject == LOCAL ,
self . constraints . is_initiator ,
) ,
2018-07-06 22:54:26 +02:00
htlcs = htlcs )
2018-09-21 19:18:34 +02:00
2018-10-25 19:53:31 +02:00
def make_closing_tx ( self , local_script : bytes , remote_script : bytes ,
fee_sat : Optional [ int ] = None ) - > Tuple [ bytes , int , str ] :
2018-09-21 19:18:34 +02:00
if fee_sat is None :
fee_sat = self . pending_local_fee
2018-10-22 21:50:13 +02:00
_ , outputs = make_commitment_outputs ( {
LOCAL : fee_sat * 1000 if self . constraints . is_initiator else 0 ,
REMOTE : fee_sat * 1000 if not self . constraints . is_initiator else 0 ,
} ,
2018-10-10 22:54:30 +02:00
self . config [ LOCAL ] . amount_msat ,
self . config [ REMOTE ] . amount_msat ,
2018-09-21 19:18:34 +02:00
( TYPE_SCRIPT , bh2u ( local_script ) ) ,
( TYPE_SCRIPT , bh2u ( remote_script ) ) ,
2018-10-10 22:54:30 +02:00
[ ] , self . config [ LOCAL ] . dust_limit_sat )
2018-09-21 19:18:34 +02:00
2018-10-10 22:54:30 +02:00
closing_tx = make_closing_tx ( self . config [ LOCAL ] . multisig_key . pubkey ,
2018-10-22 21:50:13 +02:00
self . config [ REMOTE ] . multisig_key . pubkey ,
funding_txid = self . funding_outpoint . txid ,
funding_pos = self . funding_outpoint . output_index ,
funding_sat = self . constraints . capacity ,
outputs = outputs )
2018-09-21 19:18:34 +02:00
2018-10-10 22:54:30 +02:00
der_sig = bfh ( closing_tx . sign_txin ( 0 , self . config [ LOCAL ] . multisig_key . privkey ) )
2018-09-21 19:18:34 +02:00
sig = ecc . sig_string_from_der_sig ( der_sig [ : - 1 ] )
2018-10-24 18:26:05 +02:00
return sig , fee_sat , closing_tx . txid ( )
2018-10-05 19:37:55 +02:00
def maybe_create_sweeptx_for_their_ctx_to_remote ( chan , ctx , their_pcp : bytes ,
sweep_address ) - > Optional [ EncumberedTransaction ] :
assert isinstance ( their_pcp , bytes )
2018-10-10 22:54:30 +02:00
payment_bp_privkey = ecc . ECPrivkey ( chan . config [ LOCAL ] . payment_basepoint . privkey )
2018-10-05 19:37:55 +02:00
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 )
to_remote_address = make_commitment_output_to_remote_address ( our_payment_pubkey )
for output_idx , ( type_ , addr , val ) in enumerate ( ctx . outputs ( ) ) :
if type_ == TYPE_ADDRESS and addr == to_remote_address :
break
else :
return None
sweep_tx = create_sweeptx_their_ctx_to_remote ( address = sweep_address ,
ctx = ctx ,
output_idx = output_idx ,
our_payment_privkey = our_payment_privkey )
2018-10-22 18:57:51 +02:00
return EncumberedTransaction ( ' their_ctx_to_remote ' , sweep_tx , csv_delay = 0 , cltv_expiry = 0 )
2018-10-05 19:37:55 +02:00
def maybe_create_sweeptx_for_their_ctx_to_local ( chan , ctx , per_commitment_secret : bytes ,
sweep_address ) - > Optional [ EncumberedTransaction ] :
assert isinstance ( per_commitment_secret , bytes )
per_commitment_point = ecc . ECPrivkey ( per_commitment_secret ) . get_public_key_bytes ( compressed = True )
2018-10-10 22:54:30 +02:00
revocation_privkey = derive_blinded_privkey ( chan . config [ LOCAL ] . revocation_basepoint . privkey ,
2018-10-05 19:37:55 +02:00
per_commitment_secret )
revocation_pubkey = ecc . ECPrivkey ( revocation_privkey ) . get_public_key_bytes ( compressed = True )
2018-10-10 22:54:30 +02:00
to_self_delay = chan . config [ LOCAL ] . to_self_delay
delayed_pubkey = derive_pubkey ( chan . config [ REMOTE ] . delayed_basepoint . pubkey ,
2018-10-05 19:37:55 +02:00
per_commitment_point )
witness_script = bh2u ( make_commitment_output_to_local_witness_script (
revocation_pubkey , to_self_delay , delayed_pubkey ) )
to_local_address = redeem_script_to_address ( ' p2wsh ' , witness_script )
for output_idx , o in enumerate ( ctx . outputs ( ) ) :
if o . type == TYPE_ADDRESS and o . address == to_local_address :
break
else :
return None
sweep_tx = create_sweeptx_ctx_to_local ( address = sweep_address ,
ctx = ctx ,
output_idx = output_idx ,
witness_script = witness_script ,
privkey = revocation_privkey ,
is_revocation = True )
2018-10-22 18:57:51 +02:00
return EncumberedTransaction ( ' their_ctx_to_local ' , sweep_tx , csv_delay = 0 , cltv_expiry = 0 )
2018-10-05 19:37:55 +02:00
2018-10-22 18:57:51 +02:00
def create_sweeptxs_for_our_ctx ( chan , ctx , our_pcp : bytes , sweep_address ) \
- > List [ Tuple [ Optional [ str ] , EncumberedTransaction ] ] :
2018-10-05 19:37:55 +02:00
assert isinstance ( our_pcp , bytes )
2018-10-10 22:54:30 +02:00
delayed_bp_privkey = ecc . ECPrivkey ( chan . config [ LOCAL ] . delayed_basepoint . privkey )
2018-10-05 19:37:55 +02:00
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 )
2018-10-10 22:54:30 +02:00
revocation_pubkey = derive_blinded_pubkey ( chan . config [ REMOTE ] . revocation_basepoint . pubkey ,
2018-10-05 19:37:55 +02:00
our_pcp )
2018-10-10 22:54:30 +02:00
to_self_delay = chan . config [ REMOTE ] . to_self_delay
2018-10-05 19:37:55 +02:00
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 )
2018-10-22 18:57:51 +02:00
txs = [ ]
2018-10-05 19:37:55 +02:00
for output_idx , o in enumerate ( ctx . outputs ( ) ) :
if o . type == TYPE_ADDRESS and o . address == to_local_address :
2018-10-22 18:57:51 +02:00
sweep_tx = create_sweeptx_ctx_to_local ( address = sweep_address ,
ctx = ctx ,
output_idx = output_idx ,
witness_script = witness_script ,
privkey = our_localdelayed_privkey . get_secret_bytes ( ) ,
is_revocation = False ,
to_self_delay = to_self_delay )
txs . append ( ( None , EncumberedTransaction ( ' our_ctx_to_local ' , sweep_tx , csv_delay = to_self_delay , cltv_expiry = 0 ) ) )
2018-10-05 19:37:55 +02:00
break
2018-10-22 18:57:51 +02:00
# TODO htlc successes
htlcs = list ( chan . included_htlcs ( LOCAL , LOCAL ) ) # timeouts
for htlc in htlcs :
witness_script , htlc_tx = make_htlc_tx_with_open_channel (
chan ,
our_pcp ,
True , # for_us
False , # we_receive
ctx , htlc )
data = chan . config [ LOCAL ] . current_htlc_signatures
htlc_sigs = [ data [ i : i + 64 ] for i in range ( 0 , len ( data ) , 64 ) ]
idx = chan . verify_htlc ( htlc , htlc_sigs , False )
remote_htlc_sig = ecc . der_sig_from_sig_string ( htlc_sigs [ idx ] ) + b ' \x01 '
remote_revocation_pubkey = derive_blinded_pubkey ( chan . config [ REMOTE ] . revocation_basepoint . pubkey , our_pcp )
remote_htlc_pubkey = derive_pubkey ( chan . config [ REMOTE ] . htlc_basepoint . pubkey , our_pcp )
local_htlc_key = derive_privkey (
int . from_bytes ( chan . config [ LOCAL ] . htlc_basepoint . privkey , ' big ' ) ,
our_pcp ) . to_bytes ( 32 , ' big ' )
program = make_offered_htlc ( remote_revocation_pubkey , remote_htlc_pubkey , privkey_to_pubkey ( local_htlc_key ) , htlc . payment_hash )
local_htlc_sig = bfh ( htlc_tx . sign_txin ( 0 , local_htlc_key ) )
htlc_tx . inputs ( ) [ 0 ] [ ' witness ' ] = bh2u ( make_htlc_tx_witness ( remote_htlc_sig , local_htlc_sig , b ' ' , program ) )
tx_size_bytes = 999 # TODO
fee_per_kb = FEERATE_FALLBACK_STATIC_FEE
fee = SimpleConfig . estimate_fee_for_feerate ( fee_per_kb , tx_size_bytes )
second_stage_outputs = [ TxOutput ( TYPE_ADDRESS , chan . sweep_address , htlc . amount_msat / / 1000 - fee ) ]
assert to_self_delay is not None
second_stage_inputs = [ {
' scriptSig ' : ' ' ,
' type ' : ' p2wsh ' ,
' signatures ' : [ ] ,
' num_sig ' : 0 ,
' prevout_n ' : 0 ,
' prevout_hash ' : htlc_tx . txid ( ) ,
' value ' : htlc_tx . outputs ( ) [ 0 ] . value ,
' coinbase ' : False ,
' preimage_script ' : bh2u ( witness_script ) ,
' sequence ' : to_self_delay ,
} ]
tx = Transaction . from_io ( second_stage_inputs , second_stage_outputs , version = 2 )
local_delaykey = derive_privkey (
int . from_bytes ( chan . config [ LOCAL ] . delayed_basepoint . privkey , ' big ' ) ,
our_pcp ) . to_bytes ( 32 , ' big ' )
assert local_delaykey == our_localdelayed_privkey . get_secret_bytes ( )
witness = construct_witness ( [ bfh ( tx . sign_txin ( 0 , local_delaykey ) ) , 0 , witness_script ] )
tx . inputs ( ) [ 0 ] [ ' witness ' ] = witness
assert tx . is_complete ( )
txs . append ( ( htlc_tx . txid ( ) , EncumberedTransaction ( f ' second_stage_to_wallet_ { bh2u ( htlc . payment_hash ) } ' , tx , csv_delay = to_self_delay , cltv_expiry = 0 ) ) )
txs . append ( ( ctx . txid ( ) , EncumberedTransaction ( f ' our_ctx_htlc_tx_ { bh2u ( htlc . payment_hash ) } ' , htlc_tx , csv_delay = 0 , cltv_expiry = htlc . cltv_expiry ) ) )
return txs
2018-10-05 19:37:55 +02:00
def create_sweeptx_their_ctx_to_remote ( address , ctx , output_idx : int , our_payment_privkey : ecc . ECPrivkey ,
fee_per_kb : int = None ) - > Transaction :
our_payment_pubkey = our_payment_privkey . get_public_key_hex ( compressed = True )
val = ctx . outputs ( ) [ output_idx ] . value
sweep_inputs = [ {
' type ' : ' p2wpkh ' ,
' x_pubkeys ' : [ our_payment_pubkey ] ,
' num_sig ' : 1 ,
' prevout_n ' : output_idx ,
' prevout_hash ' : ctx . txid ( ) ,
' value ' : val ,
' coinbase ' : False ,
' signatures ' : [ None ] ,
} ]
tx_size_bytes = 110 # approx size of p2wpkh->p2wpkh
if fee_per_kb is None : fee_per_kb = FEERATE_FALLBACK_STATIC_FEE
fee = SimpleConfig . estimate_fee_for_feerate ( fee_per_kb , tx_size_bytes )
sweep_outputs = [ TxOutput ( TYPE_ADDRESS , address , val - fee ) ]
sweep_tx = Transaction . from_io ( sweep_inputs , sweep_outputs )
sweep_tx . set_rbf ( True )
sweep_tx . sign ( { our_payment_pubkey : ( our_payment_privkey . get_secret_bytes ( ) , True ) } )
if not sweep_tx . is_complete ( ) :
raise Exception ( ' channel close sweep tx is not complete ' )
return sweep_tx
def create_sweeptx_ctx_to_local ( address , ctx , output_idx : int , witness_script : str ,
privkey : bytes , is_revocation : bool ,
to_self_delay : int = None ,
fee_per_kb : int = None ) - > Transaction :
""" Create a txn that sweeps the ' to_local ' output of a commitment
transaction into our wallet .
privkey : either revocation_privkey or localdelayed_privkey
is_revocation : tells us which ^
"""
val = ctx . outputs ( ) [ output_idx ] . value
sweep_inputs = [ {
' scriptSig ' : ' ' ,
' type ' : ' p2wsh ' ,
' signatures ' : [ ] ,
' num_sig ' : 0 ,
' prevout_n ' : output_idx ,
' prevout_hash ' : ctx . txid ( ) ,
' value ' : val ,
' coinbase ' : False ,
' preimage_script ' : witness_script ,
} ]
if to_self_delay is not None :
sweep_inputs [ 0 ] [ ' sequence ' ] = to_self_delay
tx_size_bytes = 121 # approx size of to_local -> p2wpkh
if fee_per_kb is None : fee_per_kb = FEERATE_FALLBACK_STATIC_FEE
fee = SimpleConfig . estimate_fee_for_feerate ( fee_per_kb , tx_size_bytes )
sweep_outputs = [ TxOutput ( TYPE_ADDRESS , address , val - fee ) ]
sweep_tx = Transaction . from_io ( sweep_inputs , sweep_outputs , version = 2 )
sig = sweep_tx . sign_txin ( 0 , privkey )
witness = construct_witness ( [ sig , int ( is_revocation ) , witness_script ] )
sweep_tx . inputs ( ) [ 0 ] [ ' witness ' ] = witness
return sweep_tx