staticaddr/loopin: use generated change addresses

Create a fresh static change address for fractional loop-ins and persist
its key locator with the selected HTLC outpoint. Recovery reconstructs
the same change output instead of returning funds to the legacy root
address.
This commit is contained in:
Slyghtning 2026-07-10 14:23:57 +02:00
parent d1b29e0bb5
commit b2f37bfa78
No known key found for this signature in database
GPG key ID: F82D456EA023C9BF
18 changed files with 878 additions and 90 deletions

View file

@ -180,14 +180,25 @@ func (q *Queries) GetLoopInSwapUpdates(ctx context.Context, swapHash []byte) ([]
const getStaticAddressLoopInSwap = `-- name: GetStaticAddressLoopInSwap :one
SELECT
swaps.id, swaps.swap_hash, swaps.preimage, swaps.initiation_time, swaps.amount_requested, swaps.cltv_expiry, swaps.max_miner_fee, swaps.max_swap_fee, swaps.initiation_height, swaps.protocol_version, swaps.label,
static_address_swaps.id, static_address_swaps.swap_hash, static_address_swaps.swap_invoice, static_address_swaps.last_hop, static_address_swaps.payment_timeout_seconds, static_address_swaps.quoted_swap_fee_satoshis, static_address_swaps.deposit_outpoints, static_address_swaps.htlc_tx_fee_rate_sat_kw, static_address_swaps.htlc_timeout_sweep_tx_id, static_address_swaps.htlc_timeout_sweep_address, static_address_swaps.selected_amount, static_address_swaps.fast, static_address_swaps.confirmation_risk_decision, static_address_swaps.confirmation_risk_decision_time,
htlc_keys.swap_hash, htlc_keys.sender_script_pubkey, htlc_keys.receiver_script_pubkey, htlc_keys.sender_internal_pubkey, htlc_keys.receiver_internal_pubkey, htlc_keys.client_key_family, htlc_keys.client_key_index
static_address_swaps.id, static_address_swaps.swap_hash, static_address_swaps.swap_invoice, static_address_swaps.last_hop, static_address_swaps.payment_timeout_seconds, static_address_swaps.quoted_swap_fee_satoshis, static_address_swaps.deposit_outpoints, static_address_swaps.htlc_tx_fee_rate_sat_kw, static_address_swaps.htlc_timeout_sweep_tx_id, static_address_swaps.htlc_timeout_sweep_address, static_address_swaps.selected_amount, static_address_swaps.fast, static_address_swaps.confirmation_risk_decision, static_address_swaps.confirmation_risk_decision_time, static_address_swaps.change_static_address_id, static_address_swaps.confirmed_htlc_tx_id, static_address_swaps.confirmed_htlc_output_index, static_address_swaps.confirmed_htlc_output_value,
htlc_keys.swap_hash, htlc_keys.sender_script_pubkey, htlc_keys.receiver_script_pubkey, htlc_keys.sender_internal_pubkey, htlc_keys.receiver_internal_pubkey, htlc_keys.client_key_family, htlc_keys.client_key_index,
change_address.client_pubkey change_client_pubkey,
change_address.server_pubkey change_server_pubkey,
change_address.expiry change_expiry,
change_address.client_key_family change_client_key_family,
change_address.client_key_index change_client_key_index,
change_address.pkscript change_pkscript,
change_address.protocol_version change_protocol_version,
change_address.initiation_height change_initiation_height
FROM
swaps
JOIN
static_address_swaps ON swaps.swap_hash = static_address_swaps.swap_hash
JOIN
htlc_keys ON swaps.swap_hash = htlc_keys.swap_hash
LEFT JOIN
static_addresses change_address
ON static_address_swaps.change_static_address_id = change_address.id
WHERE
swaps.swap_hash = $1
`
@ -218,6 +229,10 @@ type GetStaticAddressLoopInSwapRow struct {
Fast bool
ConfirmationRiskDecision string
ConfirmationRiskDecisionTime sql.NullTime
ChangeStaticAddressID sql.NullInt32
ConfirmedHtlcTxID sql.NullString
ConfirmedHtlcOutputIndex sql.NullInt32
ConfirmedHtlcOutputValue sql.NullInt64
SwapHash_3 []byte
SenderScriptPubkey []byte
ReceiverScriptPubkey []byte
@ -225,6 +240,14 @@ type GetStaticAddressLoopInSwapRow struct {
ReceiverInternalPubkey []byte
ClientKeyFamily int32
ClientKeyIndex int32
ChangeClientPubkey []byte
ChangeServerPubkey []byte
ChangeExpiry sql.NullInt32
ChangeClientKeyFamily sql.NullInt32
ChangeClientKeyIndex sql.NullInt32
ChangePkscript []byte
ChangeProtocolVersion sql.NullInt32
ChangeInitiationHeight sql.NullInt32
}
func (q *Queries) GetStaticAddressLoopInSwap(ctx context.Context, swapHash []byte) (GetStaticAddressLoopInSwapRow, error) {
@ -256,6 +279,10 @@ func (q *Queries) GetStaticAddressLoopInSwap(ctx context.Context, swapHash []byt
&i.Fast,
&i.ConfirmationRiskDecision,
&i.ConfirmationRiskDecisionTime,
&i.ChangeStaticAddressID,
&i.ConfirmedHtlcTxID,
&i.ConfirmedHtlcOutputIndex,
&i.ConfirmedHtlcOutputValue,
&i.SwapHash_3,
&i.SenderScriptPubkey,
&i.ReceiverScriptPubkey,
@ -263,6 +290,14 @@ func (q *Queries) GetStaticAddressLoopInSwap(ctx context.Context, swapHash []byt
&i.ReceiverInternalPubkey,
&i.ClientKeyFamily,
&i.ClientKeyIndex,
&i.ChangeClientPubkey,
&i.ChangeServerPubkey,
&i.ChangeExpiry,
&i.ChangeClientKeyFamily,
&i.ChangeClientKeyIndex,
&i.ChangePkscript,
&i.ChangeProtocolVersion,
&i.ChangeInitiationHeight,
)
return i, err
}
@ -270,14 +305,25 @@ func (q *Queries) GetStaticAddressLoopInSwap(ctx context.Context, swapHash []byt
const getStaticAddressLoopInSwapsByStates = `-- name: GetStaticAddressLoopInSwapsByStates :many
SELECT
swaps.id, swaps.swap_hash, swaps.preimage, swaps.initiation_time, swaps.amount_requested, swaps.cltv_expiry, swaps.max_miner_fee, swaps.max_swap_fee, swaps.initiation_height, swaps.protocol_version, swaps.label,
static_address_swaps.id, static_address_swaps.swap_hash, static_address_swaps.swap_invoice, static_address_swaps.last_hop, static_address_swaps.payment_timeout_seconds, static_address_swaps.quoted_swap_fee_satoshis, static_address_swaps.deposit_outpoints, static_address_swaps.htlc_tx_fee_rate_sat_kw, static_address_swaps.htlc_timeout_sweep_tx_id, static_address_swaps.htlc_timeout_sweep_address, static_address_swaps.selected_amount, static_address_swaps.fast, static_address_swaps.confirmation_risk_decision, static_address_swaps.confirmation_risk_decision_time,
htlc_keys.swap_hash, htlc_keys.sender_script_pubkey, htlc_keys.receiver_script_pubkey, htlc_keys.sender_internal_pubkey, htlc_keys.receiver_internal_pubkey, htlc_keys.client_key_family, htlc_keys.client_key_index
static_address_swaps.id, static_address_swaps.swap_hash, static_address_swaps.swap_invoice, static_address_swaps.last_hop, static_address_swaps.payment_timeout_seconds, static_address_swaps.quoted_swap_fee_satoshis, static_address_swaps.deposit_outpoints, static_address_swaps.htlc_tx_fee_rate_sat_kw, static_address_swaps.htlc_timeout_sweep_tx_id, static_address_swaps.htlc_timeout_sweep_address, static_address_swaps.selected_amount, static_address_swaps.fast, static_address_swaps.confirmation_risk_decision, static_address_swaps.confirmation_risk_decision_time, static_address_swaps.change_static_address_id, static_address_swaps.confirmed_htlc_tx_id, static_address_swaps.confirmed_htlc_output_index, static_address_swaps.confirmed_htlc_output_value,
htlc_keys.swap_hash, htlc_keys.sender_script_pubkey, htlc_keys.receiver_script_pubkey, htlc_keys.sender_internal_pubkey, htlc_keys.receiver_internal_pubkey, htlc_keys.client_key_family, htlc_keys.client_key_index,
change_address.client_pubkey change_client_pubkey,
change_address.server_pubkey change_server_pubkey,
change_address.expiry change_expiry,
change_address.client_key_family change_client_key_family,
change_address.client_key_index change_client_key_index,
change_address.pkscript change_pkscript,
change_address.protocol_version change_protocol_version,
change_address.initiation_height change_initiation_height
FROM
swaps
JOIN
static_address_swaps ON swaps.swap_hash = static_address_swaps.swap_hash
JOIN
htlc_keys ON swaps.swap_hash = htlc_keys.swap_hash
LEFT JOIN
static_addresses change_address
ON static_address_swaps.change_static_address_id = change_address.id
JOIN
static_address_swap_updates u ON swaps.swap_hash = u.swap_hash
-- This subquery ensures that we are checking only the latest update for
@ -319,6 +365,10 @@ type GetStaticAddressLoopInSwapsByStatesRow struct {
Fast bool
ConfirmationRiskDecision string
ConfirmationRiskDecisionTime sql.NullTime
ChangeStaticAddressID sql.NullInt32
ConfirmedHtlcTxID sql.NullString
ConfirmedHtlcOutputIndex sql.NullInt32
ConfirmedHtlcOutputValue sql.NullInt64
SwapHash_3 []byte
SenderScriptPubkey []byte
ReceiverScriptPubkey []byte
@ -326,6 +376,14 @@ type GetStaticAddressLoopInSwapsByStatesRow struct {
ReceiverInternalPubkey []byte
ClientKeyFamily int32
ClientKeyIndex int32
ChangeClientPubkey []byte
ChangeServerPubkey []byte
ChangeExpiry sql.NullInt32
ChangeClientKeyFamily sql.NullInt32
ChangeClientKeyIndex sql.NullInt32
ChangePkscript []byte
ChangeProtocolVersion sql.NullInt32
ChangeInitiationHeight sql.NullInt32
}
func (q *Queries) GetStaticAddressLoopInSwapsByStates(ctx context.Context, dollar_1 sql.NullString) ([]GetStaticAddressLoopInSwapsByStatesRow, error) {
@ -363,6 +421,10 @@ func (q *Queries) GetStaticAddressLoopInSwapsByStates(ctx context.Context, dolla
&i.Fast,
&i.ConfirmationRiskDecision,
&i.ConfirmationRiskDecisionTime,
&i.ChangeStaticAddressID,
&i.ConfirmedHtlcTxID,
&i.ConfirmedHtlcOutputIndex,
&i.ConfirmedHtlcOutputValue,
&i.SwapHash_3,
&i.SenderScriptPubkey,
&i.ReceiverScriptPubkey,
@ -370,6 +432,14 @@ func (q *Queries) GetStaticAddressLoopInSwapsByStates(ctx context.Context, dolla
&i.ReceiverInternalPubkey,
&i.ClientKeyFamily,
&i.ClientKeyIndex,
&i.ChangeClientPubkey,
&i.ChangeServerPubkey,
&i.ChangeExpiry,
&i.ChangeClientKeyFamily,
&i.ChangeClientKeyIndex,
&i.ChangePkscript,
&i.ChangeProtocolVersion,
&i.ChangeInitiationHeight,
); err != nil {
return nil, err
}
@ -396,7 +466,8 @@ INSERT INTO static_address_swaps (
htlc_tx_fee_rate_sat_kw,
htlc_timeout_sweep_tx_id,
htlc_timeout_sweep_address,
fast
fast,
change_static_address_id
) VALUES (
$1,
$2,
@ -408,7 +479,8 @@ INSERT INTO static_address_swaps (
$8,
$9,
$10,
$11
$11,
$12
)
`
@ -424,6 +496,7 @@ type InsertStaticAddressLoopInParams struct {
HtlcTimeoutSweepTxID sql.NullString
HtlcTimeoutSweepAddress string
Fast bool
ChangeStaticAddressID sql.NullInt32
}
func (q *Queries) InsertStaticAddressLoopIn(ctx context.Context, arg InsertStaticAddressLoopInParams) error {
@ -439,6 +512,7 @@ func (q *Queries) InsertStaticAddressLoopIn(ctx context.Context, arg InsertStati
arg.HtlcTimeoutSweepTxID,
arg.HtlcTimeoutSweepAddress,
arg.Fast,
arg.ChangeStaticAddressID,
)
return err
}
@ -565,18 +639,31 @@ const updateStaticAddressLoopIn = `-- name: UpdateStaticAddressLoopIn :exec
UPDATE static_address_swaps
SET
htlc_tx_fee_rate_sat_kw = $2,
htlc_timeout_sweep_tx_id = $3
htlc_timeout_sweep_tx_id = $3,
confirmed_htlc_tx_id = $4,
confirmed_htlc_output_index = $5,
confirmed_htlc_output_value = $6
WHERE
swap_hash = $1
`
type UpdateStaticAddressLoopInParams struct {
SwapHash []byte
HtlcTxFeeRateSatKw int64
HtlcTimeoutSweepTxID sql.NullString
SwapHash []byte
HtlcTxFeeRateSatKw int64
HtlcTimeoutSweepTxID sql.NullString
ConfirmedHtlcTxID sql.NullString
ConfirmedHtlcOutputIndex sql.NullInt32
ConfirmedHtlcOutputValue sql.NullInt64
}
func (q *Queries) UpdateStaticAddressLoopIn(ctx context.Context, arg UpdateStaticAddressLoopInParams) error {
_, err := q.db.ExecContext(ctx, updateStaticAddressLoopIn, arg.SwapHash, arg.HtlcTxFeeRateSatKw, arg.HtlcTimeoutSweepTxID)
_, err := q.db.ExecContext(ctx, updateStaticAddressLoopIn,
arg.SwapHash,
arg.HtlcTxFeeRateSatKw,
arg.HtlcTimeoutSweepTxID,
arg.ConfirmedHtlcTxID,
arg.ConfirmedHtlcOutputIndex,
arg.ConfirmedHtlcOutputValue,
)
return err
}