@ -44,7 +44,8 @@ from .lnutil import (Outpoint, LocalConfig, RECEIVED, UpdateAddHtlc,
RemoteMisbehaving ,
NBLOCK_OUR_CLTV_EXPIRY_DELTA , format_short_channel_id , ShortChannelID ,
IncompatibleLightningFeatures , derive_payment_secret_from_payment_preimage ,
LN_MAX_FUNDING_SAT , calc_fees_for_commitment_tx )
LN_MAX_FUNDING_SAT , calc_fees_for_commitment_tx ,
UpfrontShutdownScriptViolation )
from . lnutil import FeeUpdate , channel_id_from_funding_tx
from . lntransport import LNTransport , LNTransportBase
from . lnmsg import encode_msg , decode_msg
@ -486,12 +487,33 @@ class Peer(Logger):
def is_static_remotekey ( self ) :
return bool ( self . features & LnFeatures . OPTION_STATIC_REMOTEKEY_OPT )
def is_upfront_shutdown_script ( self ) :
return bool ( self . features & LnFeatures . OPTION_UPFRONT_SHUTDOWN_SCRIPT_OPT )
def upfront_shutdown_script_from_payload ( self , payload , msg_identifier : str ) - > Optional [ bytes ] :
if msg_identifier not in [ ' accept ' , ' open ' ] :
raise ValueError ( " msg_identifier must be either ' accept ' or ' open ' " )
uss_tlv = payload [ msg_identifier + ' _channel_tlvs ' ] . get (
' upfront_shutdown_script ' )
if uss_tlv and self . is_upfront_shutdown_script ( ) :
upfront_shutdown_script = uss_tlv [ ' shutdown_scriptpubkey ' ]
else :
upfront_shutdown_script = b ' '
self . logger . info ( f " upfront shutdown script received: { upfront_shutdown_script } " )
return upfront_shutdown_script
def make_local_config ( self , funding_sat : int , push_msat : int , initiator : HTLCOwner ) - > LocalConfig :
channel_seed = os . urandom ( 32 )
initial_msat = funding_sat * 1000 - push_msat if initiator == LOCAL else push_msat
static_remotekey = None
# sending empty bytes as the upfront_shutdown_script will give us the
# flexibility to decide an address at closing time
upfront_shutdown_script = b ' '
if self . is_static_remotekey ( ) :
# Note: in the future, if a CSV delay is added,
# we will want to derive that key
wallet = self . lnworker . wallet
assert wallet . txin_type == ' p2wpkh '
addr = wallet . get_new_sweep_address_for_channel ( )
@ -503,6 +525,7 @@ class Peer(Logger):
local_config = LocalConfig . from_seed (
channel_seed = channel_seed ,
static_remotekey = static_remotekey ,
upfront_shutdown_script = upfront_shutdown_script ,
to_self_delay = self . network . config . get ( ' lightning_to_self_delay ' , 7 * 144 ) ,
dust_limit_sat = dust_limit_sat ,
max_htlc_value_in_flight_msat = funding_sat * 1000 ,
@ -546,15 +569,27 @@ class Peer(Logger):
push_msat : int ,
temp_channel_id : bytes
) - > Tuple [ Channel , ' PartialTransaction ' ] :
""" Implements the channel opening flow.
- > open_channel message
< - accept_channel message
- > funding_created message
< - funding_signed message
Channel configurations are initialized in this method .
"""
await asyncio . wait_for ( self . initialized , LN_P2P_NETWORK_TIMEOUT )
feerate = self . lnworker . current_feerate_per_kw ( )
local_config = self . make_local_config ( funding_sat , push_msat , LOCAL )
if funding_sat > LN_MAX_FUNDING_SAT :
raise Exception ( f " MUST set funding_satoshis to less than 2^24 satoshi. { funding_sat } sat > { LN_MAX_FUNDING_SAT } " )
if push_msat > 1000 * funding_sat :
raise Exception ( f " MUST set push_msat to equal or less than 1000 * funding_satoshis: { push_msat } msat > { 1000 * funding_sat } msat " )
if funding_sat < lnutil . MIN_FUNDING_SAT :
raise Exception ( f " funding_sat too low: { funding_sat } < { lnutil . MIN_FUNDING_SAT } " )
# for the first commitment transaction
per_commitment_secret_first = get_per_commitment_secret_from_seed ( local_config . per_commitment_secret_seed ,
RevocationStore . START_INDEX )
@ -579,7 +614,13 @@ class Peer(Logger):
channel_flags = 0x00 , # not willing to announce channel
channel_reserve_satoshis = local_config . reserve_sat ,
htlc_minimum_msat = local_config . htlc_minimum_msat ,
open_channel_tlvs = {
' upfront_shutdown_script ' :
{ ' shutdown_scriptpubkey ' : local_config . upfront_shutdown_script }
}
)
# <- accept_channel
payload = await self . wait_for_message ( ' accept_channel ' , temp_channel_id )
remote_per_commitment_point = payload [ ' first_per_commitment_point ' ]
funding_txn_minimum_depth = payload [ ' minimum_depth ' ]
@ -587,6 +628,10 @@ class Peer(Logger):
raise Exception ( f " minimum depth too low, { funding_txn_minimum_depth } " )
if funding_txn_minimum_depth > 30 :
raise Exception ( f " minimum depth too high, { funding_txn_minimum_depth } " )
upfront_shutdown_script = self . upfront_shutdown_script_from_payload (
payload , ' accept ' )
remote_config = RemoteConfig (
payment_basepoint = OnlyPubkeyKeypair ( payload [ ' payment_basepoint ' ] ) ,
multisig_key = OnlyPubkeyKeypair ( payload [ " funding_pubkey " ] ) ,
@ -602,6 +647,7 @@ class Peer(Logger):
htlc_minimum_msat = payload [ ' htlc_minimum_msat ' ] ,
next_per_commitment_point = remote_per_commitment_point ,
current_per_commitment_point = None ,
upfront_shutdown_script = upfront_shutdown_script
)
remote_config . validate_params ( funding_sat = funding_sat )
# if channel_reserve_satoshis is less than dust_limit_satoshis within the open_channel message:
@ -612,6 +658,8 @@ class Peer(Logger):
# MUST reject the channel.
if local_config . reserve_sat < remote_config . dust_limit_sat :
raise Exception ( " violated constraint: local_config.reserve_sat < remote_config.dust_limit_sat " )
# -> funding created
# replace dummy output in funding tx
redeem_script = funding_output_script ( local_config , remote_config )
funding_address = bitcoin . redeem_script_to_address ( ' p2wsh ' , redeem_script )
@ -626,7 +674,7 @@ class Peer(Logger):
funding_txid = funding_tx . txid ( )
assert funding_txid
funding_index = funding_tx . outputs ( ) . index ( funding_output )
# remote commitment transaction
# build remote commitment transaction
channel_id , funding_txid_bytes = channel_id_from_funding_tx ( funding_txid , funding_index )
outpoint = Outpoint ( funding_txid , funding_index )
constraints = ChannelConstraints ( capacity = funding_sat , is_initiator = True , funding_txn_minimum_depth = funding_txn_minimum_depth )
@ -640,12 +688,15 @@ class Peer(Logger):
chan . add_or_update_peer_addr ( self . transport . peer_addr )
sig_64 , _ = chan . sign_next_commitment ( )
self . temp_id_to_id [ temp_channel_id ] = channel_id
self . send_message ( " funding_created " ,
temporary_channel_id = temp_channel_id ,
funding_txid = funding_txid_bytes ,
funding_output_index = funding_index ,
signature = sig_64 )
self . funding_created_sent . add ( channel_id )
# <- funding signed
payload = await self . wait_for_message ( ' funding_signed ' , channel_id )
self . logger . info ( ' received funding_signed ' )
remote_sig = payload [ ' signature ' ]
@ -675,6 +726,16 @@ class Peer(Logger):
return StoredDict ( chan_dict , None , [ ] )
async def on_open_channel ( self , payload ) :
""" Implements the channel acceptance flow.
< - open_channel message
- > accept_channel message
< - funding_created message
- > funding_signed message
Channel configurations are initialized in this method .
"""
# <- open_channel
if payload [ ' chain_hash ' ] != constants . net . rev_genesis_bytes ( ) :
raise Exception ( ' wrong chain_hash ' )
funding_sat = payload [ ' funding_satoshis ' ]
@ -688,6 +749,10 @@ class Peer(Logger):
raise Exception ( f " MUST set push_msat to equal or less than 1000 * funding_satoshis: { push_msat } msat > { 1000 * funding_sat } msat " )
if funding_sat < lnutil . MIN_FUNDING_SAT :
raise Exception ( f " funding_sat too low: { funding_sat } < { lnutil . MIN_FUNDING_SAT } " )
upfront_shutdown_script = self . upfront_shutdown_script_from_payload (
payload , ' open ' )
remote_config = RemoteConfig (
payment_basepoint = OnlyPubkeyKeypair ( payload [ ' payment_basepoint ' ] ) ,
multisig_key = OnlyPubkeyKeypair ( payload [ ' funding_pubkey ' ] ) ,
@ -703,7 +768,9 @@ class Peer(Logger):
htlc_minimum_msat = payload [ ' htlc_minimum_msat ' ] ,
next_per_commitment_point = payload [ ' first_per_commitment_point ' ] ,
current_per_commitment_point = None ,
upfront_shutdown_script = upfront_shutdown_script ,
)
remote_config . validate_params ( funding_sat = funding_sat )
# The receiving node MUST fail the channel if:
# the funder's amount for the initial commitment transaction is not sufficient for full fee payment.
@ -720,12 +787,15 @@ class Peer(Logger):
# note: we ignore payload['channel_flags'], which e.g. contains 'announce_channel'.
# Notably if the remote sets 'announce_channel' to True, we will ignore that too,
# but we will not play along with actually announcing the channel (so we keep it private).
# -> accept channel
# for the first commitment transaction
per_commitment_secret_first = get_per_commitment_secret_from_seed ( local_config . per_commitment_secret_seed ,
RevocationStore . START_INDEX )
per_commitment_point_first = secret_to_pubkey ( int . from_bytes ( per_commitment_secret_first , ' big ' ) )
min_depth = 3
self . send_message ( ' accept_channel ' ,
self . send_message (
' accept_channel ' ,
temporary_channel_id = temp_chan_id ,
dust_limit_satoshis = local_config . dust_limit_sat ,
max_htlc_value_in_flight_msat = local_config . max_htlc_value_in_flight_msat ,
@ -740,8 +810,16 @@ class Peer(Logger):
delayed_payment_basepoint = local_config . delayed_basepoint . pubkey ,
htlc_basepoint = local_config . htlc_basepoint . pubkey ,
first_per_commitment_point = per_commitment_point_first ,
accept_channel_tlvs = {
' upfront_shutdown_script ' :
{ ' shutdown_scriptpubkey ' : local_config . upfront_shutdown_script }
}
)
# <- funding created
funding_created = await self . wait_for_message ( ' funding_created ' , temp_chan_id )
# -> funding signed
funding_idx = funding_created [ ' funding_output_index ' ]
funding_txid = bh2u ( funding_created [ ' funding_txid ' ] [ : : - 1 ] )
channel_id , funding_txid_bytes = channel_id_from_funding_tx ( funding_txid , funding_idx )
@ -1407,6 +1485,13 @@ class Peer(Logger):
async def on_shutdown ( self , chan : Channel , payload ) :
their_scriptpubkey = payload [ ' scriptpubkey ' ]
their_upfront_scriptpubkey = chan . config [ REMOTE ] . upfront_shutdown_script
# BOLT-02 check if they use the upfront shutdown script they advertized
if their_upfront_scriptpubkey :
if not ( their_scriptpubkey == their_upfront_scriptpubkey ) :
raise UpfrontShutdownScriptViolation ( " remote didn ' t use upfront shutdown script it commited to in channel opening " )
# BOLT-02 restrict the scriptpubkey to some templates:
if not ( match_script_against_template ( their_scriptpubkey , transaction . SCRIPTPUBKEY_TEMPLATE_WITNESS_V0 )
or match_script_against_template ( their_scriptpubkey , transaction . SCRIPTPUBKEY_TEMPLATE_P2SH )
@ -1433,7 +1518,13 @@ class Peer(Logger):
async def send_shutdown ( self , chan : Channel ) :
if not self . can_send_shutdown ( chan ) :
raise Exception ( ' cannot send shutdown ' )
if chan . config [ LOCAL ] . upfront_shutdown_script :
scriptpubkey = chan . config [ LOCAL ] . upfront_shutdown_script
else :
scriptpubkey = bfh ( bitcoin . address_to_script ( chan . sweep_address ) )
assert scriptpubkey
# wait until no more pending updates (bolt2)
chan . set_can_send_ctx_updates ( False )
while chan . has_pending_changes ( REMOTE ) :
@ -1452,7 +1543,12 @@ class Peer(Logger):
# if no HTLCs remain, we must not send updates
chan . set_can_send_ctx_updates ( False )
their_scriptpubkey = payload [ ' scriptpubkey ' ]
if chan . config [ LOCAL ] . upfront_shutdown_script :
our_scriptpubkey = chan . config [ LOCAL ] . upfront_shutdown_script
else :
our_scriptpubkey = bfh ( bitcoin . address_to_script ( chan . sweep_address ) )
assert our_scriptpubkey
# estimate fee of closing tx
our_sig , closing_tx = chan . make_closing_tx ( our_scriptpubkey , their_scriptpubkey , fee_sat = 0 )
fee_rate = self . network . config . fee_per_kb ( )