mirror of https://github.com/lightninglabs/loop
routing: add routing plugin inteface and skeleton
parent
0f002733f6
commit
04ebc8d568
@ -0,0 +1,116 @@
|
|||||||
|
package loop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
|
"github.com/lightninglabs/lndclient"
|
||||||
|
"github.com/lightningnetwork/lnd/routing/route"
|
||||||
|
"github.com/lightningnetwork/lnd/zpay32"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrRoutingPluginNotApplicable means that the selected routing plugin
|
||||||
|
// is not able to enhance routing given the current conditions and
|
||||||
|
// therefore shouldn't be used.
|
||||||
|
ErrRoutingPluginNotApplicable = fmt.Errorf("routing plugin not " +
|
||||||
|
"applicable")
|
||||||
|
|
||||||
|
// ErrRoutingPluginNoMoreRetries means that the routing plugin can't
|
||||||
|
// effectively help the payment with more retries.
|
||||||
|
ErrRoutingPluginNoMoreRetries = fmt.Errorf("routing plugin can't " +
|
||||||
|
"retry more")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
routingPluginMx sync.Mutex
|
||||||
|
routingPluginInstance RoutingPlugin
|
||||||
|
)
|
||||||
|
|
||||||
|
// RoutingPlugin is a generic interface for off-chain payment helpers.
|
||||||
|
type RoutingPlugin interface {
|
||||||
|
// Init initializes the routing plugin.
|
||||||
|
Init(ctx context.Context, target route.Vertex,
|
||||||
|
routeHints [][]zpay32.HopHint, amt btcutil.Amount) error
|
||||||
|
|
||||||
|
// Done deinitializes the routing plugin (restoring any state the
|
||||||
|
// plugin might have changed).
|
||||||
|
Done(ctx context.Context) error
|
||||||
|
|
||||||
|
// BeforePayment is called before each payment. Attempt counter is
|
||||||
|
// passed, counting attempts from 1.
|
||||||
|
BeforePayment(ctx context.Context, attempt int, maxAttempts int) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeRoutingPlugin is a helper to instantiate routing plugins.
|
||||||
|
func makeRoutingPlugin(plugin RoutingPluginType,
|
||||||
|
_ lndclient.LndServices) RoutingPlugin {
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcquireRoutingPlugin will return a RoutingPlugin instance (or nil). As the
|
||||||
|
// LND instance used is a shared resource, currently only one requestor will be
|
||||||
|
// able to acquire a RoutingPlugin instance. If someone is already holding the
|
||||||
|
// instance a nil is returned.
|
||||||
|
func AcquireRoutingPlugin(ctx context.Context, pluginType RoutingPluginType,
|
||||||
|
lnd lndclient.LndServices, target route.Vertex,
|
||||||
|
routeHints [][]zpay32.HopHint, amt btcutil.Amount) (
|
||||||
|
RoutingPlugin, error) {
|
||||||
|
|
||||||
|
routingPluginMx.Lock()
|
||||||
|
defer routingPluginMx.Unlock()
|
||||||
|
|
||||||
|
// Another swap is already using the routing plugin.
|
||||||
|
if routingPluginInstance != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
routingPluginInstance = makeRoutingPlugin(pluginType, lnd)
|
||||||
|
if routingPluginInstance == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the plugin with the passed parameters.
|
||||||
|
err := routingPluginInstance.Init(ctx, target, routeHints, amt)
|
||||||
|
if err != nil {
|
||||||
|
if err == ErrRoutingPluginNotApplicable {
|
||||||
|
// Since the routing plugin is not applicable for this
|
||||||
|
// payment, we can immediately destruct it.
|
||||||
|
if err := routingPluginInstance.Done(ctx); err != nil {
|
||||||
|
log.Errorf("Error while releasing routing "+
|
||||||
|
"plugin: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrRoutingPluginNotApplicable is non critical, so
|
||||||
|
// we're masking this error as we can continue the swap
|
||||||
|
// flow without the routing plugin.
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
routingPluginInstance = nil
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return routingPluginInstance, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReleaseRoutingPlugin will release the RoutingPlugin, allowing other
|
||||||
|
// requestors to acquire the instance.
|
||||||
|
func ReleaseRoutingPlugin(ctx context.Context) {
|
||||||
|
routingPluginMx.Lock()
|
||||||
|
defer routingPluginMx.Unlock()
|
||||||
|
|
||||||
|
if routingPluginInstance == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := routingPluginInstance.Done(ctx); err != nil {
|
||||||
|
log.Errorf("Error while releasing routing plugin: %v",
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
routingPluginInstance = nil
|
||||||
|
}
|
@ -0,0 +1,119 @@
|
|||||||
|
package loop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec"
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
|
"github.com/lightninglabs/lndclient"
|
||||||
|
"github.com/lightninglabs/loop/test"
|
||||||
|
"github.com/lightningnetwork/lnd/routing/route"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
alice = route.Vertex{1}
|
||||||
|
bob = route.Vertex{2}
|
||||||
|
charlie = route.Vertex{3}
|
||||||
|
dave = route.Vertex{4}
|
||||||
|
eugene = route.Vertex{5}
|
||||||
|
loopNode = route.Vertex{99}
|
||||||
|
|
||||||
|
privFrank, _ = btcec.NewPrivateKey(btcec.S256())
|
||||||
|
frankPubKey = privFrank.PubKey()
|
||||||
|
frank = route.NewVertex(frankPubKey)
|
||||||
|
|
||||||
|
privGeorge, _ = btcec.NewPrivateKey(btcec.S256())
|
||||||
|
georgePubKey = privGeorge.PubKey()
|
||||||
|
george = route.NewVertex(georgePubKey)
|
||||||
|
)
|
||||||
|
|
||||||
|
// testChan holds simplified test data for channels.
|
||||||
|
type testChan struct {
|
||||||
|
nodeID1 route.Vertex
|
||||||
|
nodeID2 route.Vertex
|
||||||
|
chanID uint64
|
||||||
|
capacity int64
|
||||||
|
feeBase1 int64
|
||||||
|
feeRate1 int64
|
||||||
|
feeBase2 int64
|
||||||
|
feeRate2 int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeTestNetwork is a helper creating mocked network data from test inputs.
|
||||||
|
func makeTestNetwork(channels []testChan) ([]lndclient.ChannelInfo,
|
||||||
|
map[uint64]*lndclient.ChannelEdge) {
|
||||||
|
|
||||||
|
chanInfos := make([]lndclient.ChannelInfo, len(channels))
|
||||||
|
edges := make(map[uint64]*lndclient.ChannelEdge, len(channels))
|
||||||
|
for i, ch := range channels {
|
||||||
|
chanInfos[i] = lndclient.ChannelInfo{
|
||||||
|
ChannelID: ch.chanID,
|
||||||
|
}
|
||||||
|
|
||||||
|
edges[ch.chanID] = &lndclient.ChannelEdge{
|
||||||
|
ChannelID: ch.chanID,
|
||||||
|
Capacity: btcutil.Amount(ch.capacity),
|
||||||
|
Node1: ch.nodeID1,
|
||||||
|
Node2: ch.nodeID2,
|
||||||
|
Node1Policy: &lndclient.RoutingPolicy{
|
||||||
|
FeeBaseMsat: ch.feeBase1,
|
||||||
|
FeeRateMilliMsat: ch.feeRate1,
|
||||||
|
},
|
||||||
|
Node2Policy: &lndclient.RoutingPolicy{
|
||||||
|
FeeBaseMsat: ch.feeBase2,
|
||||||
|
FeeRateMilliMsat: ch.feeRate2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chanInfos, edges
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoutingPluginAcquireRelease(t *testing.T) {
|
||||||
|
mockLnd := test.NewMockLnd()
|
||||||
|
|
||||||
|
// _____Bob_____
|
||||||
|
// / \
|
||||||
|
// Alice Dave---Loop
|
||||||
|
// \___ ___/
|
||||||
|
// Charlie
|
||||||
|
//
|
||||||
|
channels := []testChan{
|
||||||
|
{alice, bob, 1, 1000, 1000, 1, 1000, 1},
|
||||||
|
{alice, charlie, 2, 1000, 1000, 1, 1000, 1},
|
||||||
|
{bob, dave, 3, 1000, 1000, 1, 1000, 1},
|
||||||
|
{charlie, dave, 4, 1000, 1000, 100, 1000, 1},
|
||||||
|
{dave, loopNode, 5, 1000, 1000, 1, 1000, 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
mockLnd.Channels, mockLnd.ChannelEdges = makeTestNetwork(channels)
|
||||||
|
lnd := lndclient.LndServices{
|
||||||
|
Client: mockLnd.Client,
|
||||||
|
Router: mockLnd.Router,
|
||||||
|
}
|
||||||
|
|
||||||
|
target := loopNode
|
||||||
|
amt := btcutil.Amount(50)
|
||||||
|
ctx := context.TODO()
|
||||||
|
|
||||||
|
// RoutingPluginNone returns nil.
|
||||||
|
plugin, err := AcquireRoutingPlugin(
|
||||||
|
ctx, RoutingPluginNone, lnd, target, nil, amt,
|
||||||
|
)
|
||||||
|
require.Nil(t, plugin)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Attempting to acquire RoutingPluginNone again still returns nil.
|
||||||
|
plugin, err = AcquireRoutingPlugin(
|
||||||
|
ctx, RoutingPluginNone, lnd, target, nil, amt,
|
||||||
|
)
|
||||||
|
require.Nil(t, plugin)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Call ReleaseRoutingPlugin twice to ensure we can call it even when no
|
||||||
|
// plugin is acquired.
|
||||||
|
ReleaseRoutingPlugin(ctx)
|
||||||
|
ReleaseRoutingPlugin(ctx)
|
||||||
|
}
|
Loading…
Reference in New Issue