diff --git a/channeldb/channel.go b/channeldb/channel.go index 9fed8e8ac..c21716a45 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -2580,6 +2580,12 @@ type HTLC struct { // HTLC. It is stored in the ExtraData field, which is used to store // a TLV stream of additional information associated with the HTLC. BlindingPoint lnwire.BlindingPointRecord + + // CustomRecords is a set of custom TLV records that are associated with + // this HTLC. These records are used to store additional information + // about the HTLC that is not part of the standard HTLC fields. This + // field is encoded within the ExtraData field. + CustomRecords lnwire.CustomRecords } // serializeExtraData encodes a TLV stream of extra data to be stored with a @@ -2598,6 +2604,11 @@ func (h *HTLC) serializeExtraData() error { records = append(records, &b) }) + records, err := h.CustomRecords.ExtendRecordProducers(records) + if err != nil { + return err + } + return h.ExtraData.PackRecords(records...) } @@ -2619,8 +2630,19 @@ func (h *HTLC) deserializeExtraData() error { if val, ok := tlvMap[h.BlindingPoint.TlvType()]; ok && val == nil { h.BlindingPoint = tlv.SomeRecordT(blindingPoint) + + // Remove the entry from the TLV map. Anything left in the map + // will be included in the custom records field. + delete(tlvMap, h.BlindingPoint.TlvType()) } + // Set the custom records field to the remaining TLV records. + customRecords, err := lnwire.NewCustomRecords(tlvMap) + if err != nil { + return err + } + h.CustomRecords = customRecords + return nil } @@ -2758,6 +2780,8 @@ func (h *HTLC) Copy() HTLC { copy(clone.Signature[:], h.Signature) copy(clone.RHash[:], h.RHash[:]) copy(clone.ExtraData, h.ExtraData) + clone.BlindingPoint = h.BlindingPoint + clone.CustomRecords = h.CustomRecords.Copy() return clone } diff --git a/channeldb/channel_test.go b/channeldb/channel_test.go index 13d77276c..fa647f158 100644 --- a/channeldb/channel_test.go +++ b/channeldb/channel_test.go @@ -1657,6 +1657,24 @@ func TestHTLCsExtraData(t *testing.T) { ), } + // Custom channel data htlc with a blinding point. + customDataHTLC := HTLC{ + Signature: testSig.Serialize(), + Incoming: false, + Amt: 10, + RHash: key, + RefundTimeout: 1, + OnionBlob: lnmock.MockOnion(), + BlindingPoint: tlv.SomeRecordT( + tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType]( + pubKey, + ), + ), + CustomRecords: map[uint64][]byte{ + uint64(lnwire.MinCustomRecordsTlvType + 3): {1, 2, 3}, + }, + } + testCases := []struct { name string htlcs []HTLC @@ -1678,6 +1696,7 @@ func TestHTLCsExtraData(t *testing.T) { mockHtlc, blindingPointHTLC, mockHtlc, + customDataHTLC, }, }, } diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 036c96eda..3cd2e7f23 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -529,6 +529,7 @@ func (c *commitment) toDiskCommit( LogIndex: htlc.LogIndex, Incoming: false, BlindingPoint: htlc.BlindingPoint, + CustomRecords: htlc.CustomRecords.Copy(), } copy(h.OnionBlob[:], htlc.OnionBlob) @@ -554,8 +555,10 @@ func (c *commitment) toDiskCommit( LogIndex: htlc.LogIndex, Incoming: true, BlindingPoint: htlc.BlindingPoint, + CustomRecords: htlc.CustomRecords.Copy(), } copy(h.OnionBlob[:], htlc.OnionBlob) + if whoseCommit.IsLocal() && htlc.sig != nil { h.Signature = htlc.sig.Serialize() } @@ -653,6 +656,7 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight, theirPkScript: theirP2WSH, theirWitnessScript: theirWitnessScript, BlindingPoint: htlc.BlindingPoint, + CustomRecords: htlc.CustomRecords.Copy(), }, nil } @@ -5727,6 +5731,9 @@ func (lc *LightningChannel) htlcAddDescriptor(htlc *lnwire.UpdateAddHTLC, OnionBlob: htlc.OnionBlob[:], OpenCircuitKey: openKey, BlindingPoint: htlc.BlindingPoint, + // TODO(guggero): Add custom records from HTLC here once we have + // the custom records in the HTLC struct (later commits in this + // PR). } } @@ -5767,7 +5774,9 @@ func (lc *LightningChannel) validateAddHtlc(pd *PaymentDescriptor, // ReceiveHTLC adds an HTLC to the state machine's remote update log. This // method should be called in response to receiving a new HTLC from the remote // party. -func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.UpdateAddHTLC) (uint64, error) { +func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.UpdateAddHTLC) (uint64, + error) { + lc.Lock() defer lc.Unlock() @@ -5785,6 +5794,9 @@ func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.UpdateAddHTLC) (uint64, err HtlcIndex: lc.remoteUpdateLog.htlcCounter, OnionBlob: htlc.OnionBlob[:], BlindingPoint: htlc.BlindingPoint, + // TODO(guggero): Add custom records from HTLC here once we have + // the custom records in the HTLC struct (later commits in this + // PR). } localACKedIndex := lc.remoteCommitChain.tail().ourMessageIndex diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index e0e402484..61fa5ed09 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -2,6 +2,7 @@ package lnwallet import ( "bytes" + crand "crypto/rand" "crypto/sha256" "fmt" "math/rand" @@ -10431,19 +10432,26 @@ func assertPayDescMatchHTLC(t *testing.T, pd PaymentDescriptor, // the `Incoming`. func createRandomHTLC(t *testing.T, incoming bool) channeldb.HTLC { var onionBlob [lnwire.OnionPacketSize]byte - _, err := rand.Read(onionBlob[:]) + _, err := crand.Read(onionBlob[:]) require.NoError(t, err) var rHash [lntypes.HashSize]byte - _, err = rand.Read(rHash[:]) + _, err = crand.Read(rHash[:]) require.NoError(t, err) sig := make([]byte, 64) - _, err = rand.Read(sig) + _, err = crand.Read(sig) require.NoError(t, err) + randCustomData := make([]byte, 32) + _, err = crand.Read(randCustomData) + require.NoError(t, err) + + randCustomType := rand.Intn(255) + lnwire.MinCustomRecordsTlvType + blinding, err := pubkeyFromHex( - "0228f2af0abe322403480fb3ee172f7f1601e67d1da6cad40b54c4468d48236c39", //nolint:lll + "0228f2af0abe322403480fb3ee172f7f1601e67d1da6cad40b54c4468d48" + + "236c39", ) require.NoError(t, err) @@ -10458,9 +10466,13 @@ func createRandomHTLC(t *testing.T, incoming bool) channeldb.HTLC { HtlcIndex: rand.Uint64(), LogIndex: rand.Uint64(), BlindingPoint: tlv.SomeRecordT( - //nolint:lll - tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType](blinding), + tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType]( + blinding, + ), ), + CustomRecords: map[uint64][]byte{ + uint64(randCustomType): randCustomData, + }, } } diff --git a/lnwallet/payment_descriptor.go b/lnwallet/payment_descriptor.go index f0ac8b9f7..6fe74bb6f 100644 --- a/lnwallet/payment_descriptor.go +++ b/lnwallet/payment_descriptor.go @@ -221,4 +221,8 @@ type PaymentDescriptor struct { // blinded route (ie, not the introduction node) from update_add_htlc's // TLVs. BlindingPoint lnwire.BlindingPointRecord + + // CustomRecords also stores the set of optional custom records that + // may have been attached to a sent HTLC. + CustomRecords lnwire.CustomRecords }