mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-04 17:55:36 +01:00
In preparation for the commit which will add the main logic for migration 32 (which will migrate the MC store to use a more minimal encoding), this commit just adds some of the code that the migration will need to the package.
583 lines
16 KiB
Go
583 lines
16 KiB
Go
package migration32
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"github.com/btcsuite/btcd/wire"
|
|
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
|
|
"github.com/lightningnetwork/lnd/tlv"
|
|
)
|
|
|
|
const (
|
|
// MPPOnionType is the type used in the onion to reference the MPP
|
|
// fields: total_amt and payment_addr.
|
|
MPPOnionType tlv.Type = 8
|
|
|
|
// AMPOnionType is the type used in the onion to reference the AMP
|
|
// fields: root_share, set_id, and child_index.
|
|
AMPOnionType tlv.Type = 14
|
|
)
|
|
|
|
// VertexSize is the size of the array to store a vertex.
|
|
const VertexSize = 33
|
|
|
|
// Vertex is a simple alias for the serialization of a compressed Bitcoin
|
|
// public key.
|
|
type Vertex [VertexSize]byte
|
|
|
|
// Route represents a path through the channel graph which runs over one or
|
|
// more channels in succession. This struct carries all the information
|
|
// required to craft the Sphinx onion packet, and send the payment along the
|
|
// first hop in the path. A route is only selected as valid if all the channels
|
|
// have sufficient capacity to carry the initial payment amount after fees are
|
|
// accounted for.
|
|
type Route struct {
|
|
// TotalTimeLock is the cumulative (final) time lock across the entire
|
|
// route. This is the CLTV value that should be extended to the first
|
|
// hop in the route. All other hops will decrement the time-lock as
|
|
// advertised, leaving enough time for all hops to wait for or present
|
|
// the payment preimage to complete the payment.
|
|
TotalTimeLock uint32
|
|
|
|
// TotalAmount is the total amount of funds required to complete a
|
|
// payment over this route. This value includes the cumulative fees at
|
|
// each hop. As a result, the HTLC extended to the first-hop in the
|
|
// route will need to have at least this many satoshis, otherwise the
|
|
// route will fail at an intermediate node due to an insufficient
|
|
// amount of fees.
|
|
TotalAmount lnwire.MilliSatoshi
|
|
|
|
// SourcePubKey is the pubkey of the node where this route originates
|
|
// from.
|
|
SourcePubKey Vertex
|
|
|
|
// Hops contains details concerning the specific forwarding details at
|
|
// each hop.
|
|
Hops []*Hop
|
|
|
|
// FirstHopAmount is the amount that should actually be sent to the
|
|
// first hop in the route. This is only different from TotalAmount above
|
|
// for custom channels where the on-chain amount doesn't necessarily
|
|
// reflect all the value of an outgoing payment.
|
|
FirstHopAmount tlv.RecordT[
|
|
tlv.TlvType0, tlv.BigSizeT[lnwire.MilliSatoshi],
|
|
]
|
|
|
|
// FirstHopWireCustomRecords is a set of custom records that should be
|
|
// included in the wire message sent to the first hop. This is only set
|
|
// on custom channels and is used to include additional information
|
|
// about the actual value of the payment.
|
|
//
|
|
// NOTE: Since these records already represent TLV records, and we
|
|
// enforce them to be in the custom range (e.g. >= 65536), we don't use
|
|
// another parent record type here. Instead, when serializing the Route
|
|
// we merge the TLV records together with the custom records and encode
|
|
// everything as a single TLV stream.
|
|
FirstHopWireCustomRecords lnwire.CustomRecords
|
|
}
|
|
|
|
// Hop represents an intermediate or final node of the route. This naming
|
|
// is in line with the definition given in BOLT #4: Onion Routing Protocol.
|
|
// The struct houses the channel along which this hop can be reached and
|
|
// the values necessary to create the HTLC that needs to be sent to the
|
|
// next hop. It is also used to encode the per-hop payload included within
|
|
// the Sphinx packet.
|
|
type Hop struct {
|
|
// PubKeyBytes is the raw bytes of the public key of the target node.
|
|
PubKeyBytes Vertex
|
|
|
|
// ChannelID is the unique channel ID for the channel. The first 3
|
|
// bytes are the block height, the next 3 the index within the block,
|
|
// and the last 2 bytes are the output index for the channel.
|
|
ChannelID uint64
|
|
|
|
// OutgoingTimeLock is the timelock value that should be used when
|
|
// crafting the _outgoing_ HTLC from this hop.
|
|
OutgoingTimeLock uint32
|
|
|
|
// AmtToForward is the amount that this hop will forward to the next
|
|
// hop. This value is less than the value that the incoming HTLC
|
|
// carries as a fee will be subtracted by the hop.
|
|
AmtToForward lnwire.MilliSatoshi
|
|
|
|
// MPP encapsulates the data required for option_mpp. This field should
|
|
// only be set for the final hop.
|
|
MPP *MPP
|
|
|
|
// AMP encapsulates the data required for option_amp. This field should
|
|
// only be set for the final hop.
|
|
AMP *AMP
|
|
|
|
// CustomRecords if non-nil are a set of additional TLV records that
|
|
// should be included in the forwarding instructions for this node.
|
|
CustomRecords lnwire.CustomRecords
|
|
|
|
// LegacyPayload if true, then this signals that this node doesn't
|
|
// understand the new TLV payload, so we must instead use the legacy
|
|
// payload.
|
|
//
|
|
// NOTE: we should no longer ever create a Hop with Legacy set to true.
|
|
// The only reason we are keeping this member is that it could be the
|
|
// case that we have serialised hops persisted to disk where
|
|
// LegacyPayload is true.
|
|
LegacyPayload bool
|
|
|
|
// Metadata is additional data that is sent along with the payment to
|
|
// the payee.
|
|
Metadata []byte
|
|
|
|
// EncryptedData is an encrypted data blob includes for hops that are
|
|
// part of a blinded route.
|
|
EncryptedData []byte
|
|
|
|
// BlindingPoint is an ephemeral public key used by introduction nodes
|
|
// in blinded routes to unblind their portion of the route and pass on
|
|
// the next ephemeral key to the next blinded node to do the same.
|
|
BlindingPoint *btcec.PublicKey
|
|
|
|
// TotalAmtMsat is the total amount for a blinded payment, potentially
|
|
// spread over more than one HTLC. This field should only be set for
|
|
// the final hop in a blinded path.
|
|
TotalAmtMsat lnwire.MilliSatoshi
|
|
}
|
|
|
|
// MPP is a record that encodes the fields necessary for multi-path payments.
|
|
type MPP struct {
|
|
// paymentAddr is a random, receiver-generated value used to avoid
|
|
// collisions with concurrent payers.
|
|
paymentAddr [32]byte
|
|
|
|
// totalMsat is the total value of the payment, potentially spread
|
|
// across more than one HTLC.
|
|
totalMsat lnwire.MilliSatoshi
|
|
}
|
|
|
|
// Record returns a tlv.Record that can be used to encode or decode this record.
|
|
func (r *MPP) Record() tlv.Record {
|
|
// Fixed-size, 32 byte payment address followed by truncated 64-bit
|
|
// total msat.
|
|
size := func() uint64 {
|
|
return 32 + tlv.SizeTUint64(uint64(r.totalMsat))
|
|
}
|
|
|
|
return tlv.MakeDynamicRecord(
|
|
MPPOnionType, r, size, MPPEncoder, MPPDecoder,
|
|
)
|
|
}
|
|
|
|
const (
|
|
// minMPPLength is the minimum length of a serialized MPP TLV record,
|
|
// which occurs when the truncated encoding of total_amt_msat takes 0
|
|
// bytes, leaving only the payment_addr.
|
|
minMPPLength = 32
|
|
|
|
// maxMPPLength is the maximum length of a serialized MPP TLV record,
|
|
// which occurs when the truncated encoding of total_amt_msat takes 8
|
|
// bytes.
|
|
maxMPPLength = 40
|
|
)
|
|
|
|
// MPPEncoder writes the MPP record to the provided io.Writer.
|
|
func MPPEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
|
|
if v, ok := val.(*MPP); ok {
|
|
err := tlv.EBytes32(w, &v.paymentAddr, buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return tlv.ETUint64T(w, uint64(v.totalMsat), buf)
|
|
}
|
|
|
|
return tlv.NewTypeForEncodingErr(val, "MPP")
|
|
}
|
|
|
|
// MPPDecoder reads the MPP record to the provided io.Reader.
|
|
func MPPDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
|
|
if v, ok := val.(*MPP); ok && minMPPLength <= l && l <= maxMPPLength {
|
|
if err := tlv.DBytes32(r, &v.paymentAddr, buf, 32); err != nil {
|
|
return err
|
|
}
|
|
|
|
var total uint64
|
|
if err := tlv.DTUint64(r, &total, buf, l-32); err != nil {
|
|
return err
|
|
}
|
|
v.totalMsat = lnwire.MilliSatoshi(total)
|
|
|
|
return nil
|
|
}
|
|
|
|
return tlv.NewTypeForDecodingErr(val, "MPP", l, maxMPPLength)
|
|
}
|
|
|
|
// AMP is a record that encodes the fields necessary for atomic multi-path
|
|
// payments.
|
|
type AMP struct {
|
|
rootShare [32]byte
|
|
setID [32]byte
|
|
childIndex uint32
|
|
}
|
|
|
|
// AMPEncoder writes the AMP record to the provided io.Writer.
|
|
func AMPEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
|
|
if v, ok := val.(*AMP); ok {
|
|
if err := tlv.EBytes32(w, &v.rootShare, buf); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := tlv.EBytes32(w, &v.setID, buf); err != nil {
|
|
return err
|
|
}
|
|
|
|
return tlv.ETUint32T(w, v.childIndex, buf)
|
|
}
|
|
|
|
return tlv.NewTypeForEncodingErr(val, "AMP")
|
|
}
|
|
|
|
const (
|
|
// minAMPLength is the minimum length of a serialized AMP TLV record,
|
|
// which occurs when the truncated encoding of child_index takes 0
|
|
// bytes, leaving only the root_share and set_id.
|
|
minAMPLength = 64
|
|
|
|
// maxAMPLength is the maximum length of a serialized AMP TLV record,
|
|
// which occurs when the truncated encoding of a child_index takes 2
|
|
// bytes.
|
|
maxAMPLength = 68
|
|
)
|
|
|
|
// AMPDecoder reads the AMP record from the provided io.Reader.
|
|
func AMPDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
|
|
if v, ok := val.(*AMP); ok && minAMPLength <= l && l <= maxAMPLength {
|
|
if err := tlv.DBytes32(r, &v.rootShare, buf, 32); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := tlv.DBytes32(r, &v.setID, buf, 32); err != nil {
|
|
return err
|
|
}
|
|
|
|
return tlv.DTUint32(r, &v.childIndex, buf, l-minAMPLength)
|
|
}
|
|
|
|
return tlv.NewTypeForDecodingErr(val, "AMP", l, maxAMPLength)
|
|
}
|
|
|
|
// Record returns a tlv.Record that can be used to encode or decode this record.
|
|
func (a *AMP) Record() tlv.Record {
|
|
return tlv.MakeDynamicRecord(
|
|
AMPOnionType, a, a.PayloadSize, AMPEncoder, AMPDecoder,
|
|
)
|
|
}
|
|
|
|
// PayloadSize returns the size this record takes up in encoded form.
|
|
func (a *AMP) PayloadSize() uint64 {
|
|
return 32 + 32 + tlv.SizeTUint32(a.childIndex)
|
|
}
|
|
|
|
// SerializeRoute serializes a route.
|
|
func SerializeRoute(w io.Writer, r Route) error {
|
|
if err := WriteElements(w,
|
|
r.TotalTimeLock, r.TotalAmount, r.SourcePubKey[:],
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := WriteElements(w, uint32(len(r.Hops))); err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, h := range r.Hops {
|
|
if err := serializeHop(w, h); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Any new/extra TLV data is encoded in serializeHTLCAttemptInfo!
|
|
|
|
return nil
|
|
}
|
|
|
|
func serializeHop(w io.Writer, h *Hop) error {
|
|
if err := WriteElements(w,
|
|
h.PubKeyBytes[:],
|
|
h.ChannelID,
|
|
h.OutgoingTimeLock,
|
|
h.AmtToForward,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := binary.Write(w, byteOrder, h.LegacyPayload); err != nil {
|
|
return err
|
|
}
|
|
|
|
// For legacy payloads, we don't need to write any TLV records, so
|
|
// we'll write a zero indicating the our serialized TLV map has no
|
|
// records.
|
|
if h.LegacyPayload {
|
|
return WriteElements(w, uint32(0))
|
|
}
|
|
|
|
// Gather all non-primitive TLV records so that they can be serialized
|
|
// as a single blob.
|
|
//
|
|
// TODO(conner): add migration to unify all fields in a single TLV
|
|
// blobs. The split approach will cause headaches down the road as more
|
|
// fields are added, which we can avoid by having a single TLV stream
|
|
// for all payload fields.
|
|
var records []tlv.Record
|
|
if h.MPP != nil {
|
|
records = append(records, h.MPP.Record())
|
|
}
|
|
|
|
// Add blinding point and encrypted data if present.
|
|
if h.EncryptedData != nil {
|
|
records = append(records, NewEncryptedDataRecord(
|
|
&h.EncryptedData,
|
|
))
|
|
}
|
|
|
|
if h.BlindingPoint != nil {
|
|
records = append(records, NewBlindingPointRecord(
|
|
&h.BlindingPoint,
|
|
))
|
|
}
|
|
|
|
if h.AMP != nil {
|
|
records = append(records, h.AMP.Record())
|
|
}
|
|
|
|
if h.Metadata != nil {
|
|
records = append(records, NewMetadataRecord(&h.Metadata))
|
|
}
|
|
|
|
if h.TotalAmtMsat != 0 {
|
|
totalMsatInt := uint64(h.TotalAmtMsat)
|
|
records = append(
|
|
records, NewTotalAmtMsatBlinded(&totalMsatInt),
|
|
)
|
|
}
|
|
|
|
// Final sanity check to absolutely rule out custom records that are not
|
|
// custom and write into the standard range.
|
|
if err := h.CustomRecords.Validate(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Convert custom records to tlv and add to the record list.
|
|
// MapToRecords sorts the list, so adding it here will keep the list
|
|
// canonical.
|
|
tlvRecords := tlv.MapToRecords(h.CustomRecords)
|
|
records = append(records, tlvRecords...)
|
|
|
|
// Otherwise, we'll transform our slice of records into a map of the
|
|
// raw bytes, then serialize them in-line with a length (number of
|
|
// elements) prefix.
|
|
mapRecords, err := tlv.RecordsToMap(records)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
numRecords := uint32(len(mapRecords))
|
|
if err := WriteElements(w, numRecords); err != nil {
|
|
return err
|
|
}
|
|
|
|
for recordType, rawBytes := range mapRecords {
|
|
if err := WriteElements(w, recordType); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := wire.WriteVarBytes(w, 0, rawBytes); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeserializeRoute deserializes a route.
|
|
func DeserializeRoute(r io.Reader) (Route, error) {
|
|
rt := Route{}
|
|
if err := ReadElements(r,
|
|
&rt.TotalTimeLock, &rt.TotalAmount,
|
|
); err != nil {
|
|
return rt, err
|
|
}
|
|
|
|
var pub []byte
|
|
if err := ReadElements(r, &pub); err != nil {
|
|
return rt, err
|
|
}
|
|
copy(rt.SourcePubKey[:], pub)
|
|
|
|
var numHops uint32
|
|
if err := ReadElements(r, &numHops); err != nil {
|
|
return rt, err
|
|
}
|
|
|
|
var hops []*Hop
|
|
for i := uint32(0); i < numHops; i++ {
|
|
hop, err := deserializeHop(r)
|
|
if err != nil {
|
|
return rt, err
|
|
}
|
|
hops = append(hops, hop)
|
|
}
|
|
rt.Hops = hops
|
|
|
|
// Any new/extra TLV data is decoded in deserializeHTLCAttemptInfo!
|
|
|
|
return rt, nil
|
|
}
|
|
|
|
// maxOnionPayloadSize is the largest Sphinx payload possible, so we don't need
|
|
// to read/write a TLV stream larger than this.
|
|
const maxOnionPayloadSize = 1300
|
|
|
|
func deserializeHop(r io.Reader) (*Hop, error) {
|
|
h := &Hop{}
|
|
|
|
var pub []byte
|
|
if err := ReadElements(r, &pub); err != nil {
|
|
return nil, err
|
|
}
|
|
copy(h.PubKeyBytes[:], pub)
|
|
|
|
if err := ReadElements(r,
|
|
&h.ChannelID, &h.OutgoingTimeLock, &h.AmtToForward,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// TODO(roasbeef): change field to allow LegacyPayload false to be the
|
|
// legacy default?
|
|
err := binary.Read(r, byteOrder, &h.LegacyPayload)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var numElements uint32
|
|
if err := ReadElements(r, &numElements); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// If there're no elements, then we can return early.
|
|
if numElements == 0 {
|
|
return h, nil
|
|
}
|
|
|
|
tlvMap := make(map[uint64][]byte)
|
|
for i := uint32(0); i < numElements; i++ {
|
|
var tlvType uint64
|
|
if err := ReadElements(r, &tlvType); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rawRecordBytes, err := wire.ReadVarBytes(
|
|
r, 0, maxOnionPayloadSize, "tlv",
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tlvMap[tlvType] = rawRecordBytes
|
|
}
|
|
|
|
// If the MPP type is present, remove it from the generic TLV map and
|
|
// parse it back into a proper MPP struct.
|
|
//
|
|
// TODO(conner): add migration to unify all fields in a single TLV
|
|
// blobs. The split approach will cause headaches down the road as more
|
|
// fields are added, which we can avoid by having a single TLV stream
|
|
// for all payload fields.
|
|
mppType := uint64(MPPOnionType)
|
|
if mppBytes, ok := tlvMap[mppType]; ok {
|
|
delete(tlvMap, mppType)
|
|
|
|
var (
|
|
mpp = &MPP{}
|
|
mppRec = mpp.Record()
|
|
r = bytes.NewReader(mppBytes)
|
|
)
|
|
err := mppRec.Decode(r, uint64(len(mppBytes)))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
h.MPP = mpp
|
|
}
|
|
|
|
// If encrypted data or blinding key are present, remove them from
|
|
// the TLV map and parse into proper types.
|
|
encryptedDataType := uint64(EncryptedDataOnionType)
|
|
if data, ok := tlvMap[encryptedDataType]; ok {
|
|
delete(tlvMap, encryptedDataType)
|
|
h.EncryptedData = data
|
|
}
|
|
|
|
blindingType := uint64(BlindingPointOnionType)
|
|
if blindingPoint, ok := tlvMap[blindingType]; ok {
|
|
delete(tlvMap, blindingType)
|
|
|
|
h.BlindingPoint, err = btcec.ParsePubKey(blindingPoint)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid blinding point: %w",
|
|
err)
|
|
}
|
|
}
|
|
|
|
ampType := uint64(AMPOnionType)
|
|
if ampBytes, ok := tlvMap[ampType]; ok {
|
|
delete(tlvMap, ampType)
|
|
|
|
var (
|
|
amp = &{}
|
|
ampRec = amp.Record()
|
|
r = bytes.NewReader(ampBytes)
|
|
)
|
|
err := ampRec.Decode(r, uint64(len(ampBytes)))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
h.AMP = amp
|
|
}
|
|
|
|
// If the metadata type is present, remove it from the tlv map and
|
|
// populate directly on the hop.
|
|
metadataType := uint64(MetadataOnionType)
|
|
if metadata, ok := tlvMap[metadataType]; ok {
|
|
delete(tlvMap, metadataType)
|
|
|
|
h.Metadata = metadata
|
|
}
|
|
|
|
totalAmtMsatType := uint64(TotalAmtMsatBlindedType)
|
|
if totalAmtMsat, ok := tlvMap[totalAmtMsatType]; ok {
|
|
delete(tlvMap, totalAmtMsatType)
|
|
|
|
var (
|
|
totalAmtMsatInt uint64
|
|
buf [8]byte
|
|
)
|
|
if err := tlv.DTUint64(
|
|
bytes.NewReader(totalAmtMsat),
|
|
&totalAmtMsatInt,
|
|
&buf,
|
|
uint64(len(totalAmtMsat)),
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
h.TotalAmtMsat = lnwire.MilliSatoshi(totalAmtMsatInt)
|
|
}
|
|
|
|
h.CustomRecords = tlvMap
|
|
|
|
return h, nil
|
|
}
|