diff --git a/funding/manager.go b/funding/manager.go index c55cbba32..ad66ba1c1 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -488,6 +488,11 @@ type Config struct { // transition from pending open to open. NotifyOpenChannelEvent func(wire.OutPoint) + // UpdateForwardingPolicies is used by the manager to update active + // links with a new policy. + UpdateForwardingPolicies func( + chanPolicies map[wire.OutPoint]htlcswitch.ForwardingPolicy) + // OpenChannelPredicate is a predicate on the lnwire.OpenChannel message // and on the requesting node's public key that returns a bool which // tells the funding manager whether or not to accept the channel. @@ -3159,6 +3164,28 @@ func (f *Manager) addToRouterGraph(completeChan *channeldb.OpenChannel, return ErrFundingManagerShuttingDown } + // The user can define non-default channel policies when opening a + // channel. They are stored in the database to be persisted from the + // moment of funding the channel to it being confirmed. We just + // announced those policies to the network, but we also need to update + // our local policy in the switch to make sure we can forward payments + // with the correct fees. We can't do this when creating the link + // initially as that only takes the static channel parameters. + updatedPolicy := map[wire.OutPoint]htlcswitch.ForwardingPolicy{ + completeChan.FundingOutpoint: { + MinHTLCOut: ann.chanUpdateAnn.HtlcMinimumMsat, + MaxHTLC: ann.chanUpdateAnn.HtlcMaximumMsat, + BaseFee: lnwire.MilliSatoshi( + ann.chanUpdateAnn.BaseFee, + ), + FeeRate: lnwire.MilliSatoshi( + ann.chanUpdateAnn.FeeRate, + ), + TimeLockDelta: uint32(ann.chanUpdateAnn.TimeLockDelta), + }, + } + f.cfg.UpdateForwardingPolicies(updatedPolicy) + return nil } @@ -3729,7 +3756,7 @@ func (f *Manager) newChanAnnouncement(localPubKey, chanUpdateAnn.FeeRate = uint32(storedFwdingPolicy.FeeRate) default: - log.Infof("No channel forwaring policy specified for channel "+ + log.Infof("No channel forwarding policy specified for channel "+ "announcement of ChannelID(%v). "+ "Assuming default fee parameters.", chanID) chanUpdateAnn.BaseFee = uint32( diff --git a/funding/manager_test.go b/funding/manager_test.go index d7205afd6..ec9a05c0f 100644 --- a/funding/manager_test.go +++ b/funding/manager_test.go @@ -250,6 +250,7 @@ type testNode struct { testDir string shutdownChannel chan struct{} reportScidChan chan struct{} + updatedPolicies chan map[wire.OutPoint]htlcswitch.ForwardingPolicy localFeatures []lnwire.FeatureBit remoteFeatures []lnwire.FeatureBit @@ -368,6 +369,9 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey, publTxChan := make(chan *wire.MsgTx, 1) shutdownChan := make(chan struct{}) reportScidChan := make(chan struct{}) + updatedPolicies := make( + chan map[wire.OutPoint]htlcswitch.ForwardingPolicy, 1, + ) wc := &mock.WalletController{ RootKey: alicePrivKey, @@ -524,6 +528,11 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey, return nil, nil }, AliasManager: aliasMgr, + UpdateForwardingPolicies: func( + p map[wire.OutPoint]htlcswitch.ForwardingPolicy) { + + updatedPolicies <- p + }, } for _, op := range options { @@ -548,6 +557,7 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey, testDir: tempTestDir, shutdownChannel: shutdownChan, reportScidChan: reportScidChan, + updatedPolicies: updatedPolicies, addr: addr, } @@ -627,11 +637,12 @@ func recreateAliceFundingManager(t *testing.T, alice *testNode) { UpdateLabel: func(chainhash.Hash, string) error { return nil }, - ZombieSweeperInterval: oldCfg.ZombieSweeperInterval, - ReservationTimeout: oldCfg.ReservationTimeout, - OpenChannelPredicate: chainedAcceptor, - DeleteAliasEdge: oldCfg.DeleteAliasEdge, - AliasManager: oldCfg.AliasManager, + ZombieSweeperInterval: oldCfg.ZombieSweeperInterval, + ReservationTimeout: oldCfg.ReservationTimeout, + OpenChannelPredicate: chainedAcceptor, + DeleteAliasEdge: oldCfg.DeleteAliasEdge, + AliasManager: oldCfg.AliasManager, + UpdateForwardingPolicies: oldCfg.UpdateForwardingPolicies, }) require.NoError(t, err, "failed recreating aliceFundingManager") @@ -1119,6 +1130,21 @@ func assertChannelAnnouncements(t *testing.T, alice, bob *testNode, } } + // At this point we should also have gotten a policy update that + // was sent to the switch subsystem. Make sure it contains the + // same values. + var policyUpdate htlcswitch.ForwardingPolicy + select { + case policyUpdateMap := <-node.updatedPolicies: + require.Len(t, policyUpdateMap, 1) + for _, policy := range policyUpdateMap { + policyUpdate = policy + } + + case <-time.After(time.Second * 5): + t.Fatalf("node didn't send policy update") + } + gotChannelAnnouncement := false gotChannelUpdate := false for _, msg := range announcements { @@ -1147,24 +1173,34 @@ func assertChannelAnnouncements(t *testing.T, alice, bob *testNode, minHtlc = customMinHtlc[j] } require.Equal(t, minHtlc, m.HtlcMinimumMsat) + require.Equal( + t, minHtlc, policyUpdate.MinHTLCOut, + ) // We might expect a custom MaxHltc value. if len(customMaxHtlc) > 0 { maxHtlc = customMaxHtlc[j] } require.Equal(t, maxHtlc, m.HtlcMaximumMsat) + require.Equal(t, maxHtlc, policyUpdate.MaxHTLC) // We might expect a custom baseFee value. if len(baseFees) > 0 { baseFee = baseFees[j] } require.EqualValues(t, baseFee, m.BaseFee) + require.EqualValues( + t, baseFee, policyUpdate.BaseFee, + ) // We might expect a custom feeRate value. if len(feeRates) > 0 { feeRate = feeRates[j] } require.EqualValues(t, feeRate, m.FeeRate) + require.EqualValues( + t, feeRate, policyUpdate.FeeRate, + ) gotChannelUpdate = true } diff --git a/server.go b/server.go index 2224976e5..7d76353de 100644 --- a/server.go +++ b/server.go @@ -1439,8 +1439,9 @@ func newServer(cfg *Config, listenAddrs []net.Addr, RegisteredChains: cfg.registeredChains, MaxAnchorsCommitFeeRate: chainfee.SatPerKVByte( s.cfg.MaxCommitFeeRateAnchors * 1000).FeePerKWeight(), - DeleteAliasEdge: deleteAliasEdge, - AliasManager: s.aliasMgr, + DeleteAliasEdge: deleteAliasEdge, + AliasManager: s.aliasMgr, + UpdateForwardingPolicies: s.htlcSwitch.UpdateForwardingPolicies, }) if err != nil { return nil, err