@@ -1770,28 +1770,67 @@ async def _shutdown(self, chan: Channel, payload, *, is_local: bool):
17701770 our_sig , closing_tx = chan .make_closing_tx (our_scriptpubkey , their_scriptpubkey , fee_sat = 0 )
17711771 fee_rate = self .network .config .fee_per_kb ()
17721772 our_fee = fee_rate * closing_tx .estimated_size () // 1000
1773+ # TODO: anchors: remove this, as commitment fee rate can be below chain head fee rate?
17731774 # BOLT2: The sending node MUST set fee less than or equal to the base fee of the final ctx
17741775 max_fee = chan .get_latest_fee (LOCAL if is_local else REMOTE )
17751776 our_fee = min (our_fee , max_fee )
1776- drop_to_remote = False
1777+
1778+ drop_to_remote = False # does the peer drop its to_local output or not?
17771779 def send_closing_signed ():
1780+ MODERN_FEE = True
1781+ if MODERN_FEE :
1782+ nonlocal fee_range_sent # we change fee_range_sent in outer scope
1783+ fee_range_sent = fee_range
1784+ closing_signed_tlvs = {'fee_range' : fee_range }
1785+ else :
1786+ closing_signed_tlvs = {}
1787+
17781788 our_sig , closing_tx = chan .make_closing_tx (our_scriptpubkey , their_scriptpubkey , fee_sat = our_fee , drop_remote = drop_to_remote )
1779- self .send_message ('closing_signed' , channel_id = chan .channel_id , fee_satoshis = our_fee , signature = our_sig )
1789+ self .logger .info (f"Sending CLOSING_SIGNED with fee range: { closing_signed_tlvs } and fee: { our_fee } " )
1790+ self .send_message (
1791+ 'closing_signed' ,
1792+ channel_id = chan .channel_id ,
1793+ fee_satoshis = our_fee ,
1794+ signature = our_sig ,
1795+ closing_signed_tlvs = closing_signed_tlvs ,
1796+ )
17801797 def verify_signature (tx , sig ):
17811798 their_pubkey = chan .config [REMOTE ].multisig_key .pubkey
17821799 preimage_hex = tx .serialize_preimage (0 )
17831800 pre_hash = sha256d (bfh (preimage_hex ))
17841801 return ecc .verify_signature (their_pubkey , sig , pre_hash )
1802+
1803+ # this is the fee range we initially try to enforce
1804+ fee_range = {'min_fee_satoshis' : our_fee // 2 , 'max_fee_satoshis' : our_fee } # TODO: find good lower bound
1805+ their_fee = None
1806+ fee_range_sent = {}
1807+ is_initiator = chan .constraints .is_initiator
17851808 # the funder sends the first 'closing_signed' message
1786- if chan . constraints . is_initiator :
1809+ if is_initiator :
17871810 send_closing_signed ()
1811+
17881812 # negotiate fee
17891813 while True :
1790- # FIXME: the remote SHOULD send closing_signed, but some don't.
1791- cs_payload = await self .wait_for_message ('closing_signed' , chan .channel_id )
1814+ try :
1815+ cs_payload = await self .wait_for_message ('closing_signed' , chan .channel_id )
1816+ except asyncio .exceptions .TimeoutError :
1817+ if not is_initiator and not their_fee : # we only force close if a peer doesn't reply
1818+ await self .lnworker .force_close_channel (chan .channel_id )
1819+ raise Exception ("Peer didn't reply with closing signed, force closed." )
1820+ else :
1821+ # situation when we as an initiator send a fee and the recipient
1822+ # already agrees with that fee, but doens't tell us
1823+ raise Exception ("Peer didn't reply, probably already closed." )
1824+
1825+ their_previous_fee = their_fee
17921826 their_fee = cs_payload ['fee_satoshis' ]
1827+
1828+ # 0. integrity checks
1829+ # TODO: anchors, remove this?
17931830 if their_fee > max_fee :
17941831 raise Exception (f'the proposed fee exceeds the base fee of the latest commitment transaction { is_local , their_fee , max_fee } ' )
1832+
1833+ # determine their closing transaction
17951834 their_sig = cs_payload ['signature' ]
17961835 # verify their sig: they might have dropped their output
17971836 our_sig , closing_tx = chan .make_closing_tx (our_scriptpubkey , their_scriptpubkey , fee_sat = their_fee , drop_remote = False )
@@ -1812,17 +1851,94 @@ def verify_signature(tx, sig):
18121851 to_remote_amount = closing_tx .outputs ()[to_remote_idx ].value
18131852 transaction .check_scriptpubkey_template_and_dust (their_scriptpubkey , to_remote_amount )
18141853
1815- # Agree if difference is lower or equal to one (see below)
1816- if abs (our_fee - their_fee ) < 2 :
1854+ # 1. check fees
1855+ # if fee_satoshis is equal to its previously sent fee_satoshis:
1856+ if our_fee == their_fee :
1857+ # SHOULD sign and broadcast the final closing transaction.
1858+ break # we publish
1859+
1860+ # 2. at start, adapt our fee range if we are not the channel initiator
1861+ fee_range_received = cs_payload ['closing_signed_tlvs' ].get ('fee_range' )
1862+ if fee_range_received :
1863+ self .logger .info (f"Got CLOSING_SIGNED with fee range: { fee_range_received } and fee: { their_fee } " )
1864+ # The sending node: if it is not the funder:
1865+ if fee_range_received and not is_initiator and not fee_range_sent :
1866+ # SHOULD set max_fee_satoshis to at least the max_fee_satoshis received
1867+ fee_range ['max_fee_satoshis' ] = max (fee_range_received ['max_fee_satoshis' ], fee_range ['max_fee_satoshis' ])
1868+ # SHOULD set min_fee_satoshis to a fairly low value
1869+ # TODO: what's fairly low value? allows the initiator to go to low values
1870+ fee_range ['min_fee_satoshis' ] = fee_range ['max_fee_satoshis' ] // 2
1871+
1872+ # 3. if fee_satoshis matches its previously sent fee_range:
1873+ if fee_range_sent and (fee_range_sent ['min_fee_satoshis' ] <= their_fee <= fee_range_sent ['max_fee_satoshis' ]):
1874+ # SHOULD reply with a closing_signed with the same fee_satoshis value if it is different from its previously sent fee_satoshis
1875+ if our_fee != their_fee :
1876+ our_fee = their_fee
1877+ send_closing_signed () # peer publishes
1878+ break
1879+ # SHOULD use `fee_satoshis` to sign and broadcast the final closing transaction
1880+ else :
1881+ our_fee = their_fee
1882+ break # we publish
1883+
1884+ # 4. if the message contains a fee_range
1885+ if fee_range_received :
1886+ overlap_min = max (fee_range ['min_fee_satoshis' ], fee_range_received ['min_fee_satoshis' ])
1887+ overlap_max = min (fee_range ['max_fee_satoshis' ], fee_range_received ['max_fee_satoshis' ])
1888+ # if there is no overlap between that and its own fee_range
1889+ if overlap_min > overlap_max :
1890+ raise Exception ("There is no overlap between between their and our fee range." )
1891+ # TODO: MUST fail the channel if it doesn't receive a satisfying fee_range after a reasonable amount of time
1892+ # otherwise:
1893+ else :
1894+ if is_initiator :
1895+ # if fee_satoshis is not in the overlap between the sent and received fee_range:
1896+ if not (overlap_min <= their_fee <= overlap_max ):
1897+ # MUST fail the channel
1898+ await self .lnworker .force_close_channel (chan .channel_id )
1899+ raise Exception ("Their fee is not in the overlap region, we force closed." )
1900+ # otherwise:
1901+ else :
1902+ our_fee = their_fee
1903+ # MUST reply with the same fee_satoshis.
1904+ send_closing_signed () # peer publishes
1905+ break
1906+ # otherwise (it is not the funder):
1907+ else :
1908+ # if it has already sent a closing_signed:
1909+ if fee_range_sent :
1910+ # if fee_satoshis is not the same as the value it sent:
1911+ if their_fee != our_fee :
1912+ # MUST fail the channel
1913+ await self .lnworker .force_close_channel (chan .channel_id )
1914+ raise Exception ("Expected the same fee as ours, we force closed." )
1915+ # otherwise:
1916+ else :
1917+ # MUST propose a fee_satoshis in the overlap between received and (about-to-be) sent fee_range.
1918+ our_fee = (overlap_min + overlap_max ) // 2
1919+ send_closing_signed ()
1920+ continue
1921+ # otherwise, if fee_satoshis is not strictly between its last-sent fee_satoshis
1922+ # and its previously-received fee_satoshis, UNLESS it has since reconnected:
1923+ elif their_previous_fee and not (min (our_fee , their_previous_fee ) < their_fee < max (our_fee , their_previous_fee )):
1924+ # SHOULD fail the connection.
1925+ raise Exception ('Their fee is not between our last sent and their last sent fee.' )
1926+ # otherwise, if the receiver agrees with the fee:
1927+ elif abs (their_fee - our_fee ) <= 1 : # we cannot have another strictly in-between value
1928+ # SHOULD reply with a closing_signed with the same fee_satoshis value.
18171929 our_fee = their_fee
1930+ send_closing_signed () # peer publishes
18181931 break
1819- # this will be "strictly between" (as in BOLT2) previous values because of the above
1820- our_fee = (our_fee + their_fee ) // 2
1821- # another round
1822- send_closing_signed ()
1823- # the non-funder replies
1824- if not chan .constraints .is_initiator :
1825- send_closing_signed ()
1932+ # otherwise:
1933+ else :
1934+ # MUST propose a value "strictly between" the received fee_satoshis and its previously-sent fee_satoshis.
1935+ our_fee_proposed = (our_fee + their_fee ) // 2
1936+ if not (min (our_fee , their_fee ) < our_fee_proposed < max (our_fee , their_fee )):
1937+ our_fee_proposed += (their_fee - our_fee ) // 2
1938+ else :
1939+ our_fee = our_fee_proposed
1940+ send_closing_signed ()
1941+
18261942 # add signatures
18271943 closing_tx .add_signature_to_txin (
18281944 txin_idx = 0 ,
0 commit comments