lnchan: if funding tx is coinbase tx, wait for maturity
What changed, and why it matters
This commit adds a check so that if a Lightning channel is funded by a coinbase transaction (newly created bitcoins from mining), Electrum waits for the standard 100-block maturity period before treating the funding as settled. Before this change, Electrum might have accepted an immature coinbase funding transaction too early, which could lead to an invalid channel state or forced channel closure if the coinbase output later becomes invalid or reorganized. The fix follows a recent update to the Lightning protocol specification.
Review and merge if part of a supported branch. Address the zeroconf FIXME by validating coinbase-funded zeroconf channels before OPEN state, or explicitly disallow them, to avoid forced closures.
Security signals we found
Prevents premature acceptance of immature coinbase outputs as Lightning channel funding
Aligns implementation with updated Lightning BOLT specification
Adds missing coinbase detection helper on Transaction class
FIXME comment indicates residual uncertainty around zeroconf handling
Evidence from the diff
The patch introduces Transaction.is_coinbase_tx(), which reports true if the transaction’s first input is a coinbase input. In Channel.is_funding_tx_mined(), after locating the funding transaction, the code now checks funding_tx.is_coinbase_tx() and returns False if the transaction has fewer than COINBASE_MATURITY (100) confirmations. This prevents the channel from progressing until coinbase outputs are spendable. A FIXME comment notes an unresolved edge case for zeroconf channels funded by coinbase transactions.
Changed components
electrum/lnchannel.pyelectrum/transaction.pyInspect captured patch +13 / −1
diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py
index d6f1396..ce9085c 100644
--- a/electrum/lnchannel.py
+++ b/electrum/lnchannel.py
@@ -34,7 +34,7 @@ from electrum_ecc import ECPubkey
from . import constants, util
from .util import bfh, chunks, TxMinedInfo, error_text_bytes_to_safe_str, now
-from .bitcoin import redeem_script_to_address
+from .bitcoin import redeem_script_to_address, COINBASE_MATURITY
from .crypto import sha256, sha256d
from .transaction import Transaction, PartialTransaction, TxInput, Sighash
from .logging import Logger
@@ -1997,6 +1997,15 @@ class Channel(AbstractChannel):
if not funding_tx:
self.logger.info(f"no funding_tx {funding_txid}")
return False
+ if funding_tx.is_coinbase_tx():
+ if conf < COINBASE_MATURITY:
+ # FIXME what about zeroconf? In the zeroconf case, is_funding_tx_mined is used as a late-check
+ # after the funding tx is already mined, to validate the funding output addr and value.
+ # If we return False here, we will force-close the channel.
+ # (though it's unlikely an LSP would open a zero-conf channel in a coinbase tx!)
+ # The proper way to fix this would be to have already validated the funding tx in the zeroconf case
+ # *before* we progress it to the OPEN chan state (just like we do it for non-zeroconf chans).
+ return False
outp = funding_tx.outputs()[funding_idx]
redeem_script = funding_output_script(self.config[REMOTE], self.config[LOCAL])
funding_address = redeem_script_to_address('p2wsh', redeem_script)
diff --git a/electrum/transaction.py b/electrum/transaction.py
index 94d4063..69b8daa 100644
--- a/electrum/transaction.py
+++ b/electrum/transaction.py
@@ -1347,6 +1347,9 @@ class Transaction:
"""Whether the tx explicitly signals BIP-0125 replace-by-fee."""
return any([txin.nsequence < 0xffffffff - 1 for txin in self.inputs()])
+ def is_coinbase_tx(self) -> bool:
+ return self.inputs()[0].is_coinbase_input()
+
def estimated_size(self) -> int:
"""Return an estimated virtual tx size in vbytes.
BIP-0141 defines 'Virtual transaction size' to be weight/4 rounded up.
Why this scored 42/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.