mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
reservation: reject duplicate reservation entries
Check active and persisted reservations before creating a new state machine, preserving the existing reservation when a duplicate arrives.
This commit is contained in:
parent
2989ceee42
commit
41b884f610
3 changed files with 56 additions and 1 deletions
|
|
@ -2,6 +2,7 @@ package reservation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -111,13 +112,28 @@ func (m *Manager) newReservation(ctx context.Context, currentHeight uint32,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = m.cfg.Store.GetReservation(ctx, reservationID)
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
return nil, ErrReservationAlreadyExists
|
||||||
|
|
||||||
|
case !errors.Is(err, ErrReservationNotFound):
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// Create the reservation state machine. We need to pass in the runCtx
|
// Create the reservation state machine. We need to pass in the runCtx
|
||||||
// of the reservation manager so that the state machine will keep on
|
// of the reservation manager so that the state machine will keep on
|
||||||
// running even if the grpc conte
|
// running even if the grpc conte
|
||||||
reservationFSM := NewFSM(m.cfg)
|
reservationFSM := NewFSM(m.cfg)
|
||||||
|
|
||||||
// Add the reservation to the active reservations map.
|
// Add the reservation to the active reservations map. Check the map while
|
||||||
|
// holding the lock as concurrent callers may both have completed the store
|
||||||
|
// lookup above.
|
||||||
m.Lock()
|
m.Lock()
|
||||||
|
if _, ok := m.activeReservations[reservationID]; ok {
|
||||||
|
m.Unlock()
|
||||||
|
return nil, ErrReservationAlreadyExists
|
||||||
|
}
|
||||||
m.activeReservations[reservationID] = reservationFSM
|
m.activeReservations[reservationID] = reservationFSM
|
||||||
m.Unlock()
|
m.Unlock()
|
||||||
|
|
||||||
|
|
@ -146,6 +162,12 @@ func (m *Manager) newReservation(ctx context.Context, currentHeight uint32,
|
||||||
fsm.WithWaitForStateOption(time.Second),
|
fsm.WithWaitForStateOption(time.Second),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
m.Lock()
|
||||||
|
if m.activeReservations[reservationID] == reservationFSM {
|
||||||
|
delete(m.activeReservations, reservationID)
|
||||||
|
}
|
||||||
|
m.Unlock()
|
||||||
|
|
||||||
if reservationFSM.LastActionError != nil {
|
if reservationFSM.LastActionError != nil {
|
||||||
return nil, fmt.Errorf("error waiting for "+
|
return nil, fmt.Errorf("error waiting for "+
|
||||||
"state: %v, last action error: %v",
|
"state: %v, last action error: %v",
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,35 @@ func TestManagerContinuesAfterInvalidNotification(t *testing.T) {
|
||||||
require.NoError(t, <-errChan)
|
require.NoError(t, <-errChan)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestManagerRejectsDuplicateReservation verifies that a duplicate server
|
||||||
|
// notification cannot replace the active FSM for an existing reservation.
|
||||||
|
func TestManagerRejectsDuplicateReservation(t *testing.T) {
|
||||||
|
testContext := newManagerTestContext(t)
|
||||||
|
ctx := t.Context()
|
||||||
|
req := &swapserverrpc.ServerReservationNotification{
|
||||||
|
ReservationId: defaultReservationId[:],
|
||||||
|
Value: uint64(defaultValue),
|
||||||
|
ServerKey: defaultPubkeyBytes,
|
||||||
|
Expiry: uint32(testContext.mockLnd.Height) +
|
||||||
|
defaultExpiry,
|
||||||
|
}
|
||||||
|
|
||||||
|
firstFSM, err := testContext.manager.newReservation(
|
||||||
|
ctx, uint32(testContext.mockLnd.Height), req,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
secondFSM, err := testContext.manager.newReservation(
|
||||||
|
ctx, uint32(testContext.mockLnd.Height), req,
|
||||||
|
)
|
||||||
|
require.ErrorIs(t, err, ErrReservationAlreadyExists)
|
||||||
|
require.Nil(t, secondFSM)
|
||||||
|
require.Same(
|
||||||
|
t, firstFSM,
|
||||||
|
testContext.manager.activeReservations[defaultReservationId],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// ManagerTestContext is a helper struct that contains all the necessary
|
// ManagerTestContext is a helper struct that contains all the necessary
|
||||||
// components to test the reservation manager.
|
// components to test the reservation manager.
|
||||||
type ManagerTestContext struct {
|
type ManagerTestContext struct {
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,10 @@ func (r *SQLStore) GetReservation(ctx context.Context,
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, ErrReservationNotFound
|
||||||
|
}
|
||||||
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue