From 63d7f5fca7304a7e135b6d8ed4c0d81ca56ae69b Mon Sep 17 00:00:00 2001 From: Andras Banki-Horvath Date: Mon, 16 Aug 2021 18:59:48 +0200 Subject: [PATCH] channeldb: flatten the payment-htlcs-bucket --- channeldb/payment_control.go | 25 ++--- channeldb/payments.go | 174 +++++++++++++++++++++-------------- 2 files changed, 114 insertions(+), 85 deletions(-) diff --git a/channeldb/payment_control.go b/channeldb/payment_control.go index 454e28316..d024bf6bf 100644 --- a/channeldb/payment_control.go +++ b/channeldb/payment_control.go @@ -368,14 +368,10 @@ func (p *PaymentControl) RegisterAttempt(paymentHash lntypes.Hash, return err } - // Create bucket for this attempt. Fail if the bucket already - // exists. - htlcBucket, err := htlcsBucket.CreateBucket(htlcIDBytes) - if err != nil { - return err - } - - err = htlcBucket.Put(htlcAttemptInfoKey, htlcInfoBytes) + err = htlcsBucket.Put( + htlcBucketKey(htlcAttemptInfoKey, htlcIDBytes), + htlcInfoBytes, + ) if err != nil { return err } @@ -427,8 +423,8 @@ func (p *PaymentControl) FailAttempt(hash lntypes.Hash, func (p *PaymentControl) updateHtlcKey(paymentHash lntypes.Hash, attemptID uint64, key, value []byte) (*MPPayment, error) { - htlcIDBytes := make([]byte, 8) - binary.BigEndian.PutUint64(htlcIDBytes, attemptID) + aid := make([]byte, 8) + binary.BigEndian.PutUint64(aid, attemptID) var payment *MPPayment err := kvdb.Batch(p.db.Backend, func(tx kvdb.RwTx) error { @@ -456,23 +452,22 @@ func (p *PaymentControl) updateHtlcKey(paymentHash lntypes.Hash, return fmt.Errorf("htlcs bucket not found") } - htlcBucket := htlcsBucket.NestedReadWriteBucket(htlcIDBytes) - if htlcBucket == nil { + if htlcsBucket.Get(htlcBucketKey(htlcAttemptInfoKey, aid)) == nil { return fmt.Errorf("HTLC with ID %v not registered", attemptID) } // Make sure the shard is not already failed or settled. - if htlcBucket.Get(htlcFailInfoKey) != nil { + if htlcsBucket.Get(htlcBucketKey(htlcFailInfoKey, aid)) != nil { return ErrAttemptAlreadyFailed } - if htlcBucket.Get(htlcSettleInfoKey) != nil { + if htlcsBucket.Get(htlcBucketKey(htlcSettleInfoKey, aid)) != nil { return ErrAttemptAlreadySettled } // Add or update the key for this htlc. - err = htlcBucket.Put(key, value) + err = htlcsBucket.Put(htlcBucketKey(key, aid), value) if err != nil { return err } diff --git a/channeldb/payments.go b/channeldb/payments.go index f044d0f1e..8024a1329 100644 --- a/channeldb/payments.go +++ b/channeldb/payments.go @@ -35,14 +35,11 @@ var ( // | | // | |--payment-htlcs-bucket (shard-bucket) // | | | - // | | |-- - // | | | |--htlc-attempt-info-key: - // | | | |--htlc-settle-info-key: <(optional) settle info> - // | | | |--htlc-fail-info-key: <(optional) fail info> + // | | |-- ai: + // | | |-- si: <(optional) settle info> + // | | |-- fi: <(optional) fail info> // | | | - // | | |-- - // | | | | - // | | ... ... + // | | ... // | | // | | // | |--duplicate-bucket (only for old, completed payments) @@ -50,9 +47,9 @@ var ( // | |-- // | | |--sequence-key: // | | |--creation-info-key: - // | | |--attempt-info-key: - // | | |--settle-info-key: - // | | |--fail-info-key: + // | | |--ai: + // | | |--si: + // | | |--fi: // | | // | |-- // | | | @@ -77,17 +74,19 @@ var ( // about the HTLCs that were attempted for a payment. paymentHtlcsBucket = []byte("payment-htlcs-bucket") - // htlcAttemptInfoKey is a key used in a HTLC's sub-bucket to store the - // info about the attempt that was done for the HTLC in question. - htlcAttemptInfoKey = []byte("htlc-attempt-info") + // htlcAttemptInfoKey is the key used as the prefix of an HTLC attempt + // to store the info about the attempt that was done for the HTLC in + // question. The HTLC attempt ID is concatenated at the end. + htlcAttemptInfoKey = []byte("ai") - // htlcSettleInfoKey is a key used in a HTLC's sub-bucket to store the - // settle info, if any. - htlcSettleInfoKey = []byte("htlc-settle-info") + // htlcSettleInfoKey is the key used as the prefix of an HTLC attempt + // settle info, if any. The HTLC attempt ID is concatenated at the end. + htlcSettleInfoKey = []byte("si") - // htlcFailInfoKey is a key used in a HTLC's sub-bucket to store - // failure information, if any. - htlcFailInfoKey = []byte("htlc-fail-info") + // htlcFailInfoKey is the key used as the prefix of an HTLC attempt + // failure information, if any.The HTLC attempt ID is concatenated at + // the end. + htlcFailInfoKey = []byte("fi") // paymentFailInfoKey is a key used in the payment's sub-bucket to // store information about the reason a payment failed. @@ -230,6 +229,15 @@ type PaymentCreationInfo struct { PaymentRequest []byte } +// htlcBucketKey creates a composite key from prefix and id where the result is +// simply the two concatenated. +func htlcBucketKey(prefix, id []byte) []byte { + key := make([]byte, len(prefix)+len(id)) + copy(key, prefix) + copy(key[len(prefix):], id) + return key +} + // FetchPayments returns all sent payments found in the DB. // // nolint: dupl @@ -378,80 +386,93 @@ func fetchPayment(bucket kvdb.RBucket) (*MPPayment, error) { // fetchHtlcAttempts retrives all htlc attempts made for the payment found in // the given bucket. func fetchHtlcAttempts(bucket kvdb.RBucket) ([]HTLCAttempt, error) { - htlcs := make([]HTLCAttempt, 0) + htlcsMap := make(map[uint64]*HTLCAttempt) - err := bucket.ForEach(func(k, _ []byte) error { - aid := byteOrder.Uint64(k) - htlcBucket := bucket.NestedReadBucket(k) + attemptInfoCount := 0 + err := bucket.ForEach(func(k, v []byte) error { + aid := byteOrder.Uint64(k[len(k)-8:]) - attemptInfo, err := fetchHtlcAttemptInfo( - htlcBucket, - ) - if err != nil { - return err - } - attemptInfo.AttemptID = aid - - htlc := HTLCAttempt{ - HTLCAttemptInfo: *attemptInfo, + if _, ok := htlcsMap[aid]; !ok { + htlcsMap[aid] = &HTLCAttempt{} } - // Settle info might be nil. - htlc.Settle, err = fetchHtlcSettleInfo(htlcBucket) - if err != nil { - return err + var err error + switch { + case bytes.HasPrefix(k, htlcAttemptInfoKey): + attemptInfo, err := readHtlcAttemptInfo(v) + if err != nil { + return err + } + + attemptInfo.AttemptID = aid + htlcsMap[aid].HTLCAttemptInfo = *attemptInfo + attemptInfoCount++ + + case bytes.HasPrefix(k, htlcSettleInfoKey): + htlcsMap[aid].Settle, err = readHtlcSettleInfo(v) + if err != nil { + return err + } + + case bytes.HasPrefix(k, htlcFailInfoKey): + htlcsMap[aid].Failure, err = readHtlcFailInfo(v) + if err != nil { + return err + } + + default: + return fmt.Errorf("unknown htlc attempt key") } - // Failure info might be nil. - htlc.Failure, err = fetchHtlcFailInfo(htlcBucket) - if err != nil { - return err - } - - htlcs = append(htlcs, htlc) return nil }) if err != nil { return nil, err } - return htlcs, nil -} - -// fetchHtlcAttemptInfo fetches the payment attempt info for this htlc from the -// bucket. -func fetchHtlcAttemptInfo(bucket kvdb.RBucket) (*HTLCAttemptInfo, error) { - b := bucket.Get(htlcAttemptInfoKey) - if b == nil { + // Sanity check that all htlcs have an attempt info. + if attemptInfoCount != len(htlcsMap) { return nil, errNoAttemptInfo } + keys := make([]uint64, len(htlcsMap)) + i := 0 + for k := range htlcsMap { + keys[i] = k + i++ + } + + // Sort HTLC attempts by their attempt ID. This is needed because in the + // DB we store the attempts with keys prefixed by their status which + // changes order (groups them together by status). + sort.Slice(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + htlcs := make([]HTLCAttempt, len(htlcsMap)) + for i, key := range keys { + htlcs[i] = *htlcsMap[key] + } + + return htlcs, nil +} + +// readHtlcAttemptInfo reads the payment attempt info for this htlc. +func readHtlcAttemptInfo(b []byte) (*HTLCAttemptInfo, error) { r := bytes.NewReader(b) return deserializeHTLCAttemptInfo(r) } -// fetchHtlcSettleInfo retrieves the settle info for the htlc. If the htlc isn't +// readHtlcSettleInfo reads the settle info for the htlc. If the htlc isn't // settled, nil is returned. -func fetchHtlcSettleInfo(bucket kvdb.RBucket) (*HTLCSettleInfo, error) { - b := bucket.Get(htlcSettleInfoKey) - if b == nil { - // Settle info is optional. - return nil, nil - } - +func readHtlcSettleInfo(b []byte) (*HTLCSettleInfo, error) { r := bytes.NewReader(b) return deserializeHTLCSettleInfo(r) } -// fetchHtlcFailInfo retrieves the failure info for the htlc. If the htlc hasn't +// readHtlcFailInfo reads the failure info for the htlc. If the htlc hasn't // failed, nil is returned. -func fetchHtlcFailInfo(bucket kvdb.RBucket) (*HTLCFailInfo, error) { - b := bucket.Get(htlcFailInfoKey) - if b == nil { - // Fail info is optional. - return nil, nil - } - +func readHtlcFailInfo(b []byte) (*HTLCFailInfo, error) { r := bytes.NewReader(b) return deserializeHTLCFailInfo(r) } @@ -797,8 +818,21 @@ func (d *DB) DeletePayments(failedOnly, failedHtlcsOnly bool) error { ) for _, aid := range htlcIDs { - err := htlcsBucket.DeleteNestedBucket(aid) - if err != nil { + if err := htlcsBucket.Delete( + htlcBucketKey(htlcAttemptInfoKey, aid), + ); err != nil { + return err + } + + if err := htlcsBucket.Delete( + htlcBucketKey(htlcFailInfoKey, aid), + ); err != nil { + return err + } + + if err := htlcsBucket.Delete( + htlcBucketKey(htlcSettleInfoKey, aid), + ); err != nil { return err } }