github.com/decred/dcrlnd@v0.7.6/channeldb/migration23/migration.go (about)

     1  package migration23
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/decred/dcrlnd/kvdb"
     7  )
     8  
     9  var (
    10  	// paymentsRootBucket is the name of the top-level bucket within the
    11  	// database that stores all data related to payments.
    12  	paymentsRootBucket = []byte("payments-root-bucket")
    13  
    14  	// paymentHtlcsBucket is a bucket where we'll store the information
    15  	// about the HTLCs that were attempted for a payment.
    16  	paymentHtlcsBucket = []byte("payment-htlcs-bucket")
    17  
    18  	// oldAttemptInfoKey is a key used in a HTLC's sub-bucket to store the
    19  	// info about the attempt that was done for the HTLC in question.
    20  	oldAttemptInfoKey = []byte("htlc-attempt-info")
    21  
    22  	// oldSettleInfoKey is a key used in a HTLC's sub-bucket to store the
    23  	// settle info, if any.
    24  	oldSettleInfoKey = []byte("htlc-settle-info")
    25  
    26  	// oldFailInfoKey is a key used in a HTLC's sub-bucket to store
    27  	// failure information, if any.
    28  	oldFailInfoKey = []byte("htlc-fail-info")
    29  
    30  	// htlcAttemptInfoKey is the key used as the prefix of an HTLC attempt
    31  	// to store the info about the attempt that was done for the HTLC in
    32  	// question. The HTLC attempt ID is concatenated at the end.
    33  	htlcAttemptInfoKey = []byte("ai")
    34  
    35  	// htlcSettleInfoKey is the key used as the prefix of an HTLC attempt
    36  	// settle info, if any. The HTLC attempt ID is concatenated at the end.
    37  	htlcSettleInfoKey = []byte("si")
    38  
    39  	// htlcFailInfoKey is the key used as the prefix of an HTLC attempt
    40  	// failure information, if any.The  HTLC attempt ID is concatenated at
    41  	// the end.
    42  	htlcFailInfoKey = []byte("fi")
    43  )
    44  
    45  // htlcBucketKey creates a composite key from prefix and id where the result is
    46  // simply the two concatenated. This is the exact copy from payments.go.
    47  func htlcBucketKey(prefix, id []byte) []byte {
    48  	key := make([]byte, len(prefix)+len(id))
    49  	copy(key, prefix)
    50  	copy(key[len(prefix):], id)
    51  	return key
    52  }
    53  
    54  // MigrateHtlcAttempts will gather all htlc-attempt-info's, htlcs-settle-info's
    55  // and htlcs-fail-info's from the attempt ID buckes and re-store them using the
    56  // flattened keys to each payment's payment-htlcs-bucket.
    57  func MigrateHtlcAttempts(tx kvdb.RwTx) error {
    58  	payments := tx.ReadWriteBucket(paymentsRootBucket)
    59  	if payments == nil {
    60  		return nil
    61  	}
    62  
    63  	// Collect all payment hashes so we can migrate payments one-by-one to
    64  	// avoid any bugs bbolt might have when invalidating cursors.
    65  	// For 100 million payments, this would need about 3 GiB memory so we
    66  	// should hopefully be fine for very large nodes too.
    67  	var paymentHashes []string
    68  	if err := payments.ForEach(func(hash, v []byte) error {
    69  		// Get the bucket which contains the payment, fail if the key
    70  		// does not have a bucket.
    71  		bucket := payments.NestedReadBucket(hash)
    72  		if bucket == nil {
    73  			return fmt.Errorf("key must be a bucket: '%v'",
    74  				string(paymentsRootBucket))
    75  		}
    76  
    77  		paymentHashes = append(paymentHashes, string(hash))
    78  		return nil
    79  	}); err != nil {
    80  		return err
    81  	}
    82  
    83  	for _, paymentHash := range paymentHashes {
    84  		payment := payments.NestedReadWriteBucket([]byte(paymentHash))
    85  		if payment.Get(paymentHtlcsBucket) != nil {
    86  			return fmt.Errorf("key must be a bucket: '%v'",
    87  				string(paymentHtlcsBucket))
    88  		}
    89  
    90  		htlcs := payment.NestedReadWriteBucket(paymentHtlcsBucket)
    91  		if htlcs == nil {
    92  			// Nothing to migrate for this payment.
    93  			continue
    94  		}
    95  
    96  		if err := migrateHtlcsBucket(htlcs); err != nil {
    97  			return err
    98  		}
    99  	}
   100  
   101  	return nil
   102  }
   103  
   104  // migrateHtlcsBucket is a helper to gather, transform and re-store htlc attempt
   105  // key/values.
   106  func migrateHtlcsBucket(htlcs kvdb.RwBucket) error {
   107  	// Collect attempt ids so that we can migrate attempts one-by-one
   108  	// to avoid any bugs bbolt might have when invalidating cursors.
   109  	var aids []string
   110  
   111  	// First we collect all htlc attempt ids.
   112  	if err := htlcs.ForEach(func(aid, v []byte) error {
   113  		aids = append(aids, string(aid))
   114  		return nil
   115  	}); err != nil {
   116  		return err
   117  	}
   118  
   119  	// Next we go over these attempts, fetch all data and migrate.
   120  	for _, aid := range aids {
   121  		aidKey := []byte(aid)
   122  		attempt := htlcs.NestedReadWriteBucket(aidKey)
   123  		if attempt == nil {
   124  			return fmt.Errorf("non bucket element '%v' in '%v' "+
   125  				"bucket", aidKey, string(paymentHtlcsBucket))
   126  		}
   127  
   128  		// Collect attempt/settle/fail infos.
   129  		attemptInfo := attempt.Get(oldAttemptInfoKey)
   130  		if len(attemptInfo) > 0 {
   131  			newKey := htlcBucketKey(htlcAttemptInfoKey, aidKey)
   132  			if err := htlcs.Put(newKey, attemptInfo); err != nil {
   133  				return err
   134  			}
   135  		}
   136  
   137  		settleInfo := attempt.Get(oldSettleInfoKey)
   138  		if len(settleInfo) > 0 {
   139  			newKey := htlcBucketKey(htlcSettleInfoKey, aidKey)
   140  			if err := htlcs.Put(newKey, settleInfo); err != nil {
   141  				return err
   142  			}
   143  
   144  		}
   145  
   146  		failInfo := attempt.Get(oldFailInfoKey)
   147  		if len(failInfo) > 0 {
   148  			newKey := htlcBucketKey(htlcFailInfoKey, aidKey)
   149  			if err := htlcs.Put(newKey, failInfo); err != nil {
   150  				return err
   151  			}
   152  		}
   153  	}
   154  
   155  	// Finally we delete old attempt buckets.
   156  	for _, aid := range aids {
   157  		if err := htlcs.DeleteNestedBucket([]byte(aid)); err != nil {
   158  			return err
   159  		}
   160  	}
   161  
   162  	return nil
   163  }