Merge pull request #698 from sputn1ck/io_reservation_expiry

pull/702/head
Konstantin Nick 3 months ago committed by GitHub
commit 6a62cd02d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -13,7 +13,10 @@ var (
ErrWaitForStateTimedOut = errors.New( ErrWaitForStateTimedOut = errors.New(
"timed out while waiting for event", "timed out while waiting for event",
) )
ErrInvalidContextType = errors.New("invalid context") ErrInvalidContextType = errors.New("invalid context")
ErrWaitingForStateEarlyAbortError = errors.New(
"waiting for state early abort",
)
) )
const ( const (
@ -73,6 +76,8 @@ type Notification struct {
NextState StateType NextState StateType
// Event is the event that was processed. // Event is the event that was processed.
Event EventType Event EventType
// LastActionError is the error returned by the last action executed.
LastActionError error
} }
// Observer is an interface that can be implemented by types that want to // 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 {
// Notify the state machine's observers. // Notify the state machine's observers.
s.observerMutex.Lock() s.observerMutex.Lock()
notification := Notification{ notification := Notification{
PreviousState: s.previous, PreviousState: s.previous,
NextState: s.current, NextState: s.current,
Event: event, Event: event,
LastActionError: s.LastActionError,
} }
for _, observer := range s.observers { for _, observer := range s.observers {

@ -55,7 +55,8 @@ type WaitForStateOption interface {
// fsmOptions is a struct that holds all options that can be passed to the // fsmOptions is a struct that holds all options that can be passed to the
// WaitForState function. // WaitForState function.
type fsmOptions struct { type fsmOptions struct {
initialWait time.Duration initialWait time.Duration
abortEarlyOnError bool
} }
// InitialWaitOption is an option that can be passed to the WaitForState // InitialWaitOption is an option that can be passed to the WaitForState
@ -76,6 +77,24 @@ func (w *InitialWaitOption) apply(o *fsmOptions) {
o.initialWait = w.initialWait o.initialWait = w.initialWait
} }
// AbortEarlyOnErrorOption is an option that can be passed to the WaitForState
// function to abort early if an error occurs.
type AbortEarlyOnErrorOption struct {
abortEarlyOnError bool
}
// apply implements the WaitForStateOption interface.
func (a *AbortEarlyOnErrorOption) apply(o *fsmOptions) {
o.abortEarlyOnError = a.abortEarlyOnError
}
// WithAbortEarlyOnErrorOption creates a new AbortEarlyOnErrorOption.
func WithAbortEarlyOnErrorOption() WaitForStateOption {
return &AbortEarlyOnErrorOption{
abortEarlyOnError: true,
}
}
// WaitForState waits for the state machine to reach the given state. // WaitForState waits for the state machine to reach the given state.
// If the optional initialWait parameter is set, the function will wait for // If the optional initialWait parameter is set, the function will wait for
// the given duration before checking the state. This is useful if the // the given duration before checking the state. This is useful if the
@ -105,7 +124,8 @@ func (s *CachedObserver) WaitForState(ctx context.Context,
defer cancel() defer cancel()
// Channel to notify when the desired state is reached // Channel to notify when the desired state is reached
ch := make(chan struct{}) // or an error occurred.
ch := make(chan error)
// Goroutine to wait on condition variable // Goroutine to wait on condition variable
go func() { go func() {
@ -115,8 +135,26 @@ func (s *CachedObserver) WaitForState(ctx context.Context,
for { for {
// Check if the last state is the desired state // Check if the last state is the desired state
if s.lastNotification.NextState == state { if s.lastNotification.NextState == state {
ch <- struct{}{} select {
return case <-timeoutCtx.Done():
return
case ch <- nil:
return
}
}
// Check if an error occurred
if s.lastNotification.Event == OnError {
if options.abortEarlyOnError {
select {
case <-timeoutCtx.Done():
return
case ch <- s.lastNotification.LastActionError:
return
}
}
} }
// Otherwise, wait for the next notification // Otherwise, wait for the next notification
@ -130,7 +168,11 @@ func (s *CachedObserver) WaitForState(ctx context.Context,
return NewErrWaitingForStateTimeout( return NewErrWaitingForStateTimeout(
state, s.lastNotification.NextState, state, s.lastNotification.NextState,
) )
case <-ch:
case lastActionErr := <-ch:
if lastActionErr != nil {
return lastActionErr
}
return nil return nil
} }
} }

@ -21,7 +21,7 @@ import (
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
) )
var ( const (
// Define route independent max routing fees. We have currently no way // Define route independent max routing fees. We have currently no way
// to get a reliable estimate of the routing fees. Best we can do is // to get a reliable estimate of the routing fees. Best we can do is
// the minimum routing fees, which is not very indicative. // the minimum routing fees, which is not very indicative.
@ -46,6 +46,10 @@ var (
// defaultPollPaymentTime is the default time to poll the server for the // defaultPollPaymentTime is the default time to poll the server for the
// payment status. // payment status.
defaultPollPaymentTime = time.Second * 15 defaultPollPaymentTime = time.Second * 15
// htlcExpiryDelta is the delta in blocks we require between the htlc
// expiry and reservation expiry.
htlcExpiryDelta = int32(40)
) )
// InitInstantOutCtx contains the context for the InitInstantOutAction. // InitInstantOutCtx contains the context for the InitInstantOutAction.
@ -96,6 +100,15 @@ func (f *FSM) InitInstantOutAction(eventCtx fsm.EventContext) fsm.EventType {
reservationAmt += uint64(res.Value) reservationAmt += uint64(res.Value)
reservationIds = append(reservationIds, resId[:]) reservationIds = append(reservationIds, resId[:])
reservations = append(reservations, res) reservations = append(reservations, res)
// Check that the reservation expiry is larger than the cltv
// expiry of the swap, with an additional delta to allow for
// preimage reveal.
if int32(res.Expiry) < initCtx.cltvExpiry+htlcExpiryDelta {
return f.HandleError(fmt.Errorf("reservation %x has "+
"expiry %v which is less than the swap expiry %v",
resId, res.Expiry, initCtx.cltvExpiry))
}
} }
// Create the preimage for the swap. // Create the preimage for the swap.

@ -8,6 +8,7 @@ import (
"time" "time"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/lightninglabs/loop/fsm"
"github.com/lightninglabs/loop/instantout/reservation" "github.com/lightninglabs/loop/instantout/reservation"
looprpc "github.com/lightninglabs/loop/swapserverrpc" looprpc "github.com/lightninglabs/loop/swapserverrpc"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
@ -169,15 +170,10 @@ func (m *Manager) NewInstantOut(ctx context.Context,
// waiting for sweepless sweep to be confirmed. // waiting for sweepless sweep to be confirmed.
err = instantOut.DefaultObserver.WaitForState( err = instantOut.DefaultObserver.WaitForState(
ctx, defaultStateWaitTime, WaitForSweeplessSweepConfirmed, ctx, defaultStateWaitTime, WaitForSweeplessSweepConfirmed,
fsm.WithAbortEarlyOnErrorOption(),
) )
if err != nil { if err != nil {
if instantOut.LastActionError != nil { return nil, err
return instantOut, fmt.Errorf(
"error waiting for sweepless sweep "+
"confirmed: %w", instantOut.LastActionError,
)
}
return instantOut, nil
} }
return instantOut, nil return instantOut, nil

Loading…
Cancel
Save