lnd/lnwire/channel_update_2.go
Elle Mouton a5f3fa17e7
lnwire21: update Msat and TrueBoolean
Add the TrueBoolean type along with its Record method. Also update the
Millisatoshi type with a Record method. Both of these will be used in an
upcoming commit which adjusts a mission control migration to use pure
TLV types.
2024-11-01 08:59:54 +02:00

442 lines
14 KiB
Go

package lnwire
import (
"bytes"
"fmt"
"io"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/tlv"
)
const (
defaultCltvExpiryDelta = uint16(80)
defaultHtlcMinMsat = MilliSatoshi(1)
defaultFeeBaseMsat = uint32(1000)
defaultFeeProportionalMillionths = uint32(1)
)
// ChannelUpdate2 message is used after taproot channel has been initially
// announced. Each side independently announces its fees and minimum expiry for
// HTLCs and other parameters. This message is also used to redeclare initially
// set channel parameters.
type ChannelUpdate2 struct {
// Signature is used to validate the announced data and prove the
// ownership of node id.
Signature Sig
// ChainHash denotes the target chain that this channel was opened
// within. This value should be the genesis hash of the target chain.
// Along with the short channel ID, this uniquely identifies the
// channel globally in a blockchain.
ChainHash tlv.RecordT[tlv.TlvType0, chainhash.Hash]
// ShortChannelID is the unique description of the funding transaction.
ShortChannelID tlv.RecordT[tlv.TlvType2, ShortChannelID]
// BlockHeight allows ordering in the case of multiple announcements. We
// should ignore the message if block height is not greater than the
// last-received. The block height must always be greater or equal to
// the block height that the channel funding transaction was confirmed
// in.
BlockHeight tlv.RecordT[tlv.TlvType4, uint32]
// DisabledFlags is an optional bitfield that describes various reasons
// that the node is communicating that the channel should be considered
// disabled.
DisabledFlags tlv.RecordT[tlv.TlvType6, ChanUpdateDisableFlags]
// SecondPeer is used to indicate which node the channel node has
// created and signed this message. If this field is present, it was
// node 2 otherwise it was node 1.
SecondPeer tlv.OptionalRecordT[tlv.TlvType8, TrueBoolean]
// CLTVExpiryDelta is the minimum number of blocks this node requires to
// be added to the expiry of HTLCs. This is a security parameter
// determined by the node operator. This value represents the required
// gap between the time locks of the incoming and outgoing HTLC's set
// to this node.
CLTVExpiryDelta tlv.RecordT[tlv.TlvType10, uint16]
// HTLCMinimumMsat is the minimum HTLC value which will be accepted.
HTLCMinimumMsat tlv.RecordT[tlv.TlvType12, MilliSatoshi]
// HtlcMaximumMsat is the maximum HTLC value which will be accepted.
HTLCMaximumMsat tlv.RecordT[tlv.TlvType14, MilliSatoshi]
// FeeBaseMsat is the base fee that must be used for incoming HTLC's to
// this particular channel. This value will be tacked onto the required
// for a payment independent of the size of the payment.
FeeBaseMsat tlv.RecordT[tlv.TlvType16, uint32]
// FeeProportionalMillionths is the fee rate that will be charged per
// millionth of a satoshi.
FeeProportionalMillionths tlv.RecordT[tlv.TlvType18, uint32]
// ExtraOpaqueData is the set of data that was appended to this message
// to fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraOpaqueData ExtraOpaqueData
}
// Decode deserializes a serialized ChannelUpdate2 stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (c *ChannelUpdate2) Decode(r io.Reader, _ uint32) error {
err := ReadElement(r, &c.Signature)
if err != nil {
return err
}
c.Signature.ForceSchnorr()
return c.DecodeTLVRecords(r)
}
// DecodeTLVRecords decodes only the TLV section of the message.
func (c *ChannelUpdate2) DecodeTLVRecords(r io.Reader) error {
// First extract into extra opaque data.
var tlvRecords ExtraOpaqueData
if err := ReadElements(r, &tlvRecords); err != nil {
return err
}
var (
chainHash = tlv.ZeroRecordT[tlv.TlvType0, [32]byte]()
secondPeer = tlv.ZeroRecordT[tlv.TlvType8, TrueBoolean]()
)
typeMap, err := tlvRecords.ExtractRecords(
&chainHash, &c.ShortChannelID, &c.BlockHeight, &c.DisabledFlags,
&secondPeer, &c.CLTVExpiryDelta, &c.HTLCMinimumMsat,
&c.HTLCMaximumMsat, &c.FeeBaseMsat,
&c.FeeProportionalMillionths,
)
if err != nil {
return err
}
// By default, the chain-hash is the bitcoin mainnet genesis block hash.
c.ChainHash.Val = *chaincfg.MainNetParams.GenesisHash
if _, ok := typeMap[c.ChainHash.TlvType()]; ok {
c.ChainHash.Val = chainHash.Val
}
// The presence of the second_peer tlv type indicates "true".
if _, ok := typeMap[c.SecondPeer.TlvType()]; ok {
c.SecondPeer = tlv.SomeRecordT(secondPeer)
}
// If the CLTV expiry delta was not encoded, then set it to the default
// value.
if _, ok := typeMap[c.CLTVExpiryDelta.TlvType()]; !ok {
c.CLTVExpiryDelta.Val = defaultCltvExpiryDelta
}
// If the HTLC Minimum msat was not encoded, then set it to the default
// value.
if _, ok := typeMap[c.HTLCMinimumMsat.TlvType()]; !ok {
c.HTLCMinimumMsat.Val = defaultHtlcMinMsat
}
// If the base fee was not encoded, then set it to the default value.
if _, ok := typeMap[c.FeeBaseMsat.TlvType()]; !ok {
c.FeeBaseMsat.Val = defaultFeeBaseMsat
}
// If the proportional fee was not encoded, then set it to the default
// value.
if _, ok := typeMap[c.FeeProportionalMillionths.TlvType()]; !ok {
c.FeeProportionalMillionths.Val = defaultFeeProportionalMillionths //nolint:lll
}
if len(tlvRecords) != 0 {
c.ExtraOpaqueData = tlvRecords
}
return nil
}
// Encode serializes the target ChannelUpdate2 into the passed io.Writer
// observing the protocol version specified.
//
// This is part of the lnwire.Message interface.
func (c *ChannelUpdate2) Encode(w *bytes.Buffer, _ uint32) error {
_, err := w.Write(c.Signature.RawBytes())
if err != nil {
return err
}
_, err = c.DataToSign()
if err != nil {
return err
}
return WriteBytes(w, c.ExtraOpaqueData)
}
// DataToSign is used to retrieve part of the announcement message which should
// be signed. For the ChannelUpdate2 message, this includes the serialised TLV
// records.
func (c *ChannelUpdate2) DataToSign() ([]byte, error) {
// The chain-hash record is only included if it is _not_ equal to the
// bitcoin mainnet genisis block hash.
var recordProducers []tlv.RecordProducer
if !c.ChainHash.Val.IsEqual(chaincfg.MainNetParams.GenesisHash) {
hash := tlv.ZeroRecordT[tlv.TlvType0, [32]byte]()
hash.Val = c.ChainHash.Val
recordProducers = append(recordProducers, &hash)
}
recordProducers = append(recordProducers,
&c.ShortChannelID, &c.BlockHeight,
)
// Only include the disable flags if any bit is set.
if !c.DisabledFlags.Val.IsEnabled() {
recordProducers = append(recordProducers, &c.DisabledFlags)
}
// We only need to encode the second peer boolean if it is true
c.SecondPeer.WhenSome(func(r tlv.RecordT[tlv.TlvType8, TrueBoolean]) {
recordProducers = append(recordProducers, &r)
})
// We only encode the cltv expiry delta if it is not equal to the
// default.
if c.CLTVExpiryDelta.Val != defaultCltvExpiryDelta {
recordProducers = append(recordProducers, &c.CLTVExpiryDelta)
}
if c.HTLCMinimumMsat.Val != defaultHtlcMinMsat {
recordProducers = append(recordProducers, &c.HTLCMinimumMsat)
}
recordProducers = append(recordProducers, &c.HTLCMaximumMsat)
if c.FeeBaseMsat.Val != defaultFeeBaseMsat {
recordProducers = append(recordProducers, &c.FeeBaseMsat)
}
if c.FeeProportionalMillionths.Val != defaultFeeProportionalMillionths {
recordProducers = append(
recordProducers, &c.FeeProportionalMillionths,
)
}
err := EncodeMessageExtraData(&c.ExtraOpaqueData, recordProducers...)
if err != nil {
return nil, err
}
return c.ExtraOpaqueData, nil
}
// MsgType returns the integer uniquely identifying this message type on the
// wire.
//
// This is part of the lnwire.Message interface.
func (c *ChannelUpdate2) MsgType() MessageType {
return MsgChannelUpdate2
}
func (c *ChannelUpdate2) ExtraData() ExtraOpaqueData {
return c.ExtraOpaqueData
}
// A compile time check to ensure ChannelUpdate2 implements the
// lnwire.Message interface.
var _ Message = (*ChannelUpdate2)(nil)
// SCID returns the ShortChannelID of the channel that the update applies to.
//
// NOTE: this is part of the ChannelUpdate interface.
func (c *ChannelUpdate2) SCID() ShortChannelID {
return c.ShortChannelID.Val
}
// IsNode1 is true if the update was produced by node 1 of the channel peers.
// Node 1 is the node with the lexicographically smaller public key.
//
// NOTE: this is part of the ChannelUpdate interface.
func (c *ChannelUpdate2) IsNode1() bool {
return c.SecondPeer.IsNone()
}
// IsDisabled is true if the update is announcing that the channel should be
// considered disabled.
//
// NOTE: this is part of the ChannelUpdate interface.
func (c *ChannelUpdate2) IsDisabled() bool {
return !c.DisabledFlags.Val.IsEnabled()
}
// GetChainHash returns the hash of the chain that the message is referring to.
//
// NOTE: this is part of the ChannelUpdate interface.
func (c *ChannelUpdate2) GetChainHash() chainhash.Hash {
return c.ChainHash.Val
}
// ForwardingPolicy returns the set of forwarding constraints of the update.
//
// NOTE: this is part of the ChannelUpdate interface.
func (c *ChannelUpdate2) ForwardingPolicy() *ForwardingPolicy {
return &ForwardingPolicy{
TimeLockDelta: c.CLTVExpiryDelta.Val,
BaseFee: MilliSatoshi(c.FeeBaseMsat.Val),
FeeRate: MilliSatoshi(c.FeeProportionalMillionths.Val),
MinHTLC: c.HTLCMinimumMsat.Val,
HasMaxHTLC: true,
MaxHTLC: c.HTLCMaximumMsat.Val,
}
}
// CmpAge can be used to determine if the update is older or newer than the
// passed update. It returns 1 if this update is newer, -1 if it is older, and
// 0 if they are the same age.
//
// NOTE: this is part of the ChannelUpdate interface.
func (c *ChannelUpdate2) CmpAge(update ChannelUpdate) (CompareResult, error) {
other, ok := update.(*ChannelUpdate2)
if !ok {
return 0, fmt.Errorf("expected *ChannelUpdate2, got: %T",
update)
}
switch {
case c.BlockHeight.Val > other.BlockHeight.Val:
return GreaterThan, nil
case c.BlockHeight.Val < other.BlockHeight.Val:
return LessThan, nil
default:
return EqualTo, nil
}
}
// SetDisabledFlag can be used to adjust the disabled flag of an update.
//
// NOTE: this is part of the ChannelUpdate interface.
func (c *ChannelUpdate2) SetDisabledFlag(disabled bool) {
if disabled {
c.DisabledFlags.Val |= ChanUpdateDisableIncoming
c.DisabledFlags.Val |= ChanUpdateDisableOutgoing
} else {
c.DisabledFlags.Val &^= ChanUpdateDisableIncoming
c.DisabledFlags.Val &^= ChanUpdateDisableOutgoing
}
}
// SetSCID can be used to overwrite the SCID of the update.
//
// NOTE: this is part of the ChannelUpdate interface.
func (c *ChannelUpdate2) SetSCID(scid ShortChannelID) {
c.ShortChannelID.Val = scid
}
// A compile time check to ensure ChannelUpdate2 implements the
// lnwire.ChannelUpdate interface.
var _ ChannelUpdate = (*ChannelUpdate2)(nil)
// ChanUpdateDisableFlags is a bit vector that can be used to indicate various
// reasons for the channel being marked as disabled.
type ChanUpdateDisableFlags uint8
const (
// ChanUpdateDisableIncoming is a bit indicates that a channel is
// disabled in the inbound direction meaning that the node broadcasting
// the update is communicating that they cannot receive funds.
ChanUpdateDisableIncoming ChanUpdateDisableFlags = 1 << iota
// ChanUpdateDisableOutgoing is a bit indicates that a channel is
// disabled in the outbound direction meaning that the node broadcasting
// the update is communicating that they cannot send or route funds.
ChanUpdateDisableOutgoing = 2
)
// IncomingDisabled returns true if the ChanUpdateDisableIncoming bit is set.
func (c ChanUpdateDisableFlags) IncomingDisabled() bool {
return c&ChanUpdateDisableIncoming == ChanUpdateDisableIncoming
}
// OutgoingDisabled returns true if the ChanUpdateDisableOutgoing bit is set.
func (c ChanUpdateDisableFlags) OutgoingDisabled() bool {
return c&ChanUpdateDisableOutgoing == ChanUpdateDisableOutgoing
}
// IsEnabled returns true if none of the disable bits are set.
func (c ChanUpdateDisableFlags) IsEnabled() bool {
return c == 0
}
// String returns the bitfield flags as a string.
func (c ChanUpdateDisableFlags) String() string {
return fmt.Sprintf("%08b", c)
}
// Record returns the tlv record for the disable flags.
func (c *ChanUpdateDisableFlags) Record() tlv.Record {
return tlv.MakeStaticRecord(0, c, 1, encodeDisableFlags,
decodeDisableFlags)
}
func encodeDisableFlags(w io.Writer, val interface{}, buf *[8]byte) error {
if v, ok := val.(*ChanUpdateDisableFlags); ok {
flagsInt := uint8(*v)
return tlv.EUint8(w, &flagsInt, buf)
}
return tlv.NewTypeForEncodingErr(val, "lnwire.ChanUpdateDisableFlags")
}
func decodeDisableFlags(r io.Reader, val interface{}, buf *[8]byte,
l uint64) error {
if v, ok := val.(*ChanUpdateDisableFlags); ok {
var flagsInt uint8
err := tlv.DUint8(r, &flagsInt, buf, l)
if err != nil {
return err
}
*v = ChanUpdateDisableFlags(flagsInt)
return nil
}
return tlv.NewTypeForDecodingErr(val, "lnwire.ChanUpdateDisableFlags",
l, l)
}
// TrueBoolean is a record that indicates true or false using the presence of
// the record. If the record is absent, it indicates false. If it is present,
// it indicates true.
type TrueBoolean struct{}
// Record returns the tlv record for the boolean entry.
func (b *TrueBoolean) Record() tlv.Record {
return tlv.MakeStaticRecord(
0, b, 0, booleanEncoder, booleanDecoder,
)
}
func booleanEncoder(_ io.Writer, val interface{}, _ *[8]byte) error {
if _, ok := val.(*TrueBoolean); ok {
return nil
}
return tlv.NewTypeForEncodingErr(val, "TrueBoolean")
}
func booleanDecoder(_ io.Reader, val interface{}, _ *[8]byte,
l uint64) error {
if _, ok := val.(*TrueBoolean); ok && (l == 0 || l == 1) {
return nil
}
return tlv.NewTypeForEncodingErr(val, "TrueBoolean")
}