staticaddr/loopin: persist risk decisions

Store server confirmation-risk decisions with static loop-in swaps and
recover accepted payment-deadline timers after restart. Wire
notification persistence through loopd so recovered swaps do not lose
pending risk state.

Deduplicate notification fanout cache entries by swap hash.
This commit is contained in:
Slyghtning 2026-06-26 12:54:30 +02:00
parent d045d5ecd2
commit fc3fba7c23
No known key found for this signature in database
GPG key ID: F82D456EA023C9BF
17 changed files with 1713 additions and 286 deletions

View file

@ -0,0 +1,3 @@
-- Drop confirmation-risk decision fields from static address loop-ins.
ALTER TABLE static_address_swaps DROP COLUMN confirmation_risk_decision;
ALTER TABLE static_address_swaps DROP COLUMN confirmation_risk_decision_time;

View file

@ -0,0 +1,9 @@
-- confirmation_risk_decision records the server's confirmation-risk decision
-- for a static address loop-in. The empty string means no decision has been
-- received yet.
ALTER TABLE static_address_swaps ADD COLUMN confirmation_risk_decision TEXT NOT NULL DEFAULT '';
-- confirmation_risk_decision_time records when loopd received and persisted
-- the server's decision, so payment deadlines can be reconstructed after
-- restart.
ALTER TABLE static_address_swaps ADD COLUMN confirmation_risk_decision_time TIMESTAMP;

View file

@ -137,18 +137,20 @@ type StaticAddress struct {
}
type StaticAddressSwap struct {
ID int32
SwapHash []byte
SwapInvoice string
LastHop []byte
PaymentTimeoutSeconds int32
QuotedSwapFeeSatoshis int64
DepositOutpoints string
HtlcTxFeeRateSatKw int64
HtlcTimeoutSweepTxID sql.NullString
HtlcTimeoutSweepAddress string
SelectedAmount int64
Fast bool
ID int32
SwapHash []byte
SwapInvoice string
LastHop []byte
PaymentTimeoutSeconds int32
QuotedSwapFeeSatoshis int64
DepositOutpoints string
HtlcTxFeeRateSatKw int64
HtlcTimeoutSweepTxID sql.NullString
HtlcTimeoutSweepAddress string
SelectedAmount int64
Fast bool
ConfirmationRiskDecision string
ConfirmationRiskDecisionTime sql.NullTime
}
type StaticAddressSwapUpdate struct {

View file

@ -67,6 +67,7 @@ type Querier interface {
MapDepositToSwap(ctx context.Context, arg MapDepositToSwapParams) error
OverrideSelectedSwapAmount(ctx context.Context, arg OverrideSelectedSwapAmountParams) error
OverrideSwapCosts(ctx context.Context, arg OverrideSwapCostsParams) error
RecordStaticAddressRiskDecision(ctx context.Context, arg RecordStaticAddressRiskDecisionParams) error
SwapHashForDepositID(ctx context.Context, depositID []byte) ([]byte, error)
UpdateBatch(ctx context.Context, arg UpdateBatchParams) error
UpdateDeposit(ctx context.Context, arg UpdateDepositParams) error

View file

@ -33,6 +33,14 @@ SET
WHERE
swap_hash = $1;
-- name: RecordStaticAddressRiskDecision :exec
UPDATE static_address_swaps
SET
confirmation_risk_decision = $2,
confirmation_risk_decision_time = $3
WHERE
swap_hash = $1;
-- name: InsertStaticAddressMetaUpdate :exec
INSERT INTO static_address_swap_updates (
swap_hash,
@ -150,4 +158,3 @@ WHERE

View file

@ -153,7 +153,7 @@ 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.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
FROM
swaps
@ -166,36 +166,38 @@ WHERE
`
type GetStaticAddressLoopInSwapRow struct {
ID int32
SwapHash []byte
Preimage []byte
InitiationTime time.Time
AmountRequested int64
CltvExpiry int32
MaxMinerFee int64
MaxSwapFee int64
InitiationHeight int32
ProtocolVersion int32
Label string
ID_2 int32
SwapHash_2 []byte
SwapInvoice string
LastHop []byte
PaymentTimeoutSeconds int32
QuotedSwapFeeSatoshis int64
DepositOutpoints string
HtlcTxFeeRateSatKw int64
HtlcTimeoutSweepTxID sql.NullString
HtlcTimeoutSweepAddress string
SelectedAmount int64
Fast bool
SwapHash_3 []byte
SenderScriptPubkey []byte
ReceiverScriptPubkey []byte
SenderInternalPubkey []byte
ReceiverInternalPubkey []byte
ClientKeyFamily int32
ClientKeyIndex int32
ID int32
SwapHash []byte
Preimage []byte
InitiationTime time.Time
AmountRequested int64
CltvExpiry int32
MaxMinerFee int64
MaxSwapFee int64
InitiationHeight int32
ProtocolVersion int32
Label string
ID_2 int32
SwapHash_2 []byte
SwapInvoice string
LastHop []byte
PaymentTimeoutSeconds int32
QuotedSwapFeeSatoshis int64
DepositOutpoints string
HtlcTxFeeRateSatKw int64
HtlcTimeoutSweepTxID sql.NullString
HtlcTimeoutSweepAddress string
SelectedAmount int64
Fast bool
ConfirmationRiskDecision string
ConfirmationRiskDecisionTime sql.NullTime
SwapHash_3 []byte
SenderScriptPubkey []byte
ReceiverScriptPubkey []byte
SenderInternalPubkey []byte
ReceiverInternalPubkey []byte
ClientKeyFamily int32
ClientKeyIndex int32
}
func (q *Queries) GetStaticAddressLoopInSwap(ctx context.Context, swapHash []byte) (GetStaticAddressLoopInSwapRow, error) {
@ -225,6 +227,8 @@ func (q *Queries) GetStaticAddressLoopInSwap(ctx context.Context, swapHash []byt
&i.HtlcTimeoutSweepAddress,
&i.SelectedAmount,
&i.Fast,
&i.ConfirmationRiskDecision,
&i.ConfirmationRiskDecisionTime,
&i.SwapHash_3,
&i.SenderScriptPubkey,
&i.ReceiverScriptPubkey,
@ -239,7 +243,7 @@ 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.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
FROM
swaps
@ -263,36 +267,38 @@ ORDER BY
`
type GetStaticAddressLoopInSwapsByStatesRow struct {
ID int32
SwapHash []byte
Preimage []byte
InitiationTime time.Time
AmountRequested int64
CltvExpiry int32
MaxMinerFee int64
MaxSwapFee int64
InitiationHeight int32
ProtocolVersion int32
Label string
ID_2 int32
SwapHash_2 []byte
SwapInvoice string
LastHop []byte
PaymentTimeoutSeconds int32
QuotedSwapFeeSatoshis int64
DepositOutpoints string
HtlcTxFeeRateSatKw int64
HtlcTimeoutSweepTxID sql.NullString
HtlcTimeoutSweepAddress string
SelectedAmount int64
Fast bool
SwapHash_3 []byte
SenderScriptPubkey []byte
ReceiverScriptPubkey []byte
SenderInternalPubkey []byte
ReceiverInternalPubkey []byte
ClientKeyFamily int32
ClientKeyIndex int32
ID int32
SwapHash []byte
Preimage []byte
InitiationTime time.Time
AmountRequested int64
CltvExpiry int32
MaxMinerFee int64
MaxSwapFee int64
InitiationHeight int32
ProtocolVersion int32
Label string
ID_2 int32
SwapHash_2 []byte
SwapInvoice string
LastHop []byte
PaymentTimeoutSeconds int32
QuotedSwapFeeSatoshis int64
DepositOutpoints string
HtlcTxFeeRateSatKw int64
HtlcTimeoutSweepTxID sql.NullString
HtlcTimeoutSweepAddress string
SelectedAmount int64
Fast bool
ConfirmationRiskDecision string
ConfirmationRiskDecisionTime sql.NullTime
SwapHash_3 []byte
SenderScriptPubkey []byte
ReceiverScriptPubkey []byte
SenderInternalPubkey []byte
ReceiverInternalPubkey []byte
ClientKeyFamily int32
ClientKeyIndex int32
}
func (q *Queries) GetStaticAddressLoopInSwapsByStates(ctx context.Context, dollar_1 sql.NullString) ([]GetStaticAddressLoopInSwapsByStatesRow, error) {
@ -328,6 +334,8 @@ func (q *Queries) GetStaticAddressLoopInSwapsByStates(ctx context.Context, dolla
&i.HtlcTimeoutSweepAddress,
&i.SelectedAmount,
&i.Fast,
&i.ConfirmationRiskDecision,
&i.ConfirmationRiskDecisionTime,
&i.SwapHash_3,
&i.SenderScriptPubkey,
&i.ReceiverScriptPubkey,
@ -482,6 +490,26 @@ func (q *Queries) OverrideSelectedSwapAmount(ctx context.Context, arg OverrideSe
return err
}
const recordStaticAddressRiskDecision = `-- name: RecordStaticAddressRiskDecision :exec
UPDATE static_address_swaps
SET
confirmation_risk_decision = $2,
confirmation_risk_decision_time = $3
WHERE
swap_hash = $1
`
type RecordStaticAddressRiskDecisionParams struct {
SwapHash []byte
ConfirmationRiskDecision string
ConfirmationRiskDecisionTime sql.NullTime
}
func (q *Queries) RecordStaticAddressRiskDecision(ctx context.Context, arg RecordStaticAddressRiskDecisionParams) error {
_, err := q.db.ExecContext(ctx, recordStaticAddressRiskDecision, arg.SwapHash, arg.ConfirmationRiskDecision, arg.ConfirmationRiskDecisionTime)
return err
}
const swapHashForDepositID = `-- name: SwapHashForDepositID :one
SELECT
swap_hash