mirror of https://github.com/lightninglabs/loop
reservation: add reservation fsm and actions
This commit adds the reservation state machine and actions to the reservation package.pull/687/head
parent
ad7d80a878
commit
d277d95e36
@ -0,0 +1,171 @@
|
|||||||
|
package reservation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(
|
||||||
|
r.ctx, 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(
|
||||||
|
r.ctx,
|
||||||
|
)
|
||||||
|
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,225 @@
|
|||||||
|
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")
|
||||||
|
|
||||||
|
// Swept is the state where the reservation has been swept by the server.
|
||||||
|
Swept = fsm.StateType("Swept")
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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, Swept, TimedOut:
|
||||||
|
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,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")
|
||||||
|
}
|
Loading…
Reference in New Issue