mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-08-29 07:00:55 +02:00
channeldb: use BigSize to encode htlc amount
This commit uses bigsize record to encode the htlc amount, which could save us 3 more bytes if the encoded value is no greater than roughly 0.043 bitcoin. The uint test has been updated with a more realistic values to reflect the actual gain.
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
)
|
||||
|
||||
@@ -43,6 +44,16 @@ var (
|
||||
// HTLCEntry specifies the minimal info needed to be stored on disk for ALL the
|
||||
// historical HTLCs, which is useful for constructing RevocationLog when a
|
||||
// breach is detected.
|
||||
// The actual size of each HTLCEntry varies based on its RHash and Amt(sat),
|
||||
// summarized as follows,
|
||||
//
|
||||
// | RHash empty | Amt<=252 | Amt<=65,535 | Amt<=4,294,967,295 | otherwise |
|
||||
// |:-----------:|:--------:|:-----------:|:------------------:|:---------:|
|
||||
// | true | 19 | 21 | 23 | 26 |
|
||||
// | false | 51 | 53 | 55 | 58 |
|
||||
//
|
||||
// So the size varies from 19 bytes to 58 bytes, where most likely to be 23 or
|
||||
// 55 bytes.
|
||||
//
|
||||
// NOTE: all the fields saved to disk use the primitive go types so they can be
|
||||
// made into tlv records without further conversion.
|
||||
@@ -86,6 +97,46 @@ type HTLCEntry struct {
|
||||
incomingTlv uint8
|
||||
}
|
||||
|
||||
// RHashLen is used by MakeDynamicRecord to return the size of the RHash.
|
||||
//
|
||||
// NOTE: for zero hash, we return a length 0.
|
||||
func (h *HTLCEntry) RHashLen() uint64 {
|
||||
if h.RHash == lntypes.ZeroHash {
|
||||
return 0
|
||||
}
|
||||
return 32
|
||||
}
|
||||
|
||||
// RHashEncoder is the customized encoder which skips encoding the empty hash.
|
||||
func RHashEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
|
||||
v, ok := val.(*[32]byte)
|
||||
if !ok {
|
||||
return tlv.NewTypeForEncodingErr(val, "RHash")
|
||||
}
|
||||
|
||||
// If the value is an empty hash, we will skip encoding it.
|
||||
if *v == lntypes.ZeroHash {
|
||||
return nil
|
||||
}
|
||||
|
||||
return tlv.EBytes32(w, v, buf)
|
||||
}
|
||||
|
||||
// RHashDecoder is the customized decoder which skips decoding the empty hash.
|
||||
func RHashDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
|
||||
v, ok := val.(*[32]byte)
|
||||
if !ok {
|
||||
return tlv.NewTypeForEncodingErr(val, "RHash")
|
||||
}
|
||||
|
||||
// If the length is zero, we will skip encoding the empty hash.
|
||||
if l == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return tlv.DBytes32(r, v, buf, 32)
|
||||
}
|
||||
|
||||
// toTlvStream converts an HTLCEntry record into a tlv representation.
|
||||
func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) {
|
||||
const (
|
||||
@@ -103,7 +154,10 @@ func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) {
|
||||
)
|
||||
|
||||
return tlv.NewStream(
|
||||
tlv.MakePrimitiveRecord(rHashType, &h.RHash),
|
||||
tlv.MakeDynamicRecord(
|
||||
rHashType, &h.RHash, h.RHashLen,
|
||||
RHashEncoder, RHashDecoder,
|
||||
),
|
||||
tlv.MakePrimitiveRecord(
|
||||
refundTimeoutType, &h.RefundTimeout,
|
||||
),
|
||||
@@ -111,7 +165,9 @@ func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) {
|
||||
outputIndexType, &h.OutputIndex,
|
||||
),
|
||||
tlv.MakePrimitiveRecord(incomingType, &h.incomingTlv),
|
||||
tlv.MakePrimitiveRecord(amtType, &h.amtTlv),
|
||||
// We will save 3 bytes if the amount is less or equal to
|
||||
// 4,294,967,295 msat, or roughly 0.043 bitcoin.
|
||||
tlv.MakeBigSizeRecord(amtType, &h.amtTlv),
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -34,30 +34,26 @@ var (
|
||||
}
|
||||
|
||||
testHTLCEntry = HTLCEntry{
|
||||
RefundTimeout: 100,
|
||||
RefundTimeout: 740_000,
|
||||
OutputIndex: 10,
|
||||
Incoming: true,
|
||||
Amt: 255,
|
||||
amtTlv: 255,
|
||||
Amt: 1000_000,
|
||||
amtTlv: 1000_000,
|
||||
incomingTlv: 1,
|
||||
}
|
||||
testHTLCEntryBytes = []byte{
|
||||
// Body length 58.
|
||||
0x39,
|
||||
// Body length 23.
|
||||
0x16,
|
||||
// Rhash tlv.
|
||||
0x0, 0x20,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0,
|
||||
// RefundTimeout tlv.
|
||||
0x1, 0x4, 0x0, 0x0, 0x0, 0x64,
|
||||
0x1, 0x4, 0x0, 0xb, 0x4a, 0xa0,
|
||||
// OutputIndex tlv.
|
||||
0x2, 0x2, 0x0, 0xa,
|
||||
// Incoming tlv.
|
||||
0x3, 0x1, 0x1,
|
||||
// Amt tlv.
|
||||
0x4, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff,
|
||||
0x4, 0x5, 0xfe, 0x0, 0xf, 0x42, 0x40,
|
||||
}
|
||||
|
||||
testChannelCommit = ChannelCommitment{
|
||||
@@ -161,7 +157,7 @@ func TestReadTLVStreamErr(t *testing.T) {
|
||||
require.Zero(t, valueRead)
|
||||
}
|
||||
|
||||
func TestSerializeHTLCEntries(t *testing.T) {
|
||||
func TestSerializeHTLCEntriesEmptyRHash(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Copy the testHTLCEntry.
|
||||
@@ -181,6 +177,37 @@ func TestSerializeHTLCEntries(t *testing.T) {
|
||||
require.Equal(t, testHTLCEntryBytes, buf.Bytes())
|
||||
}
|
||||
|
||||
func TestSerializeHTLCEntries(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Copy the testHTLCEntry.
|
||||
entry := testHTLCEntry
|
||||
|
||||
// Create a fake rHash.
|
||||
rHashBytes := bytes.Repeat([]byte{10}, 32)
|
||||
copy(entry.RHash[:], rHashBytes)
|
||||
|
||||
// Construct the serialized bytes.
|
||||
//
|
||||
// Exclude the first 3 bytes, which are total length, RHash type and
|
||||
// RHash length(0).
|
||||
partialBytes := testHTLCEntryBytes[3:]
|
||||
|
||||
// Write the total length and RHash tlv.
|
||||
expectedBytes := []byte{0x36, 0x0, 0x20}
|
||||
expectedBytes = append(expectedBytes, rHashBytes...)
|
||||
|
||||
// Append the rest.
|
||||
expectedBytes = append(expectedBytes, partialBytes...)
|
||||
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
err := serializeHTLCEntries(buf, []*HTLCEntry{&entry})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check the bytes are read as expected.
|
||||
require.Equal(t, expectedBytes, buf.Bytes())
|
||||
}
|
||||
|
||||
func TestSerializeRevocationLog(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -197,7 +224,7 @@ func TestSerializeRevocationLog(t *testing.T) {
|
||||
require.Equal(t, testRevocationLogBytes, buf.Bytes()[:bodyIndex])
|
||||
}
|
||||
|
||||
func TestDerializeHTLCEntries(t *testing.T) {
|
||||
func TestDerializeHTLCEntriesEmptyRHash(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Read the tlv stream.
|
||||
@@ -210,6 +237,38 @@ func TestDerializeHTLCEntries(t *testing.T) {
|
||||
require.Equal(t, &testHTLCEntry, htlcs[0])
|
||||
}
|
||||
|
||||
func TestDerializeHTLCEntries(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Copy the testHTLCEntry.
|
||||
entry := testHTLCEntry
|
||||
|
||||
// Create a fake rHash.
|
||||
rHashBytes := bytes.Repeat([]byte{10}, 32)
|
||||
copy(entry.RHash[:], rHashBytes)
|
||||
|
||||
// Construct the serialized bytes.
|
||||
//
|
||||
// Exclude the first 3 bytes, which are total length, RHash type and
|
||||
// RHash length(0).
|
||||
partialBytes := testHTLCEntryBytes[3:]
|
||||
|
||||
// Write the total length and RHash tlv.
|
||||
testBytes := append([]byte{0x36, 0x0, 0x20}, rHashBytes...)
|
||||
|
||||
// Append the rest.
|
||||
testBytes = append(testBytes, partialBytes...)
|
||||
|
||||
// Read the tlv stream.
|
||||
buf := bytes.NewBuffer(testBytes)
|
||||
htlcs, err := deserializeHTLCEntries(buf)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check the bytes are read as expected.
|
||||
require.Len(t, htlcs, 1)
|
||||
require.Equal(t, &entry, htlcs[0])
|
||||
}
|
||||
|
||||
func TestDerializeRevocationLog(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
Reference in New Issue
Block a user