From 228bf6a9417e39874738d8dcc155604d62e21b2d Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Fri, 25 Aug 2023 01:36:53 +0200 Subject: [PATCH 01/13] reservation: add reservation fsm and actions This commit adds the reservation state machine and actions to the reservation package. --- instantout/reservation/actions.go | 176 ++++++++++++++++++++ instantout/reservation/fsm.go | 229 ++++++++++++++++++++++++++ instantout/reservation/interfaces.go | 33 ++++ instantout/reservation/log.go | 26 +++ instantout/reservation/reservation.go | 137 +++++++++++++++ 5 files changed, 601 insertions(+) create mode 100644 instantout/reservation/actions.go create mode 100644 instantout/reservation/fsm.go create mode 100644 instantout/reservation/interfaces.go create mode 100644 instantout/reservation/log.go create mode 100644 instantout/reservation/reservation.go diff --git a/instantout/reservation/actions.go b/instantout/reservation/actions.go new file mode 100644 index 0000000..3d0965b --- /dev/null +++ b/instantout/reservation/actions.go @@ -0,0 +1,176 @@ +package reservation + +import ( + "context" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/lightninglabs/loop/fsm" + looprpc "github.com/lightninglabs/loop/swapserverrpc" +) + +// InitReservationContext contains the request parameters for a reservation. +type InitReservationContext struct { + reservationID ID + serverPubkey *btcec.PublicKey + value btcutil.Amount + expiry uint32 + heightHint uint32 +} + +// InitAction is the action that is executed when the reservation state machine +// is initialized. It creates the reservation in the database and dispatches the +// payment to the server. +func (r *FSM) InitAction(eventCtx fsm.EventContext) fsm.EventType { + // Check if the context is of the correct type. + reservationRequest, ok := eventCtx.(*InitReservationContext) + if !ok { + return r.HandleError(fsm.ErrInvalidContextType) + } + + keyRes, err := r.cfg.Wallet.DeriveNextKey( + r.ctx, KeyFamily, + ) + if err != nil { + return r.HandleError(err) + } + + // Send the client reservation details to the server. + log.Debugf("Dispatching reservation to server: %x", + reservationRequest.reservationID) + + request := &looprpc.ServerOpenReservationRequest{ + ReservationId: reservationRequest.reservationID[:], + ClientKey: keyRes.PubKey.SerializeCompressed(), + } + + _, err = r.cfg.ReservationClient.OpenReservation(r.ctx, request) + if err != nil { + return r.HandleError(err) + } + + reservation, err := NewReservation( + reservationRequest.reservationID, + reservationRequest.serverPubkey, + keyRes.PubKey, + reservationRequest.value, + reservationRequest.expiry, + reservationRequest.heightHint, + keyRes.KeyLocator, + ) + if err != nil { + return r.HandleError(err) + } + + r.reservation = reservation + + // Create the reservation in the database. + err = r.cfg.Store.CreateReservation(r.ctx, reservation) + if err != nil { + return r.HandleError(err) + } + + return OnBroadcast +} + +// SubscribeToConfirmationAction is the action that is executed when the +// reservation is waiting for confirmation. It subscribes to the confirmation +// of the reservation transaction. +func (r *FSM) SubscribeToConfirmationAction(_ fsm.EventContext) fsm.EventType { + pkscript, err := r.reservation.GetPkScript() + if err != nil { + return r.HandleError(err) + } + + callCtx, cancel := context.WithCancel(r.ctx) + defer cancel() + + // Subscribe to the confirmation of the reservation transaction. + log.Debugf("Subscribing to conf for reservation: %x pkscript: %x, "+ + "initiation height: %v", r.reservation.ID, pkscript, + r.reservation.InitiationHeight) + + confChan, errConfChan, err := r.cfg.ChainNotifier.RegisterConfirmationsNtfn( + callCtx, nil, pkscript, DefaultConfTarget, + r.reservation.InitiationHeight, + ) + if err != nil { + r.Errorf("unable to subscribe to conf notification: %v", err) + return r.HandleError(err) + } + + blockChan, errBlockChan, err := r.cfg.ChainNotifier.RegisterBlockEpochNtfn( + callCtx, + ) + if err != nil { + r.Errorf("unable to subscribe to block notifications: %v", err) + return r.HandleError(err) + } + + // We'll now wait for the confirmation of the reservation transaction. + for { + select { + case err := <-errConfChan: + r.Errorf("conf subscription error: %v", err) + return r.HandleError(err) + + case err := <-errBlockChan: + r.Errorf("block subscription error: %v", err) + return r.HandleError(err) + + case confInfo := <-confChan: + r.Debugf("reservation confirmed: %v", confInfo) + outpoint, err := r.reservation.findReservationOutput( + confInfo.Tx, + ) + if err != nil { + return r.HandleError(err) + } + + r.reservation.ConfirmationHeight = confInfo.BlockHeight + r.reservation.Outpoint = outpoint + + return OnConfirmed + + case block := <-blockChan: + r.Debugf("block received: %v expiry: %v", block, + r.reservation.Expiry) + + if uint32(block) >= r.reservation.Expiry { + return OnTimedOut + } + + case <-r.ctx.Done(): + return fsm.NoOp + } + } +} + +// ReservationConfirmedAction waits for the reservation to be either expired or +// waits for other actions to happen. +func (r *FSM) ReservationConfirmedAction(_ fsm.EventContext) fsm.EventType { + blockHeightChan, errEpochChan, err := r.cfg.ChainNotifier. + RegisterBlockEpochNtfn(r.ctx) + if err != nil { + return r.HandleError(err) + } + + for { + select { + case err := <-errEpochChan: + return r.HandleError(err) + + case blockHeight := <-blockHeightChan: + expired := blockHeight >= int32(r.reservation.Expiry) + if expired { + r.Debugf("Reservation %v expired", + r.reservation.ID) + + return OnTimedOut + } + + case <-r.ctx.Done(): + return fsm.NoOp + } + } +} diff --git a/instantout/reservation/fsm.go b/instantout/reservation/fsm.go new file mode 100644 index 0000000..3bd0820 --- /dev/null +++ b/instantout/reservation/fsm.go @@ -0,0 +1,229 @@ +package reservation + +import ( + "context" + + "github.com/lightninglabs/lndclient" + "github.com/lightninglabs/loop/fsm" + looprpc "github.com/lightninglabs/loop/swapserverrpc" +) + +const ( + // defaultObserverSize is the size of the fsm observer channel. + defaultObserverSize = 15 +) + +// Config contains all the services that the reservation FSM needs to operate. +type Config struct { + // Store is the database store for the reservations. + Store Store + + // Wallet handles the key derivation for the reservation. + Wallet lndclient.WalletKitClient + + // ChainNotifier is used to subscribe to block notifications. + ChainNotifier lndclient.ChainNotifierClient + + // ReservationClient is the client used to communicate with the + // swap server. + ReservationClient looprpc.ReservationServiceClient + + // FetchL402 is the function used to fetch the l402 token. + FetchL402 func(context.Context) error +} + +// FSM is the state machine that manages the reservation lifecycle. +type FSM struct { + *fsm.StateMachine + + cfg *Config + + reservation *Reservation + + ctx context.Context +} + +// NewFSM creates a new reservation FSM. +func NewFSM(ctx context.Context, cfg *Config) *FSM { + reservation := &Reservation{ + State: fsm.EmptyState, + } + + return NewFSMFromReservation(ctx, cfg, reservation) +} + +// NewFSMFromReservation creates a new reservation FSM from an existing +// reservation recovered from the database. +func NewFSMFromReservation(ctx context.Context, cfg *Config, + reservation *Reservation) *FSM { + + reservationFsm := &FSM{ + ctx: ctx, + cfg: cfg, + reservation: reservation, + } + + reservationFsm.StateMachine = fsm.NewStateMachineWithState( + reservationFsm.GetReservationStates(), reservation.State, + defaultObserverSize, + ) + reservationFsm.ActionEntryFunc = reservationFsm.updateReservation + + return reservationFsm +} + +// States. +var ( + // Init is the initial state of the reservation. + Init = fsm.StateType("Init") + + // WaitForConfirmation is the state where we wait for the reservation + // tx to be confirmed. + WaitForConfirmation = fsm.StateType("WaitForConfirmation") + + // Confirmed is the state where the reservation tx has been confirmed. + Confirmed = fsm.StateType("Confirmed") + + // TimedOut is the state where the reservation has timed out. + TimedOut = fsm.StateType("TimedOut") + + // Failed is the state where the reservation has failed. + Failed = fsm.StateType("Failed") + + // Spent is the state where a spend tx has been confirmed. + Spent = fsm.StateType("Spent") + + // Locked is the state where the reservation is locked and can't be + // used for instant out swaps. + Locked = fsm.StateType("Locked") +) + +// Events. +var ( + // OnServerRequest is the event that is triggered when the server + // requests a new reservation. + OnServerRequest = fsm.EventType("OnServerRequest") + + // OnBroadcast is the event that is triggered when the reservation tx + // has been broadcast. + OnBroadcast = fsm.EventType("OnBroadcast") + + // OnConfirmed is the event that is triggered when the reservation tx + // has been confirmed. + OnConfirmed = fsm.EventType("OnConfirmed") + + // OnTimedOut is the event that is triggered when the reservation has + // timed out. + OnTimedOut = fsm.EventType("OnTimedOut") + + // OnSwept is the event that is triggered when the reservation has been + // swept by the server. + OnSwept = fsm.EventType("OnSwept") + + // OnRecover is the event that is triggered when the reservation FSM + // recovers from a restart. + OnRecover = fsm.EventType("OnRecover") +) + +// GetReservationStates returns the statemap that defines the reservation +// state machine. +func (f *FSM) GetReservationStates() fsm.States { + return fsm.States{ + fsm.EmptyState: fsm.State{ + Transitions: fsm.Transitions{ + OnServerRequest: Init, + }, + Action: nil, + }, + Init: fsm.State{ + Transitions: fsm.Transitions{ + OnBroadcast: WaitForConfirmation, + OnRecover: Failed, + fsm.OnError: Failed, + }, + Action: f.InitAction, + }, + WaitForConfirmation: fsm.State{ + Transitions: fsm.Transitions{ + OnRecover: WaitForConfirmation, + OnConfirmed: Confirmed, + OnTimedOut: TimedOut, + }, + Action: f.SubscribeToConfirmationAction, + }, + Confirmed: fsm.State{ + Transitions: fsm.Transitions{ + OnTimedOut: TimedOut, + OnRecover: Confirmed, + }, + Action: f.ReservationConfirmedAction, + }, + TimedOut: fsm.State{ + Action: fsm.NoOpAction, + }, + Failed: fsm.State{ + Action: fsm.NoOpAction, + }, + } +} + +// updateReservation updates the reservation in the database. This function +// is called after every new state transition. +func (r *FSM) updateReservation(notification fsm.Notification) { + if r.reservation == nil { + return + } + + r.Debugf( + "NextState: %v, PreviousState: %v, Event: %v", + notification.NextState, notification.PreviousState, + notification.Event, + ) + + r.reservation.State = notification.NextState + + // Don't update the reservation if we are in an initial state or if we + // are transitioning from an initial state to a failed state. + if r.reservation.State == fsm.EmptyState || + r.reservation.State == Init || + (notification.PreviousState == Init && + r.reservation.State == Failed) { + + return + } + + err := r.cfg.Store.UpdateReservation(r.ctx, r.reservation) + if err != nil { + r.Errorf("unable to update reservation: %v", err) + } +} + +func (r *FSM) Infof(format string, args ...interface{}) { + log.Infof( + "Reservation %x: "+format, + append([]interface{}{r.reservation.ID}, args...)..., + ) +} + +func (r *FSM) Debugf(format string, args ...interface{}) { + log.Debugf( + "Reservation %x: "+format, + append([]interface{}{r.reservation.ID}, args...)..., + ) +} + +func (r *FSM) Errorf(format string, args ...interface{}) { + log.Errorf( + "Reservation %x: "+format, + append([]interface{}{r.reservation.ID}, args...)..., + ) +} + +// isFinalState returns true if the state is a final state. +func isFinalState(state fsm.StateType) bool { + switch state { + case Failed, TimedOut, Spent: + return true + } + return false +} diff --git a/instantout/reservation/interfaces.go b/instantout/reservation/interfaces.go new file mode 100644 index 0000000..c999d1b --- /dev/null +++ b/instantout/reservation/interfaces.go @@ -0,0 +1,33 @@ +package reservation + +import ( + "context" + "fmt" +) + +var ( + ErrReservationAlreadyExists = fmt.Errorf("reservation already exists") + ErrReservationNotFound = fmt.Errorf("reservation not found") +) + +const ( + KeyFamily = int32(42068) + DefaultConfTarget = int32(3) + IdLength = 32 +) + +// Store is the interface that stores the reservations. +type Store interface { + // CreateReservation stores the reservation in the database. + CreateReservation(ctx context.Context, reservation *Reservation) error + + // UpdateReservation updates the reservation in the database. + UpdateReservation(ctx context.Context, reservation *Reservation) error + + // GetReservation retrieves the reservation from the database. + GetReservation(ctx context.Context, id ID) (*Reservation, error) + + // ListReservations lists all existing reservations the client has ever + // made. + ListReservations(ctx context.Context) ([]*Reservation, error) +} diff --git a/instantout/reservation/log.go b/instantout/reservation/log.go new file mode 100644 index 0000000..c7f818f --- /dev/null +++ b/instantout/reservation/log.go @@ -0,0 +1,26 @@ +package reservation + +import ( + "github.com/btcsuite/btclog" + "github.com/lightningnetwork/lnd/build" +) + +// Subsystem defines the sub system name of this package. +const Subsystem = "RSRV" + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log btclog.Logger + +// The default amount of logging is none. +func init() { + UseLogger(build.NewSubLogger(Subsystem, nil)) +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/instantout/reservation/reservation.go b/instantout/reservation/reservation.go new file mode 100644 index 0000000..f220866 --- /dev/null +++ b/instantout/reservation/reservation.go @@ -0,0 +1,137 @@ +package reservation + +import ( + "bytes" + "errors" + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/loop/fsm" + reservation_script "github.com/lightninglabs/loop/instantout/reservation/script" + "github.com/lightningnetwork/lnd/keychain" +) + +// ID is a unique identifier for a reservation. +type ID [IdLength]byte + +// FromByteSlice creates a reservation id from a byte slice. +func (r *ID) FromByteSlice(b []byte) error { + if len(b) != IdLength { + return fmt.Errorf("reservation id must be 32 bytes, got %d, %x", + len(b), b) + } + + copy(r[:], b) + + return nil +} + +// Reservation holds all the necessary information for the 2-of-2 multisig +// reservation utxo. +type Reservation struct { + // ID is the unique identifier of the reservation. + ID ID + + // State is the current state of the reservation. + State fsm.StateType + + // ClientPubkey is the client's pubkey. + ClientPubkey *btcec.PublicKey + + // ServerPubkey is the server's pubkey. + ServerPubkey *btcec.PublicKey + + // Value is the amount of the reservation. + Value btcutil.Amount + + // Expiry is the absolute block height at which the reservation expires. + Expiry uint32 + + // KeyLocator is the key locator of the client's key. + KeyLocator keychain.KeyLocator + + // Outpoint is the outpoint of the reservation. + Outpoint *wire.OutPoint + + // InitiationHeight is the height at which the reservation was + // initiated. + InitiationHeight int32 + + // ConfirmationHeight is the height at which the reservation was + // confirmed. + ConfirmationHeight uint32 +} + +func NewReservation(id ID, serverPubkey, clientPubkey *btcec.PublicKey, + value btcutil.Amount, expiry, heightHint uint32, + keyLocator keychain.KeyLocator) (*Reservation, + error) { + + if id == [32]byte{} { + return nil, errors.New("id is empty") + } + + if clientPubkey == nil { + return nil, errors.New("client pubkey is nil") + } + + if serverPubkey == nil { + return nil, errors.New("server pubkey is nil") + } + + if expiry == 0 { + return nil, errors.New("expiry is 0") + } + + if value == 0 { + return nil, errors.New("value is 0") + } + + if keyLocator.Family == 0 { + return nil, errors.New("key locator family is 0") + } + return &Reservation{ + ID: id, + Value: value, + ClientPubkey: clientPubkey, + ServerPubkey: serverPubkey, + KeyLocator: keyLocator, + Expiry: expiry, + InitiationHeight: int32(heightHint), + }, nil +} + +// GetPkScript returns the pk script of the reservation. +func (r *Reservation) GetPkScript() ([]byte, error) { + // Now that we have all the required data, we can create the pk script. + pkScript, err := reservation_script.ReservationScript( + r.Expiry, r.ServerPubkey, r.ClientPubkey, + ) + if err != nil { + return nil, err + } + + return pkScript, nil +} + +func (r *Reservation) findReservationOutput(tx *wire.MsgTx) (*wire.OutPoint, + error) { + + pkScript, err := r.GetPkScript() + if err != nil { + return nil, err + } + + for i, txOut := range tx.TxOut { + if bytes.Equal(txOut.PkScript, pkScript) { + return &wire.OutPoint{ + Hash: tx.TxHash(), + Index: uint32(i), + }, nil + } + } + + return nil, errors.New("reservation output not found") +} From 1a75a5393a817c048f1cb7eaafec429664dc7641 Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Tue, 24 Oct 2023 22:31:20 +0200 Subject: [PATCH 02/13] reservation add actions tests --- instantout/reservation/actions_test.go | 371 +++++++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 instantout/reservation/actions_test.go diff --git a/instantout/reservation/actions_test.go b/instantout/reservation/actions_test.go new file mode 100644 index 0000000..13559f1 --- /dev/null +++ b/instantout/reservation/actions_test.go @@ -0,0 +1,371 @@ +package reservation + +import ( + "context" + "encoding/hex" + "errors" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/lndclient" + "github.com/lightninglabs/loop/fsm" + "github.com/lightninglabs/loop/swapserverrpc" + "github.com/lightninglabs/loop/test" + "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" +) + +var ( + defaultPubkeyBytes, _ = hex.DecodeString("021c97a90a411ff2b10dc2a8e32de2f29d2fa49d41bfbb52bd416e460db0747d0d") + defaultPubkey, _ = btcec.ParsePubKey(defaultPubkeyBytes) + + defaultValue = btcutil.Amount(100) + + defaultExpiry = uint32(100) +) + +func newValidInitReservationContext() *InitReservationContext { + return &InitReservationContext{ + reservationID: ID{0x01}, + serverPubkey: defaultPubkey, + value: defaultValue, + expiry: defaultExpiry, + heightHint: 0, + } +} + +func newValidClientReturn() *swapserverrpc.ServerOpenReservationResponse { + return &swapserverrpc.ServerOpenReservationResponse{} +} + +type mockReservationClient struct { + mock.Mock +} + +func (m *mockReservationClient) OpenReservation(ctx context.Context, + in *swapserverrpc.ServerOpenReservationRequest, + opts ...grpc.CallOption) (*swapserverrpc.ServerOpenReservationResponse, + error) { + + args := m.Called(ctx, in, opts) + return args.Get(0).(*swapserverrpc.ServerOpenReservationResponse), + args.Error(1) +} + +func (m *mockReservationClient) ReservationNotificationStream( + ctx context.Context, in *swapserverrpc.ReservationNotificationRequest, + opts ...grpc.CallOption, +) (swapserverrpc.ReservationService_ReservationNotificationStreamClient, + error) { + + args := m.Called(ctx, in, opts) + return args.Get(0).(swapserverrpc.ReservationService_ReservationNotificationStreamClient), + args.Error(1) +} + +func (m *mockReservationClient) FetchL402(ctx context.Context, + in *swapserverrpc.FetchL402Request, + opts ...grpc.CallOption) (*swapserverrpc.FetchL402Response, error) { + + args := m.Called(ctx, in, opts) + + return args.Get(0).(*swapserverrpc.FetchL402Response), + args.Error(1) +} + +type mockStore struct { + mock.Mock + + Store +} + +func (m *mockStore) CreateReservation(ctx context.Context, + reservation *Reservation) error { + + args := m.Called(ctx, reservation) + return args.Error(0) +} + +// TestInitReservationAction tests the InitReservationAction of the reservation +// state machine. +func TestInitReservationAction(t *testing.T) { + tests := []struct { + name string + eventCtx fsm.EventContext + mockStoreErr error + mockClientReturn *swapserverrpc.ServerOpenReservationResponse + mockClientErr error + expectedEvent fsm.EventType + }{ + { + name: "success", + eventCtx: newValidInitReservationContext(), + mockClientReturn: newValidClientReturn(), + expectedEvent: OnBroadcast, + }, + { + name: "invalid context", + eventCtx: struct{}{}, + expectedEvent: fsm.OnError, + }, + { + name: "reservation server error", + eventCtx: newValidInitReservationContext(), + mockClientErr: errors.New("reservation server error"), + expectedEvent: fsm.OnError, + }, + { + name: "store error", + eventCtx: newValidInitReservationContext(), + mockClientReturn: newValidClientReturn(), + mockStoreErr: errors.New("store error"), + expectedEvent: fsm.OnError, + }, + } + + for _, tc := range tests { + ctxb := context.Background() + mockLnd := test.NewMockLnd() + mockReservationClient := new(mockReservationClient) + mockReservationClient.On( + "OpenReservation", mock.Anything, + mock.Anything, mock.Anything, + ).Return(tc.mockClientReturn, tc.mockClientErr) + + mockStore := new(mockStore) + mockStore.On( + "CreateReservation", mock.Anything, mock.Anything, + ).Return(tc.mockStoreErr) + + reservationFSM := &FSM{ + ctx: ctxb, + cfg: &Config{ + Wallet: mockLnd.WalletKit, + ChainNotifier: mockLnd.ChainNotifier, + ReservationClient: mockReservationClient, + Store: mockStore, + }, + StateMachine: &fsm.StateMachine{}, + } + + event := reservationFSM.InitAction(tc.eventCtx) + require.Equal(t, tc.expectedEvent, event) + } +} + +type MockChainNotifier struct { + mock.Mock +} + +func (m *MockChainNotifier) RegisterConfirmationsNtfn(ctx context.Context, + txid *chainhash.Hash, pkScript []byte, numConfs, heightHint int32, + options ...lndclient.NotifierOption) (chan *chainntnfs.TxConfirmation, + chan error, error) { + + args := m.Called(ctx, txid, pkScript, numConfs, heightHint) + return args.Get(0).(chan *chainntnfs.TxConfirmation), args.Get(1).(chan error), args.Error(2) +} + +func (m *MockChainNotifier) RegisterBlockEpochNtfn(ctx context.Context) ( + chan int32, chan error, error) { + + args := m.Called(ctx) + return args.Get(0).(chan int32), args.Get(1).(chan error), args.Error(2) +} + +func (m *MockChainNotifier) RegisterSpendNtfn(ctx context.Context, + outpoint *wire.OutPoint, pkScript []byte, heightHint int32) ( + chan *chainntnfs.SpendDetail, chan error, error) { + + args := m.Called(ctx, pkScript, heightHint) + return args.Get(0).(chan *chainntnfs.SpendDetail), args.Get(1).(chan error), args.Error(2) +} + +// TestSubscribeToConfirmationAction tests the SubscribeToConfirmationAction of +// the reservation state machine. +func TestSubscribeToConfirmationAction(t *testing.T) { + tests := []struct { + name string + blockHeight int32 + blockErr error + sendTxConf bool + confErr error + expectedEvent fsm.EventType + }{ + { + name: "success", + blockHeight: 0, + sendTxConf: true, + expectedEvent: OnConfirmed, + }, + { + name: "expired", + blockHeight: 100, + expectedEvent: OnTimedOut, + }, + { + name: "block error", + blockHeight: 0, + blockErr: errors.New("block error"), + expectedEvent: fsm.OnError, + }, + { + name: "tx confirmation error", + blockHeight: 0, + confErr: errors.New("tx confirmation error"), + expectedEvent: fsm.OnError, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + chainNotifier := new(MockChainNotifier) + + // Create the FSM. + r := NewFSMFromReservation( + context.Background(), &Config{ + ChainNotifier: chainNotifier, + }, + &Reservation{ + Expiry: defaultExpiry, + ServerPubkey: defaultPubkey, + ClientPubkey: defaultPubkey, + Value: defaultValue, + }, + ) + pkScript, err := r.reservation.GetPkScript() + require.NoError(t, err) + + confChan := make(chan *chainntnfs.TxConfirmation) + confErrChan := make(chan error) + blockChan := make(chan int32) + blockErrChan := make(chan error) + + // Define the expected return values for the mocks. + chainNotifier.On( + "RegisterConfirmationsNtfn", mock.Anything, mock.Anything, + mock.Anything, mock.Anything, mock.Anything, + ).Return(confChan, confErrChan, nil) + + chainNotifier.On("RegisterBlockEpochNtfn", mock.Anything).Return( + blockChan, blockErrChan, nil, + ) + + go func() { + // Send the tx confirmation. + if tc.sendTxConf { + confChan <- &chainntnfs.TxConfirmation{ + Tx: &wire.MsgTx{ + TxIn: []*wire.TxIn{}, + TxOut: []*wire.TxOut{ + { + Value: int64(defaultValue), + PkScript: pkScript, + }, + }, + }, + } + } + }() + + go func() { + // Send the block notification. + if tc.blockHeight != 0 { + blockChan <- tc.blockHeight + } + }() + + go func() { + // Send the block notification error. + if tc.blockErr != nil { + blockErrChan <- tc.blockErr + } + }() + + go func() { + // Send the tx confirmation error. + if tc.confErr != nil { + confErrChan <- tc.confErr + } + }() + + eventType := r.SubscribeToConfirmationAction(nil) + // Assert that the return value is as expected + require.Equal(t, tc.expectedEvent, eventType) + + // Assert that the expected functions were called on the mocks + chainNotifier.AssertExpectations(t) + }) + } +} + +// TestReservationConfirmedAction tests the ReservationConfirmedAction of the +// reservation state machine. +func TestReservationConfirmedAction(t *testing.T) { + tests := []struct { + name string + blockHeight int32 + blockErr error + expectedEvent fsm.EventType + }{ + { + name: "expired", + blockHeight: 100, + expectedEvent: OnTimedOut, + }, + { + name: "block error", + blockHeight: 0, + blockErr: errors.New("block error"), + expectedEvent: fsm.OnError, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + chainNotifier := new(MockChainNotifier) + + // Create the FSM. + r := NewFSMFromReservation( + context.Background(), &Config{ + ChainNotifier: chainNotifier, + }, + &Reservation{ + Expiry: defaultExpiry, + }, + ) + + blockChan := make(chan int32) + blockErrChan := make(chan error) + + // Define our expected return values for the mocks. + chainNotifier.On("RegisterBlockEpochNtfn", mock.Anything).Return( + blockChan, blockErrChan, nil, + ) + go func() { + // Send the block notification. + if tc.blockHeight != 0 { + blockChan <- tc.blockHeight + } + }() + + go func() { + // Send the block notification error. + if tc.blockErr != nil { + blockErrChan <- tc.blockErr + } + }() + + eventType := r.ReservationConfirmedAction(nil) + require.Equal(t, tc.expectedEvent, eventType) + + // Assert that the expected functions were called on the mocks + chainNotifier.AssertExpectations(t) + }) + } +} From a29f7e4a6b1e0931b3315dff95fc476e9d1cf9f7 Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Wed, 18 Oct 2023 09:25:23 +0200 Subject: [PATCH 03/13] loopdb: add reservation sqlc code --- .../migrations/000003_reservations.down.sql | 2 + .../migrations/000003_reservations.up.sql | 56 +++++ loopdb/sqlc/models.go | 23 ++ loopdb/sqlc/querier.go | 6 + loopdb/sqlc/queries/reservations.sql | 66 ++++++ loopdb/sqlc/reservations.sql.go | 222 ++++++++++++++++++ 6 files changed, 375 insertions(+) create mode 100644 loopdb/sqlc/migrations/000003_reservations.down.sql create mode 100644 loopdb/sqlc/migrations/000003_reservations.up.sql create mode 100644 loopdb/sqlc/queries/reservations.sql create mode 100644 loopdb/sqlc/reservations.sql.go diff --git a/loopdb/sqlc/migrations/000003_reservations.down.sql b/loopdb/sqlc/migrations/000003_reservations.down.sql new file mode 100644 index 0000000..281e17f --- /dev/null +++ b/loopdb/sqlc/migrations/000003_reservations.down.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS reservation_updates; +DROP TABLE IF EXISTS reservations; diff --git a/loopdb/sqlc/migrations/000003_reservations.up.sql b/loopdb/sqlc/migrations/000003_reservations.up.sql new file mode 100644 index 0000000..e3fb456 --- /dev/null +++ b/loopdb/sqlc/migrations/000003_reservations.up.sql @@ -0,0 +1,56 @@ +-- reservations contains all the information about a reservation. +CREATE TABLE IF NOT EXISTS reservations ( + -- id is the auto incrementing primary key. + id INTEGER PRIMARY KEY, + + -- reservation_id is the unique identifier for the reservation. + reservation_id BLOB NOT NULL UNIQUE, + + -- client_pubkey is the public key of the client. + client_pubkey BLOB NOT NULL, + + -- server_pubkey is the public key of the server. + server_pubkey BLOB NOT NULL, + + -- expiry is the absolute expiry height of the reservation. + expiry INTEGER NOT NULL, + + -- value is the value of the reservation. + value BIGINT NOT NULL, + + -- client_key_family is the key family of the client. + client_key_family INTEGER NOT NULL, + + -- client_key_index is the key index of the client. + client_key_index INTEGER NOT NULL, + + -- initiation_height is the height at which the reservation was initiated. + initiation_height INTEGER NOT NULL, + + -- tx_hash is the hash of the transaction that created the reservation. + tx_hash BLOB, + + -- out_index is the index of the output that created the reservation. + out_index INTEGER, + + -- confirmation_height is the height at which the reservation was confirmed. + confirmation_height INTEGER +); + +CREATE INDEX IF NOT EXISTS reservations_reservation_id_idx ON reservations(reservation_id); + +-- reservation_updates contains all the updates to a reservation. +CREATE TABLE IF NOT EXISTS reservation_updates ( + -- id is the auto incrementing primary key. + id INTEGER PRIMARY KEY, + + -- reservation_id is the unique identifier for the reservation. + reservation_id BLOB NOT NULL REFERENCES reservations(reservation_id), + + -- update_state is the state of the reservation at the time of the update. + update_state TEXT NOT NULL, + + -- update_timestamp is the timestamp of the update. + update_timestamp TIMESTAMP NOT NULL +); + diff --git a/loopdb/sqlc/models.go b/loopdb/sqlc/models.go index e1aadf5..413b146 100644 --- a/loopdb/sqlc/models.go +++ b/loopdb/sqlc/models.go @@ -5,6 +5,7 @@ package sqlc import ( + "database/sql" "time" ) @@ -43,6 +44,28 @@ type LoopoutSwap struct { PublicationDeadline time.Time } +type Reservation struct { + ID int32 + ReservationID []byte + ClientPubkey []byte + ServerPubkey []byte + Expiry int32 + Value int64 + ClientKeyFamily int32 + ClientKeyIndex int32 + InitiationHeight int32 + TxHash []byte + OutIndex sql.NullInt32 + ConfirmationHeight sql.NullInt32 +} + +type ReservationUpdate struct { + ID int32 + ReservationID []byte + UpdateState string + UpdateTimestamp time.Time +} + type Swap struct { ID int32 SwapHash []byte diff --git a/loopdb/sqlc/querier.go b/loopdb/sqlc/querier.go index 6f19a37..69ae559 100644 --- a/loopdb/sqlc/querier.go +++ b/loopdb/sqlc/querier.go @@ -9,17 +9,23 @@ import ( ) type Querier interface { + CreateReservation(ctx context.Context, arg CreateReservationParams) error FetchLiquidityParams(ctx context.Context) ([]byte, error) GetLoopInSwap(ctx context.Context, swapHash []byte) (GetLoopInSwapRow, error) GetLoopInSwaps(ctx context.Context) ([]GetLoopInSwapsRow, error) GetLoopOutSwap(ctx context.Context, swapHash []byte) (GetLoopOutSwapRow, error) GetLoopOutSwaps(ctx context.Context) ([]GetLoopOutSwapsRow, error) + GetReservation(ctx context.Context, reservationID []byte) (Reservation, error) + GetReservationUpdates(ctx context.Context, reservationID []byte) ([]ReservationUpdate, error) + GetReservations(ctx context.Context) ([]Reservation, error) GetSwapUpdates(ctx context.Context, swapHash []byte) ([]SwapUpdate, error) InsertHtlcKeys(ctx context.Context, arg InsertHtlcKeysParams) error InsertLoopIn(ctx context.Context, arg InsertLoopInParams) error InsertLoopOut(ctx context.Context, arg InsertLoopOutParams) error + InsertReservationUpdate(ctx context.Context, arg InsertReservationUpdateParams) error InsertSwap(ctx context.Context, arg InsertSwapParams) error InsertSwapUpdate(ctx context.Context, arg InsertSwapUpdateParams) error + UpdateReservation(ctx context.Context, arg UpdateReservationParams) error UpsertLiquidityParams(ctx context.Context, params []byte) error } diff --git a/loopdb/sqlc/queries/reservations.sql b/loopdb/sqlc/queries/reservations.sql new file mode 100644 index 0000000..c96d664 --- /dev/null +++ b/loopdb/sqlc/queries/reservations.sql @@ -0,0 +1,66 @@ +-- name: CreateReservation :exec +INSERT INTO reservations ( + reservation_id, + client_pubkey, + server_pubkey, + expiry, + value, + client_key_family, + client_key_index, + initiation_height +) VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7, + $8 +); + +-- name: UpdateReservation :exec +UPDATE reservations +SET + tx_hash = $2, + out_index = $3, + confirmation_height = $4 +WHERE + reservations.reservation_id = $1; + +-- name: InsertReservationUpdate :exec +INSERT INTO reservation_updates ( + reservation_id, + update_state, + update_timestamp +) VALUES ( + $1, + $2, + $3 +); + +-- name: GetReservation :one +SELECT + * +FROM + reservations +WHERE + reservation_id = $1; + +-- name: GetReservations :many +SELECT + * +FROM + reservations +ORDER BY + id ASC; + +-- name: GetReservationUpdates :many +SELECT + reservation_updates.* +FROM + reservation_updates +WHERE + reservation_id = $1 +ORDER BY + id ASC; \ No newline at end of file diff --git a/loopdb/sqlc/reservations.sql.go b/loopdb/sqlc/reservations.sql.go new file mode 100644 index 0000000..863371b --- /dev/null +++ b/loopdb/sqlc/reservations.sql.go @@ -0,0 +1,222 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.17.2 +// source: reservations.sql + +package sqlc + +import ( + "context" + "database/sql" + "time" +) + +const createReservation = `-- name: CreateReservation :exec +INSERT INTO reservations ( + reservation_id, + client_pubkey, + server_pubkey, + expiry, + value, + client_key_family, + client_key_index, + initiation_height +) VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7, + $8 +) +` + +type CreateReservationParams struct { + ReservationID []byte + ClientPubkey []byte + ServerPubkey []byte + Expiry int32 + Value int64 + ClientKeyFamily int32 + ClientKeyIndex int32 + InitiationHeight int32 +} + +func (q *Queries) CreateReservation(ctx context.Context, arg CreateReservationParams) error { + _, err := q.db.ExecContext(ctx, createReservation, + arg.ReservationID, + arg.ClientPubkey, + arg.ServerPubkey, + arg.Expiry, + arg.Value, + arg.ClientKeyFamily, + arg.ClientKeyIndex, + arg.InitiationHeight, + ) + return err +} + +const getReservation = `-- name: GetReservation :one +SELECT + id, reservation_id, client_pubkey, server_pubkey, expiry, value, client_key_family, client_key_index, initiation_height, tx_hash, out_index, confirmation_height +FROM + reservations +WHERE + reservation_id = $1 +` + +func (q *Queries) GetReservation(ctx context.Context, reservationID []byte) (Reservation, error) { + row := q.db.QueryRowContext(ctx, getReservation, reservationID) + var i Reservation + err := row.Scan( + &i.ID, + &i.ReservationID, + &i.ClientPubkey, + &i.ServerPubkey, + &i.Expiry, + &i.Value, + &i.ClientKeyFamily, + &i.ClientKeyIndex, + &i.InitiationHeight, + &i.TxHash, + &i.OutIndex, + &i.ConfirmationHeight, + ) + return i, err +} + +const getReservationUpdates = `-- name: GetReservationUpdates :many +SELECT + reservation_updates.id, reservation_updates.reservation_id, reservation_updates.update_state, reservation_updates.update_timestamp +FROM + reservation_updates +WHERE + reservation_id = $1 +ORDER BY + id ASC +` + +func (q *Queries) GetReservationUpdates(ctx context.Context, reservationID []byte) ([]ReservationUpdate, error) { + rows, err := q.db.QueryContext(ctx, getReservationUpdates, reservationID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ReservationUpdate + for rows.Next() { + var i ReservationUpdate + if err := rows.Scan( + &i.ID, + &i.ReservationID, + &i.UpdateState, + &i.UpdateTimestamp, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getReservations = `-- name: GetReservations :many +SELECT + id, reservation_id, client_pubkey, server_pubkey, expiry, value, client_key_family, client_key_index, initiation_height, tx_hash, out_index, confirmation_height +FROM + reservations +ORDER BY + id ASC +` + +func (q *Queries) GetReservations(ctx context.Context) ([]Reservation, error) { + rows, err := q.db.QueryContext(ctx, getReservations) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Reservation + for rows.Next() { + var i Reservation + if err := rows.Scan( + &i.ID, + &i.ReservationID, + &i.ClientPubkey, + &i.ServerPubkey, + &i.Expiry, + &i.Value, + &i.ClientKeyFamily, + &i.ClientKeyIndex, + &i.InitiationHeight, + &i.TxHash, + &i.OutIndex, + &i.ConfirmationHeight, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const insertReservationUpdate = `-- name: InsertReservationUpdate :exec +INSERT INTO reservation_updates ( + reservation_id, + update_state, + update_timestamp +) VALUES ( + $1, + $2, + $3 +) +` + +type InsertReservationUpdateParams struct { + ReservationID []byte + UpdateState string + UpdateTimestamp time.Time +} + +func (q *Queries) InsertReservationUpdate(ctx context.Context, arg InsertReservationUpdateParams) error { + _, err := q.db.ExecContext(ctx, insertReservationUpdate, arg.ReservationID, arg.UpdateState, arg.UpdateTimestamp) + return err +} + +const updateReservation = `-- name: UpdateReservation :exec +UPDATE reservations +SET + tx_hash = $2, + out_index = $3, + confirmation_height = $4 +WHERE + reservations.reservation_id = $1 +` + +type UpdateReservationParams struct { + ReservationID []byte + TxHash []byte + OutIndex sql.NullInt32 + ConfirmationHeight sql.NullInt32 +} + +func (q *Queries) UpdateReservation(ctx context.Context, arg UpdateReservationParams) error { + _, err := q.db.ExecContext(ctx, updateReservation, + arg.ReservationID, + arg.TxHash, + arg.OutIndex, + arg.ConfirmationHeight, + ) + return err +} From 60df5fe320ea850217bd095f5ccf9f374554222a Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Wed, 18 Oct 2023 09:25:41 +0200 Subject: [PATCH 04/13] reservations: add reservation sql store --- instantout/reservation/store.go | 298 +++++++++++++++++++++++++++ instantout/reservation/store_test.go | 96 +++++++++ 2 files changed, 394 insertions(+) create mode 100644 instantout/reservation/store.go create mode 100644 instantout/reservation/store_test.go diff --git a/instantout/reservation/store.go b/instantout/reservation/store.go new file mode 100644 index 0000000..8a4cc65 --- /dev/null +++ b/instantout/reservation/store.go @@ -0,0 +1,298 @@ +package reservation + +import ( + "context" + "database/sql" + "errors" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/loop/fsm" + "github.com/lightninglabs/loop/loopdb" + "github.com/lightninglabs/loop/loopdb/sqlc" + "github.com/lightningnetwork/lnd/clock" + "github.com/lightningnetwork/lnd/keychain" +) + +// BaseDB is the interface that contains all the queries generated +// by sqlc for the reservation table. +type BaseDB interface { + // CreateReservation stores the reservation in the database. + CreateReservation(ctx context.Context, + arg sqlc.CreateReservationParams) error + + // GetReservation retrieves the reservation from the database. + GetReservation(ctx context.Context, + reservationID []byte) (sqlc.Reservation, error) + + // GetReservationUpdates fetches all updates for a reservation. + GetReservationUpdates(ctx context.Context, + reservationID []byte) ([]sqlc.ReservationUpdate, error) + + // GetReservations lists all existing reservations the client has ever + // made. + GetReservations(ctx context.Context) ([]sqlc.Reservation, error) + + // UpdateReservation inserts a new reservation update. + UpdateReservation(ctx context.Context, + arg sqlc.UpdateReservationParams) error + + // ExecTx allows for executing a function in the context of a database + // transaction. + ExecTx(ctx context.Context, txOptions loopdb.TxOptions, + txBody func(*sqlc.Queries) error) error +} + +// SQLStore manages the reservations in the database. +type SQLStore struct { + baseDb BaseDB + + clock clock.Clock +} + +// NewSQLStore creates a new SQLStore. +func NewSQLStore(db BaseDB) *SQLStore { + return &SQLStore{ + baseDb: db, + clock: clock.NewDefaultClock(), + } +} + +// CreateReservation stores the reservation in the database. +func (r *SQLStore) CreateReservation(ctx context.Context, + reservation *Reservation) error { + + args := sqlc.CreateReservationParams{ + ReservationID: reservation.ID[:], + ClientPubkey: reservation.ClientPubkey.SerializeCompressed(), + ServerPubkey: reservation.ServerPubkey.SerializeCompressed(), + Expiry: int32(reservation.Expiry), + Value: int64(reservation.Value), + ClientKeyFamily: int32(reservation.KeyLocator.Family), + ClientKeyIndex: int32(reservation.KeyLocator.Index), + InitiationHeight: reservation.InitiationHeight, + } + + updateArgs := sqlc.InsertReservationUpdateParams{ + ReservationID: reservation.ID[:], + UpdateTimestamp: r.clock.Now().UTC(), + UpdateState: string(reservation.State), + } + + return r.baseDb.ExecTx(ctx, &loopdb.SqliteTxOptions{}, + func(q *sqlc.Queries) error { + err := q.CreateReservation(ctx, args) + if err != nil { + return err + } + + return q.InsertReservationUpdate(ctx, updateArgs) + }) +} + +// UpdateReservation updates the reservation in the database. +func (r *SQLStore) UpdateReservation(ctx context.Context, + reservation *Reservation) error { + + var txHash []byte + var outIndex sql.NullInt32 + if reservation.Outpoint != nil { + txHash = reservation.Outpoint.Hash[:] + outIndex = sql.NullInt32{ + Int32: int32(reservation.Outpoint.Index), + Valid: true, + } + } + + insertUpdateArgs := sqlc.InsertReservationUpdateParams{ + ReservationID: reservation.ID[:], + UpdateTimestamp: r.clock.Now().UTC(), + UpdateState: string(reservation.State), + } + + updateArgs := sqlc.UpdateReservationParams{ + ReservationID: reservation.ID[:], + TxHash: txHash, + OutIndex: outIndex, + ConfirmationHeight: marshalSqlNullInt32( + int32(reservation.ConfirmationHeight), + ), + } + + return r.baseDb.ExecTx(ctx, &loopdb.SqliteTxOptions{}, + func(q *sqlc.Queries) error { + err := q.UpdateReservation(ctx, updateArgs) + if err != nil { + return err + } + + return q.InsertReservationUpdate(ctx, insertUpdateArgs) + }) +} + +// GetReservation retrieves the reservation from the database. +func (r *SQLStore) GetReservation(ctx context.Context, + reservationId ID) (*Reservation, error) { + + var reservation *Reservation + err := r.baseDb.ExecTx(ctx, loopdb.NewSqlReadOpts(), + func(q *sqlc.Queries) error { + var err error + reservationRow, err := q.GetReservation( + ctx, reservationId[:], + ) + if err != nil { + return err + } + + reservationUpdates, err := q.GetReservationUpdates( + ctx, reservationId[:], + ) + if err != nil { + return err + } + + if len(reservationUpdates) == 0 { + return errors.New("no reservation updates") + } + + reservation, err = sqlReservationToReservation( + reservationRow, + reservationUpdates[len(reservationUpdates)-1], + ) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return nil, err + } + + return reservation, nil +} + +// ListReservations lists all existing reservations the client has ever made. +func (r *SQLStore) ListReservations(ctx context.Context) ([]*Reservation, + error) { + + var result []*Reservation + + err := r.baseDb.ExecTx(ctx, loopdb.NewSqlReadOpts(), + func(q *sqlc.Queries) error { + var err error + + reservations, err := q.GetReservations(ctx) + if err != nil { + return err + } + + for _, reservation := range reservations { + reservationUpdates, err := q.GetReservationUpdates( + ctx, reservation.ReservationID, + ) + if err != nil { + return err + } + + if len(reservationUpdates) == 0 { + return errors.New( + "no reservation updates", + ) + } + + res, err := sqlReservationToReservation( + reservation, reservationUpdates[len( + reservationUpdates, + )-1], + ) + if err != nil { + return err + } + + result = append(result, res) + } + + return nil + }) + if err != nil { + return nil, err + } + + return result, nil +} + +// sqlReservationToReservation converts a sql reservation to a reservation. +func sqlReservationToReservation(row sqlc.Reservation, + lastUpdate sqlc.ReservationUpdate) (*Reservation, + error) { + + id := ID{} + err := id.FromByteSlice(row.ReservationID) + if err != nil { + return nil, err + } + + clientPubkey, err := btcec.ParsePubKey(row.ClientPubkey) + if err != nil { + return nil, err + } + + serverPubkey, err := btcec.ParsePubKey(row.ServerPubkey) + if err != nil { + return nil, err + } + + var txHash *chainhash.Hash + if row.TxHash != nil { + txHash, err = chainhash.NewHash(row.TxHash) + if err != nil { + return nil, err + } + } + + var outpoint *wire.OutPoint + if row.OutIndex.Valid { + outpoint = wire.NewOutPoint( + txHash, uint32(unmarshalSqlNullInt32(row.OutIndex)), + ) + } + + return &Reservation{ + ID: id, + ClientPubkey: clientPubkey, + ServerPubkey: serverPubkey, + Expiry: uint32(row.Expiry), + Value: btcutil.Amount(row.Value), + KeyLocator: keychain.KeyLocator{ + Family: keychain.KeyFamily(row.ClientKeyFamily), + Index: uint32(row.ClientKeyIndex), + }, + Outpoint: outpoint, + ConfirmationHeight: uint32( + unmarshalSqlNullInt32(row.ConfirmationHeight), + ), + InitiationHeight: row.InitiationHeight, + State: fsm.StateType(lastUpdate.UpdateState), + }, nil +} + +// marshalSqlNullInt32 converts an int32 to a sql.NullInt32. +func marshalSqlNullInt32(i int32) sql.NullInt32 { + return sql.NullInt32{ + Int32: i, + Valid: i != 0, + } +} + +// unmarshalSqlNullInt32 converts a sql.NullInt32 to an int32. +func unmarshalSqlNullInt32(i sql.NullInt32) int32 { + if i.Valid { + return i.Int32 + } + + return 0 +} diff --git a/instantout/reservation/store_test.go b/instantout/reservation/store_test.go new file mode 100644 index 0000000..67014ef --- /dev/null +++ b/instantout/reservation/store_test.go @@ -0,0 +1,96 @@ +package reservation + +import ( + "context" + "crypto/rand" + "testing" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/loop/fsm" + "github.com/lightninglabs/loop/loopdb" + "github.com/lightningnetwork/lnd/keychain" + "github.com/stretchr/testify/require" +) + +// TestSqlStore tests the basic functionality of the SQLStore. +func TestSqlStore(t *testing.T) { + ctxb := context.Background() + testDb := loopdb.NewTestDB(t) + defer testDb.Close() + + store := NewSQLStore(testDb) + + // Create a reservation and store it. + reservation := &Reservation{ + ID: getRandomReservationID(), + State: fsm.StateType("init"), + ClientPubkey: defaultPubkey, + ServerPubkey: defaultPubkey, + Value: 100, + Expiry: 100, + KeyLocator: keychain.KeyLocator{ + Family: 1, + Index: 1, + }, + } + + err := store.CreateReservation(ctxb, reservation) + require.NoError(t, err) + + // Get the reservation and compare it. + reservation2, err := store.GetReservation(ctxb, reservation.ID) + require.NoError(t, err) + require.Equal(t, reservation, reservation2) + + // Update the reservation and compare it. + reservation.State = fsm.StateType("state2") + err = store.UpdateReservation(ctxb, reservation) + require.NoError(t, err) + + reservation2, err = store.GetReservation(ctxb, reservation.ID) + require.NoError(t, err) + require.Equal(t, reservation, reservation2) + + // Add an outpoint to the reservation and compare it. + reservation.Outpoint = &wire.OutPoint{ + Hash: chainhash.Hash{0x01}, + Index: 0, + } + reservation.State = Confirmed + + err = store.UpdateReservation(ctxb, reservation) + require.NoError(t, err) + + reservation2, err = store.GetReservation(ctxb, reservation.ID) + require.NoError(t, err) + require.Equal(t, reservation, reservation2) + + // Add a second reservation. + reservation3 := &Reservation{ + ID: getRandomReservationID(), + State: fsm.StateType("init"), + ClientPubkey: defaultPubkey, + ServerPubkey: defaultPubkey, + Value: 99, + Expiry: 100, + KeyLocator: keychain.KeyLocator{ + Family: 1, + Index: 1, + }, + } + + err = store.CreateReservation(ctxb, reservation3) + require.NoError(t, err) + + reservations, err := store.ListReservations(ctxb) + require.NoError(t, err) + require.Equal(t, 2, len(reservations)) +} + +// getRandomReservationID generates a random reservation ID. +func getRandomReservationID() ID { + var id ID + rand.Read(id[:]) // nolint: errcheck + return id +} From 61a5f9da05afb79896307509ea0a1a508771c7e1 Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Fri, 25 Aug 2023 01:39:16 +0200 Subject: [PATCH 05/13] reservation: add reservation manager This commit adds the reservation manager to the reservation package. This manager manages the lifecycle of reservations. --- instantout/reservation/manager.go | 265 ++++++++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 instantout/reservation/manager.go diff --git a/instantout/reservation/manager.go b/instantout/reservation/manager.go new file mode 100644 index 0000000..64c88e4 --- /dev/null +++ b/instantout/reservation/manager.go @@ -0,0 +1,265 @@ +package reservation + +import ( + "context" + "sync" + "time" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + + "github.com/lightninglabs/loop/fsm" + reservationrpc "github.com/lightninglabs/loop/swapserverrpc" +) + +// Manager manages the reservation state machines. +type Manager struct { + // cfg contains all the services that the reservation manager needs to + // operate. + cfg *Config + + // activeReservations contains all the active reservationsFSMs. + activeReservations map[ID]*FSM + + sync.Mutex +} + +// NewReservationManager creates a new reservation manager. +func NewReservationManager(cfg *Config) *Manager { + return &Manager{ + cfg: cfg, + activeReservations: make(map[ID]*FSM), + } +} + +// Run runs the reservation manager. +func (m *Manager) Run(ctx context.Context, height int32) error { + // todo(sputn1ck): recover swaps on startup + log.Debugf("Starting reservation manager") + + runCtx, cancel := context.WithCancel(ctx) + defer cancel() + + currentHeight := height + + err := m.RecoverReservations(runCtx) + if err != nil { + return err + } + + newBlockChan, newBlockErrChan, err := m.cfg.ChainNotifier. + RegisterBlockEpochNtfn(runCtx) + if err != nil { + return err + } + + reservationResChan := make( + chan *reservationrpc.ServerReservationNotification, + ) + + err = m.RegisterReservationNotifications(runCtx, reservationResChan) + if err != nil { + return err + } + + for { + select { + case height := <-newBlockChan: + log.Debugf("Received block %v", height) + currentHeight = height + + case reservationRes := <-reservationResChan: + log.Debugf("Received reservation %x", + reservationRes.ReservationId) + err := m.newReservation( + runCtx, uint32(currentHeight), reservationRes, + ) + if err != nil { + return err + } + + case err := <-newBlockErrChan: + return err + + case <-runCtx.Done(): + log.Debugf("Stopping reservation manager") + return nil + } + } +} + +// newReservation creates a new reservation from the reservation request. +func (m *Manager) newReservation(ctx context.Context, currentHeight uint32, + req *reservationrpc.ServerReservationNotification) error { + + var reservationID ID + err := reservationID.FromByteSlice( + req.ReservationId, + ) + if err != nil { + return err + } + + serverKey, err := btcec.ParsePubKey(req.ServerKey) + if err != nil { + return err + } + + // Create the reservation state machine. We need to pass in the runCtx + // of the reservation manager so that the state machine will keep on + // running even if the grpc conte + reservationFSM := NewFSM( + ctx, m.cfg, + ) + + // Add the reservation to the active reservations map. + m.Lock() + m.activeReservations[reservationID] = reservationFSM + m.Unlock() + + initContext := &InitReservationContext{ + reservationID: reservationID, + serverPubkey: serverKey, + value: btcutil.Amount(req.Value), + expiry: req.Expiry, + heightHint: currentHeight, + } + + // Send the init event to the state machine. + go func() { + err = reservationFSM.SendEvent(OnServerRequest, initContext) + if err != nil { + log.Errorf("Error sending init event: %v", err) + } + }() + + // We'll now wait for the reservation to be in the state where it is + // waiting to be confirmed. + err = reservationFSM.DefaultObserver.WaitForState( + ctx, time.Minute, WaitForConfirmation, + fsm.WithWaitForStateOption(time.Second), + ) + if err != nil { + return err + } + + return nil +} + +// RegisterReservationNotifications registers a new reservation notification +// stream. +func (m *Manager) RegisterReservationNotifications( + ctx context.Context, reservationChan chan *reservationrpc. + ServerReservationNotification) error { + + // In order to create a valid lsat we first are going to call + // the FetchL402 method. + err := m.cfg.FetchL402(ctx) + if err != nil { + return err + } + + // We'll now subscribe to the reservation notifications. + reservationStream, err := m.cfg.ReservationClient. + ReservationNotificationStream( + ctx, &reservationrpc.ReservationNotificationRequest{}, + ) + if err != nil { + return err + } + + // We'll now start a goroutine that will forward all the reservation + // notifications to the reservationChan. + go func() { + for { + reservationRes, err := reservationStream.Recv() + if err == nil && reservationRes != nil { + log.Debugf("Received reservation %x", + reservationRes.ReservationId) + reservationChan <- reservationRes + continue + } + log.Errorf("Error receiving "+ + "reservation: %v", err) + + reconnectTimer := time.NewTimer(time.Second * 10) + + // If we encounter an error, we'll + // try to reconnect. + for { + select { + case <-ctx.Done(): + return + case <-reconnectTimer.C: + err = m.RegisterReservationNotifications( + ctx, reservationChan, + ) + if err == nil { + log.Debugf( + "Successfully " + + "reconnected", + ) + reconnectTimer.Stop() + // If we were able to + // reconnect, we'll + // return. + return + } + log.Errorf("Error "+ + "reconnecting: %v", + err) + + reconnectTimer.Reset( + time.Second * 10, + ) + } + } + } + }() + + return nil +} + +// RecoverReservations tries to recover all reservations that are still active +// from the database. +func (m *Manager) RecoverReservations(ctx context.Context) error { + reservations, err := m.cfg.Store.ListReservations(ctx) + if err != nil { + return err + } + + for _, reservation := range reservations { + if isFinalState(reservation.State) { + continue + } + + log.Debugf("Recovering reservation %x", reservation.ID) + + fsmCtx := context.WithValue(ctx, reservation.ID, nil) + + reservationFSM := NewFSMFromReservation( + fsmCtx, m.cfg, reservation, + ) + + m.activeReservations[reservation.ID] = reservationFSM + + // As SendEvent can block, we'll start a goroutine to process + // the event. + go func() { + err := reservationFSM.SendEvent(OnRecover, nil) + if err != nil { + log.Errorf("FSM %v Error sending recover "+ + "event %v, state: %v", + reservationFSM.reservation.ID, err, + reservationFSM.reservation.State) + } + }() + } + + return nil +} + +// GetReservations retrieves all reservations from the database. +func (m *Manager) GetReservations(ctx context.Context) ([]*Reservation, error) { + return m.cfg.Store.ListReservations(ctx) +} From d527b0b67c146c0bc43c88975f7eefcfb87fd21d Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Wed, 17 Jan 2024 14:26:20 +0100 Subject: [PATCH 06/13] reservation: add test for manager --- instantout/reservation/manager_test.go | 160 +++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 instantout/reservation/manager_test.go diff --git a/instantout/reservation/manager_test.go b/instantout/reservation/manager_test.go new file mode 100644 index 0000000..1a4fe05 --- /dev/null +++ b/instantout/reservation/manager_test.go @@ -0,0 +1,160 @@ +package reservation + +import ( + "context" + "encoding/hex" + "testing" + "time" + + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/loop/loopdb" + "github.com/lightninglabs/loop/swapserverrpc" + "github.com/lightninglabs/loop/test" + "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" +) + +var ( + defaultReservationId = mustDecodeID("17cecc61ab4aafebdc0542dabdae0d0cb8907ec1c9c8ae387bc5a3309ca8b600") +) + +func TestManager(t *testing.T) { + ctxb, cancel := context.WithCancel(context.Background()) + defer cancel() + + testContext := newManagerTestContext(t) + + // Start the manager. + go func() { + err := testContext.manager.Run(ctxb, testContext.mockLnd.Height) + require.NoError(t, err) + }() + + // Create a new reservation. + fsm, err := testContext.manager.newReservation( + ctxb, uint32(testContext.mockLnd.Height), + &swapserverrpc.ServerReservationNotification{ + ReservationId: defaultReservationId[:], + Value: uint64(defaultValue), + ServerKey: defaultPubkeyBytes, + Expiry: uint32(testContext.mockLnd.Height) + defaultExpiry, + }, + ) + require.NoError(t, err) + + // We'll expect the spendConfirmation to be sent to the server. + pkScript, err := fsm.reservation.GetPkScript() + require.NoError(t, err) + + conf := <-testContext.mockLnd.RegisterConfChannel + require.Equal(t, conf.PkScript, pkScript) + + confTx := &wire.MsgTx{ + TxOut: []*wire.TxOut{ + { + PkScript: pkScript, + }, + }, + } + // We'll now confirm the spend. + conf.ConfChan <- &chainntnfs.TxConfirmation{ + BlockHeight: uint32(testContext.mockLnd.Height), + Tx: confTx, + } + + // We'll now expect the reservation to be confirmed. + err = fsm.DefaultObserver.WaitForState(ctxb, 5*time.Second, Confirmed) + require.NoError(t, err) + + // We'll now expire the reservation. + err = testContext.mockLnd.NotifyHeight( + testContext.mockLnd.Height + int32(defaultExpiry), + ) + require.NoError(t, err) + + // We'll now expect the reservation to be expired. + err = fsm.DefaultObserver.WaitForState(ctxb, 5*time.Second, TimedOut) + require.NoError(t, err) +} + +// ManagerTestContext is a helper struct that contains all the necessary +// components to test the reservation manager. +type ManagerTestContext struct { + manager *Manager + context test.Context + mockLnd *test.LndMockServices + reservationNotificationChan chan *swapserverrpc.ServerReservationNotification + mockReservationClient *mockReservationClient +} + +// newManagerTestContext creates a new test context for the reservation manager. +func newManagerTestContext(t *testing.T) *ManagerTestContext { + mockLnd := test.NewMockLnd() + lndContext := test.NewContext(t, mockLnd) + + dbFixture := loopdb.NewTestDB(t) + + store := NewSQLStore(dbFixture) + + mockReservationClient := new(mockReservationClient) + + sendChan := make(chan *swapserverrpc.ServerReservationNotification) + + mockReservationClient.On( + "ReservationNotificationStream", mock.Anything, mock.Anything, + mock.Anything, + ).Return( + &dummyReservationNotificationServer{ + SendChan: sendChan, + }, nil, + ) + + mockReservationClient.On( + "OpenReservation", mock.Anything, mock.Anything, mock.Anything, + ).Return( + &swapserverrpc.ServerOpenReservationResponse{}, nil, + ) + + cfg := &Config{ + Store: store, + Wallet: mockLnd.WalletKit, + ChainNotifier: mockLnd.ChainNotifier, + FetchL402: func(context.Context) error { return nil }, + ReservationClient: mockReservationClient, + } + + manager := NewManager(cfg) + + return &ManagerTestContext{ + manager: manager, + context: lndContext, + mockLnd: mockLnd, + mockReservationClient: mockReservationClient, + reservationNotificationChan: sendChan, + } +} + +type dummyReservationNotificationServer struct { + grpc.ClientStream + + // SendChan is the channel that is used to send notifications. + SendChan chan *swapserverrpc.ServerReservationNotification +} + +func (d *dummyReservationNotificationServer) Recv() ( + *swapserverrpc.ServerReservationNotification, error) { + + return <-d.SendChan, nil +} + +func mustDecodeID(id string) ID { + bytes, err := hex.DecodeString(id) + if err != nil { + panic(err) + } + var decoded ID + copy(decoded[:], bytes) + return decoded +} From 73d5cf5bf988c32ca363ced5e28968a196aac400 Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Fri, 25 Aug 2023 01:40:49 +0200 Subject: [PATCH 07/13] swapserverrpc add ReservationServer This commit adds the ReservationServer service to the proto definitions. --- swapserverrpc/reservation.pb.go | 468 +++++++++++++++++++++++++++ swapserverrpc/reservation.proto | 70 ++++ swapserverrpc/reservation_grpc.pb.go | 171 ++++++++++ 3 files changed, 709 insertions(+) create mode 100644 swapserverrpc/reservation.pb.go create mode 100644 swapserverrpc/reservation.proto create mode 100644 swapserverrpc/reservation_grpc.pb.go diff --git a/swapserverrpc/reservation.pb.go b/swapserverrpc/reservation.pb.go new file mode 100644 index 0000000..7097970 --- /dev/null +++ b/swapserverrpc/reservation.pb.go @@ -0,0 +1,468 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc v3.6.1 +// source: reservation.proto + +// We can't change this to swapserverrpc, it would be a breaking change because +// the package name is also contained in the HTTP URIs and old clients would +// call the wrong endpoints. Luckily with the go_package option we can have +// different golang and RPC package names to fix protobuf namespace conflicts. + +package swapserverrpc + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// ReservationProtocolVersion is the version of the reservation protocol. +type ReservationProtocolVersion int32 + +const ( + // RESERVATION_NONE is the default value and means that the reservation + // protocol version is not set. + ReservationProtocolVersion_RESERVATION_NONE ReservationProtocolVersion = 0 + // RESERVATION_SERVER_REQUEST is the first version of the reservation + // protocol where the server notifies the client about a reservation. + ReservationProtocolVersion_RESERVATION_SERVER_NOTIFY ReservationProtocolVersion = 1 +) + +// Enum value maps for ReservationProtocolVersion. +var ( + ReservationProtocolVersion_name = map[int32]string{ + 0: "RESERVATION_NONE", + 1: "RESERVATION_SERVER_NOTIFY", + } + ReservationProtocolVersion_value = map[string]int32{ + "RESERVATION_NONE": 0, + "RESERVATION_SERVER_NOTIFY": 1, + } +) + +func (x ReservationProtocolVersion) Enum() *ReservationProtocolVersion { + p := new(ReservationProtocolVersion) + *p = x + return p +} + +func (x ReservationProtocolVersion) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ReservationProtocolVersion) Descriptor() protoreflect.EnumDescriptor { + return file_reservation_proto_enumTypes[0].Descriptor() +} + +func (ReservationProtocolVersion) Type() protoreflect.EnumType { + return &file_reservation_proto_enumTypes[0] +} + +func (x ReservationProtocolVersion) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ReservationProtocolVersion.Descriptor instead. +func (ReservationProtocolVersion) EnumDescriptor() ([]byte, []int) { + return file_reservation_proto_rawDescGZIP(), []int{0} +} + +// ReservationNotificationRequest is an empty request sent from the client to +// the server to open a stream to receive reservation notifications. +type ReservationNotificationRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ReservationNotificationRequest) Reset() { + *x = ReservationNotificationRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_reservation_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReservationNotificationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReservationNotificationRequest) ProtoMessage() {} + +func (x *ReservationNotificationRequest) ProtoReflect() protoreflect.Message { + mi := &file_reservation_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReservationNotificationRequest.ProtoReflect.Descriptor instead. +func (*ReservationNotificationRequest) Descriptor() ([]byte, []int) { + return file_reservation_proto_rawDescGZIP(), []int{0} +} + +// ServerReservationNotification is a notification sent from the server to the +// client if the server wants to open a reservation. +type ServerReservationNotification struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // reservation_id is the id of the reservation. + ReservationId []byte `protobuf:"bytes,1,opt,name=reservation_id,json=reservationId,proto3" json:"reservation_id,omitempty"` + // value is the value of the reservation in satoshis. + Value uint64 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"` + // server_key is the public key of the server. + ServerKey []byte `protobuf:"bytes,3,opt,name=server_key,json=serverKey,proto3" json:"server_key,omitempty"` + // expiry is the absolute expiry of the reservation. + Expiry uint32 `protobuf:"varint,4,opt,name=expiry,proto3" json:"expiry,omitempty"` + // protocol_version is the version of the reservation protocol. + ProtocolVersion ReservationProtocolVersion `protobuf:"varint,5,opt,name=protocol_version,json=protocolVersion,proto3,enum=looprpc.ReservationProtocolVersion" json:"protocol_version,omitempty"` +} + +func (x *ServerReservationNotification) Reset() { + *x = ServerReservationNotification{} + if protoimpl.UnsafeEnabled { + mi := &file_reservation_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ServerReservationNotification) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ServerReservationNotification) ProtoMessage() {} + +func (x *ServerReservationNotification) ProtoReflect() protoreflect.Message { + mi := &file_reservation_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ServerReservationNotification.ProtoReflect.Descriptor instead. +func (*ServerReservationNotification) Descriptor() ([]byte, []int) { + return file_reservation_proto_rawDescGZIP(), []int{1} +} + +func (x *ServerReservationNotification) GetReservationId() []byte { + if x != nil { + return x.ReservationId + } + return nil +} + +func (x *ServerReservationNotification) GetValue() uint64 { + if x != nil { + return x.Value + } + return 0 +} + +func (x *ServerReservationNotification) GetServerKey() []byte { + if x != nil { + return x.ServerKey + } + return nil +} + +func (x *ServerReservationNotification) GetExpiry() uint32 { + if x != nil { + return x.Expiry + } + return 0 +} + +func (x *ServerReservationNotification) GetProtocolVersion() ReservationProtocolVersion { + if x != nil { + return x.ProtocolVersion + } + return ReservationProtocolVersion_RESERVATION_NONE +} + +// ServerOpenReservationRequest is a request sent from the client to the server +// to confirm a reservation opening. +type ServerOpenReservationRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // reservation_id is the id of the reservation. + ReservationId []byte `protobuf:"bytes,1,opt,name=reservation_id,json=reservationId,proto3" json:"reservation_id,omitempty"` + // client_key is the public key of the client. + ClientKey []byte `protobuf:"bytes,2,opt,name=client_key,json=clientKey,proto3" json:"client_key,omitempty"` +} + +func (x *ServerOpenReservationRequest) Reset() { + *x = ServerOpenReservationRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_reservation_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ServerOpenReservationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ServerOpenReservationRequest) ProtoMessage() {} + +func (x *ServerOpenReservationRequest) ProtoReflect() protoreflect.Message { + mi := &file_reservation_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ServerOpenReservationRequest.ProtoReflect.Descriptor instead. +func (*ServerOpenReservationRequest) Descriptor() ([]byte, []int) { + return file_reservation_proto_rawDescGZIP(), []int{2} +} + +func (x *ServerOpenReservationRequest) GetReservationId() []byte { + if x != nil { + return x.ReservationId + } + return nil +} + +func (x *ServerOpenReservationRequest) GetClientKey() []byte { + if x != nil { + return x.ClientKey + } + return nil +} + +// ServerOpenReservationResponse is a response sent from the server to the +// client to confirm a reservation opening. +type ServerOpenReservationResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ServerOpenReservationResponse) Reset() { + *x = ServerOpenReservationResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_reservation_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ServerOpenReservationResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ServerOpenReservationResponse) ProtoMessage() {} + +func (x *ServerOpenReservationResponse) ProtoReflect() protoreflect.Message { + mi := &file_reservation_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ServerOpenReservationResponse.ProtoReflect.Descriptor instead. +func (*ServerOpenReservationResponse) Descriptor() ([]byte, []int) { + return file_reservation_proto_rawDescGZIP(), []int{3} +} + +var File_reservation_proto protoreflect.FileDescriptor + +var file_reservation_proto_rawDesc = []byte{ + 0x0a, 0x11, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x22, 0x20, 0x0a, 0x1e, + 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x6f, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xe3, + 0x01, 0x0a, 0x1d, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1d, 0x0a, + 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x09, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, + 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x65, 0x78, + 0x70, 0x69, 0x72, 0x79, 0x12, 0x4e, 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, + 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, + 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x64, 0x0a, 0x1c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4f, 0x70, + 0x65, 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x72, 0x65, + 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x22, 0x1f, 0x0a, 0x1d, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x4f, 0x70, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x51, 0x0a, 0x1a, 0x52, + 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, + 0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x10, 0x52, 0x45, 0x53, + 0x45, 0x52, 0x56, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, + 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, + 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x4e, 0x4f, 0x54, 0x49, 0x46, 0x59, 0x10, 0x01, 0x32, 0xea, + 0x01, 0x0a, 0x12, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x72, 0x0a, 0x1d, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x27, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, + 0x2e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x6f, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x26, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x6f, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x01, 0x12, 0x60, 0x0a, 0x0f, 0x4f, 0x70, 0x65, + 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x2e, 0x6c, + 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4f, 0x70, 0x65, + 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x4f, 0x70, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2d, 0x5a, 0x2b, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, + 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x6f, 0x6f, 0x70, 0x2f, 0x73, 0x77, 0x61, + 0x70, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_reservation_proto_rawDescOnce sync.Once + file_reservation_proto_rawDescData = file_reservation_proto_rawDesc +) + +func file_reservation_proto_rawDescGZIP() []byte { + file_reservation_proto_rawDescOnce.Do(func() { + file_reservation_proto_rawDescData = protoimpl.X.CompressGZIP(file_reservation_proto_rawDescData) + }) + return file_reservation_proto_rawDescData +} + +var file_reservation_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_reservation_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_reservation_proto_goTypes = []interface{}{ + (ReservationProtocolVersion)(0), // 0: looprpc.ReservationProtocolVersion + (*ReservationNotificationRequest)(nil), // 1: looprpc.ReservationNotificationRequest + (*ServerReservationNotification)(nil), // 2: looprpc.ServerReservationNotification + (*ServerOpenReservationRequest)(nil), // 3: looprpc.ServerOpenReservationRequest + (*ServerOpenReservationResponse)(nil), // 4: looprpc.ServerOpenReservationResponse +} +var file_reservation_proto_depIdxs = []int32{ + 0, // 0: looprpc.ServerReservationNotification.protocol_version:type_name -> looprpc.ReservationProtocolVersion + 1, // 1: looprpc.ReservationService.ReservationNotificationStream:input_type -> looprpc.ReservationNotificationRequest + 3, // 2: looprpc.ReservationService.OpenReservation:input_type -> looprpc.ServerOpenReservationRequest + 2, // 3: looprpc.ReservationService.ReservationNotificationStream:output_type -> looprpc.ServerReservationNotification + 4, // 4: looprpc.ReservationService.OpenReservation:output_type -> looprpc.ServerOpenReservationResponse + 3, // [3:5] is the sub-list for method output_type + 1, // [1:3] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_reservation_proto_init() } +func file_reservation_proto_init() { + if File_reservation_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_reservation_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReservationNotificationRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_reservation_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ServerReservationNotification); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_reservation_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ServerOpenReservationRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_reservation_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ServerOpenReservationResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_reservation_proto_rawDesc, + NumEnums: 1, + NumMessages: 4, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_reservation_proto_goTypes, + DependencyIndexes: file_reservation_proto_depIdxs, + EnumInfos: file_reservation_proto_enumTypes, + MessageInfos: file_reservation_proto_msgTypes, + }.Build() + File_reservation_proto = out.File + file_reservation_proto_rawDesc = nil + file_reservation_proto_goTypes = nil + file_reservation_proto_depIdxs = nil +} diff --git a/swapserverrpc/reservation.proto b/swapserverrpc/reservation.proto new file mode 100644 index 0000000..e29d056 --- /dev/null +++ b/swapserverrpc/reservation.proto @@ -0,0 +1,70 @@ +syntax = "proto3"; + +// We can't change this to swapserverrpc, it would be a breaking change because +// the package name is also contained in the HTTP URIs and old clients would +// call the wrong endpoints. Luckily with the go_package option we can have +// different golang and RPC package names to fix protobuf namespace conflicts. +package looprpc; + +option go_package = "github.com/lightninglabs/loop/swapserverrpc"; + +service ReservationService { + // ReservationNotificationStream is a server side stream that sends + // notifications if the server wants to open a reservation to the client. + rpc ReservationNotificationStream (ReservationNotificationRequest) + returns (stream ServerReservationNotification); + + // OpenReservation requests a new reservation UTXO from the server. + rpc OpenReservation (ServerOpenReservationRequest) + returns (ServerOpenReservationResponse); +} + +// ReservationNotificationRequest is an empty request sent from the client to +// the server to open a stream to receive reservation notifications. +message ReservationNotificationRequest { +} + +// ServerReservationNotification is a notification sent from the server to the +// client if the server wants to open a reservation. +message ServerReservationNotification { + // reservation_id is the id of the reservation. + bytes reservation_id = 1; + + // value is the value of the reservation in satoshis. + uint64 value = 2; + + // server_key is the public key of the server. + bytes server_key = 3; + + // expiry is the absolute expiry of the reservation. + uint32 expiry = 4; + + // protocol_version is the version of the reservation protocol. + ReservationProtocolVersion protocol_version = 5; +} + +// ServerOpenReservationRequest is a request sent from the client to the server +// to confirm a reservation opening. +message ServerOpenReservationRequest { + // reservation_id is the id of the reservation. + bytes reservation_id = 1; + + // client_key is the public key of the client. + bytes client_key = 2; +} + +// ServerOpenReservationResponse is a response sent from the server to the +// client to confirm a reservation opening. +message ServerOpenReservationResponse { +} + +// ReservationProtocolVersion is the version of the reservation protocol. +enum ReservationProtocolVersion { + // RESERVATION_NONE is the default value and means that the reservation + // protocol version is not set. + RESERVATION_NONE = 0; + + // RESERVATION_SERVER_REQUEST is the first version of the reservation + // protocol where the server notifies the client about a reservation. + RESERVATION_SERVER_NOTIFY = 1; +}; diff --git a/swapserverrpc/reservation_grpc.pb.go b/swapserverrpc/reservation_grpc.pb.go new file mode 100644 index 0000000..f7e7a15 --- /dev/null +++ b/swapserverrpc/reservation_grpc.pb.go @@ -0,0 +1,171 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package swapserverrpc + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// ReservationServiceClient is the client API for ReservationService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ReservationServiceClient interface { + // ReservationNotificationStream is a server side stream that sends + // notifications if the server wants to open a reservation to the client. + ReservationNotificationStream(ctx context.Context, in *ReservationNotificationRequest, opts ...grpc.CallOption) (ReservationService_ReservationNotificationStreamClient, error) + // OpenReservation requests a new reservation UTXO from the server. + OpenReservation(ctx context.Context, in *ServerOpenReservationRequest, opts ...grpc.CallOption) (*ServerOpenReservationResponse, error) +} + +type reservationServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewReservationServiceClient(cc grpc.ClientConnInterface) ReservationServiceClient { + return &reservationServiceClient{cc} +} + +func (c *reservationServiceClient) ReservationNotificationStream(ctx context.Context, in *ReservationNotificationRequest, opts ...grpc.CallOption) (ReservationService_ReservationNotificationStreamClient, error) { + stream, err := c.cc.NewStream(ctx, &ReservationService_ServiceDesc.Streams[0], "/looprpc.ReservationService/ReservationNotificationStream", opts...) + if err != nil { + return nil, err + } + x := &reservationServiceReservationNotificationStreamClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type ReservationService_ReservationNotificationStreamClient interface { + Recv() (*ServerReservationNotification, error) + grpc.ClientStream +} + +type reservationServiceReservationNotificationStreamClient struct { + grpc.ClientStream +} + +func (x *reservationServiceReservationNotificationStreamClient) Recv() (*ServerReservationNotification, error) { + m := new(ServerReservationNotification) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *reservationServiceClient) OpenReservation(ctx context.Context, in *ServerOpenReservationRequest, opts ...grpc.CallOption) (*ServerOpenReservationResponse, error) { + out := new(ServerOpenReservationResponse) + err := c.cc.Invoke(ctx, "/looprpc.ReservationService/OpenReservation", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ReservationServiceServer is the server API for ReservationService service. +// All implementations must embed UnimplementedReservationServiceServer +// for forward compatibility +type ReservationServiceServer interface { + // ReservationNotificationStream is a server side stream that sends + // notifications if the server wants to open a reservation to the client. + ReservationNotificationStream(*ReservationNotificationRequest, ReservationService_ReservationNotificationStreamServer) error + // OpenReservation requests a new reservation UTXO from the server. + OpenReservation(context.Context, *ServerOpenReservationRequest) (*ServerOpenReservationResponse, error) + mustEmbedUnimplementedReservationServiceServer() +} + +// UnimplementedReservationServiceServer must be embedded to have forward compatible implementations. +type UnimplementedReservationServiceServer struct { +} + +func (UnimplementedReservationServiceServer) ReservationNotificationStream(*ReservationNotificationRequest, ReservationService_ReservationNotificationStreamServer) error { + return status.Errorf(codes.Unimplemented, "method ReservationNotificationStream not implemented") +} +func (UnimplementedReservationServiceServer) OpenReservation(context.Context, *ServerOpenReservationRequest) (*ServerOpenReservationResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method OpenReservation not implemented") +} +func (UnimplementedReservationServiceServer) mustEmbedUnimplementedReservationServiceServer() {} + +// UnsafeReservationServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ReservationServiceServer will +// result in compilation errors. +type UnsafeReservationServiceServer interface { + mustEmbedUnimplementedReservationServiceServer() +} + +func RegisterReservationServiceServer(s grpc.ServiceRegistrar, srv ReservationServiceServer) { + s.RegisterService(&ReservationService_ServiceDesc, srv) +} + +func _ReservationService_ReservationNotificationStream_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(ReservationNotificationRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(ReservationServiceServer).ReservationNotificationStream(m, &reservationServiceReservationNotificationStreamServer{stream}) +} + +type ReservationService_ReservationNotificationStreamServer interface { + Send(*ServerReservationNotification) error + grpc.ServerStream +} + +type reservationServiceReservationNotificationStreamServer struct { + grpc.ServerStream +} + +func (x *reservationServiceReservationNotificationStreamServer) Send(m *ServerReservationNotification) error { + return x.ServerStream.SendMsg(m) +} + +func _ReservationService_OpenReservation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ServerOpenReservationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ReservationServiceServer).OpenReservation(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.ReservationService/OpenReservation", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ReservationServiceServer).OpenReservation(ctx, req.(*ServerOpenReservationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// ReservationService_ServiceDesc is the grpc.ServiceDesc for ReservationService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ReservationService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "looprpc.ReservationService", + HandlerType: (*ReservationServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "OpenReservation", + Handler: _ReservationService_OpenReservation_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "ReservationNotificationStream", + Handler: _ReservationService_ReservationNotificationStream_Handler, + ServerStreams: true, + }, + }, + Metadata: "reservation.proto", +} From 091c0a86bdc25f760a758e66932d39fd239ee719 Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Fri, 25 Aug 2023 01:42:35 +0200 Subject: [PATCH 08/13] looprpc: add reservation rpcs --- looprpc/client.pb.go | 606 ++++++++++++++++++++++++---------- looprpc/client.proto | 44 +++ looprpc/client.swagger.json | 50 ++- looprpc/client_grpc.pb.go | 40 +++ looprpc/swapclient.pb.json.go | 25 ++ 5 files changed, 586 insertions(+), 179 deletions(-) diff --git a/looprpc/client.pb.go b/looprpc/client.pb.go index 32a4250..f1b6cba 100644 --- a/looprpc/client.pb.go +++ b/looprpc/client.pb.go @@ -468,9 +468,9 @@ type ListSwapsFilter_SwapTypeFilter int32 const ( // ANY indicates that no filter is applied. ListSwapsFilter_ANY ListSwapsFilter_SwapTypeFilter = 0 - // LOOP_OUT indicates an loop out swap (off-chain to on-chain) + // LOOP_OUT indicates an loop out swap (off-chain to on-chain). ListSwapsFilter_LOOP_OUT ListSwapsFilter_SwapTypeFilter = 1 - // LOOP_IN indicates a loop in swap (on-chain to off-chain) + // LOOP_IN indicates a loop in swap (on-chain to off-chain). ListSwapsFilter_LOOP_IN ListSwapsFilter_SwapTypeFilter = 2 ) @@ -3390,6 +3390,192 @@ func (*AbandonSwapResponse) Descriptor() ([]byte, []int) { return file_client_proto_rawDescGZIP(), []int{32} } +type ListReservationsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ListReservationsRequest) Reset() { + *x = ListReservationsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_client_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListReservationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListReservationsRequest) ProtoMessage() {} + +func (x *ListReservationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_client_proto_msgTypes[33] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListReservationsRequest.ProtoReflect.Descriptor instead. +func (*ListReservationsRequest) Descriptor() ([]byte, []int) { + return file_client_proto_rawDescGZIP(), []int{33} +} + +type ListReservationsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The list of all currently known reservations and their status. + Reservations []*ClientReservation `protobuf:"bytes,1,rep,name=reservations,proto3" json:"reservations,omitempty"` +} + +func (x *ListReservationsResponse) Reset() { + *x = ListReservationsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_client_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListReservationsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListReservationsResponse) ProtoMessage() {} + +func (x *ListReservationsResponse) ProtoReflect() protoreflect.Message { + mi := &file_client_proto_msgTypes[34] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListReservationsResponse.ProtoReflect.Descriptor instead. +func (*ListReservationsResponse) Descriptor() ([]byte, []int) { + return file_client_proto_rawDescGZIP(), []int{34} +} + +func (x *ListReservationsResponse) GetReservations() []*ClientReservation { + if x != nil { + return x.Reservations + } + return nil +} + +type ClientReservation struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The reservation id that identifies this reservation. + ReservationId []byte `protobuf:"bytes,1,opt,name=reservation_id,json=reservationId,proto3" json:"reservation_id,omitempty"` + // + //The state the reservation is in. + State string `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"` + // + //The amount that the reservation is for. + Amount uint64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"` + // + //The transaction id of the reservation. + TxId []byte `protobuf:"bytes,4,opt,name=tx_id,json=txId,proto3" json:"tx_id,omitempty"` + // + //The vout of the reservation. + Vout uint32 `protobuf:"varint,5,opt,name=vout,proto3" json:"vout,omitempty"` + // + //The expiry of the reservation. + Expiry uint32 `protobuf:"varint,6,opt,name=expiry,proto3" json:"expiry,omitempty"` +} + +func (x *ClientReservation) Reset() { + *x = ClientReservation{} + if protoimpl.UnsafeEnabled { + mi := &file_client_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ClientReservation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientReservation) ProtoMessage() {} + +func (x *ClientReservation) ProtoReflect() protoreflect.Message { + mi := &file_client_proto_msgTypes[35] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientReservation.ProtoReflect.Descriptor instead. +func (*ClientReservation) Descriptor() ([]byte, []int) { + return file_client_proto_rawDescGZIP(), []int{35} +} + +func (x *ClientReservation) GetReservationId() []byte { + if x != nil { + return x.ReservationId + } + return nil +} + +func (x *ClientReservation) GetState() string { + if x != nil { + return x.State + } + return "" +} + +func (x *ClientReservation) GetAmount() uint64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *ClientReservation) GetTxId() []byte { + if x != nil { + return x.TxId + } + return nil +} + +func (x *ClientReservation) GetVout() uint32 { + if x != nil { + return x.Vout + } + return 0 +} + +func (x *ClientReservation) GetExpiry() uint32 { + if x != nil { + return x.Expiry + } + return 0 +} + var File_client_proto protoreflect.FileDescriptor var file_client_proto_rawDesc = []byte{ @@ -3805,142 +3991,166 @@ var file_client_proto_rawDesc = []byte{ 0x64, 0x6f, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x69, 0x4b, 0x6e, 0x6f, 0x77, 0x57, 0x68, 0x61, 0x74, 0x49, 0x41, 0x6d, 0x44, 0x6f, 0x69, 0x6e, 0x67, 0x22, 0x15, 0x0a, 0x13, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x53, 0x77, 0x61, 0x70, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x3b, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x14, 0x41, 0x44, 0x44, 0x52, 0x45, 0x53, 0x53, 0x5f, - 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x12, - 0x0a, 0x0e, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, - 0x10, 0x01, 0x2a, 0x25, 0x0a, 0x08, 0x53, 0x77, 0x61, 0x70, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0c, - 0x0a, 0x08, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x4f, 0x55, 0x54, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, - 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x49, 0x4e, 0x10, 0x01, 0x2a, 0x73, 0x0a, 0x09, 0x53, 0x77, 0x61, - 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, - 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x50, 0x52, 0x45, 0x49, 0x4d, 0x41, 0x47, - 0x45, 0x5f, 0x52, 0x45, 0x56, 0x45, 0x41, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, - 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x53, 0x48, 0x45, 0x44, 0x10, 0x02, - 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x03, 0x12, 0x0a, 0x0a, - 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x04, 0x12, 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x56, - 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x44, 0x10, 0x05, 0x2a, 0xbe, - 0x02, 0x0a, 0x0d, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, - 0x12, 0x17, 0x0a, 0x13, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, - 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x46, 0x41, 0x49, - 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4f, 0x46, 0x46, 0x43, - 0x48, 0x41, 0x49, 0x4e, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, - 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, - 0x10, 0x02, 0x12, 0x20, 0x0a, 0x1c, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, - 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x53, 0x57, 0x45, 0x45, 0x50, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, - 0x55, 0x54, 0x10, 0x03, 0x12, 0x25, 0x0a, 0x21, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, - 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, - 0x45, 0x4e, 0x54, 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x04, 0x12, 0x1c, 0x0a, 0x18, 0x46, - 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x45, - 0x4d, 0x50, 0x4f, 0x52, 0x41, 0x52, 0x59, 0x10, 0x05, 0x12, 0x23, 0x0a, 0x1f, 0x46, 0x41, 0x49, - 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x43, 0x4f, - 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x06, 0x12, 0x1c, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, + 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x22, 0x5a, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0c, + 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, + 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xa9, 0x01, 0x0a, + 0x11, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x65, + 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, + 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, 0x78, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, + 0x76, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x76, 0x6f, 0x75, 0x74, + 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x2a, 0x3b, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x14, 0x41, 0x44, 0x44, 0x52, 0x45, + 0x53, 0x53, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, + 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, + 0x4b, 0x45, 0x59, 0x10, 0x01, 0x2a, 0x25, 0x0a, 0x08, 0x53, 0x77, 0x61, 0x70, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x4f, 0x55, 0x54, 0x10, 0x00, 0x12, + 0x0b, 0x0a, 0x07, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x49, 0x4e, 0x10, 0x01, 0x2a, 0x73, 0x0a, 0x09, + 0x53, 0x77, 0x61, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x4e, 0x49, + 0x54, 0x49, 0x41, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x50, 0x52, 0x45, 0x49, + 0x4d, 0x41, 0x47, 0x45, 0x5f, 0x52, 0x45, 0x56, 0x45, 0x41, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, + 0x12, 0x0a, 0x0e, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x53, 0x48, 0x45, + 0x44, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x03, + 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x04, 0x12, 0x13, 0x0a, 0x0f, + 0x49, 0x4e, 0x56, 0x4f, 0x49, 0x43, 0x45, 0x5f, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x44, 0x10, + 0x05, 0x2a, 0xbe, 0x02, 0x0a, 0x0d, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x13, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, + 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, + 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4f, + 0x46, 0x46, 0x43, 0x48, 0x41, 0x49, 0x4e, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x46, 0x41, 0x49, + 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, + 0x4f, 0x55, 0x54, 0x10, 0x02, 0x12, 0x20, 0x0a, 0x1c, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, + 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x53, 0x57, 0x45, 0x45, 0x50, 0x5f, 0x54, 0x49, + 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x03, 0x12, 0x25, 0x0a, 0x21, 0x46, 0x41, 0x49, 0x4c, 0x55, + 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, + 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x04, 0x12, 0x1c, 0x0a, 0x18, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, - 0x5f, 0x41, 0x42, 0x41, 0x4e, 0x44, 0x4f, 0x4e, 0x45, 0x44, 0x10, 0x07, 0x12, 0x31, 0x0a, 0x2d, + 0x5f, 0x54, 0x45, 0x4d, 0x50, 0x4f, 0x52, 0x41, 0x52, 0x59, 0x10, 0x05, 0x12, 0x23, 0x0a, 0x1f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, - 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x43, 0x4f, 0x4e, 0x46, - 0x49, 0x52, 0x4d, 0x45, 0x44, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x08, 0x2a, - 0x2f, 0x0a, 0x11, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x52, 0x75, 0x6c, 0x65, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, - 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x48, 0x52, 0x45, 0x53, 0x48, 0x4f, 0x4c, 0x44, 0x10, 0x01, - 0x2a, 0xa6, 0x03, 0x0a, 0x0a, 0x41, 0x75, 0x74, 0x6f, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, - 0x17, 0x0a, 0x13, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x55, - 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x22, 0x0a, 0x1e, 0x41, 0x55, 0x54, 0x4f, - 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x42, 0x55, 0x44, 0x47, 0x45, 0x54, 0x5f, 0x4e, - 0x4f, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, - 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x53, 0x57, 0x45, 0x45, - 0x50, 0x5f, 0x46, 0x45, 0x45, 0x53, 0x10, 0x02, 0x12, 0x1e, 0x0a, 0x1a, 0x41, 0x55, 0x54, 0x4f, - 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x42, 0x55, 0x44, 0x47, 0x45, 0x54, 0x5f, 0x45, - 0x4c, 0x41, 0x50, 0x53, 0x45, 0x44, 0x10, 0x03, 0x12, 0x19, 0x0a, 0x15, 0x41, 0x55, 0x54, 0x4f, - 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x5f, 0x46, 0x4c, 0x49, 0x47, 0x48, - 0x54, 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, - 0x4f, 0x4e, 0x5f, 0x53, 0x57, 0x41, 0x50, 0x5f, 0x46, 0x45, 0x45, 0x10, 0x05, 0x12, 0x19, 0x0a, - 0x15, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4d, 0x49, 0x4e, - 0x45, 0x52, 0x5f, 0x46, 0x45, 0x45, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x41, 0x55, 0x54, 0x4f, - 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x50, 0x52, 0x45, 0x50, 0x41, 0x59, 0x10, 0x07, - 0x12, 0x1f, 0x0a, 0x1b, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, - 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x42, 0x41, 0x43, 0x4b, 0x4f, 0x46, 0x46, 0x10, - 0x08, 0x12, 0x18, 0x0a, 0x14, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, - 0x5f, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x4f, 0x55, 0x54, 0x10, 0x09, 0x12, 0x17, 0x0a, 0x13, 0x41, - 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, - 0x49, 0x4e, 0x10, 0x0a, 0x12, 0x1c, 0x0a, 0x18, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, - 0x53, 0x4f, 0x4e, 0x5f, 0x4c, 0x49, 0x51, 0x55, 0x49, 0x44, 0x49, 0x54, 0x59, 0x5f, 0x4f, 0x4b, - 0x10, 0x0b, 0x12, 0x23, 0x0a, 0x1f, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, - 0x4e, 0x5f, 0x42, 0x55, 0x44, 0x47, 0x45, 0x54, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, - 0x43, 0x49, 0x45, 0x4e, 0x54, 0x10, 0x0c, 0x12, 0x20, 0x0a, 0x1c, 0x41, 0x55, 0x54, 0x4f, 0x5f, - 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x46, 0x45, 0x45, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, - 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x10, 0x0d, 0x32, 0xca, 0x08, 0x0a, 0x0a, 0x53, 0x77, - 0x61, 0x70, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x07, 0x4c, 0x6f, 0x6f, 0x70, - 0x4f, 0x75, 0x74, 0x12, 0x17, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, - 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x6c, - 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x77, 0x61, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x12, 0x16, 0x2e, - 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, - 0x53, 0x77, 0x61, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x07, - 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x12, 0x17, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, - 0x63, 0x2e, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x13, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x77, 0x61, 0x70, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x53, - 0x77, 0x61, 0x70, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x53, 0x77, 0x61, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1a, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, - 0x61, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x08, 0x53, - 0x77, 0x61, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, - 0x63, 0x2e, 0x53, 0x77, 0x61, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x77, 0x61, 0x70, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x48, 0x0a, 0x0b, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, - 0x6e, 0x53, 0x77, 0x61, 0x70, 0x12, 0x1b, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, - 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x53, 0x77, 0x61, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x62, 0x61, - 0x6e, 0x64, 0x6f, 0x6e, 0x53, 0x77, 0x61, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x40, 0x0a, 0x0c, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x54, 0x65, 0x72, 0x6d, 0x73, - 0x12, 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, - 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0c, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x51, 0x75, 0x6f, - 0x74, 0x65, 0x12, 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x6f, - 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, - 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x6f, 0x70, 0x49, - 0x6e, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x12, 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, - 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, - 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x52, + 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x41, 0x4d, 0x4f, 0x55, 0x4e, 0x54, 0x10, + 0x06, 0x12, 0x1c, 0x0a, 0x18, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, + 0x53, 0x4f, 0x4e, 0x5f, 0x41, 0x42, 0x41, 0x4e, 0x44, 0x4f, 0x4e, 0x45, 0x44, 0x10, 0x07, 0x12, + 0x31, 0x0a, 0x2d, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, + 0x4e, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x43, + 0x4f, 0x4e, 0x46, 0x49, 0x52, 0x4d, 0x45, 0x44, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, + 0x10, 0x08, 0x2a, 0x2f, 0x0a, 0x11, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x52, + 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, + 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x48, 0x52, 0x45, 0x53, 0x48, 0x4f, 0x4c, + 0x44, 0x10, 0x01, 0x2a, 0xa6, 0x03, 0x0a, 0x0a, 0x41, 0x75, 0x74, 0x6f, 0x52, 0x65, 0x61, 0x73, + 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x13, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, + 0x4e, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x22, 0x0a, 0x1e, 0x41, + 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x42, 0x55, 0x44, 0x47, 0x45, + 0x54, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, + 0x1a, 0x0a, 0x16, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x53, + 0x57, 0x45, 0x45, 0x50, 0x5f, 0x46, 0x45, 0x45, 0x53, 0x10, 0x02, 0x12, 0x1e, 0x0a, 0x1a, 0x41, + 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x42, 0x55, 0x44, 0x47, 0x45, + 0x54, 0x5f, 0x45, 0x4c, 0x41, 0x50, 0x53, 0x45, 0x44, 0x10, 0x03, 0x12, 0x19, 0x0a, 0x15, 0x41, + 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x5f, 0x46, 0x4c, + 0x49, 0x47, 0x48, 0x54, 0x10, 0x04, 0x12, 0x18, 0x0a, 0x14, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, + 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x53, 0x57, 0x41, 0x50, 0x5f, 0x46, 0x45, 0x45, 0x10, 0x05, + 0x12, 0x19, 0x0a, 0x15, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, + 0x4d, 0x49, 0x4e, 0x45, 0x52, 0x5f, 0x46, 0x45, 0x45, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x41, + 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x50, 0x52, 0x45, 0x50, 0x41, + 0x59, 0x10, 0x07, 0x12, 0x1f, 0x0a, 0x1b, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, + 0x4f, 0x4e, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x42, 0x41, 0x43, 0x4b, 0x4f, + 0x46, 0x46, 0x10, 0x08, 0x12, 0x18, 0x0a, 0x14, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, + 0x53, 0x4f, 0x4e, 0x5f, 0x4c, 0x4f, 0x4f, 0x50, 0x5f, 0x4f, 0x55, 0x54, 0x10, 0x09, 0x12, 0x17, + 0x0a, 0x13, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4c, 0x4f, + 0x4f, 0x50, 0x5f, 0x49, 0x4e, 0x10, 0x0a, 0x12, 0x1c, 0x0a, 0x18, 0x41, 0x55, 0x54, 0x4f, 0x5f, + 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4c, 0x49, 0x51, 0x55, 0x49, 0x44, 0x49, 0x54, 0x59, + 0x5f, 0x4f, 0x4b, 0x10, 0x0b, 0x12, 0x23, 0x0a, 0x1f, 0x41, 0x55, 0x54, 0x4f, 0x5f, 0x52, 0x45, + 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x42, 0x55, 0x44, 0x47, 0x45, 0x54, 0x5f, 0x49, 0x4e, 0x53, 0x55, + 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x10, 0x0c, 0x12, 0x20, 0x0a, 0x1c, 0x41, 0x55, + 0x54, 0x4f, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x46, 0x45, 0x45, 0x5f, 0x49, 0x4e, + 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x10, 0x0d, 0x32, 0xa3, 0x09, 0x0a, + 0x0a, 0x53, 0x77, 0x61, 0x70, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x07, 0x4c, + 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x12, 0x17, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, + 0x2e, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x77, 0x61, 0x70, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, + 0x12, 0x16, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x70, 0x49, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x77, 0x61, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x39, 0x0a, 0x07, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x12, 0x17, 0x2e, 0x6c, 0x6f, 0x6f, + 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x77, + 0x61, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x09, 0x4c, 0x69, + 0x73, 0x74, 0x53, 0x77, 0x61, 0x70, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, + 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x77, 0x61, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x53, 0x77, 0x61, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, + 0x0a, 0x08, 0x53, 0x77, 0x61, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x2e, 0x6c, 0x6f, 0x6f, + 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x77, 0x61, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x77, 0x61, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x48, 0x0a, 0x0b, 0x41, 0x62, 0x61, + 0x6e, 0x64, 0x6f, 0x6e, 0x53, 0x77, 0x61, 0x70, 0x12, 0x1b, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, + 0x70, 0x63, 0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x53, 0x77, 0x61, 0x70, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, + 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x53, 0x77, 0x61, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0c, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x54, 0x65, + 0x72, 0x6d, 0x73, 0x12, 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, + 0x72, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6f, 0x6f, + 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0c, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, + 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, + 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, + 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x75, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4c, 0x6f, - 0x6f, 0x70, 0x49, 0x6e, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, - 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x18, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x51, 0x75, 0x6f, - 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x50, 0x72, - 0x6f, 0x62, 0x65, 0x12, 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x72, - 0x6f, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6c, 0x6f, 0x6f, - 0x70, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x72, 0x6f, 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4c, 0x73, 0x61, 0x74, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x73, 0x12, 0x16, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6c, 0x6f, - 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, - 0x17, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, - 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, - 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x56, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, - 0x74, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, - 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x50, - 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, - 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, - 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x5d, 0x0a, 0x12, 0x53, 0x65, + 0x6f, 0x70, 0x49, 0x6e, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x12, 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, + 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x18, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x54, 0x65, 0x72, + 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0e, 0x47, 0x65, + 0x74, 0x4c, 0x6f, 0x6f, 0x70, 0x49, 0x6e, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x15, 0x2e, 0x6c, + 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, + 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, + 0x05, 0x50, 0x72, 0x6f, 0x62, 0x65, 0x12, 0x15, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x72, 0x6f, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, + 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x72, 0x6f, 0x62, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4c, 0x73, 0x61, 0x74, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x16, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, + 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, + 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x17, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, + 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6f, + 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4c, 0x69, 0x71, 0x75, + 0x69, 0x64, 0x69, 0x74, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x22, 0x2e, 0x6c, 0x6f, + 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, + 0x74, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1c, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, + 0x69, 0x74, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x5d, 0x0a, + 0x12, 0x53, 0x65, 0x74, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x73, 0x12, 0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, - 0x12, 0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4c, 0x69, - 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, - 0x65, 0x74, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0c, 0x53, 0x75, 0x67, - 0x67, 0x65, 0x73, 0x74, 0x53, 0x77, 0x61, 0x70, 0x73, 0x12, 0x1c, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, - 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x67, 0x67, 0x65, 0x73, 0x74, 0x53, 0x77, 0x61, 0x70, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, - 0x63, 0x2e, 0x53, 0x75, 0x67, 0x67, 0x65, 0x73, 0x74, 0x53, 0x77, 0x61, 0x70, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, - 0x62, 0x73, 0x2f, 0x6c, 0x6f, 0x6f, 0x70, 0x2f, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, + 0x63, 0x2e, 0x53, 0x65, 0x74, 0x4c, 0x69, 0x71, 0x75, 0x69, 0x64, 0x69, 0x74, 0x79, 0x50, 0x61, + 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0c, + 0x53, 0x75, 0x67, 0x67, 0x65, 0x73, 0x74, 0x53, 0x77, 0x61, 0x70, 0x73, 0x12, 0x1c, 0x2e, 0x6c, + 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x67, 0x67, 0x65, 0x73, 0x74, 0x53, 0x77, + 0x61, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6f, 0x6f, + 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x67, 0x67, 0x65, 0x73, 0x74, 0x53, 0x77, 0x61, 0x70, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x10, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x20, 0x2e, + 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x65, + 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x21, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, + 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, + 0x6f, 0x6f, 0x70, 0x2f, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -3956,7 +4166,7 @@ func file_client_proto_rawDescGZIP() []byte { } var file_client_proto_enumTypes = make([]protoimpl.EnumInfo, 7) -var file_client_proto_msgTypes = make([]protoimpl.MessageInfo, 33) +var file_client_proto_msgTypes = make([]protoimpl.MessageInfo, 36) var file_client_proto_goTypes = []interface{}{ (AddressType)(0), // 0: looprpc.AddressType (SwapType)(0), // 1: looprpc.SwapType @@ -3998,19 +4208,22 @@ var file_client_proto_goTypes = []interface{}{ (*SuggestSwapsResponse)(nil), // 37: looprpc.SuggestSwapsResponse (*AbandonSwapRequest)(nil), // 38: looprpc.AbandonSwapRequest (*AbandonSwapResponse)(nil), // 39: looprpc.AbandonSwapResponse - (*swapserverrpc.RouteHint)(nil), // 40: looprpc.RouteHint + (*ListReservationsRequest)(nil), // 40: looprpc.ListReservationsRequest + (*ListReservationsResponse)(nil), // 41: looprpc.ListReservationsResponse + (*ClientReservation)(nil), // 42: looprpc.ClientReservation + (*swapserverrpc.RouteHint)(nil), // 43: looprpc.RouteHint } var file_client_proto_depIdxs = []int32{ 0, // 0: looprpc.LoopOutRequest.account_addr_type:type_name -> looprpc.AddressType - 40, // 1: looprpc.LoopInRequest.route_hints:type_name -> looprpc.RouteHint + 43, // 1: looprpc.LoopInRequest.route_hints:type_name -> looprpc.RouteHint 1, // 2: looprpc.SwapStatus.type:type_name -> looprpc.SwapType 2, // 3: looprpc.SwapStatus.state:type_name -> looprpc.SwapState 3, // 4: looprpc.SwapStatus.failure_reason:type_name -> looprpc.FailureReason 13, // 5: looprpc.ListSwapsRequest.list_swap_filter:type_name -> looprpc.ListSwapsFilter 6, // 6: looprpc.ListSwapsFilter.swap_type:type_name -> looprpc.ListSwapsFilter.SwapTypeFilter 11, // 7: looprpc.ListSwapsResponse.swaps:type_name -> looprpc.SwapStatus - 40, // 8: looprpc.QuoteRequest.loop_in_route_hints:type_name -> looprpc.RouteHint - 40, // 9: looprpc.ProbeRequest.route_hints:type_name -> looprpc.RouteHint + 43, // 8: looprpc.QuoteRequest.loop_in_route_hints:type_name -> looprpc.RouteHint + 43, // 9: looprpc.ProbeRequest.route_hints:type_name -> looprpc.RouteHint 26, // 10: looprpc.TokensResponse.tokens:type_name -> looprpc.LsatToken 27, // 11: looprpc.GetInfoResponse.loop_out_stats:type_name -> looprpc.LoopStats 27, // 12: looprpc.GetInfoResponse.loop_in_stats:type_name -> looprpc.LoopStats @@ -4023,43 +4236,46 @@ var file_client_proto_depIdxs = []int32{ 7, // 19: looprpc.SuggestSwapsResponse.loop_out:type_name -> looprpc.LoopOutRequest 8, // 20: looprpc.SuggestSwapsResponse.loop_in:type_name -> looprpc.LoopInRequest 36, // 21: looprpc.SuggestSwapsResponse.disqualified:type_name -> looprpc.Disqualified - 7, // 22: looprpc.SwapClient.LoopOut:input_type -> looprpc.LoopOutRequest - 8, // 23: looprpc.SwapClient.LoopIn:input_type -> looprpc.LoopInRequest - 10, // 24: looprpc.SwapClient.Monitor:input_type -> looprpc.MonitorRequest - 12, // 25: looprpc.SwapClient.ListSwaps:input_type -> looprpc.ListSwapsRequest - 15, // 26: looprpc.SwapClient.SwapInfo:input_type -> looprpc.SwapInfoRequest - 38, // 27: looprpc.SwapClient.AbandonSwap:input_type -> looprpc.AbandonSwapRequest - 16, // 28: looprpc.SwapClient.LoopOutTerms:input_type -> looprpc.TermsRequest - 19, // 29: looprpc.SwapClient.LoopOutQuote:input_type -> looprpc.QuoteRequest - 16, // 30: looprpc.SwapClient.GetLoopInTerms:input_type -> looprpc.TermsRequest - 19, // 31: looprpc.SwapClient.GetLoopInQuote:input_type -> looprpc.QuoteRequest - 22, // 32: looprpc.SwapClient.Probe:input_type -> looprpc.ProbeRequest - 24, // 33: looprpc.SwapClient.GetLsatTokens:input_type -> looprpc.TokensRequest - 28, // 34: looprpc.SwapClient.GetInfo:input_type -> looprpc.GetInfoRequest - 30, // 35: looprpc.SwapClient.GetLiquidityParams:input_type -> looprpc.GetLiquidityParamsRequest - 33, // 36: looprpc.SwapClient.SetLiquidityParams:input_type -> looprpc.SetLiquidityParamsRequest - 35, // 37: looprpc.SwapClient.SuggestSwaps:input_type -> looprpc.SuggestSwapsRequest - 9, // 38: looprpc.SwapClient.LoopOut:output_type -> looprpc.SwapResponse - 9, // 39: looprpc.SwapClient.LoopIn:output_type -> looprpc.SwapResponse - 11, // 40: looprpc.SwapClient.Monitor:output_type -> looprpc.SwapStatus - 14, // 41: looprpc.SwapClient.ListSwaps:output_type -> looprpc.ListSwapsResponse - 11, // 42: looprpc.SwapClient.SwapInfo:output_type -> looprpc.SwapStatus - 39, // 43: looprpc.SwapClient.AbandonSwap:output_type -> looprpc.AbandonSwapResponse - 18, // 44: looprpc.SwapClient.LoopOutTerms:output_type -> looprpc.OutTermsResponse - 21, // 45: looprpc.SwapClient.LoopOutQuote:output_type -> looprpc.OutQuoteResponse - 17, // 46: looprpc.SwapClient.GetLoopInTerms:output_type -> looprpc.InTermsResponse - 20, // 47: looprpc.SwapClient.GetLoopInQuote:output_type -> looprpc.InQuoteResponse - 23, // 48: looprpc.SwapClient.Probe:output_type -> looprpc.ProbeResponse - 25, // 49: looprpc.SwapClient.GetLsatTokens:output_type -> looprpc.TokensResponse - 29, // 50: looprpc.SwapClient.GetInfo:output_type -> looprpc.GetInfoResponse - 31, // 51: looprpc.SwapClient.GetLiquidityParams:output_type -> looprpc.LiquidityParameters - 34, // 52: looprpc.SwapClient.SetLiquidityParams:output_type -> looprpc.SetLiquidityParamsResponse - 37, // 53: looprpc.SwapClient.SuggestSwaps:output_type -> looprpc.SuggestSwapsResponse - 38, // [38:54] is the sub-list for method output_type - 22, // [22:38] is the sub-list for method input_type - 22, // [22:22] is the sub-list for extension type_name - 22, // [22:22] is the sub-list for extension extendee - 0, // [0:22] is the sub-list for field type_name + 42, // 22: looprpc.ListReservationsResponse.reservations:type_name -> looprpc.ClientReservation + 7, // 23: looprpc.SwapClient.LoopOut:input_type -> looprpc.LoopOutRequest + 8, // 24: looprpc.SwapClient.LoopIn:input_type -> looprpc.LoopInRequest + 10, // 25: looprpc.SwapClient.Monitor:input_type -> looprpc.MonitorRequest + 12, // 26: looprpc.SwapClient.ListSwaps:input_type -> looprpc.ListSwapsRequest + 15, // 27: looprpc.SwapClient.SwapInfo:input_type -> looprpc.SwapInfoRequest + 38, // 28: looprpc.SwapClient.AbandonSwap:input_type -> looprpc.AbandonSwapRequest + 16, // 29: looprpc.SwapClient.LoopOutTerms:input_type -> looprpc.TermsRequest + 19, // 30: looprpc.SwapClient.LoopOutQuote:input_type -> looprpc.QuoteRequest + 16, // 31: looprpc.SwapClient.GetLoopInTerms:input_type -> looprpc.TermsRequest + 19, // 32: looprpc.SwapClient.GetLoopInQuote:input_type -> looprpc.QuoteRequest + 22, // 33: looprpc.SwapClient.Probe:input_type -> looprpc.ProbeRequest + 24, // 34: looprpc.SwapClient.GetLsatTokens:input_type -> looprpc.TokensRequest + 28, // 35: looprpc.SwapClient.GetInfo:input_type -> looprpc.GetInfoRequest + 30, // 36: looprpc.SwapClient.GetLiquidityParams:input_type -> looprpc.GetLiquidityParamsRequest + 33, // 37: looprpc.SwapClient.SetLiquidityParams:input_type -> looprpc.SetLiquidityParamsRequest + 35, // 38: looprpc.SwapClient.SuggestSwaps:input_type -> looprpc.SuggestSwapsRequest + 40, // 39: looprpc.SwapClient.ListReservations:input_type -> looprpc.ListReservationsRequest + 9, // 40: looprpc.SwapClient.LoopOut:output_type -> looprpc.SwapResponse + 9, // 41: looprpc.SwapClient.LoopIn:output_type -> looprpc.SwapResponse + 11, // 42: looprpc.SwapClient.Monitor:output_type -> looprpc.SwapStatus + 14, // 43: looprpc.SwapClient.ListSwaps:output_type -> looprpc.ListSwapsResponse + 11, // 44: looprpc.SwapClient.SwapInfo:output_type -> looprpc.SwapStatus + 39, // 45: looprpc.SwapClient.AbandonSwap:output_type -> looprpc.AbandonSwapResponse + 18, // 46: looprpc.SwapClient.LoopOutTerms:output_type -> looprpc.OutTermsResponse + 21, // 47: looprpc.SwapClient.LoopOutQuote:output_type -> looprpc.OutQuoteResponse + 17, // 48: looprpc.SwapClient.GetLoopInTerms:output_type -> looprpc.InTermsResponse + 20, // 49: looprpc.SwapClient.GetLoopInQuote:output_type -> looprpc.InQuoteResponse + 23, // 50: looprpc.SwapClient.Probe:output_type -> looprpc.ProbeResponse + 25, // 51: looprpc.SwapClient.GetLsatTokens:output_type -> looprpc.TokensResponse + 29, // 52: looprpc.SwapClient.GetInfo:output_type -> looprpc.GetInfoResponse + 31, // 53: looprpc.SwapClient.GetLiquidityParams:output_type -> looprpc.LiquidityParameters + 34, // 54: looprpc.SwapClient.SetLiquidityParams:output_type -> looprpc.SetLiquidityParamsResponse + 37, // 55: looprpc.SwapClient.SuggestSwaps:output_type -> looprpc.SuggestSwapsResponse + 41, // 56: looprpc.SwapClient.ListReservations:output_type -> looprpc.ListReservationsResponse + 40, // [40:57] is the sub-list for method output_type + 23, // [23:40] is the sub-list for method input_type + 23, // [23:23] is the sub-list for extension type_name + 23, // [23:23] is the sub-list for extension extendee + 0, // [0:23] is the sub-list for field type_name } func init() { file_client_proto_init() } @@ -4464,6 +4680,42 @@ func file_client_proto_init() { return nil } } + file_client_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListReservationsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_client_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListReservationsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_client_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ClientReservation); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -4471,7 +4723,7 @@ func file_client_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_client_proto_rawDesc, NumEnums: 7, - NumMessages: 33, + NumMessages: 36, NumExtensions: 0, NumServices: 1, }, diff --git a/looprpc/client.proto b/looprpc/client.proto index 3ab2142..6b33852 100644 --- a/looprpc/client.proto +++ b/looprpc/client.proto @@ -109,6 +109,12 @@ service SwapClient { [EXPERIMENTAL]: endpoint is subject to change. */ rpc SuggestSwaps (SuggestSwapsRequest) returns (SuggestSwapsResponse); + + /* loop: `listreservations` + ListReservations returns a list of all reservations the server opened to us. + */ + rpc ListReservations (ListReservationsRequest) + returns (ListReservationsResponse); } message LoopOutRequest { @@ -1242,4 +1248,42 @@ message AbandonSwapRequest { } message AbandonSwapResponse { +} + +message ListReservationsRequest { +} + +message ListReservationsResponse { + /* + The list of all currently known reservations and their status. + */ + repeated ClientReservation reservations = 1; +} + +message ClientReservation { + /* + The reservation id that identifies this reservation. + */ + bytes reservation_id = 1; + + /* + The state the reservation is in. + */ + string state = 2; + /* + The amount that the reservation is for. + */ + uint64 amount = 3; + /* + The transaction id of the reservation. + */ + bytes tx_id = 4; + /* + The vout of the reservation. + */ + uint32 vout = 5; + /* + The expiry of the reservation. + */ + uint32 expiry = 6; } \ No newline at end of file diff --git a/looprpc/client.swagger.json b/looprpc/client.swagger.json index a15306c..03996cc 100644 --- a/looprpc/client.swagger.json +++ b/looprpc/client.swagger.json @@ -465,7 +465,7 @@ "parameters": [ { "name": "list_swap_filter.swap_type", - "description": "The type of the swap.\n\n - ANY: ANY indicates that no filter is applied.\n - 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": "The type of the swap.\n\n - ANY: ANY indicates that no filter is applied.\n - 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).", "in": "query", "required": false, "type": "string", @@ -549,7 +549,7 @@ "LOOP_IN" ], "default": "ANY", - "title": "- ANY: ANY indicates that no filter is applied.\n - 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": " - ANY: ANY indicates that no filter is applied.\n - 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)." }, "looprpcAbandonSwapResponse": { "type": "object" @@ -585,6 +585,40 @@ "default": "AUTO_REASON_UNKNOWN", "description": " - AUTO_REASON_BUDGET_NOT_STARTED: Budget not started indicates that we do not recommend any swaps because\nthe start time for our budget has not arrived yet.\n - AUTO_REASON_SWEEP_FEES: Sweep fees indicates that the estimated fees to sweep swaps are too high\nright now.\n - AUTO_REASON_BUDGET_ELAPSED: Budget elapsed indicates that the autoloop budget for the period has been\nelapsed.\n - AUTO_REASON_IN_FLIGHT: In flight indicates that the limit on in-flight automatically dispatched\nswaps has already been reached.\n - AUTO_REASON_SWAP_FEE: Swap fee indicates that the server fee for a specific swap is too high.\n - AUTO_REASON_MINER_FEE: Miner fee indicates that the miner fee for a specific swap is to high.\n - AUTO_REASON_PREPAY: Prepay indicates that the prepay fee for a specific swap is too high.\n - AUTO_REASON_FAILURE_BACKOFF: Failure backoff indicates that a swap has recently failed for this target,\nand the backoff period has not yet passed.\n - AUTO_REASON_LOOP_OUT: Loop out indicates that a loop out swap is currently utilizing the channel,\nso it is not eligible.\n - AUTO_REASON_LOOP_IN: Loop In indicates that a loop in swap is currently in flight for the peer,\nso it is not eligible.\n - AUTO_REASON_LIQUIDITY_OK: Liquidity ok indicates that a target meets the liquidity balance expressed\nin its rule, so no swap is needed.\n - AUTO_REASON_BUDGET_INSUFFICIENT: Budget insufficient indicates that we cannot perform a swap because we do\nnot have enough pending budget available. This differs from budget elapsed,\nbecause we still have some budget available, but we have allocated it to\nother swaps.\n - AUTO_REASON_FEE_INSUFFICIENT: Fee insufficient indicates that the fee estimate for a swap is higher than\nthe portion of total swap amount that we allow fees to consume." }, + "looprpcClientReservation": { + "type": "object", + "properties": { + "reservation_id": { + "type": "string", + "format": "byte", + "description": "The reservation id that identifies this reservation." + }, + "state": { + "type": "string", + "description": "The state the reservation is in." + }, + "amount": { + "type": "string", + "format": "uint64", + "description": "The amount that the reservation is for." + }, + "tx_id": { + "type": "string", + "format": "byte", + "description": "The transaction id of the reservation." + }, + "vout": { + "type": "integer", + "format": "int64", + "description": "The vout of the reservation." + }, + "expiry": { + "type": "integer", + "format": "int64", + "description": "The expiry of the reservation." + } + } + }, "looprpcDisqualified": { "type": "object", "properties": { @@ -889,6 +923,18 @@ ], "default": "UNKNOWN" }, + "looprpcListReservationsResponse": { + "type": "object", + "properties": { + "reservations": { + "type": "array", + "items": { + "$ref": "#/definitions/looprpcClientReservation" + }, + "description": "The list of all currently known reservations and their status." + } + } + }, "looprpcListSwapsFilter": { "type": "object", "properties": { diff --git a/looprpc/client_grpc.pb.go b/looprpc/client_grpc.pb.go index 09917de..f902806 100644 --- a/looprpc/client_grpc.pb.go +++ b/looprpc/client_grpc.pb.go @@ -83,6 +83,9 @@ type SwapClientClient interface { //Note that only loop out suggestions are currently supported. //[EXPERIMENTAL]: endpoint is subject to change. SuggestSwaps(ctx context.Context, in *SuggestSwapsRequest, opts ...grpc.CallOption) (*SuggestSwapsResponse, error) + // loop: `listreservations` + //ListReservations returns a list of all reservations the server opened to us. + ListReservations(ctx context.Context, in *ListReservationsRequest, opts ...grpc.CallOption) (*ListReservationsResponse, error) } type swapClientClient struct { @@ -260,6 +263,15 @@ func (c *swapClientClient) SuggestSwaps(ctx context.Context, in *SuggestSwapsReq return out, nil } +func (c *swapClientClient) ListReservations(ctx context.Context, in *ListReservationsRequest, opts ...grpc.CallOption) (*ListReservationsResponse, error) { + out := new(ListReservationsResponse) + err := c.cc.Invoke(ctx, "/looprpc.SwapClient/ListReservations", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // SwapClientServer is the server API for SwapClient service. // All implementations must embed UnimplementedSwapClientServer // for forward compatibility @@ -329,6 +341,9 @@ type SwapClientServer interface { //Note that only loop out suggestions are currently supported. //[EXPERIMENTAL]: endpoint is subject to change. SuggestSwaps(context.Context, *SuggestSwapsRequest) (*SuggestSwapsResponse, error) + // loop: `listreservations` + //ListReservations returns a list of all reservations the server opened to us. + ListReservations(context.Context, *ListReservationsRequest) (*ListReservationsResponse, error) mustEmbedUnimplementedSwapClientServer() } @@ -384,6 +399,9 @@ func (UnimplementedSwapClientServer) SetLiquidityParams(context.Context, *SetLiq func (UnimplementedSwapClientServer) SuggestSwaps(context.Context, *SuggestSwapsRequest) (*SuggestSwapsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SuggestSwaps not implemented") } +func (UnimplementedSwapClientServer) ListReservations(context.Context, *ListReservationsRequest) (*ListReservationsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListReservations not implemented") +} func (UnimplementedSwapClientServer) mustEmbedUnimplementedSwapClientServer() {} // UnsafeSwapClientServer may be embedded to opt out of forward compatibility for this service. @@ -688,6 +706,24 @@ func _SwapClient_SuggestSwaps_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _SwapClient_ListReservations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListReservationsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SwapClientServer).ListReservations(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.SwapClient/ListReservations", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SwapClientServer).ListReservations(ctx, req.(*ListReservationsRequest)) + } + return interceptor(ctx, in, info, handler) +} + // SwapClient_ServiceDesc is the grpc.ServiceDesc for SwapClient service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -755,6 +791,10 @@ var SwapClient_ServiceDesc = grpc.ServiceDesc{ MethodName: "SuggestSwaps", Handler: _SwapClient_SuggestSwaps_Handler, }, + { + MethodName: "ListReservations", + Handler: _SwapClient_ListReservations_Handler, + }, }, Streams: []grpc.StreamDesc{ { diff --git a/looprpc/swapclient.pb.json.go b/looprpc/swapclient.pb.json.go index 44ecb14..516b433 100644 --- a/looprpc/swapclient.pb.json.go +++ b/looprpc/swapclient.pb.json.go @@ -437,4 +437,29 @@ func RegisterSwapClientJSONCallbacks(registry map[string]func(ctx context.Contex } callback(string(respBytes), nil) } + + registry["looprpc.SwapClient.ListReservations"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &ListReservationsRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewSwapClientClient(conn) + resp, err := client.ListReservations(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } } From 4d558b14185d73b333099e7b42eb7b740b0df816 Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Fri, 25 Aug 2023 01:41:45 +0200 Subject: [PATCH 09/13] loop: expose server grpc connection --- client.go | 7 +++++++ config.go | 2 ++ 2 files changed, 9 insertions(+) diff --git a/client.go b/client.go index fedc311..cf54815 100644 --- a/client.go +++ b/client.go @@ -19,6 +19,7 @@ import ( "github.com/lightninglabs/loop/sweep" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/routing/route" + "google.golang.org/grpc" "google.golang.org/grpc/status" ) @@ -148,6 +149,7 @@ func NewClient(dbDir string, loopDB loopdb.SwapStore, LndServices: cfg.Lnd, Server: swapServerClient, Store: loopDB, + Conn: swapServerClient.conn, LsatStore: lsatStore, CreateExpiryTimer: func(d time.Duration) <-chan time.Time { return time.NewTimer(d).C @@ -200,6 +202,11 @@ func NewClient(dbDir string, loopDB loopdb.SwapStore, return client, cleanup, nil } +// GetConn returns the gRPC connection to the server. +func (s *Client) GetConn() *grpc.ClientConn { + return s.clientConfig.Conn +} + // FetchSwaps returns all loop in and out swaps currently in the database. func (s *Client) FetchSwaps(ctx context.Context) ([]*SwapInfo, error) { loopOutSwaps, err := s.Store.FetchLoopOutSwaps(ctx) diff --git a/config.go b/config.go index 7edba8b..0e2a7b5 100644 --- a/config.go +++ b/config.go @@ -6,12 +6,14 @@ import ( "github.com/lightninglabs/aperture/lsat" "github.com/lightninglabs/lndclient" "github.com/lightninglabs/loop/loopdb" + "google.golang.org/grpc" ) // clientConfig contains config items for the swap client. type clientConfig struct { LndServices *lndclient.LndServices Server swapServerClient + Conn *grpc.ClientConn Store loopdb.SwapStore LsatStore lsat.Store CreateExpiryTimer func(expiry time.Duration) <-chan time.Time From 49c40d917396dd5f7610e1093f8e32e70cd9b315 Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Fri, 25 Aug 2023 01:42:17 +0200 Subject: [PATCH 10/13] loopd: add reservation handling --- instantout/reservation/manager.go | 24 +++++---- loopd/daemon.go | 82 ++++++++++++++++++++++++++----- loopd/log.go | 4 ++ loopd/perms/perms.go | 4 ++ loopd/swapclient_server.go | 80 +++++++++++++++++++++++++----- 5 files changed, 161 insertions(+), 33 deletions(-) diff --git a/instantout/reservation/manager.go b/instantout/reservation/manager.go index 64c88e4..58baeae 100644 --- a/instantout/reservation/manager.go +++ b/instantout/reservation/manager.go @@ -2,6 +2,7 @@ package reservation import ( "context" + "fmt" "sync" "time" @@ -24,8 +25,8 @@ type Manager struct { sync.Mutex } -// NewReservationManager creates a new reservation manager. -func NewReservationManager(cfg *Config) *Manager { +// NewManager creates a new reservation manager. +func NewManager(cfg *Config) *Manager { return &Manager{ cfg: cfg, activeReservations: make(map[ID]*FSM), @@ -71,7 +72,7 @@ func (m *Manager) Run(ctx context.Context, height int32) error { case reservationRes := <-reservationResChan: log.Debugf("Received reservation %x", reservationRes.ReservationId) - err := m.newReservation( + _, err := m.newReservation( runCtx, uint32(currentHeight), reservationRes, ) if err != nil { @@ -90,19 +91,19 @@ func (m *Manager) Run(ctx context.Context, height int32) error { // newReservation creates a new reservation from the reservation request. func (m *Manager) newReservation(ctx context.Context, currentHeight uint32, - req *reservationrpc.ServerReservationNotification) error { + req *reservationrpc.ServerReservationNotification) (*FSM, error) { var reservationID ID err := reservationID.FromByteSlice( req.ReservationId, ) if err != nil { - return err + return nil, err } serverKey, err := btcec.ParsePubKey(req.ServerKey) if err != nil { - return err + return nil, err } // Create the reservation state machine. We need to pass in the runCtx @@ -136,14 +137,19 @@ func (m *Manager) newReservation(ctx context.Context, currentHeight uint32, // We'll now wait for the reservation to be in the state where it is // waiting to be confirmed. err = reservationFSM.DefaultObserver.WaitForState( - ctx, time.Minute, WaitForConfirmation, + ctx, 5*time.Second, WaitForConfirmation, fsm.WithWaitForStateOption(time.Second), ) if err != nil { - return err + if reservationFSM.LastActionError != nil { + return nil, fmt.Errorf("error waiting for "+ + "state: %v, last action error: %v", + err, reservationFSM.LastActionError) + } + return nil, err } - return nil + return reservationFSM, nil } // RegisterReservationNotifications registers a new reservation notification diff --git a/loopd/daemon.go b/loopd/daemon.go index 0acbb01..d3f48be 100644 --- a/loopd/daemon.go +++ b/loopd/daemon.go @@ -18,7 +18,11 @@ import ( "github.com/lightninglabs/loop" "github.com/lightninglabs/loop/loopd/perms" "github.com/lightninglabs/loop/loopdb" - "github.com/lightninglabs/loop/looprpc" + + "github.com/lightninglabs/loop/instantout/reservation" + loop_looprpc "github.com/lightninglabs/loop/looprpc" + + loop_swaprpc "github.com/lightninglabs/loop/swapserverrpc" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/macaroons" "google.golang.org/grpc" @@ -62,6 +66,10 @@ type Daemon struct { // same process. swapClientServer + // reservationManager is the manager that handles all reservation state + // machines. + reservationManager *reservation.Manager + // ErrChan is an error channel that users of the Daemon struct must use // to detect runtime errors and also whether a shutdown is fully // completed. @@ -226,7 +234,7 @@ func (d *Daemon) startWebServers() error { grpc.UnaryInterceptor(unaryInterceptor), grpc.StreamInterceptor(streamInterceptor), ) - looprpc.RegisterSwapClientServer(d.grpcServer, d) + loop_looprpc.RegisterSwapClientServer(d.grpcServer, d) // Register our debug server if it is compiled in. d.registerDebugServer() @@ -286,7 +294,7 @@ func (d *Daemon) startWebServers() error { restProxyDest, "[::]", "[::1]", 1, ) } - err = looprpc.RegisterSwapClientHandlerFromEndpoint( + err = loop_looprpc.RegisterSwapClientHandlerFromEndpoint( ctx, mux, restProxyDest, proxyOpts, ) if err != nil { @@ -399,7 +407,7 @@ func (d *Daemon) initialize(withMacaroonService bool) error { return err } - swapDb, _, err := openDatabase(d.cfg, chainParams) + swapDb, baseDb, err := openDatabase(d.cfg, chainParams) if err != nil { return err } @@ -413,6 +421,15 @@ func (d *Daemon) initialize(withMacaroonService bool) error { } d.clientCleanup = clientCleanup + // Create a reservation server client. + reservationClient := loop_swaprpc.NewReservationServiceClient( + swapClient.Conn, + ) + + // Both the client RPC server and the swap server client should stop + // on main context cancel. So we create it early and pass it down. + d.mainCtx, d.mainCtxCancel = context.WithCancel(context.Background()) + // Add our debug permissions to our main set of required permissions // if compiled in. for endpoint, perm := range debugRequiredPermissions { @@ -466,17 +483,32 @@ func (d *Daemon) initialize(withMacaroonService bool) error { } } + // Create the reservation rpc server. + reservationStore := reservation.NewSQLStore(baseDb) + reservationConfig := &reservation.Config{ + Store: reservationStore, + Wallet: d.lnd.WalletKit, + ChainNotifier: d.lnd.ChainNotifier, + ReservationClient: reservationClient, + FetchL402: swapClient.Server.FetchL402, + } + + d.reservationManager = reservation.NewManager( + reservationConfig, + ) + // Now finally fully initialize the swap client RPC server instance. d.swapClientServer = swapClientServer{ - config: d.cfg, - network: lndclient.Network(d.cfg.Network), - impl: swapClient, - liquidityMgr: getLiquidityManager(swapClient), - lnd: &d.lnd.LndServices, - swaps: make(map[lntypes.Hash]loop.SwapInfo), - subscribers: make(map[int]chan<- interface{}), - statusChan: make(chan loop.SwapInfo), - mainCtx: d.mainCtx, + config: d.cfg, + network: lndclient.Network(d.cfg.Network), + impl: swapClient, + liquidityMgr: getLiquidityManager(swapClient), + lnd: &d.lnd.LndServices, + swaps: make(map[lntypes.Hash]loop.SwapInfo), + subscribers: make(map[int]chan<- interface{}), + statusChan: make(chan loop.SwapInfo), + mainCtx: d.mainCtx, + reservationManager: d.reservationManager, } // Retrieve all currently existing swaps from the database. @@ -543,6 +575,30 @@ func (d *Daemon) initialize(withMacaroonService bool) error { log.Info("Liquidity manager stopped") }() + // Start the reservation manager. + d.wg.Add(1) + go func() { + defer d.wg.Done() + + // We need to know the current block height to properly + // initialize the reservation manager. + getInfo, err := d.lnd.Client.GetInfo(d.mainCtx) + if err != nil { + d.internalErrChan <- err + return + } + + log.Info("Starting reservation manager") + defer log.Info("Reservation manager stopped") + + err = d.reservationManager.Run( + d.mainCtx, int32(getInfo.BlockHeight), + ) + if err != nil && !errors.Is(err, context.Canceled) { + d.internalErrChan <- err + } + }() + // Last, start our internal error handler. This will return exactly one // error or nil on the main error channel to inform the caller that // something went wrong or that shutdown is complete. We don't add to diff --git a/loopd/log.go b/loopd/log.go index 7a0ce47..b9ccb74 100644 --- a/loopd/log.go +++ b/loopd/log.go @@ -6,6 +6,7 @@ import ( "github.com/lightninglabs/lndclient" "github.com/lightninglabs/loop" "github.com/lightninglabs/loop/fsm" + "github.com/lightninglabs/loop/instantout/reservation" "github.com/lightninglabs/loop/liquidity" "github.com/lightninglabs/loop/loopdb" "github.com/lightningnetwork/lnd" @@ -38,6 +39,9 @@ func SetupLoggers(root *build.RotatingLogWriter, intercept signal.Interceptor) { root, liquidity.Subsystem, intercept, liquidity.UseLogger, ) lnd.AddSubLogger(root, fsm.Subsystem, intercept, fsm.UseLogger) + lnd.AddSubLogger( + root, reservation.Subsystem, intercept, reservation.UseLogger, + ) } // genSubLogger creates a logger for a subsystem. We provide an instance of diff --git a/loopd/perms/perms.go b/loopd/perms/perms.go index 90b7aa2..6e2d078 100644 --- a/loopd/perms/perms.go +++ b/loopd/perms/perms.go @@ -96,4 +96,8 @@ var RequiredPermissions = map[string][]bakery.Op{ Entity: "loop", Action: "in", }}, + "/looprpc.SwapClient/ListReservations": {{ + Entity: "reservation", + Action: "read", + }}, } diff --git a/loopd/swapclient_server.go b/loopd/swapclient_server.go index 14de0eb..31b27db 100644 --- a/loopd/swapclient_server.go +++ b/loopd/swapclient_server.go @@ -18,6 +18,7 @@ import ( "github.com/lightninglabs/aperture/lsat" "github.com/lightninglabs/lndclient" "github.com/lightninglabs/loop" + "github.com/lightninglabs/loop/instantout/reservation" "github.com/lightninglabs/loop/labels" "github.com/lightninglabs/loop/liquidity" "github.com/lightninglabs/loop/loopdb" @@ -74,17 +75,18 @@ type swapClientServer struct { clientrpc.UnimplementedSwapClientServer clientrpc.UnimplementedDebugServer - config *Config - network lndclient.Network - impl *loop.Client - liquidityMgr *liquidity.Manager - lnd *lndclient.LndServices - swaps map[lntypes.Hash]loop.SwapInfo - subscribers map[int]chan<- interface{} - statusChan chan loop.SwapInfo - nextSubscriberID int - swapsLock sync.Mutex - mainCtx context.Context + config *Config + network lndclient.Network + impl *loop.Client + liquidityMgr *liquidity.Manager + lnd *lndclient.LndServices + reservationManager *reservation.Manager + swaps map[lntypes.Hash]loop.SwapInfo + subscribers map[int]chan<- interface{} + statusChan chan loop.SwapInfo + nextSubscriberID int + swapsLock sync.Mutex + mainCtx context.Context } // LoopOut initiates a loop out swap with the given parameters. The call returns @@ -1138,6 +1140,25 @@ func (s *swapClientServer) SuggestSwaps(ctx context.Context, return resp, nil } +// ListReservations lists all existing reservations the client has ever made. +func (s *swapClientServer) ListReservations(ctx context.Context, + _ *clientrpc.ListReservationsRequest) ( + *clientrpc.ListReservationsResponse, error) { + + reservations, err := s.reservationManager.GetReservations( + ctx, + ) + if err != nil { + return nil, err + } + + return &clientrpc.ListReservationsResponse{ + Reservations: ToClientReservations( + reservations, + ), + }, nil +} + func rpcAutoloopReason(reason liquidity.Reason) (clientrpc.AutoReason, error) { switch reason { case liquidity.ReasonNone: @@ -1397,3 +1418,40 @@ func getPublicationDeadline(unixTimestamp uint64) time.Time { return time.Unix(int64(unixTimestamp), 0) } } + +// ToClientReservations converts a slice of server +// reservations to a slice of client reservations. +func ToClientReservations( + res []*reservation.Reservation) []*clientrpc.ClientReservation { + + var result []*clientrpc.ClientReservation + for _, r := range res { + result = append(result, toClientReservation(r)) + } + + return result +} + +// toClientReservation converts a server reservation to a +// client reservation. +func toClientReservation( + res *reservation.Reservation) *clientrpc.ClientReservation { + + var ( + txid []byte + vout uint32 + ) + if res.Outpoint != nil { + txid = res.Outpoint.Hash[:] + vout = res.Outpoint.Index + } + + return &clientrpc.ClientReservation{ + ReservationId: res.ID[:], + State: string(res.State), + Amount: uint64(res.Value), + TxId: txid, + Vout: vout, + Expiry: res.Expiry, + } +} From 30acccbb6f71948a7f383ddc3eace80f9d230070 Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Fri, 25 Aug 2023 01:42:43 +0200 Subject: [PATCH 11/13] loop: add reservation cli commands --- cmd/loop/main.go | 2 +- cmd/loop/reservations.go | 55 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 cmd/loop/reservations.go diff --git a/cmd/loop/main.go b/cmd/loop/main.go index 42ffba3..3ac4cea 100644 --- a/cmd/loop/main.go +++ b/cmd/loop/main.go @@ -147,7 +147,7 @@ func main() { monitorCommand, quoteCommand, listAuthCommand, listSwapsCommand, swapInfoCommand, getLiquidityParamsCommand, setLiquidityRuleCommand, suggestSwapCommand, setParamsCommand, - getInfoCommand, abandonSwapCommand, + getInfoCommand, abandonSwapCommand, reservationsCommands, } err := app.Run(os.Args) diff --git a/cmd/loop/reservations.go b/cmd/loop/reservations.go new file mode 100644 index 0000000..9f5c123 --- /dev/null +++ b/cmd/loop/reservations.go @@ -0,0 +1,55 @@ +package main + +import ( + "context" + + "github.com/lightninglabs/loop/looprpc" + "github.com/urfave/cli" +) + +var reservationsCommands = cli.Command{ + + Name: "reservations", + ShortName: "r", + Usage: "manage reservations", + Description: ` + With loopd running, you can use this command to manage your + reservations. Reservations are 2-of-2 multisig utxos that + the loop server can open to clients. The reservations are used + to enable instant swaps. + `, + Subcommands: []cli.Command{ + listReservationsCommand, + }, +} + +var ( + listReservationsCommand = cli.Command{ + Name: "list", + ShortName: "l", + Usage: "list all reservations", + ArgsUsage: "", + Description: ` + List all reservations. + `, + Action: listReservations, + } +) + +func listReservations(ctx *cli.Context) error { + client, cleanup, err := getClient(ctx) + if err != nil { + return err + } + defer cleanup() + + resp, err := client.ListReservations( + context.Background(), &looprpc.ListReservationsRequest{}, + ) + if err != nil { + return err + } + + printRespJSON(resp) + return nil +} From f00329d7c7e2349ee74baf074689de92bfe1d0ff Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Wed, 17 Jan 2024 15:25:35 +0100 Subject: [PATCH 12/13] loopd: hide reservation manager behind flag. --- loopd/config.go | 2 +- loopd/daemon.go | 64 ++++++++++++++++++++------------------ loopd/swapclient_server.go | 4 +++ 3 files changed, 39 insertions(+), 31 deletions(-) diff --git a/loopd/config.go b/loopd/config.go index 6e92912..3fd5663 100644 --- a/loopd/config.go +++ b/loopd/config.go @@ -168,7 +168,7 @@ type Config struct { TotalPaymentTimeout time.Duration `long:"totalpaymenttimeout" description:"The timeout to use for off-chain payments."` MaxPaymentRetries int `long:"maxpaymentretries" description:"The maximum number of times an off-chain payment may be retried."` - EnableExperimental bool `long:"experimental" description:"Enable experimental features: taproot HTLCs and MuSig2 loop out sweeps."` + EnableExperimental bool `long:"experimental" description:"Enable experimental features: reservations"` Lnd *lndConfig `group:"lnd" namespace:"lnd"` diff --git a/loopd/daemon.go b/loopd/daemon.go index d3f48be..1d7027d 100644 --- a/loopd/daemon.go +++ b/loopd/daemon.go @@ -484,18 +484,20 @@ func (d *Daemon) initialize(withMacaroonService bool) error { } // Create the reservation rpc server. - reservationStore := reservation.NewSQLStore(baseDb) - reservationConfig := &reservation.Config{ - Store: reservationStore, - Wallet: d.lnd.WalletKit, - ChainNotifier: d.lnd.ChainNotifier, - ReservationClient: reservationClient, - FetchL402: swapClient.Server.FetchL402, - } + if d.cfg.EnableExperimental { + reservationStore := reservation.NewSQLStore(baseDb) + reservationConfig := &reservation.Config{ + Store: reservationStore, + Wallet: d.lnd.WalletKit, + ChainNotifier: d.lnd.ChainNotifier, + ReservationClient: reservationClient, + FetchL402: swapClient.Server.FetchL402, + } - d.reservationManager = reservation.NewManager( - reservationConfig, - ) + d.reservationManager = reservation.NewManager( + reservationConfig, + ) + } // Now finally fully initialize the swap client RPC server instance. d.swapClientServer = swapClientServer{ @@ -576,28 +578,30 @@ func (d *Daemon) initialize(withMacaroonService bool) error { }() // Start the reservation manager. - d.wg.Add(1) - go func() { - defer d.wg.Done() + if d.reservationManager != nil { + d.wg.Add(1) + go func() { + defer d.wg.Done() - // We need to know the current block height to properly - // initialize the reservation manager. - getInfo, err := d.lnd.Client.GetInfo(d.mainCtx) - if err != nil { - d.internalErrChan <- err - return - } + // We need to know the current block height to properly + // initialize the reservation manager. + getInfo, err := d.lnd.Client.GetInfo(d.mainCtx) + if err != nil { + d.internalErrChan <- err + return + } - log.Info("Starting reservation manager") - defer log.Info("Reservation manager stopped") + log.Info("Starting reservation manager") + defer log.Info("Reservation manager stopped") - err = d.reservationManager.Run( - d.mainCtx, int32(getInfo.BlockHeight), - ) - if err != nil && !errors.Is(err, context.Canceled) { - d.internalErrChan <- err - } - }() + err = d.reservationManager.Run( + d.mainCtx, int32(getInfo.BlockHeight), + ) + if err != nil && !errors.Is(err, context.Canceled) { + d.internalErrChan <- err + } + }() + } // Last, start our internal error handler. This will return exactly one // error or nil on the main error channel to inform the caller that diff --git a/loopd/swapclient_server.go b/loopd/swapclient_server.go index 31b27db..845885c 100644 --- a/loopd/swapclient_server.go +++ b/loopd/swapclient_server.go @@ -1145,6 +1145,10 @@ func (s *swapClientServer) ListReservations(ctx context.Context, _ *clientrpc.ListReservationsRequest) ( *clientrpc.ListReservationsResponse, error) { + if s.reservationManager == nil { + return nil, status.Error(codes.Unimplemented, + "Restart loop with --experimental") + } reservations, err := s.reservationManager.GetReservations( ctx, ) From 9b178dd9791f508b67d047d71f8314de482309f5 Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Fri, 25 Aug 2023 01:46:25 +0200 Subject: [PATCH 13/13] fsm: add reservation fsm compiling --- fsm/fsm.go | 2 ++ fsm/stateparser/stateparser.go | 8 ++++++++ instantout/reservation/reservation_fsm.md | 17 +++++++++++++++++ scripts/fsm-generate.sh | 3 ++- 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 instantout/reservation/reservation_fsm.md diff --git a/fsm/fsm.go b/fsm/fsm.go index 99fe339..f1f1649 100644 --- a/fsm/fsm.go +++ b/fsm/fsm.go @@ -206,6 +206,8 @@ func (s *StateMachine) SendEvent(event EventType, eventCtx EventContext) error { // current state. state, err := s.getNextState(event) if err != nil { + log.Errorf("unable to get next state: %v from event: "+ + "%v, current state: %v", err, event, s.current) return ErrEventRejected } diff --git a/fsm/stateparser/stateparser.go b/fsm/stateparser/stateparser.go index 9e107d9..d065833 100644 --- a/fsm/stateparser/stateparser.go +++ b/fsm/stateparser/stateparser.go @@ -10,6 +10,7 @@ import ( "sort" "github.com/lightninglabs/loop/fsm" + "github.com/lightninglabs/loop/instantout/reservation" ) func main() { @@ -41,6 +42,13 @@ func run() error { return err } + case "reservation": + reservationFSM := &reservation.FSM{} + err = writeMermaidFile(fp, reservationFSM.GetReservationStates()) + if err != nil { + return err + } + default: fmt.Println("Missing or wrong argument: fsm must be one of:") fmt.Println("\treservations") diff --git a/instantout/reservation/reservation_fsm.md b/instantout/reservation/reservation_fsm.md new file mode 100644 index 0000000..712a1ea --- /dev/null +++ b/instantout/reservation/reservation_fsm.md @@ -0,0 +1,17 @@ +```mermaid +stateDiagram-v2 +[*] --> Init: OnServerRequest +Confirmed +Confirmed --> TimedOut: OnTimedOut +Confirmed --> Confirmed: OnRecover +Failed +Init +Init --> Failed: OnError +Init --> WaitForConfirmation: OnBroadcast +Init --> Failed: OnRecover +TimedOut +WaitForConfirmation +WaitForConfirmation --> WaitForConfirmation: OnRecover +WaitForConfirmation --> Confirmed: OnConfirmed +WaitForConfirmation --> TimedOut: OnTimedOut +``` \ No newline at end of file diff --git a/scripts/fsm-generate.sh b/scripts/fsm-generate.sh index d6b1ce3..60e6803 100755 --- a/scripts/fsm-generate.sh +++ b/scripts/fsm-generate.sh @@ -1,2 +1,3 @@ #!/usr/bin/env bash -go run ./fsm/stateparser/stateparser.go --out ./fsm/example_fsm.md --fsm example \ No newline at end of file +go run ./fsm/stateparser/stateparser.go --out ./fsm/example_fsm.md --fsm example +go run ./fsm/stateparser/stateparser.go --out ./reservation/reservation_fsm.md --fsm reservation \ No newline at end of file