Skip to content

Commit 6a62cd0

Browse files
authored
Merge pull request #698 from sputn1ck/io_reservation_expiry
2 parents 8ed8274 + a7ab998 commit 6a62cd0

File tree

4 files changed

+74
-17
lines changed

4 files changed

+74
-17
lines changed

fsm/fsm.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ var (
1313
ErrWaitForStateTimedOut = errors.New(
1414
"timed out while waiting for event",
1515
)
16-
ErrInvalidContextType = errors.New("invalid context")
16+
ErrInvalidContextType = errors.New("invalid context")
17+
ErrWaitingForStateEarlyAbortError = errors.New(
18+
"waiting for state early abort",
19+
)
1720
)
1821

1922
const (
@@ -73,6 +76,8 @@ type Notification struct {
7376
NextState StateType
7477
// Event is the event that was processed.
7578
Event EventType
79+
// LastActionError is the error returned by the last action executed.
80+
LastActionError error
7681
}
7782

7883
// Observer is an interface that can be implemented by types that want to
@@ -214,9 +219,10 @@ func (s *StateMachine) SendEvent(event EventType, eventCtx EventContext) error {
214219
// Notify the state machine's observers.
215220
s.observerMutex.Lock()
216221
notification := Notification{
217-
PreviousState: s.previous,
218-
NextState: s.current,
219-
Event: event,
222+
PreviousState: s.previous,
223+
NextState: s.current,
224+
Event: event,
225+
LastActionError: s.LastActionError,
220226
}
221227

222228
for _, observer := range s.observers {

fsm/observer.go

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ type WaitForStateOption interface {
5555
// fsmOptions is a struct that holds all options that can be passed to the
5656
// WaitForState function.
5757
type fsmOptions struct {
58-
initialWait time.Duration
58+
initialWait time.Duration
59+
abortEarlyOnError bool
5960
}
6061

6162
// InitialWaitOption is an option that can be passed to the WaitForState
@@ -76,6 +77,24 @@ func (w *InitialWaitOption) apply(o *fsmOptions) {
7677
o.initialWait = w.initialWait
7778
}
7879

80+
// AbortEarlyOnErrorOption is an option that can be passed to the WaitForState
81+
// function to abort early if an error occurs.
82+
type AbortEarlyOnErrorOption struct {
83+
abortEarlyOnError bool
84+
}
85+
86+
// apply implements the WaitForStateOption interface.
87+
func (a *AbortEarlyOnErrorOption) apply(o *fsmOptions) {
88+
o.abortEarlyOnError = a.abortEarlyOnError
89+
}
90+
91+
// WithAbortEarlyOnErrorOption creates a new AbortEarlyOnErrorOption.
92+
func WithAbortEarlyOnErrorOption() WaitForStateOption {
93+
return &AbortEarlyOnErrorOption{
94+
abortEarlyOnError: true,
95+
}
96+
}
97+
7998
// WaitForState waits for the state machine to reach the given state.
8099
// If the optional initialWait parameter is set, the function will wait for
81100
// the given duration before checking the state. This is useful if the
@@ -105,7 +124,8 @@ func (s *CachedObserver) WaitForState(ctx context.Context,
105124
defer cancel()
106125

107126
// Channel to notify when the desired state is reached
108-
ch := make(chan struct{})
127+
// or an error occurred.
128+
ch := make(chan error)
109129

110130
// Goroutine to wait on condition variable
111131
go func() {
@@ -115,8 +135,26 @@ func (s *CachedObserver) WaitForState(ctx context.Context,
115135
for {
116136
// Check if the last state is the desired state
117137
if s.lastNotification.NextState == state {
118-
ch <- struct{}{}
119-
return
138+
select {
139+
case <-timeoutCtx.Done():
140+
return
141+
142+
case ch <- nil:
143+
return
144+
}
145+
}
146+
147+
// Check if an error occurred
148+
if s.lastNotification.Event == OnError {
149+
if options.abortEarlyOnError {
150+
select {
151+
case <-timeoutCtx.Done():
152+
return
153+
154+
case ch <- s.lastNotification.LastActionError:
155+
return
156+
}
157+
}
120158
}
121159

122160
// Otherwise, wait for the next notification
@@ -130,7 +168,11 @@ func (s *CachedObserver) WaitForState(ctx context.Context,
130168
return NewErrWaitingForStateTimeout(
131169
state, s.lastNotification.NextState,
132170
)
133-
case <-ch:
171+
172+
case lastActionErr := <-ch:
173+
if lastActionErr != nil {
174+
return lastActionErr
175+
}
134176
return nil
135177
}
136178
}

instantout/actions.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121
"github.com/lightningnetwork/lnd/lntypes"
2222
)
2323

24-
var (
24+
const (
2525
// Define route independent max routing fees. We have currently no way
2626
// to get a reliable estimate of the routing fees. Best we can do is
2727
// the minimum routing fees, which is not very indicative.
@@ -46,6 +46,10 @@ var (
4646
// defaultPollPaymentTime is the default time to poll the server for the
4747
// payment status.
4848
defaultPollPaymentTime = time.Second * 15
49+
50+
// htlcExpiryDelta is the delta in blocks we require between the htlc
51+
// expiry and reservation expiry.
52+
htlcExpiryDelta = int32(40)
4953
)
5054

5155
// InitInstantOutCtx contains the context for the InitInstantOutAction.
@@ -96,6 +100,15 @@ func (f *FSM) InitInstantOutAction(eventCtx fsm.EventContext) fsm.EventType {
96100
reservationAmt += uint64(res.Value)
97101
reservationIds = append(reservationIds, resId[:])
98102
reservations = append(reservations, res)
103+
104+
// Check that the reservation expiry is larger than the cltv
105+
// expiry of the swap, with an additional delta to allow for
106+
// preimage reveal.
107+
if int32(res.Expiry) < initCtx.cltvExpiry+htlcExpiryDelta {
108+
return f.HandleError(fmt.Errorf("reservation %x has "+
109+
"expiry %v which is less than the swap expiry %v",
110+
resId, res.Expiry, initCtx.cltvExpiry))
111+
}
99112
}
100113

101114
// Create the preimage for the swap.

instantout/manager.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"time"
99

1010
"github.com/btcsuite/btcd/btcutil"
11+
"github.com/lightninglabs/loop/fsm"
1112
"github.com/lightninglabs/loop/instantout/reservation"
1213
looprpc "github.com/lightninglabs/loop/swapserverrpc"
1314
"github.com/lightningnetwork/lnd/lntypes"
@@ -169,15 +170,10 @@ func (m *Manager) NewInstantOut(ctx context.Context,
169170
// waiting for sweepless sweep to be confirmed.
170171
err = instantOut.DefaultObserver.WaitForState(
171172
ctx, defaultStateWaitTime, WaitForSweeplessSweepConfirmed,
173+
fsm.WithAbortEarlyOnErrorOption(),
172174
)
173175
if err != nil {
174-
if instantOut.LastActionError != nil {
175-
return instantOut, fmt.Errorf(
176-
"error waiting for sweepless sweep "+
177-
"confirmed: %w", instantOut.LastActionError,
178-
)
179-
}
180-
return instantOut, nil
176+
return nil, err
181177
}
182178

183179
return instantOut, nil

0 commit comments

Comments
 (0)