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 }