1
0

txbatcher: add TODOs re nLocktime block-height vs timestamp confusion

seems harmless atm but some rethinking is needed and checks should be added
This commit is contained in:
SomberNight
2025-12-04 15:32:46 +00:00
parent 1eb69c90dd
commit ef0e4e02b7
3 changed files with 20 additions and 10 deletions

View File

@@ -40,6 +40,7 @@ HTLCTX_INPUT_OUTPUT_INDEX = 0
class SweepInfo(NamedTuple): class SweepInfo(NamedTuple):
name: str name: str
cltv_abs: Optional[int] # set to None only if the script has no cltv cltv_abs: Optional[int] # set to None only if the script has no cltv
# TODO add asserts that cltv_abs is block-based (see NLOCKTIME_BLOCKHEIGHT_MAX)
txin: PartialTxInput txin: PartialTxInput
txout: Optional[PartialTxOutput] # only for first-stage htlc tx txout: Optional[PartialTxOutput] # only for first-stage htlc tx
can_be_batched: bool # todo: this could be more fine-grained can_be_batched: bool # todo: this could be more fine-grained

View File

@@ -349,16 +349,18 @@ class TxInput:
def get_time_based_relative_locktime(self) -> Optional[int]: def get_time_based_relative_locktime(self) -> Optional[int]:
# see bip 68 # see bip 68
if self.nsequence & (1<<31): if self.nsequence & (1<<31): # "disable" flag
return return None
if self.nsequence & (1<<22): if self.nsequence & (1<<22): # in units of 512 sec
return self.nsequence & 0xffff return self.nsequence & 0xffff
return None
def get_block_based_relative_locktime(self) -> Optional[int]: def get_block_based_relative_locktime(self) -> Optional[int]:
if self.nsequence & (1<<31): if self.nsequence & (1<<31): # "disable" flag
return return None
if not self.nsequence & (1<<22): if not self.nsequence & (1<<22): # in blocks
return self.nsequence & 0xffff return self.nsequence & 0xffff
return None
@property @property
def short_id(self): def short_id(self):
@@ -1328,13 +1330,13 @@ class Transaction:
def get_time_based_relative_locktime(self) -> Optional[int]: def get_time_based_relative_locktime(self) -> Optional[int]:
if self.version < 2: if self.version < 2:
return return None
locktimes = list(filter(None, [txin.get_time_based_relative_locktime() for txin in self.inputs()])) locktimes = list(filter(None, [txin.get_time_based_relative_locktime() for txin in self.inputs()]))
return max(locktimes) if locktimes else None return max(locktimes) if locktimes else None
def get_block_based_relative_locktime(self) -> Optional[int]: def get_block_based_relative_locktime(self) -> Optional[int]:
if self.version < 2: if self.version < 2:
return return None
locktimes = list(filter(None, [txin.get_block_based_relative_locktime() for txin in self.inputs()])) locktimes = list(filter(None, [txin.get_block_based_relative_locktime() for txin in self.inputs()]))
return max(locktimes) if locktimes else None return max(locktimes) if locktimes else None

View File

@@ -45,7 +45,7 @@
# #
# 1. CPFP: # 1. CPFP:
# When a batch is forgotten but not mined (because the server returned an error), we no longer bump its fee. # When a batch is forgotten but not mined (because the server returned an error), we no longer bump its fee.
# However, the current code does not theat the next batch as a CPFP when computing the fee. # However, the current code does not treat the next batch as a CPFP when computing the fee.
# #
# 2. Reorgs: # 2. Reorgs:
# This code does not guarantee that a payment or a sweep will happen. # This code does not guarantee that a payment or a sweep will happen.
@@ -56,6 +56,13 @@
# In the case of sweeps, lnwatcher ensures that SweepInfo is added again after a client restart. # In the case of sweeps, lnwatcher ensures that SweepInfo is added again after a client restart.
# In order to generalize that logic to payments, callers would need to pass a unique ID along with # In order to generalize that logic to payments, callers would need to pass a unique ID along with
# the payment output, so that we can prevent paying twice. # the payment output, so that we can prevent paying twice.
#
# - nLocktime/CLTV values (bip-65) and nSequence/CSV values (bip-112) are either explicitly
# or implicitly block-height-based everywhere in this file.
# SCRIPT execution fails on height vs timestamp confusion, and
# it is not safe to do naive integer comparison between these values without establishing type.
# TODO review this is correct, and add checks.
# nLocktime/CLTV usage in particular seems dangerously *implicit* for being block-heights
import asyncio import asyncio
import threading import threading
@@ -517,7 +524,7 @@ class TxBatch(Logger):
# sort inputs so that txin-txout pairs are first # sort inputs so that txin-txout pairs are first
for sweep_info in sorted(to_sweep, key=lambda x: not bool(x.txout)): for sweep_info in sorted(to_sweep, key=lambda x: not bool(x.txout)):
if sweep_info.cltv_abs is not None: if sweep_info.cltv_abs is not None:
if locktime is None or locktime < sweep_info.cltv_abs: if locktime is None or locktime < sweep_info.cltv_abs: # FIXME height vs timestamp confusion
# nLockTime must be greater than or equal to the stack operand. # nLockTime must be greater than or equal to the stack operand.
locktime = sweep_info.cltv_abs locktime = sweep_info.cltv_abs
inputs.append(copy.deepcopy(sweep_info.txin)) inputs.append(copy.deepcopy(sweep_info.txin))