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