-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Light-152 multihop routing #37
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,8 @@ import ( | |
"golang.org/x/net/context" | ||
|
||
"github.com/BitfuryLightning/tools/rt/visualizer" | ||
"strconv" | ||
"github.com/lightningnetwork/lnd/lnwire" | ||
) | ||
|
||
// TODO(roasbeef): cli logic for supporting both positional and unix style | ||
|
@@ -866,3 +868,228 @@ func printRTAsJSON(r *rt.RoutingTable) { | |
} | ||
printRespJson(channels) | ||
} | ||
|
||
var SendMultihopPayment = cli.Command{ | ||
Name: "sendmultihop", | ||
Usage: "send a multihop payment", | ||
Action: sendMultihopPayment, | ||
} | ||
|
||
func sendMultihopPayment(ctx *cli.Context) error { | ||
if len(ctx.Args()) < 2 { | ||
fmt.Println("Usage: amount ID1 ID2 ...") | ||
return nil | ||
} | ||
amount, err := strconv.ParseInt(ctx.Args()[0], 10, 64) | ||
if amount <= 0 { | ||
return fmt.Errorf("Amount should be positive. Got %v", amount) | ||
} | ||
if err != nil { | ||
return err | ||
} | ||
path := make([]string, 0) | ||
for i:=1; i<len(ctx.Args()); i++ { | ||
p, err := hex.DecodeString(ctx.Args()[i]) | ||
if err != nil { | ||
return err | ||
} | ||
if len(p) != 32 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the latest version of |
||
return fmt.Errorf("Size of LightningID should be 32 bytes, got %v", len(p)) | ||
} | ||
path = append(path, string(p)) | ||
} | ||
ctxb := context.Background() | ||
client := getClient(ctx) | ||
req := &lnrpc.MultihopPaymentRequest{ | ||
Amount: amount, | ||
LightningIDs: path, | ||
} | ||
_, err = client.SendMultihopPayment(ctxb, req) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
|
||
var FindPathCommand = cli.Command{ | ||
Name: "findpath", | ||
Description: "finds path from node to some other node", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the description should be updated to be a bit more explicit. How about something along the lines of:
|
||
Usage: "findpath --destination <LightningID> --maxpath <MaxPath> --validate --amount <Satoshis>", | ||
Flags: []cli.Flag{ | ||
cli.StringFlag{ | ||
Name: "destination", | ||
Usage: "lightningID of destination", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In order to be in line with the latest version of master, a compressed public key should be expected instead. |
||
}, | ||
cli.IntFlag{ | ||
Name: "maxpath", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To make the arg a bit more explicit, it should be extended a bit to be named |
||
Usage: "maximal number of paths to find", | ||
Value: 10, | ||
}, | ||
cli.BoolFlag{ | ||
Name: "validate", | ||
Usage: "send message to know if paths are valid", | ||
}, | ||
cli.Int64Flag{ | ||
Name: "amount", | ||
Usage: "amount of money which could be send through path", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about:
|
||
Value: 10, | ||
}, | ||
cli.IntFlag{ | ||
Name: "timeout", | ||
Usage: "timeout which is used during path validation. In seconds", | ||
Value: 1, | ||
}, | ||
}, | ||
Action: findPath, | ||
} | ||
|
||
func validationStatusToStr(st lnwire.AllowHTLCStatus) string { | ||
switch st { | ||
case lnwire.AllowHTLCStatus_Allow: | ||
return "ALLOW" | ||
case lnwire.AllowHTLCStatus_Decline: | ||
return "DECLINE" | ||
case lnwire.AllowHTLCStatus_Timeout: | ||
return "TIMEOUT" | ||
default: | ||
return "UNKNOWN" | ||
} | ||
} | ||
|
||
func findPath(ctx *cli.Context) error { | ||
// TODO(roasbeef): add deadline to context | ||
ctxb := context.Background() | ||
client := getClient(ctx) | ||
|
||
destID, err := hex.DecodeString(ctx.String("destination")) | ||
if err != nil{ | ||
return err | ||
} | ||
if len(destID) != 32 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The length should be checked to be exactly 33 as compressed public keys are now expected. Taking it a step further, we can attempt to parse the byte slice as a public key on our desired curve client-side before proceeding. |
||
return fmt.Errorf("Incorrect size of LightningID, got %v, want %v", len(destID), 32) | ||
} | ||
req := &lnrpc.FindPathRequest{ | ||
TargetID: string(destID), | ||
NumberOfPaths: int32(ctx.Int("maxpath")), | ||
Validate: ctx.Bool("validate"), | ||
Amount: ctx.Int64("amount"), | ||
Timeout: int32(ctx.Int("timeout")), | ||
} | ||
|
||
resp, err := client.FindPath(ctxb, req) | ||
if err != nil { | ||
return err | ||
} | ||
// We need to convert byte-strings into hex-encoded string | ||
type PathResult struct { | ||
Path []string `json:"path"` | ||
ValidationStatus string `json:"validation_status"` | ||
} | ||
var convResp struct { | ||
Paths []PathResult `json:"paths"` | ||
} | ||
convResp.Paths = make([]PathResult, len(resp.Paths)) | ||
for i := 0; i< len(resp.Paths); i++{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Go's built-in |
||
convPath := make([]string, len(resp.Paths[i].Path)) | ||
for j:=0; j<len(resp.Paths[i].Path); j++ { | ||
convPath[j] = hex.EncodeToString([]byte(resp.Paths[i].Path[j])) | ||
} | ||
convResp.Paths[i].Path = convPath | ||
convResp.Paths[i].ValidationStatus = validationStatusToStr(lnwire.AllowHTLCStatus(resp.Paths[i].ValidationStatus)) | ||
} | ||
printRespJson(convResp) | ||
return nil | ||
} | ||
|
||
var PayMultihopCommand = cli.Command{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This command seems a bit redundant as the PR attempts to introduce the Generally, as this is meant to a simple CLI interface, most of the logic should be pushed into the server, with the CLI commands simply handling parsing, basic input validation, and displaying the final result to |
||
Name: "paymultihop", | ||
Description: "finds path from node to some other node and send payment to it", | ||
Usage: "paymultihop --destination <LightningID> --amount <Amount in Satoshi> --maxpath <MaxPath> --timeout <Path timeout>", | ||
Flags: []cli.Flag{ | ||
cli.StringFlag{ | ||
Name: "destination", | ||
Usage: "lightningID of destination", | ||
}, | ||
cli.IntFlag{ | ||
Name: "maxpath", | ||
Usage: "maximal number of paths to find", | ||
Value: 10, | ||
}, | ||
cli.Int64Flag{ | ||
Name: "amount", | ||
Usage: "amount of money which could be send through path", | ||
Value: 10, | ||
}, | ||
cli.IntFlag{ | ||
Name: "timeout", | ||
Usage: "timeout which is used during path validation. In seconds", | ||
Value: 1, | ||
}, | ||
}, | ||
Action: payMultihop, | ||
} | ||
|
||
func payMultihop(ctx *cli.Context) error { | ||
// TODO(roasbeef): add deadline to context | ||
ctxb := context.Background() | ||
client := getClient(ctx) | ||
|
||
amount := ctx.Int64("amount") | ||
destID, err := hex.DecodeString(ctx.String("destination")) | ||
if err != nil{ | ||
return err | ||
} | ||
if len(destID) != 32 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should check for a length of 33 bytes. |
||
return fmt.Errorf("Incorrect size of LightningID, got %v, want %v", len(destID), 32) | ||
} | ||
req := &lnrpc.FindPathRequest{ | ||
TargetID: string(destID), | ||
NumberOfPaths: int32(ctx.Int("maxpath")), | ||
Validate: true, | ||
Amount: amount, | ||
Timeout: int32(ctx.Int("timeout")), | ||
} | ||
|
||
resp, err := client.FindPath(ctxb, req) | ||
if err != nil { | ||
return err | ||
} | ||
fmt.Printf("Found %v paths\n", len(resp.Paths)) | ||
iPathToUse := -1 | ||
var convPathToUse []string | ||
// We need to convert byte-strings into hex-encoded string | ||
for i := 0; i< len(resp.Paths); i++{ | ||
convPath := make([]string, len(resp.Paths[i].Path)) | ||
for j:=0; j<len(resp.Paths[i].Path); j++ { | ||
convPath[j] = hex.EncodeToString([]byte(resp.Paths[i].Path[j])) | ||
} | ||
status := lnwire.AllowHTLCStatus(resp.Paths[i].ValidationStatus) | ||
fmt.Printf("%v: %v\n", validationStatusToStr(status), convPath) | ||
// We need first valid path because it is shortest | ||
if iPathToUse == -1 && status == lnwire.AllowHTLCStatus_Allow{ | ||
iPathToUse = i | ||
convPathToUse = convPath | ||
} | ||
} | ||
if iPathToUse == -1 { | ||
fmt.Println("We didn't find any suitable path") | ||
} else { | ||
pathToUse := resp.Paths[iPathToUse].Path | ||
fmt.Println("Suitable path found.") | ||
fmt.Printf("Will make payment %v using path %v\n", amount, convPathToUse) | ||
req := &lnrpc.MultihopPaymentRequest{ | ||
Amount: amount, | ||
// We need to ignore first node(because it is ourselves) | ||
LightningIDs: pathToUse[1:], | ||
} | ||
_, err := client.SendMultihopPayment(ctxb, req) | ||
if err != nil { | ||
return err | ||
} | ||
fmt.Println("Payment send") | ||
|
||
} | ||
return nil | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
package: github.com/lightningnetwork/lnd | ||
import: | ||
- package: github.com/BitfuryLightning/tools | ||
version: ^0.0.2 | ||
version: ^0.0.3 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the latest version of master, the external routing tools are now pinned against a particular commit rather the a version. |
||
subpackages: | ||
- routing | ||
- rt | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think there should be distinct cli nor RPC commands for sending multi-hop payments vs single hop payments. At the time this PR was being put together the onion routing stuff wasn't in place, but it is now so the caller doesn't require any knowledge of the full route, only the final destination.
However, I can see how it might be useful to give the caller full control over the final path which the payment is execute over. So how about we merge this behavior into the existing
SendPayment
command with an additional argument which provides full "path selection".