From 2f79908a49f14406706ce4a6aa1919c0a29d6805 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Thu, 1 Sep 2022 12:56:02 +0200 Subject: [PATCH] htlcswitch: add p2p inbound fee --- htlcswitch/link.go | 77 ++++++++++++++++++++++++++++++++ htlcswitch/link_test.go | 14 ++++++ lnwallet/channel.go | 4 ++ lnwire/message.go | 5 +++ lnwire/update_inbound_fee.go | 86 ++++++++++++++++++++++++++++++++++++ 5 files changed, 186 insertions(+) create mode 100644 lnwire/update_inbound_fee.go diff --git a/htlcswitch/link.go b/htlcswitch/link.go index f2525ba86e..b5fd6ad87b 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -399,6 +399,12 @@ type channelLink struct { // log is a link-specific logging instance. log btclog.Logger + ourInboundBaseFee int32 + ourInboundFeeRate int32 + + theirInboundBaseFee int32 + theirInboundFeeRate int32 + wg sync.WaitGroup quit chan struct{} } @@ -1127,6 +1133,18 @@ func (l *channelLink) htlcManager() { go l.fwdPkgGarbager() } + l.ourInboundBaseFee = -10000 + l.ourInboundFeeRate = -20000 + + err := l.cfg.Peer.SendMessage(false, &lnwire.UpdateInboundFee{ + ChanID: l.ChanID(), + BaseFee: l.ourInboundBaseFee, + FeeRate: l.ourInboundFeeRate, + }) + if err != nil { + l.log.Warnf("unable to send UpdateInboundFee: %v", err) + } + for { // We must always check if we failed at some point processing // the last update before processing the next. @@ -1416,6 +1434,14 @@ func (l *channelLink) handleDownstreamUpdateAdd(pkt *htlcPacket) error { return errors.New("not an UpdateAddHTLC packet") } + inboundFee := lnwire.MilliSatoshi(l.calculateInboundFee(int64(pkt.amount))) + + l.log.Infof("Adding inbound fee to htlc amount: fee=%v, baseFee=%v, feeRate=%v", + int64(inboundFee), l.theirInboundBaseFee, l.theirInboundFeeRate) + + htlc.Amount += inboundFee + pkt.amount += inboundFee + // If hodl.AddOutgoing mode is active, we exit early to simulate // arbitrary delays between the switch adding an ADD to the // mailbox, and the HTLC being added to the commitment state. @@ -2090,6 +2116,14 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { "ChannelPoint(%v): received error from peer: %v", l.channel.ChannelPoint(), msg.Error(), ) + + case *lnwire.UpdateInboundFee: + l.log.Infof("Updating inbound fee to %v + %v ppm", + msg.BaseFee, msg.FeeRate) + + l.theirInboundBaseFee = msg.BaseFee + l.theirInboundFeeRate = msg.FeeRate + default: l.log.Warnf("received unknown message of type %T", msg) } @@ -2995,6 +3029,12 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, fwdInfo := pld.ForwardingInfo() + // Subtract inbound fee. + modifiedPd := *pd + inboundFee := lnwire.MilliSatoshi(l.calculateInboundFeeFromIncoming(int64(pd.Amount))) + modifiedPd.Amount -= inboundFee + pd = &modifiedPd + switch fwdInfo.NextHop { case hop.Exit: err := l.processExitHop( @@ -3162,6 +3202,43 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg, l.forwardBatch(replay, switchPackets...) } +func calculateInboundFee( + amt int64, baseFee, feeRate int32) int64 { + + return int64(baseFee) + + int64(feeRate)*amt/1e6 +} + +func (l *channelLink) calculateInboundFee(outgoingAmt int64) int64 { + return calculateInboundFee( + outgoingAmt, l.theirInboundBaseFee, l.theirInboundFeeRate, + ) +} + +// divideCeil divides dividend by factor and rounds the result up. +func divideCeil(dividend, factor int64) int64 { + return (dividend + factor - 1) / factor +} + +func calculateInboundFeeFromIncoming( + incomingAmt int64, baseFee, feeRate int32) int64 { + + return incomingAmt - divideCeil( + 1e6*(incomingAmt-int64(baseFee)), + 1e6+int64(feeRate), + ) +} + +// ComputeFeeFromIncoming computes the fee to forward an HTLC given the incoming +// amount. +func (l *channelLink) calculateInboundFeeFromIncoming( + incomingAmt int64) int64 { + + return calculateInboundFeeFromIncoming( + incomingAmt, l.ourInboundBaseFee, l.ourInboundFeeRate, + ) +} + // processExitHop handles an htlc for which this link is the exit hop. It // returns a boolean indicating whether the commitment tx needs an update. func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor, diff --git a/htlcswitch/link_test.go b/htlcswitch/link_test.go index ce49b1ea6b..6664790dab 100644 --- a/htlcswitch/link_test.go +++ b/htlcswitch/link_test.go @@ -6649,3 +6649,17 @@ func assertFailureCode(t *testing.T, err error, code lnwire.FailCode) { code, rtErr.WireMessage().Code()) } } + +func TestCalculateInboundFee(t *testing.T) { + const ( + amt = 10000000 + feeBase = -10000 + feeRate = -20000 + ) + + fee := calculateInboundFee(amt, feeBase, feeRate) + require.Equal(t, -210000, int(fee)) + + fee = calculateInboundFeeFromIncoming(amt+fee, feeBase, feeRate) + require.Equal(t, -210000, int(fee)) +} diff --git a/lnwallet/channel.go b/lnwallet/channel.go index da93b3d2cc..702c5e0c8f 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -5366,6 +5366,8 @@ func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.UpdateAddHTLC) (uint64, err return 0, err } + lc.log.Infof("DEBUG: receiving amount %v", htlc.Amount) + lc.remoteUpdateLog.appendHtlc(pd) return pd.HtlcIndex, nil @@ -5407,6 +5409,8 @@ func (lc *LightningChannel) SettleHTLC(preimage [32]byte, return ErrUnknownHtlcIndex{lc.ShortChanID(), htlcIndex} } + lc.log.Infof("DEBUG: settling amount %v", htlc.Amount) + // Now that we know the HTLC exists, before checking to see if the // preimage matches, we'll ensure that we haven't already attempted to // modify the HTLC. diff --git a/lnwire/message.go b/lnwire/message.go index ac57c8a294..4688b59c3a 100644 --- a/lnwire/message.go +++ b/lnwire/message.go @@ -42,6 +42,7 @@ const ( MsgUpdateFee = 134 MsgUpdateFailMalformedHTLC = 135 MsgChannelReestablish = 136 + MsgUpdateInboundFee = 32768 MsgChannelAnnouncement = 256 MsgNodeAnnouncement = 257 MsgChannelUpdate = 258 @@ -96,6 +97,8 @@ func (t MessageType) String() string { return "ClosingSigned" case MsgUpdateAddHTLC: return "UpdateAddHTLC" + case MsgUpdateInboundFee: + return "UpdateInboundFee" case MsgUpdateFailHTLC: return "UpdateFailHTLC" case MsgUpdateFulfillHTLC: @@ -198,6 +201,8 @@ func makeEmptyMessage(msgType MessageType) (Message, error) { msg = &ClosingSigned{} case MsgUpdateAddHTLC: msg = &UpdateAddHTLC{} + case MsgUpdateInboundFee: + msg = &UpdateInboundFee{} case MsgUpdateFailHTLC: msg = &UpdateFailHTLC{} case MsgUpdateFulfillHTLC: diff --git a/lnwire/update_inbound_fee.go b/lnwire/update_inbound_fee.go new file mode 100644 index 0000000000..2f812f9a68 --- /dev/null +++ b/lnwire/update_inbound_fee.go @@ -0,0 +1,86 @@ +package lnwire + +import ( + "bytes" + "io" +) + +type UpdateInboundFee struct { + // ChanID is the particular active channel that this + // UpdateInboundDiscount is bound to. + ChanID ChannelID + + BaseFee int32 + + FeeRate int32 + + ExtraData ExtraOpaqueData +} + +// NewUpdateAddHTLC returns a new empty UpdateAddHTLC message. +func NewUpdateInboundDiscount() *UpdateInboundFee { + return &UpdateInboundFee{} +} + +// A compile time check to ensure UpdateAddHTLC implements the lnwire.Message +// interface. +var _ Message = (*UpdateInboundFee)(nil) + +// Decode deserializes a serialized UpdateAddHTLC message stored in the passed +// io.Reader observing the specified protocol version. +// +// This is part of the lnwire.Message interface. +func (c *UpdateInboundFee) Decode(r io.Reader, pver uint32) error { + var baseFee, feeRate uint32 + + err := ReadElements(r, + &c.ChanID, + &baseFee, + &feeRate, + ) + if err != nil { + return err + } + + c.BaseFee = int32(baseFee) + c.FeeRate = int32(feeRate) + + return c.ExtraData.Decode(r) +} + +// Encode serializes the target UpdateAddHTLC into the passed io.Writer +// observing the protocol version specified. +// +// This is part of the lnwire.Message interface. +func (c *UpdateInboundFee) Encode(w *bytes.Buffer, pver uint32) error { + if err := WriteChannelID(w, c.ChanID); err != nil { + return err + } + + if err := WriteUint32(w, uint32(c.BaseFee)); err != nil { + return err + } + + if err := WriteUint32(w, uint32(c.FeeRate)); err != nil { + return err + } + + // Finally, append any extra opaque data. + return WriteBytes(w, c.ExtraData) +} + +// MsgType returns the integer uniquely identifying this message type on the +// wire. +// +// This is part of the lnwire.Message interface. +func (c *UpdateInboundFee) MsgType() MessageType { + return MsgUpdateInboundFee +} + +// TargetChanID returns the channel id of the link for which this message is +// intended. +// +// NOTE: Part of peer.LinkUpdater interface. +func (c *UpdateInboundFee) TargetChanID() ChannelID { + return c.ChanID +}