From 19d84dd1cc62db4bbc21d121d5e792551c4099d7 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 11 Jul 2018 20:38:02 -0700 Subject: [PATCH] channeldb: add new migration to finalize invoice migration for outgoing payments In this commit, we migrate the database away from a partially migrated state. In a prior commit, we migrated the database in order to update the Invoice struct with three new fields: add index, settle index, paid amt. However, it was overlooked that the OutgoingPayment struct also embedded an Invoice within it. As a result, nodes that upgraded to the first migration found themselves unable to start up, or call listpayments, as the internal invoice within the OutgoignPayment hadn't yet been updated. This would result in an OOM typically as we went to allocate a slice with a integer that should have been small, but may have ended up actually being a set of random bytes, so a very large number. In this commit, we finish the DB migration by also migrating the internal invoice within each OutgoingPayment. Fixes #1538. Fixes #1546. --- channeldb/db.go | 6 ++++ channeldb/migrations.go | 71 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/channeldb/db.go b/channeldb/db.go index 911e40db..b428baed 100644 --- a/channeldb/db.go +++ b/channeldb/db.go @@ -53,6 +53,12 @@ var ( number: 2, migration: migrateInvoiceTimeSeries, }, + { + // The DB version that updated the embedded invoice in + // outgoing payments to match the new format. + number: 3, + migration: migrateInvoiceTimeSeriesOutgoingPayments, + }, } // Big endian is the preferred byte order, due to cursor scans over diff --git a/channeldb/migrations.go b/channeldb/migrations.go index cebcac1a..d2679b39 100644 --- a/channeldb/migrations.go +++ b/channeldb/migrations.go @@ -180,7 +180,7 @@ func migrateInvoiceTimeSeries(tx *bolt.Tx) error { return err } - log.Tracef("Adding invoice (preimage=%v, add_index=%v) to add "+ + log.Tracef("Adding invoice (preimage=%x, add_index=%v) to add "+ "time series", invoice.Terms.PaymentPreimage[:], nextAddSeqNo) @@ -231,3 +231,72 @@ func migrateInvoiceTimeSeries(tx *bolt.Tx) error { return nil } + +// migrateInvoiceTimeSeriesOutgoingPayments is a follow up to the +// migrateInvoiceTimeSeries migration. As at the time of writing, the +// OutgoingPayment struct embeddeds an instance of the Invoice struct. As a +// result, we also need to migrate the internal invoice to the new format. +func migrateInvoiceTimeSeriesOutgoingPayments(tx *bolt.Tx) error { + payBucket := tx.Bucket(paymentBucket) + if payBucket == nil { + return nil + } + + log.Infof("Migrating invoice database to new outgoing payment format") + + err := payBucket.ForEach(func(payID, paymentBytes []byte) error { + log.Tracef("Migrating payment %x", payID[:]) + + // The internal invoices for each payment only contain a + // populated contract term, and creation date, as a result, + // most of the bytes will be "empty". + + // We'll calculate the end of the invoice index assuming a + // "minimal" index that's embedded within the greater + // OutgoingPayment. The breakdown is: + // 3 bytes empty var bytes, 16 bytes creation date, 16 bytes + // settled date, 32 bytes payment pre-image, 8 bytes value, 1 + // byte settled. + endOfInvoiceIndex := 1 + 1 + 1 + 16 + 16 + 32 + 8 + 1 + + // We'll now extract the prefix of the pure invoice embedded + // within. + invoiceBytes := paymentBytes[:endOfInvoiceIndex] + + // With the prefix extracted, we'll copy over the invoice, and + // also add padding for the new 24 bytes of fields, and finally + // append the remainder of the outgoing payment. + paymentCopy := make([]byte, len(invoiceBytes)) + copy(paymentCopy[:], invoiceBytes) + + padding := bytes.Repeat([]byte{0}, 24) + paymentCopy = append(paymentCopy, padding...) + paymentCopy = append( + paymentCopy, paymentBytes[endOfInvoiceIndex:]..., + ) + + // At this point, we now have the new format of the outgoing + // payments, we'll attempt to deserialize it to ensure the + // bytes are properly formatted. + paymentReader := bytes.NewReader(paymentCopy) + _, err := deserializeOutgoingPayment(paymentReader) + if err != nil { + return fmt.Errorf("unable to deserialize payment: %v", err) + } + + // Now that we know the modifications was successful, we'll + // write it back to disk in the new format. + if err := payBucket.Put(payID, paymentCopy); err != nil { + return err + } + + return nil + }) + if err != nil { + return err + } + + log.Infof("Migration to outgoing payment invoices complete!") + + return nil +}