mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
Merge pull request #1165 from GustavoStingelin/feat/monitor-static-address
Some checks failed
CI / Commit Message (push) Has been cancelled
CI / RPC compilation check (push) Has been cancelled
CI / SQL compilation check (push) Has been cancelled
CI / go mod check (push) Has been cancelled
CI / build and lint code (push) Has been cancelled
CI / verify that auto-generated documentation is up-to-date (push) Has been cancelled
CI / run unit-test sqlite3 race (push) Has been cancelled
CI / run unit-test postgres race (push) Has been cancelled
CI / run LiT itests (push) Has been cancelled
CI / run LiT unit tests (push) Has been cancelled
Some checks failed
CI / Commit Message (push) Has been cancelled
CI / RPC compilation check (push) Has been cancelled
CI / SQL compilation check (push) Has been cancelled
CI / go mod check (push) Has been cancelled
CI / build and lint code (push) Has been cancelled
CI / verify that auto-generated documentation is up-to-date (push) Has been cancelled
CI / run unit-test sqlite3 race (push) Has been cancelled
CI / run unit-test postgres race (push) Has been cancelled
CI / run LiT itests (push) Has been cancelled
CI / run LiT unit tests (push) Has been cancelled
multi: add static address loop in monitor
This commit is contained in:
commit
edad26b876
24 changed files with 1968 additions and 181 deletions
|
|
@ -560,10 +560,7 @@ func parseAmt(text string) (btcutil.Amount, error) {
|
|||
|
||||
func logSwap(swap *looprpc.SwapStatus) {
|
||||
// If our swap failed, we add our failure reason to the state.
|
||||
swapState := fmt.Sprintf("%v", swap.State)
|
||||
if swap.State == looprpc.SwapState_FAILED {
|
||||
swapState = fmt.Sprintf("%v (%v)", swapState, swap.FailureReason)
|
||||
}
|
||||
swapState := monitorSwapState(swap)
|
||||
|
||||
if swap.Type == looprpc.SwapType_LOOP_OUT {
|
||||
fmt.Printf("%v %v %v %v - %v",
|
||||
|
|
@ -585,10 +582,32 @@ func logSwap(swap *looprpc.SwapStatus) {
|
|||
}
|
||||
}
|
||||
|
||||
if swap.State != looprpc.SwapState_INITIATED &&
|
||||
swap.State != looprpc.SwapState_HTLC_PUBLISHED &&
|
||||
swap.State != looprpc.SwapState_PREIMAGE_REVEALED {
|
||||
showCost := shouldShowSwapCost(swap.GetState())
|
||||
if swap.Type == looprpc.SwapType_STATIC_LOOP_IN {
|
||||
staticState := swap.GetStaticLoopInState()
|
||||
// Static loop-ins key cost visibility off the dedicated FSM state, not
|
||||
// the generic SwapState lifecycle used by traditional swaps.
|
||||
switch staticState {
|
||||
case looprpc.StaticAddressLoopInSwapState_INIT_HTLC,
|
||||
looprpc.StaticAddressLoopInSwapState_SIGN_HTLC_TX,
|
||||
looprpc.StaticAddressLoopInSwapState_MONITOR_INVOICE_HTLC_TX,
|
||||
looprpc.StaticAddressLoopInSwapState_SWEEP_STATIC_ADDRESS_HTLC_TIMEOUT,
|
||||
looprpc.StaticAddressLoopInSwapState_MONITOR_HTLC_TIMEOUT_SWEEP,
|
||||
looprpc.StaticAddressLoopInSwapState_UNLOCK_DEPOSITS:
|
||||
|
||||
showCost = false
|
||||
|
||||
case looprpc.StaticAddressLoopInSwapState_PAYMENT_RECEIVED,
|
||||
looprpc.StaticAddressLoopInSwapState_HTLC_STATIC_ADDRESS_TIMEOUT_SWEPT,
|
||||
looprpc.StaticAddressLoopInSwapState_SUCCEEDED,
|
||||
looprpc.StaticAddressLoopInSwapState_SUCCEEDED_TRANSITIONING_FAILED,
|
||||
looprpc.StaticAddressLoopInSwapState_FAILED_STATIC_ADDRESS_SWAP:
|
||||
|
||||
showCost = true
|
||||
}
|
||||
}
|
||||
|
||||
if showCost {
|
||||
fmt.Printf(" (cost: server %v, onchain %v, offchain %v)",
|
||||
swap.CostServer, swap.CostOnchain, swap.CostOffchain,
|
||||
)
|
||||
|
|
@ -597,6 +616,38 @@ func logSwap(swap *looprpc.SwapStatus) {
|
|||
fmt.Println()
|
||||
}
|
||||
|
||||
// monitorSwapState returns the static loop-in FSM label for static swaps and
|
||||
// the shared swap-state label for all others.
|
||||
func monitorSwapState(swap *looprpc.SwapStatus) string {
|
||||
if swap.Type == looprpc.SwapType_STATIC_LOOP_IN {
|
||||
return swap.GetStaticLoopInState().String()
|
||||
}
|
||||
|
||||
return genericMonitorSwapState(swap)
|
||||
}
|
||||
|
||||
// shouldShowSwapCost reports whether a swap's generic state is terminal enough
|
||||
// to include the persisted cost summary in monitor output.
|
||||
func shouldShowSwapCost(loopState looprpc.SwapState) bool {
|
||||
return loopState != looprpc.SwapState_INITIATED &&
|
||||
loopState != looprpc.SwapState_HTLC_PUBLISHED &&
|
||||
loopState != looprpc.SwapState_PREIMAGE_REVEALED
|
||||
}
|
||||
|
||||
// genericMonitorSwapState formats the shared swap-state label and failure
|
||||
// reason used by non-static swaps.
|
||||
func genericMonitorSwapState(swap *looprpc.SwapStatus) string {
|
||||
loopState := swap.GetState()
|
||||
swapState := fmt.Sprintf("%v", loopState)
|
||||
if loopState == looprpc.SwapState_FAILED {
|
||||
swapState = fmt.Sprintf(
|
||||
"%v (%v)", swapState, swap.FailureReason,
|
||||
)
|
||||
}
|
||||
|
||||
return swapState
|
||||
}
|
||||
|
||||
// getClientConn dials the loopd gRPC server with TLS and macaroon auth.
|
||||
func getClientConn(address, tlsCertPath, macaroonPath string) (daemonConn,
|
||||
func(), error) {
|
||||
|
|
|
|||
275
cmd/loop/monitor_test.go
Normal file
275
cmd/loop/monitor_test.go
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lightninglabs/loop/looprpc"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestMonitorSwapStateKeepsRegularLoopInFailureReason preserves the generic
|
||||
// failure suffix for regular loop-in swaps so terminal errors stay visible.
|
||||
func TestMonitorSwapStateKeepsRegularLoopInFailureReason(t *testing.T) {
|
||||
swap := &looprpc.SwapStatus{
|
||||
Type: looprpc.SwapType_LOOP_IN,
|
||||
State: looprpc.SwapState_FAILED,
|
||||
FailureReason: looprpc.
|
||||
FailureReason_FAILURE_REASON_TIMEOUT,
|
||||
}
|
||||
|
||||
got := monitorSwapState(swap)
|
||||
require.Equal(t, "FAILED (FAILURE_REASON_TIMEOUT)", got)
|
||||
}
|
||||
|
||||
// TestMonitorSwapStateLabelsStaticLoopInStages locks the precise static loop-in
|
||||
// stage names so monitor output stays stable as the FSM evolves.
|
||||
func TestMonitorSwapStateLabelsStaticLoopInStages(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
staticState looprpc.StaticAddressLoopInSwapState
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "init htlc",
|
||||
staticState: looprpc.StaticAddressLoopInSwapState_INIT_HTLC,
|
||||
want: "INIT_HTLC",
|
||||
},
|
||||
{
|
||||
name: "sign htlc",
|
||||
staticState: looprpc.
|
||||
StaticAddressLoopInSwapState_SIGN_HTLC_TX,
|
||||
want: "SIGN_HTLC_TX",
|
||||
},
|
||||
{
|
||||
name: "monitor invoice and htlc",
|
||||
staticState: looprpc.
|
||||
StaticAddressLoopInSwapState_MONITOR_INVOICE_HTLC_TX,
|
||||
want: "MONITOR_INVOICE_HTLC_TX",
|
||||
},
|
||||
{
|
||||
name: "unlock deposits",
|
||||
staticState: looprpc.
|
||||
StaticAddressLoopInSwapState_UNLOCK_DEPOSITS,
|
||||
want: "UNLOCK_DEPOSITS",
|
||||
},
|
||||
{
|
||||
name: "payment received",
|
||||
staticState: looprpc.StaticAddressLoopInSwapState_PAYMENT_RECEIVED,
|
||||
want: "PAYMENT_RECEIVED",
|
||||
},
|
||||
{
|
||||
name: "timeout sweep",
|
||||
staticState: looprpc.
|
||||
StaticAddressLoopInSwapState_SWEEP_STATIC_ADDRESS_HTLC_TIMEOUT,
|
||||
want: "SWEEP_STATIC_ADDRESS_HTLC_TIMEOUT",
|
||||
},
|
||||
{
|
||||
name: "monitor timeout sweep",
|
||||
staticState: looprpc.
|
||||
StaticAddressLoopInSwapState_MONITOR_HTLC_TIMEOUT_SWEEP,
|
||||
want: "MONITOR_HTLC_TIMEOUT_SWEEP",
|
||||
},
|
||||
{
|
||||
name: "timeout swept",
|
||||
staticState: looprpc.
|
||||
StaticAddressLoopInSwapState_HTLC_STATIC_ADDRESS_TIMEOUT_SWEPT,
|
||||
want: "HTLC_STATIC_ADDRESS_TIMEOUT_SWEPT",
|
||||
},
|
||||
{
|
||||
name: "success",
|
||||
staticState: looprpc.StaticAddressLoopInSwapState_SUCCEEDED,
|
||||
want: "SUCCEEDED",
|
||||
},
|
||||
{
|
||||
name: "succeeded transitioning failed",
|
||||
staticState: looprpc.
|
||||
StaticAddressLoopInSwapState_SUCCEEDED_TRANSITIONING_FAILED,
|
||||
want: "SUCCEEDED_TRANSITIONING_FAILED",
|
||||
},
|
||||
{
|
||||
name: "failed",
|
||||
staticState: looprpc.
|
||||
StaticAddressLoopInSwapState_FAILED_STATIC_ADDRESS_SWAP,
|
||||
want: "FAILED_STATIC_ADDRESS_SWAP",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
swap := &looprpc.SwapStatus{
|
||||
Type: looprpc.SwapType_STATIC_LOOP_IN,
|
||||
StaticLoopInStateOptional: &looprpc.SwapStatus_StaticLoopInState{
|
||||
StaticLoopInState: test.staticState,
|
||||
},
|
||||
}
|
||||
|
||||
got := monitorSwapState(swap)
|
||||
require.Equal(t, test.want, got)
|
||||
})
|
||||
}
|
||||
|
||||
initSwap := &looprpc.SwapStatus{
|
||||
Type: looprpc.SwapType_STATIC_LOOP_IN,
|
||||
StaticLoopInStateOptional: &looprpc.SwapStatus_StaticLoopInState{
|
||||
StaticLoopInState: looprpc.
|
||||
StaticAddressLoopInSwapState_INIT_HTLC,
|
||||
},
|
||||
}
|
||||
signSwap := &looprpc.SwapStatus{
|
||||
Type: looprpc.SwapType_STATIC_LOOP_IN,
|
||||
StaticLoopInStateOptional: &looprpc.SwapStatus_StaticLoopInState{
|
||||
StaticLoopInState: looprpc.
|
||||
StaticAddressLoopInSwapState_SIGN_HTLC_TX,
|
||||
},
|
||||
}
|
||||
require.NotEqual(t, monitorSwapState(initSwap), monitorSwapState(signSwap))
|
||||
}
|
||||
|
||||
// TestMonitorSwapStateLabelsUnknownStaticLoopIn ensures the absent static
|
||||
// oneof maps to the UNKNOWN static loop-in label.
|
||||
func TestMonitorSwapStateLabelsUnknownStaticLoopIn(t *testing.T) {
|
||||
swap := &looprpc.SwapStatus{
|
||||
Type: looprpc.SwapType_STATIC_LOOP_IN,
|
||||
}
|
||||
|
||||
got := monitorSwapState(swap)
|
||||
require.Equal(t, "UNKNOWN_STATIC_ADDRESS_SWAP_STATE", got)
|
||||
}
|
||||
|
||||
// TestLogSwapHidesStaticLoopInCostForInFlightState proves in-flight static FSM
|
||||
// state suppresses cost even with generic SUCCESS set deliberately.
|
||||
func TestLogSwapHidesStaticLoopInCostForInFlightState(t *testing.T) {
|
||||
swap := &looprpc.SwapStatus{
|
||||
LastUpdateTime: time.Unix(1, 0).UnixNano(),
|
||||
Type: looprpc.SwapType_STATIC_LOOP_IN,
|
||||
State: looprpc.SwapState_SUCCESS,
|
||||
StaticLoopInStateOptional: &looprpc.SwapStatus_StaticLoopInState{
|
||||
StaticLoopInState: looprpc.
|
||||
StaticAddressLoopInSwapState_INIT_HTLC,
|
||||
},
|
||||
Amt: 50_000,
|
||||
CostServer: 11,
|
||||
CostOnchain: 22,
|
||||
CostOffchain: 33,
|
||||
HtlcAddressP2Wsh: "bc1qstaticinflighttestaddress",
|
||||
}
|
||||
|
||||
output := captureStdout(t, func() {
|
||||
logSwap(swap)
|
||||
})
|
||||
|
||||
require.Contains(t, output, "STATIC_LOOP_IN INIT_HTLC 0.00050000 BTC")
|
||||
require.NotContains(t, output, "(cost:")
|
||||
}
|
||||
|
||||
// TestLogSwapHidesCostForUnknownStaticLoopInState ensures unknown static
|
||||
// states print the UNKNOWN label without a cost summary.
|
||||
func TestLogSwapHidesCostForUnknownStaticLoopInState(t *testing.T) {
|
||||
swap := &looprpc.SwapStatus{
|
||||
LastUpdateTime: time.Unix(3, 0).UnixNano(),
|
||||
Type: looprpc.SwapType_STATIC_LOOP_IN,
|
||||
StaticLoopInStateOptional: &looprpc.SwapStatus_StaticLoopInState{
|
||||
StaticLoopInState: looprpc.
|
||||
StaticAddressLoopInSwapState_UNKNOWN_STATIC_ADDRESS_SWAP_STATE,
|
||||
},
|
||||
Amt: 50_000,
|
||||
CostServer: 11,
|
||||
CostOnchain: 22,
|
||||
CostOffchain: 33,
|
||||
HtlcAddressP2Wsh: "bc1qunknownstaticterminaltestaddress",
|
||||
}
|
||||
|
||||
output := captureStdout(t, func() {
|
||||
logSwap(swap)
|
||||
})
|
||||
|
||||
require.Contains(
|
||||
t, output, "STATIC_LOOP_IN UNKNOWN_STATIC_ADDRESS_SWAP_STATE 0.00050000 BTC",
|
||||
)
|
||||
require.NotContains(t, output, "(cost:")
|
||||
}
|
||||
|
||||
// TestLogSwapShowsStaticLoopInCostForTerminalState preserves terminal cost
|
||||
// output, including the P2WSH address, once the static loop-in is done.
|
||||
func TestLogSwapShowsStaticLoopInCostForTerminalState(t *testing.T) {
|
||||
swap := &looprpc.SwapStatus{
|
||||
LastUpdateTime: time.Unix(2, 0).UnixNano(),
|
||||
Type: looprpc.SwapType_STATIC_LOOP_IN,
|
||||
State: looprpc.SwapState_INITIATED,
|
||||
StaticLoopInStateOptional: &looprpc.SwapStatus_StaticLoopInState{
|
||||
StaticLoopInState: looprpc.
|
||||
StaticAddressLoopInSwapState_SUCCEEDED,
|
||||
},
|
||||
Amt: 50_000,
|
||||
CostServer: 11,
|
||||
CostOnchain: 22,
|
||||
CostOffchain: 33,
|
||||
HtlcAddressP2Wsh: "bc1qstaticterminaltestaddress",
|
||||
}
|
||||
|
||||
output := captureStdout(t, func() {
|
||||
logSwap(swap)
|
||||
})
|
||||
|
||||
require.Contains(
|
||||
t, output,
|
||||
"STATIC_LOOP_IN SUCCEEDED 0.00050000 BTC - P2WSH: bc1qstaticterminaltestaddress",
|
||||
)
|
||||
require.Contains(
|
||||
t, output,
|
||||
"(cost: server 11, onchain 22, offchain 33)",
|
||||
)
|
||||
}
|
||||
|
||||
// TestLogSwapDisplaysStaticLoopInTypeAndStage ensures the monitor output names
|
||||
// the static loop-in type and active FSM stage instead of collapsing them.
|
||||
func TestLogSwapDisplaysStaticLoopInTypeAndStage(t *testing.T) {
|
||||
swap := &looprpc.SwapStatus{
|
||||
LastUpdateTime: time.Unix(1, 0).UnixNano(),
|
||||
Type: looprpc.SwapType_STATIC_LOOP_IN,
|
||||
StaticLoopInStateOptional: &looprpc.SwapStatus_StaticLoopInState{
|
||||
StaticLoopInState: looprpc.
|
||||
StaticAddressLoopInSwapState_SIGN_HTLC_TX,
|
||||
},
|
||||
Amt: 50_000,
|
||||
HtlcAddressP2Wsh: "tb1q5cyxnuxmeuwuvkwfem96llyxf8duyshm56t8k8",
|
||||
}
|
||||
|
||||
output := captureStdout(t, func() {
|
||||
logSwap(swap)
|
||||
})
|
||||
|
||||
require.Contains(
|
||||
t, output, "STATIC_LOOP_IN SIGN_HTLC_TX 0.00050000 BTC",
|
||||
)
|
||||
require.Contains(
|
||||
t, output, "P2WSH: tb1q5cyxnuxmeuwuvkwfem96llyxf8duyshm56t8k8",
|
||||
)
|
||||
require.NotContains(t, output, "STATIC_LOOP_IN INIT_HTLC")
|
||||
}
|
||||
|
||||
func captureStdout(t *testing.T, fn func()) string {
|
||||
t.Helper()
|
||||
|
||||
originalStdout := os.Stdout
|
||||
reader, writer, err := os.Pipe()
|
||||
require.NoError(t, err)
|
||||
os.Stdout = writer
|
||||
|
||||
fn()
|
||||
|
||||
require.NoError(t, writer.Close())
|
||||
os.Stdout = originalStdout
|
||||
|
||||
var buf bytes.Buffer
|
||||
_, err = io.Copy(&buf, reader)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, reader.Close())
|
||||
|
||||
return strings.TrimSpace(buf.String())
|
||||
}
|
||||
|
|
@ -15,9 +15,10 @@ import (
|
|||
|
||||
var listSwapsCommand = &cli.Command{
|
||||
Name: "listswaps",
|
||||
Usage: "list all swaps in the local database",
|
||||
Description: "Allows the user to get a list of all swaps that are " +
|
||||
"currently stored in the database",
|
||||
Usage: "list traditional Loop In and Loop Out swaps",
|
||||
Description: "Lists traditional Loop In and Loop Out swaps that are " +
|
||||
"currently stored in the database. Static address loop-ins are not " +
|
||||
"included; use `loop static listswaps` to view them.",
|
||||
Action: listSwaps,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
|
|
@ -128,10 +129,12 @@ func listSwaps(ctx context.Context, cmd *cli.Command) error {
|
|||
|
||||
var swapInfoCommand = &cli.Command{
|
||||
Name: "swapinfo",
|
||||
Usage: "show the status of a swap",
|
||||
Usage: "show the status of a traditional swap",
|
||||
ArgsUsage: "id",
|
||||
Description: "Allows the user to get the status of a single swap " +
|
||||
"currently stored in the database",
|
||||
Description: "Shows the status of a traditional Loop In or Loop Out " +
|
||||
"swap currently stored in the database. Static address loop-ins " +
|
||||
"must be viewed with `loop static listswaps`; there is no generic " +
|
||||
"per-swap static lookup command.",
|
||||
Flags: []cli.Flag{
|
||||
&cli.Uint64Flag{
|
||||
Name: "id",
|
||||
|
|
|
|||
267
cmd/loop/testdata/sessions/basic-swaps/04_loop-monitor-static-loop-in.json
vendored
Normal file
267
cmd/loop/testdata/sessions/basic-swaps/04_loop-monitor-static-loop-in.json
vendored
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
{
|
||||
"metadata": {
|
||||
"args": [
|
||||
"loop",
|
||||
"monitor",
|
||||
"--network",
|
||||
"regtest"
|
||||
],
|
||||
"env": {},
|
||||
"version": "0.33.3-beta commit= commit_hash=",
|
||||
"run_error": "recv: rpc error: code = Canceled desc = context canceled",
|
||||
"duration": 25407490958,
|
||||
"clock_start_unix": 1784150305
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"time_ms": 13,
|
||||
"kind": "grpc",
|
||||
"data": {
|
||||
"method": "/looprpc.SwapClient/Monitor",
|
||||
"event": "send",
|
||||
"message_type": "looprpc.MonitorRequest",
|
||||
"payload": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"time_ms": 13,
|
||||
"kind": "stdout",
|
||||
"data": {
|
||||
"lines": [
|
||||
"Note: offchain cost may report as 0 after loopd restart during swap\n"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"time_ms": 16479,
|
||||
"kind": "grpc",
|
||||
"data": {
|
||||
"method": "/looprpc.SwapClient/Monitor",
|
||||
"event": "recv",
|
||||
"message_type": "looprpc.SwapStatus",
|
||||
"payload": {
|
||||
"amt": "500000",
|
||||
"id": "bec3777d45337f652d96136e5714ed90a5d4f7cda87b5842110ecc38c9a65826",
|
||||
"id_bytes": "vsN3fUUzf2UtlhNuVxTtkKXU982oe1hCEQ7MOMmmWCY=",
|
||||
"type": "STATIC_LOOP_IN",
|
||||
"state": "INITIATED",
|
||||
"static_loop_in_state": "INIT_HTLC",
|
||||
"failure_reason": "FAILURE_REASON_NONE",
|
||||
"initiation_time": "1784150320647961000",
|
||||
"last_update_time": "1784150321494895000",
|
||||
"htlc_address": "bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv",
|
||||
"htlc_address_p2wsh": "bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv",
|
||||
"htlc_address_p2tr": "",
|
||||
"cost_server": "0",
|
||||
"cost_onchain": "0",
|
||||
"cost_offchain": "0",
|
||||
"last_hop": "",
|
||||
"outgoing_chan_set": [],
|
||||
"label": "",
|
||||
"asset_info": null
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"time_ms": 16479,
|
||||
"kind": "stdout",
|
||||
"data": {
|
||||
"lines": [
|
||||
"2026-07-15T18:18:41-03:00 STATIC_LOOP_IN INIT_HTLC 0.00500000 BTC - P2WSH: bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv\n"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"time_ms": 16482,
|
||||
"kind": "grpc",
|
||||
"data": {
|
||||
"method": "/looprpc.SwapClient/Monitor",
|
||||
"event": "recv",
|
||||
"message_type": "looprpc.SwapStatus",
|
||||
"payload": {
|
||||
"amt": "500000",
|
||||
"id": "bec3777d45337f652d96136e5714ed90a5d4f7cda87b5842110ecc38c9a65826",
|
||||
"id_bytes": "vsN3fUUzf2UtlhNuVxTtkKXU982oe1hCEQ7MOMmmWCY=",
|
||||
"type": "STATIC_LOOP_IN",
|
||||
"state": "INITIATED",
|
||||
"static_loop_in_state": "SIGN_HTLC_TX",
|
||||
"failure_reason": "FAILURE_REASON_NONE",
|
||||
"initiation_time": "1784150320647961000",
|
||||
"last_update_time": "1784150321499639000",
|
||||
"htlc_address": "bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv",
|
||||
"htlc_address_p2wsh": "bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv",
|
||||
"htlc_address_p2tr": "",
|
||||
"cost_server": "0",
|
||||
"cost_onchain": "0",
|
||||
"cost_offchain": "0",
|
||||
"last_hop": "",
|
||||
"outgoing_chan_set": [],
|
||||
"label": "",
|
||||
"asset_info": null
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"time_ms": 16482,
|
||||
"kind": "stdout",
|
||||
"data": {
|
||||
"lines": [
|
||||
"2026-07-15T18:18:41-03:00 STATIC_LOOP_IN SIGN_HTLC_TX 0.00500000 BTC - P2WSH: bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv\n"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"time_ms": 16530,
|
||||
"kind": "grpc",
|
||||
"data": {
|
||||
"method": "/looprpc.SwapClient/Monitor",
|
||||
"event": "recv",
|
||||
"message_type": "looprpc.SwapStatus",
|
||||
"payload": {
|
||||
"amt": "500000",
|
||||
"id": "bec3777d45337f652d96136e5714ed90a5d4f7cda87b5842110ecc38c9a65826",
|
||||
"id_bytes": "vsN3fUUzf2UtlhNuVxTtkKXU982oe1hCEQ7MOMmmWCY=",
|
||||
"type": "STATIC_LOOP_IN",
|
||||
"state": "INITIATED",
|
||||
"static_loop_in_state": "MONITOR_INVOICE_HTLC_TX",
|
||||
"failure_reason": "FAILURE_REASON_NONE",
|
||||
"initiation_time": "1784150320647961000",
|
||||
"last_update_time": "1784150321548450000",
|
||||
"htlc_address": "bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv",
|
||||
"htlc_address_p2wsh": "bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv",
|
||||
"htlc_address_p2tr": "",
|
||||
"cost_server": "0",
|
||||
"cost_onchain": "0",
|
||||
"cost_offchain": "0",
|
||||
"last_hop": "",
|
||||
"outgoing_chan_set": [],
|
||||
"label": "",
|
||||
"asset_info": null
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"time_ms": 16530,
|
||||
"kind": "stdout",
|
||||
"data": {
|
||||
"lines": [
|
||||
"2026-07-15T18:18:41-03:00 STATIC_LOOP_IN MONITOR_INVOICE_HTLC_TX 0.00500000 BTC - P2WSH: bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv\n"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"time_ms": 17027,
|
||||
"kind": "grpc",
|
||||
"data": {
|
||||
"method": "/looprpc.SwapClient/Monitor",
|
||||
"event": "recv",
|
||||
"message_type": "looprpc.SwapStatus",
|
||||
"payload": {
|
||||
"amt": "500000",
|
||||
"id": "bec3777d45337f652d96136e5714ed90a5d4f7cda87b5842110ecc38c9a65826",
|
||||
"id_bytes": "vsN3fUUzf2UtlhNuVxTtkKXU982oe1hCEQ7MOMmmWCY=",
|
||||
"type": "STATIC_LOOP_IN",
|
||||
"state": "INITIATED",
|
||||
"static_loop_in_state": "PAYMENT_RECEIVED",
|
||||
"failure_reason": "FAILURE_REASON_NONE",
|
||||
"initiation_time": "1784150320647961000",
|
||||
"last_update_time": "1784150322044521000",
|
||||
"htlc_address": "bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv",
|
||||
"htlc_address_p2wsh": "bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv",
|
||||
"htlc_address_p2tr": "",
|
||||
"cost_server": "1828",
|
||||
"cost_onchain": "0",
|
||||
"cost_offchain": "0",
|
||||
"last_hop": "",
|
||||
"outgoing_chan_set": [],
|
||||
"label": "",
|
||||
"asset_info": null
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"time_ms": 17027,
|
||||
"kind": "stdout",
|
||||
"data": {
|
||||
"lines": [
|
||||
"2026-07-15T18:18:42-03:00 STATIC_LOOP_IN PAYMENT_RECEIVED 0.00500000 BTC - P2WSH: bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv (cost: server 1828, onchain 0, offchain 0)\n"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"time_ms": 17036,
|
||||
"kind": "grpc",
|
||||
"data": {
|
||||
"method": "/looprpc.SwapClient/Monitor",
|
||||
"event": "recv",
|
||||
"message_type": "looprpc.SwapStatus",
|
||||
"payload": {
|
||||
"amt": "500000",
|
||||
"id": "bec3777d45337f652d96136e5714ed90a5d4f7cda87b5842110ecc38c9a65826",
|
||||
"id_bytes": "vsN3fUUzf2UtlhNuVxTtkKXU982oe1hCEQ7MOMmmWCY=",
|
||||
"type": "STATIC_LOOP_IN",
|
||||
"state": "INITIATED",
|
||||
"static_loop_in_state": "SUCCEEDED",
|
||||
"failure_reason": "FAILURE_REASON_NONE",
|
||||
"initiation_time": "1784150320647961000",
|
||||
"last_update_time": "1784150322052572000",
|
||||
"htlc_address": "bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv",
|
||||
"htlc_address_p2wsh": "bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv",
|
||||
"htlc_address_p2tr": "",
|
||||
"cost_server": "1828",
|
||||
"cost_onchain": "0",
|
||||
"cost_offchain": "0",
|
||||
"last_hop": "",
|
||||
"outgoing_chan_set": [],
|
||||
"label": "",
|
||||
"asset_info": null
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"time_ms": 17036,
|
||||
"kind": "stdout",
|
||||
"data": {
|
||||
"lines": [
|
||||
"2026-07-15T18:18:42-03:00 STATIC_LOOP_IN SUCCEEDED 0.00500000 BTC - P2WSH: bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv (cost: server 1828, onchain 0, offchain 0)\n"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"time_ms": 25406,
|
||||
"kind": "signal",
|
||||
"data": {
|
||||
"signal": "interrupt"
|
||||
}
|
||||
},
|
||||
{
|
||||
"time_ms": 25407,
|
||||
"kind": "grpc",
|
||||
"data": {
|
||||
"method": "/looprpc.SwapClient/Monitor",
|
||||
"event": "error",
|
||||
"error": "rpc error: code = Canceled desc = context canceled",
|
||||
"status": {
|
||||
"code": 1,
|
||||
"message": "context canceled"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"time_ms": 25407,
|
||||
"kind": "stderr",
|
||||
"data": {
|
||||
"lines": [
|
||||
"[loop] recv: rpc error: code = Canceled desc = context canceled\n"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"time_ms": 25407,
|
||||
"kind": "exit",
|
||||
"data": {
|
||||
"run_error": "recv: rpc error: code = Canceled desc = context canceled"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -203,7 +203,7 @@ fetches a new L402 authentication token from the server
|
|||
\fB--help, -h\fP: show help
|
||||
|
||||
.SH listswaps
|
||||
list all swaps in the local database
|
||||
list traditional Loop In and Loop Out swaps
|
||||
|
||||
.PP
|
||||
\fB--channel\fP="": the comma-separated list of short channel IDs of the channels to loop out
|
||||
|
|
@ -233,7 +233,7 @@ list all swaps in the local database
|
|||
\fB--start_time_ns\fP="": Unix timestamp in nanoseconds to select swaps initiated after this time (default: 0)
|
||||
|
||||
.SH swapinfo
|
||||
show the status of a swap
|
||||
show the status of a traditional swap
|
||||
|
||||
.PP
|
||||
\fB--help, -h\fP: show help
|
||||
|
|
|
|||
|
|
@ -235,9 +235,9 @@ The following flags are supported:
|
|||
|
||||
### `listswaps` command
|
||||
|
||||
list all swaps in the local database.
|
||||
list traditional Loop In and Loop Out swaps.
|
||||
|
||||
Allows the user to get a list of all swaps that are currently stored in the database.
|
||||
Lists traditional Loop In and Loop Out swaps that are currently stored in the database. Static address loop-ins are not included; use `loop static listswaps` to view them.
|
||||
|
||||
Usage:
|
||||
|
||||
|
|
@ -261,9 +261,9 @@ The following flags are supported:
|
|||
|
||||
### `swapinfo` command
|
||||
|
||||
show the status of a swap.
|
||||
show the status of a traditional swap.
|
||||
|
||||
Allows the user to get the status of a single swap currently stored in the database.
|
||||
Shows the status of a traditional Loop In or Loop Out swap currently stored in the database. Static address loop-ins must be viewed with `loop static listswaps`; there is no generic per-swap static lookup command.
|
||||
|
||||
Usage:
|
||||
|
||||
|
|
|
|||
14
interface.go
14
interface.go
|
|
@ -4,6 +4,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/lightninglabs/loop/fsm"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/swap"
|
||||
"github.com/lightninglabs/taproot-assets/rfqmath"
|
||||
|
|
@ -476,8 +477,12 @@ type SwapInfoKit struct {
|
|||
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 {
|
||||
// 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.SwapContract
|
||||
|
|
@ -488,9 +493,14 @@ type SwapInfo struct {
|
|||
// SwapHash stores the swap preimage 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
|
||||
|
||||
// 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)
|
||||
// swap htlc. This is used for both loop-in and loop-out.
|
||||
HtlcAddressP2WSH btcutil.Address
|
||||
|
|
|
|||
|
|
@ -400,9 +400,16 @@ func rpcToFee(req *clientrpc.LiquidityParameters) (FeeLimit, error) {
|
|||
|
||||
// rpcToRule switches on rpc rule type to convert to our rule interface.
|
||||
func rpcToRule(rule *clientrpc.LiquidityRule) (*SwapRule, error) {
|
||||
swapType := swap.TypeOut
|
||||
if rule.SwapType == clientrpc.SwapType_LOOP_IN {
|
||||
var swapType swap.Type
|
||||
switch rule.SwapType {
|
||||
case clientrpc.SwapType_LOOP_OUT:
|
||||
swapType = swap.TypeOut
|
||||
|
||||
case clientrpc.SwapType_LOOP_IN:
|
||||
swapType = swap.TypeIn
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown swap type: %v", rule.SwapType)
|
||||
}
|
||||
|
||||
switch rule.Type {
|
||||
|
|
|
|||
|
|
@ -3,9 +3,62 @@ package liquidity
|
|||
import (
|
||||
"testing"
|
||||
|
||||
clientrpc "github.com/lightninglabs/loop/looprpc"
|
||||
"github.com/lightninglabs/loop/swap"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestRPCToRuleSwapType verifies RPC swap type conversion.
|
||||
func TestRPCToRuleSwapType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
swapType clientrpc.SwapType
|
||||
wantType swap.Type
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "loop out",
|
||||
swapType: clientrpc.SwapType_LOOP_OUT,
|
||||
wantType: swap.TypeOut,
|
||||
},
|
||||
{
|
||||
name: "loop in",
|
||||
swapType: clientrpc.SwapType_LOOP_IN,
|
||||
wantType: swap.TypeIn,
|
||||
},
|
||||
{
|
||||
name: "static loop in rejected",
|
||||
swapType: clientrpc.SwapType_STATIC_LOOP_IN,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "unknown swap type rejected",
|
||||
swapType: clientrpc.SwapType(99),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range tests {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
rpcRule := &clientrpc.LiquidityRule{
|
||||
Type: clientrpc.LiquidityRuleType_THRESHOLD,
|
||||
IncomingThreshold: 10,
|
||||
OutgoingThreshold: 20,
|
||||
SwapType: testCase.swapType,
|
||||
}
|
||||
|
||||
got, err := rpcToRule(rpcRule)
|
||||
if testCase.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, testCase.wantType, got.Type)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidateRestrictions tests validating client restrictions against a set
|
||||
// of server restrictions.
|
||||
func TestValidateRestrictions(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -110,6 +110,10 @@ type Daemon struct {
|
|||
macaroonService *lndclient.MacaroonService
|
||||
}
|
||||
|
||||
// staticLoopInStatusChanBuffer keeps the shared swap-status fanout from
|
||||
// stalling static loop-in FSM progress during transient subscriber gaps.
|
||||
const staticLoopInStatusChanBuffer = 20
|
||||
|
||||
// New creates a new instance of the loop client daemon.
|
||||
func New(config *Config, lisCfg *ListenerCfg) *Daemon {
|
||||
return &Daemon{
|
||||
|
|
@ -681,6 +685,7 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
|
|||
LightningClient: d.lnd.Client,
|
||||
}
|
||||
openChannelManager = openchannel.NewManager(openChannelCfg)
|
||||
statusChan := make(chan loop.SwapInfo, staticLoopInStatusChanBuffer)
|
||||
|
||||
// Run the deposit swap hash migration.
|
||||
err = loopin.MigrateDepositSwapHash(
|
||||
|
|
@ -701,6 +706,11 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
|
|||
|
||||
return err
|
||||
}
|
||||
statusUpdater := &staticLoopInStatusUpdater{
|
||||
statusChan: statusChan,
|
||||
mainCtx: d.mainCtx,
|
||||
chainParams: d.lnd.ChainParams,
|
||||
}
|
||||
|
||||
staticLoopInManager, err = loopin.NewManager(&loopin.Config{
|
||||
Server: staticAddressClient,
|
||||
|
|
@ -718,6 +728,7 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
|
|||
ChainParams: d.lnd.ChainParams,
|
||||
Signer: d.lnd.Signer,
|
||||
ValidateLoopInContract: loop.ValidateLoopInContract,
|
||||
SendUpdate: statusUpdater.sendUpdate,
|
||||
MaxStaticAddrHtlcFeePercentage: d.cfg.MaxStaticAddrHtlcFeePercentage,
|
||||
MaxStaticAddrHtlcBackupFeePercentage: d.cfg.MaxStaticAddrHtlcBackupFeePercentage,
|
||||
}, blockHeight)
|
||||
|
|
@ -783,7 +794,7 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
|
|||
lnd: &d.lnd.LndServices,
|
||||
swaps: make(map[lntypes.Hash]loop.SwapInfo),
|
||||
subscribers: make(map[int]chan<- any),
|
||||
statusChan: make(chan loop.SwapInfo),
|
||||
statusChan: statusChan,
|
||||
mainCtx: d.mainCtx,
|
||||
reservationManager: reservationManager,
|
||||
instantOutManager: instantOutManager,
|
||||
|
|
|
|||
47
loopd/static_loopin_status_updater.go
Normal file
47
loopd/static_loopin_status_updater.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package loopd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/lightninglabs/loop"
|
||||
"github.com/lightninglabs/loop/staticaddr/loopin"
|
||||
)
|
||||
|
||||
// staticLoopInStatusUpdater publishes static-address loop-in status updates to
|
||||
// the client-facing swap stream.
|
||||
type staticLoopInStatusUpdater struct {
|
||||
// statusChan sends updates to the client-facing swap stream.
|
||||
statusChan chan<- loop.SwapInfo
|
||||
|
||||
// mainCtx is canceled when loopd is shutting down.
|
||||
mainCtx context.Context
|
||||
|
||||
// chainParams are used to reconstruct the static loop-in HTLC address.
|
||||
chainParams *chaincfg.Params
|
||||
}
|
||||
|
||||
// sendUpdate converts the persisted static-address loop-in into swap info and
|
||||
// forwards it to the status stream unless either context is canceled.
|
||||
func (u *staticLoopInStatusUpdater) sendUpdate(ctx context.Context,
|
||||
swp *loopin.StaticAddressLoopIn) error {
|
||||
|
||||
info, err := staticAddressLoopInSwapInfoWithChainParams(
|
||||
swp, u.chainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to notify static loop-in update: %w", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case u.statusChan <- *info:
|
||||
return nil
|
||||
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
|
||||
case <-u.mainCtx.Done():
|
||||
return u.mainCtx.Err()
|
||||
}
|
||||
}
|
||||
|
|
@ -411,6 +411,8 @@ func (s *swapClientServer) marshallSwap(ctx context.Context,
|
|||
}
|
||||
|
||||
var swapType looprpc.SwapType
|
||||
staticLoopInState := looprpc.
|
||||
StaticAddressLoopInSwapState_UNKNOWN_STATIC_ADDRESS_SWAP_STATE
|
||||
var (
|
||||
htlcAddress string
|
||||
htlcAddressP2TR string
|
||||
|
|
@ -437,6 +439,27 @@ func (s *swapClientServer) marshallSwap(ctx context.Context,
|
|||
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:
|
||||
swapType = looprpc.SwapType_LOOP_OUT
|
||||
if loopSwap.HtlcAddressP2WSH != nil {
|
||||
|
|
@ -478,7 +501,7 @@ func (s *swapClientServer) marshallSwap(ctx context.Context,
|
|||
return nil, errors.New("unknown swap type")
|
||||
}
|
||||
|
||||
return &looprpc.SwapStatus{
|
||||
rpcSwap := &looprpc.SwapStatus{
|
||||
Amt: int64(loopSwap.AmountRequested),
|
||||
Id: loopSwap.SwapHash.String(),
|
||||
IdBytes: loopSwap.SwapHash[:],
|
||||
|
|
@ -497,7 +520,15 @@ func (s *swapClientServer) marshallSwap(ctx context.Context,
|
|||
LastHop: lastHop,
|
||||
OutgoingChanSet: outGoingChanSet,
|
||||
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.
|
||||
|
|
@ -518,27 +549,28 @@ func (s *swapClientServer) Monitor(in *looprpc.MonitorRequest,
|
|||
// Start a notification queue for this subscriber.
|
||||
queue := queue.NewConcurrentQueue(20)
|
||||
queue.Start()
|
||||
ctx := server.Context()
|
||||
|
||||
// Add this subscriber to the global subscriber list. Also create a
|
||||
// snapshot of all pending and completed swaps within the lock, to
|
||||
// prevent subscribers from receiving duplicate updates.
|
||||
s.swapsLock.Lock()
|
||||
|
||||
id := s.nextSubscriberID
|
||||
s.nextSubscriberID++
|
||||
s.subscribers[id] = queue.ChanIn()
|
||||
|
||||
var pendingSwaps, completedSwaps []loop.SwapInfo
|
||||
for _, swap := range s.swaps {
|
||||
if swap.State.Type() == loopdb.StateTypePending {
|
||||
pendingSwaps = append(pendingSwaps, swap)
|
||||
} else {
|
||||
completedSwaps = append(completedSwaps, swap)
|
||||
}
|
||||
}
|
||||
|
||||
pendingSwaps, completedSwaps := s.monitorCachedSwaps()
|
||||
s.swapsLock.Unlock()
|
||||
|
||||
err := s.appendStaticAddressLoopInMonitorSnapshot(
|
||||
ctx, &pendingSwaps, &completedSwaps,
|
||||
)
|
||||
if err != nil {
|
||||
s.swapsLock.Lock()
|
||||
delete(s.subscribers, id)
|
||||
s.swapsLock.Unlock()
|
||||
queue.Stop()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
s.swapsLock.Lock()
|
||||
delete(s.subscribers, id)
|
||||
|
|
@ -568,6 +600,13 @@ func (s *swapClientServer) Monitor(in *looprpc.MonitorRequest,
|
|||
)
|
||||
})
|
||||
|
||||
// Static-address loop-in updates can arrive from both the initial snapshot
|
||||
// and the live queue. Build a high-water mark from the snapshot so we can
|
||||
// suppress stale duplicate snapshot items without dropping newer live ones.
|
||||
staticSnapshotHighWater := staticAddressLoopInMonitorHighWater(
|
||||
filteredSwaps,
|
||||
)
|
||||
|
||||
// Return swaps to caller.
|
||||
for _, swap := range filteredSwaps {
|
||||
if err := send(swap); err != nil {
|
||||
|
|
@ -585,6 +624,13 @@ func (s *swapClientServer) Monitor(in *looprpc.MonitorRequest,
|
|||
}
|
||||
|
||||
swap := queueItem.(loop.SwapInfo)
|
||||
if isInitialStaticAddressLoopInStale(
|
||||
staticSnapshotHighWater, swap,
|
||||
) {
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err := send(swap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -600,6 +646,102 @@ func (s *swapClientServer) Monitor(in *looprpc.MonitorRequest,
|
|||
}
|
||||
}
|
||||
|
||||
// staticAddressLoopInMonitorHighWater records the latest snapshot item for each
|
||||
// static-address loop-in swap hash.
|
||||
func staticAddressLoopInMonitorHighWater(
|
||||
swaps []loop.SwapInfo) map[lntypes.Hash]staticAddressLoopInHighWater {
|
||||
|
||||
highWater := make(map[lntypes.Hash]staticAddressLoopInHighWater)
|
||||
for _, swp := range swaps {
|
||||
if swp.SwapType != swap.TypeStaticAddressLoopIn {
|
||||
continue
|
||||
}
|
||||
|
||||
current, ok := highWater[swp.SwapHash]
|
||||
if !ok || swp.LastUpdate.After(current.lastUpdate) {
|
||||
highWater[swp.SwapHash] = staticAddressLoopInHighWater{
|
||||
lastUpdate: swp.LastUpdate,
|
||||
staticState: swp.StaticAddressLoopInState,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return highWater
|
||||
}
|
||||
|
||||
// staticAddressLoopInHighWater stores the most recent snapshot timestamp and
|
||||
// state for one static-address loop-in swap.
|
||||
type staticAddressLoopInHighWater struct {
|
||||
lastUpdate time.Time
|
||||
staticState fsm.StateType
|
||||
}
|
||||
|
||||
// isInitialStaticAddressLoopInStale reports whether a live static-address
|
||||
// loop-in update is older than the snapshot copy already sent, or equal to it
|
||||
// with the same static FSM state.
|
||||
func isInitialStaticAddressLoopInStale(
|
||||
highWater map[lntypes.Hash]staticAddressLoopInHighWater,
|
||||
swp loop.SwapInfo) bool {
|
||||
|
||||
if swp.SwapType != swap.TypeStaticAddressLoopIn {
|
||||
return false
|
||||
}
|
||||
current, ok := highWater[swp.SwapHash]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if swp.LastUpdate.Before(current.lastUpdate) {
|
||||
return true
|
||||
}
|
||||
if swp.LastUpdate.After(current.lastUpdate) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Equal timestamps can race the initial DB snapshot, so match state too.
|
||||
return swp.StaticAddressLoopInState == current.staticState
|
||||
}
|
||||
|
||||
// monitorCachedSwaps returns the current in-memory swaps split into pending and
|
||||
// completed slices for monitor snapshot construction.
|
||||
func (s *swapClientServer) monitorCachedSwaps() ([]loop.SwapInfo,
|
||||
[]loop.SwapInfo) {
|
||||
|
||||
var pendingSwaps, completedSwaps []loop.SwapInfo
|
||||
for _, swap := range s.swaps {
|
||||
if swap.State.Type() == loopdb.StateTypePending {
|
||||
pendingSwaps = append(pendingSwaps, swap)
|
||||
} else {
|
||||
completedSwaps = append(completedSwaps, swap)
|
||||
}
|
||||
}
|
||||
|
||||
return pendingSwaps, completedSwaps
|
||||
}
|
||||
|
||||
// appendStaticAddressLoopInMonitorSnapshot appends the current static-address
|
||||
// loop-in swaps to the monitor snapshot.
|
||||
func (s *swapClientServer) appendStaticAddressLoopInMonitorSnapshot(
|
||||
ctx context.Context, pendingSwaps, completedSwaps *[]loop.SwapInfo) error {
|
||||
|
||||
staticSwaps, err := s.staticAddressLoopInSwapInfos(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, swap := range staticSwaps {
|
||||
if slices.Contains(
|
||||
loopin.FinalStates, swap.StaticAddressLoopInState,
|
||||
) {
|
||||
|
||||
*completedSwaps = append(*completedSwaps, *swap)
|
||||
} else {
|
||||
*pendingSwaps = append(*pendingSwaps, *swap)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListSwaps returns a list of all currently known swaps and their current
|
||||
// status.
|
||||
func (s *swapClientServer) ListSwaps(ctx context.Context,
|
||||
|
|
@ -754,10 +896,13 @@ func (s *swapClientServer) SwapInfo(ctx context.Context,
|
|||
|
||||
// Just return the server's in-memory cache here too as we also want to
|
||||
// return temporary failures to the client.
|
||||
s.swapsLock.Lock()
|
||||
swp, ok := s.swaps[swapHash]
|
||||
s.swapsLock.Unlock()
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("swap with hash %s not found", req.Id)
|
||||
}
|
||||
|
||||
return s.marshallSwap(ctx, &swp)
|
||||
}
|
||||
|
||||
|
|
@ -2089,6 +2234,8 @@ func (s *swapClientServer) ListStaticAddressSwaps(ctx context.Context,
|
|||
}, nil
|
||||
}
|
||||
|
||||
// staticAddressLoopInTimestamp converts a non-zero timestamp to Unix nano
|
||||
// form and preserves zero timestamps as zero.
|
||||
func staticAddressLoopInTimestamp(t time.Time) int64 {
|
||||
if t.IsZero() {
|
||||
return 0
|
||||
|
|
@ -2114,6 +2261,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
|
||||
// information. Amongst deposits and withdrawals and their total values, it also
|
||||
// includes a list of detailed deposit information filtered by their state.
|
||||
|
|
@ -2420,6 +2702,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(
|
||||
state fsm.StateType) looprpc.StaticAddressLoopInSwapState {
|
||||
|
||||
|
|
@ -2572,7 +2856,12 @@ func (s *swapClientServer) processStatusUpdates(mainCtx context.Context) {
|
|||
// subscribers about the changes.
|
||||
case swp := <-s.statusChan:
|
||||
s.swapsLock.Lock()
|
||||
s.swaps[swp.SwapHash] = swp
|
||||
// Static loop-ins are broadcast to monitor subscribers, but they
|
||||
// stay out of the legacy swap cache so ListSwaps and SwapInfo remain
|
||||
// traditional-swap views.
|
||||
if swp.SwapType != swap.TypeStaticAddressLoopIn {
|
||||
s.swaps[swp.SwapHash] = swp
|
||||
}
|
||||
|
||||
for _, subscriber := range s.subscribers {
|
||||
select {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
|
|
@ -25,6 +26,7 @@ import (
|
|||
"github.com/lightninglabs/loop/staticaddr/script"
|
||||
"github.com/lightninglabs/loop/swap"
|
||||
mock_lnd "github.com/lightninglabs/loop/test"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
|
|
@ -32,6 +34,7 @@ import (
|
|||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
|
|
@ -501,10 +504,554 @@ 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",
|
||||
)
|
||||
}
|
||||
|
||||
// TestMonitorSnapshotIncludesStaticAddressLoopIns protects the monitor snapshot
|
||||
// invariant that pending static loop-ins are included alongside cached generic
|
||||
// swaps with their static state and swap-specific HTLC address.
|
||||
func TestMonitorSnapshotIncludesStaticAddressLoopIns(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
server, staticLoopIn := newGenericStaticLoopInServer(t)
|
||||
|
||||
pendingSwaps, completedSwaps := server.monitorCachedSwaps()
|
||||
err := server.appendStaticAddressLoopInMonitorSnapshot(
|
||||
ctx, &pendingSwaps, &completedSwaps,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, completedSwaps)
|
||||
require.Len(t, pendingSwaps, 1)
|
||||
require.Equal(t, staticLoopIn.SwapHash, pendingSwaps[0].SwapHash)
|
||||
require.Equal(t, swap.TypeStaticAddressLoopIn, pendingSwaps[0].SwapType)
|
||||
require.Equal(
|
||||
t, staticLoopIn.GetState(),
|
||||
pendingSwaps[0].StaticAddressLoopInState,
|
||||
)
|
||||
assertStaticLoopInUsesSwapHtlcAddress(t, staticLoopIn, pendingSwaps[0])
|
||||
}
|
||||
|
||||
// TestMonitorSnapshotIncludesFinalStaticAddressLoopIns protects the monitor
|
||||
// snapshot invariant that exact final static loop-in states are completed swaps.
|
||||
func TestMonitorSnapshotIncludesFinalStaticAddressLoopIns(t *testing.T) {
|
||||
server, staticLoopIn := newGenericStaticLoopInServer(t)
|
||||
staticLoopIn.SetState(loopin.Succeeded)
|
||||
|
||||
pendingSwaps, completedSwaps := server.monitorCachedSwaps()
|
||||
err := server.appendStaticAddressLoopInMonitorSnapshot(
|
||||
t.Context(), &pendingSwaps, &completedSwaps,
|
||||
)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, pendingSwaps)
|
||||
require.Len(t, completedSwaps, 1)
|
||||
}
|
||||
|
||||
// TestStaticLoopInStatusUpdaterUsesSwapHtlcAddress protects the live-update
|
||||
// invariant that static loop-in status events derive the HTLC address from the
|
||||
// swap, not from reusable static address parameters.
|
||||
func TestStaticLoopInStatusUpdaterUsesSwapHtlcAddress(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
_, staticLoopIn := newGenericStaticLoopInServer(t)
|
||||
staticLoopIn.AddressParams = nil
|
||||
statusChan := make(chan loop.SwapInfo, 1)
|
||||
updater := &staticLoopInStatusUpdater{
|
||||
statusChan: statusChan,
|
||||
mainCtx: ctx,
|
||||
chainParams: &chaincfg.TestNet3Params,
|
||||
}
|
||||
|
||||
err := updater.sendUpdate(ctx, staticLoopIn)
|
||||
require.NoError(t, err)
|
||||
swapInfo := <-statusChan
|
||||
assertStaticLoopInUsesSwapHtlcAddress(t, staticLoopIn, swapInfo)
|
||||
}
|
||||
|
||||
// TestMonitorSuppressesStaticAddressLoopInSnapshotLiveDuplicate protects the
|
||||
// monitor race invariant that live static loop-in updates arriving during the
|
||||
// initial snapshot are deduplicated without dropping newer progress.
|
||||
func TestMonitorSuppressesStaticAddressLoopInSnapshotLiveDuplicate(t *testing.T) {
|
||||
logger := btclog.NewSLogger(
|
||||
btclog.NewDefaultHandler(os.Stdout),
|
||||
)
|
||||
setLogger(logger.SubSystem(Subsystem))
|
||||
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
mainCtx, mainCancel := context.WithCancel(t.Context())
|
||||
defer mainCancel()
|
||||
server, staticLoopIn, store := newGenericStaticLoopInServerWithStore(t)
|
||||
server.statusChan = make(chan loop.SwapInfo)
|
||||
server.subscribers = make(map[int]chan<- any)
|
||||
server.mainCtx = mainCtx
|
||||
|
||||
snapshotStarted := make(chan struct{}, 1)
|
||||
releaseSnapshot := make(chan struct{})
|
||||
store.beforeGet = func() {
|
||||
select {
|
||||
case snapshotStarted <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
store.waitGet = releaseSnapshot
|
||||
|
||||
go server.processStatusUpdates(mainCtx)
|
||||
|
||||
monitorServer := &testMonitorServer{
|
||||
ctx: ctx,
|
||||
sent: make(chan *looprpc.SwapStatus, 3),
|
||||
}
|
||||
errChan := make(chan error, 1)
|
||||
go func() {
|
||||
errChan <- server.Monitor(&looprpc.MonitorRequest{}, monitorServer)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-snapshotStarted:
|
||||
case <-ctx.Done():
|
||||
t.Fatal(ctx.Err())
|
||||
}
|
||||
|
||||
staticUpdate, err := server.staticAddressLoopInSwapInfo(ctx, staticLoopIn)
|
||||
require.NoError(t, err)
|
||||
staleUpdate := *staticUpdate
|
||||
staleUpdate.State = loopdb.StateInitiated
|
||||
staleUpdate.LastUpdate = staticUpdate.LastUpdate.Add(-time.Second)
|
||||
server.statusChan <- staleUpdate
|
||||
|
||||
server.statusChan <- *staticUpdate
|
||||
close(releaseSnapshot)
|
||||
|
||||
first := receiveMonitorUpdate(t, ctx, monitorServer.sent)
|
||||
require.Equal(t, staticLoopIn.SwapHash[:], first.IdBytes)
|
||||
require.Equal(t, looprpc.SwapType_STATIC_LOOP_IN, first.Type)
|
||||
require.Equal(
|
||||
t, looprpc.StaticAddressLoopInSwapState_PAYMENT_RECEIVED,
|
||||
first.GetStaticLoopInState(),
|
||||
)
|
||||
|
||||
nextUpdate := *staticUpdate
|
||||
nextUpdate.State = loopdb.StateSuccess
|
||||
nextUpdate.StaticAddressLoopInState = loopin.Succeeded
|
||||
nextUpdate.LastUpdate = staticUpdate.LastUpdate.Add(time.Second)
|
||||
server.statusChan <- nextUpdate
|
||||
|
||||
second := receiveMonitorUpdate(t, ctx, monitorServer.sent)
|
||||
require.Equal(t, staticLoopIn.SwapHash[:], second.IdBytes)
|
||||
require.Equal(t, looprpc.SwapType_STATIC_LOOP_IN, second.Type)
|
||||
require.Equal(
|
||||
t, looprpc.StaticAddressLoopInSwapState_SUCCEEDED,
|
||||
second.GetStaticLoopInState(),
|
||||
)
|
||||
|
||||
cancel()
|
||||
require.NoError(t, <-errChan)
|
||||
}
|
||||
|
||||
// TestStaticAddressLoopInHighWaterSuppressesExactDuplicate protects the
|
||||
// high-water dedup invariant that an initial live update identical to the
|
||||
// snapshot is treated as stale.
|
||||
func TestStaticAddressLoopInHighWaterSuppressesExactDuplicate(t *testing.T) {
|
||||
swapHash := lntypes.Hash{1, 2, 3}
|
||||
lastUpdate := time.Unix(100, 0).UTC()
|
||||
snapshot := loop.SwapInfo{
|
||||
SwapHash: swapHash,
|
||||
SwapType: swap.TypeStaticAddressLoopIn,
|
||||
LastUpdate: lastUpdate,
|
||||
StaticAddressLoopInState: loopin.PaymentReceived,
|
||||
}
|
||||
highWater := staticAddressLoopInMonitorHighWater([]loop.SwapInfo{
|
||||
snapshot,
|
||||
})
|
||||
|
||||
isStale := isInitialStaticAddressLoopInStale(highWater, snapshot)
|
||||
|
||||
require.True(t, isStale)
|
||||
}
|
||||
|
||||
// TestStaticAddressLoopInHighWaterKeepsSameTimeDifferentState protects the
|
||||
// high-water timing invariant that equal timestamps do not hide a distinct
|
||||
// static loop-in state transition.
|
||||
func TestStaticAddressLoopInHighWaterKeepsSameTimeDifferentState(t *testing.T) {
|
||||
swapHash := lntypes.Hash{1, 2, 3}
|
||||
lastUpdate := time.Unix(100, 0).UTC()
|
||||
snapshot := loop.SwapInfo{
|
||||
SwapHash: swapHash,
|
||||
SwapType: swap.TypeStaticAddressLoopIn,
|
||||
LastUpdate: lastUpdate,
|
||||
StaticAddressLoopInState: loopin.PaymentReceived,
|
||||
}
|
||||
liveUpdate := snapshot
|
||||
liveUpdate.StaticAddressLoopInState = loopin.Succeeded
|
||||
highWater := staticAddressLoopInMonitorHighWater([]loop.SwapInfo{
|
||||
snapshot,
|
||||
})
|
||||
|
||||
isStale := isInitialStaticAddressLoopInStale(highWater, liveUpdate)
|
||||
|
||||
require.False(t, isStale)
|
||||
}
|
||||
|
||||
// TestStaticAddressLoopInHighWaterSuppressesOlderStaticOnly protects the
|
||||
// high-water cache invariant that stale suppression applies only to static
|
||||
// loop-ins and cannot filter generic swap updates.
|
||||
func TestStaticAddressLoopInHighWaterSuppressesOlderStaticOnly(t *testing.T) {
|
||||
swapHash := lntypes.Hash{1, 2, 3}
|
||||
lastUpdate := time.Unix(100, 0).UTC()
|
||||
snapshot := loop.SwapInfo{
|
||||
SwapHash: swapHash,
|
||||
SwapType: swap.TypeStaticAddressLoopIn,
|
||||
LastUpdate: lastUpdate,
|
||||
StaticAddressLoopInState: loopin.PaymentReceived,
|
||||
}
|
||||
highWater := staticAddressLoopInMonitorHighWater([]loop.SwapInfo{
|
||||
snapshot,
|
||||
})
|
||||
olderStatic := snapshot
|
||||
olderStatic.LastUpdate = lastUpdate.Add(-time.Second)
|
||||
olderStatic.StaticAddressLoopInState = loopin.Succeeded
|
||||
nonStatic := olderStatic
|
||||
nonStatic.SwapType = swap.TypeOut
|
||||
|
||||
staticStale := isInitialStaticAddressLoopInStale(highWater, olderStatic)
|
||||
nonStaticStale := isInitialStaticAddressLoopInStale(
|
||||
highWater, nonStatic,
|
||||
)
|
||||
|
||||
require.True(t, staticStale)
|
||||
require.False(t, nonStaticStale)
|
||||
}
|
||||
|
||||
// TestStaticAddressLoopInStatusUpdateDoesNotEnterGenericSwapCache protects the
|
||||
// cache isolation invariant that static loop-in live updates reach subscribers
|
||||
// without entering the generic swap cache.
|
||||
func TestStaticAddressLoopInStatusUpdateDoesNotEnterGenericSwapCache(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
server, staticLoopIn := newGenericStaticLoopInServer(t)
|
||||
server.statusChan = make(chan loop.SwapInfo)
|
||||
updates := make(chan any, 1)
|
||||
server.subscribers = map[int]chan<- any{0: updates}
|
||||
mainCtx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
go server.processStatusUpdates(mainCtx)
|
||||
|
||||
staticUpdate, err := server.staticAddressLoopInSwapInfo(ctx, staticLoopIn)
|
||||
require.NoError(t, err)
|
||||
server.statusChan <- *staticUpdate
|
||||
|
||||
select {
|
||||
case update := <-updates:
|
||||
require.Equal(t, staticLoopIn.SwapHash, update.(loop.SwapInfo).SwapHash)
|
||||
|
||||
case <-ctx.Done():
|
||||
t.Fatal(ctx.Err())
|
||||
}
|
||||
|
||||
server.swapsLock.Lock()
|
||||
_, cached := server.swaps[staticLoopIn.SwapHash]
|
||||
server.swapsLock.Unlock()
|
||||
require.False(t, cached)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// assertStaticLoopInUsesSwapHtlcAddress verifies the static loop-in uses the
|
||||
// swap HTLC P2WSH address expected by the fixture.
|
||||
func assertStaticLoopInUsesSwapHtlcAddress(t *testing.T,
|
||||
staticLoopIn *loopin.StaticAddressLoopIn, swapInfo loop.SwapInfo) {
|
||||
|
||||
t.Helper()
|
||||
|
||||
expectedAddress, err := staticAddressLoopInHtlcAddress(
|
||||
staticLoopIn, &chaincfg.TestNet3Params,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, swapInfo.HtlcAddressP2TR)
|
||||
require.NotNil(t, swapInfo.HtlcAddressP2WSH)
|
||||
require.Equal(
|
||||
t, expectedAddress.EncodeAddress(),
|
||||
swapInfo.HtlcAddressP2WSH.EncodeAddress(),
|
||||
)
|
||||
}
|
||||
|
||||
// testMonitorServer implements the monitor stream interface for tests.
|
||||
type testMonitorServer struct {
|
||||
ctx context.Context
|
||||
sent chan *looprpc.SwapStatus
|
||||
}
|
||||
|
||||
// Send forwards monitor updates to the test channel until the context is canceled.
|
||||
func (s *testMonitorServer) Send(swapStatus *looprpc.SwapStatus) error {
|
||||
select {
|
||||
case s.sent <- swapStatus:
|
||||
return nil
|
||||
|
||||
case <-s.ctx.Done():
|
||||
return s.ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// SetHeader is a no-op stub that satisfies the monitor stream interface in tests.
|
||||
func (s *testMonitorServer) SetHeader(metadata.MD) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendHeader is a no-op stub that satisfies the monitor stream interface in tests.
|
||||
func (s *testMonitorServer) SendHeader(metadata.MD) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTrailer is a no-op stub that satisfies the monitor stream interface in tests.
|
||||
func (s *testMonitorServer) SetTrailer(metadata.MD) {}
|
||||
|
||||
// Context returns the stream context used by the test monitor server.
|
||||
func (s *testMonitorServer) Context() context.Context {
|
||||
return s.ctx
|
||||
}
|
||||
|
||||
// SendMsg is a no-op stub that satisfies the monitor stream interface in tests.
|
||||
func (s *testMonitorServer) SendMsg(any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RecvMsg is a no-op stub that satisfies the monitor stream interface in tests.
|
||||
func (s *testMonitorServer) RecvMsg(any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// receiveMonitorUpdate waits for a monitor update or fails if the context is canceled.
|
||||
func receiveMonitorUpdate(t *testing.T, ctx context.Context,
|
||||
updates <-chan *looprpc.SwapStatus) *looprpc.SwapStatus {
|
||||
|
||||
t.Helper()
|
||||
|
||||
select {
|
||||
case update := <-updates:
|
||||
return update
|
||||
|
||||
case <-ctx.Done():
|
||||
t.Fatal(ctx.Err())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// mockStaticAddressLoopInStore is a minimal in-memory loop-in store for RPC
|
||||
// response mapping tests.
|
||||
type mockStaticAddressLoopInStore struct {
|
||||
swaps []*loopin.StaticAddressLoopIn
|
||||
swaps []*loopin.StaticAddressLoopIn
|
||||
beforeGet func()
|
||||
waitGet <-chan struct{}
|
||||
}
|
||||
|
||||
// CreateLoopIn satisfies the static loop-in store interface.
|
||||
|
|
@ -523,9 +1070,20 @@ func (s *mockStaticAddressLoopInStore) UpdateLoopIn(_ context.Context,
|
|||
|
||||
// GetStaticAddressLoopInSwapsByStates returns the configured loop-ins.
|
||||
func (s *mockStaticAddressLoopInStore) GetStaticAddressLoopInSwapsByStates(
|
||||
_ context.Context, _ []fsm.StateType) ([]*loopin.StaticAddressLoopIn,
|
||||
ctx context.Context, _ []fsm.StateType) ([]*loopin.StaticAddressLoopIn,
|
||||
error) {
|
||||
|
||||
if s.beforeGet != nil {
|
||||
s.beforeGet()
|
||||
}
|
||||
if s.waitGet != nil {
|
||||
select {
|
||||
case <-s.waitGet:
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return s.swaps, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -80,6 +80,8 @@ const (
|
|||
SwapType_LOOP_OUT SwapType = 0
|
||||
// LOOP_IN indicates a loop in swap (on-chain to off-chain)
|
||||
SwapType_LOOP_IN SwapType = 1
|
||||
// STATIC_LOOP_IN indicates a static address loop in swap.
|
||||
SwapType_STATIC_LOOP_IN SwapType = 2
|
||||
)
|
||||
|
||||
// Enum value maps for SwapType.
|
||||
|
|
@ -87,10 +89,12 @@ var (
|
|||
SwapType_name = map[int32]string{
|
||||
0: "LOOP_OUT",
|
||||
1: "LOOP_IN",
|
||||
2: "STATIC_LOOP_IN",
|
||||
}
|
||||
SwapType_value = map[string]int32{
|
||||
"LOOP_OUT": 0,
|
||||
"LOOP_IN": 1,
|
||||
"LOOP_OUT": 0,
|
||||
"LOOP_IN": 1,
|
||||
"STATIC_LOOP_IN": 2,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -1474,8 +1478,12 @@ type SwapStatus struct {
|
|||
IdBytes []byte `protobuf:"bytes,11,opt,name=id_bytes,json=idBytes,proto3" json:"id_bytes,omitempty"`
|
||||
// The type of the swap.
|
||||
Type SwapType `protobuf:"varint,3,opt,name=type,proto3,enum=looprpc.SwapType" json:"type,omitempty"`
|
||||
// State the swap is currently in, see State enum.
|
||||
// Generic loop-in/loop-out state for swaps.
|
||||
State SwapState `protobuf:"varint,4,opt,name=state,proto3,enum=looprpc.SwapState" json:"state,omitempty"`
|
||||
// Types that are valid to be assigned to StaticLoopInStateOptional:
|
||||
//
|
||||
// *SwapStatus_StaticLoopInState
|
||||
StaticLoopInStateOptional isSwapStatus_StaticLoopInStateOptional `protobuf_oneof:"static_loop_in_state_optional"`
|
||||
// A failure reason for the swap, only set if the swap has failed.
|
||||
FailureReason FailureReason `protobuf:"varint,14,opt,name=failure_reason,json=failureReason,proto3,enum=looprpc.FailureReason" json:"failure_reason,omitempty"`
|
||||
// Initiation time of the swap.
|
||||
|
|
@ -1578,6 +1586,22 @@ func (x *SwapStatus) GetState() SwapState {
|
|||
return SwapState_INITIATED
|
||||
}
|
||||
|
||||
func (x *SwapStatus) GetStaticLoopInStateOptional() isSwapStatus_StaticLoopInStateOptional {
|
||||
if x != nil {
|
||||
return x.StaticLoopInStateOptional
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SwapStatus) GetStaticLoopInState() StaticAddressLoopInSwapState {
|
||||
if x != nil {
|
||||
if x, ok := x.StaticLoopInStateOptional.(*SwapStatus_StaticLoopInState); ok {
|
||||
return x.StaticLoopInState
|
||||
}
|
||||
}
|
||||
return StaticAddressLoopInSwapState_UNKNOWN_STATIC_ADDRESS_SWAP_STATE
|
||||
}
|
||||
|
||||
func (x *SwapStatus) GetFailureReason() FailureReason {
|
||||
if x != nil {
|
||||
return x.FailureReason
|
||||
|
|
@ -1670,6 +1694,17 @@ func (x *SwapStatus) GetAssetInfo() *AssetLoopOutInfo {
|
|||
return nil
|
||||
}
|
||||
|
||||
type isSwapStatus_StaticLoopInStateOptional interface {
|
||||
isSwapStatus_StaticLoopInStateOptional()
|
||||
}
|
||||
|
||||
type SwapStatus_StaticLoopInState struct {
|
||||
// Static address loop-in FSM state when type is STATIC_LOOP_IN.
|
||||
StaticLoopInState StaticAddressLoopInSwapState `protobuf:"varint,20,opt,name=static_loop_in_state,json=staticLoopInState,proto3,enum=looprpc.StaticAddressLoopInSwapState,oneof"`
|
||||
}
|
||||
|
||||
func (*SwapStatus_StaticLoopInState) isSwapStatus_StaticLoopInStateOptional() {}
|
||||
|
||||
type ListSwapsRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Optional filter to only return swaps that match the filter.
|
||||
|
|
@ -6704,14 +6739,15 @@ const file_client_proto_rawDesc = "" +
|
|||
"\x12htlc_address_p2wsh\x18\x05 \x01(\tR\x10htlcAddressP2wsh\x12*\n" +
|
||||
"\x11htlc_address_p2tr\x18\a \x01(\tR\x0fhtlcAddressP2tr\x12%\n" +
|
||||
"\x0eserver_message\x18\x06 \x01(\tR\rserverMessageJ\x04\b\x04\x10\x05\"\x10\n" +
|
||||
"\x0eMonitorRequest\"\xb1\x05\n" +
|
||||
"\x0eMonitorRequest\"\xac\x06\n" +
|
||||
"\n" +
|
||||
"SwapStatus\x12\x10\n" +
|
||||
"\x03amt\x18\x01 \x01(\x03R\x03amt\x12\x12\n" +
|
||||
"\x02id\x18\x02 \x01(\tB\x02\x18\x01R\x02id\x12\x19\n" +
|
||||
"\bid_bytes\x18\v \x01(\fR\aidBytes\x12%\n" +
|
||||
"\x04type\x18\x03 \x01(\x0e2\x11.looprpc.SwapTypeR\x04type\x12(\n" +
|
||||
"\x05state\x18\x04 \x01(\x0e2\x12.looprpc.SwapStateR\x05state\x12=\n" +
|
||||
"\x05state\x18\x04 \x01(\x0e2\x12.looprpc.SwapStateR\x05state\x12X\n" +
|
||||
"\x14static_loop_in_state\x18\x14 \x01(\x0e2%.looprpc.StaticAddressLoopInSwapStateH\x00R\x11staticLoopInState\x12=\n" +
|
||||
"\x0efailure_reason\x18\x0e \x01(\x0e2\x16.looprpc.FailureReasonR\rfailureReason\x12'\n" +
|
||||
"\x0finitiation_time\x18\x05 \x01(\x03R\x0einitiationTime\x12(\n" +
|
||||
"\x10last_update_time\x18\x06 \x01(\x03R\x0elastUpdateTime\x12%\n" +
|
||||
|
|
@ -6727,7 +6763,8 @@ const file_client_proto_rawDesc = "" +
|
|||
"\x11outgoing_chan_set\x18\x11 \x03(\x04R\x0foutgoingChanSet\x12\x14\n" +
|
||||
"\x05label\x18\x0f \x01(\tR\x05label\x128\n" +
|
||||
"\n" +
|
||||
"asset_info\x18\x13 \x01(\v2\x19.looprpc.AssetLoopOutInfoR\tassetInfo\"s\n" +
|
||||
"asset_info\x18\x13 \x01(\v2\x19.looprpc.AssetLoopOutInfoR\tassetInfoB\x1f\n" +
|
||||
"\x1dstatic_loop_in_state_optional\"s\n" +
|
||||
"\x10ListSwapsRequest\x12B\n" +
|
||||
"\x10list_swap_filter\x18\x01 \x01(\v2\x18.looprpc.ListSwapsFilterR\x0elistSwapFilter\x12\x1b\n" +
|
||||
"\tmax_swaps\x18\x02 \x01(\x04R\bmaxSwaps\"\xf1\x02\n" +
|
||||
|
|
@ -7088,10 +7125,11 @@ const file_client_proto_rawDesc = "" +
|
|||
"\x13asset_cost_offchain\x18\x03 \x01(\x04R\x11assetCostOffchain*;\n" +
|
||||
"\vAddressType\x12\x18\n" +
|
||||
"\x14ADDRESS_TYPE_UNKNOWN\x10\x00\x12\x12\n" +
|
||||
"\x0eTAPROOT_PUBKEY\x10\x01*%\n" +
|
||||
"\x0eTAPROOT_PUBKEY\x10\x01*9\n" +
|
||||
"\bSwapType\x12\f\n" +
|
||||
"\bLOOP_OUT\x10\x00\x12\v\n" +
|
||||
"\aLOOP_IN\x10\x01*s\n" +
|
||||
"\aLOOP_IN\x10\x01\x12\x12\n" +
|
||||
"\x0eSTATIC_LOOP_IN\x10\x02*s\n" +
|
||||
"\tSwapState\x12\r\n" +
|
||||
"\tINITIATED\x10\x00\x12\x15\n" +
|
||||
"\x11PREIMAGE_REVEALED\x10\x01\x12\x12\n" +
|
||||
|
|
@ -7321,121 +7359,122 @@ var file_client_proto_depIdxs = []int32{
|
|||
91, // 4: looprpc.LoopInRequest.route_hints:type_name -> looprpc.RouteHint
|
||||
1, // 5: looprpc.SwapStatus.type:type_name -> looprpc.SwapType
|
||||
2, // 6: looprpc.SwapStatus.state:type_name -> looprpc.SwapState
|
||||
3, // 7: looprpc.SwapStatus.failure_reason:type_name -> looprpc.FailureReason
|
||||
88, // 8: looprpc.SwapStatus.asset_info:type_name -> looprpc.AssetLoopOutInfo
|
||||
20, // 9: looprpc.ListSwapsRequest.list_swap_filter:type_name -> looprpc.ListSwapsFilter
|
||||
9, // 10: looprpc.ListSwapsFilter.swap_type:type_name -> looprpc.ListSwapsFilter.SwapTypeFilter
|
||||
18, // 11: looprpc.ListSwapsResponse.swaps:type_name -> looprpc.SwapStatus
|
||||
24, // 12: looprpc.SweepHtlcResponse.not_requested:type_name -> looprpc.PublishNotRequested
|
||||
25, // 13: looprpc.SweepHtlcResponse.published:type_name -> looprpc.PublishSucceeded
|
||||
26, // 14: looprpc.SweepHtlcResponse.failed:type_name -> looprpc.PublishFailed
|
||||
91, // 15: looprpc.QuoteRequest.loop_in_route_hints:type_name -> looprpc.RouteHint
|
||||
85, // 16: looprpc.QuoteRequest.asset_info:type_name -> looprpc.AssetLoopOutRequest
|
||||
86, // 17: looprpc.OutQuoteResponse.asset_rfq_info:type_name -> looprpc.AssetRfqInfo
|
||||
91, // 18: looprpc.ProbeRequest.route_hints:type_name -> looprpc.RouteHint
|
||||
40, // 19: looprpc.TokensResponse.tokens:type_name -> looprpc.L402Token
|
||||
41, // 20: looprpc.GetInfoResponse.loop_out_stats:type_name -> looprpc.LoopStats
|
||||
41, // 21: looprpc.GetInfoResponse.loop_in_stats:type_name -> looprpc.LoopStats
|
||||
47, // 22: looprpc.LiquidityParameters.rules:type_name -> looprpc.LiquidityRule
|
||||
0, // 23: looprpc.LiquidityParameters.account_addr_type:type_name -> looprpc.AddressType
|
||||
89, // 24: looprpc.LiquidityParameters.easy_asset_params:type_name -> looprpc.LiquidityParameters.EasyAssetParamsEntry
|
||||
4, // 25: looprpc.LiquidityParameters.loop_in_source:type_name -> looprpc.LoopInSource
|
||||
1, // 26: looprpc.LiquidityRule.swap_type:type_name -> looprpc.SwapType
|
||||
5, // 27: looprpc.LiquidityRule.type:type_name -> looprpc.LiquidityRuleType
|
||||
45, // 28: looprpc.SetLiquidityParamsRequest.parameters:type_name -> looprpc.LiquidityParameters
|
||||
6, // 29: looprpc.Disqualified.reason:type_name -> looprpc.AutoReason
|
||||
14, // 30: looprpc.SuggestSwapsResponse.loop_out:type_name -> looprpc.LoopOutRequest
|
||||
15, // 31: looprpc.SuggestSwapsResponse.loop_in:type_name -> looprpc.LoopInRequest
|
||||
83, // 32: looprpc.SuggestSwapsResponse.static_loop_in:type_name -> looprpc.StaticAddressLoopInRequest
|
||||
51, // 33: looprpc.SuggestSwapsResponse.disqualified:type_name -> looprpc.Disqualified
|
||||
57, // 34: looprpc.ListReservationsResponse.reservations:type_name -> looprpc.ClientReservation
|
||||
64, // 35: looprpc.ListInstantOutsResponse.swaps:type_name -> looprpc.InstantOut
|
||||
69, // 36: looprpc.ListUnspentDepositsResponse.utxos:type_name -> looprpc.Utxo
|
||||
92, // 37: looprpc.WithdrawDepositsRequest.outpoints:type_name -> lnrpc.OutPoint
|
||||
7, // 38: looprpc.ListStaticAddressDepositsRequest.state_filter:type_name -> looprpc.DepositState
|
||||
80, // 39: looprpc.ListStaticAddressDepositsResponse.filtered_deposits:type_name -> looprpc.Deposit
|
||||
81, // 40: looprpc.ListStaticAddressWithdrawalResponse.withdrawals:type_name -> looprpc.StaticAddressWithdrawal
|
||||
82, // 41: looprpc.ListStaticAddressSwapsResponse.swaps:type_name -> looprpc.StaticAddressLoopInSwap
|
||||
7, // 42: looprpc.Deposit.state:type_name -> looprpc.DepositState
|
||||
80, // 43: looprpc.StaticAddressWithdrawal.deposits:type_name -> looprpc.Deposit
|
||||
8, // 44: looprpc.StaticAddressLoopInSwap.state:type_name -> looprpc.StaticAddressLoopInSwapState
|
||||
80, // 45: looprpc.StaticAddressLoopInSwap.deposits:type_name -> looprpc.Deposit
|
||||
91, // 46: looprpc.StaticAddressLoopInRequest.route_hints:type_name -> looprpc.RouteHint
|
||||
80, // 47: looprpc.StaticAddressLoopInResponse.used_deposits:type_name -> looprpc.Deposit
|
||||
87, // 48: looprpc.AssetRfqInfo.prepay_asset_rate:type_name -> looprpc.FixedPoint
|
||||
87, // 49: looprpc.AssetRfqInfo.swap_asset_rate:type_name -> looprpc.FixedPoint
|
||||
46, // 50: looprpc.LiquidityParameters.EasyAssetParamsEntry.value:type_name -> looprpc.EasyAssetAutoloopParams
|
||||
14, // 51: looprpc.SwapClient.LoopOut:input_type -> looprpc.LoopOutRequest
|
||||
15, // 52: looprpc.SwapClient.LoopIn:input_type -> looprpc.LoopInRequest
|
||||
17, // 53: looprpc.SwapClient.Monitor:input_type -> looprpc.MonitorRequest
|
||||
19, // 54: looprpc.SwapClient.ListSwaps:input_type -> looprpc.ListSwapsRequest
|
||||
22, // 55: looprpc.SwapClient.SweepHtlc:input_type -> looprpc.SweepHtlcRequest
|
||||
27, // 56: looprpc.SwapClient.SwapInfo:input_type -> looprpc.SwapInfoRequest
|
||||
53, // 57: looprpc.SwapClient.AbandonSwap:input_type -> looprpc.AbandonSwapRequest
|
||||
28, // 58: looprpc.SwapClient.LoopOutTerms:input_type -> looprpc.TermsRequest
|
||||
31, // 59: looprpc.SwapClient.LoopOutQuote:input_type -> looprpc.QuoteRequest
|
||||
28, // 60: looprpc.SwapClient.GetLoopInTerms:input_type -> looprpc.TermsRequest
|
||||
31, // 61: looprpc.SwapClient.GetLoopInQuote:input_type -> looprpc.QuoteRequest
|
||||
34, // 62: looprpc.SwapClient.Probe:input_type -> looprpc.ProbeRequest
|
||||
36, // 63: looprpc.SwapClient.GetL402Tokens:input_type -> looprpc.TokensRequest
|
||||
36, // 64: looprpc.SwapClient.GetLsatTokens:input_type -> looprpc.TokensRequest
|
||||
38, // 65: looprpc.SwapClient.FetchL402Token:input_type -> looprpc.FetchL402TokenRequest
|
||||
42, // 66: looprpc.SwapClient.GetInfo:input_type -> looprpc.GetInfoRequest
|
||||
12, // 67: looprpc.SwapClient.StopDaemon:input_type -> looprpc.StopDaemonRequest
|
||||
44, // 68: looprpc.SwapClient.GetLiquidityParams:input_type -> looprpc.GetLiquidityParamsRequest
|
||||
48, // 69: looprpc.SwapClient.SetLiquidityParams:input_type -> looprpc.SetLiquidityParamsRequest
|
||||
50, // 70: looprpc.SwapClient.SuggestSwaps:input_type -> looprpc.SuggestSwapsRequest
|
||||
55, // 71: looprpc.SwapClient.ListReservations:input_type -> looprpc.ListReservationsRequest
|
||||
58, // 72: looprpc.SwapClient.InstantOut:input_type -> looprpc.InstantOutRequest
|
||||
60, // 73: looprpc.SwapClient.InstantOutQuote:input_type -> looprpc.InstantOutQuoteRequest
|
||||
62, // 74: looprpc.SwapClient.ListInstantOuts:input_type -> looprpc.ListInstantOutsRequest
|
||||
65, // 75: looprpc.SwapClient.NewStaticAddress:input_type -> looprpc.NewStaticAddressRequest
|
||||
67, // 76: looprpc.SwapClient.ListUnspentDeposits:input_type -> looprpc.ListUnspentDepositsRequest
|
||||
70, // 77: looprpc.SwapClient.WithdrawDeposits:input_type -> looprpc.WithdrawDepositsRequest
|
||||
72, // 78: looprpc.SwapClient.ListStaticAddressDeposits:input_type -> looprpc.ListStaticAddressDepositsRequest
|
||||
74, // 79: looprpc.SwapClient.ListStaticAddressWithdrawals:input_type -> looprpc.ListStaticAddressWithdrawalRequest
|
||||
76, // 80: looprpc.SwapClient.ListStaticAddressSwaps:input_type -> looprpc.ListStaticAddressSwapsRequest
|
||||
78, // 81: looprpc.SwapClient.GetStaticAddressSummary:input_type -> looprpc.StaticAddressSummaryRequest
|
||||
83, // 82: looprpc.SwapClient.StaticAddressLoopIn:input_type -> looprpc.StaticAddressLoopInRequest
|
||||
10, // 83: looprpc.SwapClient.StaticOpenChannel:input_type -> looprpc.StaticOpenChannelRequest
|
||||
16, // 84: looprpc.SwapClient.LoopOut:output_type -> looprpc.SwapResponse
|
||||
16, // 85: looprpc.SwapClient.LoopIn:output_type -> looprpc.SwapResponse
|
||||
18, // 86: looprpc.SwapClient.Monitor:output_type -> looprpc.SwapStatus
|
||||
21, // 87: looprpc.SwapClient.ListSwaps:output_type -> looprpc.ListSwapsResponse
|
||||
23, // 88: looprpc.SwapClient.SweepHtlc:output_type -> looprpc.SweepHtlcResponse
|
||||
18, // 89: looprpc.SwapClient.SwapInfo:output_type -> looprpc.SwapStatus
|
||||
54, // 90: looprpc.SwapClient.AbandonSwap:output_type -> looprpc.AbandonSwapResponse
|
||||
30, // 91: looprpc.SwapClient.LoopOutTerms:output_type -> looprpc.OutTermsResponse
|
||||
33, // 92: looprpc.SwapClient.LoopOutQuote:output_type -> looprpc.OutQuoteResponse
|
||||
29, // 93: looprpc.SwapClient.GetLoopInTerms:output_type -> looprpc.InTermsResponse
|
||||
32, // 94: looprpc.SwapClient.GetLoopInQuote:output_type -> looprpc.InQuoteResponse
|
||||
35, // 95: looprpc.SwapClient.Probe:output_type -> looprpc.ProbeResponse
|
||||
37, // 96: looprpc.SwapClient.GetL402Tokens:output_type -> looprpc.TokensResponse
|
||||
37, // 97: looprpc.SwapClient.GetLsatTokens:output_type -> looprpc.TokensResponse
|
||||
39, // 98: looprpc.SwapClient.FetchL402Token:output_type -> looprpc.FetchL402TokenResponse
|
||||
43, // 99: looprpc.SwapClient.GetInfo:output_type -> looprpc.GetInfoResponse
|
||||
13, // 100: looprpc.SwapClient.StopDaemon:output_type -> looprpc.StopDaemonResponse
|
||||
45, // 101: looprpc.SwapClient.GetLiquidityParams:output_type -> looprpc.LiquidityParameters
|
||||
49, // 102: looprpc.SwapClient.SetLiquidityParams:output_type -> looprpc.SetLiquidityParamsResponse
|
||||
52, // 103: looprpc.SwapClient.SuggestSwaps:output_type -> looprpc.SuggestSwapsResponse
|
||||
56, // 104: looprpc.SwapClient.ListReservations:output_type -> looprpc.ListReservationsResponse
|
||||
59, // 105: looprpc.SwapClient.InstantOut:output_type -> looprpc.InstantOutResponse
|
||||
61, // 106: looprpc.SwapClient.InstantOutQuote:output_type -> looprpc.InstantOutQuoteResponse
|
||||
63, // 107: looprpc.SwapClient.ListInstantOuts:output_type -> looprpc.ListInstantOutsResponse
|
||||
66, // 108: looprpc.SwapClient.NewStaticAddress:output_type -> looprpc.NewStaticAddressResponse
|
||||
68, // 109: looprpc.SwapClient.ListUnspentDeposits:output_type -> looprpc.ListUnspentDepositsResponse
|
||||
71, // 110: looprpc.SwapClient.WithdrawDeposits:output_type -> looprpc.WithdrawDepositsResponse
|
||||
73, // 111: looprpc.SwapClient.ListStaticAddressDeposits:output_type -> looprpc.ListStaticAddressDepositsResponse
|
||||
75, // 112: looprpc.SwapClient.ListStaticAddressWithdrawals:output_type -> looprpc.ListStaticAddressWithdrawalResponse
|
||||
77, // 113: looprpc.SwapClient.ListStaticAddressSwaps:output_type -> looprpc.ListStaticAddressSwapsResponse
|
||||
79, // 114: looprpc.SwapClient.GetStaticAddressSummary:output_type -> looprpc.StaticAddressSummaryResponse
|
||||
84, // 115: looprpc.SwapClient.StaticAddressLoopIn:output_type -> looprpc.StaticAddressLoopInResponse
|
||||
11, // 116: looprpc.SwapClient.StaticOpenChannel:output_type -> looprpc.StaticOpenChannelResponse
|
||||
84, // [84:117] is the sub-list for method output_type
|
||||
51, // [51:84] is the sub-list for method input_type
|
||||
51, // [51:51] is the sub-list for extension type_name
|
||||
51, // [51:51] is the sub-list for extension extendee
|
||||
0, // [0:51] is the sub-list for field type_name
|
||||
8, // 7: looprpc.SwapStatus.static_loop_in_state:type_name -> looprpc.StaticAddressLoopInSwapState
|
||||
3, // 8: looprpc.SwapStatus.failure_reason:type_name -> looprpc.FailureReason
|
||||
88, // 9: looprpc.SwapStatus.asset_info:type_name -> looprpc.AssetLoopOutInfo
|
||||
20, // 10: looprpc.ListSwapsRequest.list_swap_filter:type_name -> looprpc.ListSwapsFilter
|
||||
9, // 11: looprpc.ListSwapsFilter.swap_type:type_name -> looprpc.ListSwapsFilter.SwapTypeFilter
|
||||
18, // 12: looprpc.ListSwapsResponse.swaps:type_name -> looprpc.SwapStatus
|
||||
24, // 13: looprpc.SweepHtlcResponse.not_requested:type_name -> looprpc.PublishNotRequested
|
||||
25, // 14: looprpc.SweepHtlcResponse.published:type_name -> looprpc.PublishSucceeded
|
||||
26, // 15: looprpc.SweepHtlcResponse.failed:type_name -> looprpc.PublishFailed
|
||||
91, // 16: looprpc.QuoteRequest.loop_in_route_hints:type_name -> looprpc.RouteHint
|
||||
85, // 17: looprpc.QuoteRequest.asset_info:type_name -> looprpc.AssetLoopOutRequest
|
||||
86, // 18: looprpc.OutQuoteResponse.asset_rfq_info:type_name -> looprpc.AssetRfqInfo
|
||||
91, // 19: looprpc.ProbeRequest.route_hints:type_name -> looprpc.RouteHint
|
||||
40, // 20: looprpc.TokensResponse.tokens:type_name -> looprpc.L402Token
|
||||
41, // 21: looprpc.GetInfoResponse.loop_out_stats:type_name -> looprpc.LoopStats
|
||||
41, // 22: looprpc.GetInfoResponse.loop_in_stats:type_name -> looprpc.LoopStats
|
||||
47, // 23: looprpc.LiquidityParameters.rules:type_name -> looprpc.LiquidityRule
|
||||
0, // 24: looprpc.LiquidityParameters.account_addr_type:type_name -> looprpc.AddressType
|
||||
89, // 25: looprpc.LiquidityParameters.easy_asset_params:type_name -> looprpc.LiquidityParameters.EasyAssetParamsEntry
|
||||
4, // 26: looprpc.LiquidityParameters.loop_in_source:type_name -> looprpc.LoopInSource
|
||||
1, // 27: looprpc.LiquidityRule.swap_type:type_name -> looprpc.SwapType
|
||||
5, // 28: looprpc.LiquidityRule.type:type_name -> looprpc.LiquidityRuleType
|
||||
45, // 29: looprpc.SetLiquidityParamsRequest.parameters:type_name -> looprpc.LiquidityParameters
|
||||
6, // 30: looprpc.Disqualified.reason:type_name -> looprpc.AutoReason
|
||||
14, // 31: looprpc.SuggestSwapsResponse.loop_out:type_name -> looprpc.LoopOutRequest
|
||||
15, // 32: looprpc.SuggestSwapsResponse.loop_in:type_name -> looprpc.LoopInRequest
|
||||
83, // 33: looprpc.SuggestSwapsResponse.static_loop_in:type_name -> looprpc.StaticAddressLoopInRequest
|
||||
51, // 34: looprpc.SuggestSwapsResponse.disqualified:type_name -> looprpc.Disqualified
|
||||
57, // 35: looprpc.ListReservationsResponse.reservations:type_name -> looprpc.ClientReservation
|
||||
64, // 36: looprpc.ListInstantOutsResponse.swaps:type_name -> looprpc.InstantOut
|
||||
69, // 37: looprpc.ListUnspentDepositsResponse.utxos:type_name -> looprpc.Utxo
|
||||
92, // 38: looprpc.WithdrawDepositsRequest.outpoints:type_name -> lnrpc.OutPoint
|
||||
7, // 39: looprpc.ListStaticAddressDepositsRequest.state_filter:type_name -> looprpc.DepositState
|
||||
80, // 40: looprpc.ListStaticAddressDepositsResponse.filtered_deposits:type_name -> looprpc.Deposit
|
||||
81, // 41: looprpc.ListStaticAddressWithdrawalResponse.withdrawals:type_name -> looprpc.StaticAddressWithdrawal
|
||||
82, // 42: looprpc.ListStaticAddressSwapsResponse.swaps:type_name -> looprpc.StaticAddressLoopInSwap
|
||||
7, // 43: looprpc.Deposit.state:type_name -> looprpc.DepositState
|
||||
80, // 44: looprpc.StaticAddressWithdrawal.deposits:type_name -> looprpc.Deposit
|
||||
8, // 45: looprpc.StaticAddressLoopInSwap.state:type_name -> looprpc.StaticAddressLoopInSwapState
|
||||
80, // 46: looprpc.StaticAddressLoopInSwap.deposits:type_name -> looprpc.Deposit
|
||||
91, // 47: looprpc.StaticAddressLoopInRequest.route_hints:type_name -> looprpc.RouteHint
|
||||
80, // 48: looprpc.StaticAddressLoopInResponse.used_deposits:type_name -> looprpc.Deposit
|
||||
87, // 49: looprpc.AssetRfqInfo.prepay_asset_rate:type_name -> looprpc.FixedPoint
|
||||
87, // 50: looprpc.AssetRfqInfo.swap_asset_rate:type_name -> looprpc.FixedPoint
|
||||
46, // 51: looprpc.LiquidityParameters.EasyAssetParamsEntry.value:type_name -> looprpc.EasyAssetAutoloopParams
|
||||
14, // 52: looprpc.SwapClient.LoopOut:input_type -> looprpc.LoopOutRequest
|
||||
15, // 53: looprpc.SwapClient.LoopIn:input_type -> looprpc.LoopInRequest
|
||||
17, // 54: looprpc.SwapClient.Monitor:input_type -> looprpc.MonitorRequest
|
||||
19, // 55: looprpc.SwapClient.ListSwaps:input_type -> looprpc.ListSwapsRequest
|
||||
22, // 56: looprpc.SwapClient.SweepHtlc:input_type -> looprpc.SweepHtlcRequest
|
||||
27, // 57: looprpc.SwapClient.SwapInfo:input_type -> looprpc.SwapInfoRequest
|
||||
53, // 58: looprpc.SwapClient.AbandonSwap:input_type -> looprpc.AbandonSwapRequest
|
||||
28, // 59: looprpc.SwapClient.LoopOutTerms:input_type -> looprpc.TermsRequest
|
||||
31, // 60: looprpc.SwapClient.LoopOutQuote:input_type -> looprpc.QuoteRequest
|
||||
28, // 61: looprpc.SwapClient.GetLoopInTerms:input_type -> looprpc.TermsRequest
|
||||
31, // 62: looprpc.SwapClient.GetLoopInQuote:input_type -> looprpc.QuoteRequest
|
||||
34, // 63: looprpc.SwapClient.Probe:input_type -> looprpc.ProbeRequest
|
||||
36, // 64: looprpc.SwapClient.GetL402Tokens:input_type -> looprpc.TokensRequest
|
||||
36, // 65: looprpc.SwapClient.GetLsatTokens:input_type -> looprpc.TokensRequest
|
||||
38, // 66: looprpc.SwapClient.FetchL402Token:input_type -> looprpc.FetchL402TokenRequest
|
||||
42, // 67: looprpc.SwapClient.GetInfo:input_type -> looprpc.GetInfoRequest
|
||||
12, // 68: looprpc.SwapClient.StopDaemon:input_type -> looprpc.StopDaemonRequest
|
||||
44, // 69: looprpc.SwapClient.GetLiquidityParams:input_type -> looprpc.GetLiquidityParamsRequest
|
||||
48, // 70: looprpc.SwapClient.SetLiquidityParams:input_type -> looprpc.SetLiquidityParamsRequest
|
||||
50, // 71: looprpc.SwapClient.SuggestSwaps:input_type -> looprpc.SuggestSwapsRequest
|
||||
55, // 72: looprpc.SwapClient.ListReservations:input_type -> looprpc.ListReservationsRequest
|
||||
58, // 73: looprpc.SwapClient.InstantOut:input_type -> looprpc.InstantOutRequest
|
||||
60, // 74: looprpc.SwapClient.InstantOutQuote:input_type -> looprpc.InstantOutQuoteRequest
|
||||
62, // 75: looprpc.SwapClient.ListInstantOuts:input_type -> looprpc.ListInstantOutsRequest
|
||||
65, // 76: looprpc.SwapClient.NewStaticAddress:input_type -> looprpc.NewStaticAddressRequest
|
||||
67, // 77: looprpc.SwapClient.ListUnspentDeposits:input_type -> looprpc.ListUnspentDepositsRequest
|
||||
70, // 78: looprpc.SwapClient.WithdrawDeposits:input_type -> looprpc.WithdrawDepositsRequest
|
||||
72, // 79: looprpc.SwapClient.ListStaticAddressDeposits:input_type -> looprpc.ListStaticAddressDepositsRequest
|
||||
74, // 80: looprpc.SwapClient.ListStaticAddressWithdrawals:input_type -> looprpc.ListStaticAddressWithdrawalRequest
|
||||
76, // 81: looprpc.SwapClient.ListStaticAddressSwaps:input_type -> looprpc.ListStaticAddressSwapsRequest
|
||||
78, // 82: looprpc.SwapClient.GetStaticAddressSummary:input_type -> looprpc.StaticAddressSummaryRequest
|
||||
83, // 83: looprpc.SwapClient.StaticAddressLoopIn:input_type -> looprpc.StaticAddressLoopInRequest
|
||||
10, // 84: looprpc.SwapClient.StaticOpenChannel:input_type -> looprpc.StaticOpenChannelRequest
|
||||
16, // 85: looprpc.SwapClient.LoopOut:output_type -> looprpc.SwapResponse
|
||||
16, // 86: looprpc.SwapClient.LoopIn:output_type -> looprpc.SwapResponse
|
||||
18, // 87: looprpc.SwapClient.Monitor:output_type -> looprpc.SwapStatus
|
||||
21, // 88: looprpc.SwapClient.ListSwaps:output_type -> looprpc.ListSwapsResponse
|
||||
23, // 89: looprpc.SwapClient.SweepHtlc:output_type -> looprpc.SweepHtlcResponse
|
||||
18, // 90: looprpc.SwapClient.SwapInfo:output_type -> looprpc.SwapStatus
|
||||
54, // 91: looprpc.SwapClient.AbandonSwap:output_type -> looprpc.AbandonSwapResponse
|
||||
30, // 92: looprpc.SwapClient.LoopOutTerms:output_type -> looprpc.OutTermsResponse
|
||||
33, // 93: looprpc.SwapClient.LoopOutQuote:output_type -> looprpc.OutQuoteResponse
|
||||
29, // 94: looprpc.SwapClient.GetLoopInTerms:output_type -> looprpc.InTermsResponse
|
||||
32, // 95: looprpc.SwapClient.GetLoopInQuote:output_type -> looprpc.InQuoteResponse
|
||||
35, // 96: looprpc.SwapClient.Probe:output_type -> looprpc.ProbeResponse
|
||||
37, // 97: looprpc.SwapClient.GetL402Tokens:output_type -> looprpc.TokensResponse
|
||||
37, // 98: looprpc.SwapClient.GetLsatTokens:output_type -> looprpc.TokensResponse
|
||||
39, // 99: looprpc.SwapClient.FetchL402Token:output_type -> looprpc.FetchL402TokenResponse
|
||||
43, // 100: looprpc.SwapClient.GetInfo:output_type -> looprpc.GetInfoResponse
|
||||
13, // 101: looprpc.SwapClient.StopDaemon:output_type -> looprpc.StopDaemonResponse
|
||||
45, // 102: looprpc.SwapClient.GetLiquidityParams:output_type -> looprpc.LiquidityParameters
|
||||
49, // 103: looprpc.SwapClient.SetLiquidityParams:output_type -> looprpc.SetLiquidityParamsResponse
|
||||
52, // 104: looprpc.SwapClient.SuggestSwaps:output_type -> looprpc.SuggestSwapsResponse
|
||||
56, // 105: looprpc.SwapClient.ListReservations:output_type -> looprpc.ListReservationsResponse
|
||||
59, // 106: looprpc.SwapClient.InstantOut:output_type -> looprpc.InstantOutResponse
|
||||
61, // 107: looprpc.SwapClient.InstantOutQuote:output_type -> looprpc.InstantOutQuoteResponse
|
||||
63, // 108: looprpc.SwapClient.ListInstantOuts:output_type -> looprpc.ListInstantOutsResponse
|
||||
66, // 109: looprpc.SwapClient.NewStaticAddress:output_type -> looprpc.NewStaticAddressResponse
|
||||
68, // 110: looprpc.SwapClient.ListUnspentDeposits:output_type -> looprpc.ListUnspentDepositsResponse
|
||||
71, // 111: looprpc.SwapClient.WithdrawDeposits:output_type -> looprpc.WithdrawDepositsResponse
|
||||
73, // 112: looprpc.SwapClient.ListStaticAddressDeposits:output_type -> looprpc.ListStaticAddressDepositsResponse
|
||||
75, // 113: looprpc.SwapClient.ListStaticAddressWithdrawals:output_type -> looprpc.ListStaticAddressWithdrawalResponse
|
||||
77, // 114: looprpc.SwapClient.ListStaticAddressSwaps:output_type -> looprpc.ListStaticAddressSwapsResponse
|
||||
79, // 115: looprpc.SwapClient.GetStaticAddressSummary:output_type -> looprpc.StaticAddressSummaryResponse
|
||||
84, // 116: looprpc.SwapClient.StaticAddressLoopIn:output_type -> looprpc.StaticAddressLoopInResponse
|
||||
11, // 117: looprpc.SwapClient.StaticOpenChannel:output_type -> looprpc.StaticOpenChannelResponse
|
||||
85, // [85:118] is the sub-list for method output_type
|
||||
52, // [52:85] is the sub-list for method input_type
|
||||
52, // [52:52] is the sub-list for extension type_name
|
||||
52, // [52:52] is the sub-list for extension extendee
|
||||
0, // [0:52] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_client_proto_init() }
|
||||
|
|
@ -7443,6 +7482,9 @@ func file_client_proto_init() {
|
|||
if File_client_proto != nil {
|
||||
return
|
||||
}
|
||||
file_client_proto_msgTypes[8].OneofWrappers = []any{
|
||||
(*SwapStatus_StaticLoopInState)(nil),
|
||||
}
|
||||
file_client_proto_msgTypes[13].OneofWrappers = []any{
|
||||
(*SweepHtlcResponse_NotRequested)(nil),
|
||||
(*SweepHtlcResponse_Published)(nil),
|
||||
|
|
|
|||
|
|
@ -546,10 +546,17 @@ message SwapStatus {
|
|||
SwapType type = 3;
|
||||
|
||||
/*
|
||||
State the swap is currently in, see State enum.
|
||||
Generic loop-in/loop-out state for swaps.
|
||||
*/
|
||||
SwapState state = 4;
|
||||
|
||||
oneof static_loop_in_state_optional {
|
||||
/*
|
||||
Static address loop-in FSM state when type is STATIC_LOOP_IN.
|
||||
*/
|
||||
StaticAddressLoopInSwapState static_loop_in_state = 20;
|
||||
}
|
||||
|
||||
/*
|
||||
A failure reason for the swap, only set if the swap has failed.
|
||||
*/
|
||||
|
|
@ -608,6 +615,9 @@ enum SwapType {
|
|||
|
||||
// LOOP_IN indicates a loop in swap (on-chain to off-chain)
|
||||
LOOP_IN = 1;
|
||||
|
||||
// STATIC_LOOP_IN indicates a static address loop in swap.
|
||||
STATIC_LOOP_IN = 2;
|
||||
}
|
||||
|
||||
enum SwapState {
|
||||
|
|
|
|||
|
|
@ -3061,7 +3061,11 @@
|
|||
},
|
||||
"state": {
|
||||
"$ref": "#/definitions/looprpcSwapState",
|
||||
"description": "State the swap is currently in, see State enum."
|
||||
"description": "Generic loop-in/loop-out state for swaps."
|
||||
},
|
||||
"static_loop_in_state": {
|
||||
"$ref": "#/definitions/looprpcStaticAddressLoopInSwapState",
|
||||
"description": "Static address loop-in FSM state when type is STATIC_LOOP_IN."
|
||||
},
|
||||
"failure_reason": {
|
||||
"$ref": "#/definitions/looprpcFailureReason",
|
||||
|
|
@ -3131,10 +3135,11 @@
|
|||
"type": "string",
|
||||
"enum": [
|
||||
"LOOP_OUT",
|
||||
"LOOP_IN"
|
||||
"LOOP_IN",
|
||||
"STATIC_LOOP_IN"
|
||||
],
|
||||
"default": "LOOP_OUT",
|
||||
"title": "- LOOP_OUT: LOOP_OUT indicates an loop out swap (off-chain to on-chain)\n - LOOP_IN: LOOP_IN indicates a loop in swap (on-chain to off-chain)"
|
||||
"description": " - LOOP_OUT: LOOP_OUT indicates an loop out swap (off-chain to on-chain)\n - LOOP_IN: LOOP_IN indicates a loop in swap (on-chain to off-chain)\n - STATIC_LOOP_IN: STATIC_LOOP_IN indicates a static address loop in swap."
|
||||
},
|
||||
"looprpcSweepHtlcRequest": {
|
||||
"type": "object",
|
||||
|
|
|
|||
|
|
@ -341,6 +341,11 @@ func (f *FSM) InitHtlcAction(ctx context.Context,
|
|||
// Once the swap is stored, restart/recovery code owns invoice lifecycle.
|
||||
invoiceNeedsCleanup = false
|
||||
|
||||
err = f.sendUpdate(ctx)
|
||||
if err != nil {
|
||||
f.Errorf("Error sending loop-in update: %v", err)
|
||||
}
|
||||
|
||||
event = OnHtlcInitiated
|
||||
|
||||
return event
|
||||
|
|
|
|||
|
|
@ -878,6 +878,68 @@ func TestCheckDepositsAvailableRejectsDivergentDepositOutpoints(
|
|||
require.Empty(t, checker.outpoints)
|
||||
}
|
||||
|
||||
// TestInitHtlcActionIgnoresSendUpdateErrorAfterPersistence protects the
|
||||
// persistence-first invariant: once the loop-in is stored, a later status
|
||||
// update failure must not roll back the action or state transition.
|
||||
func TestInitHtlcActionIgnoresSendUpdateErrorAfterPersistence(t *testing.T) {
|
||||
mockLnd := test.NewMockLnd()
|
||||
_, serverKey := test.CreateKey(22)
|
||||
|
||||
server := &mockStaticAddressServer{
|
||||
response: testStaticAddressLoopInResponse(
|
||||
serverKey.SerializeCompressed(),
|
||||
),
|
||||
}
|
||||
|
||||
dep := &deposit.Deposit{
|
||||
OutPoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{2},
|
||||
Index: 0,
|
||||
},
|
||||
Value: 500_000,
|
||||
}
|
||||
|
||||
loopIn := &StaticAddressLoopIn{
|
||||
Deposits: []*deposit.Deposit{dep},
|
||||
DepositOutpoints: []string{dep.OutPoint.String()},
|
||||
SelectedAmount: dep.Value,
|
||||
QuotedSwapFee: 1_000,
|
||||
InitiationHeight: uint32(mockLnd.Height),
|
||||
InitiationTime: time.Now(),
|
||||
PaymentTimeoutSeconds: 3_600,
|
||||
}
|
||||
|
||||
sendUpdateErr := errors.New("status channel blocked")
|
||||
sendUpdateCalled := false
|
||||
f := &FSM{
|
||||
StateMachine: &fsm.StateMachine{},
|
||||
cfg: &Config{
|
||||
Server: server,
|
||||
DepositManager: &noopDepositManager{},
|
||||
LndClient: mockLnd.Client,
|
||||
WalletKit: mockLnd.WalletKit,
|
||||
ChainParams: mockLnd.ChainParams,
|
||||
Store: &mockStore{},
|
||||
ValidateLoopInContract: testValidateLoopInContract,
|
||||
MaxStaticAddrHtlcFeePercentage: 1,
|
||||
MaxStaticAddrHtlcBackupFeePercentage: 1,
|
||||
SendUpdate: func(context.Context,
|
||||
*StaticAddressLoopIn) error {
|
||||
|
||||
sendUpdateCalled = true
|
||||
|
||||
return sendUpdateErr
|
||||
},
|
||||
},
|
||||
loopIn: loopIn,
|
||||
}
|
||||
|
||||
event := f.InitHtlcAction(t.Context(), nil)
|
||||
require.Equal(t, OnHtlcInitiated, event)
|
||||
require.Nil(t, f.LastActionError)
|
||||
require.True(t, sendUpdateCalled)
|
||||
}
|
||||
|
||||
// mockStaticAddressServer captures static-address loop-in requests in tests.
|
||||
type mockStaticAddressServer struct {
|
||||
swapserverrpc.StaticAddressServerClient
|
||||
|
|
|
|||
|
|
@ -273,6 +273,22 @@ func (f *FSM) updateLoopIn(ctx context.Context, notification fsm.Notification) {
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
err = f.sendUpdate(ctx)
|
||||
if err != nil {
|
||||
f.Errorf("Error sending loop-in update: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// sendUpdate publishes the latest loop-in state after it has been persisted.
|
||||
// The callback must remain lightweight because it runs synchronously with FSM
|
||||
// state transitions.
|
||||
func (f *FSM) sendUpdate(ctx context.Context) error {
|
||||
if f.cfg.SendUpdate == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return f.cfg.SendUpdate(ctx, f.loopIn)
|
||||
}
|
||||
|
||||
// isUpdateSkipped returns true if the loop-in should not be updated for the
|
||||
|
|
|
|||
|
|
@ -98,6 +98,9 @@ type Config struct {
|
|||
// request.
|
||||
ValidateLoopInContract ValidateLoopInContract
|
||||
|
||||
// SendUpdate publishes a loop-in status update after it is persisted.
|
||||
SendUpdate func(context.Context, *StaticAddressLoopIn) error
|
||||
|
||||
// MaxStaticAddrHtlcFeePercentage is the percentage of the swap amount
|
||||
// that we allow the server to charge for the htlc transaction.
|
||||
// Although highly unlikely, this is a defense against the server
|
||||
|
|
|
|||
|
|
@ -245,6 +245,45 @@ func TestInitiateLoopInAllowsReservedAutoloopLabel(t *testing.T) {
|
|||
require.Equal(t, selectedDeposit.Value, quoteGetter.amount)
|
||||
}
|
||||
|
||||
// TestUpdateLoopInSendsUpdateAfterSuccessfulStoreUpdate protects the
|
||||
// notification contract: after a successful database update, the manager must
|
||||
// publish the stored loop-in state to listeners.
|
||||
func TestUpdateLoopInSendsUpdateAfterSuccessfulStoreUpdate(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
swapHash := lntypes.Hash{1, 2, 3}
|
||||
updates := make(chan *StaticAddressLoopIn, 1)
|
||||
loopIn := &StaticAddressLoopIn{SwapHash: swapHash}
|
||||
loopIn.SetState(SignHtlcTx)
|
||||
|
||||
loopInFsm := &FSM{
|
||||
cfg: &Config{
|
||||
Store: &mockStore{stored: true},
|
||||
SendUpdate: func(_ context.Context,
|
||||
updated *StaticAddressLoopIn) error {
|
||||
|
||||
updates <- updated
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
loopIn: loopIn,
|
||||
}
|
||||
|
||||
loopInFsm.updateLoopIn(ctx, fsm.Notification{
|
||||
PreviousState: SignHtlcTx,
|
||||
NextState: MonitorInvoiceAndHtlcTx,
|
||||
})
|
||||
|
||||
select {
|
||||
case updated := <-updates:
|
||||
require.Equal(t, swapHash, updated.SwapHash)
|
||||
require.Equal(t, MonitorInvoiceAndHtlcTx, updated.GetState())
|
||||
|
||||
case <-ctx.Done():
|
||||
t.Fatal(ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleLoopInSweepReqRejectsInvalidServerNonce ensures that a malformed
|
||||
// MuSig2 nonce returned by the server is rejected before it reaches the signer.
|
||||
func TestHandleLoopInSweepReqRejectsInvalidServerNonce(t *testing.T) {
|
||||
|
|
@ -501,6 +540,7 @@ type mockStore struct {
|
|||
swaps []*StaticAddressLoopIn
|
||||
loopIns map[lntypes.Hash]*StaticAddressLoopIn
|
||||
mapIDs map[lntypes.Hash][]deposit.ID
|
||||
stored bool
|
||||
}
|
||||
|
||||
func (s *mockStore) CreateLoopIn(_ context.Context,
|
||||
|
|
@ -521,7 +561,7 @@ func (s *mockStore) GetStaticAddressLoopInSwapsByStates(_ context.Context,
|
|||
return s.swaps, nil
|
||||
}
|
||||
func (s *mockStore) IsStored(_ context.Context, _ lntypes.Hash) (bool, error) {
|
||||
return false, nil
|
||||
return s.stored, nil
|
||||
}
|
||||
|
||||
// RecordStaticAddressRiskDecision implements Store for manager tests.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"database/sql"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
|
|
@ -23,6 +24,12 @@ import (
|
|||
|
||||
const OutpointSeparator = ";"
|
||||
|
||||
// sqlStoreUpdateTime returns the PostgreSQL-compatible timestamp precision used
|
||||
// for persisted loop-in update metadata.
|
||||
func sqlStoreUpdateTime(clock clock.Clock) time.Time {
|
||||
return clock.Now().Truncate(time.Microsecond)
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrInvalidOutpoint is returned when an outpoint contains the outpoint
|
||||
// separator.
|
||||
|
|
@ -288,13 +295,14 @@ func (s *SqlStore) CreateLoopIn(ctx context.Context,
|
|||
Fast: loopIn.Fast,
|
||||
}
|
||||
|
||||
updateTime := sqlStoreUpdateTime(s.clock)
|
||||
updateArgs := sqlc.InsertStaticAddressMetaUpdateParams{
|
||||
SwapHash: loopIn.SwapHash[:],
|
||||
UpdateTimestamp: s.clock.Now(),
|
||||
UpdateTimestamp: updateTime,
|
||||
UpdateState: string(loopIn.GetState()),
|
||||
}
|
||||
|
||||
return s.baseDB.ExecTx(ctx, loopdb.NewSqlWriteOpts(),
|
||||
err := s.baseDB.ExecTx(ctx, loopdb.NewSqlWriteOpts(),
|
||||
func(q Querier) error {
|
||||
err := q.InsertSwap(ctx, swapArgs)
|
||||
if err != nil {
|
||||
|
|
@ -331,6 +339,13 @@ func (s *SqlStore) CreateLoopIn(ctx context.Context,
|
|||
return q.InsertStaticAddressMetaUpdate(ctx, updateArgs)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
loopIn.LastUpdateTime = updateTime
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateLoopIn updates the loop-in in the database.
|
||||
|
|
@ -351,13 +366,14 @@ func (s *SqlStore) UpdateLoopIn(ctx context.Context,
|
|||
},
|
||||
}
|
||||
|
||||
updateTime := sqlStoreUpdateTime(s.clock)
|
||||
updateArgs := sqlc.InsertStaticAddressMetaUpdateParams{
|
||||
SwapHash: loopIn.SwapHash[:],
|
||||
UpdateState: string(loopIn.GetState()),
|
||||
UpdateTimestamp: s.clock.Now(),
|
||||
UpdateTimestamp: updateTime,
|
||||
}
|
||||
|
||||
return s.baseDB.ExecTx(ctx, loopdb.NewSqlWriteOpts(),
|
||||
err := s.baseDB.ExecTx(ctx, loopdb.NewSqlWriteOpts(),
|
||||
func(q Querier) error {
|
||||
err := q.UpdateStaticAddressLoopIn(ctx, updateParams)
|
||||
if err != nil {
|
||||
|
|
@ -367,6 +383,13 @@ func (s *SqlStore) UpdateLoopIn(ctx context.Context,
|
|||
return q.InsertStaticAddressMetaUpdate(ctx, updateArgs)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
loopIn.LastUpdateTime = updateTime
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RecordStaticAddressRiskDecision stores the server's confirmation-risk
|
||||
|
|
|
|||
|
|
@ -252,7 +252,9 @@ func TestCreateLoopIn(t *testing.T) {
|
|||
// Set up test context objects.
|
||||
ctx := t.Context()
|
||||
testDb := loopdb.NewTestDB(t)
|
||||
testClock := clock.NewTestClock(time.Now())
|
||||
createTime := time.Unix(1_717_171_717, 123_456_789).UTC()
|
||||
expectedCreateTime := createTime.Truncate(time.Microsecond)
|
||||
testClock := clock.NewTestClock(createTime)
|
||||
defer testDb.Close()
|
||||
|
||||
depositStore := deposit.NewSqlStore(testDb.BaseDB)
|
||||
|
|
@ -325,6 +327,7 @@ func TestCreateLoopIn(t *testing.T) {
|
|||
|
||||
err = swapStore.CreateLoopIn(ctx, &swapPending)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedCreateTime, swapPending.LastUpdateTime)
|
||||
|
||||
depositIDs, err := swapStore.DepositIDsForSwapHash(
|
||||
ctx, swapHashPending,
|
||||
|
|
@ -349,6 +352,7 @@ func TestCreateLoopIn(t *testing.T) {
|
|||
require.Equal(t, []string{d1.OutPoint.String(), d2.OutPoint.String()},
|
||||
swap.DepositOutpoints)
|
||||
require.Equal(t, SignHtlcTx, swap.GetState())
|
||||
require.Equal(t, swapPending.LastUpdateTime, swap.LastUpdateTime)
|
||||
require.Equal(
|
||||
t, ConfirmationRiskDecisionNone,
|
||||
swap.ConfirmationRiskDecision,
|
||||
|
|
@ -445,14 +449,15 @@ func TestCreateLoopIn(t *testing.T) {
|
|||
|
||||
err = swapStore.UpdateLoopIn(ctx, &swapPending)
|
||||
require.NoError(t, err)
|
||||
require.Equal(
|
||||
t, updateTime.Truncate(time.Microsecond),
|
||||
swapPending.LastUpdateTime,
|
||||
)
|
||||
|
||||
swap, err = swapStore.GetLoopInByHash(ctx, swapHashPending)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, Succeeded, swap.GetState())
|
||||
require.WithinDuration(
|
||||
t, updateTime.UTC(), swap.LastUpdateTime.UTC(),
|
||||
time.Microsecond,
|
||||
)
|
||||
require.Equal(t, swapPending.LastUpdateTime, swap.LastUpdateTime)
|
||||
}
|
||||
|
||||
// TestGetLoopInByHashOrdersDepositsBySnapshot ensures recovered deposits are
|
||||
|
|
|
|||
|
|
@ -9,10 +9,13 @@ const (
|
|||
|
||||
// TypeOut is a loop out swap.
|
||||
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
|
||||
// swap.
|
||||
// IsOut returns true only if the swap is TypeOut; TypeIn and
|
||||
// TypeStaticAddressLoopIn both return false.
|
||||
func (t Type) IsOut() bool {
|
||||
return t == TypeOut
|
||||
}
|
||||
|
|
@ -23,6 +26,8 @@ func (t Type) String() string {
|
|||
return "In"
|
||||
case TypeOut:
|
||||
return "Out"
|
||||
case TypeStaticAddressLoopIn:
|
||||
return "StaticAddressLoopIn"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue