From b3b355659bf0853b14c8f79bd8030e8ebb4e35b4 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Tue, 17 Dec 2019 13:37:25 +0100 Subject: [PATCH 01/25] lncfg+lncli: replace WithDialer->WitchContextDialer WithDialer is deprecated, use WitchContextDialer instead. --- cmd/lncli/main.go | 2 +- lncfg/address.go | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/cmd/lncli/main.go b/cmd/lncli/main.go index d06c2de8..30aa2485 100644 --- a/cmd/lncli/main.go +++ b/cmd/lncli/main.go @@ -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...) diff --git a/lncfg/address.go b/lncfg/address.go index 5a5a07fd..7fe2d71d 100644 --- a/lncfg/address.go +++ b/lncfg/address.go @@ -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(), ) } } From 37d9ee302c4b400fb8309b98a2e7ba614ed18754 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 18 Dec 2019 12:43:18 +0100 Subject: [PATCH 02/25] channeldb: take serialized key to fetch lightning node This prevents inefficient key conversions in a follow up commit that change the inner pathfinding loop. --- autopilot/graph.go | 10 +++++++++- channeldb/graph.go | 14 +++++++------- channeldb/graph_test.go | 33 ++++++++++----------------------- routing/pathfind.go | 8 +------- routing/pathfind_test.go | 12 ++---------- routing/router.go | 6 +----- routing/router_test.go | 4 ++-- rpcserver.go | 6 +----- server.go | 7 ++++++- 9 files changed, 39 insertions(+), 61 deletions(-) diff --git a/autopilot/graph.go b/autopilot/graph.go index f02d0a8e..c63d6650 100644 --- a/autopilot/graph.go +++ b/autopilot/graph.go @@ -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(vertex) switch { case err == channeldb.ErrGraphNodeNotFound: fallthrough diff --git a/channeldb/graph.go b/channeldb/graph.go index abfc5d49..3ec06981 100644 --- a/channeldb/graph.go +++ b/channeldb/graph.go @@ -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,9 +2179,10 @@ 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) { +func (c *ChannelGraph) FetchLightningNode(nodePub route.Vertex) (*LightningNode, + error) { + var node *LightningNode - nodePub := pub.SerializeCompressed() err := c.db.View(func(tx *bbolt.Tx) error { // First grab the nodes bucket which stores the mapping from // pubKey to node information. @@ -2193,7 +2193,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 } diff --git a/channeldb/graph_test.go b/channeldb/graph_test.go index 0768e829..27e29871 100644 --- a/channeldb/graph_test.go +++ b/channeldb/graph_test.go @@ -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. @@ -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) @@ -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) @@ -2386,11 +2387,7 @@ 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 { + if _, err := graph.FetchLightningNode(node3.PubKeyBytes); err == nil { t.Fatalf("node 3 should have been deleted!") } } @@ -2430,18 +2427,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(node1.PubKeyBytes) if err != nil { t.Fatalf("unable to fetch node1: %v", err) } @@ -2449,7 +2437,7 @@ func TestAddChannelEdgeShellNodes(t *testing.T) { t.Fatalf("have shell announcement for node1, shouldn't") } - node2, err = graph.FetchLightningNode(node2Pub) + node2, err = graph.FetchLightningNode(node2.PubKeyBytes) if err != nil { t.Fatalf("unable to fetch node2: %v", err) } @@ -2504,8 +2492,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) } diff --git a/routing/pathfind.go b/routing/pathfind.go index ea5315b6..9eca590a 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -7,7 +7,6 @@ import ( "math" "time" - "github.com/btcsuite/btcd/btcec" "github.com/coreos/bbolt" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/feature" @@ -447,12 +446,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(target) switch { // If the node exists and has features, use them directly. diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index acff0d2b..3a26428c 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -2070,11 +2070,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(bob) if err != nil { t.Fatalf("unable to find bob: %v", err) } @@ -2123,11 +2119,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(alice) if err != nil { t.Fatalf("unable to find alice: %v", err) } diff --git a/routing/router.go b/routing/router.go index ef38788d..87a73937 100644 --- a/routing/router.go +++ b/routing/router.go @@ -2089,11 +2089,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(node) } // ForEachNode is used to iterate over every node in router topology. diff --git a/routing/router_test.go b/routing/router_test.go index e3ae0108..483abec4 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -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(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(pub2) if err != nil { t.Fatalf("unable to fetch node: %v", err) } diff --git a/rpcserver.go b/rpcserver.go index 239df76d..703ce25a 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -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 } diff --git a/server.go b/server.go index 46ec00d0..58f8f17b 100644 --- a/server.go +++ b/server.go @@ -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(vertex) if err != nil { return nil, err } From 2c3041d8bc16ee44b39f8733e2704e79a386a09a Mon Sep 17 00:00:00 2001 From: Andras Banki-Horvath Date: Thu, 19 Dec 2019 22:19:18 +0100 Subject: [PATCH 03/25] autopilot: fix memChannelGraph channel edge addition This PR fixes an issue that happens when adding a new channel edge between two nodes in a memChannelGraph. Originally a channel edge held a node value which made the graph different when iterating from the two endpoints of an edge. This is simply fixed by holding pointers instead. --- autopilot/graph.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/autopilot/graph.go b/autopilot/graph.go index f02d0a8e..03d33f92 100644 --- a/autopilot/graph.go +++ b/autopilot/graph.go @@ -307,7 +307,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 +318,7 @@ var _ ChannelGraph = (*memChannelGraph)(nil) // implementation. func newMemChannelGraph() *memChannelGraph { return &memChannelGraph{ - graph: make(map[NodeID]memNode), + graph: make(map[NodeID]*memNode), } } @@ -360,14 +360,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 +381,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 +394,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 +408,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 +446,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{ From e745798a6ec04558291e72395dd0171051e1f459 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Tue, 17 Dec 2019 14:26:23 -0800 Subject: [PATCH 04/25] zpay32: update spec test vectors, write features last This commit updates the BOLT11 test vectors to use the updated versions from the spec (with TLV bit set). This also pointed out that the ordering was inconsistent with the spec, in that the payment secret should be serialized before the feature vector. --- zpay32/invoice.go | 16 ++++++++-------- zpay32/invoice_test.go | 21 +++++++++++++++------ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/zpay32/invoice.go b/zpay32/invoice.go index 1677fa8d..b76924c0 100644 --- a/zpay32/invoice.go +++ b/zpay32/invoice.go @@ -1094,6 +1094,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) @@ -1106,14 +1114,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 } diff --git a/zpay32/invoice_test.go b/zpay32/invoice_test.go index 9a34b590..923e692e 100644 --- a/zpay32/invoice_test.go +++ b/zpay32/invoice_test.go @@ -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, ), } From e6db31f267b25f3f7d9ec5f9fea911d2bc144770 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 19 Dec 2019 16:25:59 -0800 Subject: [PATCH 05/25] routerrpc: allow dest_features graph fallback --- lnrpc/routerrpc/router.pb.go | 4 +++- lnrpc/routerrpc/router.proto | 4 +++- lnrpc/routerrpc/router_backend.go | 10 +++++++++- lnrpc/rpc.pb.go | 4 +++- lnrpc/rpc.proto | 4 +++- lnrpc/rpc.swagger.json | 2 +- 6 files changed, 22 insertions(+), 6 deletions(-) diff --git a/lnrpc/routerrpc/router.pb.go b/lnrpc/routerrpc/router.pb.go index 4f872cfc..b80c3f2c 100644 --- a/lnrpc/routerrpc/router.pb.go +++ b/lnrpc/routerrpc/router.pb.go @@ -258,7 +258,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:"-"` diff --git a/lnrpc/routerrpc/router.proto b/lnrpc/routerrpc/router.proto index 3019eaeb..aa0d9676 100644 --- a/lnrpc/routerrpc/router.proto +++ b/lnrpc/routerrpc/router.proto @@ -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; } diff --git a/lnrpc/routerrpc/router_backend.go b/lnrpc/routerrpc/router_backend.go index acd3125a..79976ca3 100644 --- a/lnrpc/routerrpc/router_backend.go +++ b/lnrpc/routerrpc/router_backend.go @@ -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)) diff --git a/lnrpc/rpc.pb.go b/lnrpc/rpc.pb.go index cf4b5095..42829000 100644 --- a/lnrpc/rpc.pb.go +++ b/lnrpc/rpc.pb.go @@ -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:"-"` diff --git a/lnrpc/rpc.proto b/lnrpc/rpc.proto index 1aad92eb..6665bf3a 100644 --- a/lnrpc/rpc.proto +++ b/lnrpc/rpc.proto @@ -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; } diff --git a/lnrpc/rpc.swagger.json b/lnrpc/rpc.swagger.json index 643a9f9e..a817a5e1 100644 --- a/lnrpc/rpc.swagger.json +++ b/lnrpc/rpc.swagger.json @@ -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." } } }, From a3a1b488a68724dee96e91576da84cced6238e7d Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 20 Dec 2019 08:55:45 +0100 Subject: [PATCH 06/25] routing: fix gap in result interpretation Fixes an off by one error that skipped assigning a success pair. Added a test that previously failed because of this bug. --- routing/result_interpretation.go | 8 +++--- routing/result_interpretation_test.go | 36 ++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/routing/result_interpretation.go b/routing/result_interpretation.go index 7b3d539a..284cc19f 100644 --- a/routing/result_interpretation.go +++ b/routing/result_interpretation.go @@ -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, @@ -223,7 +221,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) } diff --git a/routing/result_interpretation_test.go b/routing/result_interpretation_test.go index 42f3ec87..a3d67489 100644 --- a/routing/result_interpretation_test.go +++ b/routing/result_interpretation_test.go @@ -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. From 95ddab57faa611ecde2d3ab6402d1854ae42b0e0 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 20 Dec 2019 10:14:13 +0100 Subject: [PATCH 07/25] channeldb: add tx argument for FetchLightningNode To allow execution within an existing tx. --- autopilot/graph.go | 2 +- channeldb/graph.go | 21 +++++++++++++++++---- channeldb/graph_test.go | 15 ++++++++------- routing/pathfind.go | 2 +- routing/pathfind_test.go | 4 ++-- routing/router.go | 2 +- routing/router_test.go | 4 ++-- rpcserver.go | 2 +- server.go | 2 +- 9 files changed, 34 insertions(+), 20 deletions(-) diff --git a/autopilot/graph.go b/autopilot/graph.go index c63d6650..5792443f 100644 --- a/autopilot/graph.go +++ b/autopilot/graph.go @@ -153,7 +153,7 @@ func (d *databaseChannelGraph) addRandChannel(node1, node2 *btcec.PublicKey, return nil, err } - dbNode, err := d.db.FetchLightningNode(vertex) + dbNode, err := d.db.FetchLightningNode(nil, vertex) switch { case err == channeldb.ErrGraphNodeNotFound: fallthrough diff --git a/channeldb/graph.go b/channeldb/graph.go index 3ec06981..61ca7a5f 100644 --- a/channeldb/graph.go +++ b/channeldb/graph.go @@ -2179,11 +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(nodePub route.Vertex) (*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 - 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) @@ -2210,7 +2216,14 @@ func (c *ChannelGraph) FetchLightningNode(nodePub route.Vertex) (*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 } diff --git a/channeldb/graph_test.go b/channeldb/graph_test.go index 27e29871..d79f15dd 100644 --- a/channeldb/graph_test.go +++ b/channeldb/graph_test.go @@ -104,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) } @@ -128,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!") } @@ -160,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) } @@ -192,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!") } @@ -2387,7 +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. - if _, err := graph.FetchLightningNode(node3.PubKeyBytes); err == nil { + _, err = graph.FetchLightningNode(nil, node3.PubKeyBytes) + if err == nil { t.Fatalf("node 3 should have been deleted!") } } @@ -2429,7 +2430,7 @@ func TestAddChannelEdgeShellNodes(t *testing.T) { // Ensure that node1 was inserted as a full node, while node2 only has // a shell node present. - node1, err = graph.FetchLightningNode(node1.PubKeyBytes) + node1, err = graph.FetchLightningNode(nil, node1.PubKeyBytes) if err != nil { t.Fatalf("unable to fetch node1: %v", err) } @@ -2437,7 +2438,7 @@ func TestAddChannelEdgeShellNodes(t *testing.T) { t.Fatalf("have shell announcement for node1, shouldn't") } - node2, err = graph.FetchLightningNode(node2.PubKeyBytes) + node2, err = graph.FetchLightningNode(nil, node2.PubKeyBytes) if err != nil { t.Fatalf("unable to fetch node2: %v", err) } diff --git a/routing/pathfind.go b/routing/pathfind.go index 9eca590a..1049e5da 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -446,7 +446,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, // we have for the target node from our graph. features := r.DestFeatures if features == nil { - targetNode, err := g.graph.FetchLightningNode(target) + targetNode, err := g.graph.FetchLightningNode(tx, target) switch { // If the node exists and has features, use them directly. diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 3a26428c..1bec640a 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -2070,7 +2070,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"] - bobNode, err := ctx.graph.FetchLightningNode(bob) + bobNode, err := ctx.graph.FetchLightningNode(nil, bob) if err != nil { t.Fatalf("unable to find bob: %v", err) } @@ -2119,7 +2119,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"] - aliceNode, err := ctx.graph.FetchLightningNode(alice) + aliceNode, err := ctx.graph.FetchLightningNode(nil, alice) if err != nil { t.Fatalf("unable to find alice: %v", err) } diff --git a/routing/router.go b/routing/router.go index 87a73937..085db61f 100644 --- a/routing/router.go +++ b/routing/router.go @@ -2089,7 +2089,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) { - return r.cfg.Graph.FetchLightningNode(node) + return r.cfg.Graph.FetchLightningNode(nil, node) } // ForEachNode is used to iterate over every node in router topology. diff --git a/routing/router_test.go b/routing/router_test.go index 483abec4..26803492 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -1319,7 +1319,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { t.Fatalf("unable to find any routes: %v", err) } - copy1, err := ctx.graph.FetchLightningNode(pub1) + 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(pub2) + copy2, err := ctx.graph.FetchLightningNode(nil, pub2) if err != nil { t.Fatalf("unable to fetch node: %v", err) } diff --git a/rpcserver.go b/rpcserver.go index 703ce25a..1ae6f2ad 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -4239,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 } diff --git a/server.go b/server.go index 58f8f17b..dd97b2be 100644 --- a/server.go +++ b/server.go @@ -3374,7 +3374,7 @@ func (s *server) fetchNodeAdvertisedAddr(pub *btcec.PublicKey) (net.Addr, error) return nil, err } - node, err := s.chanDB.ChannelGraph().FetchLightningNode(vertex) + node, err := s.chanDB.ChannelGraph().FetchLightningNode(nil, vertex) if err != nil { return nil, err } From e546a2c42ba7cb91c44d803864a95b13d4ca5597 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 18 Dec 2019 08:16:53 +0100 Subject: [PATCH 08/25] routing: more efficient feature validation Also prepares for payload size tracking during pathfinding --- routing/pathfind.go | 92 ++++++++++++++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 30 deletions(-) diff --git a/routing/pathfind.go b/routing/pathfind.go index 1049e5da..2f64e311 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -670,36 +670,6 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, edge.ChannelID) } - switch { - - // 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 - } - } - // All conditions are met and this new tentative distance is // better than the current best known distance to this node. // The new better distance is recorded, and also our "next hop" @@ -724,6 +694,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++ @@ -771,6 +779,17 @@ 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) @@ -818,6 +837,19 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, } } + // 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 + // 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 From 72a63839753e11cfb5a6ead66c142f16f13d681c Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 17 Dec 2019 11:55:03 +0100 Subject: [PATCH 09/25] routing: use absolute final expiry in pathfinding --- routing/heap.go | 7 ++++--- routing/pathfind.go | 16 ++++++++++------ routing/pathfind_test.go | 34 +++++++++++++++++---------------- routing/payment_session.go | 4 +++- routing/payment_session_test.go | 4 ++-- routing/router.go | 18 +++++++++-------- routing/router_test.go | 2 +- 7 files changed, 48 insertions(+), 37 deletions(-) diff --git a/routing/heap.go b/routing/heap.go index 39a6f4ed..e7e1b587 100644 --- a/routing/heap.go +++ b/routing/heap.go @@ -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. diff --git a/routing/pathfind.go b/routing/pathfind.go index 2f64e311..f74d00d9 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -47,7 +47,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 @@ -411,7 +412,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 @@ -542,10 +543,14 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, weight: 0, node: target, amountToReceive: amt, - incomingCltv: 0, + incomingCltv: finalHtlcExpiry, probability: 1, } + // 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, @@ -590,11 +595,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 } diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 1bec640a..c47f7348 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -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, ) } @@ -1383,7 +1385,7 @@ func TestNewRoutePathTooLong(t *testing.T) { graph: graph.graph, }, noRestrictions, testPathFindingConfig, - sourceNode.PubKeyBytes, target, paymentAmt, + sourceNode.PubKeyBytes, target, paymentAmt, 0, ) if err != nil { t.Fatalf("path should have been found") @@ -1397,7 +1399,7 @@ func TestNewRoutePathTooLong(t *testing.T) { graph: graph.graph, }, noRestrictions, testPathFindingConfig, - sourceNode.PubKeyBytes, target, paymentAmt, + sourceNode.PubKeyBytes, target, paymentAmt, 0, ) if err == nil { t.Fatalf("should not have been able to find path, supposed to be "+ @@ -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) @@ -2872,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, ) } diff --git a/routing/payment_session.go b/routing/payment_session.go index e5474ddf..47732e01 100644 --- a/routing/payment_session.go +++ b/routing/payment_session.go @@ -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 diff --git a/routing/payment_session_test.go b/routing/payment_session_test.go index 14f98449..55549c44 100644 --- a/routing/payment_session_test.go +++ b/routing/payment_session_test.go @@ -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). diff --git a/routing/router.go b/routing/router.go index 085db61f..55cefaae 100644 --- a/routing/router.go +++ b/routing/router.go @@ -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), diff --git a/routing/router_test.go b/routing/router_test.go index 26803492..6fd9a630 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -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) From e8fd05e8e3f57d3afbf1031031dcc622a41759a1 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 16 Dec 2019 21:34:29 +0100 Subject: [PATCH 10/25] tlv+record+routing: add payload size calculation --- record/mpp.go | 5 +++ routing/route/route.go | 47 +++++++++++++++++++++++++++ routing/route/route_test.go | 63 ++++++++++++++++++++++++++++++++++++- tlv/varint.go | 7 +++++ 4 files changed, 121 insertions(+), 1 deletion(-) diff --git a/record/mpp.go b/record/mpp.go index 38877caf..6e260d54 100644 --- a/record/mpp.go +++ b/record/mpp.go @@ -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) diff --git a/routing/route/route.go b/routing/route/route.go index ff043ef8..90a407e3 100644 --- a/routing/route/route.go +++ b/routing/route/route.go @@ -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 diff --git a/routing/route/route_test.go b/routing/route/route_test.go index 2894cbdc..6c32d8be 100644 --- a/routing/route/route_test.go +++ b/routing/route/route_test.go @@ -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) + } + } +} diff --git a/tlv/varint.go b/tlv/varint.go index 3888bfcb..38c7a7cd 100644 --- a/tlv/varint.go +++ b/tlv/varint.go @@ -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)) +} From 513341516eebc1290dc109d57e0aaf2ca1fbd2a5 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 18 Dec 2019 10:28:21 +0100 Subject: [PATCH 11/25] routing: fix max route length test Enable the test again and use a programmatically built network. --- routing/pathfind_test.go | 73 ++--- routing/testdata/excessive_hops.json | 410 --------------------------- 2 files changed, 31 insertions(+), 452 deletions(-) delete mode 100644 routing/testdata/excessive_hops.json diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index c47f7348..bddb5fbf 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -1360,53 +1360,42 @@ 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, 0, - ) - 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 != errMaxHopsExceeded { + t.Fatalf("expected route too long, 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, 0, - ) - 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)) - } - } func TestPathNotAvailable(t *testing.T) { diff --git a/routing/testdata/excessive_hops.json b/routing/testdata/excessive_hops.json deleted file mode 100644 index 3094f1b4..00000000 --- a/routing/testdata/excessive_hops.json +++ /dev/null @@ -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 - } - ] -} From b760b252290a3e2d0cae0dd1dc6502eeea6e632d Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 16 Dec 2019 14:22:42 +0100 Subject: [PATCH 12/25] routing: limit routing info size during pathfinding Also the max hop count check can be removed, because the real bound is the payload size. By moving the check inside the search loop, we now also backtrack when we hit the limit. --- routing/heap.go | 4 ++ routing/pathfind.go | 72 +++++++++++++++++++++++++----------- routing/pathfind_test.go | 15 +++++++- routing/payment_lifecycle.go | 1 - 4 files changed, 68 insertions(+), 24 deletions(-) diff --git a/routing/heap.go b/routing/heap.go index e7e1b587..f6869663 100644 --- a/routing/heap.go +++ b/routing/heap.go @@ -40,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 diff --git a/routing/pathfind.go b/routing/pathfind.go index f74d00d9..1c1eea9f 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -8,6 +8,7 @@ import ( "time" "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" @@ -16,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 @@ -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") @@ -529,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 @@ -545,6 +553,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, amountToReceive: amt, incomingCltv: finalHtlcExpiry, probability: 1, + routingInfoSize: finalHop.PayloadSize(0), } // Calculate the absolute cltv limit. Use uint64 to prevent an overflow @@ -554,6 +563,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, // 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++ @@ -674,6 +684,34 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, edge.ChannelID) } + // 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, + ) + + 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 // better than the current best known distance to this node. // The new better distance is recorded, and also our "next hop" @@ -686,6 +724,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, incomingCltv: incomingCltv, probability: probability, nextHop: edge, + routingInfoSize: routingInfoSize, } distance[fromVertex] = withDist @@ -796,7 +835,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, // 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 { @@ -854,17 +893,8 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, // findPath, and avoid using ChannelEdgePolicy altogether. pathEdges[len(pathEdges)-1].Node.Features = features - // 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 - } - log.Debugf("Found route: probability=%v, hops=%v, fee=%v\n", - distance[source].probability, numEdges, + distance[source].probability, len(pathEdges), distance[source].amountToReceive-amt) return pathEdges, nil diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index bddb5fbf..910d13db 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -1393,8 +1393,19 @@ func TestNewRoutePathTooLong(t *testing.T) { // Assert that finding a 21 hop route fails. node21 := ctx.keyFromAlias("node-21") _, err = ctx.findPath(node21, payAmt) - if err != errMaxHopsExceeded { - t.Fatalf("expected route too long, but got %v", err) + if err != errNoPathFound { + t.Fatalf("not route error expected, but got %v", err) + } + + // 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) } } diff --git a/routing/payment_lifecycle.go b/routing/payment_lifecycle.go index b5203671..2283ca05 100644 --- a/routing/payment_lifecycle.go +++ b/routing/payment_lifecycle.go @@ -196,7 +196,6 @@ func errorToPaymentFailure(err error) channeldb.FailureReason { errNoTlvPayload, errNoPaymentAddr, errNoPathFound, - errMaxHopsExceeded, errPrebuiltRouteTried: return channeldb.FailureReasonNoRoute From c84e57a5228897dd62c7adcd05024be90ec4c65a Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 20 Dec 2019 10:28:48 +0100 Subject: [PATCH 13/25] routing: remove unnecessary newline --- routing/pathfind.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routing/pathfind.go b/routing/pathfind.go index 1c1eea9f..a6bd5878 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -893,7 +893,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, // findPath, and avoid using ChannelEdgePolicy altogether. pathEdges[len(pathEdges)-1].Node.Features = features - log.Debugf("Found route: probability=%v, hops=%v, fee=%v\n", + log.Debugf("Found route: probability=%v, hops=%v, fee=%v", distance[source].probability, len(pathEdges), distance[source].amountToReceive-amt) From db85c51b77bdaf25ee1ae2029617e21919c90da1 Mon Sep 17 00:00:00 2001 From: carla Date: Fri, 20 Dec 2019 12:25:07 +0200 Subject: [PATCH 14/25] invoices: export and rename update result This commit exports UpdateResult so that calling functions can interpret the outcome of an invoice update. This is useful for determining the wire failure required (fail invalid details or mpp_timeout once implemented) and for notifying specific htlc failure details. The enum is renamed to ResolutionResult. --- invoices/invoiceregistry.go | 2 +- invoices/update.go | 171 ++++++++++++++++++++++-------------- 2 files changed, 108 insertions(+), 65 deletions(-) diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go index d4e77ad5..ae93a635 100644 --- a/invoices/invoiceregistry.go +++ b/invoices/invoiceregistry.go @@ -706,7 +706,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( diff --git a/invoices/update.go b/invoices/update.go index 913caeb0..6cf58d0d 100644 --- a/invoices/update.go +++ b/invoices/update.go @@ -8,86 +8,129 @@ 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 + + // 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 + + // 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 ) // 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 +153,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 +183,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 +199,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 +229,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 +240,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 +264,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 +274,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 +282,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 +301,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 +314,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 +345,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 +358,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 +366,5 @@ func updateLegacy(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) ( Preimage: inv.Terms.PaymentPreimage, } - return &update, resultSettled, nil + return &update, ResultSettled, nil } From 2c1eb17192e18dd5489abbafd3e3bd1551984e28 Mon Sep 17 00:00:00 2001 From: carla Date: Fri, 20 Dec 2019 12:25:07 +0200 Subject: [PATCH 15/25] mutli: rename HodlEvent to HtlcResolution This commit renames HodlEvent to HtlcResolution to better reflect the fact that the struct is only used for htlc settles and cancels, and that it is not specifically used for hodl invoices. --- .../htlc_incoming_contest_resolver.go | 10 ++-- contractcourt/htlc_incoming_resolver_test.go | 8 ++-- contractcourt/interfaces.go | 4 +- contractcourt/mock_registry_test.go | 4 +- htlcswitch/interfaces.go | 4 +- htlcswitch/link.go | 46 ++++++++++--------- htlcswitch/mock.go | 2 +- invoices/invoiceregistry.go | 37 +++++++-------- invoices/invoiceregistry_test.go | 28 +++++------ 9 files changed, 73 insertions(+), 70 deletions(-) diff --git a/contractcourt/htlc_incoming_contest_resolver.go b/contractcourt/htlc_incoming_contest_resolver.go index abedcc14..3c66fdc6 100644 --- a/contractcourt/htlc_incoming_contest_resolver.go +++ b/contractcourt/htlc_incoming_contest_resolver.go @@ -167,9 +167,9 @@ 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, + // Define closure to process htlc resolutions either direct or triggered by + // later notification. + processHodlEvent := func(e invoices.HtlcResolution) (ContractResolver, error) { if e.Preimage == nil { @@ -252,9 +252,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 processHodlEvent(htlcResolution) case newBlock, ok := <-blockEpochs.Epochs: if !ok { diff --git a/contractcourt/htlc_incoming_resolver_test.go b/contractcourt/htlc_incoming_resolver_test.go index 850126c0..1c6628a3 100644 --- a/contractcourt/htlc_incoming_resolver_test.go +++ b/contractcourt/htlc_incoming_resolver_test.go @@ -96,7 +96,7 @@ func TestHtlcIncomingResolverExitSettle(t *testing.T) { defer timeout(t)() ctx := newIncomingResolverTestContext(t) - ctx.registry.notifyEvent = &invoices.HodlEvent{ + ctx.registry.notifyEvent = &invoices.HtlcResolution{ CircuitKey: testResCircuitKey, Preimage: &testResPreimage, } @@ -126,7 +126,7 @@ func TestHtlcIncomingResolverExitCancel(t *testing.T) { defer timeout(t)() ctx := newIncomingResolverTestContext(t) - ctx.registry.notifyEvent = &invoices.HodlEvent{ + ctx.registry.notifyEvent = &invoices.HtlcResolution{ CircuitKey: testResCircuitKey, } ctx.resolve() @@ -143,7 +143,7 @@ func TestHtlcIncomingResolverExitSettleHodl(t *testing.T) { ctx.resolve() notifyData := <-ctx.registry.notifyChan - notifyData.hodlChan <- invoices.HodlEvent{ + notifyData.hodlChan <- invoices.HtlcResolution{ CircuitKey: testResCircuitKey, Preimage: &testResPreimage, } @@ -172,7 +172,7 @@ func TestHtlcIncomingResolverExitCancelHodl(t *testing.T) { ctx := newIncomingResolverTestContext(t) ctx.resolve() notifyData := <-ctx.registry.notifyChan - notifyData.hodlChan <- invoices.HodlEvent{ + notifyData.hodlChan <- invoices.HtlcResolution{ CircuitKey: testResCircuitKey, } ctx.waitForResult(false) diff --git a/contractcourt/interfaces.go b/contractcourt/interfaces.go index 45e9b0bd..086a2aee 100644 --- a/contractcourt/interfaces.go +++ b/contractcourt/interfaces.go @@ -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{}) } diff --git a/contractcourt/mock_registry_test.go b/contractcourt/mock_registry_test.go index 43195249..8ccd0f6c 100644 --- a/contractcourt/mock_registry_test.go +++ b/contractcourt/mock_registry_test.go @@ -18,13 +18,13 @@ type notifyExitHopData struct { type mockRegistry struct { notifyChan chan notifyExitHopData notifyErr error - notifyEvent *invoices.HodlEvent + notifyEvent *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, diff --git a/htlcswitch/interfaces.go b/htlcswitch/interfaces.go index 3b9ac6df..bb34b283 100644 --- a/htlcswitch/interfaces.go +++ b/htlcswitch/interfaces.go @@ -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{}) } diff --git a/htlcswitch/link.go b/htlcswitch/link.go index ce4d0073..a262ff21 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -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.processHodlEvent(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,29 +1187,29 @@ 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, +// processHodlEvent 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) processHodlEvent(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) + 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", circuitKey) // 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), + htlc.pd.Amount, uint32(resolution.AcceptHeight), ) l.sendHTLCError( diff --git a/htlcswitch/mock.go b/htlcswitch/mock.go index 1d0731e8..5c905822 100644 --- a/htlcswitch/mock.go +++ b/htlcswitch/mock.go @@ -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, diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go index ae93a635..bc1b38b0 100644 --- a/invoices/invoiceregistry.go +++ b/invoices/invoiceregistry.go @@ -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 @@ -652,7 +652,7 @@ func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash, return fmt.Errorf("htlc %v not found", key) } if htlc.State == channeldb.HtlcStateCanceled { - i.notifyHodlSubscribers(HodlEvent{ + i.notifyHodlSubscribers(HtlcResolution{ CircuitKey: key, AcceptHeight: int32(htlc.AcceptHeight), Preimage: nil, @@ -679,7 +679,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() @@ -745,7 +745,7 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, // If it isn't recorded, cancel htlc. if !ok { - return &HodlEvent{ + return &HtlcResolution{ CircuitKey: circuitKey, AcceptHeight: currentHeight, }, nil @@ -759,7 +759,7 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, switch invoiceHtlc.State { case channeldb.HtlcStateCanceled: - return &HodlEvent{ + return &HtlcResolution{ CircuitKey: circuitKey, AcceptHeight: acceptHeight, }, nil @@ -773,14 +773,14 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, continue } - i.notifyHodlSubscribers(HodlEvent{ + i.notifyHodlSubscribers(HtlcResolution{ CircuitKey: key, Preimage: &invoice.Terms.PaymentPreimage, AcceptHeight: int32(htlc.AcceptHeight), }) } - return &HodlEvent{ + return &HtlcResolution{ CircuitKey: circuitKey, Preimage: &invoice.Terms.PaymentPreimage, AcceptHeight: acceptHeight, @@ -854,7 +854,7 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error { continue } - i.notifyHodlSubscribers(HodlEvent{ + i.notifyHodlSubscribers(HtlcResolution{ CircuitKey: key, Preimage: &preimage, AcceptHeight: int32(htlc.AcceptHeight), @@ -932,7 +932,7 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash, continue } - i.notifyHodlSubscribers(HodlEvent{ + i.notifyHodlSubscribers(HtlcResolution{ CircuitKey: key, AcceptHeight: int32(htlc.AcceptHeight), }) @@ -1201,9 +1201,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 +1214,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. diff --git a/invoices/invoiceregistry_test.go b/invoices/invoiceregistry_test.go index e47eaa37..76b60003 100644 --- a/invoices/invoiceregistry_test.go +++ b/invoices/invoiceregistry_test.go @@ -281,7 +281,7 @@ func TestCancelInvoice(t *testing.T) { } if event.Preimage != nil { - t.Fatal("expected cancel hodl event") + t.Fatal("expected cancel htlc resolution") } if event.AcceptHeight != testCurrentHeight { t.Fatalf("expected acceptHeight %v, but got %v", @@ -421,11 +421,11 @@ 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) } @@ -513,9 +513,9 @@ 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 @@ -538,10 +538,10 @@ func TestCancelHoldInvoice(t *testing.T) { } // 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() @@ -593,8 +593,8 @@ func TestSettleMpp(t *testing.T) { // Simulate mpp timeout releasing htlc 1. ctx.clock.SetTime(testTime.Add(30 * time.Second)) - hodlEvent := (<-hodlChan1).(HodlEvent) - if hodlEvent.Preimage != nil { + htlcResolution := (<-hodlChan1).(HtlcResolution) + if htlcResolution.Preimage != nil { t.Fatal("expected cancel event") } From 2c44afa3a7d300068a902a193764e9053dff297f Mon Sep 17 00:00:00 2001 From: carla Date: Fri, 20 Dec 2019 12:25:07 +0200 Subject: [PATCH 16/25] contractcourt: rename mock notifyEvent to notifyResolution --- contractcourt/htlc_incoming_resolver_test.go | 4 ++-- contractcourt/mock_registry_test.go | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contractcourt/htlc_incoming_resolver_test.go b/contractcourt/htlc_incoming_resolver_test.go index 1c6628a3..a27c5883 100644 --- a/contractcourt/htlc_incoming_resolver_test.go +++ b/contractcourt/htlc_incoming_resolver_test.go @@ -96,7 +96,7 @@ func TestHtlcIncomingResolverExitSettle(t *testing.T) { defer timeout(t)() ctx := newIncomingResolverTestContext(t) - ctx.registry.notifyEvent = &invoices.HtlcResolution{ + ctx.registry.notifyResolution = &invoices.HtlcResolution{ CircuitKey: testResCircuitKey, Preimage: &testResPreimage, } @@ -126,7 +126,7 @@ func TestHtlcIncomingResolverExitCancel(t *testing.T) { defer timeout(t)() ctx := newIncomingResolverTestContext(t) - ctx.registry.notifyEvent = &invoices.HtlcResolution{ + ctx.registry.notifyResolution = &invoices.HtlcResolution{ CircuitKey: testResCircuitKey, } ctx.resolve() diff --git a/contractcourt/mock_registry_test.go b/contractcourt/mock_registry_test.go index 8ccd0f6c..dca27d62 100644 --- a/contractcourt/mock_registry_test.go +++ b/contractcourt/mock_registry_test.go @@ -16,9 +16,9 @@ type notifyExitHopData struct { } type mockRegistry struct { - notifyChan chan notifyExitHopData - notifyErr error - notifyEvent *invoices.HtlcResolution + notifyChan chan notifyExitHopData + notifyErr error + notifyResolution *invoices.HtlcResolution } func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash, @@ -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{}) {} From 6464f0dda0c2db8dfd23d10de68b5327fb3329f3 Mon Sep 17 00:00:00 2001 From: carla Date: Fri, 20 Dec 2019 12:25:07 +0200 Subject: [PATCH 17/25] invoices: rename event to resolution in tests --- invoices/invoiceregistry_test.go | 86 ++++++++++++++++---------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/invoices/invoiceregistry_test.go b/invoices/invoiceregistry_test.go index 76b60003..07e22823 100644 --- a/invoices/invoiceregistry_test.go +++ b/invoices/invoiceregistry_test.go @@ -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,12 +74,12 @@ 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) } // Settle invoice with a slightly higher amount. @@ -120,42 +120,42 @@ 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") } // 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") } // 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") } // Check that settled amount is equal to the sum of values of the htlcs @@ -177,7 +177,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 +270,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 +280,12 @@ func TestCancelInvoice(t *testing.T) { t.Fatal("expected settlement of a canceled invoice to succeed") } - if event.Preimage != nil { + 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) } } @@ -354,52 +354,52 @@ 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") } @@ -427,7 +427,7 @@ func TestSettleHoldInvoice(t *testing.T) { } if htlcResolution.AcceptHeight != testCurrentHeight { t.Fatalf("expected acceptHeight %v, but got %v", - testCurrentHeight, event.AcceptHeight) + testCurrentHeight, resolution.AcceptHeight) } // We expect a settled notification to be sent out for both all and @@ -496,14 +496,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") } @@ -521,19 +521,19 @@ func TestCancelHoldInvoice(t *testing.T) { // 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) } } @@ -578,7 +578,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,7 +586,7 @@ func TestSettleMpp(t *testing.T) { if err != nil { t.Fatal(err) } - if event != nil { + if resolution != nil { t.Fatal("expected no direct resolution") } @@ -595,12 +595,12 @@ func TestSettleMpp(t *testing.T) { htlcResolution := (<-hodlChan1).(HtlcResolution) if htlcResolution.Preimage != nil { - t.Fatal("expected cancel event") + t.Fatal("expected cancel resolution") } // 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 +608,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,8 +622,8 @@ 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") } // Check that settled amount is equal to the sum of values of the htlcs From ebfbc489735aeef2b7e1d8662701c4242e548c83 Mon Sep 17 00:00:00 2001 From: carla Date: Fri, 20 Dec 2019 12:25:07 +0200 Subject: [PATCH 18/25] link+contractcourt: rename processHodlEvent to processHtlcResolution --- contractcourt/htlc_incoming_contest_resolver.go | 8 ++++---- htlcswitch/link.go | 11 ++++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/contractcourt/htlc_incoming_contest_resolver.go b/contractcourt/htlc_incoming_contest_resolver.go index 3c66fdc6..9d026cd0 100644 --- a/contractcourt/htlc_incoming_contest_resolver.go +++ b/contractcourt/htlc_incoming_contest_resolver.go @@ -169,8 +169,8 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) { // Define closure to process htlc resolutions either direct or triggered by // later notification. - processHodlEvent := func(e invoices.HtlcResolution) (ContractResolver, - error) { + processHtlcResolution := func(e invoices.HtlcResolution) ( + ContractResolver, error) { if e.Preimage == nil { log.Infof("%T(%v): Exit hop HTLC canceled "+ @@ -212,7 +212,7 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) { // Resolve the htlc directly if possible. if event != nil { - return processHodlEvent(*event) + return processHtlcResolution(*event) } default: return nil, err @@ -254,7 +254,7 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) { case hodlItem := <-hodlChan: htlcResolution := hodlItem.(invoices.HtlcResolution) - return processHodlEvent(htlcResolution) + return processHtlcResolution(htlcResolution) case newBlock, ok := <-blockEpochs.Epochs: if !ok { diff --git a/htlcswitch/link.go b/htlcswitch/link.go index a262ff21..a1913381 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -1164,7 +1164,7 @@ loop: return fmt.Errorf("hodl htlc not found: %v", circuitKey) } - if err := l.processHodlEvent(htlcResolution, hodlHtlc); err != nil { + if err := l.processHtlcResolution(htlcResolution, hodlHtlc); err != nil { return err } @@ -1187,9 +1187,10 @@ loop: return nil } -// processHodlEvent 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) processHodlEvent(resolution invoices.HtlcResolution, +// 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 := resolution.CircuitKey @@ -2851,7 +2852,7 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor, } // Process the received resolution. - return l.processHodlEvent(*event, htlc) + return l.processHtlcResolution(*event, htlc) } // settleHTLC settles the HTLC on the channel. From 273cd84355a5a6719d5b2e3b3d0ea5c7c9285082 Mon Sep 17 00:00:00 2001 From: carla Date: Fri, 20 Dec 2019 12:25:07 +0200 Subject: [PATCH 19/25] invoices+contractcourt: add HtlcResolution constructor This commit adds a constructor for HtlcResolution creation to enforce provision of all relevant values when an event is created. A custom construstor which also takes a preimage is added for settle events. --- contractcourt/htlc_incoming_resolver_test.go | 38 +++++----- invoices/invoiceregistry.go | 80 ++++++++++++-------- 2 files changed, 67 insertions(+), 51 deletions(-) diff --git a/contractcourt/htlc_incoming_resolver_test.go b/contractcourt/htlc_incoming_resolver_test.go index a27c5883..ef5277f4 100644 --- a/contractcourt/htlc_incoming_resolver_test.go +++ b/contractcourt/htlc_incoming_resolver_test.go @@ -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 @@ -96,10 +97,10 @@ func TestHtlcIncomingResolverExitSettle(t *testing.T) { defer timeout(t)() ctx := newIncomingResolverTestContext(t) - ctx.registry.notifyResolution = &invoices.HtlcResolution{ - CircuitKey: testResCircuitKey, - Preimage: &testResPreimage, - } + ctx.registry.notifyResolution = invoices.NewSettleResolution( + testResPreimage, testResCircuitKey, testAcceptHeight, + ) + ctx.resolve() data := <-ctx.registry.notifyChan @@ -126,9 +127,10 @@ func TestHtlcIncomingResolverExitCancel(t *testing.T) { defer timeout(t)() ctx := newIncomingResolverTestContext(t) - ctx.registry.notifyResolution = &invoices.HtlcResolution{ - CircuitKey: testResCircuitKey, - } + ctx.registry.notifyResolution = invoices.NewFailureResolution( + testResCircuitKey, testAcceptHeight, + ) + ctx.resolve() ctx.waitForResult(false) } @@ -143,10 +145,9 @@ func TestHtlcIncomingResolverExitSettleHodl(t *testing.T) { ctx.resolve() notifyData := <-ctx.registry.notifyChan - notifyData.hodlChan <- invoices.HtlcResolution{ - CircuitKey: testResCircuitKey, - Preimage: &testResPreimage, - } + notifyData.hodlChan <- *invoices.NewSettleResolution( + testResPreimage, testResCircuitKey, testAcceptHeight, + ) ctx.waitForResult(true) } @@ -172,9 +173,10 @@ func TestHtlcIncomingResolverExitCancelHodl(t *testing.T) { ctx := newIncomingResolverTestContext(t) ctx.resolve() notifyData := <-ctx.registry.notifyChan - notifyData.hodlChan <- invoices.HtlcResolution{ - CircuitKey: testResCircuitKey, - } + notifyData.hodlChan <- *invoices.NewFailureResolution( + testResCircuitKey, testAcceptHeight, + ) + ctx.waitForResult(false) } diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go index bc1b38b0..a65799c5 100644 --- a/invoices/invoiceregistry.go +++ b/invoices/invoiceregistry.go @@ -50,6 +50,28 @@ type HtlcResolution struct { AcceptHeight int32 } +// NewFailureResolution returns a htlc failure resolution. +func NewFailureResolution(key channeldb.CircuitKey, + acceptHeight int32) *HtlcResolution { + + return &HtlcResolution{ + CircuitKey: key, + AcceptHeight: acceptHeight, + } +} + +// NewSettleResolution returns a htlc resolution which is associated with a +// settle. +func NewSettleResolution(preimage lntypes.Preimage, key channeldb.CircuitKey, + acceptHeight int32) *HtlcResolution { + + return &HtlcResolution{ + Preimage: &preimage, + CircuitKey: key, + AcceptHeight: acceptHeight, + } +} + // RegistryConfig contains the configuration parameters for invoice registry. type RegistryConfig struct { // FinalCltvRejectDelta defines the number of blocks before the expiry @@ -652,11 +674,9 @@ func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash, return fmt.Errorf("htlc %v not found", key) } if htlc.State == channeldb.HtlcStateCanceled { - i.notifyHodlSubscribers(HtlcResolution{ - CircuitKey: key, - AcceptHeight: int32(htlc.AcceptHeight), - Preimage: nil, - }) + i.notifyHodlSubscribers( + *NewFailureResolution(key, int32(htlc.AcceptHeight)), + ) } return nil } @@ -745,10 +765,7 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, // If it isn't recorded, cancel htlc. if !ok { - return &HtlcResolution{ - CircuitKey: circuitKey, - AcceptHeight: currentHeight, - }, nil + return NewFailureResolution(circuitKey, currentHeight), nil } // Determine accepted height of this htlc. If the htlc reached the @@ -759,10 +776,7 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, switch invoiceHtlc.State { case channeldb.HtlcStateCanceled: - return &HtlcResolution{ - CircuitKey: circuitKey, - AcceptHeight: acceptHeight, - }, nil + return NewFailureResolution(circuitKey, acceptHeight), nil case channeldb.HtlcStateSettled: // Also settle any previously accepted htlcs. The invoice state @@ -773,18 +787,17 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, continue } - i.notifyHodlSubscribers(HtlcResolution{ - CircuitKey: key, - Preimage: &invoice.Terms.PaymentPreimage, - AcceptHeight: int32(htlc.AcceptHeight), - }) + resolution := *NewSettleResolution( + invoice.Terms.PaymentPreimage, key, + acceptHeight, + ) + + i.notifyHodlSubscribers(resolution) } - return &HtlcResolution{ - CircuitKey: circuitKey, - Preimage: &invoice.Terms.PaymentPreimage, - AcceptHeight: acceptHeight, - }, nil + return NewSettleResolution( + invoice.Terms.PaymentPreimage, circuitKey, acceptHeight, + ), nil case channeldb.HtlcStateAccepted: // (Re)start the htlc timer if the invoice is still open. It can @@ -836,7 +849,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 +869,11 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error { continue } - i.notifyHodlSubscribers(HtlcResolution{ - CircuitKey: key, - Preimage: &preimage, - AcceptHeight: int32(htlc.AcceptHeight), - }) + resolution := *NewSettleResolution( + preimage, key, int32(htlc.AcceptHeight), + ) + + i.notifyHodlSubscribers(resolution) } i.notifyClients(hash, invoice, invoice.State) @@ -932,10 +947,9 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash, continue } - i.notifyHodlSubscribers(HtlcResolution{ - CircuitKey: key, - AcceptHeight: int32(htlc.AcceptHeight), - }) + i.notifyHodlSubscribers( + *NewFailureResolution(key, int32(htlc.AcceptHeight)), + ) } i.notifyClients(payHash, invoice, channeldb.ContractCanceled) From 7b5dda0417679bd9508afc651b713c958ddff603 Mon Sep 17 00:00:00 2001 From: carla Date: Fri, 20 Dec 2019 12:25:07 +0200 Subject: [PATCH 20/25] invoices: add resolution result to htlcResolution This commit adds the resolution result obtained while updating an invoice in the registry to htlcResolution. The field can be used by calling functions to determine the outcome of the update and act appropriately. --- contractcourt/htlc_incoming_resolver_test.go | 5 +- htlcswitch/link.go | 15 +++--- invoices/invoiceregistry.go | 55 ++++++++++++++------ invoices/invoiceregistry_test.go | 53 +++++++++++++++++-- invoices/update.go | 8 +++ 5 files changed, 109 insertions(+), 27 deletions(-) diff --git a/contractcourt/htlc_incoming_resolver_test.go b/contractcourt/htlc_incoming_resolver_test.go index ef5277f4..cce6343b 100644 --- a/contractcourt/htlc_incoming_resolver_test.go +++ b/contractcourt/htlc_incoming_resolver_test.go @@ -99,6 +99,7 @@ func TestHtlcIncomingResolverExitSettle(t *testing.T) { ctx := newIncomingResolverTestContext(t) ctx.registry.notifyResolution = invoices.NewSettleResolution( testResPreimage, testResCircuitKey, testAcceptHeight, + invoices.ResultReplayToSettled, ) ctx.resolve() @@ -129,6 +130,7 @@ func TestHtlcIncomingResolverExitCancel(t *testing.T) { ctx := newIncomingResolverTestContext(t) ctx.registry.notifyResolution = invoices.NewFailureResolution( testResCircuitKey, testAcceptHeight, + invoices.ResultInvoiceAlreadyCanceled, ) ctx.resolve() @@ -147,6 +149,7 @@ func TestHtlcIncomingResolverExitSettleHodl(t *testing.T) { notifyData := <-ctx.registry.notifyChan notifyData.hodlChan <- *invoices.NewSettleResolution( testResPreimage, testResCircuitKey, testAcceptHeight, + invoices.ResultSettled, ) ctx.waitForResult(true) @@ -174,7 +177,7 @@ func TestHtlcIncomingResolverExitCancelHodl(t *testing.T) { ctx.resolve() notifyData := <-ctx.registry.notifyChan notifyData.hodlChan <- *invoices.NewFailureResolution( - testResCircuitKey, testAcceptHeight, + testResCircuitKey, testAcceptHeight, invoices.ResultCanceled, ) ctx.waitForResult(false) diff --git a/htlcswitch/link.go b/htlcswitch/link.go index a1913381..596b13ee 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -1195,7 +1195,8 @@ func (l *channelLink) processHtlcResolution(resolution invoices.HtlcResolution, circuitKey := resolution.CircuitKey - // Determine required action for the resolution. + // 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) @@ -1205,10 +1206,12 @@ func (l *channelLink) processHtlcResolution(resolution invoices.HtlcResolution, ) } - l.log.Debugf("received cancel resolution 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. + // The htlc has failed so we cancel it with FailIncorrectDetails. This + // error covers invoice failures and hodl cancels (which return it to avoid + // leaking information). failure := lnwire.NewFailIncorrectDetails( htlc.pd.Amount, uint32(resolution.AcceptHeight), ) @@ -2844,10 +2847,10 @@ 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 } diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go index a65799c5..0ee0364c 100644 --- a/invoices/invoiceregistry.go +++ b/invoices/invoiceregistry.go @@ -48,27 +48,32 @@ type HtlcResolution 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) *HtlcResolution { + 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) *HtlcResolution { + acceptHeight int32, outcome ResolutionResult) *HtlcResolution { return &HtlcResolution{ Preimage: &preimage, CircuitKey: key, AcceptHeight: acceptHeight, + Outcome: outcome, } } @@ -347,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) @@ -596,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() @@ -674,9 +681,11 @@ func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash, return fmt.Errorf("htlc %v not found", key) } if htlc.State == channeldb.HtlcStateCanceled { - i.notifyHodlSubscribers( - *NewFailureResolution(key, int32(htlc.AcceptHeight)), + resolution := *NewFailureResolution( + key, int32(htlc.AcceptHeight), result, ) + + i.notifyHodlSubscribers(resolution) } return nil } @@ -765,7 +774,9 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, // If it isn't recorded, cancel htlc. if !ok { - return NewFailureResolution(circuitKey, currentHeight), nil + return NewFailureResolution( + circuitKey, currentHeight, result, + ), nil } // Determine accepted height of this htlc. If the htlc reached the @@ -776,7 +787,9 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, switch invoiceHtlc.State { case channeldb.HtlcStateCanceled: - return NewFailureResolution(circuitKey, acceptHeight), nil + return NewFailureResolution( + circuitKey, acceptHeight, result, + ), nil case channeldb.HtlcStateSettled: // Also settle any previously accepted htlcs. The invoice state @@ -787,17 +800,24 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash, continue } + // 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, + acceptHeight, result, ) i.notifyHodlSubscribers(resolution) } - return NewSettleResolution( - invoice.Terms.PaymentPreimage, circuitKey, 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 @@ -870,7 +890,7 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error { } resolution := *NewSettleResolution( - preimage, key, int32(htlc.AcceptHeight), + preimage, key, int32(htlc.AcceptHeight), ResultSettled, ) i.notifyHodlSubscribers(resolution) @@ -888,7 +908,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 { @@ -948,7 +969,9 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash, } i.notifyHodlSubscribers( - *NewFailureResolution(key, int32(htlc.AcceptHeight)), + *NewFailureResolution( + key, int32(htlc.AcceptHeight), ResultCanceled, + ), ) } i.notifyClients(payHash, invoice, channeldb.ContractCanceled) diff --git a/invoices/invoiceregistry_test.go b/invoices/invoiceregistry_test.go index 07e22823..27a63c25 100644 --- a/invoices/invoiceregistry_test.go +++ b/invoices/invoiceregistry_test.go @@ -81,16 +81,23 @@ func TestSettleInvoice(t *testing.T) { t.Fatalf("expected acceptHeight %v, but got %v", 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. @@ -130,6 +137,10 @@ func TestSettleInvoice(t *testing.T) { 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 @@ -144,6 +155,10 @@ func TestSettleInvoice(t *testing.T) { 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. @@ -157,6 +172,10 @@ func TestSettleInvoice(t *testing.T) { 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 // 0 and 1. @@ -287,6 +306,10 @@ func TestCancelInvoice(t *testing.T) { t.Fatalf("expected acceptHeight %v, but got %v", testCurrentHeight, resolution.AcceptHeight) } + if resolution.Outcome != ResultInvoiceAlreadyCanceled { + t.Fatalf("expected invoice already canceled, got: %v", + resolution.Outcome) + } } // TestSettleHoldInvoice tests settling of a hold invoice and related @@ -402,6 +425,10 @@ func TestSettleHoldInvoice(t *testing.T) { 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. @@ -429,6 +456,10 @@ func TestSettleHoldInvoice(t *testing.T) { t.Fatalf("expected acceptHeight %v, but got %v", 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 // single invoice subscribers. @@ -535,6 +566,10 @@ func TestCancelHoldInvoice(t *testing.T) { t.Fatalf("expected acceptHeight %v, but got %v", 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 @@ -559,8 +594,10 @@ func TestUnknownInvoice(t *testing.T) { } } -// 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) @@ -597,6 +634,10 @@ func TestSettleMpp(t *testing.T) { 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) @@ -625,9 +666,13 @@ func TestSettleMpp(t *testing.T) { 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) diff --git a/invoices/update.go b/invoices/update.go index 6cf58d0d..fcf7a8e1 100644 --- a/invoices/update.go +++ b/invoices/update.go @@ -49,6 +49,10 @@ const ( // 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 @@ -59,6 +63,10 @@ const ( // 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 From d2e395d5f2eb8db93d466c1f62b718fe063a29cf Mon Sep 17 00:00:00 2001 From: carla Date: Fri, 20 Dec 2019 12:25:08 +0200 Subject: [PATCH 21/25] multi: replace errInvoiceNotFound with resolution result This commit moves handling of invoice not found errors into NotifyExitHopHtlc and exposes a resolution result to the calling functions. The intention of this change is to make calling functions as naive of the invoice registry's mechanics as possible. When NotifyExitHopHtlc is called and an invoice is not found, calling functions can take action based on the HtlcResolution's InvoiceNotFound outcome rather than having to add a special error check on every call to handle the error. --- .../htlc_incoming_contest_resolver.go | 25 +++++++++++-------- contractcourt/htlc_incoming_resolver_test.go | 21 ++++++++++++---- htlcswitch/link.go | 16 +----------- invoices/invoiceregistry.go | 14 +++++++++-- invoices/invoiceregistry_test.go | 10 +++++--- invoices/update.go | 4 +++ 6 files changed, 54 insertions(+), 36 deletions(-) diff --git a/contractcourt/htlc_incoming_contest_resolver.go b/contractcourt/htlc_incoming_contest_resolver.go index 9d026cd0..98e52500 100644 --- a/contractcourt/htlc_incoming_contest_resolver.go +++ b/contractcourt/htlc_incoming_contest_resolver.go @@ -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 processHtlcResolution(*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) diff --git a/contractcourt/htlc_incoming_resolver_test.go b/contractcourt/htlc_incoming_resolver_test.go index cce6343b..773b22c0 100644 --- a/contractcourt/htlc_incoming_resolver_test.go +++ b/contractcourt/htlc_incoming_resolver_test.go @@ -35,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) @@ -49,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. @@ -66,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. @@ -82,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() diff --git a/htlcswitch/link.go b/htlcswitch/link.go index 596b13ee..005b2e3b 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -2823,21 +2823,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 } diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go index 0ee0364c..0b316324 100644 --- a/invoices/invoiceregistry.go +++ b/invoices/invoiceregistry.go @@ -758,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 { diff --git a/invoices/invoiceregistry_test.go b/invoices/invoiceregistry_test.go index 27a63c25..2750e942 100644 --- a/invoices/invoiceregistry_test.go +++ b/invoices/invoiceregistry_test.go @@ -585,12 +585,16 @@ 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) } } diff --git a/invoices/update.go b/invoices/update.go index fcf7a8e1..2d512dbe 100644 --- a/invoices/update.go +++ b/invoices/update.go @@ -81,6 +81,10 @@ const ( // 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. From 1d3bb5aed659feb42c745514e3869ff3114f5f5b Mon Sep 17 00:00:00 2001 From: carla Date: Fri, 20 Dec 2019 12:25:08 +0200 Subject: [PATCH 22/25] lnwire: add FailMPPTimeout message --- lnwire/onion_error.go | 27 +++++++++++++++++++++++++++ lnwire/onion_error_test.go | 1 + 2 files changed, 28 insertions(+) diff --git a/lnwire/onion_error.go b/lnwire/onion_error.go index 961373b9..c6235552 100644 --- a/lnwire/onion_error.go +++ b/lnwire/onion_error.go @@ -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 "" } @@ -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) } diff --git a/lnwire/onion_error_test.go b/lnwire/onion_error_test.go index 5dafcedd..3ec147d1 100644 --- a/lnwire/onion_error_test.go +++ b/lnwire/onion_error_test.go @@ -38,6 +38,7 @@ var onionFailures = []FailureMessage{ &FailUnknownNextPeer{}, &FailIncorrectPaymentAmount{}, &FailFinalExpiryTooSoon{}, + &FailMPPTimeout{}, NewFailIncorrectDetails(99, 100), NewInvalidOnionVersion(testOnionHash), From e0c86f1e7112484ea1dc36b078b81619b7dea5b7 Mon Sep 17 00:00:00 2001 From: carla Date: Fri, 20 Dec 2019 12:25:08 +0200 Subject: [PATCH 23/25] htlcswitch+invoices: fail mpp timeouts with FailMPPTimeout This commit adds a getResolutionFailure function which returns an appropriate wire failure based on the outcome of a htlc resolution. It also updates the MissionControlStore test to ensure that lnd can handle failures which occur due to mpp timeout. --- htlcswitch/link.go | 27 +++++++++++++++++++++------ routing/missioncontrol_store_test.go | 9 +++++++-- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/htlcswitch/link.go b/htlcswitch/link.go index 005b2e3b..0bba3f65 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -1209,12 +1209,8 @@ func (l *channelLink) processHtlcResolution(resolution invoices.HtlcResolution, l.log.Debugf("received cancel resolution for %v with outcome: %v", circuitKey, resolution.Outcome) - // The htlc has failed so we cancel it with FailIncorrectDetails. This - // error covers invoice failures and hodl cancels (which return it to avoid - // leaking information). - failure := lnwire.NewFailIncorrectDetails( - htlc.pd.Amount, uint32(resolution.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, @@ -1223,6 +1219,25 @@ func (l *channelLink) processHtlcResolution(resolution invoices.HtlcResolution, 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. diff --git a/routing/missioncontrol_store_test.go b/routing/missioncontrol_store_test.go index 9d070f4d..e44c5919 100644 --- a/routing/missioncontrol_store_test.go +++ b/routing/missioncontrol_store_test.go @@ -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 { From b6f546503a163af71cb50a2e8ac19e2badc28a96 Mon Sep 17 00:00:00 2001 From: carla Date: Fri, 20 Dec 2019 12:25:08 +0200 Subject: [PATCH 24/25] routerrpc: add MppTimeout code to failures --- lnrpc/routerrpc/router.pb.go | 268 ++++++++++++++++--------------- lnrpc/routerrpc/router.proto | 1 + lnrpc/routerrpc/router_server.go | 3 + 3 files changed, 140 insertions(+), 132 deletions(-) diff --git a/lnrpc/routerrpc/router.pb.go b/lnrpc/routerrpc/router.pb.go index b80c3f2c..1be1423e 100644 --- a/lnrpc/routerrpc/router.pb.go +++ b/lnrpc/routerrpc/router.pb.go @@ -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, } @@ -1526,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. diff --git a/lnrpc/routerrpc/router.proto b/lnrpc/routerrpc/router.proto index aa0d9676..034ee2c5 100644 --- a/lnrpc/routerrpc/router.proto +++ b/lnrpc/routerrpc/router.proto @@ -256,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. diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index c872afe3..ba23a079 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -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 From 25a877373696c4c127af7e0e6986ce39376d4944 Mon Sep 17 00:00:00 2001 From: carla Date: Fri, 20 Dec 2019 12:25:08 +0200 Subject: [PATCH 25/25] routing: interpret mpp timeout failues This commit adds success mission control results for all hops along the route in a mpp timeout and takes no action for the final hop along the route. This is a temporary measure to prevent the default logic from penalizing the final node while we decide how to penalize mpp timeouts. --- routing/result_interpretation.go | 16 +++++++++++++ routing/result_interpretation_test.go | 33 +++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/routing/result_interpretation.go b/routing/result_interpretation.go index 7b3d539a..fae2835a 100644 --- a/routing/result_interpretation.go +++ b/routing/result_interpretation.go @@ -216,6 +216,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 diff --git a/routing/result_interpretation_test.go b/routing/result_interpretation_test.go index 42f3ec87..a5ed7989 100644 --- a/routing/result_interpretation_test.go +++ b/routing/result_interpretation_test.go @@ -272,6 +272,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