mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
loopd: map static address loop-ins to swap status
This commit is contained in:
parent
df7dfba12a
commit
34cf98e8e8
4 changed files with 409 additions and 6 deletions
14
interface.go
14
interface.go
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
|
"github.com/lightninglabs/loop/fsm"
|
||||||
"github.com/lightninglabs/loop/loopdb"
|
"github.com/lightninglabs/loop/loopdb"
|
||||||
"github.com/lightninglabs/loop/swap"
|
"github.com/lightninglabs/loop/swap"
|
||||||
"github.com/lightninglabs/taproot-assets/rfqmath"
|
"github.com/lightninglabs/taproot-assets/rfqmath"
|
||||||
|
|
@ -476,8 +477,12 @@ type SwapInfoKit struct {
|
||||||
LastUpdateTime time.Time
|
LastUpdateTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// SwapInfo exposes common info fields for loop in and loop out swaps.
|
// SwapInfo exposes common info fields for traditional swaps and static address
|
||||||
|
// loop-ins.
|
||||||
type SwapInfo struct {
|
type SwapInfo struct {
|
||||||
|
// SwapStateData.State is authoritative for swap.TypeIn and swap.TypeOut.
|
||||||
|
// For swap.TypeStaticAddressLoopIn, StaticAddressLoopInState is
|
||||||
|
// authoritative and State remains its zero value, loopdb.StateInitiated.
|
||||||
loopdb.SwapStateData
|
loopdb.SwapStateData
|
||||||
|
|
||||||
loopdb.SwapContract
|
loopdb.SwapContract
|
||||||
|
|
@ -488,9 +493,14 @@ type SwapInfo struct {
|
||||||
// SwapHash stores the swap preimage hash.
|
// SwapHash stores the swap preimage hash.
|
||||||
SwapHash lntypes.Hash
|
SwapHash lntypes.Hash
|
||||||
|
|
||||||
// SwapType describes whether this is a loop in or loop out swap.
|
// SwapType describes the kind of swap.
|
||||||
SwapType swap.Type
|
SwapType swap.Type
|
||||||
|
|
||||||
|
// StaticAddressLoopInState stores the precise static address loop-in FSM
|
||||||
|
// state when SwapType is swap.TypeStaticAddressLoopIn. For traditional
|
||||||
|
// swaps, it remains the fsm.StateType zero value, fsm.EmptyState.
|
||||||
|
StaticAddressLoopInState fsm.StateType
|
||||||
|
|
||||||
// HtlcAddressP2WSH stores the address of the P2WSH (native segwit)
|
// HtlcAddressP2WSH stores the address of the P2WSH (native segwit)
|
||||||
// swap htlc. This is used for both loop-in and loop-out.
|
// swap htlc. This is used for both loop-in and loop-out.
|
||||||
HtlcAddressP2WSH btcutil.Address
|
HtlcAddressP2WSH btcutil.Address
|
||||||
|
|
|
||||||
|
|
@ -411,6 +411,8 @@ func (s *swapClientServer) marshallSwap(ctx context.Context,
|
||||||
}
|
}
|
||||||
|
|
||||||
var swapType looprpc.SwapType
|
var swapType looprpc.SwapType
|
||||||
|
staticLoopInState := looprpc.
|
||||||
|
StaticAddressLoopInSwapState_UNKNOWN_STATIC_ADDRESS_SWAP_STATE
|
||||||
var (
|
var (
|
||||||
htlcAddress string
|
htlcAddress string
|
||||||
htlcAddressP2TR string
|
htlcAddressP2TR string
|
||||||
|
|
@ -437,6 +439,27 @@ func (s *swapClientServer) marshallSwap(ctx context.Context,
|
||||||
lastHop = loopSwap.LastHop[:]
|
lastHop = loopSwap.LastHop[:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case swap.TypeStaticAddressLoopIn:
|
||||||
|
// Static loop-ins surface their precise FSM state through the
|
||||||
|
// optional oneof and keep the reconstructed HTLC P2WSH address,
|
||||||
|
// not the reusable static address.
|
||||||
|
swapType = looprpc.SwapType_STATIC_LOOP_IN
|
||||||
|
staticLoopInState = toClientStaticAddressLoopInState(
|
||||||
|
loopSwap.StaticAddressLoopInState,
|
||||||
|
)
|
||||||
|
|
||||||
|
if loopSwap.HtlcAddressP2WSH == nil {
|
||||||
|
return nil, errors.New(
|
||||||
|
"missing static address loop-in P2WSH HTLC address",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
htlcAddressP2WSH = loopSwap.HtlcAddressP2WSH.EncodeAddress()
|
||||||
|
htlcAddress = htlcAddressP2WSH
|
||||||
|
|
||||||
|
if loopSwap.LastHop != nil {
|
||||||
|
lastHop = loopSwap.LastHop[:]
|
||||||
|
}
|
||||||
|
|
||||||
case swap.TypeOut:
|
case swap.TypeOut:
|
||||||
swapType = looprpc.SwapType_LOOP_OUT
|
swapType = looprpc.SwapType_LOOP_OUT
|
||||||
if loopSwap.HtlcAddressP2WSH != nil {
|
if loopSwap.HtlcAddressP2WSH != nil {
|
||||||
|
|
@ -478,7 +501,7 @@ func (s *swapClientServer) marshallSwap(ctx context.Context,
|
||||||
return nil, errors.New("unknown swap type")
|
return nil, errors.New("unknown swap type")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &looprpc.SwapStatus{
|
rpcSwap := &looprpc.SwapStatus{
|
||||||
Amt: int64(loopSwap.AmountRequested),
|
Amt: int64(loopSwap.AmountRequested),
|
||||||
Id: loopSwap.SwapHash.String(),
|
Id: loopSwap.SwapHash.String(),
|
||||||
IdBytes: loopSwap.SwapHash[:],
|
IdBytes: loopSwap.SwapHash[:],
|
||||||
|
|
@ -497,7 +520,15 @@ func (s *swapClientServer) marshallSwap(ctx context.Context,
|
||||||
LastHop: lastHop,
|
LastHop: lastHop,
|
||||||
OutgoingChanSet: outGoingChanSet,
|
OutgoingChanSet: outGoingChanSet,
|
||||||
AssetInfo: assetInfo,
|
AssetInfo: assetInfo,
|
||||||
}, nil
|
}
|
||||||
|
if swapType == looprpc.SwapType_STATIC_LOOP_IN {
|
||||||
|
rpcSwap.StaticLoopInStateOptional =
|
||||||
|
&looprpc.SwapStatus_StaticLoopInState{
|
||||||
|
StaticLoopInState: staticLoopInState,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rpcSwap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Monitor will return a stream of swap updates for currently active swaps.
|
// Monitor will return a stream of swap updates for currently active swaps.
|
||||||
|
|
@ -2089,6 +2120,8 @@ func (s *swapClientServer) ListStaticAddressSwaps(ctx context.Context,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// staticAddressLoopInTimestamp converts a non-zero timestamp to Unix nano
|
||||||
|
// form and preserves zero timestamps as zero.
|
||||||
func staticAddressLoopInTimestamp(t time.Time) int64 {
|
func staticAddressLoopInTimestamp(t time.Time) int64 {
|
||||||
if t.IsZero() {
|
if t.IsZero() {
|
||||||
return 0
|
return 0
|
||||||
|
|
@ -2114,6 +2147,141 @@ func staticAddressLoopInSwapServerCost(swp *loopin.StaticAddressLoopIn) int64 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// staticAddressLoopInSwapInfos loads the static-address loop-in manager swaps
|
||||||
|
// and converts them to client-facing swap info records.
|
||||||
|
func (s *swapClientServer) staticAddressLoopInSwapInfos(
|
||||||
|
ctx context.Context) ([]*loop.SwapInfo, error) {
|
||||||
|
|
||||||
|
if s.staticLoopInManager == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
staticSwaps, err := s.staticLoopInManager.GetAllSwaps(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
swapInfos := make([]*loop.SwapInfo, 0, len(staticSwaps))
|
||||||
|
for _, swp := range staticSwaps {
|
||||||
|
if swp == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
swapInfo, err := s.staticAddressLoopInSwapInfo(ctx, swp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
swapInfos = append(swapInfos, swapInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return swapInfos, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// staticAddressLoopInSwapInfo converts one static-address loop-in into swap
|
||||||
|
// info using the daemon's current chain parameters.
|
||||||
|
func (s *swapClientServer) staticAddressLoopInSwapInfo(_ context.Context,
|
||||||
|
swp *loopin.StaticAddressLoopIn) (*loop.SwapInfo, error) {
|
||||||
|
|
||||||
|
chainParams, err := s.network.ChainParams()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting chain params")
|
||||||
|
}
|
||||||
|
|
||||||
|
return staticAddressLoopInSwapInfoWithChainParams(swp, chainParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
// staticAddressLoopInSwapInfoWithChainParams converts one static-address
|
||||||
|
// loop-in into swap info, including its reconstructed V2 P2WSH HTLC address.
|
||||||
|
func staticAddressLoopInSwapInfoWithChainParams(
|
||||||
|
swp *loopin.StaticAddressLoopIn,
|
||||||
|
chainParams *chaincfg.Params) (*loop.SwapInfo, error) {
|
||||||
|
|
||||||
|
htlcAddress, err := staticAddressLoopInHtlcAddress(swp, chainParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastHop *route.Vertex
|
||||||
|
if len(swp.LastHop) > 0 {
|
||||||
|
vertex, err := route.NewVertexFromBytes(swp.LastHop)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lastHop = &vertex
|
||||||
|
}
|
||||||
|
|
||||||
|
amount := swp.TotalDepositAmount()
|
||||||
|
if swp.SelectedAmount > 0 {
|
||||||
|
amount = swp.SelectedAmount
|
||||||
|
}
|
||||||
|
|
||||||
|
lastUpdate := swp.LastUpdateTime
|
||||||
|
if lastUpdate.IsZero() {
|
||||||
|
lastUpdate = swp.InitiationTime
|
||||||
|
}
|
||||||
|
|
||||||
|
return &loop.SwapInfo{
|
||||||
|
SwapStateData: loopdb.SwapStateData{
|
||||||
|
// Mirror ListStaticAddressSwaps by reporting only the persisted
|
||||||
|
// client-visible server cost. On-chain and off-chain costs stay
|
||||||
|
// zero until static loop-ins persist real fee data.
|
||||||
|
Cost: loopdb.SwapCost{
|
||||||
|
Server: btcutil.Amount(
|
||||||
|
staticAddressLoopInSwapServerCost(swp),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SwapContract: loopdb.SwapContract{
|
||||||
|
AmountRequested: amount,
|
||||||
|
CltvExpiry: swp.HtlcCltvExpiry,
|
||||||
|
MaxSwapFee: swp.MaxSwapFee,
|
||||||
|
InitiationTime: swp.InitiationTime,
|
||||||
|
Label: swp.Label,
|
||||||
|
ProtocolVersion: loopdb.ProtocolVersion(
|
||||||
|
swp.ProtocolVersion,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
LastUpdate: lastUpdate,
|
||||||
|
SwapHash: swp.SwapHash,
|
||||||
|
SwapType: swap.TypeStaticAddressLoopIn,
|
||||||
|
StaticAddressLoopInState: swp.GetState(),
|
||||||
|
HtlcAddressP2WSH: htlcAddress,
|
||||||
|
LastHop: lastHop,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// staticAddressLoopInHtlcAddress reconstructs the V2 P2WSH HTLC address from
|
||||||
|
// the static-address loop-in's client and server keys.
|
||||||
|
func staticAddressLoopInHtlcAddress(swp *loopin.StaticAddressLoopIn,
|
||||||
|
chainParams *chaincfg.Params) (btcutil.Address, error) {
|
||||||
|
|
||||||
|
if swp.ClientPubkey == nil {
|
||||||
|
return nil, errors.New("missing static address loop-in client HTLC key")
|
||||||
|
}
|
||||||
|
if swp.ServerPubkey == nil {
|
||||||
|
return nil, errors.New("missing static address loop-in server HTLC key")
|
||||||
|
}
|
||||||
|
|
||||||
|
htlc, err := swap.NewHtlcV2(
|
||||||
|
swp.HtlcCltvExpiry, pubkeyTo33ByteSlice(swp.ClientPubkey),
|
||||||
|
pubkeyTo33ByteSlice(swp.ServerPubkey), swp.SwapHash, chainParams,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("construct static address loop-in HTLC: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return htlc.Address, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pubkeyTo33ByteSlice converts a compressed public key to a fixed 33-byte
|
||||||
|
// array.
|
||||||
|
func pubkeyTo33ByteSlice(pubkey *btcec.PublicKey) [33]byte {
|
||||||
|
var pubkeyBytes [33]byte
|
||||||
|
copy(pubkeyBytes[:], pubkey.SerializeCompressed())
|
||||||
|
|
||||||
|
return pubkeyBytes
|
||||||
|
}
|
||||||
|
|
||||||
// GetStaticAddressSummary returns a summary of static address-related
|
// GetStaticAddressSummary returns a summary of static address-related
|
||||||
// information. Amongst deposits and withdrawals and their total values, it also
|
// information. Amongst deposits and withdrawals and their total values, it also
|
||||||
// includes a list of detailed deposit information filtered by their state.
|
// includes a list of detailed deposit information filtered by their state.
|
||||||
|
|
@ -2420,6 +2588,8 @@ func toClientDepositState(state fsm.StateType) looprpc.DepositState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// toClientStaticAddressLoopInState maps the static-address loop-in FSM state
|
||||||
|
// to the RPC enum exposed to clients.
|
||||||
func toClientStaticAddressLoopInState(
|
func toClientStaticAddressLoopInState(
|
||||||
state fsm.StateType) looprpc.StaticAddressLoopInSwapState {
|
state fsm.StateType) looprpc.StaticAddressLoopInSwapState {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
|
@ -25,6 +26,7 @@ import (
|
||||||
"github.com/lightninglabs/loop/staticaddr/script"
|
"github.com/lightninglabs/loop/staticaddr/script"
|
||||||
"github.com/lightninglabs/loop/swap"
|
"github.com/lightninglabs/loop/swap"
|
||||||
mock_lnd "github.com/lightninglabs/loop/test"
|
mock_lnd "github.com/lightninglabs/loop/test"
|
||||||
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
|
|
@ -501,6 +503,222 @@ func TestListStaticAddressSwapsPopulatesTimingAndCosts(t *testing.T) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestStaticAddressLoopInMarshallUsesStaticTypeAndP2WSH protects the RPC
|
||||||
|
// mapping invariant that static loop-ins expose their static type, static
|
||||||
|
// state, and P2WSH HTLC address without leaking a taproot HTLC address.
|
||||||
|
func TestStaticAddressLoopInMarshallUsesStaticTypeAndP2WSH(t *testing.T) {
|
||||||
|
server := &swapClientServer{}
|
||||||
|
loopSwap := &loop.SwapInfo{
|
||||||
|
SwapStateData: loopdb.SwapStateData{
|
||||||
|
State: loopdb.StateInitiated,
|
||||||
|
},
|
||||||
|
SwapContract: loopdb.SwapContract{
|
||||||
|
InitiationTime: time.Now(),
|
||||||
|
},
|
||||||
|
LastUpdate: time.Now(),
|
||||||
|
SwapHash: lntypes.Hash{1},
|
||||||
|
SwapType: swap.TypeStaticAddressLoopIn,
|
||||||
|
StaticAddressLoopInState: loopin.SignHtlcTx,
|
||||||
|
HtlcAddressP2WSH: testnetAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
rpcSwap, err := server.marshallSwap(t.Context(), loopSwap)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, looprpc.SwapType_STATIC_LOOP_IN, rpcSwap.Type)
|
||||||
|
require.Equal(
|
||||||
|
t, looprpc.StaticAddressLoopInSwapState_SIGN_HTLC_TX,
|
||||||
|
rpcSwap.GetStaticLoopInState(),
|
||||||
|
)
|
||||||
|
require.Equal(t, looprpc.SwapState_INITIATED, rpcSwap.State)
|
||||||
|
require.Equal(t, testnetAddr.EncodeAddress(), rpcSwap.HtlcAddressP2Wsh)
|
||||||
|
require.Empty(t, rpcSwap.HtlcAddressP2Tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestStaticAddressLoopInMarshallFailuresLeaveLegacyFieldsDefault asserts that
|
||||||
|
// static loop-in failures keep default legacy fields while preserving the
|
||||||
|
// precise static state.
|
||||||
|
func TestStaticAddressLoopInMarshallFailuresLeaveLegacyFieldsDefault(
|
||||||
|
t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
state fsm.StateType
|
||||||
|
wantStaticState looprpc.StaticAddressLoopInSwapState
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "failed",
|
||||||
|
state: loopin.Failed,
|
||||||
|
wantStaticState: looprpc.
|
||||||
|
StaticAddressLoopInSwapState_FAILED_STATIC_ADDRESS_SWAP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "succeeded transitioning failed",
|
||||||
|
state: loopin.SucceededTransitioningFailed,
|
||||||
|
wantStaticState: looprpc.
|
||||||
|
StaticAddressLoopInSwapState_SUCCEEDED_TRANSITIONING_FAILED,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
server, staticLoopIn := newGenericStaticLoopInServer(t)
|
||||||
|
staticLoopIn.SetState(test.state)
|
||||||
|
loopSwap, err := server.staticAddressLoopInSwapInfo(
|
||||||
|
t.Context(), staticLoopIn,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
rpcSwap, err := server.marshallSwap(t.Context(), loopSwap)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, looprpc.SwapState_INITIATED, rpcSwap.State)
|
||||||
|
require.Equal(
|
||||||
|
t, looprpc.FailureReason_FAILURE_REASON_NONE,
|
||||||
|
rpcSwap.FailureReason,
|
||||||
|
)
|
||||||
|
require.Equal(
|
||||||
|
t, test.wantStaticState,
|
||||||
|
rpcSwap.GetStaticLoopInState(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestStaticAddressLoopInMarshallRejectsMissingHtlcAddress protects the
|
||||||
|
// fail-closed HTLC-address invariant for static loop-ins missing the P2WSH
|
||||||
|
// address required by the client-facing RPC representation.
|
||||||
|
func TestStaticAddressLoopInMarshallRejectsMissingHtlcAddress(t *testing.T) {
|
||||||
|
_, taprootAddress := newTestStaticAddressParams(t)
|
||||||
|
server := &swapClientServer{}
|
||||||
|
loopSwap := &loop.SwapInfo{
|
||||||
|
SwapStateData: loopdb.SwapStateData{
|
||||||
|
State: loopdb.StateInitiated,
|
||||||
|
},
|
||||||
|
SwapContract: loopdb.SwapContract{
|
||||||
|
InitiationTime: time.Now(),
|
||||||
|
},
|
||||||
|
LastUpdate: time.Now(),
|
||||||
|
SwapHash: lntypes.Hash{1},
|
||||||
|
SwapType: swap.TypeStaticAddressLoopIn,
|
||||||
|
StaticAddressLoopInState: loopin.SignHtlcTx,
|
||||||
|
HtlcAddressP2TR: taprootAddress,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := server.marshallSwap(t.Context(), loopSwap)
|
||||||
|
require.ErrorContains(t, err, "missing static address loop-in P2WSH HTLC address")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestStaticAddressLoopInSwapInfoFailsClosedWhenHtlcKeysMissing protects the
|
||||||
|
// HTLC-address construction invariant that missing cooperative keys must not
|
||||||
|
// produce monitorable swap info.
|
||||||
|
func TestStaticAddressLoopInSwapInfoFailsClosedWhenHtlcKeysMissing(t *testing.T) {
|
||||||
|
server, staticLoopIn := newGenericStaticLoopInServer(t)
|
||||||
|
staticLoopIn.ClientPubkey = nil
|
||||||
|
|
||||||
|
_, err := server.staticAddressLoopInSwapInfo(t.Context(), staticLoopIn)
|
||||||
|
require.ErrorContains(
|
||||||
|
t, err, "missing static address loop-in client HTLC key",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGenericStaticLoopInServer(t *testing.T) (*swapClientServer,
|
||||||
|
*loopin.StaticAddressLoopIn) {
|
||||||
|
|
||||||
|
server, staticLoopIn, _ := newGenericStaticLoopInServerWithStore(t)
|
||||||
|
|
||||||
|
return server, staticLoopIn
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestStaticAddressParams(t *testing.T) (*script.Parameters,
|
||||||
|
*btcutil.AddressTaproot) {
|
||||||
|
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
const staticAddressExpiry = uint32(25)
|
||||||
|
|
||||||
|
_, staticClientPubkey := mock_lnd.CreateKey(12)
|
||||||
|
_, staticServerPubkey := mock_lnd.CreateKey(13)
|
||||||
|
staticAddress, err := script.NewStaticAddress(
|
||||||
|
input.MuSig2Version100RC2, int64(staticAddressExpiry),
|
||||||
|
staticClientPubkey, staticServerPubkey,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
staticPkScript, err := staticAddress.StaticAddressScript()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
taprootAddress, err := btcutil.NewAddressTaproot(
|
||||||
|
schnorr.SerializePubKey(staticAddress.TaprootKey),
|
||||||
|
&chaincfg.TestNet3Params,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return &script.Parameters{
|
||||||
|
ClientPubkey: staticClientPubkey,
|
||||||
|
ServerPubkey: staticServerPubkey,
|
||||||
|
Expiry: staticAddressExpiry,
|
||||||
|
PkScript: staticPkScript,
|
||||||
|
}, taprootAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGenericStaticLoopInServerWithStore(t *testing.T) (*swapClientServer,
|
||||||
|
*loopin.StaticAddressLoopIn, *mockStaticAddressLoopInStore) {
|
||||||
|
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
_, clientPubkey := mock_lnd.CreateKey(10)
|
||||||
|
_, serverPubkey := mock_lnd.CreateKey(11)
|
||||||
|
addressParams, _ := newTestStaticAddressParams(t)
|
||||||
|
depositOutpoint := wire.OutPoint{
|
||||||
|
Hash: chainhash.Hash{12, 13, 14},
|
||||||
|
Index: 2,
|
||||||
|
}
|
||||||
|
staticDeposit := &deposit.Deposit{
|
||||||
|
OutPoint: depositOutpoint,
|
||||||
|
Value: 51_000,
|
||||||
|
}
|
||||||
|
lastHop := route.Vertex{7, 8, 9}
|
||||||
|
|
||||||
|
staticLoopIn := &loopin.StaticAddressLoopIn{
|
||||||
|
SwapHash: lntypes.Hash{1, 2, 3},
|
||||||
|
HtlcCltvExpiry: 700,
|
||||||
|
InitiationTime: time.Unix(100, 0).UTC(),
|
||||||
|
LastUpdateTime: time.Unix(200, 0).UTC(),
|
||||||
|
Label: "static-loop-in",
|
||||||
|
ClientPubkey: clientPubkey,
|
||||||
|
ServerPubkey: serverPubkey,
|
||||||
|
LastHop: lastHop[:],
|
||||||
|
QuotedSwapFee: 1_111,
|
||||||
|
SelectedAmount: 50_000,
|
||||||
|
DepositOutpoints: []string{depositOutpoint.String()},
|
||||||
|
Deposits: []*deposit.Deposit{staticDeposit},
|
||||||
|
AddressParams: addressParams,
|
||||||
|
}
|
||||||
|
staticLoopIn.SetState(loopin.PaymentReceived)
|
||||||
|
|
||||||
|
depositStore := &mockDepositStore{
|
||||||
|
byOutpoint: map[string]*deposit.Deposit{
|
||||||
|
depositOutpoint.String(): staticDeposit,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
loopInStore := &mockStaticAddressLoopInStore{
|
||||||
|
swaps: []*loopin.StaticAddressLoopIn{staticLoopIn},
|
||||||
|
}
|
||||||
|
staticLoopInManager, err := loopin.NewManager(&loopin.Config{
|
||||||
|
Store: loopInStore,
|
||||||
|
DepositManager: deposit.NewManager(&deposit.ManagerConfig{
|
||||||
|
Store: depositStore,
|
||||||
|
}),
|
||||||
|
}, 1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return &swapClientServer{
|
||||||
|
network: lndclient.NetworkTestnet,
|
||||||
|
swaps: make(map[lntypes.Hash]loop.SwapInfo),
|
||||||
|
staticLoopInManager: staticLoopInManager,
|
||||||
|
}, staticLoopIn, loopInStore
|
||||||
|
}
|
||||||
|
|
||||||
// mockStaticAddressLoopInStore is a minimal in-memory loop-in store for RPC
|
// mockStaticAddressLoopInStore is a minimal in-memory loop-in store for RPC
|
||||||
// response mapping tests.
|
// response mapping tests.
|
||||||
type mockStaticAddressLoopInStore struct {
|
type mockStaticAddressLoopInStore struct {
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,13 @@ const (
|
||||||
|
|
||||||
// TypeOut is a loop out swap.
|
// TypeOut is a loop out swap.
|
||||||
TypeOut
|
TypeOut
|
||||||
|
|
||||||
|
// TypeStaticAddressLoopIn is a static-address loop-in swap.
|
||||||
|
TypeStaticAddressLoopIn
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsOut returns true if the swap is a loop out swap, false if it is a loop in
|
// IsOut returns true only if the swap is TypeOut; TypeIn and
|
||||||
// swap.
|
// TypeStaticAddressLoopIn both return false.
|
||||||
func (t Type) IsOut() bool {
|
func (t Type) IsOut() bool {
|
||||||
return t == TypeOut
|
return t == TypeOut
|
||||||
}
|
}
|
||||||
|
|
@ -23,6 +26,8 @@ func (t Type) String() string {
|
||||||
return "In"
|
return "In"
|
||||||
case TypeOut:
|
case TypeOut:
|
||||||
return "Out"
|
return "Out"
|
||||||
|
case TypeStaticAddressLoopIn:
|
||||||
|
return "StaticAddressLoopIn"
|
||||||
default:
|
default:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue