1
0

lnutil+lnchannel: add anchors, adapt to_remote

* to_remote has now an additional csv lock of 1
* anchor outputs are added if to_local/remote outputs are present
* funder balance is reduced to accomodate anchors
This commit is contained in:
bitromortac
2021-09-13 13:41:01 +02:00
committed by ThomasV
parent 7907b9c05d
commit 7aa3dc1e40
3 changed files with 155 additions and 45 deletions

View File

@@ -52,6 +52,7 @@ HTLC_SUCCESS_WEIGHT_ANCHORS = 706
COMMITMENT_TX_WEIGHT = 724
COMMITMENT_TX_WEIGHT_ANCHORS = 1124
HTLC_OUTPUT_WEIGHT = 172
FIXED_ANCHOR_SAT = 330
LN_MAX_FUNDING_SAT_LEGACY = pow(2, 24) - 1
DUST_LIMIT_MAX = 1000
@@ -992,26 +993,64 @@ RECEIVED = Direction.RECEIVED
LOCAL = HTLCOwner.LOCAL
REMOTE = HTLCOwner.REMOTE
def make_commitment_outputs(*, fees_per_participant: Mapping[HTLCOwner, int], local_amount_msat: int, remote_amount_msat: int,
local_script: bytes, remote_script: bytes, htlcs: List[ScriptHtlc], dust_limit_sat: int) -> Tuple[List[PartialTxOutput], List[PartialTxOutput]]:
# BOLT-03: "Base commitment transaction fees are extracted from the funder's amount;
# if that amount is insufficient, the entire amount of the funder's output is used."
# -> if funder cannot afford feerate, their output might go negative, so take max(0, x) here:
to_local_amt = max(0, local_amount_msat - fees_per_participant[LOCAL])
to_local = PartialTxOutput(scriptpubkey=local_script, value=to_local_amt // 1000)
to_remote_amt = max(0, remote_amount_msat - fees_per_participant[REMOTE])
to_remote = PartialTxOutput(scriptpubkey=remote_script, value=to_remote_amt // 1000)
non_htlc_outputs = [to_local, to_remote]
def make_commitment_outputs(
*,
fees_per_participant: Mapping[HTLCOwner, int],
local_amount_msat: int,
remote_amount_msat: int,
local_script: bytes,
remote_script: bytes,
htlcs: List[ScriptHtlc],
dust_limit_sat: int,
has_anchors: bool,
local_anchor_script: Optional[str],
remote_anchor_script: Optional[str]
) -> Tuple[List[PartialTxOutput], List[PartialTxOutput]]:
# determine HTLC outputs and trim below dust to know if anchors need to be included
htlc_outputs = []
for script, htlc in htlcs:
addr = bitcoin.redeem_script_to_address('p2wsh', script)
htlc_outputs.append(PartialTxOutput(scriptpubkey=address_to_script(addr),
value=htlc.amount_msat // 1000))
if htlc.amount_msat // 1000 > dust_limit_sat:
htlc_outputs.append(
PartialTxOutput(
scriptpubkey=address_to_script(addr),
value=htlc.amount_msat // 1000
))
# BOLT-03: "Base commitment transaction fees are extracted from the funder's amount;
# if that amount is insufficient, the entire amount of the funder's output is used."
non_htlc_outputs = []
to_local_amt_msat = local_amount_msat - fees_per_participant[LOCAL]
to_remote_amt_msat = remote_amount_msat - fees_per_participant[REMOTE]
anchor_outputs = []
# if no anchor scripts are set, we ignore anchor outputs, useful when this
# function is used to determine outputs for a collaborative close
if has_anchors and local_anchor_script and remote_anchor_script:
local_pays_anchors = bool(fees_per_participant[LOCAL])
# we always allocate for two anchor outputs even if they are not added
if local_pays_anchors:
to_local_amt_msat -= 2 * FIXED_ANCHOR_SAT * 1000
else:
to_remote_amt_msat -= 2 * FIXED_ANCHOR_SAT * 1000
# include anchors for outputs that materialize, include both if there are HTLCs present
if to_local_amt_msat // 1000 >= dust_limit_sat or htlc_outputs:
anchor_outputs.append(PartialTxOutput(scriptpubkey=local_anchor_script, value=FIXED_ANCHOR_SAT))
if to_remote_amt_msat // 1000 >= dust_limit_sat or htlc_outputs:
anchor_outputs.append(PartialTxOutput(scriptpubkey=remote_anchor_script, value=FIXED_ANCHOR_SAT))
# if funder cannot afford feerate, their output might go negative, so take max(0, x) here
to_local_amt_msat = max(0, to_local_amt_msat)
to_remote_amt_msat = max(0, to_remote_amt_msat)
non_htlc_outputs.append(PartialTxOutput(scriptpubkey=local_script, value=to_local_amt_msat // 1000))
non_htlc_outputs.append(PartialTxOutput(scriptpubkey=remote_script, value=to_remote_amt_msat // 1000))
# trim outputs
c_outputs_filtered = list(filter(lambda x: x.value >= dust_limit_sat, non_htlc_outputs + htlc_outputs))
return htlc_outputs, c_outputs_filtered
c_outputs = c_outputs_filtered + anchor_outputs
return htlc_outputs, c_outputs
def effective_htlc_tx_weight(success: bool, has_anchors: bool):
@@ -1086,7 +1125,8 @@ def make_commitment(
remote_amount: int,
dust_limit_sat: int,
fees_per_participant: Mapping[HTLCOwner, int],
htlcs: List[ScriptHtlc]
htlcs: List[ScriptHtlc],
has_anchors: bool
) -> PartialTransaction:
c_input = make_funding_input(local_funding_pubkey, remote_funding_pubkey,
funding_pos, funding_txid, funding_sat)
@@ -1099,7 +1139,12 @@ def make_commitment(
# commitment tx outputs
local_address = make_commitment_output_to_local_address(revocation_pubkey, to_self_delay, delayed_pubkey)
remote_address = make_commitment_output_to_remote_address(remote_payment_pubkey)
remote_address = make_commitment_output_to_remote_address(remote_payment_pubkey, has_anchors)
local_anchor_address = None
remote_anchor_address = None
if has_anchors:
local_anchor_address = make_commitment_output_to_anchor_address(local_funding_pubkey)
remote_anchor_address = make_commitment_output_to_anchor_address(remote_funding_pubkey)
# note: it is assumed that the given 'htlcs' are all non-dust (dust htlcs already trimmed)
# BOLT-03: "Transaction Input and Output Ordering
@@ -1116,7 +1161,11 @@ def make_commitment(
local_script=address_to_script(local_address),
remote_script=address_to_script(remote_address),
htlcs=htlcs,
dust_limit_sat=dust_limit_sat)
dust_limit_sat=dust_limit_sat,
has_anchors=has_anchors,
local_anchor_script=address_to_script(local_anchor_address) if local_anchor_address else None,
remote_anchor_script=address_to_script(remote_anchor_address) if remote_anchor_address else None
)
assert sum(x.value for x in c_outputs_filtered) <= funding_sat, (c_outputs_filtered, funding_sat)
@@ -1148,8 +1197,39 @@ def make_commitment_output_to_local_address(
local_script = make_commitment_output_to_local_witness_script(revocation_pubkey, to_self_delay, delayed_pubkey)
return bitcoin.redeem_script_to_address('p2wsh', local_script)
def make_commitment_output_to_remote_address(remote_payment_pubkey: bytes) -> str:
return bitcoin.pubkey_to_address('p2wpkh', remote_payment_pubkey.hex())
def make_commitment_output_to_remote_witness_script(remote_payment_pubkey: bytes) -> bytes:
assert isinstance(remote_payment_pubkey, bytes)
script = construct_script([
remote_payment_pubkey,
opcodes.OP_CHECKSIGVERIFY,
opcodes.OP_1,
opcodes.OP_CHECKSEQUENCEVERIFY,
])
return script
def make_commitment_output_to_remote_address(remote_payment_pubkey: bytes, has_anchors: bool) -> str:
if has_anchors:
remote_script = make_commitment_output_to_remote_witness_script(remote_payment_pubkey)
return bitcoin.redeem_script_to_address('p2wsh', remote_script.hex())
else:
return bitcoin.pubkey_to_address('p2wpkh', remote_payment_pubkey.hex())
def make_commitment_output_to_anchor_witness_script(funding_pubkey: bytes) -> bytes:
assert isinstance(funding_pubkey, bytes)
script = construct_script([
funding_pubkey,
opcodes.OP_CHECKSIG,
opcodes.OP_IFDUP,
opcodes.OP_NOTIF,
opcodes.OP_16,
opcodes.OP_CHECKSEQUENCEVERIFY,
opcodes.OP_ENDIF,
])
return script
def make_commitment_output_to_anchor_address(funding_pubkey: bytes) -> str:
script = make_commitment_output_to_anchor_witness_script(funding_pubkey)
return bitcoin.redeem_script_to_address('p2wsh', script.hex())
def sign_and_get_sig_string(tx: PartialTransaction, local_config, remote_config):
tx.sign({local_config.multisig_key.pubkey: local_config.multisig_key.privkey})