mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-08-30 07:35:07 +02:00
routing+discovery: extract local channel manager
The policy update logic that resided part in the gossiper and part in the rpc server is extracted into its own object. This prepares for additional validation logic to be added for policy updates that would otherwise make the gossiper heavier. It is also a small first step towards separation of our own channel data from the rest of the graph.
This commit is contained in:
145
routing/localchans/manager.go
Normal file
145
routing/localchans/manager.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package localchans
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/discovery"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/routing"
|
||||
)
|
||||
|
||||
// Manager manages the node's local channels. The only operation that is
|
||||
// currently implemented is updating forwarding policies.
|
||||
type Manager struct {
|
||||
// UpdateForwardingPolicies is used by the manager to update active
|
||||
// links with a new policy.
|
||||
UpdateForwardingPolicies func(
|
||||
chanPolicies map[wire.OutPoint]htlcswitch.ForwardingPolicy)
|
||||
|
||||
// PropagateChanPolicyUpdate is called to persist a new policy to disk
|
||||
// and broadcast it to the network.
|
||||
PropagateChanPolicyUpdate func(
|
||||
edgesToUpdate []discovery.EdgeWithInfo) error
|
||||
|
||||
// ForAllOutgoingChannels is required to iterate over all our local
|
||||
// channels.
|
||||
ForAllOutgoingChannels func(cb func(*channeldb.ChannelEdgeInfo,
|
||||
*channeldb.ChannelEdgePolicy) error) error
|
||||
|
||||
// policyUpdateLock ensures that the database and the link do not fall
|
||||
// out of sync if there are concurrent fee update calls. Without it,
|
||||
// there is a chance that policy A updates the database, then policy B
|
||||
// updates the database, then policy B updates the link, then policy A
|
||||
// updates the link.
|
||||
policyUpdateLock sync.Mutex
|
||||
}
|
||||
|
||||
// UpdatePolicy updates the policy for the specified channels on disk and in the
|
||||
// active links.
|
||||
func (r *Manager) UpdatePolicy(newSchema routing.ChannelPolicy,
|
||||
chanPoints ...wire.OutPoint) error {
|
||||
|
||||
r.policyUpdateLock.Lock()
|
||||
defer r.policyUpdateLock.Unlock()
|
||||
|
||||
// First, we'll construct a set of all the channels that need to be
|
||||
// updated.
|
||||
chansToUpdate := make(map[wire.OutPoint]struct{})
|
||||
for _, chanPoint := range chanPoints {
|
||||
chansToUpdate[chanPoint] = struct{}{}
|
||||
}
|
||||
|
||||
haveChanFilter := len(chansToUpdate) != 0
|
||||
|
||||
var edgesToUpdate []discovery.EdgeWithInfo
|
||||
policiesToUpdate := make(map[wire.OutPoint]htlcswitch.ForwardingPolicy)
|
||||
|
||||
// Next, we'll loop over all the outgoing channels the router knows of.
|
||||
// If we have a filter then we'll only collected those channels,
|
||||
// otherwise we'll collect them all.
|
||||
err := r.ForAllOutgoingChannels(func(
|
||||
info *channeldb.ChannelEdgeInfo,
|
||||
edge *channeldb.ChannelEdgePolicy) error {
|
||||
|
||||
// If we have a channel filter, and this channel isn't a part
|
||||
// of it, then we'll skip it.
|
||||
_, ok := chansToUpdate[info.ChannelPoint]
|
||||
if !ok && haveChanFilter {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply the new policy to the edge.
|
||||
err := r.updateEdge(info.Capacity, edge, newSchema)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add updated edge to list of edges to send to gossiper.
|
||||
edgesToUpdate = append(edgesToUpdate, discovery.EdgeWithInfo{
|
||||
Info: info,
|
||||
Edge: edge,
|
||||
})
|
||||
|
||||
// Add updated policy to list of policies to send to switch.
|
||||
policiesToUpdate[info.ChannelPoint] = htlcswitch.ForwardingPolicy{
|
||||
BaseFee: edge.FeeBaseMSat,
|
||||
FeeRate: edge.FeeProportionalMillionths,
|
||||
TimeLockDelta: uint32(edge.TimeLockDelta),
|
||||
MinHTLC: edge.MinHTLC,
|
||||
MaxHTLC: edge.MaxHTLC,
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Commit the policy updates to disk and broadcast to the network. We
|
||||
// validated the new policy above, so we expect no validation errors. If
|
||||
// this would happen because of a bug, the link policy will be
|
||||
// desynchronized. It is currently not possible to atomically commit
|
||||
// multiple edge updates.
|
||||
err = r.PropagateChanPolicyUpdate(edgesToUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update active links.
|
||||
r.UpdateForwardingPolicies(policiesToUpdate)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateEdge updates the given edge with the new schema.
|
||||
func (r *Manager) updateEdge(capacity btcutil.Amount,
|
||||
edge *channeldb.ChannelEdgePolicy,
|
||||
newSchema routing.ChannelPolicy) error {
|
||||
|
||||
// Update forwarding fee scheme and required time lock delta.
|
||||
edge.FeeBaseMSat = newSchema.BaseFee
|
||||
edge.FeeProportionalMillionths = lnwire.MilliSatoshi(
|
||||
newSchema.FeeRate,
|
||||
)
|
||||
edge.TimeLockDelta = uint16(newSchema.TimeLockDelta)
|
||||
|
||||
// Max htlc is currently always set to the channel capacity.
|
||||
edge.MessageFlags |= lnwire.ChanUpdateOptionMaxHtlc
|
||||
edge.MaxHTLC = lnwire.NewMSatFromSatoshis(capacity)
|
||||
|
||||
// Validate htlc amount constraints.
|
||||
if edge.MinHTLC > edge.MaxHTLC {
|
||||
return fmt.Errorf("min_htlc %v greater than max_htlc %v",
|
||||
edge.MinHTLC, edge.MaxHTLC)
|
||||
}
|
||||
|
||||
// Clear signature to help prevent usage of the previous signature.
|
||||
edge.SetSigBytes(nil)
|
||||
|
||||
return nil
|
||||
}
|
111
routing/localchans/manager_test.go
Normal file
111
routing/localchans/manager_test.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package localchans
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcutil"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/discovery"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||
"github.com/lightningnetwork/lnd/routing"
|
||||
)
|
||||
|
||||
// TestManager tests that the local channel manager properly propagates fee
|
||||
// updates to gossiper and links.
|
||||
func TestManager(t *testing.T) {
|
||||
chanPoint := wire.OutPoint{Hash: chainhash.Hash{1}, Index: 2}
|
||||
chanCap := btcutil.Amount(1000)
|
||||
|
||||
newPolicy := routing.ChannelPolicy{
|
||||
FeeSchema: routing.FeeSchema{
|
||||
BaseFee: 100,
|
||||
FeeRate: 200,
|
||||
},
|
||||
TimeLockDelta: 80,
|
||||
}
|
||||
|
||||
updateForwardingPolicies := func(
|
||||
chanPolicies map[wire.OutPoint]htlcswitch.ForwardingPolicy) {
|
||||
|
||||
if len(chanPolicies) != 1 {
|
||||
t.Fatal("unexpected number of policies to apply")
|
||||
}
|
||||
|
||||
policy := chanPolicies[chanPoint]
|
||||
if policy.TimeLockDelta != newPolicy.TimeLockDelta {
|
||||
t.Fatal("unexpected time lock delta")
|
||||
}
|
||||
if policy.BaseFee != newPolicy.BaseFee {
|
||||
t.Fatal("unexpected base fee")
|
||||
}
|
||||
if uint32(policy.FeeRate) != newPolicy.FeeRate {
|
||||
t.Fatal("unexpected base fee")
|
||||
}
|
||||
if policy.MaxHTLC != lnwire.NewMSatFromSatoshis(chanCap) {
|
||||
t.Fatal("unexpected max htlc")
|
||||
}
|
||||
}
|
||||
|
||||
propagateChanPolicyUpdate := func(
|
||||
edgesToUpdate []discovery.EdgeWithInfo) error {
|
||||
|
||||
if len(edgesToUpdate) != 1 {
|
||||
t.Fatal("unexpected number of edges to update")
|
||||
}
|
||||
|
||||
policy := edgesToUpdate[0].Edge
|
||||
if !policy.MessageFlags.HasMaxHtlc() {
|
||||
t.Fatal("expected max htlc flag")
|
||||
}
|
||||
if policy.TimeLockDelta != uint16(newPolicy.TimeLockDelta) {
|
||||
t.Fatal("unexpected time lock delta")
|
||||
}
|
||||
if policy.FeeBaseMSat != newPolicy.BaseFee {
|
||||
t.Fatal("unexpected base fee")
|
||||
}
|
||||
if uint32(policy.FeeProportionalMillionths) != newPolicy.FeeRate {
|
||||
t.Fatal("unexpected base fee")
|
||||
}
|
||||
if policy.MaxHTLC != lnwire.NewMSatFromSatoshis(chanCap) {
|
||||
t.Fatal("unexpected max htlc")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
forAllOutgoingChannels := func(cb func(*channeldb.ChannelEdgeInfo,
|
||||
*channeldb.ChannelEdgePolicy) error) error {
|
||||
|
||||
return cb(
|
||||
&channeldb.ChannelEdgeInfo{
|
||||
Capacity: chanCap,
|
||||
ChannelPoint: chanPoint,
|
||||
},
|
||||
&channeldb.ChannelEdgePolicy{},
|
||||
)
|
||||
}
|
||||
|
||||
manager := Manager{
|
||||
UpdateForwardingPolicies: updateForwardingPolicies,
|
||||
PropagateChanPolicyUpdate: propagateChanPolicyUpdate,
|
||||
ForAllOutgoingChannels: forAllOutgoingChannels,
|
||||
}
|
||||
|
||||
// Test updating a specific channels.
|
||||
err := manager.UpdatePolicy(newPolicy, chanPoint)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test updating all channels, which comes down to the same as testing a
|
||||
// specific channel because there is only one channel.
|
||||
err = manager.UpdatePolicy(newPolicy)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user