github.com/decred/dcrlnd@v0.7.6/routing/control_tower.go (about)

     1  package routing
     2  
     3  import (
     4  	"sync"
     5  
     6  	"github.com/decred/dcrlnd/channeldb"
     7  	"github.com/decred/dcrlnd/lntypes"
     8  	"github.com/decred/dcrlnd/multimutex"
     9  	"github.com/decred/dcrlnd/queue"
    10  )
    11  
    12  // ControlTower tracks all outgoing payments made, whose primary purpose is to
    13  // prevent duplicate payments to the same payment hash. In production, a
    14  // persistent implementation is preferred so that tracking can survive across
    15  // restarts. Payments are transitioned through various payment states, and the
    16  // ControlTower interface provides access to driving the state transitions.
    17  type ControlTower interface {
    18  	// This method checks that no suceeded payment exist for this payment
    19  	// hash.
    20  	InitPayment(lntypes.Hash, *channeldb.PaymentCreationInfo) error
    21  
    22  	// RegisterAttempt atomically records the provided HTLCAttemptInfo.
    23  	RegisterAttempt(lntypes.Hash, *channeldb.HTLCAttemptInfo) error
    24  
    25  	// SettleAttempt marks the given attempt settled with the preimage. If
    26  	// this is a multi shard payment, this might implicitly mean the the
    27  	// full payment succeeded.
    28  	//
    29  	// After invoking this method, InitPayment should always return an
    30  	// error to prevent us from making duplicate payments to the same
    31  	// payment hash. The provided preimage is atomically saved to the DB
    32  	// for record keeping.
    33  	SettleAttempt(lntypes.Hash, uint64, *channeldb.HTLCSettleInfo) (
    34  		*channeldb.HTLCAttempt, error)
    35  
    36  	// FailAttempt marks the given payment attempt failed.
    37  	FailAttempt(lntypes.Hash, uint64, *channeldb.HTLCFailInfo) (
    38  		*channeldb.HTLCAttempt, error)
    39  
    40  	// FetchPayment fetches the payment corresponding to the given payment
    41  	// hash.
    42  	FetchPayment(paymentHash lntypes.Hash) (*channeldb.MPPayment, error)
    43  
    44  	// Fail transitions a payment into the Failed state, and records the
    45  	// ultimate reason the payment failed. Note that this should only be
    46  	// called when all active active attempts are already failed. After
    47  	// invoking this method, InitPayment should return nil on its next call
    48  	// for this payment hash, allowing the user to make a subsequent
    49  	// payment.
    50  	Fail(lntypes.Hash, channeldb.FailureReason) error
    51  
    52  	// FetchInFlightPayments returns all payments with status InFlight.
    53  	FetchInFlightPayments() ([]*channeldb.MPPayment, error)
    54  
    55  	// SubscribePayment subscribes to updates for the payment with the given
    56  	// hash. A first update with the current state of the payment is always
    57  	// sent out immediately.
    58  	SubscribePayment(paymentHash lntypes.Hash) (*ControlTowerSubscriber,
    59  		error)
    60  }
    61  
    62  // ControlTowerSubscriber contains the state for a payment update subscriber.
    63  type ControlTowerSubscriber struct {
    64  	// Updates is the channel over which *channeldb.MPPayment updates can be
    65  	// received.
    66  	Updates <-chan interface{}
    67  
    68  	queue *queue.ConcurrentQueue
    69  	quit  chan struct{}
    70  }
    71  
    72  // newControlTowerSubscriber instantiates a new subscriber state object.
    73  func newControlTowerSubscriber() *ControlTowerSubscriber {
    74  	// Create a queue for payment updates.
    75  	queue := queue.NewConcurrentQueue(20)
    76  	queue.Start()
    77  
    78  	return &ControlTowerSubscriber{
    79  		Updates: queue.ChanOut(),
    80  		queue:   queue,
    81  		quit:    make(chan struct{}),
    82  	}
    83  }
    84  
    85  // Close signals that the subscriber is no longer interested in updates.
    86  func (s *ControlTowerSubscriber) Close() {
    87  	// Close quit channel so that any pending writes to the queue are
    88  	// cancelled.
    89  	close(s.quit)
    90  
    91  	// Stop the queue goroutine so that it won't leak.
    92  	s.queue.Stop()
    93  }
    94  
    95  // controlTower is persistent implementation of ControlTower to restrict
    96  // double payment sending.
    97  type controlTower struct {
    98  	db *channeldb.PaymentControl
    99  
   100  	subscribers    map[lntypes.Hash][]*ControlTowerSubscriber
   101  	subscribersMtx sync.Mutex
   102  
   103  	// paymentsMtx provides synchronization on the payment level to ensure
   104  	// that no race conditions occur in between updating the database and
   105  	// sending a notification.
   106  	paymentsMtx *multimutex.HashMutex
   107  }
   108  
   109  // NewControlTower creates a new instance of the controlTower.
   110  func NewControlTower(db *channeldb.PaymentControl) ControlTower {
   111  	return &controlTower{
   112  		db:          db,
   113  		subscribers: make(map[lntypes.Hash][]*ControlTowerSubscriber),
   114  		paymentsMtx: multimutex.NewHashMutex(),
   115  	}
   116  }
   117  
   118  // InitPayment checks or records the given PaymentCreationInfo with the DB,
   119  // making sure it does not already exist as an in-flight payment. Then this
   120  // method returns successfully, the payment is guranteeed to be in the InFlight
   121  // state.
   122  func (p *controlTower) InitPayment(paymentHash lntypes.Hash,
   123  	info *channeldb.PaymentCreationInfo) error {
   124  
   125  	return p.db.InitPayment(paymentHash, info)
   126  }
   127  
   128  // RegisterAttempt atomically records the provided HTLCAttemptInfo to the
   129  // DB.
   130  func (p *controlTower) RegisterAttempt(paymentHash lntypes.Hash,
   131  	attempt *channeldb.HTLCAttemptInfo) error {
   132  
   133  	p.paymentsMtx.Lock(paymentHash)
   134  	defer p.paymentsMtx.Unlock(paymentHash)
   135  
   136  	payment, err := p.db.RegisterAttempt(paymentHash, attempt)
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	// Notify subscribers of the attempt registration.
   142  	p.notifySubscribers(paymentHash, payment)
   143  
   144  	return nil
   145  }
   146  
   147  // SettleAttempt marks the given attempt settled with the preimage. If
   148  // this is a multi shard payment, this might implicitly mean the the
   149  // full payment succeeded.
   150  func (p *controlTower) SettleAttempt(paymentHash lntypes.Hash,
   151  	attemptID uint64, settleInfo *channeldb.HTLCSettleInfo) (
   152  	*channeldb.HTLCAttempt, error) {
   153  
   154  	p.paymentsMtx.Lock(paymentHash)
   155  	defer p.paymentsMtx.Unlock(paymentHash)
   156  
   157  	payment, err := p.db.SettleAttempt(paymentHash, attemptID, settleInfo)
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  
   162  	// Notify subscribers of success event.
   163  	p.notifySubscribers(paymentHash, payment)
   164  
   165  	return payment.GetAttempt(attemptID)
   166  }
   167  
   168  // FailAttempt marks the given payment attempt failed.
   169  func (p *controlTower) FailAttempt(paymentHash lntypes.Hash,
   170  	attemptID uint64, failInfo *channeldb.HTLCFailInfo) (
   171  	*channeldb.HTLCAttempt, error) {
   172  
   173  	p.paymentsMtx.Lock(paymentHash)
   174  	defer p.paymentsMtx.Unlock(paymentHash)
   175  
   176  	payment, err := p.db.FailAttempt(paymentHash, attemptID, failInfo)
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  
   181  	// Notify subscribers of failed attempt.
   182  	p.notifySubscribers(paymentHash, payment)
   183  
   184  	return payment.GetAttempt(attemptID)
   185  }
   186  
   187  // FetchPayment fetches the payment corresponding to the given payment hash.
   188  func (p *controlTower) FetchPayment(paymentHash lntypes.Hash) (
   189  	*channeldb.MPPayment, error) {
   190  
   191  	return p.db.FetchPayment(paymentHash)
   192  }
   193  
   194  // Fail transitions a payment into the Failed state, and records the reason the
   195  // payment failed. After invoking this method, InitPayment should return nil on
   196  // its next call for this payment hash, allowing the switch to make a
   197  // subsequent payment.
   198  func (p *controlTower) Fail(paymentHash lntypes.Hash,
   199  	reason channeldb.FailureReason) error {
   200  
   201  	p.paymentsMtx.Lock(paymentHash)
   202  	defer p.paymentsMtx.Unlock(paymentHash)
   203  
   204  	payment, err := p.db.Fail(paymentHash, reason)
   205  	if err != nil {
   206  		return err
   207  	}
   208  
   209  	// Notify subscribers of fail event.
   210  	p.notifySubscribers(paymentHash, payment)
   211  
   212  	return nil
   213  }
   214  
   215  // FetchInFlightPayments returns all payments with status InFlight.
   216  func (p *controlTower) FetchInFlightPayments() ([]*channeldb.MPPayment, error) {
   217  	return p.db.FetchInFlightPayments()
   218  }
   219  
   220  // SubscribePayment subscribes to updates for the payment with the given hash. A
   221  // first update with the current state of the payment is always sent out
   222  // immediately.
   223  func (p *controlTower) SubscribePayment(paymentHash lntypes.Hash) (
   224  	*ControlTowerSubscriber, error) {
   225  
   226  	// Take lock before querying the db to prevent missing or duplicating an
   227  	// update.
   228  	p.paymentsMtx.Lock(paymentHash)
   229  	defer p.paymentsMtx.Unlock(paymentHash)
   230  
   231  	payment, err := p.db.FetchPayment(paymentHash)
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  
   236  	subscriber := newControlTowerSubscriber()
   237  
   238  	// Always write current payment state to the channel.
   239  	subscriber.queue.ChanIn() <- payment
   240  
   241  	// Payment is currently in flight. Register this subscriber for further
   242  	// updates. Otherwise this update is the final update and the incoming
   243  	// channel can be closed. This will close the queue's outgoing channel
   244  	// when all updates have been written.
   245  	if payment.Status == channeldb.StatusInFlight {
   246  		p.subscribersMtx.Lock()
   247  		p.subscribers[paymentHash] = append(
   248  			p.subscribers[paymentHash], subscriber,
   249  		)
   250  		p.subscribersMtx.Unlock()
   251  	} else {
   252  		close(subscriber.queue.ChanIn())
   253  	}
   254  
   255  	return subscriber, nil
   256  }
   257  
   258  // notifySubscribers sends a final payment event to all subscribers of this
   259  // payment. The channel will be closed after this. Note that this function must
   260  // be executed atomically (by means of a lock) with the database update to
   261  // guarantuee consistency of the notifications.
   262  func (p *controlTower) notifySubscribers(paymentHash lntypes.Hash,
   263  	event *channeldb.MPPayment) {
   264  
   265  	// Get all subscribers for this payment.
   266  	p.subscribersMtx.Lock()
   267  	list, ok := p.subscribers[paymentHash]
   268  	if !ok {
   269  		p.subscribersMtx.Unlock()
   270  		return
   271  	}
   272  
   273  	// If the payment reached a terminal state, the subscriber list can be
   274  	// cleared. There won't be any more updates.
   275  	terminal := event.Status != channeldb.StatusInFlight
   276  	if terminal {
   277  		delete(p.subscribers, paymentHash)
   278  	}
   279  	p.subscribersMtx.Unlock()
   280  
   281  	// Notify all subscribers of the event.
   282  	for _, subscriber := range list {
   283  		select {
   284  		case subscriber.queue.ChanIn() <- event:
   285  			// If this event is the last, close the incoming channel
   286  			// of the queue. This will signal the subscriber that
   287  			// there won't be any more updates.
   288  			if terminal {
   289  				close(subscriber.queue.ChanIn())
   290  			}
   291  
   292  		// If subscriber disappeared, skip notification. For further
   293  		// notifications, we'll keep skipping over this subscriber.
   294  		case <-subscriber.quit:
   295  		}
   296  	}
   297  }