Merge branch 'master' of github.com:lightningnetwork/lnd

This commit is contained in:
kiwiidb 2019-12-20 15:00:20 +01:00
commit af1ff291a7
44 changed files with 1156 additions and 1050 deletions

View File

@ -13,6 +13,7 @@ import (
"github.com/coreos/bbolt"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
var (
@ -145,7 +146,14 @@ func (d *databaseChannelGraph) addRandChannel(node1, node2 *btcec.PublicKey,
fetchNode := func(pub *btcec.PublicKey) (*channeldb.LightningNode, error) {
if pub != nil {
dbNode, err := d.db.FetchLightningNode(pub)
vertex, err := route.NewVertexFromBytes(
pub.SerializeCompressed(),
)
if err != nil {
return nil, err
}
dbNode, err := d.db.FetchLightningNode(nil, vertex)
switch {
case err == channeldb.ErrGraphNodeNotFound:
fallthrough
@ -307,7 +315,7 @@ func (d *databaseChannelGraph) addRandNode() (*btcec.PublicKey, error) {
// memChannelGraph is an implementation of the autopilot.ChannelGraph backed by
// an in-memory graph.
type memChannelGraph struct {
graph map[NodeID]memNode
graph map[NodeID]*memNode
}
// A compile time assertion to ensure memChannelGraph meets the
@ -318,7 +326,7 @@ var _ ChannelGraph = (*memChannelGraph)(nil)
// implementation.
func newMemChannelGraph() *memChannelGraph {
return &memChannelGraph{
graph: make(map[NodeID]memNode),
graph: make(map[NodeID]*memNode),
}
}
@ -360,14 +368,14 @@ func (m *memChannelGraph) addRandChannel(node1, node2 *btcec.PublicKey,
capacity btcutil.Amount) (*ChannelEdge, *ChannelEdge, error) {
var (
vertex1, vertex2 memNode
vertex1, vertex2 *memNode
ok bool
)
if node1 != nil {
vertex1, ok = m.graph[NewNodeID(node1)]
if !ok {
vertex1 = memNode{
vertex1 = &memNode{
pub: node1,
addrs: []net.Addr{
&net.TCPAddr{
@ -381,7 +389,7 @@ func (m *memChannelGraph) addRandChannel(node1, node2 *btcec.PublicKey,
if err != nil {
return nil, nil, err
}
vertex1 = memNode{
vertex1 = &memNode{
pub: newPub,
addrs: []net.Addr{
&net.TCPAddr{
@ -394,7 +402,7 @@ func (m *memChannelGraph) addRandChannel(node1, node2 *btcec.PublicKey,
if node2 != nil {
vertex2, ok = m.graph[NewNodeID(node2)]
if !ok {
vertex2 = memNode{
vertex2 = &memNode{
pub: node2,
addrs: []net.Addr{
&net.TCPAddr{
@ -408,7 +416,7 @@ func (m *memChannelGraph) addRandChannel(node1, node2 *btcec.PublicKey,
if err != nil {
return nil, nil, err
}
vertex2 = memNode{
vertex2 = &memNode{
pub: newPub,
addrs: []net.Addr{
&net.TCPAddr{
@ -446,7 +454,7 @@ func (m *memChannelGraph) addRandNode() (*btcec.PublicKey, error) {
if err != nil {
return nil, err
}
vertex := memNode{
vertex := &memNode{
pub: newPub,
addrs: []net.Addr{
&net.TCPAddr{

View File

@ -20,6 +20,7 @@ import (
"github.com/btcsuite/btcutil"
"github.com/coreos/bbolt"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
var (
@ -513,7 +514,7 @@ func (c *ChannelGraph) LookupAlias(pub *btcec.PublicKey) (string, error) {
// DeleteLightningNode starts a new database transaction to remove a vertex/node
// from the database according to the node's public key.
func (c *ChannelGraph) DeleteLightningNode(nodePub *btcec.PublicKey) error {
func (c *ChannelGraph) DeleteLightningNode(nodePub route.Vertex) error {
// TODO(roasbeef): ensure dangling edges are removed...
return c.db.Update(func(tx *bbolt.Tx) error {
nodes := tx.Bucket(nodeBucket)
@ -521,9 +522,7 @@ func (c *ChannelGraph) DeleteLightningNode(nodePub *btcec.PublicKey) error {
return ErrGraphNodeNotFound
}
return c.deleteLightningNode(
nodes, nodePub.SerializeCompressed(),
)
return c.deleteLightningNode(nodes, nodePub[:])
})
}
@ -2180,10 +2179,17 @@ func (l *LightningNode) isPublic(tx *bbolt.Tx, sourcePubKey []byte) (bool, error
// FetchLightningNode attempts to look up a target node by its identity public
// key. If the node isn't found in the database, then ErrGraphNodeNotFound is
// returned.
func (c *ChannelGraph) FetchLightningNode(pub *btcec.PublicKey) (*LightningNode, error) {
//
// If the caller wishes to re-use an existing boltdb transaction, then it
// should be passed as the first argument. Otherwise the first argument should
// be nil and a fresh transaction will be created to execute the graph
// traversal.
func (c *ChannelGraph) FetchLightningNode(tx *bbolt.Tx, nodePub route.Vertex) (
*LightningNode, error) {
var node *LightningNode
nodePub := pub.SerializeCompressed()
err := c.db.View(func(tx *bbolt.Tx) error {
fetchNode := func(tx *bbolt.Tx) error {
// First grab the nodes bucket which stores the mapping from
// pubKey to node information.
nodes := tx.Bucket(nodeBucket)
@ -2193,7 +2199,7 @@ func (c *ChannelGraph) FetchLightningNode(pub *btcec.PublicKey) (*LightningNode,
// If a key for this serialized public key isn't found, then
// the target node doesn't exist within the database.
nodeBytes := nodes.Get(nodePub)
nodeBytes := nodes.Get(nodePub[:])
if nodeBytes == nil {
return ErrGraphNodeNotFound
}
@ -2210,7 +2216,14 @@ func (c *ChannelGraph) FetchLightningNode(pub *btcec.PublicKey) (*LightningNode,
node = &n
return nil
})
}
var err error
if tx == nil {
err = c.db.View(fetchNode)
} else {
err = fetchNode(tx)
}
if err != nil {
return nil, err
}

View File

@ -20,6 +20,7 @@ import (
"github.com/coreos/bbolt"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
var (
@ -37,6 +38,8 @@ var (
_, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10)
testFeatures = lnwire.NewFeatureVector(nil, lnwire.Features)
testPub = route.Vertex{2, 202, 4}
)
func createLightningNode(db *DB, priv *btcec.PrivateKey) (*LightningNode, error) {
@ -80,7 +83,6 @@ func TestNodeInsertionAndDeletion(t *testing.T) {
// We'd like to test basic insertion/deletion for vertexes from the
// graph, so we'll create a test vertex to start with.
_, testPub := btcec.PrivKeyFromBytes(btcec.S256(), key[:])
node := &LightningNode{
HaveNodeAnnouncement: true,
AuthSigBytes: testSig.Serialize(),
@ -90,9 +92,9 @@ func TestNodeInsertionAndDeletion(t *testing.T) {
Features: testFeatures,
Addresses: testAddrs,
ExtraOpaqueData: []byte("extra new data"),
PubKeyBytes: testPub,
db: db,
}
copy(node.PubKeyBytes[:], testPub.SerializeCompressed())
// First, insert the node into the graph DB. This should succeed
// without any errors.
@ -102,7 +104,7 @@ func TestNodeInsertionAndDeletion(t *testing.T) {
// Next, fetch the node from the database to ensure everything was
// serialized properly.
dbNode, err := graph.FetchLightningNode(testPub)
dbNode, err := graph.FetchLightningNode(nil, testPub)
if err != nil {
t.Fatalf("unable to locate node: %v", err)
}
@ -126,7 +128,7 @@ func TestNodeInsertionAndDeletion(t *testing.T) {
// Finally, attempt to fetch the node again. This should fail as the
// node should have been deleted from the database.
_, err = graph.FetchLightningNode(testPub)
_, err = graph.FetchLightningNode(nil, testPub)
if err != ErrGraphNodeNotFound {
t.Fatalf("fetch after delete should fail!")
}
@ -147,11 +149,10 @@ func TestPartialNode(t *testing.T) {
// We want to be able to insert nodes into the graph that only has the
// PubKey set.
_, testPub := btcec.PrivKeyFromBytes(btcec.S256(), key[:])
node := &LightningNode{
HaveNodeAnnouncement: false,
PubKeyBytes: testPub,
}
copy(node.PubKeyBytes[:], testPub.SerializeCompressed())
if err := graph.AddLightningNode(node); err != nil {
t.Fatalf("unable to add node: %v", err)
@ -159,7 +160,7 @@ func TestPartialNode(t *testing.T) {
// Next, fetch the node from the database to ensure everything was
// serialized properly.
dbNode, err := graph.FetchLightningNode(testPub)
dbNode, err := graph.FetchLightningNode(nil, testPub)
if err != nil {
t.Fatalf("unable to locate node: %v", err)
}
@ -175,9 +176,9 @@ func TestPartialNode(t *testing.T) {
node = &LightningNode{
HaveNodeAnnouncement: false,
LastUpdate: time.Unix(0, 0),
PubKeyBytes: testPub,
db: db,
}
copy(node.PubKeyBytes[:], testPub.SerializeCompressed())
if err := compareNodes(node, dbNode); err != nil {
t.Fatalf("nodes don't match: %v", err)
@ -191,7 +192,7 @@ func TestPartialNode(t *testing.T) {
// Finally, attempt to fetch the node again. This should fail as the
// node should have been deleted from the database.
_, err = graph.FetchLightningNode(testPub)
_, err = graph.FetchLightningNode(nil, testPub)
if err != ErrGraphNodeNotFound {
t.Fatalf("fetch after delete should fail!")
}
@ -2386,11 +2387,8 @@ func TestPruneGraphNodes(t *testing.T) {
// Finally, we'll ensure that node3, the only fully unconnected node as
// properly deleted from the graph and not another node in its place.
node3Pub, err := node3.PubKey()
if err != nil {
t.Fatalf("unable to fetch the pubkey of node3: %v", err)
}
if _, err := graph.FetchLightningNode(node3Pub); err == nil {
_, err = graph.FetchLightningNode(nil, node3.PubKeyBytes)
if err == nil {
t.Fatalf("node 3 should have been deleted!")
}
}
@ -2430,18 +2428,9 @@ func TestAddChannelEdgeShellNodes(t *testing.T) {
t.Fatalf("unable to add edge: %v", err)
}
node1Pub, err := node1.PubKey()
if err != nil {
t.Fatalf("unable to parse node 1 pub: %v", err)
}
node2Pub, err := node2.PubKey()
if err != nil {
t.Fatalf("unable to parse node 2 pub: %v", err)
}
// Ensure that node1 was inserted as a full node, while node2 only has
// a shell node present.
node1, err = graph.FetchLightningNode(node1Pub)
node1, err = graph.FetchLightningNode(nil, node1.PubKeyBytes)
if err != nil {
t.Fatalf("unable to fetch node1: %v", err)
}
@ -2449,7 +2438,7 @@ func TestAddChannelEdgeShellNodes(t *testing.T) {
t.Fatalf("have shell announcement for node1, shouldn't")
}
node2, err = graph.FetchLightningNode(node2Pub)
node2, err = graph.FetchLightningNode(nil, node2.PubKeyBytes)
if err != nil {
t.Fatalf("unable to fetch node2: %v", err)
}
@ -2504,8 +2493,7 @@ func TestNodePruningUpdateIndexDeletion(t *testing.T) {
// We'll now delete the node from the graph, this should result in it
// being removed from the update index as well.
nodePub, _ := node1.PubKey()
if err := graph.DeleteLightningNode(nodePub); err != nil {
if err := graph.DeleteLightningNode(node1.PubKeyBytes); err != nil {
t.Fatalf("unable to delete node: %v", err)
}

View File

@ -136,7 +136,7 @@ func getClientConn(ctx *cli.Context, skipMacaroons bool) *grpc.ClientConn {
// We need to use a custom dialer so we can also connect to unix sockets
// and not just TCP addresses.
genericDialer := lncfg.ClientAddressDialer(defaultRPCPort)
opts = append(opts, grpc.WithDialer(genericDialer))
opts = append(opts, grpc.WithContextDialer(genericDialer))
opts = append(opts, grpc.WithDefaultCallOptions(maxMsgRecvSize))
conn, err := grpc.Dial(ctx.GlobalString("rpcserver"), opts...)

View File

@ -167,10 +167,10 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
preimageSubscription := h.PreimageDB.SubscribeUpdates()
defer preimageSubscription.CancelSubscription()
// Define closure to process hodl events either direct or triggered by
// later notifcation.
processHodlEvent := func(e invoices.HodlEvent) (ContractResolver,
error) {
// Define closure to process htlc resolutions either direct or triggered by
// later notification.
processHtlcResolution := func(e invoices.HtlcResolution) (
ContractResolver, error) {
if e.Preimage == nil {
log.Infof("%T(%v): Exit hop HTLC canceled "+
@ -201,23 +201,26 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
HtlcID: h.htlc.HtlcIndex,
}
event, err := h.Registry.NotifyExitHopHtlc(
resolution, err := h.Registry.NotifyExitHopHtlc(
h.htlc.RHash, h.htlc.Amt, h.htlcExpiry, currentHeight,
circuitKey, hodlChan, payload,
)
switch err {
case channeldb.ErrInvoiceNotFound:
case nil:
defer h.Registry.HodlUnsubscribeAll(hodlChan)
// Resolve the htlc directly if possible.
if event != nil {
return processHodlEvent(*event)
}
default:
if err != nil {
return nil, err
}
defer h.Registry.HodlUnsubscribeAll(hodlChan)
// If the resolution is non-nil (indicating that a settle or cancel has
// occurred), and the invoice is known to the registry (indicating that
// the htlc is paying one of our invoices and is not a forward), try to
// resolve it directly.
if resolution != nil &&
resolution.Outcome != invoices.ResultInvoiceNotFound {
return processHtlcResolution(*resolution)
}
// With the epochs and preimage subscriptions initialized, we'll query
// to see if we already know the preimage.
preimage, ok := h.PreimageDB.LookupPreimage(h.htlc.RHash)
@ -252,9 +255,9 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
return &h.htlcSuccessResolver, nil
case hodlItem := <-hodlChan:
hodlEvent := hodlItem.(invoices.HodlEvent)
htlcResolution := hodlItem.(invoices.HtlcResolution)
return processHodlEvent(hodlEvent)
return processHtlcResolution(htlcResolution)
case newBlock, ok := <-blockEpochs.Epochs:
if !ok {

View File

@ -21,10 +21,11 @@ const (
)
var (
testResPreimage = lntypes.Preimage{1, 2, 3}
testResHash = testResPreimage.Hash()
testResCircuitKey = channeldb.CircuitKey{}
testOnionBlob = []byte{4, 5, 6}
testResPreimage = lntypes.Preimage{1, 2, 3}
testResHash = testResPreimage.Hash()
testResCircuitKey = channeldb.CircuitKey{}
testOnionBlob = []byte{4, 5, 6}
testAcceptHeight int32 = 1234
)
// TestHtlcIncomingResolverFwdPreimageKnown tests resolution of a forwarded htlc
@ -34,7 +35,10 @@ func TestHtlcIncomingResolverFwdPreimageKnown(t *testing.T) {
defer timeout(t)()
ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyErr = channeldb.ErrInvoiceNotFound
ctx.registry.notifyResolution = invoices.NewFailureResolution(
testResCircuitKey, testHtlcExpiry,
invoices.ResultInvoiceNotFound,
)
ctx.witnessBeacon.lookupPreimage[testResHash] = testResPreimage
ctx.resolve()
ctx.waitForResult(true)
@ -48,7 +52,10 @@ func TestHtlcIncomingResolverFwdContestedSuccess(t *testing.T) {
defer timeout(t)()
ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyErr = channeldb.ErrInvoiceNotFound
ctx.registry.notifyResolution = invoices.NewFailureResolution(
testResCircuitKey, testHtlcExpiry,
invoices.ResultInvoiceNotFound,
)
ctx.resolve()
// Simulate a new block coming in. HTLC is not yet expired.
@ -65,7 +72,10 @@ func TestHtlcIncomingResolverFwdContestedTimeout(t *testing.T) {
defer timeout(t)()
ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyErr = channeldb.ErrInvoiceNotFound
ctx.registry.notifyResolution = invoices.NewFailureResolution(
testResCircuitKey, testHtlcExpiry,
invoices.ResultInvoiceNotFound,
)
ctx.resolve()
// Simulate a new block coming in. HTLC expires.
@ -81,8 +91,10 @@ func TestHtlcIncomingResolverFwdTimeout(t *testing.T) {
defer timeout(t)()
ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyErr = channeldb.ErrInvoiceNotFound
ctx.registry.notifyResolution = invoices.NewFailureResolution(
testResCircuitKey, testHtlcExpiry,
invoices.ResultInvoiceNotFound,
)
ctx.witnessBeacon.lookupPreimage[testResHash] = testResPreimage
ctx.resolver.htlcExpiry = 90
ctx.resolve()
@ -96,10 +108,11 @@ func TestHtlcIncomingResolverExitSettle(t *testing.T) {
defer timeout(t)()
ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyEvent = &invoices.HodlEvent{
CircuitKey: testResCircuitKey,
Preimage: &testResPreimage,
}
ctx.registry.notifyResolution = invoices.NewSettleResolution(
testResPreimage, testResCircuitKey, testAcceptHeight,
invoices.ResultReplayToSettled,
)
ctx.resolve()
data := <-ctx.registry.notifyChan
@ -126,9 +139,11 @@ func TestHtlcIncomingResolverExitCancel(t *testing.T) {
defer timeout(t)()
ctx := newIncomingResolverTestContext(t)
ctx.registry.notifyEvent = &invoices.HodlEvent{
CircuitKey: testResCircuitKey,
}
ctx.registry.notifyResolution = invoices.NewFailureResolution(
testResCircuitKey, testAcceptHeight,
invoices.ResultInvoiceAlreadyCanceled,
)
ctx.resolve()
ctx.waitForResult(false)
}
@ -143,10 +158,10 @@ func TestHtlcIncomingResolverExitSettleHodl(t *testing.T) {
ctx.resolve()
notifyData := <-ctx.registry.notifyChan
notifyData.hodlChan <- invoices.HodlEvent{
CircuitKey: testResCircuitKey,
Preimage: &testResPreimage,
}
notifyData.hodlChan <- *invoices.NewSettleResolution(
testResPreimage, testResCircuitKey, testAcceptHeight,
invoices.ResultSettled,
)
ctx.waitForResult(true)
}
@ -172,9 +187,10 @@ func TestHtlcIncomingResolverExitCancelHodl(t *testing.T) {
ctx := newIncomingResolverTestContext(t)
ctx.resolve()
notifyData := <-ctx.registry.notifyChan
notifyData.hodlChan <- invoices.HodlEvent{
CircuitKey: testResCircuitKey,
}
notifyData.hodlChan <- *invoices.NewFailureResolution(
testResCircuitKey, testAcceptHeight, invoices.ResultCanceled,
)
ctx.waitForResult(false)
}

View File

@ -27,9 +27,9 @@ type Registry interface {
NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi,
expiry uint32, currentHeight int32,
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
payload invoices.Payload) (*invoices.HodlEvent, error)
payload invoices.Payload) (*invoices.HtlcResolution, error)
// HodlUnsubscribeAll unsubscribes from all hodl events.
// HodlUnsubscribeAll unsubscribes from all htlc resolutions.
HodlUnsubscribeAll(subscriber chan<- interface{})
}

View File

@ -16,15 +16,15 @@ type notifyExitHopData struct {
}
type mockRegistry struct {
notifyChan chan notifyExitHopData
notifyErr error
notifyEvent *invoices.HodlEvent
notifyChan chan notifyExitHopData
notifyErr error
notifyResolution *invoices.HtlcResolution
}
func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash,
paidAmount lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
payload invoices.Payload) (*invoices.HodlEvent, error) {
payload invoices.Payload) (*invoices.HtlcResolution, error) {
r.notifyChan <- notifyExitHopData{
hodlChan: hodlChan,
@ -34,7 +34,7 @@ func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash,
currentHeight: currentHeight,
}
return r.notifyEvent, r.notifyErr
return r.notifyResolution, r.notifyErr
}
func (r *mockRegistry) HodlUnsubscribeAll(subscriber chan<- interface{}) {}

View File

@ -27,7 +27,7 @@ type InvoiceDatabase interface {
NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi,
expiry uint32, currentHeight int32,
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
payload invoices.Payload) (*invoices.HodlEvent, error)
payload invoices.Payload) (*invoices.HtlcResolution, error)
// CancelInvoice attempts to cancel the invoice corresponding to the
// passed payment hash.
@ -36,7 +36,7 @@ type InvoiceDatabase interface {
// SettleHodlInvoice settles a hold invoice.
SettleHodlInvoice(preimage lntypes.Preimage) error
// HodlUnsubscribeAll unsubscribes from all hodl events.
// HodlUnsubscribeAll unsubscribes from all htlc resolutions.
HodlUnsubscribeAll(subscriber chan<- interface{})
}

View File

@ -489,8 +489,8 @@ func (l *channelLink) Stop() {
l.log.Info("stopping")
// As the link is stopping, we are no longer interested in hodl events
// coming from the invoice registry.
// As the link is stopping, we are no longer interested in htlc
// resolutions coming from the invoice registry.
l.cfg.Registry.HodlUnsubscribeAll(l.hodlQueue.ChanIn())
if l.cfg.ChainEvents.Cancel != nil {
@ -1126,11 +1126,11 @@ out:
case msg := <-l.upstream:
l.handleUpstreamMsg(msg)
// A hodl event is received. This means that we now have a
// A htlc resolution is received. This means that we now have a
// resolution for a previously accepted htlc.
case hodlItem := <-l.hodlQueue.ChanOut():
hodlEvent := hodlItem.(invoices.HodlEvent)
err := l.processHodlQueue(hodlEvent)
htlcResolution := hodlItem.(invoices.HtlcResolution)
err := l.processHodlQueue(htlcResolution)
if err != nil {
l.fail(LinkFailureError{code: ErrInternalError},
fmt.Sprintf("process hodl queue: %v",
@ -1145,24 +1145,26 @@ out:
}
}
// processHodlQueue processes a received hodl event and continues reading from
// the hodl queue until no more events remain. When this function returns
// without an error, the commit tx should be updated.
func (l *channelLink) processHodlQueue(firstHodlEvent invoices.HodlEvent) error {
// processHodlQueue processes a received htlc resolution and continues reading
// from the hodl queue until no more resolutions remain. When this function
// returns without an error, the commit tx should be updated.
func (l *channelLink) processHodlQueue(
firstResolution invoices.HtlcResolution) error {
// Try to read all waiting resolution messages, so that they can all be
// processed in a single commitment tx update.
hodlEvent := firstHodlEvent
htlcResolution := firstResolution
loop:
for {
// Lookup all hodl htlcs that can be failed or settled with this event.
// The hodl htlc must be present in the map.
circuitKey := hodlEvent.CircuitKey
circuitKey := htlcResolution.CircuitKey
hodlHtlc, ok := l.hodlMap[circuitKey]
if !ok {
return fmt.Errorf("hodl htlc not found: %v", circuitKey)
}
if err := l.processHodlEvent(hodlEvent, hodlHtlc); err != nil {
if err := l.processHtlcResolution(htlcResolution, hodlHtlc); err != nil {
return err
}
@ -1171,7 +1173,7 @@ loop:
select {
case item := <-l.hodlQueue.ChanOut():
hodlEvent = item.(invoices.HodlEvent)
htlcResolution = item.(invoices.HtlcResolution)
default:
break loop
}
@ -1185,30 +1187,30 @@ loop:
return nil
}
// processHodlEvent applies a received hodl event to the provided htlc. When
// this function returns without an error, the commit tx should be updated.
func (l *channelLink) processHodlEvent(hodlEvent invoices.HodlEvent,
// processHtlcResolution applies a received htlc resolution to the provided
// htlc. When this function returns without an error, the commit tx should be
// updated.
func (l *channelLink) processHtlcResolution(resolution invoices.HtlcResolution,
htlc hodlHtlc) error {
circuitKey := hodlEvent.CircuitKey
circuitKey := resolution.CircuitKey
// Determine required action for the resolution.
if hodlEvent.Preimage != nil {
l.log.Debugf("received hodl settle event for %v", circuitKey)
// Determine required action for the resolution. If the event's preimage is
// non-nil, the htlc must be settled. Otherwise, it should be canceled.
if resolution.Preimage != nil {
l.log.Debugf("received settle resolution for %v", circuitKey)
return l.settleHTLC(
*hodlEvent.Preimage, htlc.pd.HtlcIndex,
*resolution.Preimage, htlc.pd.HtlcIndex,
htlc.pd.SourceRef,
)
}
l.log.Debugf("received hodl cancel event for %v", circuitKey)
l.log.Debugf("received cancel resolution for %v with outcome: %v",
circuitKey, resolution.Outcome)
// In case of a cancel, always return
// incorrect_or_unknown_payment_details in order to avoid leaking info.
failure := lnwire.NewFailIncorrectDetails(
htlc.pd.Amount, uint32(hodlEvent.AcceptHeight),
)
// Get the lnwire failure message based on the resolution result.
failure := getResolutionFailure(resolution, htlc.pd.Amount)
l.sendHTLCError(
htlc.pd.HtlcIndex, failure, htlc.obfuscator,
@ -1217,6 +1219,25 @@ func (l *channelLink) processHodlEvent(hodlEvent invoices.HodlEvent,
return nil
}
// getResolutionFailure returns the wire message that a htlc resolution should
// be failed with.
func getResolutionFailure(resolution invoices.HtlcResolution,
amount lnwire.MilliSatoshi) lnwire.FailureMessage {
// If the resolution has been resolved as part of a MPP timeout, we need
// to fail the htlc with lnwire.FailMppTimeout.
if resolution.Outcome == invoices.ResultMppTimeout {
return &lnwire.FailMPPTimeout{}
}
// If the htlc is not a MPP timeout, we fail it with FailIncorrectDetails
// This covers hodl cancels (which return it to avoid leaking information
// and other invoice failures such as underpayment or expiry too soon.
return lnwire.NewFailIncorrectDetails(
amount, uint32(resolution.AcceptHeight),
)
}
// randomFeeUpdateTimeout returns a random timeout between the bounds defined
// within the link's configuration that will be used to determine when the link
// should propose an update to its commitment fee rate.
@ -2817,21 +2838,7 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor,
invoiceHash, pd.Amount, pd.Timeout, int32(heightNow),
circuitKey, l.hodlQueue.ChanIn(), payload,
)
switch err {
// Cancel htlc if we don't have an invoice for it.
case channeldb.ErrInvoiceNotFound:
failure := lnwire.NewFailIncorrectDetails(pd.Amount, heightNow)
l.sendHTLCError(pd.HtlcIndex, failure, obfuscator, pd.SourceRef)
return nil
// No error.
case nil:
// Pass error to caller.
default:
if err != nil {
return err
}
@ -2841,15 +2848,15 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor,
obfuscator: obfuscator,
}
// If the event is nil, the invoice is being held, so we save payment
// descriptor for future reference.
if event == nil {
// Save payment descriptor for future reference.
l.hodlMap[circuitKey] = htlc
return nil
}
// Process the received resolution.
return l.processHodlEvent(*event, htlc)
return l.processHtlcResolution(*event, htlc)
}
// settleHTLC settles the HTLC on the channel.

View File

@ -819,7 +819,7 @@ func (i *mockInvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error
func (i *mockInvoiceRegistry) NotifyExitHopHtlc(rhash lntypes.Hash,
amt lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
payload invoices.Payload) (*invoices.HodlEvent, error) {
payload invoices.Payload) (*invoices.HtlcResolution, error) {
event, err := i.registry.NotifyExitHopHtlc(
rhash, amt, expiry, currentHeight, circuitKey, hodlChan,

View File

@ -35,10 +35,10 @@ const (
DefaultHtlcHoldDuration = 120 * time.Second
)
// HodlEvent describes how an htlc should be resolved. If HodlEvent.Preimage is
// set, the event indicates a settle event. If Preimage is nil, it is a cancel
// event.
type HodlEvent struct {
// HtlcResolution describes how an htlc should be resolved. If the preimage
// field is set, the event indicates a settle event. If Preimage is nil, it is
// a cancel event.
type HtlcResolution struct {
// Preimage is the htlc preimage. Its value is nil in case of a cancel.
Preimage *lntypes.Preimage
@ -48,6 +48,33 @@ type HodlEvent struct {
// AcceptHeight is the original height at which the htlc was accepted.
AcceptHeight int32
// Outcome indicates the outcome of the invoice registry update.
Outcome ResolutionResult
}
// NewFailureResolution returns a htlc failure resolution.
func NewFailureResolution(key channeldb.CircuitKey,
acceptHeight int32, outcome ResolutionResult) *HtlcResolution {
return &HtlcResolution{
CircuitKey: key,
AcceptHeight: acceptHeight,
Outcome: outcome,
}
}
// NewSettleResolution returns a htlc resolution which is associated with a
// settle.
func NewSettleResolution(preimage lntypes.Preimage, key channeldb.CircuitKey,
acceptHeight int32, outcome ResolutionResult) *HtlcResolution {
return &HtlcResolution{
Preimage: &preimage,
CircuitKey: key,
AcceptHeight: acceptHeight,
Outcome: outcome,
}
}
// RegistryConfig contains the configuration parameters for invoice registry.
@ -325,7 +352,7 @@ func (i *InvoiceRegistry) invoiceEventLoop() {
case <-nextReleaseTick:
event := autoReleaseHeap.Pop().(*htlcReleaseEvent)
err := i.cancelSingleHtlc(
event.hash, event.key,
event.hash, event.key, ResultMppTimeout,
)
if err != nil {
log.Errorf("HTLC timer: %v", err)
@ -574,9 +601,11 @@ func (i *InvoiceRegistry) startHtlcTimer(hash lntypes.Hash,
}
}
// cancelSingleHtlc cancels a single accepted htlc on an invoice.
// cancelSingleHtlc cancels a single accepted htlc on an invoice. It takes
// a resolution result which will be used to notify subscribed links and
// resolvers of the details of the htlc cancellation.
func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash,
key channeldb.CircuitKey) error {
key channeldb.CircuitKey, result ResolutionResult) error {
i.Lock()
defer i.Unlock()
@ -652,11 +681,11 @@ func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash,
return fmt.Errorf("htlc %v not found", key)
}
if htlc.State == channeldb.HtlcStateCanceled {
i.notifyHodlSubscribers(HodlEvent{
CircuitKey: key,
AcceptHeight: int32(htlc.AcceptHeight),
Preimage: nil,
})
resolution := *NewFailureResolution(
key, int32(htlc.AcceptHeight), result,
)
i.notifyHodlSubscribers(resolution)
}
return nil
}
@ -679,7 +708,7 @@ func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash,
func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
amtPaid lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
payload Payload) (*HodlEvent, error) {
payload Payload) (*HtlcResolution, error) {
i.Lock()
defer i.Unlock()
@ -706,7 +735,7 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
// We'll attempt to settle an invoice matching this rHash on disk (if
// one exists). The callback will update the invoice state and/or htlcs.
var (
result updateResult
result ResolutionResult
updateSubscribers bool
)
invoice, err := i.cdb.UpdateInvoice(
@ -729,11 +758,21 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
return updateDesc, nil
},
)
if err != nil {
debugLog(err.Error())
switch err {
case channeldb.ErrInvoiceNotFound:
// If the invoice was not found, return a failure resolution
// with an invoice not found result.
return NewFailureResolution(
circuitKey, currentHeight, ResultInvoiceNotFound,
), nil
case nil:
default:
debugLog(err.Error())
return nil, err
}
debugLog(result.String())
if updateSubscribers {
@ -745,10 +784,9 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
// If it isn't recorded, cancel htlc.
if !ok {
return &HodlEvent{
CircuitKey: circuitKey,
AcceptHeight: currentHeight,
}, nil
return NewFailureResolution(
circuitKey, currentHeight, result,
), nil
}
// Determine accepted height of this htlc. If the htlc reached the
@ -759,10 +797,9 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
switch invoiceHtlc.State {
case channeldb.HtlcStateCanceled:
return &HodlEvent{
CircuitKey: circuitKey,
AcceptHeight: acceptHeight,
}, nil
return NewFailureResolution(
circuitKey, acceptHeight, result,
), nil
case channeldb.HtlcStateSettled:
// Also settle any previously accepted htlcs. The invoice state
@ -773,18 +810,24 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
continue
}
i.notifyHodlSubscribers(HodlEvent{
CircuitKey: key,
Preimage: &invoice.Terms.PaymentPreimage,
AcceptHeight: int32(htlc.AcceptHeight),
})
// Notify subscribers that the htlcs should be settled
// with our peer. Note that the outcome of the
// resolution is set based on the outcome of the single
// htlc that we just settled, so may not be accurate
// for all htlcs.
resolution := *NewSettleResolution(
invoice.Terms.PaymentPreimage, key,
acceptHeight, result,
)
i.notifyHodlSubscribers(resolution)
}
return &HodlEvent{
CircuitKey: circuitKey,
Preimage: &invoice.Terms.PaymentPreimage,
AcceptHeight: acceptHeight,
}, nil
resolution := NewSettleResolution(
invoice.Terms.PaymentPreimage, circuitKey,
acceptHeight, result,
)
return resolution, nil
case channeldb.HtlcStateAccepted:
// (Re)start the htlc timer if the invoice is still open. It can
@ -836,7 +879,9 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error {
hash := preimage.Hash()
invoice, err := i.cdb.UpdateInvoice(hash, updateInvoice)
if err != nil {
log.Errorf("SettleHodlInvoice with preimage %v: %v", preimage, err)
log.Errorf("SettleHodlInvoice with preimage %v: %v",
preimage, err)
return err
}
@ -854,11 +899,11 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error {
continue
}
i.notifyHodlSubscribers(HodlEvent{
CircuitKey: key,
Preimage: &preimage,
AcceptHeight: int32(htlc.AcceptHeight),
})
resolution := *NewSettleResolution(
preimage, key, int32(htlc.AcceptHeight), ResultSettled,
)
i.notifyHodlSubscribers(resolution)
}
i.notifyClients(hash, invoice, invoice.State)
@ -873,7 +918,8 @@ func (i *InvoiceRegistry) CancelInvoice(payHash lntypes.Hash) error {
// cancelInvoice attempts to cancel the invoice corresponding to the passed
// payment hash. Accepted invoices will only be canceled if explicitly
// requested to do so.
// requested to do so. It notifies subscribing links and resolvers that
// the associated htlcs were canceled if they change state.
func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash,
cancelAccepted bool) error {
@ -932,10 +978,11 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash,
continue
}
i.notifyHodlSubscribers(HodlEvent{
CircuitKey: key,
AcceptHeight: int32(htlc.AcceptHeight),
})
i.notifyHodlSubscribers(
*NewFailureResolution(
key, int32(htlc.AcceptHeight), ResultCanceled,
),
)
}
i.notifyClients(payHash, invoice, channeldb.ContractCanceled)
@ -1201,9 +1248,10 @@ func (i *InvoiceRegistry) SubscribeSingleInvoice(
return client, nil
}
// notifyHodlSubscribers sends out the hodl event to all current subscribers.
func (i *InvoiceRegistry) notifyHodlSubscribers(hodlEvent HodlEvent) {
subscribers, ok := i.hodlSubscriptions[hodlEvent.CircuitKey]
// notifyHodlSubscribers sends out the htlc resolution to all current
// subscribers.
func (i *InvoiceRegistry) notifyHodlSubscribers(htlcResolution HtlcResolution) {
subscribers, ok := i.hodlSubscriptions[htlcResolution.CircuitKey]
if !ok {
return
}
@ -1213,18 +1261,18 @@ func (i *InvoiceRegistry) notifyHodlSubscribers(hodlEvent HodlEvent) {
// single resolution for each hash.
for subscriber := range subscribers {
select {
case subscriber <- hodlEvent:
case subscriber <- htlcResolution:
case <-i.quit:
return
}
delete(
i.hodlReverseSubscriptions[subscriber],
hodlEvent.CircuitKey,
htlcResolution.CircuitKey,
)
}
delete(i.hodlSubscriptions, hodlEvent.CircuitKey)
delete(i.hodlSubscriptions, htlcResolution.CircuitKey)
}
// hodlSubscribe adds a new invoice subscription.

View File

@ -66,7 +66,7 @@ func TestSettleInvoice(t *testing.T) {
hodlChan := make(chan interface{}, 1)
// Try to settle invoice with an htlc that expires too soon.
event, err := ctx.registry.NotifyExitHopHtlc(
resolution, err := ctx.registry.NotifyExitHopHtlc(
testInvoicePaymentHash, testInvoice.Terms.Value,
uint32(testCurrentHeight)+testInvoiceCltvDelta-1,
testCurrentHeight, getCircuitKey(10), hodlChan, testPayload,
@ -74,23 +74,30 @@ func TestSettleInvoice(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if event.Preimage != nil {
t.Fatal("expected cancel event")
if resolution.Preimage != nil {
t.Fatal("expected cancel resolution")
}
if event.AcceptHeight != testCurrentHeight {
if resolution.AcceptHeight != testCurrentHeight {
t.Fatalf("expected acceptHeight %v, but got %v",
testCurrentHeight, event.AcceptHeight)
testCurrentHeight, resolution.AcceptHeight)
}
if resolution.Outcome != ResultExpiryTooSoon {
t.Fatalf("expected expiry too soon, got: %v",
resolution.Outcome)
}
// Settle invoice with a slightly higher amount.
amtPaid := lnwire.MilliSatoshi(100500)
_, err = ctx.registry.NotifyExitHopHtlc(
resolution, err = ctx.registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight,
getCircuitKey(0), hodlChan, testPayload,
)
if err != nil {
t.Fatal(err)
}
if resolution.Outcome != ResultSettled {
t.Fatalf("expected settled, got: %v", resolution.Outcome)
}
// We expect the settled state to be sent to the single invoice
// subscriber.
@ -120,42 +127,54 @@ func TestSettleInvoice(t *testing.T) {
// Try to settle again with the same htlc id. We need this idempotent
// behaviour after a restart.
event, err = ctx.registry.NotifyExitHopHtlc(
resolution, err = ctx.registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight,
getCircuitKey(0), hodlChan, testPayload,
)
if err != nil {
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
}
if event.Preimage == nil {
t.Fatal("expected settle event")
if resolution.Preimage == nil {
t.Fatal("expected settle resolution")
}
if resolution.Outcome != ResultReplayToSettled {
t.Fatalf("expected replay settled, got: %v",
resolution.Outcome)
}
// Try to settle again with a new higher-valued htlc. This payment
// should also be accepted, to prevent any change in behaviour for a
// paid invoice that may open up a probe vector.
event, err = ctx.registry.NotifyExitHopHtlc(
resolution, err = ctx.registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amtPaid+600, testHtlcExpiry, testCurrentHeight,
getCircuitKey(1), hodlChan, testPayload,
)
if err != nil {
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
}
if event.Preimage == nil {
t.Fatal("expected settle event")
if resolution.Preimage == nil {
t.Fatal("expected settle resolution")
}
if resolution.Outcome != ResultDuplicateToSettled {
t.Fatalf("expected duplicate settled, got: %v",
resolution.Outcome)
}
// Try to settle again with a lower amount. This should fail just as it
// would have failed if it were the first payment.
event, err = ctx.registry.NotifyExitHopHtlc(
resolution, err = ctx.registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amtPaid-600, testHtlcExpiry, testCurrentHeight,
getCircuitKey(2), hodlChan, testPayload,
)
if err != nil {
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
}
if event.Preimage != nil {
t.Fatal("expected cancel event")
if resolution.Preimage != nil {
t.Fatal("expected cancel resolution")
}
if resolution.Outcome != ResultAmountTooLow {
t.Fatalf("expected amount too low, got: %v",
resolution.Outcome)
}
// Check that settled amount is equal to the sum of values of the htlcs
@ -177,7 +196,7 @@ func TestSettleInvoice(t *testing.T) {
// As this is a direct sette, we expect nothing on the hodl chan.
select {
case <-hodlChan:
t.Fatal("unexpected event")
t.Fatal("unexpected resolution")
default:
}
}
@ -270,9 +289,9 @@ func TestCancelInvoice(t *testing.T) {
}
// Notify arrival of a new htlc paying to this invoice. This should
// result in a cancel event.
// result in a cancel resolution.
hodlChan := make(chan interface{})
event, err := ctx.registry.NotifyExitHopHtlc(
resolution, err := ctx.registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amt, testHtlcExpiry, testCurrentHeight,
getCircuitKey(0), hodlChan, testPayload,
)
@ -280,12 +299,16 @@ func TestCancelInvoice(t *testing.T) {
t.Fatal("expected settlement of a canceled invoice to succeed")
}
if event.Preimage != nil {
t.Fatal("expected cancel hodl event")
if resolution.Preimage != nil {
t.Fatal("expected cancel htlc resolution")
}
if event.AcceptHeight != testCurrentHeight {
if resolution.AcceptHeight != testCurrentHeight {
t.Fatalf("expected acceptHeight %v, but got %v",
testCurrentHeight, event.AcceptHeight)
testCurrentHeight, resolution.AcceptHeight)
}
if resolution.Outcome != ResultInvoiceAlreadyCanceled {
t.Fatalf("expected invoice already canceled, got: %v",
resolution.Outcome)
}
}
@ -354,54 +377,58 @@ func TestSettleHoldInvoice(t *testing.T) {
// NotifyExitHopHtlc without a preimage present in the invoice registry
// should be possible.
event, err := registry.NotifyExitHopHtlc(
resolution, err := registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight,
getCircuitKey(0), hodlChan, testPayload,
)
if err != nil {
t.Fatalf("expected settle to succeed but got %v", err)
}
if event != nil {
if resolution != nil {
t.Fatalf("expected htlc to be held")
}
// Test idempotency.
event, err = registry.NotifyExitHopHtlc(
resolution, err = registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight,
getCircuitKey(0), hodlChan, testPayload,
)
if err != nil {
t.Fatalf("expected settle to succeed but got %v", err)
}
if event != nil {
if resolution != nil {
t.Fatalf("expected htlc to be held")
}
// Test replay at a higher height. We expect the same result because it
// is a replay.
event, err = registry.NotifyExitHopHtlc(
resolution, err = registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight+10,
getCircuitKey(0), hodlChan, testPayload,
)
if err != nil {
t.Fatalf("expected settle to succeed but got %v", err)
}
if event != nil {
if resolution != nil {
t.Fatalf("expected htlc to be held")
}
// Test a new htlc coming in that doesn't meet the final cltv delta
// requirement. It should be rejected.
event, err = registry.NotifyExitHopHtlc(
resolution, err = registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amtPaid, 1, testCurrentHeight,
getCircuitKey(1), hodlChan, testPayload,
)
if err != nil {
t.Fatalf("expected settle to succeed but got %v", err)
}
if event == nil || event.Preimage != nil {
if resolution == nil || resolution.Preimage != nil {
t.Fatalf("expected htlc to be canceled")
}
if resolution.Outcome != ResultExpiryTooSoon {
t.Fatalf("expected expiry too soon, got: %v",
resolution.Outcome)
}
// We expect the accepted state to be sent to the single invoice
// subscriber. For all invoice subscribers, we don't expect an update.
@ -421,13 +448,17 @@ func TestSettleHoldInvoice(t *testing.T) {
t.Fatal("expected set preimage to succeed")
}
hodlEvent := (<-hodlChan).(HodlEvent)
if *hodlEvent.Preimage != testInvoicePreimage {
t.Fatal("unexpected preimage in hodl event")
htlcResolution := (<-hodlChan).(HtlcResolution)
if *htlcResolution.Preimage != testInvoicePreimage {
t.Fatal("unexpected preimage in hodl resolution")
}
if hodlEvent.AcceptHeight != testCurrentHeight {
if htlcResolution.AcceptHeight != testCurrentHeight {
t.Fatalf("expected acceptHeight %v, but got %v",
testCurrentHeight, event.AcceptHeight)
testCurrentHeight, resolution.AcceptHeight)
}
if htlcResolution.Outcome != ResultSettled {
t.Fatalf("expected result settled, got: %v",
htlcResolution.Outcome)
}
// We expect a settled notification to be sent out for both all and
@ -496,14 +527,14 @@ func TestCancelHoldInvoice(t *testing.T) {
// NotifyExitHopHtlc without a preimage present in the invoice registry
// should be possible.
event, err := registry.NotifyExitHopHtlc(
resolution, err := registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight,
getCircuitKey(0), hodlChan, testPayload,
)
if err != nil {
t.Fatalf("expected settle to succeed but got %v", err)
}
if event != nil {
if resolution != nil {
t.Fatalf("expected htlc to be held")
}
@ -513,35 +544,39 @@ func TestCancelHoldInvoice(t *testing.T) {
t.Fatal("cancel invoice failed")
}
hodlEvent := (<-hodlChan).(HodlEvent)
if hodlEvent.Preimage != nil {
t.Fatal("expected cancel hodl event")
htlcResolution := (<-hodlChan).(HtlcResolution)
if htlcResolution.Preimage != nil {
t.Fatal("expected cancel htlc resolution")
}
// Offering the same htlc again at a higher height should still result
// in a rejection. The accept height is expected to be the original
// accept height.
event, err = registry.NotifyExitHopHtlc(
resolution, err = registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amtPaid, testHtlcExpiry, testCurrentHeight+1,
getCircuitKey(0), hodlChan, testPayload,
)
if err != nil {
t.Fatalf("expected settle to succeed but got %v", err)
}
if event.Preimage != nil {
if resolution.Preimage != nil {
t.Fatalf("expected htlc to be canceled")
}
if event.AcceptHeight != testCurrentHeight {
if resolution.AcceptHeight != testCurrentHeight {
t.Fatalf("expected acceptHeight %v, but got %v",
testCurrentHeight, event.AcceptHeight)
testCurrentHeight, resolution.AcceptHeight)
}
if resolution.Outcome != ResultReplayToCanceled {
t.Fatalf("expected replay to canceled, got %v",
resolution.Outcome)
}
}
// TestUnknownInvoice tests that invoice registry returns an error when the
// invoice is unknown. This is to guard against returning a cancel hodl event
// for forwarded htlcs. In the link, NotifyExitHopHtlc is only called if we are
// the exit hop, but in htlcIncomingContestResolver it is called with forwarded
// htlc hashes as well.
// invoice is unknown. This is to guard against returning a cancel htlc
// resolution for forwarded htlcs. In the link, NotifyExitHopHtlc is only called
// if we are the exit hop, but in htlcIncomingContestResolver it is called with
// forwarded htlc hashes as well.
func TestUnknownInvoice(t *testing.T) {
ctx := newTestContext(t)
defer ctx.cleanup()
@ -550,17 +585,23 @@ func TestUnknownInvoice(t *testing.T) {
// succeed.
hodlChan := make(chan interface{})
amt := lnwire.MilliSatoshi(100000)
_, err := ctx.registry.NotifyExitHopHtlc(
result, err := ctx.registry.NotifyExitHopHtlc(
testInvoicePaymentHash, amt, testHtlcExpiry, testCurrentHeight,
getCircuitKey(0), hodlChan, testPayload,
)
if err != channeldb.ErrInvoiceNotFound {
t.Fatal("expected invoice not found error")
if err != nil {
t.Fatal("unexpected error")
}
if result.Outcome != ResultInvoiceNotFound {
t.Fatalf("expected ResultInvoiceNotFound, got: %v",
result.Outcome)
}
}
// TestSettleMpp tests settling of an invoice with multiple partial payments.
func TestSettleMpp(t *testing.T) {
// TestMppPayment tests settling of an invoice with multiple partial payments.
// It covers the case where there is a mpp timeout before the whole invoice is
// paid and the case where the invoice is settled in time.
func TestMppPayment(t *testing.T) {
defer timeout()()
ctx := newTestContext(t)
@ -578,7 +619,7 @@ func TestSettleMpp(t *testing.T) {
// Send htlc 1.
hodlChan1 := make(chan interface{}, 1)
event, err := ctx.registry.NotifyExitHopHtlc(
resolution, err := ctx.registry.NotifyExitHopHtlc(
testInvoicePaymentHash, testInvoice.Terms.Value/2,
testHtlcExpiry,
testCurrentHeight, getCircuitKey(10), hodlChan1, mppPayload,
@ -586,21 +627,25 @@ func TestSettleMpp(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if event != nil {
if resolution != nil {
t.Fatal("expected no direct resolution")
}
// Simulate mpp timeout releasing htlc 1.
ctx.clock.SetTime(testTime.Add(30 * time.Second))
hodlEvent := (<-hodlChan1).(HodlEvent)
if hodlEvent.Preimage != nil {
t.Fatal("expected cancel event")
htlcResolution := (<-hodlChan1).(HtlcResolution)
if htlcResolution.Preimage != nil {
t.Fatal("expected cancel resolution")
}
if htlcResolution.Outcome != ResultMppTimeout {
t.Fatalf("expected mpp timeout, got: %v",
htlcResolution.Outcome)
}
// Send htlc 2.
hodlChan2 := make(chan interface{}, 1)
event, err = ctx.registry.NotifyExitHopHtlc(
resolution, err = ctx.registry.NotifyExitHopHtlc(
testInvoicePaymentHash, testInvoice.Terms.Value/2,
testHtlcExpiry,
testCurrentHeight, getCircuitKey(11), hodlChan2, mppPayload,
@ -608,13 +653,13 @@ func TestSettleMpp(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if event != nil {
if resolution != nil {
t.Fatal("expected no direct resolution")
}
// Send htlc 3.
hodlChan3 := make(chan interface{}, 1)
event, err = ctx.registry.NotifyExitHopHtlc(
resolution, err = ctx.registry.NotifyExitHopHtlc(
testInvoicePaymentHash, testInvoice.Terms.Value/2,
testHtlcExpiry,
testCurrentHeight, getCircuitKey(12), hodlChan3, mppPayload,
@ -622,12 +667,16 @@ func TestSettleMpp(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if event == nil {
t.Fatal("expected a settle event")
if resolution == nil {
t.Fatal("expected a settle resolution")
}
if resolution.Outcome != ResultSettled {
t.Fatalf("expected result settled, got: %v",
resolution.Outcome)
}
// Check that settled amount is equal to the sum of values of the htlcs
// 0 and 1.
// 2 and 3.
inv, err := ctx.registry.LookupInvoice(testInvoicePaymentHash)
if err != nil {
t.Fatal(err)

View File

@ -8,86 +8,141 @@ import (
"github.com/lightningnetwork/lnd/record"
)
// updateResult is the result of the invoice update call.
type updateResult uint8
// ResolutionResult provides metadata which about an invoice update which can
// be used to take custom actions on resolution of the htlc. Only results which
// are actionable by the link are exported.
type ResolutionResult uint8
const (
resultInvalid updateResult = iota
resultReplayToCanceled
resultReplayToAccepted
resultReplayToSettled
resultInvoiceAlreadyCanceled
resultAmountTooLow
resultExpiryTooSoon
resultDuplicateToAccepted
resultDuplicateToSettled
resultAccepted
resultSettled
resultInvoiceNotOpen
resultPartialAccepted
resultMppInProgress
resultAddressMismatch
resultHtlcSetTotalMismatch
resultHtlcSetTotalTooLow
resultHtlcSetOverpayment
resultInvalid ResolutionResult = iota
// ResultReplayToCanceled is returned when we replay a canceled invoice.
ResultReplayToCanceled
// ResultReplayToAccepted is returned when we replay an accepted invoice.
ResultReplayToAccepted
// ResultReplayToSettled is returned when we replay a settled invoice.
ResultReplayToSettled
// ResultInvoiceAlreadyCanceled is returned when trying to pay an invoice
// that is already canceled.
ResultInvoiceAlreadyCanceled
// ResultAmountTooLow is returned when an invoice is underpaid.
ResultAmountTooLow
// ResultExpiryTooSoon is returned when we do not accept an invoice payment
// because it expires too soon.
ResultExpiryTooSoon
// ResultDuplicateToAccepted is returned when we accept a duplicate htlc.
ResultDuplicateToAccepted
// ResultDuplicateToSettled is returned when we settle an invoice which has
// already been settled at least once.
ResultDuplicateToSettled
// ResultAccepted is returned when we accept a hodl invoice.
ResultAccepted
// ResultSettled is returned when we settle an invoice.
ResultSettled
// ResultCanceled is returned when we cancel an invoice and its associated
// htlcs.
ResultCanceled
// ResultInvoiceNotOpen is returned when a mpp invoice is not open.
ResultInvoiceNotOpen
// ResultPartialAccepted is returned when we have partially received
// payment.
ResultPartialAccepted
// ResultMppInProgress is returned when we are busy receiving a mpp payment.
ResultMppInProgress
// ResultMppTimeout is returned when an invoice paid with multiple partial
// payments times out before it is fully paid.
ResultMppTimeout
// ResultAddressMismatch is returned when the payment address for a mpp
// invoice does not match.
ResultAddressMismatch
// ResultHtlcSetTotalMismatch is returned when the amount paid by a htlc
// does not match its set total.
ResultHtlcSetTotalMismatch
// ResultHtlcSetTotalTooLow is returned when a mpp set total is too low for
// an invoice.
ResultHtlcSetTotalTooLow
// ResultHtlcSetOverpayment is returned when a mpp set is overpaid.
ResultHtlcSetOverpayment
// ResultInvoiceNotFound is returned when an attempt is made to pay an
// invoice that is unknown to us.
ResultInvoiceNotFound
)
// String returns a human-readable representation of the invoice update result.
func (u updateResult) String() string {
func (u ResolutionResult) String() string {
switch u {
case resultInvalid:
return "invalid"
case resultReplayToCanceled:
case ResultReplayToCanceled:
return "replayed htlc to canceled invoice"
case resultReplayToAccepted:
case ResultReplayToAccepted:
return "replayed htlc to accepted invoice"
case resultReplayToSettled:
case ResultReplayToSettled:
return "replayed htlc to settled invoice"
case resultInvoiceAlreadyCanceled:
case ResultInvoiceAlreadyCanceled:
return "invoice already canceled"
case resultAmountTooLow:
case ResultAmountTooLow:
return "amount too low"
case resultExpiryTooSoon:
case ResultExpiryTooSoon:
return "expiry too soon"
case resultDuplicateToAccepted:
case ResultDuplicateToAccepted:
return "accepting duplicate payment to accepted invoice"
case resultDuplicateToSettled:
case ResultDuplicateToSettled:
return "accepting duplicate payment to settled invoice"
case resultAccepted:
case ResultAccepted:
return "accepted"
case resultSettled:
case ResultSettled:
return "settled"
case resultInvoiceNotOpen:
case ResultInvoiceNotOpen:
return "invoice no longer open"
case resultPartialAccepted:
case ResultPartialAccepted:
return "partial payment accepted"
case resultMppInProgress:
case ResultMppInProgress:
return "mpp reception in progress"
case resultAddressMismatch:
case ResultAddressMismatch:
return "payment address mismatch"
case resultHtlcSetTotalMismatch:
case ResultHtlcSetTotalMismatch:
return "htlc total amt doesn't match set total"
case resultHtlcSetTotalTooLow:
case ResultHtlcSetTotalTooLow:
return "set total too low for invoice"
case resultHtlcSetOverpayment:
case ResultHtlcSetOverpayment:
return "mpp is overpaying set total"
default:
@ -110,20 +165,20 @@ type invoiceUpdateCtx struct {
// updateInvoice is a callback for DB.UpdateInvoice that contains the invoice
// settlement logic.
func updateInvoice(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
*channeldb.InvoiceUpdateDesc, updateResult, error) {
*channeldb.InvoiceUpdateDesc, ResolutionResult, error) {
// Don't update the invoice when this is a replayed htlc.
htlc, ok := inv.Htlcs[ctx.circuitKey]
if ok {
switch htlc.State {
case channeldb.HtlcStateCanceled:
return nil, resultReplayToCanceled, nil
return nil, ResultReplayToCanceled, nil
case channeldb.HtlcStateAccepted:
return nil, resultReplayToAccepted, nil
return nil, ResultReplayToAccepted, nil
case channeldb.HtlcStateSettled:
return nil, resultReplayToSettled, nil
return nil, ResultReplayToSettled, nil
default:
return nil, 0, errors.New("unknown htlc state")
@ -140,7 +195,7 @@ func updateInvoice(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
// updateMpp is a callback for DB.UpdateInvoice that contains the invoice
// settlement logic for mpp payments.
func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
*channeldb.InvoiceUpdateDesc, updateResult, error) {
*channeldb.InvoiceUpdateDesc, ResolutionResult, error) {
// Start building the accept descriptor.
acceptDesc := &channeldb.HtlcAcceptDesc{
@ -156,23 +211,23 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
// Because non-mpp payments don't have a payment address, this is needed
// to thwart probing.
if inv.State != channeldb.ContractOpen {
return nil, resultInvoiceNotOpen, nil
return nil, ResultInvoiceNotOpen, nil
}
// Check the payment address that authorizes the payment.
if ctx.mpp.PaymentAddr() != inv.Terms.PaymentAddr {
return nil, resultAddressMismatch, nil
return nil, ResultAddressMismatch, nil
}
// Don't accept zero-valued sets.
if ctx.mpp.TotalMsat() == 0 {
return nil, resultHtlcSetTotalTooLow, nil
return nil, ResultHtlcSetTotalTooLow, nil
}
// Check that the total amt of the htlc set is high enough. In case this
// is a zero-valued invoice, it will always be enough.
if ctx.mpp.TotalMsat() < inv.Terms.Value {
return nil, resultHtlcSetTotalTooLow, nil
return nil, ResultHtlcSetTotalTooLow, nil
}
// Check whether total amt matches other htlcs in the set.
@ -186,7 +241,7 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
}
if ctx.mpp.TotalMsat() != htlc.MppTotalAmt {
return nil, resultHtlcSetTotalMismatch, nil
return nil, ResultHtlcSetTotalMismatch, nil
}
newSetTotal += htlc.Amt
@ -197,16 +252,16 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
// Make sure the communicated set total isn't overpaid.
if newSetTotal > ctx.mpp.TotalMsat() {
return nil, resultHtlcSetOverpayment, nil
return nil, ResultHtlcSetOverpayment, nil
}
// The invoice is still open. Check the expiry.
if ctx.expiry < uint32(ctx.currentHeight+ctx.finalCltvRejectDelta) {
return nil, resultExpiryTooSoon, nil
return nil, ResultExpiryTooSoon, nil
}
if ctx.expiry < uint32(ctx.currentHeight+inv.Terms.FinalCltvDelta) {
return nil, resultExpiryTooSoon, nil
return nil, ResultExpiryTooSoon, nil
}
// Record HTLC in the invoice database.
@ -221,7 +276,7 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
// If the invoice cannot be settled yet, only record the htlc.
setComplete := newSetTotal == ctx.mpp.TotalMsat()
if !setComplete {
return &update, resultPartialAccepted, nil
return &update, ResultPartialAccepted, nil
}
// Check to see if we can settle or this is an hold invoice and
@ -231,7 +286,7 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
update.State = &channeldb.InvoiceStateUpdateDesc{
NewState: channeldb.ContractAccepted,
}
return &update, resultAccepted, nil
return &update, ResultAccepted, nil
}
update.State = &channeldb.InvoiceStateUpdateDesc{
@ -239,18 +294,18 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
Preimage: inv.Terms.PaymentPreimage,
}
return &update, resultSettled, nil
return &update, ResultSettled, nil
}
// updateLegacy is a callback for DB.UpdateInvoice that contains the invoice
// settlement logic for legacy payments.
func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
*channeldb.InvoiceUpdateDesc, updateResult, error) {
*channeldb.InvoiceUpdateDesc, ResolutionResult, error) {
// If the invoice is already canceled, there is no further
// checking to do.
if inv.State == channeldb.ContractCanceled {
return nil, resultInvoiceAlreadyCanceled, nil
return nil, ResultInvoiceAlreadyCanceled, nil
}
// If an invoice amount is specified, check that enough is paid. Also
@ -258,7 +313,7 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
// or accepted. In case this is a zero-valued invoice, it will always be
// enough.
if ctx.amtPaid < inv.Terms.Value {
return nil, resultAmountTooLow, nil
return nil, ResultAmountTooLow, nil
}
// TODO(joostjager): Check invoice mpp required feature
@ -271,17 +326,17 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
if htlc.State == channeldb.HtlcStateAccepted &&
htlc.MppTotalAmt > 0 {
return nil, resultMppInProgress, nil
return nil, ResultMppInProgress, nil
}
}
// The invoice is still open. Check the expiry.
if ctx.expiry < uint32(ctx.currentHeight+ctx.finalCltvRejectDelta) {
return nil, resultExpiryTooSoon, nil
return nil, ResultExpiryTooSoon, nil
}
if ctx.expiry < uint32(ctx.currentHeight+inv.Terms.FinalCltvDelta) {
return nil, resultExpiryTooSoon, nil
return nil, ResultExpiryTooSoon, nil
}
// Record HTLC in the invoice database.
@ -302,10 +357,10 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
// We do accept or settle the HTLC.
switch inv.State {
case channeldb.ContractAccepted:
return &update, resultDuplicateToAccepted, nil
return &update, ResultDuplicateToAccepted, nil
case channeldb.ContractSettled:
return &update, resultDuplicateToSettled, nil
return &update, ResultDuplicateToSettled, nil
}
// Check to see if we can settle or this is an hold invoice and we need
@ -315,7 +370,7 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
update.State = &channeldb.InvoiceStateUpdateDesc{
NewState: channeldb.ContractAccepted,
}
return &update, resultAccepted, nil
return &update, ResultAccepted, nil
}
update.State = &channeldb.InvoiceStateUpdateDesc{
@ -323,5 +378,5 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
Preimage: inv.Terms.PaymentPreimage,
}
return &update, resultSettled, nil
return &update, ResultSettled, nil
}

View File

@ -1,13 +1,13 @@
package lncfg
import (
"context"
"crypto/tls"
"encoding/hex"
"fmt"
"net"
"strconv"
"strings"
"time"
"github.com/btcsuite/btcd/btcec"
"github.com/lightningnetwork/lnd/lnwire"
@ -274,8 +274,10 @@ func verifyPort(address string, defaultPort string) string {
// ClientAddressDialer creates a gRPC dialer that can also dial unix socket
// addresses instead of just TCP addresses.
func ClientAddressDialer(defaultPort string) func(string, time.Duration) (net.Conn, error) {
return func(addr string, timeout time.Duration) (net.Conn, error) {
func ClientAddressDialer(defaultPort string) func(context.Context,
string) (net.Conn, error) {
return func(ctx context.Context, addr string) (net.Conn, error) {
parsedAddr, err := ParseAddressString(
addr, defaultPort, net.ResolveTCPAddr,
)
@ -283,8 +285,9 @@ func ClientAddressDialer(defaultPort string) func(string, time.Duration) (net.Co
return nil, err
}
return net.DialTimeout(
parsedAddr.Network(), parsedAddr.String(), timeout,
d := net.Dialer{}
return d.DialContext(
ctx, parsedAddr.Network(), parsedAddr.String(),
)
}
}

View File

@ -109,6 +109,7 @@ const (
Failure_PERMANENT_NODE_FAILURE Failure_FailureCode = 20
Failure_PERMANENT_CHANNEL_FAILURE Failure_FailureCode = 21
Failure_EXPIRY_TOO_FAR Failure_FailureCode = 22
Failure_MPP_TIMEOUT Failure_FailureCode = 23
//*
//The error source is known, but the failure itself couldn't be decoded.
Failure_UNKNOWN_FAILURE Failure_FailureCode = 998
@ -142,6 +143,7 @@ var Failure_FailureCode_name = map[int32]string{
20: "PERMANENT_NODE_FAILURE",
21: "PERMANENT_CHANNEL_FAILURE",
22: "EXPIRY_TOO_FAR",
23: "MPP_TIMEOUT",
998: "UNKNOWN_FAILURE",
999: "UNREADABLE_FAILURE",
}
@ -170,6 +172,7 @@ var Failure_FailureCode_value = map[string]int32{
"PERMANENT_NODE_FAILURE": 20,
"PERMANENT_CHANNEL_FAILURE": 21,
"EXPIRY_TOO_FAR": 22,
"MPP_TIMEOUT": 23,
"UNKNOWN_FAILURE": 998,
"UNREADABLE_FAILURE": 999,
}
@ -258,7 +261,9 @@ type SendPaymentRequest struct {
//*
//Features assumed to be supported by the final node. All transitive feature
//depdencies must also be set properly. For a given feature bit pair, either
//optional or remote may be set, but not both.
//optional or remote may be set, but not both. If this field is nil or empty,
//the router will try to load destination features from the graph as a
//fallback.
DestFeatures []lnrpc.FeatureBit `protobuf:"varint,16,rep,packed,name=dest_features,json=destFeatures,proto3,enum=lnrpc.FeatureBit" json:"dest_features,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
@ -1524,138 +1529,139 @@ func init() {
func init() { proto.RegisterFile("routerrpc/router.proto", fileDescriptor_7a0613f69d37b0a5) }
var fileDescriptor_7a0613f69d37b0a5 = []byte{
// 2093 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x58, 0x4f, 0x73, 0xdb, 0xb8,
0x15, 0x5f, 0x5a, 0xff, 0x9f, 0xfe, 0xd1, 0xb0, 0xe3, 0x30, 0x72, 0xbc, 0xf1, 0x32, 0x69, 0x56,
0x93, 0xc9, 0xda, 0xa9, 0xdb, 0xcd, 0x64, 0x7a, 0x68, 0x47, 0x96, 0xa8, 0x35, 0x1d, 0x89, 0x72,
0x20, 0x29, 0xbb, 0xe9, 0x1e, 0x30, 0xb4, 0x04, 0x5b, 0x1c, 0x53, 0xa4, 0x96, 0x84, 0xb2, 0xf1,
0x77, 0xe8, 0xf7, 0x68, 0x0f, 0x6d, 0x2f, 0xfd, 0x4e, 0xed, 0xbd, 0x33, 0x3d, 0xf4, 0xd6, 0x01,
0x40, 0x4a, 0x94, 0x2c, 0x67, 0x7b, 0xb2, 0xf0, 0x7b, 0x3f, 0xbc, 0x07, 0xe2, 0xe1, 0xfd, 0xf0,
0x60, 0xd8, 0x0b, 0xfc, 0x39, 0xa3, 0x41, 0x30, 0x1b, 0x1d, 0xcb, 0x5f, 0x47, 0xb3, 0xc0, 0x67,
0x3e, 0x2a, 0x2c, 0xf0, 0x5a, 0x21, 0x98, 0x8d, 0x24, 0xaa, 0xff, 0x37, 0x03, 0xa8, 0x4f, 0xbd,
0xf1, 0x85, 0x7d, 0x3b, 0xa5, 0x1e, 0xc3, 0xf4, 0xa7, 0x39, 0x0d, 0x19, 0x42, 0x90, 0x1e, 0xd3,
0x90, 0x69, 0xca, 0xa1, 0x52, 0x2f, 0x61, 0xf1, 0x1b, 0xa9, 0x90, 0xb2, 0xa7, 0x4c, 0xdb, 0x3a,
0x54, 0xea, 0x29, 0xcc, 0x7f, 0xa2, 0x47, 0x90, 0xb7, 0xa7, 0x8c, 0x4c, 0x43, 0x9b, 0x69, 0x25,
0x01, 0xe7, 0xec, 0x29, 0xeb, 0x86, 0x36, 0x43, 0x5f, 0x41, 0x69, 0x26, 0x5d, 0x92, 0x89, 0x1d,
0x4e, 0xb4, 0x94, 0x70, 0x54, 0x8c, 0xb0, 0x33, 0x3b, 0x9c, 0xa0, 0x3a, 0xa8, 0x57, 0x8e, 0x67,
0xbb, 0x64, 0xe4, 0xb2, 0x8f, 0x64, 0x4c, 0x5d, 0x66, 0x6b, 0xe9, 0x43, 0xa5, 0x9e, 0xc1, 0x15,
0x81, 0x37, 0x5d, 0xf6, 0xb1, 0xc5, 0x51, 0xf4, 0x35, 0x54, 0x63, 0x67, 0x81, 0x5c, 0xa0, 0x96,
0x39, 0x54, 0xea, 0x05, 0x5c, 0x99, 0xad, 0x2e, 0xfb, 0x6b, 0xa8, 0x32, 0x67, 0x4a, 0xfd, 0x39,
0x23, 0x21, 0x1d, 0xf9, 0xde, 0x38, 0xd4, 0xb2, 0xd2, 0x63, 0x04, 0xf7, 0x25, 0x8a, 0x74, 0x28,
0x5f, 0x51, 0x4a, 0x5c, 0x67, 0xea, 0x30, 0xc2, 0x97, 0x9f, 0x13, 0xcb, 0x2f, 0x5e, 0x51, 0xda,
0xe1, 0x58, 0xdf, 0x66, 0xe8, 0x19, 0x54, 0x96, 0x1c, 0xf1, 0x8d, 0x65, 0x41, 0x2a, 0xc5, 0x24,
0xf1, 0xa1, 0x2f, 0x41, 0xf5, 0xe7, 0xec, 0xda, 0x77, 0xbc, 0x6b, 0x32, 0x9a, 0xd8, 0x1e, 0x71,
0xc6, 0x5a, 0xfe, 0x50, 0xa9, 0xa7, 0x4f, 0xb7, 0x5e, 0x29, 0xb8, 0x12, 0xdb, 0x9a, 0x13, 0xdb,
0x33, 0xc7, 0xe8, 0x39, 0x54, 0x5d, 0x3b, 0x64, 0x64, 0xe2, 0xcf, 0xc8, 0x6c, 0x7e, 0x79, 0x43,
0x6f, 0xb5, 0x8a, 0xd8, 0x99, 0x32, 0x87, 0xcf, 0xfc, 0xd9, 0x85, 0x00, 0xd1, 0x01, 0x80, 0xd8,
0x15, 0x11, 0x5c, 0x2b, 0x88, 0x6f, 0x28, 0x70, 0x44, 0x04, 0x46, 0x27, 0x50, 0x14, 0xd9, 0x24,
0x13, 0xc7, 0x63, 0xa1, 0x06, 0x87, 0xa9, 0x7a, 0xf1, 0x44, 0x3d, 0x72, 0x3d, 0x9e, 0x58, 0xcc,
0x2d, 0x67, 0x8e, 0xc7, 0x70, 0x92, 0x84, 0xc6, 0xb0, 0xc3, 0xd3, 0x48, 0x46, 0xf3, 0x90, 0xf9,
0x53, 0x12, 0xd0, 0x91, 0x1f, 0x8c, 0x43, 0xad, 0x28, 0xe6, 0xfe, 0xf6, 0x68, 0x71, 0x3a, 0x8e,
0xee, 0x1e, 0x87, 0xa3, 0x16, 0x0d, 0x59, 0x53, 0xcc, 0xc3, 0x72, 0x9a, 0xe1, 0xb1, 0xe0, 0x16,
0x6f, 0x8f, 0xd7, 0x71, 0xf4, 0x12, 0x90, 0xed, 0xba, 0xfe, 0xcf, 0x24, 0xa4, 0xee, 0x15, 0x89,
0xd2, 0xa3, 0x55, 0x0f, 0x95, 0x7a, 0x1e, 0xab, 0xc2, 0xd2, 0xa7, 0xee, 0x55, 0xe4, 0x1e, 0xbd,
0x86, 0xb2, 0x58, 0xd3, 0x15, 0xb5, 0xd9, 0x3c, 0xa0, 0xa1, 0xa6, 0x1e, 0xa6, 0xea, 0x95, 0x93,
0xed, 0xe8, 0x4b, 0xda, 0x12, 0x3e, 0x75, 0x18, 0x2e, 0x71, 0x5e, 0x34, 0x0e, 0x6b, 0x2d, 0xd8,
0xdb, 0xbc, 0x24, 0x7e, 0x48, 0xf9, 0xa6, 0xf2, 0x73, 0x9b, 0xc6, 0xfc, 0x27, 0xda, 0x85, 0xcc,
0x47, 0xdb, 0x9d, 0x53, 0x71, 0x70, 0x4b, 0x58, 0x0e, 0x7e, 0xb7, 0xf5, 0x46, 0xd1, 0xdf, 0xc0,
0xce, 0x20, 0xb0, 0x47, 0x37, 0x6b, 0x67, 0x7f, 0xfd, 0xe8, 0x2a, 0x77, 0x8e, 0xae, 0xfe, 0x17,
0x05, 0xca, 0xd1, 0xac, 0x3e, 0xb3, 0xd9, 0x3c, 0x44, 0xdf, 0x40, 0x26, 0x64, 0x36, 0xa3, 0x82,
0x5d, 0x39, 0x79, 0x98, 0xd8, 0xcf, 0x04, 0x91, 0x62, 0xc9, 0x42, 0x35, 0xc8, 0xcf, 0x02, 0xea,
0x4c, 0xed, 0xeb, 0x78, 0x5d, 0x8b, 0x31, 0xd2, 0x21, 0x23, 0x26, 0x8b, 0x9a, 0x29, 0x9e, 0x94,
0x92, 0x69, 0xc5, 0xd2, 0x84, 0xea, 0x90, 0x99, 0x30, 0x77, 0x14, 0x6a, 0x69, 0x91, 0x3e, 0x14,
0x71, 0xce, 0x06, 0x9d, 0x66, 0x83, 0x31, 0x3a, 0x9d, 0x31, 0x2c, 0x09, 0xfa, 0xef, 0xa1, 0x2a,
0x66, 0xb6, 0x29, 0xfd, 0x5c, 0x71, 0x3f, 0x04, 0x5e, 0xba, 0xa2, 0x14, 0x64, 0x81, 0x67, 0xed,
0x29, 0xaf, 0x02, 0x7d, 0x0c, 0xea, 0x72, 0x7e, 0x38, 0xf3, 0xbd, 0x90, 0x47, 0x57, 0xf9, 0x32,
0xf8, 0x91, 0xe7, 0x15, 0x22, 0x6a, 0x43, 0x11, 0xb3, 0x2a, 0x11, 0xde, 0xa6, 0x54, 0x54, 0xc7,
0x73, 0x59, 0x90, 0xc4, 0xf5, 0x47, 0x37, 0xbc, 0xc4, 0xed, 0xdb, 0xc8, 0x7d, 0x99, 0xc3, 0x1d,
0x7f, 0x74, 0xd3, 0xe2, 0xa0, 0xfe, 0xa3, 0x54, 0xa1, 0x81, 0x2f, 0xbf, 0xf2, 0xff, 0xce, 0xc4,
0x72, 0xb3, 0xb6, 0xee, 0xdd, 0x2c, 0x9d, 0xc0, 0xce, 0x8a, 0xf3, 0xe8, 0x2b, 0x92, 0x39, 0x50,
0xd6, 0x72, 0xf0, 0x12, 0x72, 0x57, 0xb6, 0xe3, 0xce, 0x83, 0xd8, 0x31, 0x4a, 0x24, 0xb4, 0x2d,
0x2d, 0x38, 0xa6, 0xe8, 0xff, 0xc9, 0x41, 0x2e, 0x02, 0xd1, 0x09, 0xa4, 0x47, 0xfe, 0x38, 0x3e,
0x07, 0x5f, 0xde, 0x9d, 0x16, 0xff, 0x6d, 0xfa, 0x63, 0x8a, 0x05, 0x17, 0xfd, 0x01, 0x2a, 0x5c,
0x3a, 0x3c, 0xea, 0x92, 0xf9, 0x6c, 0x6c, 0x2f, 0x52, 0xaf, 0x25, 0x66, 0x37, 0x25, 0x61, 0x28,
0xec, 0xb8, 0x3c, 0x4a, 0x0e, 0xd1, 0x3e, 0x14, 0x78, 0xb6, 0x65, 0x26, 0xd2, 0xe2, 0xec, 0xe7,
0x39, 0x20, 0x72, 0xa0, 0x43, 0xd9, 0xf7, 0x1c, 0xdf, 0x23, 0xe1, 0xc4, 0x26, 0x27, 0xdf, 0xbe,
0x16, 0xda, 0x59, 0xc2, 0x45, 0x01, 0xf6, 0x27, 0xf6, 0xc9, 0xb7, 0xaf, 0xd1, 0x13, 0x28, 0x0a,
0xbd, 0xa1, 0x9f, 0x66, 0x4e, 0x70, 0x2b, 0x44, 0xb3, 0x8c, 0x85, 0x04, 0x19, 0x02, 0xe1, 0x55,
0x74, 0xe5, 0xda, 0xd7, 0xa1, 0x10, 0xca, 0x32, 0x96, 0x03, 0xf4, 0x0a, 0x76, 0xa3, 0x3d, 0x20,
0xa1, 0x3f, 0x0f, 0x46, 0x94, 0x38, 0xde, 0x98, 0x7e, 0x12, 0x02, 0x58, 0xc6, 0x28, 0xb2, 0xf5,
0x85, 0xc9, 0xe4, 0x16, 0xb4, 0x07, 0xd9, 0x09, 0x75, 0xae, 0x27, 0x52, 0xd4, 0xca, 0x38, 0x1a,
0xe9, 0x7f, 0xcd, 0x40, 0x31, 0xb1, 0x31, 0xa8, 0x04, 0x79, 0x6c, 0xf4, 0x0d, 0xfc, 0xde, 0x68,
0xa9, 0x5f, 0xa0, 0x3a, 0x3c, 0x33, 0xad, 0x66, 0x0f, 0x63, 0xa3, 0x39, 0x20, 0x3d, 0x4c, 0x86,
0xd6, 0x5b, 0xab, 0xf7, 0xbd, 0x45, 0x2e, 0x1a, 0x1f, 0xba, 0x86, 0x35, 0x20, 0x2d, 0x63, 0xd0,
0x30, 0x3b, 0x7d, 0x55, 0x41, 0x8f, 0x41, 0x5b, 0x32, 0x63, 0x73, 0xa3, 0xdb, 0x1b, 0x5a, 0x03,
0x75, 0x0b, 0x3d, 0x81, 0xfd, 0xb6, 0x69, 0x35, 0x3a, 0x64, 0xc9, 0x69, 0x76, 0x06, 0xef, 0x89,
0xf1, 0xc3, 0x85, 0x89, 0x3f, 0xa8, 0xa9, 0x4d, 0x04, 0x5e, 0x53, 0xb1, 0x87, 0x34, 0x7a, 0x04,
0x0f, 0x24, 0x41, 0x4e, 0x21, 0x83, 0x5e, 0x8f, 0xf4, 0x7b, 0x3d, 0x4b, 0xcd, 0xa0, 0x6d, 0x28,
0x9b, 0xd6, 0xfb, 0x46, 0xc7, 0x6c, 0x11, 0x6c, 0x34, 0x3a, 0x5d, 0x35, 0x8b, 0x76, 0xa0, 0xba,
0xce, 0xcb, 0x71, 0x17, 0x31, 0xaf, 0x67, 0x99, 0x3d, 0x8b, 0xbc, 0x37, 0x70, 0xdf, 0xec, 0x59,
0x6a, 0x1e, 0xed, 0x01, 0x5a, 0x35, 0x9d, 0x75, 0x1b, 0x4d, 0xb5, 0x80, 0x1e, 0xc0, 0xf6, 0x2a,
0xfe, 0xd6, 0xf8, 0xa0, 0x02, 0xd2, 0x60, 0x57, 0x2e, 0x8c, 0x9c, 0x1a, 0x9d, 0xde, 0xf7, 0xa4,
0x6b, 0x5a, 0x66, 0x77, 0xd8, 0x55, 0x8b, 0x68, 0x17, 0xd4, 0xb6, 0x61, 0x10, 0xd3, 0xea, 0x0f,
0xdb, 0x6d, 0xb3, 0x69, 0x1a, 0xd6, 0x40, 0x2d, 0xc9, 0xc8, 0x9b, 0x3e, 0xbc, 0xcc, 0x27, 0x34,
0xcf, 0x1a, 0x96, 0x65, 0x74, 0x48, 0xcb, 0xec, 0x37, 0x4e, 0x3b, 0x46, 0x4b, 0xad, 0xa0, 0x03,
0x78, 0x34, 0x30, 0xba, 0x17, 0x3d, 0xdc, 0xc0, 0x1f, 0x48, 0x6c, 0x6f, 0x37, 0xcc, 0xce, 0x10,
0x1b, 0x6a, 0x15, 0x7d, 0x05, 0x07, 0xd8, 0x78, 0x37, 0x34, 0xb1, 0xd1, 0x22, 0x56, 0xaf, 0x65,
0x90, 0xb6, 0xd1, 0x18, 0x0c, 0xb1, 0x41, 0xba, 0x66, 0xbf, 0x6f, 0x5a, 0xdf, 0xa9, 0x2a, 0x7a,
0x06, 0x87, 0x0b, 0xca, 0xc2, 0xc1, 0x1a, 0x6b, 0x9b, 0x7f, 0x5f, 0x9c, 0x52, 0xcb, 0xf8, 0x61,
0x40, 0x2e, 0x0c, 0x03, 0xab, 0x08, 0xd5, 0x60, 0x6f, 0x19, 0x5e, 0x06, 0x88, 0x62, 0xef, 0x70,
0xdb, 0x85, 0x81, 0xbb, 0x0d, 0x8b, 0x27, 0x78, 0xc5, 0xb6, 0xcb, 0x97, 0xbd, 0xb4, 0xad, 0x2f,
0xfb, 0x01, 0x42, 0x50, 0x49, 0x64, 0xa5, 0xdd, 0xc0, 0xea, 0x1e, 0xda, 0x85, 0x6a, 0xbc, 0x82,
0x98, 0xf8, 0xcf, 0x1c, 0x7a, 0x08, 0x68, 0x68, 0x61, 0xa3, 0xd1, 0xe2, 0x1b, 0xb2, 0x30, 0xfc,
0x2b, 0x77, 0x9e, 0xce, 0x6f, 0xa9, 0x29, 0xfd, 0x1f, 0x29, 0x28, 0xaf, 0xd4, 0x25, 0x7a, 0x0c,
0x85, 0xd0, 0xb9, 0xf6, 0xc4, 0x35, 0x15, 0x89, 0xca, 0x12, 0x10, 0xb7, 0xfa, 0xc4, 0x76, 0x3c,
0xa9, 0x66, 0x52, 0xf7, 0x0b, 0x02, 0x11, 0x5a, 0xb6, 0x0f, 0xb9, 0xb8, 0x83, 0x48, 0x2d, 0x3a,
0x88, 0xec, 0x48, 0x76, 0x0e, 0x8f, 0xa1, 0xc0, 0x25, 0x33, 0x64, 0xf6, 0x74, 0x26, 0x4a, 0xbc,
0x8c, 0x97, 0x00, 0x7a, 0x0a, 0xe5, 0x29, 0x0d, 0x43, 0xfb, 0x9a, 0x12, 0x59, 0xa6, 0x20, 0x18,
0xa5, 0x08, 0x6c, 0x8b, 0x6a, 0x7d, 0x0a, 0xb1, 0x6c, 0x44, 0xa4, 0x8c, 0x24, 0x45, 0xa0, 0x24,
0xad, 0x2b, 0x36, 0xb3, 0x23, 0x35, 0x48, 0x2a, 0x36, 0xb3, 0xd1, 0x0b, 0xd8, 0x96, 0x92, 0xe3,
0x78, 0xce, 0x74, 0x3e, 0x95, 0xd2, 0x93, 0x13, 0xd2, 0x53, 0x15, 0xd2, 0x23, 0x71, 0xa1, 0x40,
0x8f, 0x20, 0x7f, 0x69, 0x87, 0x94, 0x5f, 0x16, 0x91, 0x34, 0xe4, 0xf8, 0xb8, 0x4d, 0x29, 0x37,
0xf1, 0x2b, 0x24, 0xe0, 0xa2, 0x27, 0x15, 0x21, 0x77, 0x45, 0x29, 0xe6, 0x7b, 0xb9, 0x88, 0x60,
0x7f, 0x5a, 0x46, 0x28, 0x26, 0x22, 0x48, 0x5c, 0x44, 0x78, 0x01, 0xdb, 0xf4, 0x13, 0x0b, 0x6c,
0xe2, 0xcf, 0xec, 0x9f, 0xe6, 0x94, 0x8c, 0x6d, 0x66, 0x8b, 0x96, 0xb4, 0x84, 0xab, 0xc2, 0xd0,
0x13, 0x78, 0xcb, 0x66, 0xb6, 0xfe, 0x18, 0x6a, 0x98, 0x86, 0x94, 0x75, 0x9d, 0x30, 0x74, 0x7c,
0xaf, 0xe9, 0x7b, 0x2c, 0xf0, 0xdd, 0xe8, 0xce, 0xd1, 0x0f, 0x60, 0x7f, 0xa3, 0x55, 0x5e, 0x1a,
0x7c, 0xf2, 0xbb, 0x39, 0x0d, 0x6e, 0x37, 0x4f, 0x7e, 0x07, 0xfb, 0x1b, 0xad, 0xd1, 0x8d, 0xf3,
0x12, 0x32, 0x33, 0xdb, 0x09, 0x42, 0x6d, 0x4b, 0xdc, 0xda, 0x7b, 0x2b, 0x4d, 0x82, 0x13, 0x9c,
0x39, 0x21, 0xf3, 0x83, 0x5b, 0x2c, 0x49, 0xe7, 0xe9, 0xbc, 0xa2, 0x6e, 0xe9, 0x7f, 0x52, 0xa0,
0x98, 0x30, 0xf2, 0x73, 0xe0, 0xf9, 0x63, 0x4a, 0xae, 0x02, 0x7f, 0x1a, 0x9f, 0xb0, 0x05, 0x80,
0x34, 0xc8, 0x89, 0x01, 0xf3, 0xa3, 0xe3, 0x15, 0x0f, 0xd1, 0x37, 0x90, 0x9b, 0x48, 0x17, 0x22,
0x4b, 0xc5, 0x93, 0x9d, 0xb5, 0xe8, 0x7c, 0x6f, 0x70, 0xcc, 0x39, 0x4f, 0xe7, 0x53, 0x6a, 0xfa,
0x3c, 0x9d, 0x4f, 0xab, 0x99, 0xf3, 0x74, 0x3e, 0xa3, 0x66, 0xcf, 0xd3, 0xf9, 0xac, 0x9a, 0xd3,
0xff, 0xad, 0x40, 0x3e, 0x66, 0xf3, 0xb5, 0x70, 0x89, 0x27, 0xfc, 0x64, 0x44, 0x0d, 0xc0, 0x12,
0x40, 0x3a, 0x94, 0xc4, 0x60, 0xb5, 0xaf, 0x58, 0xc1, 0xd0, 0x33, 0x28, 0x2f, 0xc6, 0x8b, 0xcb,
0x2b, 0x85, 0x57, 0x41, 0xee, 0x29, 0x9c, 0x8f, 0x46, 0x34, 0x0c, 0x65, 0xa8, 0x8c, 0xf4, 0x94,
0xc4, 0x50, 0x1d, 0xaa, 0xf1, 0x38, 0x0e, 0x98, 0x15, 0xb4, 0x75, 0x18, 0xbd, 0x00, 0x35, 0x09,
0x4d, 0x97, 0xed, 0xff, 0x1d, 0x5c, 0x6e, 0x83, 0x3e, 0x85, 0x87, 0x22, 0xad, 0x17, 0x81, 0x7f,
0x69, 0x5f, 0x3a, 0xae, 0xc3, 0x6e, 0xe3, 0x16, 0x85, 0x6f, 0x41, 0xe0, 0x4f, 0x89, 0x17, 0xdf,
0xf9, 0x25, 0xbc, 0x04, 0x78, 0x3a, 0x98, 0x2f, 0x6d, 0x51, 0x3a, 0xa2, 0x21, 0x6f, 0x3e, 0x16,
0xc1, 0x53, 0x22, 0xf8, 0x62, 0xac, 0xdf, 0x80, 0x76, 0x37, 0x5c, 0x74, 0x84, 0x0e, 0xa1, 0x38,
0x5b, 0xc2, 0x22, 0xa2, 0x82, 0x93, 0x50, 0x32, 0xd1, 0x5b, 0xbf, 0x9c, 0x68, 0xfd, 0xcf, 0x0a,
0x6c, 0x9f, 0xce, 0x1d, 0x77, 0xbc, 0xd2, 0x79, 0x25, 0x5f, 0x76, 0xca, 0xea, 0xcb, 0x6e, 0xd3,
0xb3, 0x6d, 0x6b, 0xe3, 0xb3, 0x6d, 0xd3, 0xd3, 0x28, 0x75, 0xef, 0xd3, 0xe8, 0x09, 0x14, 0x97,
0xaf, 0x22, 0xd9, 0xd8, 0x96, 0x30, 0x4c, 0xe2, 0x27, 0x51, 0xa8, 0xbf, 0x01, 0x94, 0x5c, 0x68,
0xb4, 0x21, 0x8b, 0x06, 0x50, 0xb9, 0xb7, 0x01, 0x7c, 0xf1, 0x77, 0x05, 0x4a, 0xc9, 0x2e, 0x1c,
0x95, 0xa1, 0x60, 0x5a, 0xa4, 0xdd, 0x31, 0xbf, 0x3b, 0x1b, 0xa8, 0x5f, 0xf0, 0x61, 0x7f, 0xd8,
0x6c, 0x1a, 0x46, 0xcb, 0x68, 0xa9, 0x0a, 0xbf, 0x1f, 0xb8, 0xd4, 0x1b, 0x2d, 0x32, 0x30, 0xbb,
0x46, 0x6f, 0xc8, 0x3b, 0x87, 0x1d, 0xa8, 0x46, 0x98, 0xd5, 0x23, 0xb8, 0x37, 0x1c, 0x18, 0x6a,
0x0a, 0xa9, 0x50, 0x8a, 0x40, 0x03, 0xe3, 0x1e, 0x56, 0xd3, 0xfc, 0xba, 0x8b, 0x90, 0xbb, 0x5d,
0x48, 0xdc, 0xa4, 0x64, 0x44, 0x97, 0x11, 0xb3, 0x96, 0x17, 0x34, 0x39, 0x6d, 0x74, 0x1a, 0x56,
0xd3, 0x50, 0xb3, 0x27, 0x7f, 0xcb, 0x40, 0x56, 0x7c, 0x41, 0x80, 0xce, 0xa0, 0x98, 0x78, 0x90,
0xa1, 0x83, 0xcf, 0x3e, 0xd4, 0x6a, 0xda, 0xe6, 0x77, 0xc7, 0x3c, 0x7c, 0xa5, 0xa0, 0x73, 0x28,
0x25, 0x9f, 0x3b, 0x28, 0xd9, 0x9b, 0x6e, 0x78, 0x07, 0x7d, 0xd6, 0xd7, 0x5b, 0x50, 0x8d, 0x90,
0x39, 0x53, 0xde, 0x8b, 0x46, 0xaf, 0x03, 0x54, 0x4b, 0xf0, 0xd7, 0x9e, 0x1c, 0xb5, 0xfd, 0x8d,
0xb6, 0x28, 0x85, 0x1d, 0xf9, 0x89, 0x51, 0x7f, 0x7e, 0xe7, 0x13, 0x57, 0x1f, 0x05, 0xb5, 0x2f,
0xef, 0x33, 0x47, 0xde, 0xc6, 0xb0, 0xb3, 0x41, 0xc0, 0xd1, 0xaf, 0x92, 0x2b, 0xb8, 0x57, 0xfe,
0x6b, 0xcf, 0x7f, 0x89, 0xb6, 0x8c, 0xb2, 0x41, 0xe9, 0x57, 0xa2, 0xdc, 0x7f, 0x4f, 0xac, 0x44,
0xf9, 0xdc, 0x85, 0xf1, 0x23, 0xa8, 0xeb, 0x4a, 0x80, 0xf4, 0xf5, 0xb9, 0x77, 0x55, 0xa9, 0xf6,
0xf4, 0xb3, 0x9c, 0xc8, 0xb9, 0x09, 0xb0, 0xac, 0x27, 0xf4, 0x38, 0x31, 0xe5, 0x8e, 0x1e, 0xd4,
0x0e, 0xee, 0xb1, 0x4a, 0x57, 0xa7, 0xbf, 0xfe, 0xe3, 0xf1, 0xb5, 0xc3, 0x26, 0xf3, 0xcb, 0xa3,
0x91, 0x3f, 0x3d, 0x76, 0x79, 0x47, 0xef, 0x39, 0xde, 0xb5, 0x47, 0xd9, 0xcf, 0x7e, 0x70, 0x73,
0xec, 0x7a, 0xe3, 0x63, 0x51, 0x96, 0xc7, 0x0b, 0x2f, 0x97, 0x59, 0xf1, 0xff, 0xa7, 0xdf, 0xfc,
0x2f, 0x00, 0x00, 0xff, 0xff, 0x42, 0x87, 0x8c, 0xeb, 0xaf, 0x12, 0x00, 0x00,
// 2102 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x58, 0xcb, 0x73, 0xdb, 0xc6,
0x19, 0x0f, 0xc4, 0xf7, 0xc7, 0x17, 0xb4, 0x92, 0x65, 0x98, 0xb2, 0x62, 0x05, 0x76, 0x1d, 0x8e,
0xc7, 0x91, 0x5c, 0xb5, 0xf1, 0x78, 0x7a, 0x68, 0x87, 0x22, 0xc1, 0x08, 0x32, 0x09, 0xca, 0x4b,
0xd2, 0x89, 0x9b, 0xc3, 0x0e, 0x44, 0xae, 0x44, 0x8c, 0x40, 0x80, 0x01, 0x96, 0x8e, 0xf5, 0x0f,
0xf4, 0xd4, 0xff, 0xa3, 0xbd, 0xb4, 0x97, 0x9e, 0xfb, 0xef, 0xb4, 0xf7, 0xde, 0x7a, 0xeb, 0xec,
0x2e, 0x40, 0x82, 0x14, 0xe5, 0xf4, 0x44, 0xec, 0xef, 0x7b, 0xed, 0xee, 0xb7, 0xdf, 0x8b, 0xb0,
0x17, 0xf8, 0x73, 0x46, 0x83, 0x60, 0x36, 0x3a, 0x96, 0x5f, 0x47, 0xb3, 0xc0, 0x67, 0x3e, 0x2a,
0x2c, 0xf0, 0x5a, 0x21, 0x98, 0x8d, 0x24, 0xaa, 0xff, 0x37, 0x03, 0xa8, 0x4f, 0xbd, 0xf1, 0x85,
0x7d, 0x3b, 0xa5, 0x1e, 0xc3, 0xf4, 0xa7, 0x39, 0x0d, 0x19, 0x42, 0x90, 0x1e, 0xd3, 0x90, 0x69,
0xca, 0xa1, 0x52, 0x2f, 0x61, 0xf1, 0x8d, 0x54, 0x48, 0xd9, 0x53, 0xa6, 0x6d, 0x1d, 0x2a, 0xf5,
0x14, 0xe6, 0x9f, 0xe8, 0x11, 0xe4, 0xed, 0x29, 0x23, 0xd3, 0xd0, 0x66, 0x5a, 0x49, 0xc0, 0x39,
0x7b, 0xca, 0xba, 0xa1, 0xcd, 0xd0, 0x57, 0x50, 0x9a, 0x49, 0x95, 0x64, 0x62, 0x87, 0x13, 0x2d,
0x25, 0x14, 0x15, 0x23, 0xec, 0xcc, 0x0e, 0x27, 0xa8, 0x0e, 0xea, 0x95, 0xe3, 0xd9, 0x2e, 0x19,
0xb9, 0xec, 0x23, 0x19, 0x53, 0x97, 0xd9, 0x5a, 0xfa, 0x50, 0xa9, 0x67, 0x70, 0x45, 0xe0, 0x4d,
0x97, 0x7d, 0x6c, 0x71, 0x14, 0x7d, 0x0d, 0xd5, 0x58, 0x59, 0x20, 0x37, 0xa8, 0x65, 0x0e, 0x95,
0x7a, 0x01, 0x57, 0x66, 0xab, 0xdb, 0xfe, 0x1a, 0xaa, 0xcc, 0x99, 0x52, 0x7f, 0xce, 0x48, 0x48,
0x47, 0xbe, 0x37, 0x0e, 0xb5, 0xac, 0xd4, 0x18, 0xc1, 0x7d, 0x89, 0x22, 0x1d, 0xca, 0x57, 0x94,
0x12, 0xd7, 0x99, 0x3a, 0x8c, 0xf0, 0xed, 0xe7, 0xc4, 0xf6, 0x8b, 0x57, 0x94, 0x76, 0x38, 0xd6,
0xb7, 0x19, 0x7a, 0x06, 0x95, 0x25, 0x8f, 0x38, 0x63, 0x59, 0x30, 0x95, 0x62, 0x26, 0x71, 0xd0,
0x97, 0xa0, 0xfa, 0x73, 0x76, 0xed, 0x3b, 0xde, 0x35, 0x19, 0x4d, 0x6c, 0x8f, 0x38, 0x63, 0x2d,
0x7f, 0xa8, 0xd4, 0xd3, 0xa7, 0x5b, 0xaf, 0x14, 0x5c, 0x89, 0x69, 0xcd, 0x89, 0xed, 0x99, 0x63,
0xf4, 0x1c, 0xaa, 0xae, 0x1d, 0x32, 0x32, 0xf1, 0x67, 0x64, 0x36, 0xbf, 0xbc, 0xa1, 0xb7, 0x5a,
0x45, 0xdc, 0x4c, 0x99, 0xc3, 0x67, 0xfe, 0xec, 0x42, 0x80, 0xe8, 0x00, 0x40, 0xdc, 0x8a, 0x30,
0xae, 0x15, 0xc4, 0x19, 0x0a, 0x1c, 0x11, 0x86, 0xd1, 0x09, 0x14, 0x85, 0x37, 0xc9, 0xc4, 0xf1,
0x58, 0xa8, 0xc1, 0x61, 0xaa, 0x5e, 0x3c, 0x51, 0x8f, 0x5c, 0x8f, 0x3b, 0x16, 0x73, 0xca, 0x99,
0xe3, 0x31, 0x9c, 0x64, 0x42, 0x63, 0xd8, 0xe1, 0x6e, 0x24, 0xa3, 0x79, 0xc8, 0xfc, 0x29, 0x09,
0xe8, 0xc8, 0x0f, 0xc6, 0xa1, 0x56, 0x14, 0xb2, 0xbf, 0x3d, 0x5a, 0xbc, 0x8e, 0xa3, 0xbb, 0xcf,
0xe1, 0xa8, 0x45, 0x43, 0xd6, 0x14, 0x72, 0x58, 0x8a, 0x19, 0x1e, 0x0b, 0x6e, 0xf1, 0xf6, 0x78,
0x1d, 0x47, 0x2f, 0x01, 0xd9, 0xae, 0xeb, 0xff, 0x4c, 0x42, 0xea, 0x5e, 0x91, 0xc8, 0x3d, 0x5a,
0xf5, 0x50, 0xa9, 0xe7, 0xb1, 0x2a, 0x28, 0x7d, 0xea, 0x5e, 0x45, 0xea, 0xd1, 0x6b, 0x28, 0x8b,
0x3d, 0x5d, 0x51, 0x9b, 0xcd, 0x03, 0x1a, 0x6a, 0xea, 0x61, 0xaa, 0x5e, 0x39, 0xd9, 0x8e, 0x4e,
0xd2, 0x96, 0xf0, 0xa9, 0xc3, 0x70, 0x89, 0xf3, 0x45, 0xeb, 0xb0, 0xd6, 0x82, 0xbd, 0xcd, 0x5b,
0xe2, 0x8f, 0x94, 0x5f, 0x2a, 0x7f, 0xb7, 0x69, 0xcc, 0x3f, 0xd1, 0x2e, 0x64, 0x3e, 0xda, 0xee,
0x9c, 0x8a, 0x87, 0x5b, 0xc2, 0x72, 0xf1, 0xbb, 0xad, 0x37, 0x8a, 0xfe, 0x06, 0x76, 0x06, 0x81,
0x3d, 0xba, 0x59, 0x7b, 0xfb, 0xeb, 0x4f, 0x57, 0xb9, 0xf3, 0x74, 0xf5, 0xbf, 0x2a, 0x50, 0x8e,
0xa4, 0xfa, 0xcc, 0x66, 0xf3, 0x10, 0x7d, 0x03, 0x99, 0x90, 0xd9, 0x8c, 0x0a, 0xee, 0xca, 0xc9,
0xc3, 0xc4, 0x7d, 0x26, 0x18, 0x29, 0x96, 0x5c, 0xa8, 0x06, 0xf9, 0x59, 0x40, 0x9d, 0xa9, 0x7d,
0x1d, 0xef, 0x6b, 0xb1, 0x46, 0x3a, 0x64, 0x84, 0xb0, 0x88, 0x99, 0xe2, 0x49, 0x29, 0xe9, 0x56,
0x2c, 0x49, 0xa8, 0x0e, 0x99, 0x09, 0x73, 0x47, 0xa1, 0x96, 0x16, 0xee, 0x43, 0x11, 0xcf, 0xd9,
0xa0, 0xd3, 0x6c, 0x30, 0x46, 0xa7, 0x33, 0x86, 0x25, 0x83, 0xfe, 0x7b, 0xa8, 0x0a, 0xc9, 0x36,
0xa5, 0x9f, 0x0b, 0xee, 0x87, 0xc0, 0x43, 0x57, 0x84, 0x82, 0x0c, 0xf0, 0xac, 0x3d, 0xe5, 0x51,
0xa0, 0x8f, 0x41, 0x5d, 0xca, 0x87, 0x33, 0xdf, 0x0b, 0xb9, 0x75, 0x95, 0x6f, 0x83, 0x3f, 0x79,
0x1e, 0x21, 0x22, 0x36, 0x14, 0x21, 0x55, 0x89, 0xf0, 0x36, 0xa5, 0x22, 0x3a, 0x9e, 0xcb, 0x80,
0x24, 0xae, 0x3f, 0xba, 0xe1, 0x21, 0x6e, 0xdf, 0x46, 0xea, 0xcb, 0x1c, 0xee, 0xf8, 0xa3, 0x9b,
0x16, 0x07, 0xf5, 0x1f, 0x65, 0x16, 0x1a, 0xf8, 0xf2, 0x94, 0xff, 0xb7, 0x27, 0x96, 0x97, 0xb5,
0x75, 0xef, 0x65, 0xe9, 0x04, 0x76, 0x56, 0x94, 0x47, 0xa7, 0x48, 0xfa, 0x40, 0x59, 0xf3, 0xc1,
0x4b, 0xc8, 0x5d, 0xd9, 0x8e, 0x3b, 0x0f, 0x62, 0xc5, 0x28, 0xe1, 0xd0, 0xb6, 0xa4, 0xe0, 0x98,
0x45, 0xff, 0x53, 0x1e, 0x72, 0x11, 0x88, 0x4e, 0x20, 0x3d, 0xf2, 0xc7, 0xf1, 0x3b, 0xf8, 0xf2,
0xae, 0x58, 0xfc, 0xdb, 0xf4, 0xc7, 0x14, 0x0b, 0x5e, 0xf4, 0x07, 0xa8, 0xf0, 0xd4, 0xe1, 0x51,
0x97, 0xcc, 0x67, 0x63, 0x7b, 0xe1, 0x7a, 0x2d, 0x21, 0xdd, 0x94, 0x0c, 0x43, 0x41, 0xc7, 0xe5,
0x51, 0x72, 0x89, 0xf6, 0xa1, 0xc0, 0xbd, 0x2d, 0x3d, 0x91, 0x16, 0x6f, 0x3f, 0xcf, 0x01, 0xe1,
0x03, 0x1d, 0xca, 0xbe, 0xe7, 0xf8, 0x1e, 0x09, 0x27, 0x36, 0x39, 0xf9, 0xf6, 0xb5, 0xc8, 0x9d,
0x25, 0x5c, 0x14, 0x60, 0x7f, 0x62, 0x9f, 0x7c, 0xfb, 0x1a, 0x3d, 0x81, 0xa2, 0xc8, 0x37, 0xf4,
0xd3, 0xcc, 0x09, 0x6e, 0x45, 0xd2, 0x2c, 0x63, 0x91, 0x82, 0x0c, 0x81, 0xf0, 0x28, 0xba, 0x72,
0xed, 0xeb, 0x50, 0x24, 0xca, 0x32, 0x96, 0x0b, 0xf4, 0x0a, 0x76, 0xa3, 0x3b, 0x20, 0xa1, 0x3f,
0x0f, 0x46, 0x94, 0x38, 0xde, 0x98, 0x7e, 0x12, 0x09, 0xb0, 0x8c, 0x51, 0x44, 0xeb, 0x0b, 0x92,
0xc9, 0x29, 0x68, 0x0f, 0xb2, 0x13, 0xea, 0x5c, 0x4f, 0x64, 0x52, 0x2b, 0xe3, 0x68, 0xa5, 0xff,
0x33, 0x03, 0xc5, 0xc4, 0xc5, 0xa0, 0x12, 0xe4, 0xb1, 0xd1, 0x37, 0xf0, 0x7b, 0xa3, 0xa5, 0x7e,
0x81, 0xea, 0xf0, 0xcc, 0xb4, 0x9a, 0x3d, 0x8c, 0x8d, 0xe6, 0x80, 0xf4, 0x30, 0x19, 0x5a, 0x6f,
0xad, 0xde, 0xf7, 0x16, 0xb9, 0x68, 0x7c, 0xe8, 0x1a, 0xd6, 0x80, 0xb4, 0x8c, 0x41, 0xc3, 0xec,
0xf4, 0x55, 0x05, 0x3d, 0x06, 0x6d, 0xc9, 0x19, 0x93, 0x1b, 0xdd, 0xde, 0xd0, 0x1a, 0xa8, 0x5b,
0xe8, 0x09, 0xec, 0xb7, 0x4d, 0xab, 0xd1, 0x21, 0x4b, 0x9e, 0x66, 0x67, 0xf0, 0x9e, 0x18, 0x3f,
0x5c, 0x98, 0xf8, 0x83, 0x9a, 0xda, 0xc4, 0xc0, 0x63, 0x2a, 0xd6, 0x90, 0x46, 0x8f, 0xe0, 0x81,
0x64, 0x90, 0x22, 0x64, 0xd0, 0xeb, 0x91, 0x7e, 0xaf, 0x67, 0xa9, 0x19, 0xb4, 0x0d, 0x65, 0xd3,
0x7a, 0xdf, 0xe8, 0x98, 0x2d, 0x82, 0x8d, 0x46, 0xa7, 0xab, 0x66, 0xd1, 0x0e, 0x54, 0xd7, 0xf9,
0x72, 0x5c, 0x45, 0xcc, 0xd7, 0xb3, 0xcc, 0x9e, 0x45, 0xde, 0x1b, 0xb8, 0x6f, 0xf6, 0x2c, 0x35,
0x8f, 0xf6, 0x00, 0xad, 0x92, 0xce, 0xba, 0x8d, 0xa6, 0x5a, 0x40, 0x0f, 0x60, 0x7b, 0x15, 0x7f,
0x6b, 0x7c, 0x50, 0x01, 0x69, 0xb0, 0x2b, 0x37, 0x46, 0x4e, 0x8d, 0x4e, 0xef, 0x7b, 0xd2, 0x35,
0x2d, 0xb3, 0x3b, 0xec, 0xaa, 0x45, 0xb4, 0x0b, 0x6a, 0xdb, 0x30, 0x88, 0x69, 0xf5, 0x87, 0xed,
0xb6, 0xd9, 0x34, 0x0d, 0x6b, 0xa0, 0x96, 0xa4, 0xe5, 0x4d, 0x07, 0x2f, 0x73, 0x81, 0xe6, 0x59,
0xc3, 0xb2, 0x8c, 0x0e, 0x69, 0x99, 0xfd, 0xc6, 0x69, 0xc7, 0x68, 0xa9, 0x15, 0x74, 0x00, 0x8f,
0x06, 0x46, 0xf7, 0xa2, 0x87, 0x1b, 0xf8, 0x03, 0x89, 0xe9, 0xed, 0x86, 0xd9, 0x19, 0x62, 0x43,
0xad, 0xa2, 0xaf, 0xe0, 0x00, 0x1b, 0xef, 0x86, 0x26, 0x36, 0x5a, 0xc4, 0xea, 0xb5, 0x0c, 0xd2,
0x36, 0x1a, 0x83, 0x21, 0x36, 0x48, 0xd7, 0xec, 0xf7, 0x4d, 0xeb, 0x3b, 0x55, 0x45, 0xcf, 0xe0,
0x70, 0xc1, 0xb2, 0x50, 0xb0, 0xc6, 0xb5, 0xcd, 0xcf, 0x17, 0xbb, 0xd4, 0x32, 0x7e, 0x18, 0x90,
0x0b, 0xc3, 0xc0, 0x2a, 0x42, 0x35, 0xd8, 0x5b, 0x9a, 0x97, 0x06, 0x22, 0xdb, 0x3b, 0x9c, 0x76,
0x61, 0xe0, 0x6e, 0xc3, 0xe2, 0x0e, 0x5e, 0xa1, 0xed, 0xf2, 0x6d, 0x2f, 0x69, 0xeb, 0xdb, 0x7e,
0x80, 0x10, 0x54, 0x12, 0x5e, 0x69, 0x37, 0xb0, 0xba, 0x87, 0xaa, 0x50, 0xec, 0x5e, 0x5c, 0x90,
0x81, 0xd9, 0x35, 0x7a, 0xc3, 0x81, 0xfa, 0x10, 0xed, 0x42, 0x35, 0xde, 0x52, 0x2c, 0xf9, 0xaf,
0x1c, 0x7a, 0x08, 0x68, 0x68, 0x61, 0xa3, 0xd1, 0xe2, 0x37, 0xb4, 0x20, 0xfc, 0x3b, 0x77, 0x9e,
0xce, 0x6f, 0xa9, 0x29, 0xfd, 0x1f, 0x29, 0x28, 0xaf, 0x04, 0x2a, 0x7a, 0x0c, 0x85, 0xd0, 0xb9,
0xf6, 0x44, 0xdd, 0x8a, 0xb2, 0xcc, 0x12, 0x10, 0x65, 0x7e, 0x62, 0x3b, 0x9e, 0x4c, 0x6f, 0xb2,
0x10, 0x14, 0x04, 0x22, 0x92, 0xdb, 0x3e, 0xe4, 0xe2, 0x96, 0x22, 0xb5, 0x68, 0x29, 0xb2, 0x23,
0xd9, 0x4a, 0x3c, 0x86, 0x02, 0xcf, 0xa1, 0x21, 0xb3, 0xa7, 0x33, 0x11, 0xf3, 0x65, 0xbc, 0x04,
0xd0, 0x53, 0x28, 0x4f, 0x69, 0x18, 0xda, 0xd7, 0x94, 0xc8, 0xb8, 0x05, 0xc1, 0x51, 0x8a, 0xc0,
0xb6, 0x08, 0xdf, 0xa7, 0x10, 0xe7, 0x91, 0x88, 0x29, 0x23, 0x99, 0x22, 0x50, 0x32, 0xad, 0xa7,
0x70, 0x66, 0x47, 0xe9, 0x21, 0x99, 0xc2, 0x99, 0x8d, 0x5e, 0xc0, 0xb6, 0xcc, 0x41, 0x8e, 0xe7,
0x4c, 0xe7, 0x53, 0x99, 0x8b, 0x72, 0x22, 0x17, 0x55, 0x45, 0x2e, 0x92, 0xb8, 0x48, 0x49, 0x8f,
0x20, 0x7f, 0x69, 0x87, 0x94, 0x57, 0x8f, 0x28, 0x57, 0xe4, 0xf8, 0xba, 0x4d, 0x29, 0x27, 0xf1,
0x9a, 0x12, 0xf0, 0x2c, 0x28, 0x53, 0x44, 0xee, 0x8a, 0x52, 0xcc, 0xef, 0x72, 0x61, 0xc1, 0xfe,
0xb4, 0xb4, 0x50, 0x4c, 0x58, 0x90, 0xb8, 0xb0, 0xf0, 0x02, 0xb6, 0xe9, 0x27, 0x16, 0xd8, 0xc4,
0x9f, 0xd9, 0x3f, 0xcd, 0x29, 0x19, 0xdb, 0xcc, 0x16, 0x3d, 0x6a, 0x09, 0x57, 0x05, 0xa1, 0x27,
0xf0, 0x96, 0xcd, 0x6c, 0xfd, 0x31, 0xd4, 0x30, 0x0d, 0x29, 0xeb, 0x3a, 0x61, 0xe8, 0xf8, 0x5e,
0xd3, 0xf7, 0x58, 0xe0, 0xbb, 0x51, 0x11, 0xd2, 0x0f, 0x60, 0x7f, 0x23, 0x55, 0x56, 0x11, 0x2e,
0xfc, 0x6e, 0x4e, 0x83, 0xdb, 0xcd, 0xc2, 0xef, 0x60, 0x7f, 0x23, 0x35, 0x2a, 0x41, 0x2f, 0x21,
0x33, 0xb3, 0x9d, 0x20, 0xd4, 0xb6, 0x44, 0x19, 0xdf, 0x5b, 0xe9, 0x1a, 0x9c, 0xe0, 0xcc, 0x09,
0x99, 0x1f, 0xdc, 0x62, 0xc9, 0x74, 0x9e, 0xce, 0x2b, 0xea, 0x96, 0xfe, 0x67, 0x05, 0x8a, 0x09,
0x22, 0x7f, 0x07, 0x9e, 0x3f, 0xa6, 0xe4, 0x2a, 0xf0, 0xa7, 0xf1, 0x0b, 0x5b, 0x00, 0x48, 0x83,
0x9c, 0x58, 0x30, 0x3f, 0x7a, 0x5e, 0xf1, 0x12, 0x7d, 0x03, 0xb9, 0x89, 0x54, 0x21, 0xbc, 0x54,
0x3c, 0xd9, 0x59, 0xb3, 0xce, 0xef, 0x06, 0xc7, 0x3c, 0xe7, 0xe9, 0x7c, 0x4a, 0x4d, 0x9f, 0xa7,
0xf3, 0x69, 0x35, 0x73, 0x9e, 0xce, 0x67, 0xd4, 0xec, 0x79, 0x3a, 0x9f, 0x55, 0x73, 0xfa, 0x7f,
0x14, 0xc8, 0xc7, 0xdc, 0x7c, 0x2f, 0x3c, 0xe7, 0x13, 0xfe, 0x32, 0xa2, 0x8e, 0x60, 0x09, 0x20,
0x1d, 0x4a, 0x62, 0xb1, 0xda, 0x68, 0xac, 0x60, 0xe8, 0x19, 0x94, 0x17, 0xeb, 0x45, 0x35, 0x4b,
0xe1, 0x55, 0x90, 0x6b, 0x0a, 0xe7, 0xa3, 0x11, 0x0d, 0x43, 0x69, 0x2a, 0x23, 0x35, 0x25, 0x31,
0x54, 0x87, 0x6a, 0xbc, 0x8e, 0x0d, 0x66, 0x05, 0xdb, 0x3a, 0x8c, 0x5e, 0x80, 0x9a, 0x84, 0xa6,
0xcb, 0x79, 0xe0, 0x0e, 0x2e, 0xaf, 0x41, 0x9f, 0xc2, 0x43, 0xe1, 0xd6, 0x8b, 0xc0, 0xbf, 0xb4,
0x2f, 0x1d, 0xd7, 0x61, 0xb7, 0x71, 0xcf, 0xc2, 0xaf, 0x20, 0xf0, 0xa7, 0xc4, 0x8b, 0x9b, 0x80,
0x12, 0x5e, 0x02, 0xdc, 0x1d, 0xcc, 0x97, 0xb4, 0xc8, 0x1d, 0xd1, 0x92, 0x77, 0x23, 0x0b, 0xe3,
0x29, 0x61, 0x7c, 0xb1, 0xd6, 0x6f, 0x40, 0xbb, 0x6b, 0x2e, 0x7a, 0x42, 0x87, 0x50, 0x9c, 0x2d,
0x61, 0x61, 0x51, 0xc1, 0x49, 0x28, 0xe9, 0xe8, 0xad, 0x5f, 0x76, 0xb4, 0xfe, 0x17, 0x05, 0xb6,
0x4f, 0xe7, 0x8e, 0x3b, 0x5e, 0x69, 0xc5, 0x92, 0xa3, 0x9e, 0xb2, 0x3a, 0xea, 0x6d, 0x9a, 0xe3,
0xb6, 0x36, 0xce, 0x71, 0x9b, 0x66, 0xa5, 0xd4, 0xbd, 0xb3, 0xd2, 0x13, 0x28, 0x2e, 0xc7, 0x24,
0xd9, 0xe9, 0x96, 0x30, 0x4c, 0xe2, 0x19, 0x29, 0xd4, 0xdf, 0x00, 0x4a, 0x6e, 0x34, 0xba, 0x90,
0x45, 0x47, 0xa8, 0xdc, 0xdb, 0x11, 0xbe, 0xf8, 0xbb, 0x02, 0xa5, 0x64, 0x5b, 0x8e, 0xca, 0x50,
0x30, 0x2d, 0xd2, 0xee, 0x98, 0xdf, 0x9d, 0x0d, 0xd4, 0x2f, 0xf8, 0xb2, 0x3f, 0x6c, 0x36, 0x0d,
0xa3, 0x65, 0xb4, 0x54, 0x85, 0x17, 0x0c, 0x9e, 0xea, 0x8d, 0xd6, 0xa2, 0x3e, 0x6c, 0xf1, 0xd2,
0x1e, 0x61, 0x56, 0x8f, 0xe0, 0xde, 0x70, 0x60, 0xa8, 0x29, 0xa4, 0x42, 0x29, 0x02, 0x0d, 0x8c,
0x7b, 0x58, 0x4d, 0xf3, 0xfa, 0x17, 0x21, 0x77, 0xdb, 0x92, 0xb8, 0x6b, 0xc9, 0x88, 0xb6, 0x23,
0xe6, 0x5a, 0x56, 0x6c, 0x72, 0xda, 0xe8, 0x34, 0xac, 0xa6, 0xa1, 0x66, 0x4f, 0xfe, 0x96, 0x81,
0xac, 0x38, 0x41, 0x80, 0xce, 0xa0, 0x98, 0x98, 0xd0, 0xd0, 0xc1, 0x67, 0x27, 0xb7, 0x9a, 0xb6,
0x79, 0x10, 0x99, 0x87, 0xaf, 0x14, 0x74, 0x0e, 0xa5, 0xe4, 0xfc, 0x83, 0x92, 0xcd, 0xea, 0x86,
0xc1, 0xe8, 0xb3, 0xba, 0xde, 0x82, 0x6a, 0x84, 0xcc, 0x99, 0xf2, 0xe6, 0x34, 0x1a, 0x17, 0x50,
0x2d, 0xc1, 0xbf, 0x36, 0x83, 0xd4, 0xf6, 0x37, 0xd2, 0x22, 0x17, 0x76, 0xe4, 0x11, 0xa3, 0x86,
0xfd, 0xce, 0x11, 0x57, 0xa7, 0x84, 0xda, 0x97, 0xf7, 0x91, 0x23, 0x6d, 0x63, 0xd8, 0xd9, 0x90,
0xc0, 0xd1, 0xaf, 0x92, 0x3b, 0xb8, 0x37, 0xfd, 0xd7, 0x9e, 0xff, 0x12, 0xdb, 0xd2, 0xca, 0x86,
0x4c, 0xbf, 0x62, 0xe5, 0xfe, 0x3a, 0xb1, 0x62, 0xe5, 0x73, 0x05, 0xe3, 0x47, 0x50, 0xd7, 0x33,
0x01, 0xd2, 0xd7, 0x65, 0xef, 0x66, 0xa5, 0xda, 0xd3, 0xcf, 0xf2, 0x44, 0xca, 0x4d, 0x80, 0x65,
0x3c, 0xa1, 0xc7, 0x09, 0x91, 0x3b, 0xf9, 0xa0, 0x76, 0x70, 0x0f, 0x55, 0xaa, 0x3a, 0xfd, 0xf5,
0x1f, 0x8f, 0xaf, 0x1d, 0x36, 0x99, 0x5f, 0x1e, 0x8d, 0xfc, 0xe9, 0xb1, 0xcb, 0x5b, 0x7c, 0xcf,
0xf1, 0xae, 0x3d, 0xca, 0x7e, 0xf6, 0x83, 0x9b, 0x63, 0xd7, 0x1b, 0x1f, 0x8b, 0xb0, 0x3c, 0x5e,
0x68, 0xb9, 0xcc, 0x8a, 0x3f, 0xa4, 0x7e, 0xf3, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x10, 0xf6,
0xab, 0x74, 0xc0, 0x12, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.

View File

@ -109,7 +109,9 @@ message SendPaymentRequest {
/**
Features assumed to be supported by the final node. All transitive feature
depdencies must also be set properly. For a given feature bit pair, either
optional or remote may be set, but not both.
optional or remote may be set, but not both. If this field is nil or empty,
the router will try to load destination features from the graph as a
fallback.
*/
repeated lnrpc.FeatureBit dest_features = 16;
}
@ -254,6 +256,7 @@ message Failure {
PERMANENT_NODE_FAILURE = 20;
PERMANENT_CHANNEL_FAILURE = 21;
EXPIRY_TOO_FAR = 22;
MPP_TIMEOUT = 23;
/**
The error source is known, but the failure itself couldn't be decoded.

View File

@ -710,7 +710,15 @@ func unmarshallHopHint(rpcHint *lnrpc.HopHint) (zpay32.HopHint, error) {
// UnmarshalFeatures converts a list of uint32's into a valid feature vector.
// This method checks that feature bit pairs aren't assigned toegether, and
// validates transitive depdencies.
func UnmarshalFeatures(rpcFeatures []lnrpc.FeatureBit) (*lnwire.FeatureVector, error) {
func UnmarshalFeatures(
rpcFeatures []lnrpc.FeatureBit) (*lnwire.FeatureVector, error) {
// If no destination features are specified we'll return nil to signal
// that the router should try to use the graph as a fallback.
if rpcFeatures == nil {
return nil, nil
}
raw := lnwire.NewRawFeatureVector()
for _, bit := range rpcFeatures {
err := raw.SafeSet(lnwire.FeatureBit(bit))

View File

@ -415,6 +415,9 @@ func marshallError(sendError error) (*Failure, error) {
case *lnwire.FailPermanentChannelFailure:
response.Code = Failure_PERMANENT_CHANNEL_FAILURE
case *lnwire.FailMPPTimeout:
response.Code = Failure_MPP_TIMEOUT
case nil:
response.Code = Failure_UNKNOWN_FAILURE

View File

@ -1229,7 +1229,9 @@ type SendRequest struct {
//*
//Features assumed to be supported by the final node. All transitive feature
//depdencies must also be set properly. For a given feature bit pair, either
//optional or remote may be set, but not both.
//optional or remote may be set, but not both. If this field is nil or empty,
//the router will try to load destination features from the graph as a
//fallback.
DestFeatures []FeatureBit `protobuf:"varint,15,rep,packed,name=dest_features,json=destFeatures,proto3,enum=lnrpc.FeatureBit" json:"dest_features,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`

View File

@ -971,7 +971,9 @@ message SendRequest {
/**
Features assumed to be supported by the final node. All transitive feature
depdencies must also be set properly. For a given feature bit pair, either
optional or remote may be set, but not both.
optional or remote may be set, but not both. If this field is nil or empty,
the router will try to load destination features from the graph as a
fallback.
*/
repeated FeatureBit dest_features = 15;
}

View File

@ -3874,7 +3874,7 @@
"items": {
"$ref": "#/definitions/lnrpcFeatureBit"
},
"description": "*\nFeatures assumed to be supported by the final node. All transitive feature\ndepdencies must also be set properly. For a given feature bit pair, either\noptional or remote may be set, but not both."
"description": "*\nFeatures assumed to be supported by the final node. All transitive feature\ndepdencies must also be set properly. For a given feature bit pair, either\noptional or remote may be set, but not both. If this field is nil or empty,\nthe router will try to load destination features from the graph as a\nfallback."
}
}
},

View File

@ -80,6 +80,7 @@ const (
CodeFinalIncorrectHtlcAmount FailCode = 19
CodeExpiryTooFar FailCode = 21
CodeInvalidOnionPayload = FlagPerm | 22
CodeMPPTimeout FailCode = 23
)
// String returns the string representation of the failure code.
@ -154,6 +155,9 @@ func (c FailCode) String() string {
case CodeInvalidOnionPayload:
return "InvalidOnionPayload"
case CodeMPPTimeout:
return "MPPTimeout"
default:
return "<unknown>"
}
@ -1182,6 +1186,26 @@ func (f *InvalidOnionPayload) Encode(w io.Writer, pver uint32) error {
return WriteElements(w, f.Offset)
}
// FailMPPTimeout is returned if the complete amount for a multi part payment
// was not received within a reasonable time.
//
// NOTE: May only be returned by the final node in the path.
type FailMPPTimeout struct{}
// Code returns the failure unique code.
//
// NOTE: Part of the FailureMessage interface.
func (f *FailMPPTimeout) Code() FailCode {
return CodeMPPTimeout
}
// Returns a human readable string describing the target FailureMessage.
//
// NOTE: Implements the error interface.
func (f *FailMPPTimeout) Error() string {
return f.Code().String()
}
// DecodeFailure decodes, validates, and parses the lnwire onion failure, for
// the provided protocol version.
func DecodeFailure(r io.Reader, pver uint32) (FailureMessage, error) {
@ -1366,6 +1390,9 @@ func makeEmptyOnionError(code FailCode) (FailureMessage, error) {
case CodeInvalidOnionPayload:
return &InvalidOnionPayload{}, nil
case CodeMPPTimeout:
return &FailMPPTimeout{}, nil
default:
return nil, errors.Errorf("unknown error code: %v", code)
}

View File

@ -38,6 +38,7 @@ var onionFailures = []FailureMessage{
&FailUnknownNextPeer{},
&FailIncorrectPaymentAmount{},
&FailFinalExpiryTooSoon{},
&FailMPPTimeout{},
NewFailIncorrectDetails(99, 100),
NewInvalidOnionVersion(testOnionHash),

View File

@ -98,6 +98,11 @@ func (r *MPP) Record() tlv.Record {
)
}
// PayloadSize returns the size this record takes up in encoded form.
func (r *MPP) PayloadSize() uint64 {
return 32 + tlv.SizeTUint64(uint64(r.totalMsat))
}
// String returns a human-readable representation of the mpp payload field.
func (r *MPP) String() string {
return fmt.Sprintf("total=%v, addr=%x", r.totalMsat, r.paymentAddr)

View File

@ -24,9 +24,10 @@ type nodeWithDist struct {
// amount that includes also the fees for subsequent hops.
amountToReceive lnwire.MilliSatoshi
// incomingCltv is the expected cltv value for the incoming htlc of this
// node. This value does not include the final cltv.
incomingCltv uint32
// incomingCltv is the expected absolute expiry height for the incoming
// htlc of this node. This value should already include the final cltv
// delta.
incomingCltv int32
// probability is the probability that from this node onward the route
// is successful.
@ -39,6 +40,10 @@ type nodeWithDist struct {
// nextHop is the edge this route comes from.
nextHop *channeldb.ChannelEdgePolicy
// routingInfoSize is the total size requirement for the payloads field
// in the onion packet from this hop towards the final destination.
routingInfoSize uint64
}
// distanceHeap is a min-distance heap that's used within our path finding

View File

@ -16,8 +16,12 @@ import (
const testMaxRecords = 2
// TestMissionControlStore tests the recording of payment failure events
// in mission control. It tests encoding and decoding of differing lnwire
// failures (FailIncorrectDetails and FailMppTimeout), pruning of results
// and idempotent writes.
func TestMissionControlStore(t *testing.T) {
// Set time zone explictly to keep test deterministic.
// Set time zone explicitly to keep test deterministic.
time.Local = time.UTC
file, err := ioutil.TempFile("", "*.db")
@ -115,11 +119,12 @@ func TestMissionControlStore(t *testing.T) {
t.Fatal(err)
}
// Add a newer result.
// Add a newer result which failed due to mpp timeout.
result3 := result1
result3.timeReply = result1.timeReply.Add(2 * time.Hour)
result3.timeFwd = result1.timeReply.Add(2 * time.Hour)
result3.id = 3
result3.failure = &lnwire.FailMPPTimeout{}
err = store.AddResult(&result3)
if err != nil {

View File

@ -7,8 +7,8 @@ import (
"math"
"time"
"github.com/btcsuite/btcd/btcec"
"github.com/coreos/bbolt"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/feature"
"github.com/lightningnetwork/lnd/lnwire"
@ -17,12 +17,6 @@ import (
)
const (
// HopLimit is the maximum number hops that is permissible as a route.
// Any potential paths found that lie above this limit will be rejected
// with an error. This value is computed using the current fixed-size
// packet length of the Sphinx construction.
HopLimit = 20
// infinity is used as a starting distance in our shortest path search.
infinity = math.MaxInt64
@ -48,7 +42,8 @@ const (
// pathFinder defines the interface of a path finding algorithm.
type pathFinder = func(g *graphParams, r *RestrictParams,
cfg *PathFindingConfig, source, target route.Vertex,
amt lnwire.MilliSatoshi) ([]*channeldb.ChannelEdgePolicy, error)
amt lnwire.MilliSatoshi, finalHtlcExpiry int32) (
[]*channeldb.ChannelEdgePolicy, error)
var (
// DefaultPaymentAttemptPenalty is the virtual cost in path finding weight
@ -79,10 +74,6 @@ var (
// not exist in the graph.
errNoPathFound = errors.New("unable to find a path to destination")
// errMaxHopsExceeded is returned when a candidate path is found, but
// the length of that path exceeds HopLimit.
errMaxHopsExceeded = errors.New("potential path has too many hops")
// errInsufficientLocalBalance is returned when none of the local
// channels have enough balance for the payment.
errInsufficientBalance = errors.New("insufficient local balance")
@ -412,7 +403,7 @@ func getMaxOutgoingAmt(node route.Vertex, outgoingChan *uint64,
// that need to be paid along the path and accurately check the amount
// to forward at every node against the available bandwidth.
func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
source, target route.Vertex, amt lnwire.MilliSatoshi) (
source, target route.Vertex, amt lnwire.MilliSatoshi, finalHtlcExpiry int32) (
[]*channeldb.ChannelEdgePolicy, error) {
// Pathfinding can be a significant portion of the total payment
@ -447,12 +438,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
// we have for the target node from our graph.
features := r.DestFeatures
if features == nil {
targetKey, err := btcec.ParsePubKey(target[:], btcec.S256())
if err != nil {
return nil, err
}
targetNode, err := g.graph.FetchLightningNode(targetKey)
targetNode, err := g.graph.FetchLightningNode(tx, target)
switch {
// If the node exists and has features, use them directly.
@ -534,6 +520,23 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
}
}
// Build a preliminary destination hop structure to obtain the payload
// size.
var mpp *record.MPP
if r.PaymentAddr != nil {
mpp = record.NewMPP(amt, *r.PaymentAddr)
}
finalHop := route.Hop{
AmtToForward: amt,
OutgoingTimeLock: uint32(finalHtlcExpiry),
CustomRecords: r.DestCustomRecords,
LegacyPayload: !features.HasFeature(
lnwire.TLVOnionPayloadOptional,
),
MPP: mpp,
}
// We can't always assume that the end destination is publicly
// advertised to the network so we'll manually include the target node.
// The target node charges no fee. Distance is set to 0, because this is
@ -548,13 +551,19 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
weight: 0,
node: target,
amountToReceive: amt,
incomingCltv: 0,
incomingCltv: finalHtlcExpiry,
probability: 1,
routingInfoSize: finalHop.PayloadSize(0),
}
// Calculate the absolute cltv limit. Use uint64 to prevent an overflow
// if the cltv limit is MaxUint32.
absoluteCltvLimit := uint64(r.CltvLimit) + uint64(finalHtlcExpiry)
// processEdge is a helper closure that will be used to make sure edges
// satisfy our specific requirements.
processEdge := func(fromVertex route.Vertex,
fromFeatures *lnwire.FeatureVector,
edge *channeldb.ChannelEdgePolicy, toNodeDist *nodeWithDist) {
edgesExpanded++
@ -596,11 +605,10 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
timeLockDelta = edge.TimeLockDelta
}
incomingCltv := toNodeDist.incomingCltv +
uint32(timeLockDelta)
incomingCltv := toNodeDist.incomingCltv + int32(timeLockDelta)
// Check that we are within our CLTV limit.
if incomingCltv > r.CltvLimit {
if uint64(incomingCltv) > absoluteCltvLimit {
return
}
@ -676,34 +684,32 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
edge.ChannelID)
}
switch {
// Calculate the total routing info size if this hop were to be
// included. If we are coming from the source hop, the payload
// size is zero, because the original htlc isn't in the onion
// blob.
var payloadSize uint64
if fromVertex != source {
supportsTlv := fromFeatures.HasFeature(
lnwire.TLVOnionPayloadOptional,
)
// If this edge takes us to the final hop, we'll set the node
// features to those determined above. These are either taken
// from the destination features, e.g. virtual or invoice
// features, or loaded as a fallback from the graph. The
// transitive dependencies were already validated above, so no
// need to do so now.
//
// NOTE: This may overwrite features loaded from the graph if
// destination features were provided. This is fine though,
// since our route construction does not care where the features
// are actually taken from. In the future we may wish to do
// route construction within findPath, and avoid using
// ChannelEdgePolicy altogether.
case edge.Node.PubKeyBytes == target:
edge.Node.Features = features
// Otherwise, this is some other intermediary node. Verify the
// transitive feature dependencies for this node, and skip the
// channel if they are invalid.
default:
err := feature.ValidateDeps(edge.Node.Features)
if err != nil {
log.Tracef("Node %x has invalid features",
edge.Node.PubKeyBytes)
return
hop := route.Hop{
AmtToForward: amountToSend,
OutgoingTimeLock: uint32(
toNodeDist.incomingCltv,
),
LegacyPayload: !supportsTlv,
}
payloadSize = hop.PayloadSize(edge.ChannelID)
}
routingInfoSize := toNodeDist.routingInfoSize + payloadSize
// Skip paths that would exceed the maximum routing info size.
if routingInfoSize > sphinx.MaxPayloadSize {
return
}
// All conditions are met and this new tentative distance is
@ -718,6 +724,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
incomingCltv: incomingCltv,
probability: probability,
nextHop: edge,
routingInfoSize: routingInfoSize,
}
distance[fromVertex] = withDist
@ -730,6 +737,44 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
// TODO(roasbeef): also add path caching
// * similar to route caching, but doesn't factor in the amount
// Cache features because we visit nodes multiple times.
featureCache := make(map[route.Vertex]*lnwire.FeatureVector)
// getGraphFeatures returns (cached) node features from the graph.
getGraphFeatures := func(node route.Vertex) (*lnwire.FeatureVector,
error) {
// Check cache for features of the fromNode.
fromFeatures, ok := featureCache[node]
if !ok {
targetNode, err := g.graph.FetchLightningNode(tx, node)
switch {
// If the node exists and has valid features, use them.
case err == nil:
err := feature.ValidateDeps(targetNode.Features)
if err == nil {
fromFeatures = targetNode.Features
}
// If an error other than the node not existing is hit,
// abort.
case err != channeldb.ErrGraphNodeNotFound:
return nil, err
// Otherwise, we couldn't find a node announcement,
// populate a blank feature vector.
default:
fromFeatures = lnwire.EmptyFeatureVector()
}
// Update cache.
featureCache[node] = fromFeatures
}
return fromFeatures, nil
}
routeToSelf := source == target
for {
nodesVisited++
@ -777,9 +822,20 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
continue
}
// Get feature vector for fromNode.
fromFeatures, err := getGraphFeatures(fromNode)
if err != nil {
return nil, err
}
// If there are no valid features, skip this node.
if fromFeatures == nil {
continue
}
// Check if this candidate node is better than what we
// already have.
processEdge(fromNode, policy, partialPath)
processEdge(fromNode, fromFeatures, policy, partialPath)
}
if nodeHeap.Len() == 0 {
@ -824,17 +880,21 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
}
}
// The route is invalid if it spans more than 20 hops. The current
// Sphinx (onion routing) implementation can only encode up to 20 hops
// as the entire packet is fixed size. If this route is more than 20
// hops, then it's invalid.
numEdges := len(pathEdges)
if numEdges > HopLimit {
return nil, errMaxHopsExceeded
}
// For the final hop, we'll set the node features to those determined
// above. These are either taken from the destination features, e.g.
// virtual or invoice features, or loaded as a fallback from the graph.
// The transitive dependencies were already validated above, so no need
// to do so now.
//
// NOTE: This may overwrite features loaded from the graph if
// destination features were provided. This is fine though, since our
// route construction does not care where the features are actually
// taken from. In the future we may wish to do route construction within
// findPath, and avoid using ChannelEdgePolicy altogether.
pathEdges[len(pathEdges)-1].Node.Features = features
log.Debugf("Found route: probability=%v, hops=%v, fee=%v\n",
distance[source].probability, numEdges,
log.Debugf("Found route: probability=%v, hops=%v, fee=%v",
distance[source].probability, len(pathEdges),
distance[source].amountToReceive-amt)
return pathEdges, nil

View File

@ -823,6 +823,7 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
},
testPathFindingConfig,
sourceNode.PubKeyBytes, target, paymentAmt,
startingHeight+finalHopCLTV,
)
if test.expectFailureNoPath {
if err == nil {
@ -1012,6 +1013,7 @@ func TestPathFindingWithAdditionalEdges(t *testing.T) {
},
r, testPathFindingConfig,
sourceNode.PubKeyBytes, doge.PubKeyBytes, paymentAmt,
0,
)
}
@ -1358,53 +1360,53 @@ func TestNewRoute(t *testing.T) {
}
func TestNewRoutePathTooLong(t *testing.T) {
t.Skip()
t.Parallel()
// Ensure that potential paths which are over the maximum hop-limit are
// rejected.
graph, err := parseTestGraph(excessiveHopsGraphFilePath)
var testChannels []*testChannel
// Setup a linear network of 21 hops.
fromNode := "start"
for i := 0; i < 21; i++ {
toNode := fmt.Sprintf("node-%v", i+1)
c := symmetricTestChannel(fromNode, toNode, 100000, &testChannelPolicy{
Expiry: 144,
FeeRate: 400,
MinHTLC: 1,
MaxHTLC: 100000001,
})
testChannels = append(testChannels, c)
fromNode = toNode
}
ctx := newPathFindingTestContext(t, testChannels, "start")
defer ctx.cleanup()
// Assert that we can find 20 hop routes.
node20 := ctx.keyFromAlias("node-20")
payAmt := lnwire.MilliSatoshi(100001)
_, err := ctx.findPath(node20, payAmt)
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
defer graph.cleanUp()
sourceNode, err := graph.graph.SourceNode()
if err != nil {
t.Fatalf("unable to fetch source node: %v", err)
t.Fatalf("unexpected pathfinding failure: %v", err)
}
paymentAmt := lnwire.NewMSatFromSatoshis(100)
// We start by confirming that routing a payment 20 hops away is
// possible. Alice should be able to find a valid route to ursula.
target := graph.aliasMap["ursula"]
_, err = findPath(
&graphParams{
graph: graph.graph,
},
noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, target, paymentAmt,
)
if err != nil {
t.Fatalf("path should have been found")
// Assert that finding a 21 hop route fails.
node21 := ctx.keyFromAlias("node-21")
_, err = ctx.findPath(node21, payAmt)
if err != errNoPathFound {
t.Fatalf("not route error expected, but got %v", err)
}
// Vincent is 21 hops away from Alice, and thus no valid route should be
// presented to Alice.
target = graph.aliasMap["vincent"]
path, err := findPath(
&graphParams{
graph: graph.graph,
},
noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, target, paymentAmt,
)
if err == nil {
t.Fatalf("should not have been able to find path, supposed to be "+
"greater than 20 hops, found route with %v hops",
len(path))
// Assert that we can't find a 20 hop route if custom records make it
// exceed the maximum payload size.
ctx.restrictParams.DestFeatures = tlvFeatures
ctx.restrictParams.DestCustomRecords = map[uint64][]byte{
100000: bytes.Repeat([]byte{1}, 100),
}
_, err = ctx.findPath(node20, payAmt)
if err != errNoPathFound {
t.Fatalf("not route error expected, but got %v", err)
}
}
func TestPathNotAvailable(t *testing.T) {
@ -1437,7 +1439,7 @@ func TestPathNotAvailable(t *testing.T) {
graph: graph.graph,
},
noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, unknownNode, 100,
sourceNode.PubKeyBytes, unknownNode, 100, 0,
)
if err != errNoPathFound {
t.Fatalf("path shouldn't have been found: %v", err)
@ -1495,7 +1497,7 @@ func TestDestTLVGraphFallback(t *testing.T) {
graph: ctx.graphParams.graph,
},
r, testPathFindingConfig,
sourceNode.PubKeyBytes, target, 100,
sourceNode.PubKeyBytes, target, 100, 0,
)
}
@ -1604,7 +1606,7 @@ func TestMissingFeatureDep(t *testing.T) {
graph: ctx.graphParams.graph,
},
r, testPathFindingConfig,
sourceNode.PubKeyBytes, target, 100,
sourceNode.PubKeyBytes, target, 100, 0,
)
}
@ -1682,7 +1684,7 @@ func TestDestPaymentAddr(t *testing.T) {
graph: ctx.graphParams.graph,
},
r, testPathFindingConfig,
sourceNode.PubKeyBytes, target, 100,
sourceNode.PubKeyBytes, target, 100, 0,
)
}
@ -1743,7 +1745,7 @@ func TestPathInsufficientCapacity(t *testing.T) {
graph: graph.graph,
},
noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, target, payAmt,
sourceNode.PubKeyBytes, target, payAmt, 0,
)
if err != errNoPathFound {
t.Fatalf("graph shouldn't be able to support payment: %v", err)
@ -1776,7 +1778,7 @@ func TestRouteFailMinHTLC(t *testing.T) {
graph: graph.graph,
},
noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, target, payAmt,
sourceNode.PubKeyBytes, target, payAmt, 0,
)
if err != errNoPathFound {
t.Fatalf("graph shouldn't be able to support payment: %v", err)
@ -1875,7 +1877,7 @@ func TestRouteFailDisabledEdge(t *testing.T) {
graph: graph.graph,
},
noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, target, payAmt,
sourceNode.PubKeyBytes, target, payAmt, 0,
)
if err != nil {
t.Fatalf("unable to find path: %v", err)
@ -1903,7 +1905,7 @@ func TestRouteFailDisabledEdge(t *testing.T) {
graph: graph.graph,
},
noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, target, payAmt,
sourceNode.PubKeyBytes, target, payAmt, 0,
)
if err != nil {
t.Fatalf("unable to find path: %v", err)
@ -1928,7 +1930,7 @@ func TestRouteFailDisabledEdge(t *testing.T) {
graph: graph.graph,
},
noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, target, payAmt,
sourceNode.PubKeyBytes, target, payAmt, 0,
)
if err != errNoPathFound {
t.Fatalf("graph shouldn't be able to support payment: %v", err)
@ -1962,7 +1964,7 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
graph: graph.graph,
},
noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, target, payAmt,
sourceNode.PubKeyBytes, target, payAmt, 0,
)
if err != nil {
t.Fatalf("unable to find path: %v", err)
@ -1986,7 +1988,7 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
bandwidthHints: bandwidths,
},
noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, target, payAmt,
sourceNode.PubKeyBytes, target, payAmt, 0,
)
if err != errNoPathFound {
t.Fatalf("graph shouldn't be able to support payment: %v", err)
@ -2004,7 +2006,7 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
bandwidthHints: bandwidths,
},
noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, target, payAmt,
sourceNode.PubKeyBytes, target, payAmt, 0,
)
if err != nil {
t.Fatalf("unable to find path: %v", err)
@ -2035,7 +2037,7 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
bandwidthHints: bandwidths,
},
noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, target, payAmt,
sourceNode.PubKeyBytes, target, payAmt, 0,
)
if err != nil {
t.Fatalf("unable to find path: %v", err)
@ -2070,11 +2072,7 @@ func TestPathFindSpecExample(t *testing.T) {
// Carol, so we set "B" as the source node so path finding starts from
// Bob.
bob := ctx.aliases["B"]
bobKey, err := btcec.ParsePubKey(bob[:], btcec.S256())
if err != nil {
t.Fatal(err)
}
bobNode, err := ctx.graph.FetchLightningNode(bobKey)
bobNode, err := ctx.graph.FetchLightningNode(nil, bob)
if err != nil {
t.Fatalf("unable to find bob: %v", err)
}
@ -2123,11 +2121,7 @@ func TestPathFindSpecExample(t *testing.T) {
// Next, we'll set A as the source node so we can assert that we create
// the proper route for any queries starting with Alice.
alice := ctx.aliases["A"]
aliceKey, err := btcec.ParsePubKey(alice[:], btcec.S256())
if err != nil {
t.Fatal(err)
}
aliceNode, err := ctx.graph.FetchLightningNode(aliceKey)
aliceNode, err := ctx.graph.FetchLightningNode(nil, alice)
if err != nil {
t.Fatalf("unable to find alice: %v", err)
}
@ -2880,7 +2874,7 @@ func (c *pathFindingTestContext) findPath(target route.Vertex,
return findPath(
&c.graphParams, &c.restrictParams, &c.pathFindingConfig,
c.source, target, amt,
c.source, target, amt, 0,
)
}

View File

@ -196,7 +196,6 @@ func errorToPaymentFailure(err error) channeldb.FailureReason {
errNoTlvPayload,
errNoPaymentAddr,
errNoPathFound,
errMaxHopsExceeded,
errPrebuiltRouteTried:
return channeldb.FailureReasonNoRoute

View File

@ -113,6 +113,8 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment,
return nil, err
}
finalHtlcExpiry := int32(height) + int32(finalCltvDelta)
path, err := p.pathFinder(
&graphParams{
graph: ss.Graph,
@ -121,7 +123,7 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment,
},
restrictions, &ss.PathFindingConfig,
ss.SelfNode.PubKeyBytes, payment.Target,
payment.Amount,
payment.Amount, finalHtlcExpiry,
)
if err != nil {
return nil, err

View File

@ -15,8 +15,8 @@ func TestRequestRoute(t *testing.T) {
findPath := func(g *graphParams, r *RestrictParams,
cfg *PathFindingConfig, source, target route.Vertex,
amt lnwire.MilliSatoshi) ([]*channeldb.ChannelEdgePolicy,
error) {
amt lnwire.MilliSatoshi, finalHtlcExpiry int32) (
[]*channeldb.ChannelEdgePolicy, error) {
// We expect find path to receive a cltv limit excluding the
// final cltv delta (including the block padding).

View File

@ -191,10 +191,8 @@ func (i *interpretedResult) processPaymentOutcomeFinal(
i.failPair(route, n-1)
// The other hops relayed corectly, so assign those pairs a
// success result.
if n > 2 {
i.successPairRange(route, 0, n-2)
}
// success result. At this point, n >= 2.
i.successPairRange(route, 0, n-2)
// We are using wrong payment hash or amount, fail the payment.
case *lnwire.FailIncorrectPaymentAmount,
@ -216,6 +214,22 @@ func (i *interpretedResult) processPaymentOutcomeFinal(
// deliberately. What to penalize?
i.finalFailureReason = &reasonIncorrectDetails
case *lnwire.FailMPPTimeout:
// TODO(carla): decide how to penalize mpp timeout. In the
// meantime, attribute success to the hops along the route and
// do not penalize the final node.
i.finalFailureReason = &reasonError
// If this is a direct payment, take no action.
if n == 1 {
return
}
// Assign all pairs a success result except the final hop, as
// the payment reached the destination correctly.
i.successPairRange(route, 0, n-2)
default:
// All other errors are considered terminal if coming from the
// final hop. They indicate that something is wrong at the
@ -223,7 +237,7 @@ func (i *interpretedResult) processPaymentOutcomeFinal(
i.failNode(route, n)
// Other channels in the route forwarded correctly.
if n > 2 {
if n >= 2 {
i.successPairRange(route, 0, n-2)
}

View File

@ -32,6 +32,16 @@ var (
},
}
routeThreeHop = route.Route{
SourcePubKey: hops[0],
TotalAmount: 100,
Hops: []*route.Hop{
{PubKeyBytes: hops[1], AmtToForward: 99},
{PubKeyBytes: hops[2], AmtToForward: 97},
{PubKeyBytes: hops[3], AmtToForward: 94},
},
}
routeFourHop = route.Route{
SourcePubKey: hops[0],
TotalAmount: 100,
@ -203,7 +213,7 @@ var resultTestCases = []resultTestCase{
// be failed while the proceeding hops are reproed as successes. The
// failure is terminal since the receiver can't process our onion.
{
name: "fail invalid onion payload final hop",
name: "fail invalid onion payload final hop four",
route: &routeFourHop,
failureSrcIdx: 4,
failure: lnwire.NewInvalidOnionPayload(0, 0),
@ -229,6 +239,30 @@ var resultTestCases = []resultTestCase{
},
},
// Tests an invalid onion payload from a final hop on a three hop route.
{
name: "fail invalid onion payload final hop three",
route: &routeThreeHop,
failureSrcIdx: 3,
failure: lnwire.NewInvalidOnionPayload(0, 0),
expectedResult: &interpretedResult{
pairResults: map[DirectedNodePair]pairResult{
getTestPair(0, 1): {
success: true,
amt: 100,
},
getTestPair(1, 2): {
success: true,
amt: 99,
},
getTestPair(3, 2): {},
},
finalFailureReason: &reasonError,
nodeFailure: &hops[3],
},
},
// Tests an invalid onion payload from an intermediate hop. Only the
// reporting node should be failed. The failure is non-terminal since we
// can still try other paths.
@ -272,6 +306,39 @@ var resultTestCases = []resultTestCase{
nodeFailure: &hops[1],
},
},
// Tests a single hop mpp timeout. Test that final node is not
// penalized. This is a temporary measure while we decide how to
// penalize mpp timeouts.
{
name: "one hop mpp timeout",
route: &routeOneHop,
failureSrcIdx: 1,
failure: &lnwire.FailMPPTimeout{},
expectedResult: &interpretedResult{
finalFailureReason: &reasonError,
nodeFailure: nil,
},
},
// Tests a two hop mpp timeout. Test that final node is not penalized
// and the intermediate hop is attributed the success. This is a
// temporary measure while we decide how to penalize mpp timeouts.
{
name: "two hop mpp timeout",
route: &routeTwoHop,
failureSrcIdx: 2,
failure: &lnwire.FailMPPTimeout{},
expectedResult: &interpretedResult{
pairResults: map[DirectedNodePair]pairResult{
getTestPair(0, 1): successPairResult(100),
},
finalFailureReason: &reasonError,
nodeFailure: nil,
},
},
}
// TestResultInterpretation executes a list of test cases that test the result

View File

@ -184,6 +184,53 @@ func (h *Hop) PackHopPayload(w io.Writer, nextChanID uint64) error {
return tlvStream.Encode(w)
}
// Size returns the total size this hop's payload would take up in the onion
// packet.
func (h *Hop) PayloadSize(nextChanID uint64) uint64 {
if h.LegacyPayload {
return sphinx.LegacyHopDataSize
}
var payloadSize uint64
addRecord := func(tlvType tlv.Type, length uint64) {
payloadSize += tlv.VarIntSize(uint64(tlvType)) +
tlv.VarIntSize(length) + length
}
// Add amount size.
addRecord(record.AmtOnionType, tlv.SizeTUint64(uint64(h.AmtToForward)))
// Add lock time size.
addRecord(
record.LockTimeOnionType,
tlv.SizeTUint64(uint64(h.OutgoingTimeLock)),
)
// Add next hop if present.
if nextChanID != 0 {
addRecord(record.NextHopOnionType, 8)
}
// Add mpp if present.
if h.MPP != nil {
addRecord(record.MPPOnionType, h.MPP.PayloadSize())
}
// Add custom records.
for k, v := range h.CustomRecords {
addRecord(tlv.Type(k), uint64(len(v)))
}
// Add the size required to encode the payload length.
payloadSize += tlv.VarIntSize(payloadSize)
// Add HMAC.
payloadSize += sphinx.HMACSize
return payloadSize
}
// Route represents a path through the channel graph which runs over one or
// more channels in succession. This struct carries all the information
// required to craft the Sphinx onion packet, and send the payment along the

View File

@ -2,12 +2,20 @@ package route
import (
"bytes"
"encoding/hex"
"testing"
"github.com/btcsuite/btcd/btcec"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
)
var (
testPrivKeyBytes, _ = hex.DecodeString("e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734")
_, testPubKey = btcec.PrivKeyFromBytes(btcec.S256(), testPrivKeyBytes)
testPubKeyBytes, _ = NewVertexFromBytes(testPubKey.SerializeCompressed())
)
// TestRouteTotalFees checks that a route reports the expected total fee.
func TestRouteTotalFees(t *testing.T) {
t.Parallel()
@ -56,7 +64,6 @@ func TestRouteTotalFees(t *testing.T) {
if r.TotalFees() != fee {
t.Fatalf("expected %v fees, got %v", fee, r.TotalFees())
}
}
var (
@ -93,3 +100,57 @@ func TestMPPHop(t *testing.T) {
t.Fatalf("expected err: %v, got: %v", nil, err)
}
}
// TestPayloadSize tests the payload size calculation that is provided by Hop
// structs.
func TestPayloadSize(t *testing.T) {
hops := []*Hop{
{
PubKeyBytes: testPubKeyBytes,
AmtToForward: 1000,
OutgoingTimeLock: 600000,
ChannelID: 3432483437438,
LegacyPayload: true,
},
{
PubKeyBytes: testPubKeyBytes,
AmtToForward: 1200,
OutgoingTimeLock: 700000,
ChannelID: 63584534844,
},
{
PubKeyBytes: testPubKeyBytes,
AmtToForward: 1200,
OutgoingTimeLock: 700000,
MPP: record.NewMPP(500, [32]byte{}),
CustomRecords: map[uint64][]byte{
100000: {1, 2, 3},
1000000: {4, 5},
},
},
}
rt := Route{
Hops: hops,
}
path, err := rt.ToSphinxPath()
if err != nil {
t.Fatal(err)
}
for i, onionHop := range path[:path.TrueRouteLength()] {
hop := hops[i]
var nextChan uint64
if i < len(hops)-1 {
nextChan = hops[i+1].ChannelID
}
expected := uint64(onionHop.HopPayload.NumBytes())
actual := hop.PayloadSize(nextChan)
if expected != actual {
t.Fatalf("unexpected payload size at hop %v: "+
"expected %v, got %v",
i, expected, actual)
}
}
}

View File

@ -1431,27 +1431,29 @@ func (r *ChannelRouter) FindRoute(source, target route.Vertex,
return nil, err
}
// We'll fetch the current block height so we can properly calculate the
// required HTLC time locks within the route.
_, currentHeight, err := r.cfg.Chain.GetBestBlock()
if err != nil {
return nil, err
}
// Now that we know the destination is reachable within the graph, we'll
// execute our path finding algorithm.
finalHtlcExpiry := currentHeight + int32(finalCLTVDelta)
path, err := findPath(
&graphParams{
graph: r.cfg.Graph,
bandwidthHints: bandwidthHints,
},
restrictions, &r.cfg.PathFindingConfig,
source, target, amt,
source, target, amt, finalHtlcExpiry,
)
if err != nil {
return nil, err
}
// We'll fetch the current block height so we can properly calculate the
// required HTLC time locks within the route.
_, currentHeight, err := r.cfg.Chain.GetBestBlock()
if err != nil {
return nil, err
}
// Create the route with absolute time lock values.
route, err := newRoute(
source, path, uint32(currentHeight),
@ -2089,11 +2091,7 @@ func (r *ChannelRouter) GetChannelByID(chanID lnwire.ShortChannelID) (
//
// NOTE: This method is part of the ChannelGraphSource interface.
func (r *ChannelRouter) FetchLightningNode(node route.Vertex) (*channeldb.LightningNode, error) {
pubKey, err := btcec.ParsePubKey(node[:], btcec.S256())
if err != nil {
return nil, fmt.Errorf("unable to parse raw public key: %v", err)
}
return r.cfg.Graph.FetchLightningNode(pubKey)
return r.cfg.Graph.FetchLightningNode(nil, node)
}
// ForEachNode is used to iterate over every node in router topology.

View File

@ -1319,7 +1319,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
t.Fatalf("unable to find any routes: %v", err)
}
copy1, err := ctx.graph.FetchLightningNode(priv1.PubKey())
copy1, err := ctx.graph.FetchLightningNode(nil, pub1)
if err != nil {
t.Fatalf("unable to fetch node: %v", err)
}
@ -1328,7 +1328,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
t.Fatalf("fetched node not equal to original")
}
copy2, err := ctx.graph.FetchLightningNode(priv2.PubKey())
copy2, err := ctx.graph.FetchLightningNode(nil, pub2)
if err != nil {
t.Fatalf("unable to fetch node: %v", err)
}
@ -2174,7 +2174,7 @@ func TestFindPathFeeWeighting(t *testing.T) {
},
noRestrictions,
testPathFindingConfig,
sourceNode.PubKeyBytes, target, amt,
sourceNode.PubKeyBytes, target, amt, 0,
)
if err != nil {
t.Fatalf("unable to find path: %v", err)

View File

@ -1,410 +0,0 @@
{
"nodes": [
{
"source": true,
"pubkey": "021b96642e723592ee0b095983fe3a26c8b40b8926968d8b7510e51c9429d4562c",
"alias": "alice"
},
{
"source": false,
"pubkey": "022096b2b0ac083e708074a5ab57288bc821b6bef7b964185b307e073772c3748f",
"alias": "bob"
},
{
"source": false,
"pubkey": "022a190ce901ab2b6f349483f18b28a1d72c64a7bccb8057291f25784c0899840f",
"alias": "carol"
},
{
"source": false,
"pubkey": "022d855d09971dd047b7ecf929b23c6f147b568d4668af67fb2226eb8c15c4660d",
"alias": "dave"
},
{
"source": false,
"pubkey": "024ca436834b0d38d9dc7ee4d95aa21db321c45598dc5921a4a52304a8e0dd2952",
"alias": "eve"
},
{
"source": false,
"pubkey": "025234a0c44cbf1b20c18e2c397107ad731376831e1c43ddb360b41dbb98c10266",
"alias": "fez"
},
{
"source": false,
"pubkey": "0253e9d03030f2ff08d3a7f1d824ad6d8c0dae422f324e72d5bb313e3f2a2d45a8",
"alias": "gabby"
},
{
"source": false,
"pubkey": "0263d4f2baca258ff3bd5bce86c2754e95daaea27f268ae1a048c1253ff20de56e",
"alias": "harold"
},
{
"source": false,
"pubkey": "02650db8e44302f75e265e9427264bc0d7e2337831d6b9ceb7c58ed1e725d4576a",
"alias": "inez"
},
{
"source": false,
"pubkey": "02727bfd298aa055a6419404931dfc1ccb4f0eb7c9660a7df346b93d0025df3ba1",
"alias": "jake"
},
{
"source": false,
"pubkey": "0280c83b3eded413dcec12f7952410e2738f079bd9cbc9a7c462e32ed4d74bd5b7",
"alias": "karen"
},
{
"source": false,
"pubkey": "0290bf454f4b95baf9227801301b331e35d477c6b6e7f36a599983ae58747b3828",
"alias": "liam"
},
{
"source": false,
"pubkey": "0297c8de635d17e3dd5775edfa2797be0874c53b0026f69009787cecd2fa577de8",
"alias": "maggie"
},
{
"source": false,
"pubkey": "02a27227113c71eab0c8609ac0cdc7e76791fc3163c16e643cb4658d1080c7e336",
"alias": "nick"
},
{
"source": false,
"pubkey": "02f5f6bb6373fc60528118003f803557b916fbecd90c3a0c5df4c86c6a6e962fd1",
"alias": "ophelia"
},
{
"source": false,
"pubkey": "02fd7a5f04d550cf0ba8af6053a20e0080d956f41b1221357a35fab3a363e5f78e",
"alias": "patrick"
},
{
"source": false,
"pubkey": "030da942ed7cfc7d3096811b3264e15115778e692eaacb2b7a76fb27a58cbb5359",
"alias": "quinn"
},
{
"source": false,
"pubkey": "0319d6b038e26ac89802e856d7e78f293e9d109c414614f98e3fa5c626f20934be",
"alias": "rick"
},
{
"source": false,
"pubkey": "03384439e78e87d168fecabe8d88218dfd5983c5e14fd8fa6dc89caeb3cc0fb171",
"alias": "sarah"
},
{
"source": false,
"pubkey": "0362002b8fbc1a799c839c8bcea43fce38a147467a00bc450414bbeab5c7a19efe",
"alias": "tim"
},
{
"source": false,
"pubkey": "0369bca64993fce966745d32c09b882f668958d9bd7aabb60ba35ef1884013be1d",
"alias": "ursula"
},
{
"source": false,
"pubkey": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6",
"alias": "vincent"
}
],
"edges": [
{
"node_1": "021b96642e723592ee0b095983fe3a26c8b40b8926968d8b7510e51c9429d4562c",
"node_2": "022096b2b0ac083e708074a5ab57288bc821b6bef7b964185b307e073772c3748f",
"channel_id": 12345,
"channel_point": "99dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "022096b2b0ac083e708074a5ab57288bc821b6bef7b964185b307e073772c3748f",
"node_2": "022a190ce901ab2b6f349483f18b28a1d72c64a7bccb8057291f25784c0899840f",
"channel_id": 12346,
"channel_point": "79dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "022a190ce901ab2b6f349483f18b28a1d72c64a7bccb8057291f25784c0899840f",
"node_2": "022d855d09971dd047b7ecf929b23c6f147b568d4668af67fb2226eb8c15c4660d",
"channel_id": 12347,
"channel_point": "69dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "022d855d09971dd047b7ecf929b23c6f147b568d4668af67fb2226eb8c15c4660d",
"node_2": "024ca436834b0d38d9dc7ee4d95aa21db321c45598dc5921a4a52304a8e0dd2952",
"channel_id": 12348,
"channel_point": "59dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "024ca436834b0d38d9dc7ee4d95aa21db321c45598dc5921a4a52304a8e0dd2952",
"node_2": "025234a0c44cbf1b20c18e2c397107ad731376831e1c43ddb360b41dbb98c10266",
"channel_id": 12349,
"channel_point": "49dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "025234a0c44cbf1b20c18e2c397107ad731376831e1c43ddb360b41dbb98c10266",
"node_2": "0253e9d03030f2ff08d3a7f1d824ad6d8c0dae422f324e72d5bb313e3f2a2d45a8",
"channel_id": 12340,
"channel_point": "39dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "0253e9d03030f2ff08d3a7f1d824ad6d8c0dae422f324e72d5bb313e3f2a2d45a8",
"node_2": "0263d4f2baca258ff3bd5bce86c2754e95daaea27f268ae1a048c1253ff20de56e",
"channel_id": 12344,
"channel_point": "29dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "0263d4f2baca258ff3bd5bce86c2754e95daaea27f268ae1a048c1253ff20de56e",
"node_2": "02650db8e44302f75e265e9427264bc0d7e2337831d6b9ceb7c58ed1e725d4576a",
"channel_id": 12343,
"channel_point": "19dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "02650db8e44302f75e265e9427264bc0d7e2337831d6b9ceb7c58ed1e725d4576a",
"node_2": "02727bfd298aa055a6419404931dfc1ccb4f0eb7c9660a7df346b93d0025df3ba1",
"channel_id": 12342,
"channel_point": "88dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "02727bfd298aa055a6419404931dfc1ccb4f0eb7c9660a7df346b93d0025df3ba1",
"node_2": "0280c83b3eded413dcec12f7952410e2738f079bd9cbc9a7c462e32ed4d74bd5b7",
"channel_id": 12341,
"channel_point": "87dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "0280c83b3eded413dcec12f7952410e2738f079bd9cbc9a7c462e32ed4d74bd5b7",
"node_2": "0290bf454f4b95baf9227801301b331e35d477c6b6e7f36a599983ae58747b3828",
"channel_id": 12355,
"channel_point": "86dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "0290bf454f4b95baf9227801301b331e35d477c6b6e7f36a599983ae58747b3828",
"node_2": "0297c8de635d17e3dd5775edfa2797be0874c53b0026f69009787cecd2fa577de8",
"channel_id": 12365,
"channel_point": "85dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "0297c8de635d17e3dd5775edfa2797be0874c53b0026f69009787cecd2fa577de8",
"node_2": "02a27227113c71eab0c8609ac0cdc7e76791fc3163c16e643cb4658d1080c7e336",
"channel_id": 12375,
"channel_point": "84dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "02a27227113c71eab0c8609ac0cdc7e76791fc3163c16e643cb4658d1080c7e336",
"node_2": "02f5f6bb6373fc60528118003f803557b916fbecd90c3a0c5df4c86c6a6e962fd1",
"channel_id": 12385,
"channel_point": "83dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "02f5f6bb6373fc60528118003f803557b916fbecd90c3a0c5df4c86c6a6e962fd1",
"node_2": "02fd7a5f04d550cf0ba8af6053a20e0080d956f41b1221357a35fab3a363e5f78e",
"channel_id": 12395,
"channel_point": "82dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "02fd7a5f04d550cf0ba8af6053a20e0080d956f41b1221357a35fab3a363e5f78e",
"node_2": "030da942ed7cfc7d3096811b3264e15115778e692eaacb2b7a76fb27a58cbb5359",
"channel_id": 12305,
"channel_point": "81dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "030da942ed7cfc7d3096811b3264e15115778e692eaacb2b7a76fb27a58cbb5359",
"node_2": "0319d6b038e26ac89802e856d7e78f293e9d109c414614f98e3fa5c626f20934be",
"channel_id": 12335,
"channel_point": "80dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "0319d6b038e26ac89802e856d7e78f293e9d109c414614f98e3fa5c626f20934be",
"node_2": "03384439e78e87d168fecabe8d88218dfd5983c5e14fd8fa6dc89caeb3cc0fb171",
"channel_id": 12325,
"channel_point": "89ec56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "03384439e78e87d168fecabe8d88218dfd5983c5e14fd8fa6dc89caeb3cc0fb171",
"node_2": "0362002b8fbc1a799c839c8bcea43fce38a147467a00bc450414bbeab5c7a19efe",
"channel_id": 12315,
"channel_point": "89fc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "0362002b8fbc1a799c839c8bcea43fce38a147467a00bc450414bbeab5c7a19efe",
"node_2": "0369bca64993fce966745d32c09b882f668958d9bd7aabb60ba35ef1884013be1d",
"channel_id": 12445,
"channel_point": "89cc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "0369bca64993fce966745d32c09b882f668958d9bd7aabb60ba35ef1884013be1d",
"node_2": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6",
"channel_id": 12545,
"channel_point": "89bc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
}
]
}

View File

@ -4231,11 +4231,7 @@ func (r *rpcServer) GetNodeInfo(ctx context.Context,
// First, parse the hex-encoded public key into a full in-memory public
// key object we can work with for querying.
pubKeyBytes, err := hex.DecodeString(in.PubKey)
if err != nil {
return nil, err
}
pubKey, err := btcec.ParsePubKey(pubKeyBytes, btcec.S256())
pubKey, err := route.NewVertexFromStr(in.PubKey)
if err != nil {
return nil, err
}
@ -4243,7 +4239,7 @@ func (r *rpcServer) GetNodeInfo(ctx context.Context,
// With the public key decoded, attempt to fetch the node corresponding
// to this public key. If the node cannot be found, then an error will
// be returned.
node, err := graph.FetchLightningNode(pubKey)
node, err := graph.FetchLightningNode(nil, pubKey)
if err != nil {
return nil, err
}

View File

@ -3369,7 +3369,12 @@ func computeNextBackoff(currBackoff time.Duration) time.Duration {
// fetchNodeAdvertisedAddr attempts to fetch an advertised address of a node.
func (s *server) fetchNodeAdvertisedAddr(pub *btcec.PublicKey) (net.Addr, error) {
node, err := s.chanDB.ChannelGraph().FetchLightningNode(pub)
vertex, err := route.NewVertexFromBytes(pub.SerializeCompressed())
if err != nil {
return nil, err
}
node, err := s.chanDB.ChannelGraph().FetchLightningNode(nil, vertex)
if err != nil {
return nil, err
}

View File

@ -4,6 +4,8 @@ import (
"encoding/binary"
"errors"
"io"
"github.com/btcsuite/btcd/wire"
)
// ErrVarIntNotCanonical signals that the decoded varint was not minimally encoded.
@ -107,3 +109,8 @@ func WriteVarInt(w io.Writer, val uint64, buf *[8]byte) error {
_, err := w.Write(buf[:length])
return err
}
// VarIntSize returns the required number of bytes to encode a var int.
func VarIntSize(val uint64) uint64 {
return uint64(wire.VarIntSerializeSize(val))
}

View File

@ -1095,6 +1095,14 @@ func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error {
return err
}
}
if invoice.PaymentAddr != nil {
err := writeBytes32(
bufferBase32, fieldTypeS, *invoice.PaymentAddr,
)
if err != nil {
return err
}
}
if invoice.Features.SerializeSize32() > 0 {
var b bytes.Buffer
err := invoice.Features.RawFeatureVector.EncodeBase32(&b)
@ -1107,14 +1115,6 @@ func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error {
return err
}
}
if invoice.PaymentAddr != nil {
err := writeBytes32(
bufferBase32, fieldTypeS, *invoice.PaymentAddr,
)
if err != nil {
return err
}
}
return nil
}

View File

@ -42,6 +42,13 @@ var (
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
}
specPaymentAddr = [32]byte{
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
}
testEmptyString = ""
testCupOfCoffee = "1 cup coffee"
testCoffeeBeans = "coffee beans"
@ -499,8 +506,8 @@ func TestDecodeEncode(t *testing.T) {
},
{
// On mainnet, please send $30 coffee beans supporting
// features 1 and 9.
encodedInvoice: "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdees9qzsze992adudgku8p05pstl6zh7av6rx2f297pv89gu5q93a0hf3g7lynl3xq56t23dpvah6u7y9qey9lccrdml3gaqwc6nxsl5ktzm464sq73t7cl",
// features 9, 15 and 99, using secret 0x11...
encodedInvoice: "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q5sqqqqqqqqqqqqqqqpqsq67gye39hfg3zd8rgc80k32tvy9xk2xunwm5lzexnvpx6fd77en8qaq424dxgt56cag2dpt359k3ssyhetktkpqh24jqnjyw6uqd08sgptq44qu",
valid: true,
decodedInvoice: func() *Invoice {
return &Invoice{
@ -508,10 +515,11 @@ func TestDecodeEncode(t *testing.T) {
MilliSat: &testMillisat25mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
PaymentAddr: &specPaymentAddr,
Description: &testCoffeeBeans,
Destination: testPubKey,
Features: lnwire.NewFeatureVector(
lnwire.NewRawFeatureVector(1, 9),
lnwire.NewRawFeatureVector(9, 15, 99),
lnwire.Features,
),
}
@ -525,8 +533,8 @@ func TestDecodeEncode(t *testing.T) {
},
{
// On mainnet, please send $30 coffee beans supporting
// features 1, 9, and 100.
encodedInvoice: "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdees9q4pqqqqqqqqqqqqqqqqqqszk3ed62snp73037h4py4gry05eltlp0uezm2w9ajnerhmxzhzhsu40g9mgyx5v3ad4aqwkmvyftzk4k9zenz90mhjcy9hcevc7r3lx2sphzfxz7",
// features 9, 15, 99, and 100, using secret 0x11...
encodedInvoice: "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q4psqqqqqqqqqqqqqqqpqqqqu7fz6pjqczdm3jp3qps7xntj2w2mm70e0ckhw3c5xk9p36pvk3sewn7ncaex6uzfq0vtqzy28se6pcwn790vxex7xystzumhg55p6qq9wq7td",
valid: false,
skipEncoding: true,
decodedInvoice: func() *Invoice {
@ -535,10 +543,11 @@ func TestDecodeEncode(t *testing.T) {
MilliSat: &testMillisat25mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
PaymentAddr: &specPaymentAddr,
Description: &testCoffeeBeans,
Destination: testPubKey,
Features: lnwire.NewFeatureVector(
lnwire.NewRawFeatureVector(1, 9, 100),
lnwire.NewRawFeatureVector(9, 15, 99, 100),
lnwire.Features,
),
}