mirror of https://github.com/lightninglabs/loop
Merge pull request #632 from sputn1ck/instantloopout_2
[2/?] Instant loop out: Add reservationsupdate-to-v0.27.0-beta
commit
e9d374a341
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
@ -0,0 +1,271 @@
|
||||
package reservation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"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
|
||||
}
|
||||
|
||||
// NewManager creates a new reservation manager.
|
||||
func NewManager(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) (*FSM, error) {
|
||||
|
||||
var reservationID ID
|
||||
err := reservationID.FromByteSlice(
|
||||
req.ReservationId,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverKey, err := btcec.ParsePubKey(req.ServerKey)
|
||||
if err != nil {
|
||||
return nil, 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, 5*time.Second, WaitForConfirmation,
|
||||
fsm.WithWaitForStateOption(time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
if reservationFSM.LastActionError != nil {
|
||||
return nil, fmt.Errorf("error waiting for "+
|
||||
"state: %v, last action error: %v",
|
||||
err, reservationFSM.LastActionError)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return reservationFSM, 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)
|
||||
}
|
@ -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
|
||||
}
|
@ -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")
|
||||
}
|
@ -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
|
||||
```
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
DROP TABLE IF EXISTS reservation_updates;
|
||||
DROP TABLE IF EXISTS reservations;
|
@ -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
|
||||
);
|
||||
|
@ -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;
|
@ -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
|
||||
}
|
@ -1,2 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
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
|
@ -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
|
||||
}
|
@ -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;
|
||||
};
|
@ -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",
|
||||
}
|
Loading…
Reference in New Issue