channeldb: add custom blobs to RevocationLog+HTLCEntry

This'll be useful for custom channel types that want to store extra information that'll be useful to help handle channel revocation cases.
This commit is contained in:
Olaoluwa Osuntokun authored and Oliver Gugger committed 2024-08-28 13:23:30 +02:00
1 parent 61f276856a
commit 669740c84e
4 files changed
+120 -21

No files matched your search

+79 -9
View File
@@ -164,22 +164,32 @@ type HTLCEntry struct {
// //
// NOTE: this field is the memory representation of the field amtUint. // NOTE: this field is the memory representation of the field amtUint.
Amt tlv.RecordT[tlv.TlvType4, tlv.BigSizeT[btcutil.Amount]] Amt tlv.RecordT[tlv.TlvType4, tlv.BigSizeT[btcutil.Amount]]
// CustomBlob is an optional blob that can be used to store information
// specific to revocation handling for a custom channel type.
CustomBlob tlv.OptionalRecordT[tlv.TlvType5, tlv.Blob]
} }
// toTlvStream converts an HTLCEntry record into a tlv representation. // toTlvStream converts an HTLCEntry record into a tlv representation.
func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) { func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) {
return tlv.NewStream( records := []tlv.Record{
h.RHash.Record(), h.RHash.Record(),
h.RefundTimeout.Record(), h.RefundTimeout.Record(),
h.OutputIndex.Record(), h.OutputIndex.Record(),
h.Incoming.Record(), h.Incoming.Record(),
h.Amt.Record(), h.Amt.Record(),
) }
h.CustomBlob.WhenSome(func(r tlv.RecordT[tlv.TlvType5, tlv.Blob]) {
records = append(records, r.Record())
})
return tlv.NewStream(records...)
} }
// NewHTLCEntryFromHTLC creates a new HTLCEntry from an HTLC. // NewHTLCEntryFromHTLC creates a new HTLCEntry from an HTLC.
func NewHTLCEntryFromHTLC(htlc HTLC) *HTLCEntry { func NewHTLCEntryFromHTLC(htlc HTLC) (*HTLCEntry, error) {
return &HTLCEntry{ h := &HTLCEntry{
RHash: tlv.NewRecordT[tlv.TlvType0]( RHash: tlv.NewRecordT[tlv.TlvType0](
NewSparsePayHash(htlc.RHash), NewSparsePayHash(htlc.RHash),
), ),
@@ -194,6 +204,19 @@ func NewHTLCEntryFromHTLC(htlc HTLC) *HTLCEntry {
tlv.NewBigSizeT(htlc.Amt.ToSatoshis()), tlv.NewBigSizeT(htlc.Amt.ToSatoshis()),
), ),
} }
if len(htlc.CustomRecords) != 0 {
blob, err := htlc.CustomRecords.Serialize()
if err != nil {
return nil, err
}
h.CustomBlob = tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType5, tlv.Blob](blob),
)
}
return h, nil
} }
// RevocationLog stores the info needed to construct a breach retribution. Its // RevocationLog stores the info needed to construct a breach retribution. Its
@@ -236,13 +259,19 @@ type RevocationLog struct {
// this field, it could be the case that the field is not present for // this field, it could be the case that the field is not present for
// all revocation logs. // all revocation logs.
TheirBalance tlv.OptionalRecordT[tlv.TlvType4, BigSizeMilliSatoshi] TheirBalance tlv.OptionalRecordT[tlv.TlvType4, BigSizeMilliSatoshi]
// CustomBlob is an optional blob that can be used to store information
// specific to a custom channel type. This information is only created
// at channel funding time, and after wards is to be considered
// immutable.
CustomBlob tlv.OptionalRecordT[tlv.TlvType5, tlv.Blob]
} }
// NewRevocationLog creates a new RevocationLog from the given parameters. // NewRevocationLog creates a new RevocationLog from the given parameters.
func NewRevocationLog(ourOutputIndex uint16, theirOutputIndex uint16, func NewRevocationLog(ourOutputIndex uint16, theirOutputIndex uint16,
commitHash [32]byte, ourBalance, commitHash [32]byte, ourBalance,
theirBalance fn.Option[lnwire.MilliSatoshi], theirBalance fn.Option[lnwire.MilliSatoshi], htlcs []*HTLCEntry,
htlcs []*HTLCEntry) RevocationLog { customBlob fn.Option[tlv.Blob]) RevocationLog {
rl := RevocationLog{ rl := RevocationLog{
OurOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType0]( OurOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType0](
@@ -267,6 +296,12 @@ func NewRevocationLog(ourOutputIndex uint16, theirOutputIndex uint16,
)) ))
}) })
customBlob.WhenSome(func(blob tlv.Blob) {
rl.CustomBlob = tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType5, tlv.Blob](blob),
)
})
return rl return rl
} }
@@ -298,6 +333,12 @@ func putRevocationLog(bucket kvdb.RwBucket, commit *ChannelCommitment,
HTLCEntries: make([]*HTLCEntry, 0, len(commit.Htlcs)), HTLCEntries: make([]*HTLCEntry, 0, len(commit.Htlcs)),
} }
commit.CustomBlob.WhenSome(func(blob tlv.Blob) {
rl.CustomBlob = tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType5, tlv.Blob](blob),
)
})
if !noAmtData { if !noAmtData {
rl.OurBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType3]( rl.OurBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType3](
tlv.NewBigSizeT(commit.LocalBalance), tlv.NewBigSizeT(commit.LocalBalance),
@@ -320,7 +361,10 @@ func putRevocationLog(bucket kvdb.RwBucket, commit *ChannelCommitment,
return ErrOutputIndexTooBig return ErrOutputIndexTooBig
} }
entry := NewHTLCEntryFromHTLC(htlc) entry, err := NewHTLCEntryFromHTLC(htlc)
if err != nil {
return err
}
rl.HTLCEntries = append(rl.HTLCEntries, entry) rl.HTLCEntries = append(rl.HTLCEntries, entry)
} }
@@ -373,6 +417,10 @@ func serializeRevocationLog(w io.Writer, rl *RevocationLog) error {
}, },
) )
rl.CustomBlob.WhenSome(func(r tlv.RecordT[tlv.TlvType5, tlv.Blob]) {
records = append(records, r.Record())
})
// Create the tlv stream. // Create the tlv stream.
tlvStream, err := tlv.NewStream(records...) tlvStream, err := tlv.NewStream(records...)
if err != nil { if err != nil {
@@ -413,6 +461,7 @@ func deserializeRevocationLog(r io.Reader) (RevocationLog, error) {
ourBalance := rl.OurBalance.Zero() ourBalance := rl.OurBalance.Zero()
theirBalance := rl.TheirBalance.Zero() theirBalance := rl.TheirBalance.Zero()
customBlob := rl.CustomBlob.Zero()
// Create the tlv stream. // Create the tlv stream.
tlvStream, err := tlv.NewStream( tlvStream, err := tlv.NewStream(
@@ -421,6 +470,7 @@ func deserializeRevocationLog(r io.Reader) (RevocationLog, error) {
rl.CommitTxHash.Record(), rl.CommitTxHash.Record(),
ourBalance.Record(), ourBalance.Record(),
theirBalance.Record(), theirBalance.Record(),
customBlob.Record(),
) )
if err != nil { if err != nil {
return rl, err return rl, err
@@ -440,6 +490,10 @@ func deserializeRevocationLog(r io.Reader) (RevocationLog, error) {
rl.TheirBalance = tlv.SomeRecordT(theirBalance) rl.TheirBalance = tlv.SomeRecordT(theirBalance)
} }
if t, ok := parsedTypes[customBlob.TlvType()]; ok && t == nil {
rl.CustomBlob = tlv.SomeRecordT(customBlob)
}
// Read the HTLC entries. // Read the HTLC entries.
rl.HTLCEntries, err = deserializeHTLCEntries(r) rl.HTLCEntries, err = deserializeHTLCEntries(r)
@@ -454,14 +508,26 @@ func deserializeHTLCEntries(r io.Reader) ([]*HTLCEntry, error) {
for { for {
var htlc HTLCEntry var htlc HTLCEntry
customBlob := htlc.CustomBlob.Zero()
// Create the tlv stream. // Create the tlv stream.
tlvStream, err := htlc.toTlvStream() records := []tlv.Record{
htlc.RHash.Record(),
htlc.RefundTimeout.Record(),
htlc.OutputIndex.Record(),
htlc.Incoming.Record(),
htlc.Amt.Record(),
customBlob.Record(),
}
tlvStream, err := tlv.NewStream(records...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Read the HTLC entry. // Read the HTLC entry.
if _, err := readTlvStream(r, tlvStream); err != nil { parsedTypes, err := readTlvStream(r, tlvStream)
if err != nil {
// We've reached the end when hitting an EOF. // We've reached the end when hitting an EOF.
if err == io.ErrUnexpectedEOF { if err == io.ErrUnexpectedEOF {
break break
@@ -469,6 +535,10 @@ func deserializeHTLCEntries(r io.Reader) ([]*HTLCEntry, error) {
return nil, err return nil, err
} }
if t, ok := parsedTypes[customBlob.TlvType()]; ok && t == nil {
htlc.CustomBlob = tlv.SomeRecordT(customBlob)
}
// Append the entry. // Append the entry.
htlcs = append(htlcs, &htlc) htlcs = append(htlcs, &htlc)
} }
+35 -10
View File
@@ -34,6 +34,17 @@ var (
0xff, // value = 255 0xff, // value = 255
} }
customRecords = lnwire.CustomRecords{
lnwire.MinCustomRecordsTlvType + 1: []byte("custom data"),
}
blobBytes = []byte{
// Corresponds to the encoded version of the above custom
// records.
0xfe, 0x00, 0x01, 0x00, 0x01, 0x0b, 0x63, 0x75, 0x73, 0x74,
0x6f, 0x6d, 0x20, 0x64, 0x61, 0x74, 0x61,
}
testHTLCEntry = HTLCEntry{ testHTLCEntry = HTLCEntry{
RefundTimeout: tlv.NewPrimitiveRecord[tlv.TlvType1, uint32]( RefundTimeout: tlv.NewPrimitiveRecord[tlv.TlvType1, uint32](
740_000, 740_000,
@@ -45,10 +56,13 @@ var (
Amt: tlv.NewRecordT[tlv.TlvType4]( Amt: tlv.NewRecordT[tlv.TlvType4](
tlv.NewBigSizeT(btcutil.Amount(1_000_000)), tlv.NewBigSizeT(btcutil.Amount(1_000_000)),
), ),
CustomBlob: tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType5](blobBytes),
),
} }
testHTLCEntryBytes = []byte{ testHTLCEntryBytes = []byte{
// Body length 23. // Body length 41.
0x16, 0x29,
// Rhash tlv. // Rhash tlv.
0x0, 0x0, 0x0, 0x0,
// RefundTimeout tlv. // RefundTimeout tlv.
@@ -59,6 +73,9 @@ var (
0x3, 0x1, 0x1, 0x3, 0x1, 0x1,
// Amt tlv. // Amt tlv.
0x4, 0x5, 0xfe, 0x0, 0xf, 0x42, 0x40, 0x4, 0x5, 0xfe, 0x0, 0xf, 0x42, 0x40,
// Custom blob tlv.
0x5, 0x11, 0xfe, 0x00, 0x01, 0x00, 0x01, 0x0b, 0x63, 0x75, 0x73,
0x74, 0x6f, 0x6d, 0x20, 0x64, 0x61, 0x74, 0x61,
} }
testHTLCEntryHash = HTLCEntry{ testHTLCEntryHash = HTLCEntry{
@@ -113,17 +130,19 @@ var (
Amt: lnwire.NewMSatFromSatoshis( Amt: lnwire.NewMSatFromSatoshis(
testHTLCEntry.Amt.Val.Int(), testHTLCEntry.Amt.Val.Int(),
), ),
CustomRecords: customRecords,
}}, }},
CustomBlob: fn.Some(blobBytes),
} }
testRevocationLogNoAmts = NewRevocationLog( testRevocationLogNoAmts = NewRevocationLog(
0, 1, testChannelCommit.CommitTx.TxHash(), 0, 1, testChannelCommit.CommitTx.TxHash(),
fn.None[lnwire.MilliSatoshi](), fn.None[lnwire.MilliSatoshi](), fn.None[lnwire.MilliSatoshi](), fn.None[lnwire.MilliSatoshi](),
[]*HTLCEntry{&testHTLCEntry}, []*HTLCEntry{&testHTLCEntry}, fn.Some(blobBytes),
) )
testRevocationLogNoAmtsBytes = []byte{ testRevocationLogNoAmtsBytes = []byte{
// Body length 42. // Body length 61.
0x2a, 0x3d,
// OurOutputIndex tlv. // OurOutputIndex tlv.
0x0, 0x2, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0,
// TheirOutputIndex tlv. // TheirOutputIndex tlv.
@@ -134,16 +153,19 @@ var (
0x6e, 0x60, 0x29, 0x23, 0x1d, 0x5e, 0xc5, 0xe6, 0x6e, 0x60, 0x29, 0x23, 0x1d, 0x5e, 0xc5, 0xe6,
0xbd, 0xf7, 0xd3, 0x9b, 0x16, 0x7d, 0x0, 0xff, 0xbd, 0xf7, 0xd3, 0x9b, 0x16, 0x7d, 0x0, 0xff,
0xc8, 0x22, 0x51, 0xb1, 0x5b, 0xa0, 0xbf, 0xd, 0xc8, 0x22, 0x51, 0xb1, 0x5b, 0xa0, 0xbf, 0xd,
// Custom blob tlv.
0x5, 0x11, 0xfe, 0x00, 0x01, 0x00, 0x01, 0x0b, 0x63, 0x75, 0x73,
0x74, 0x6f, 0x6d, 0x20, 0x64, 0x61, 0x74, 0x61,
} }
testRevocationLogWithAmts = NewRevocationLog( testRevocationLogWithAmts = NewRevocationLog(
0, 1, testChannelCommit.CommitTx.TxHash(), 0, 1, testChannelCommit.CommitTx.TxHash(),
fn.Some(localBalance), fn.Some(remoteBalance), fn.Some(localBalance), fn.Some(remoteBalance),
[]*HTLCEntry{&testHTLCEntry}, []*HTLCEntry{&testHTLCEntry}, fn.Some(blobBytes),
) )
testRevocationLogWithAmtsBytes = []byte{ testRevocationLogWithAmtsBytes = []byte{
// Body length 52. // Body length 71.
0x34, 0x47,
// OurOutputIndex tlv. // OurOutputIndex tlv.
0x0, 0x2, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0,
// TheirOutputIndex tlv. // TheirOutputIndex tlv.
@@ -158,6 +180,9 @@ var (
0x3, 0x3, 0xfd, 0x23, 0x28, 0x3, 0x3, 0xfd, 0x23, 0x28,
// Remote Balance. // Remote Balance.
0x4, 0x3, 0xfd, 0x0b, 0xb8, 0x4, 0x3, 0xfd, 0x0b, 0xb8,
// Custom blob tlv.
0x5, 0x11, 0xfe, 0x00, 0x01, 0x00, 0x01, 0x0b, 0x63, 0x75, 0x73,
0x74, 0x6f, 0x6d, 0x20, 0x64, 0x61, 0x74, 0x61,
} }
) )
@@ -269,7 +294,7 @@ func TestSerializeHTLCEntries(t *testing.T) {
partialBytes := testHTLCEntryBytes[3:] partialBytes := testHTLCEntryBytes[3:]
// Write the total length and RHash tlv. // Write the total length and RHash tlv.
expectedBytes := []byte{0x36, 0x0, 0x20} expectedBytes := []byte{0x49, 0x0, 0x20}
expectedBytes = append(expectedBytes, rHashBytes...) expectedBytes = append(expectedBytes, rHashBytes...)
// Append the rest. // Append the rest.
@@ -384,7 +409,7 @@ func TestDeserializeHTLCEntries(t *testing.T) {
partialBytes := testHTLCEntryBytes[3:] partialBytes := testHTLCEntryBytes[3:]
// Write the total length and RHash tlv. // Write the total length and RHash tlv.
testBytes := append([]byte{0x36, 0x0, 0x20}, rHashBytes...) testBytes := append([]byte{0x4d, 0x0, 0x20}, rHashBytes...)
// Append the rest. // Append the rest.
testBytes = append(testBytes, partialBytes...) testBytes = append(testBytes, partialBytes...)
+5 -1
View File
@@ -2420,7 +2420,11 @@ func createBreachRetributionLegacy(revokedLog *channeldb.ChannelCommitment,
continue continue
} }
entry := channeldb.NewHTLCEntryFromHTLC(htlc) entry, err := channeldb.NewHTLCEntryFromHTLC(htlc)
if err != nil {
return nil, 0, 0, err
}
hr, err := createHtlcRetribution( hr, err := createHtlcRetribution(
chanState, keyRing, commitHash, chanState, keyRing, commitHash,
commitmentSecret, leaseExpiry, entry, commitmentSecret, leaseExpiry, entry,
+1 -1
View File
@@ -10031,7 +10031,7 @@ func TestCreateBreachRetribution(t *testing.T) {
revokedLog := channeldb.NewRevocationLog( revokedLog := channeldb.NewRevocationLog(
uint16(localIndex), uint16(remoteIndex), commitHash, uint16(localIndex), uint16(remoteIndex), commitHash,
fn.Some(ourAmtMsat), fn.Some(theirAmtMsat), fn.Some(ourAmtMsat), fn.Some(theirAmtMsat),
[]*channeldb.HTLCEntry{htlc}, []*channeldb.HTLCEntry{htlc}, fn.None[tlv.Blob](),
) )
// Create a log with an empty local output index. // Create a log with an empty local output index.