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") }