loop/staticaddr/deposit/deposit.go
Slyghtning 58fbe2230e
staticaddr/deposit: guard confirmation height access
Document deposit lock ownership for mutable confirmation state and
route production reads through deposit accessors.

Keep store persistence on no-lock helpers while callers hold the
deposit lock, preserving the existing transition behavior without
leaving direct field reads in user-facing paths.
2026-07-08 09:01:01 +02:00

149 lines
3.4 KiB
Go

package deposit
import (
"crypto/rand"
"fmt"
"sync"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/loop/fsm"
"github.com/lightningnetwork/lnd/lntypes"
)
// ID is a unique identifier for a deposit.
type ID [IdLength]byte
// FromByteSlice creates a deposit id from a byte slice.
func (r *ID) FromByteSlice(b []byte) error {
if len(b) != IdLength {
return fmt.Errorf("deposit id must be 32 bytes, got %d, %x",
len(b), b)
}
copy(r[:], b)
return nil
}
// Deposit bundles an utxo at a static address together with manager-relevant
// data.
//
// Lock order: if both Manager.mu and a Deposit lock are needed, acquire
// Manager.mu before Deposit.Lock. Never acquire Manager.mu while holding a
// Deposit lock.
//
// The state and ConfirmationHeight fields are mutable and protected by the
// deposit lock.
type Deposit struct {
sync.Mutex
// Outpoint of the deposit.
wire.OutPoint
// ID is the unique identifier of the deposit.
ID ID
// state is the current state of the deposit.
state fsm.StateType
// Value is the amount of the deposit.
Value btcutil.Amount
// ConfirmationHeight is the absolute height at which the deposit was
// first confirmed.
ConfirmationHeight int64
// TimeOutSweepPkScript is the pk script that is used to sweep the
// deposit to after it is expired.
TimeOutSweepPkScript []byte
// ExpirySweepTxid is the transaction id of the expiry sweep.
ExpirySweepTxid chainhash.Hash
// SwapHash is an optional reference to a static address loop-in swap
// that used this deposit.
SwapHash *lntypes.Hash
// FinalizedWithdrawalTx is the coop-signed withdrawal transaction. It
// is republished on new block arrivals and on client restarts.
FinalizedWithdrawalTx *wire.MsgTx
}
// IsInFinalState returns true if the deposit is final.
func (d *Deposit) IsInFinalState() bool {
d.Lock()
defer d.Unlock()
return d.isInFinalStateNoLock()
}
// isInFinalStateNoLock returns true if the deposit is final without acquiring
// the deposit lock.
func (d *Deposit) isInFinalStateNoLock() bool {
return d.state == Expired || d.state == Withdrawn ||
d.state == LoopedIn || d.state == HtlcTimeoutSwept ||
d.state == ChannelPublished
}
func (d *Deposit) IsExpired(currentHeight, expiry uint32) bool {
d.Lock()
defer d.Unlock()
return currentHeight >= uint32(d.ConfirmationHeight)+expiry
}
func (d *Deposit) GetState() fsm.StateType {
d.Lock()
defer d.Unlock()
return d.state
}
func (d *Deposit) getStateNoLock() fsm.StateType {
return d.state
}
func (d *Deposit) SetState(state fsm.StateType) {
d.Lock()
defer d.Unlock()
d.state = state
}
func (d *Deposit) setStateNoLock(state fsm.StateType) {
d.state = state
}
func (d *Deposit) IsInState(state fsm.StateType) bool {
d.Lock()
defer d.Unlock()
return d.state == state
}
func (d *Deposit) isInStateNoLock(state fsm.StateType) bool {
return d.state == state
}
// GetConfirmationHeight returns the deposit confirmation height.
func (d *Deposit) GetConfirmationHeight() int64 {
d.Lock()
defer d.Unlock()
return d.ConfirmationHeight
}
// GetConfirmationHeightNoLock returns the deposit confirmation height without
// acquiring the deposit lock.
func (d *Deposit) GetConfirmationHeightNoLock() int64 {
return d.ConfirmationHeight
}
// GetRandomDepositID generates a random deposit ID.
func GetRandomDepositID() (ID, error) {
var id ID
_, err := rand.Read(id[:])
return id, err
}