package models import ( "fmt" "time" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnwire" ) // ChannelEdgePolicy represents a *directed* edge within the channel graph. For // each channel in the database, there are two distinct edges: one for each // possible direction of travel along the channel. The edges themselves hold // information concerning fees, and minimum time-lock information which is // utilized during path finding. type ChannelEdgePolicy struct { // Version is the gossip version of the channel update that produced // this policy. Version lnwire.GossipVersion // SigBytes is the raw bytes of the signature of the channel edge // policy. We'll only parse these if the caller needs to access the // signature for validation purposes. SigBytes []byte // 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 // LastUpdate is the last time an authenticated edge for this channel // was received. LastUpdate time.Time // LastBlockHeight is the block height that timestamps the last update // for v2 channel updates. LastBlockHeight uint32 // SecondPeer indicates whether this policy was announced by the second // peer in the channel for v2 channel updates. SecondPeer bool // MessageFlags is a bitfield which indicates the presence of optional // fields (like max_htlc) in the policy. MessageFlags lnwire.ChanUpdateMsgFlags // ChannelFlags is a bitfield which signals the capabilities of the // channel as well as the directed edge this update applies to. ChannelFlags lnwire.ChanUpdateChanFlags // DisableFlags is a v2-specific bitfield which signals whether the // channel is disabled for incoming or outgoing traffic. DisableFlags lnwire.ChanUpdateDisableFlags // TimeLockDelta is the number of blocks this node will subtract from // the expiry of an incoming HTLC. This value expresses the time buffer // the node would like to HTLC exchanges. TimeLockDelta uint16 // MinHTLC is the smallest value HTLC this node will forward, expressed // in millisatoshi. MinHTLC lnwire.MilliSatoshi // MaxHTLC is the largest value HTLC this node will forward, expressed // in millisatoshi. MaxHTLC lnwire.MilliSatoshi // FeeBaseMSat is the base HTLC fee that will be charged for forwarding // ANY HTLC, expressed in mSAT's. FeeBaseMSat lnwire.MilliSatoshi // FeeProportionalMillionths is the rate that the node will charge for // HTLCs for each millionth of a satoshi forwarded. FeeProportionalMillionths lnwire.MilliSatoshi // ToNode is the public key of the node that this directed edge leads // to. Using this pub key, the channel graph can further be traversed. ToNode [33]byte // InboundFee is the fee that must be paid for incoming HTLCs. // // NOTE: for our kvdb implementation of the graph store, inbound fees // are still only persisted as part of extra opaque data and so this // field is not explicitly stored but is rather populated from the // ExtraOpaqueData field on deserialization. For our SQL implementation, // this field will be explicitly persisted in the database. InboundFee fn.Option[lnwire.Fee] // ExtraOpaqueData is the set of data that was appended to this // message, some of which we may not actually know how to iterate or // parse. By holding onto this data, we ensure that we're able to // properly validate the set of signatures that cover these new fields, // and ensure we're able to make upgrades to the network in a forwards // compatible manner. ExtraOpaqueData lnwire.ExtraOpaqueData // ExtraSignedFields are the extra signed fields found in v2 channel // updates. ExtraSignedFields map[uint64][]byte } // ChanEdgePolicyFromWire constructs a ChannelEdgePolicy from a channel update // message. func ChanEdgePolicyFromWire(scid uint64, update lnwire.ChannelUpdate) (*ChannelEdgePolicy, error) { switch upd := update.(type) { case *lnwire.ChannelUpdate1: //nolint:ll return &ChannelEdgePolicy{ Version: lnwire.GossipVersion1, SigBytes: upd.Signature.ToSignatureBytes(), ChannelID: scid, LastUpdate: time.Unix(int64(upd.Timestamp), 0), MessageFlags: upd.MessageFlags, ChannelFlags: upd.ChannelFlags, TimeLockDelta: upd.TimeLockDelta, MinHTLC: upd.HtlcMinimumMsat, MaxHTLC: upd.HtlcMaximumMsat, FeeBaseMSat: lnwire.MilliSatoshi(upd.BaseFee), FeeProportionalMillionths: lnwire.MilliSatoshi(upd.FeeRate), InboundFee: upd.InboundFee.ValOpt(), ExtraOpaqueData: upd.ExtraOpaqueData, }, nil case *lnwire.ChannelUpdate2: return &ChannelEdgePolicy{ Version: lnwire.GossipVersion2, SigBytes: upd.Signature.Val.ToSignatureBytes(), ChannelID: scid, LastBlockHeight: upd.BlockHeight.Val, SecondPeer: upd.SecondPeer.IsSome(), DisableFlags: upd.DisabledFlags.Val, TimeLockDelta: upd.CLTVExpiryDelta.Val, MinHTLC: upd.HTLCMinimumMsat.Val, MaxHTLC: upd.HTLCMaximumMsat.Val, FeeBaseMSat: lnwire.MilliSatoshi( upd.FeeBaseMsat.Val, ), FeeProportionalMillionths: lnwire.MilliSatoshi( upd.FeeProportionalMillionths.Val, ), InboundFee: upd.InboundFee.ValOpt(), ExtraSignedFields: upd.ExtraSignedFields, }, nil } return nil, fmt.Errorf("unknown channel update version: %v", update.MsgType()) } // IsNode1 returns true if this policy was announced by the channel's node_1. func (c *ChannelEdgePolicy) IsNode1() bool { if c.Version == lnwire.GossipVersion1 { return c.ChannelFlags&lnwire.ChanUpdateDirection == 0 } return !c.SecondPeer } // IsDisabled determines whether the edge has the disabled bit set. // // NOTE: for v2 channel updates, we return true here only if both the incoming // and outgoing disabled bits are set. func (c *ChannelEdgePolicy) IsDisabled() bool { if c.Version == lnwire.GossipVersion1 { return c.ChannelFlags.IsDisabled() } return !c.DisableFlags.IsEnabled() } // ComputeFee computes the fee to forward an HTLC of `amt` milli-satoshis over // the passed active payment channel. This value is currently computed as // specified in BOLT07, but will likely change in the near future. func (c *ChannelEdgePolicy) ComputeFee( amt lnwire.MilliSatoshi) lnwire.MilliSatoshi { return c.FeeBaseMSat + (amt*c.FeeProportionalMillionths)/feeRateParts } // String returns a human-readable version of the channel edge policy. func (c *ChannelEdgePolicy) String() string { if c.Version == lnwire.GossipVersion1 { return fmt.Sprintf("ChannelID=%v, MessageFlags=%v, "+ "ChannelFlags=%v, LastUpdate=%v", c.ChannelID, c.MessageFlags, c.ChannelFlags, c.LastUpdate) } return fmt.Sprintf("ChannelID=%v, Node1=%v, DisableFlags=%v, "+ "BlockHeight=%v", c.ChannelID, !c.SecondPeer, c.DisableFlags, c.LastBlockHeight) }