diff --git a/channeldb/migration12/invoices.go b/channeldb/migration12/invoices.go index 7c08a9c9..e1c34c7b 100644 --- a/channeldb/migration12/invoices.go +++ b/channeldb/migration12/invoices.go @@ -73,6 +73,13 @@ type ContractTerm struct { // State describes the state the invoice is in. State ContractState + + // PaymentAddr is a randomly generated value include in the MPP record + // by the sender to prevent probing of the receiver. + PaymentAddr [32]byte + + // Features is the feature vectors advertised on the payment request. + Features *lnwire.FeatureVector } // Invoice is a payment invoice generated by a payee in order to request @@ -232,3 +239,80 @@ func deserializeHtlcs(r io.Reader) ([]byte, error) { _, err := io.Copy(&b, r) return b.Bytes(), err } + +// SerializeInvoice serializes an invoice to a writer. +func SerializeInvoice(w io.Writer, i *Invoice) error { + creationDateBytes, err := i.CreationDate.MarshalBinary() + if err != nil { + return err + } + + settleDateBytes, err := i.SettleDate.MarshalBinary() + if err != nil { + return err + } + + var fb bytes.Buffer + err = i.Terms.Features.EncodeBase256(&fb) + if err != nil { + return err + } + featureBytes := fb.Bytes() + + preimage := [32]byte(i.Terms.PaymentPreimage) + value := uint64(i.Terms.Value) + cltvDelta := uint32(i.FinalCltvDelta) + expiry := uint64(i.Expiry) + + amtPaid := uint64(i.AmtPaid) + state := uint8(i.Terms.State) + + tlvStream, err := tlv.NewStream( + // Memo and payreq. + tlv.MakePrimitiveRecord(memoType, &i.Memo), + tlv.MakePrimitiveRecord(payReqType, &i.PaymentRequest), + + // Add/settle metadata. + tlv.MakePrimitiveRecord(createTimeType, &creationDateBytes), + tlv.MakePrimitiveRecord(settleTimeType, &settleDateBytes), + tlv.MakePrimitiveRecord(addIndexType, &i.AddIndex), + tlv.MakePrimitiveRecord(settleIndexType, &i.SettleIndex), + + // Terms. + tlv.MakePrimitiveRecord(preimageType, &preimage), + tlv.MakePrimitiveRecord(valueType, &value), + tlv.MakePrimitiveRecord(cltvDeltaType, &cltvDelta), + tlv.MakePrimitiveRecord(expiryType, &expiry), + tlv.MakePrimitiveRecord(paymentAddrType, &i.Terms.PaymentAddr), + tlv.MakePrimitiveRecord(featuresType, &featureBytes), + + // Invoice state. + tlv.MakePrimitiveRecord(invStateType, &state), + tlv.MakePrimitiveRecord(amtPaidType, &amtPaid), + ) + if err != nil { + return err + } + + var b bytes.Buffer + if err = tlvStream.Encode(&b); err != nil { + return err + } + + err = binary.Write(w, byteOrder, uint64(b.Len())) + if err != nil { + return err + } + + if _, err = w.Write(b.Bytes()); err != nil { + return err + } + + return serializeHtlcs(w, i.Htlcs) +} + +// serializeHtlcs writes a serialized list of invoice htlcs into a writer. +func serializeHtlcs(w io.Writer, htlcs []byte) error { + _, err := w.Write(htlcs) + return err +} diff --git a/channeldb/migration12/log.go b/channeldb/migration12/log.go new file mode 100644 index 00000000..1352e52a --- /dev/null +++ b/channeldb/migration12/log.go @@ -0,0 +1,14 @@ +package migration12 + +import ( + "github.com/btcsuite/btclog" +) + +// log is a logger that is initialized as disabled. This means the package will +// not perform any logging by default until a logger is set. +var log = btclog.Disabled + +// UseLogger uses a specified Logger to output package logging info. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/channeldb/migration12/migration.go b/channeldb/migration12/migration.go new file mode 100644 index 00000000..d80497dd --- /dev/null +++ b/channeldb/migration12/migration.go @@ -0,0 +1,74 @@ +package migration12 + +import ( + "bytes" + + "github.com/coreos/bbolt" + "github.com/lightningnetwork/lnd/lnwire" +) + +var emptyFeatures = lnwire.NewFeatureVector(nil, nil) + +// MigrateInvoiceTLV migrates all existing invoice bodies over to be serialized +// in a single TLV stream. In the process, we drop the Receipt field and add +// PaymentAddr and Features to the invoice Terms. +func MigrateInvoiceTLV(tx *bbolt.Tx) error { + log.Infof("Migrating invoice bodies to TLV, " + + "adding payment addresses and feature vectors.") + + invoiceB := tx.Bucket(invoiceBucket) + if invoiceB == nil { + return nil + } + + type keyedInvoice struct { + key []byte + invoice Invoice + } + + // Read in all existing invoices using the old format. + var invoices []keyedInvoice + err := invoiceB.ForEach(func(k, v []byte) error { + if v == nil { + return nil + } + + invoiceReader := bytes.NewReader(v) + invoice, err := LegacyDeserializeInvoice(invoiceReader) + if err != nil { + return err + } + + // Insert an empty feature vector on all old payments. + invoice.Terms.Features = emptyFeatures + + invoices = append(invoices, keyedInvoice{ + key: k, + invoice: invoice, + }) + + return nil + }) + if err != nil { + return err + } + + // Write out each one under its original key using TLV. + for _, ki := range invoices { + var b bytes.Buffer + err = SerializeInvoice(&b, &ki.invoice) + if err != nil { + return err + } + + err = invoiceB.Put(ki.key, b.Bytes()) + if err != nil { + return err + } + } + + log.Infof("Migration to TLV invoice bodies, " + + "payment address, and features complete!") + + return nil +}