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

     1  package routing
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"fmt"
     7  	"sync"
     8  	"time"
     9  
    10  	list "github.com/bahlo/generic-list-go"
    11  	"github.com/decred/dcrd/wire"
    12  	"github.com/decred/dcrlnd/channeldb"
    13  	"github.com/decred/dcrlnd/kvdb"
    14  	"github.com/decred/dcrlnd/lnwire"
    15  )
    16  
    17  var (
    18  	// resultsKey is the fixed key under which the attempt results are
    19  	// stored.
    20  	resultsKey = []byte("missioncontrol-results")
    21  
    22  	// Big endian is the preferred byte order, due to cursor scans over
    23  	// integer keys iterating in order.
    24  	byteOrder = binary.BigEndian
    25  )
    26  
    27  const (
    28  	// unknownFailureSourceIdx is the database encoding of an unknown error
    29  	// source.
    30  	unknownFailureSourceIdx = -1
    31  )
    32  
    33  // missionControlStore is a bbolt db based implementation of a mission control
    34  // store. It stores the raw payment attempt data from which the internal mission
    35  // controls state can be rederived on startup. This allows the mission control
    36  // internal data structure to be changed without requiring a database migration.
    37  // Also changes to mission control parameters can be applied to historical data.
    38  // Finally, it enables importing raw data from an external source.
    39  type missionControlStore struct {
    40  	done    chan struct{}
    41  	wg      sync.WaitGroup
    42  	db      kvdb.Backend
    43  	queueMx sync.Mutex
    44  
    45  	// queue stores all pending payment results not yet added to the store.
    46  	queue *list.List[*paymentResult]
    47  
    48  	// queueChan is signalled when the first item is put into queue after
    49  	// a storeResult().
    50  	queueChan chan struct{}
    51  
    52  	// keys holds the stored MC store item keys in the order of storage.
    53  	// We use this list when adding/deleting items from the database to
    54  	// avoid cursor use which may be slow in the remote DB case.
    55  	keys *list.List[string]
    56  
    57  	// keysMap holds the stored MC store item keys. We use this map to check
    58  	// if a new payment result has already been stored.
    59  	keysMap map[string]struct{}
    60  
    61  	// maxRecords is the maximum amount of records we will store in the db.
    62  	maxRecords int
    63  
    64  	// flushInterval is the configured interval we use to store new results
    65  	// and delete outdated ones from the db.
    66  	flushInterval time.Duration
    67  }
    68  
    69  func newMissionControlStore(db kvdb.Backend, maxRecords int,
    70  	flushInterval time.Duration) (*missionControlStore, error) {
    71  
    72  	var (
    73  		keys    *list.List[string]
    74  		keysMap map[string]struct{}
    75  	)
    76  
    77  	// Create buckets if not yet existing.
    78  	err := kvdb.Update(db, func(tx kvdb.RwTx) error {
    79  		resultsBucket, err := tx.CreateTopLevelBucket(resultsKey)
    80  		if err != nil {
    81  			return fmt.Errorf("cannot create results bucket: %v",
    82  				err)
    83  		}
    84  
    85  		// Collect all keys to be able to quickly calculate the
    86  		// difference when updating the DB state.
    87  		c := resultsBucket.ReadCursor()
    88  		for k, _ := c.First(); k != nil; k, _ = c.Next() {
    89  			keys.PushBack(string(k))
    90  			keysMap[string(k)] = struct{}{}
    91  		}
    92  
    93  		return nil
    94  	}, func() {
    95  		keys = list.New[string]()
    96  		keysMap = make(map[string]struct{})
    97  	})
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	log.Infof("Loaded %d mission control entries", len(keysMap))
   103  
   104  	return &missionControlStore{
   105  		done:          make(chan struct{}),
   106  		db:            db,
   107  		queue:         list.New[*paymentResult](),
   108  		queueChan:     make(chan struct{}, 1),
   109  		keys:          keys,
   110  		keysMap:       keysMap,
   111  		maxRecords:    maxRecords,
   112  		flushInterval: flushInterval,
   113  	}, nil
   114  }
   115  
   116  // clear removes all results from the db.
   117  func (b *missionControlStore) clear() error {
   118  	b.queueMx.Lock()
   119  	defer b.queueMx.Unlock()
   120  
   121  	err := kvdb.Update(b.db, func(tx kvdb.RwTx) error {
   122  		if err := tx.DeleteTopLevelBucket(resultsKey); err != nil {
   123  			return err
   124  		}
   125  
   126  		_, err := tx.CreateTopLevelBucket(resultsKey)
   127  		return err
   128  	}, func() {})
   129  
   130  	if err != nil {
   131  		return err
   132  	}
   133  
   134  	b.queue = list.New[*paymentResult]()
   135  	return nil
   136  }
   137  
   138  // fetchAll returns all results currently stored in the database.
   139  func (b *missionControlStore) fetchAll() ([]*paymentResult, error) {
   140  	var results []*paymentResult
   141  
   142  	err := kvdb.View(b.db, func(tx kvdb.RTx) error {
   143  		resultBucket := tx.ReadBucket(resultsKey)
   144  		results = make([]*paymentResult, 0)
   145  
   146  		return resultBucket.ForEach(func(k, v []byte) error {
   147  			result, err := deserializeResult(k, v)
   148  			if err != nil {
   149  				return err
   150  			}
   151  
   152  			results = append(results, result)
   153  
   154  			return nil
   155  		})
   156  
   157  	}, func() {
   158  		results = nil
   159  	})
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	return results, nil
   165  }
   166  
   167  // serializeResult serializes a payment result and returns a key and value byte
   168  // slice to insert into the bucket.
   169  func serializeResult(rp *paymentResult) ([]byte, []byte, error) {
   170  	// Write timestamps, success status, failure source index and route.
   171  	var b bytes.Buffer
   172  
   173  	var dbFailureSourceIdx int32
   174  	if rp.failureSourceIdx == nil {
   175  		dbFailureSourceIdx = unknownFailureSourceIdx
   176  	} else {
   177  		dbFailureSourceIdx = int32(*rp.failureSourceIdx)
   178  	}
   179  
   180  	err := channeldb.WriteElements(
   181  		&b,
   182  		uint64(rp.timeFwd.UnixNano()),
   183  		uint64(rp.timeReply.UnixNano()),
   184  		rp.success, dbFailureSourceIdx,
   185  	)
   186  	if err != nil {
   187  		return nil, nil, err
   188  	}
   189  
   190  	if err := channeldb.SerializeRoute(&b, *rp.route); err != nil {
   191  		return nil, nil, err
   192  	}
   193  
   194  	// Write failure. If there is no failure message, write an empty
   195  	// byte slice.
   196  	var failureBytes bytes.Buffer
   197  	if rp.failure != nil {
   198  		err := lnwire.EncodeFailureMessage(&failureBytes, rp.failure, 0)
   199  		if err != nil {
   200  			return nil, nil, err
   201  		}
   202  	}
   203  	err = wire.WriteVarBytes(&b, 0, failureBytes.Bytes())
   204  	if err != nil {
   205  		return nil, nil, err
   206  	}
   207  
   208  	// Compose key that identifies this result.
   209  	key := getResultKey(rp)
   210  
   211  	return key, b.Bytes(), nil
   212  }
   213  
   214  // deserializeResult deserializes a payment result.
   215  func deserializeResult(k, v []byte) (*paymentResult, error) {
   216  	// Parse payment id.
   217  	result := paymentResult{
   218  		id: byteOrder.Uint64(k[8:]),
   219  	}
   220  
   221  	r := bytes.NewReader(v)
   222  
   223  	// Read timestamps, success status and failure source index.
   224  	var (
   225  		timeFwd, timeReply uint64
   226  		dbFailureSourceIdx int32
   227  	)
   228  
   229  	err := channeldb.ReadElements(
   230  		r, &timeFwd, &timeReply, &result.success, &dbFailureSourceIdx,
   231  	)
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  
   236  	// Convert time stamps to local time zone for consistent logging.
   237  	result.timeFwd = time.Unix(0, int64(timeFwd)).Local()
   238  	result.timeReply = time.Unix(0, int64(timeReply)).Local()
   239  
   240  	// Convert from unknown index magic number to nil value.
   241  	if dbFailureSourceIdx != unknownFailureSourceIdx {
   242  		failureSourceIdx := int(dbFailureSourceIdx)
   243  		result.failureSourceIdx = &failureSourceIdx
   244  	}
   245  
   246  	// Read route.
   247  	route, err := channeldb.DeserializeRoute(r)
   248  	if err != nil {
   249  		return nil, err
   250  	}
   251  	result.route = &route
   252  
   253  	// Read failure.
   254  	failureBytes, err := wire.ReadVarBytes(
   255  		r, 0, lnwire.FailureMessageLength, "failure",
   256  	)
   257  	if err != nil {
   258  		return nil, err
   259  	}
   260  	if len(failureBytes) > 0 {
   261  		result.failure, err = lnwire.DecodeFailureMessage(
   262  			bytes.NewReader(failureBytes), 0,
   263  		)
   264  		if err != nil {
   265  			return nil, err
   266  		}
   267  	}
   268  
   269  	return &result, nil
   270  }
   271  
   272  // AddResult adds a new result to the db.
   273  func (b *missionControlStore) AddResult(rp *paymentResult) {
   274  	b.queueMx.Lock()
   275  	b.queue.PushBack(rp)
   276  	signalRun := b.queue.Len() == 1
   277  	b.queueMx.Unlock()
   278  
   279  	// Signal run() that the queue is non-empty.
   280  	if signalRun {
   281  		select {
   282  		case <-b.done:
   283  		case b.queueChan <- struct{}{}:
   284  		}
   285  	}
   286  }
   287  
   288  // stop stops the store ticker goroutine.
   289  func (b *missionControlStore) stop() {
   290  	close(b.done)
   291  	b.wg.Wait()
   292  }
   293  
   294  // run runs the MC store ticker goroutine.
   295  func (b *missionControlStore) run() {
   296  	b.wg.Add(1)
   297  
   298  	go func() {
   299  		// TimerChan is non-null when there are items to be stored.
   300  		var timerChan <-chan time.Time
   301  		defer b.wg.Done()
   302  
   303  		for {
   304  			select {
   305  			case <-b.queueChan:
   306  				timerChan = time.After(b.flushInterval)
   307  
   308  			case <-timerChan:
   309  				timerChan = nil
   310  				if err := b.storeResults(); err != nil {
   311  					log.Errorf("Failed to update mission "+
   312  						"control store: %v", err)
   313  				}
   314  
   315  			case <-b.done:
   316  				return
   317  			}
   318  		}
   319  	}()
   320  }
   321  
   322  // storeResults stores all accumulated results.
   323  func (b *missionControlStore) storeResults() error {
   324  	b.queueMx.Lock()
   325  	l := b.queue
   326  
   327  	// Only recreate queue if its not empty because if it is empty we will
   328  	// return early from the func without running any updates.
   329  	isEmpty := l.Len() == 0
   330  	if !isEmpty {
   331  		b.queue = list.New[*paymentResult]()
   332  	}
   333  	b.queueMx.Unlock()
   334  
   335  	// Return early when no new items will be updated.
   336  	if isEmpty {
   337  		return nil
   338  	}
   339  
   340  	// Create a deduped list of new entries.
   341  	newKeys := make(map[string]*paymentResult, l.Len())
   342  	for e := l.Front(); e != nil; {
   343  		pr := e.Value
   344  		key := string(getResultKey(pr))
   345  		if _, ok := b.keysMap[key]; ok {
   346  			e, _ = e.Next(), l.Remove(e)
   347  			continue
   348  		}
   349  		if _, ok := newKeys[key]; ok {
   350  			e, _ = e.Next(), l.Remove(e)
   351  			continue
   352  		}
   353  		newKeys[key] = pr
   354  		e = e.Next()
   355  	}
   356  
   357  	// Create a list of entries to delete.
   358  	toDelete := b.keys.Len() + len(newKeys) - b.maxRecords
   359  	var delKeys []string
   360  	if b.maxRecords > 0 && toDelete > 0 {
   361  		delKeys = make([]string, 0, toDelete)
   362  
   363  		// Delete as many as needed from old keys.
   364  		for e := b.keys.Front(); len(delKeys) < toDelete && e != nil; {
   365  			delKeys = append(delKeys, e.Value)
   366  			e = e.Next()
   367  		}
   368  
   369  		// If more deletions are needed, simply do not add from the
   370  		// list of new keys.
   371  		for e := l.Front(); len(delKeys) < toDelete && e != nil; {
   372  			toDelete--
   373  			pr := e.Value
   374  			key := string(getResultKey(pr))
   375  			delete(newKeys, key)
   376  			l.Remove(e)
   377  			e = l.Front()
   378  		}
   379  	}
   380  
   381  	var storeCount, pruneCount int
   382  
   383  	err := kvdb.Update(b.db, func(tx kvdb.RwTx) error {
   384  		bucket := tx.ReadWriteBucket(resultsKey)
   385  
   386  		for e := l.Front(); e != nil; e = e.Next() {
   387  			pr := e.Value
   388  			// Serialize result into key and value byte slices.
   389  			k, v, err := serializeResult(pr)
   390  			if err != nil {
   391  				return err
   392  			}
   393  
   394  			// Put into results bucket.
   395  			if err := bucket.Put(k, v); err != nil {
   396  				return err
   397  			}
   398  
   399  			storeCount++
   400  		}
   401  
   402  		// Prune oldest entries.
   403  		for _, key := range delKeys {
   404  			if err := bucket.Delete([]byte(key)); err != nil {
   405  				return err
   406  			}
   407  			pruneCount++
   408  		}
   409  
   410  		return nil
   411  	}, func() {
   412  		storeCount, pruneCount = 0, 0
   413  	})
   414  
   415  	if err != nil {
   416  		return err
   417  	}
   418  
   419  	log.Debugf("Stored mission control results: %d added, %d deleted",
   420  		storeCount, pruneCount)
   421  
   422  	// DB Update was successful, update the in-memory cache.
   423  	for _, key := range delKeys {
   424  		delete(b.keysMap, key)
   425  		b.keys.Remove(b.keys.Front())
   426  	}
   427  	for e := l.Front(); e != nil; e = e.Next() {
   428  		pr := e.Value
   429  		key := string(getResultKey(pr))
   430  		b.keys.PushBack(key)
   431  	}
   432  
   433  	return nil
   434  }
   435  
   436  // getResultKey returns a byte slice representing a unique key for this payment
   437  // result.
   438  func getResultKey(rp *paymentResult) []byte {
   439  	var keyBytes [8 + 8 + 33]byte
   440  
   441  	// Identify records by a combination of time, payment id and sender pub
   442  	// key. This allows importing mission control data from an external
   443  	// source without key collisions and keeps the records sorted
   444  	// chronologically.
   445  	byteOrder.PutUint64(keyBytes[:], uint64(rp.timeReply.UnixNano()))
   446  	byteOrder.PutUint64(keyBytes[8:], rp.id)
   447  	copy(keyBytes[16:], rp.route.SourcePubKey[:])
   448  
   449  	return keyBytes[:]
   450  }