github.com/decred/dcrlnd@v0.7.6/htlcswitch/payment_result.go (about)

     1  package htlcswitch
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"errors"
     7  	"io"
     8  	"sync"
     9  
    10  	"github.com/decred/dcrlnd/channeldb"
    11  	"github.com/decred/dcrlnd/kvdb"
    12  	"github.com/decred/dcrlnd/lnwire"
    13  	"github.com/decred/dcrlnd/multimutex"
    14  )
    15  
    16  var (
    17  
    18  	// networkResultStoreBucketKey is used for the root level bucket that
    19  	// stores the network result for each payment ID.
    20  	networkResultStoreBucketKey = []byte("network-result-store-bucket")
    21  
    22  	// ErrPaymentIDNotFound is an error returned if the given paymentID is
    23  	// not found.
    24  	ErrPaymentIDNotFound = errors.New("paymentID not found")
    25  
    26  	// ErrPaymentIDAlreadyExists is returned if we try to write a pending
    27  	// payment whose paymentID already exists.
    28  	ErrPaymentIDAlreadyExists = errors.New("paymentID already exists")
    29  )
    30  
    31  // PaymentResult wraps a decoded result received from the network after a
    32  // payment attempt was made. This is what is eventually handed to the router
    33  // for processing.
    34  type PaymentResult struct {
    35  	// Preimage is set by the switch in case a sent HTLC was settled.
    36  	Preimage [32]byte
    37  
    38  	// Error is non-nil in case a HTLC send failed, and the HTLC is now
    39  	// irrevocably canceled. If the payment failed during forwarding, this
    40  	// error will be a *ForwardingError.
    41  	Error error
    42  }
    43  
    44  // networkResult is the raw result received from the network after a payment
    45  // attempt has been made. Since the switch doesn't always have the necessary
    46  // data to decode the raw message, we store it together with some meta data,
    47  // and decode it when the router query for the final result.
    48  type networkResult struct {
    49  	// msg is the received result. This should be of type UpdateFulfillHTLC
    50  	// or UpdateFailHTLC.
    51  	msg lnwire.Message
    52  
    53  	// unencrypted indicates whether the failure encoded in the message is
    54  	// unencrypted, and hence doesn't need to be decrypted.
    55  	unencrypted bool
    56  
    57  	// isResolution indicates whether this is a resolution message, in
    58  	// which the failure reason might not be included.
    59  	isResolution bool
    60  }
    61  
    62  // serializeNetworkResult serializes the networkResult.
    63  func serializeNetworkResult(w io.Writer, n *networkResult) error {
    64  	return channeldb.WriteElements(w, n.msg, n.unencrypted, n.isResolution)
    65  }
    66  
    67  // deserializeNetworkResult deserializes the networkResult.
    68  func deserializeNetworkResult(r io.Reader) (*networkResult, error) {
    69  	n := &networkResult{}
    70  
    71  	if err := channeldb.ReadElements(r,
    72  		&n.msg, &n.unencrypted, &n.isResolution,
    73  	); err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	return n, nil
    78  }
    79  
    80  // networkResultStore is a persistent store that stores any results of HTLCs in
    81  // flight on the network. Since payment results are inherently asynchronous, it
    82  // is used as a common access point for senders of HTLCs, to know when a result
    83  // is back. The Switch will checkpoint any received result to the store, and
    84  // the store will keep results and notify the callers about them.
    85  type networkResultStore struct {
    86  	backend kvdb.Backend
    87  
    88  	// results is a map from paymentIDs to channels where subscribers to
    89  	// payment results will be notified.
    90  	results    map[uint64][]chan *networkResult
    91  	resultsMtx sync.Mutex
    92  
    93  	// paymentIDMtx is a multimutex used to make sure the database and
    94  	// result subscribers map is consistent for each payment ID in case of
    95  	// concurrent callers.
    96  	paymentIDMtx *multimutex.Mutex
    97  }
    98  
    99  func newNetworkResultStore(db kvdb.Backend) *networkResultStore {
   100  	return &networkResultStore{
   101  		backend:      db,
   102  		results:      make(map[uint64][]chan *networkResult),
   103  		paymentIDMtx: multimutex.NewMutex(),
   104  	}
   105  }
   106  
   107  // storeResult stores the networkResult for the given paymentID, and
   108  // notifies any subscribers.
   109  func (store *networkResultStore) storeResult(paymentID uint64,
   110  	result *networkResult) error {
   111  
   112  	// We get a mutex for this payment ID. This is needed to ensure
   113  	// consistency between the database state and the subscribers in case
   114  	// of concurrent calls.
   115  	store.paymentIDMtx.Lock(paymentID)
   116  	defer store.paymentIDMtx.Unlock(paymentID)
   117  
   118  	log.Debugf("Storing result for paymentID=%v", paymentID)
   119  
   120  	// Serialize the payment result.
   121  	var b bytes.Buffer
   122  	if err := serializeNetworkResult(&b, result); err != nil {
   123  		return err
   124  	}
   125  
   126  	var paymentIDBytes [8]byte
   127  	binary.BigEndian.PutUint64(paymentIDBytes[:], paymentID)
   128  
   129  	err := kvdb.Batch(store.backend, func(tx kvdb.RwTx) error {
   130  		networkResults, err := tx.CreateTopLevelBucket(
   131  			networkResultStoreBucketKey,
   132  		)
   133  		if err != nil {
   134  			return err
   135  		}
   136  
   137  		return networkResults.Put(paymentIDBytes[:], b.Bytes())
   138  	})
   139  	if err != nil {
   140  		return err
   141  	}
   142  
   143  	// Now that the result is stored in the database, we can notify any
   144  	// active subscribers.
   145  	store.resultsMtx.Lock()
   146  	for _, res := range store.results[paymentID] {
   147  		res <- result
   148  	}
   149  	delete(store.results, paymentID)
   150  	store.resultsMtx.Unlock()
   151  
   152  	return nil
   153  }
   154  
   155  // subscribeResult is used to get the payment result for the given
   156  // payment ID. It returns a channel on which the result will be delivered when
   157  // ready.
   158  func (store *networkResultStore) subscribeResult(paymentID uint64) (
   159  	<-chan *networkResult, error) {
   160  
   161  	// We get a mutex for this payment ID. This is needed to ensure
   162  	// consistency between the database state and the subscribers in case
   163  	// of concurrent calls.
   164  	store.paymentIDMtx.Lock(paymentID)
   165  	defer store.paymentIDMtx.Unlock(paymentID)
   166  
   167  	log.Debugf("Subscribing to result for paymentID=%v", paymentID)
   168  
   169  	var (
   170  		result     *networkResult
   171  		resultChan = make(chan *networkResult, 1)
   172  	)
   173  
   174  	err := kvdb.View(store.backend, func(tx kvdb.RTx) error {
   175  		var err error
   176  		result, err = fetchResult(tx, paymentID)
   177  		switch {
   178  
   179  		// Result not yet available, we will notify once a result is
   180  		// available.
   181  		case err == ErrPaymentIDNotFound:
   182  			return nil
   183  
   184  		case err != nil:
   185  			return err
   186  
   187  		// The result was found, and will be returned immediately.
   188  		default:
   189  			return nil
   190  		}
   191  	}, func() {
   192  		result = nil
   193  	})
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  
   198  	// If the result was found, we can send it on the result channel
   199  	// imemdiately.
   200  	if result != nil {
   201  		resultChan <- result
   202  		return resultChan, nil
   203  	}
   204  
   205  	// Otherwise we store the result channel for when the result is
   206  	// available.
   207  	store.resultsMtx.Lock()
   208  	store.results[paymentID] = append(
   209  		store.results[paymentID], resultChan,
   210  	)
   211  	store.resultsMtx.Unlock()
   212  
   213  	return resultChan, nil
   214  }
   215  
   216  // getResult attempts to immediately fetch the result for the given pid from
   217  // the store. If no result is available, ErrPaymentIDNotFound is returned.
   218  func (store *networkResultStore) getResult(pid uint64) (
   219  	*networkResult, error) {
   220  
   221  	var result *networkResult
   222  	err := kvdb.View(store.backend, func(tx kvdb.RTx) error {
   223  		var err error
   224  		result, err = fetchResult(tx, pid)
   225  		return err
   226  	}, func() {
   227  		result = nil
   228  	})
   229  	if err != nil {
   230  		return nil, err
   231  	}
   232  
   233  	return result, nil
   234  }
   235  
   236  func fetchResult(tx kvdb.RTx, pid uint64) (*networkResult, error) {
   237  	var paymentIDBytes [8]byte
   238  	binary.BigEndian.PutUint64(paymentIDBytes[:], pid)
   239  
   240  	networkResults := tx.ReadBucket(networkResultStoreBucketKey)
   241  	if networkResults == nil {
   242  		return nil, ErrPaymentIDNotFound
   243  	}
   244  
   245  	// Check whether a result is already available.
   246  	resultBytes := networkResults.Get(paymentIDBytes[:])
   247  	if resultBytes == nil {
   248  		return nil, ErrPaymentIDNotFound
   249  	}
   250  
   251  	// Decode the result we found.
   252  	r := bytes.NewReader(resultBytes)
   253  
   254  	return deserializeNetworkResult(r)
   255  }
   256  
   257  // cleanStore removes all entries from the store, except the payment IDs given.
   258  // NOTE: Since every result not listed in the keep map will be deleted, care
   259  // should be taken to ensure no new payment attempts are being made
   260  // concurrently while this process is ongoing, as its result might end up being
   261  // deleted.
   262  func (store *networkResultStore) cleanStore(keep map[uint64]struct{}) error {
   263  	return kvdb.Update(store.backend, func(tx kvdb.RwTx) error {
   264  		networkResults, err := tx.CreateTopLevelBucket(
   265  			networkResultStoreBucketKey,
   266  		)
   267  		if err != nil {
   268  			return err
   269  		}
   270  
   271  		// Iterate through the bucket, deleting all items not in the
   272  		// keep map.
   273  		var toClean [][]byte
   274  		if err := networkResults.ForEach(func(k, _ []byte) error {
   275  			pid := binary.BigEndian.Uint64(k)
   276  			if _, ok := keep[pid]; ok {
   277  				return nil
   278  			}
   279  
   280  			toClean = append(toClean, k)
   281  			return nil
   282  		}); err != nil {
   283  			return err
   284  		}
   285  
   286  		for _, k := range toClean {
   287  			err := networkResults.Delete(k)
   288  			if err != nil {
   289  				return err
   290  			}
   291  		}
   292  
   293  		if len(toClean) > 0 {
   294  			log.Infof("Removed %d stale entries from network "+
   295  				"result store", len(toClean))
   296  		}
   297  
   298  		return nil
   299  	}, func() {})
   300  }